diff --git a/app/src/main/java/com/casic/detector/common/extensions/Double.kt b/app/src/main/java/com/casic/detector/common/extensions/Double.kt new file mode 100644 index 0000000..a91efd0 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/extensions/Double.kt @@ -0,0 +1,21 @@ +package com.casic.detector.common.extensions + +/** + * 经纬度转度分格式,ddmm.mmmm + * + * longitude: 116.25980833333332 + * latitude: 39.912785 + * + * 转换后的纬度 = dd + (mm.mmmm/60) + * 转换后的经度 = ddd + (mm.mmmm/60) + * */ +fun Double.convert(): String { + //整数部分,即度数 + val degree = this.toInt() + + //小数部分 + val temp = this - degree + val mm = "%.4f".format(temp * 60) + + return "$degree$mm" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/extensions/Double.kt b/app/src/main/java/com/casic/detector/common/extensions/Double.kt new file mode 100644 index 0000000..a91efd0 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/extensions/Double.kt @@ -0,0 +1,21 @@ +package com.casic.detector.common.extensions + +/** + * 经纬度转度分格式,ddmm.mmmm + * + * longitude: 116.25980833333332 + * latitude: 39.912785 + * + * 转换后的纬度 = dd + (mm.mmmm/60) + * 转换后的经度 = ddd + (mm.mmmm/60) + * */ +fun Double.convert(): String { + //整数部分,即度数 + val degree = this.toInt() + + //小数部分 + val temp = this - degree + val mm = "%.4f".format(temp * 60) + + return "$degree$mm" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/extensions/Location.kt b/app/src/main/java/com/casic/detector/common/extensions/Location.kt new file mode 100644 index 0000000..70392b8 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/extensions/Location.kt @@ -0,0 +1,67 @@ +package com.casic.detector.common.extensions + +import android.location.Location +import com.casic.detector.common.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.appendZero +import com.pengxh.kt.lite.utils.SaveKeyValues +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale +import java.util.TimeZone + +/** + * 组装千寻报文 + *

+ * $GPGGA,000001,3112.518576,N,12127.901251,E,1,8,1,0,M,-32,M,3,0*4B + * $GPGGA,092204.999,4250.5589,N,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F + * + * 字段0:$GPGGA,语句ID,表明该语句为Global Positioning System Fix Data(GGA)GPS定位信息 + * 字段1:UTC 时间,hhmmss.sss,时分秒格式 + * 字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0) + * 字段3:纬度N(北纬)或S(南纬) + * 字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0) + * 字段5:经度E(东经)或W(西经) + * 字段6:GPS状态,0=未定位,1=单点定位,2=伪距/SBAS,3=无效PPS,4=RTK固定,5=RTK浮动,6=正在估算 + * 7=手动启动基准站,8=RTK宽巷解,9=伪距(诺瓦泰615) + * 字段7:正在使用的卫星数量(00 - 12)(前导位数不足则补0) + * 字段8:HDOP水平精度因子(0.5 - 99.9) + * 字段9:海拔高度(-9999.9 - 99999.9) + * 字段10:地球椭球面相对大地水准面的高度 + * 字段11:差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空) + * 字段12:差分站ID号0000 - 1023(前导位数不足则补0,如果不是差分定位将为空) + * 字段13:校验值 + * */ +fun Location.convertToGPGGA(): String { + /** + * 获取UTC时间 + * */ + val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + val sdf = SimpleDateFormat("HHmmss", Locale.CHINA) + sdf.timeZone = TimeZone.getTimeZone("UTC") + // 获取时间(不包含毫秒) + val timeStr = sdf.format(calendar.time) + // 获取毫秒部分 + val millis = calendar[Calendar.MILLISECOND] + // 拼接毫秒部分,注意要转换为字符串并保留三位小数 + val millisStr = "%03d".format(millis) + val utc = "$timeStr.$millisStr" + + val count = SaveKeyValues.getValue(LocaleConstant.SATELLITE_EFFECTIVE_COUNT, 0) as Int + val satelliteCount = if (count >= 12) { + 12 + } else { + count.appendZero() + } + + val formatAltitude = "%.1f".format(altitude) + + //TODO HDOP水平精度因子(0.5 - 99.9)不确定 + val hdop = 1.0 + + //拼接不带校验位的参数 + val temp = + "\$GPGGA,$utc,${latitude.convert()},N,${longitude.convert()},E,1,$satelliteCount,$hdop,$formatAltitude,M,,,,0000*" + + //拼接校验位 + return "$temp${temp.calculateCheckDigit()}\r\n" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/extensions/Double.kt b/app/src/main/java/com/casic/detector/common/extensions/Double.kt new file mode 100644 index 0000000..a91efd0 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/extensions/Double.kt @@ -0,0 +1,21 @@ +package com.casic.detector.common.extensions + +/** + * 经纬度转度分格式,ddmm.mmmm + * + * longitude: 116.25980833333332 + * latitude: 39.912785 + * + * 转换后的纬度 = dd + (mm.mmmm/60) + * 转换后的经度 = ddd + (mm.mmmm/60) + * */ +fun Double.convert(): String { + //整数部分,即度数 + val degree = this.toInt() + + //小数部分 + val temp = this - degree + val mm = "%.4f".format(temp * 60) + + return "$degree$mm" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/extensions/Location.kt b/app/src/main/java/com/casic/detector/common/extensions/Location.kt new file mode 100644 index 0000000..70392b8 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/extensions/Location.kt @@ -0,0 +1,67 @@ +package com.casic.detector.common.extensions + +import android.location.Location +import com.casic.detector.common.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.appendZero +import com.pengxh.kt.lite.utils.SaveKeyValues +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale +import java.util.TimeZone + +/** + * 组装千寻报文 + *

+ * $GPGGA,000001,3112.518576,N,12127.901251,E,1,8,1,0,M,-32,M,3,0*4B + * $GPGGA,092204.999,4250.5589,N,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F + * + * 字段0:$GPGGA,语句ID,表明该语句为Global Positioning System Fix Data(GGA)GPS定位信息 + * 字段1:UTC 时间,hhmmss.sss,时分秒格式 + * 字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0) + * 字段3:纬度N(北纬)或S(南纬) + * 字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0) + * 字段5:经度E(东经)或W(西经) + * 字段6:GPS状态,0=未定位,1=单点定位,2=伪距/SBAS,3=无效PPS,4=RTK固定,5=RTK浮动,6=正在估算 + * 7=手动启动基准站,8=RTK宽巷解,9=伪距(诺瓦泰615) + * 字段7:正在使用的卫星数量(00 - 12)(前导位数不足则补0) + * 字段8:HDOP水平精度因子(0.5 - 99.9) + * 字段9:海拔高度(-9999.9 - 99999.9) + * 字段10:地球椭球面相对大地水准面的高度 + * 字段11:差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空) + * 字段12:差分站ID号0000 - 1023(前导位数不足则补0,如果不是差分定位将为空) + * 字段13:校验值 + * */ +fun Location.convertToGPGGA(): String { + /** + * 获取UTC时间 + * */ + val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + val sdf = SimpleDateFormat("HHmmss", Locale.CHINA) + sdf.timeZone = TimeZone.getTimeZone("UTC") + // 获取时间(不包含毫秒) + val timeStr = sdf.format(calendar.time) + // 获取毫秒部分 + val millis = calendar[Calendar.MILLISECOND] + // 拼接毫秒部分,注意要转换为字符串并保留三位小数 + val millisStr = "%03d".format(millis) + val utc = "$timeStr.$millisStr" + + val count = SaveKeyValues.getValue(LocaleConstant.SATELLITE_EFFECTIVE_COUNT, 0) as Int + val satelliteCount = if (count >= 12) { + 12 + } else { + count.appendZero() + } + + val formatAltitude = "%.1f".format(altitude) + + //TODO HDOP水平精度因子(0.5 - 99.9)不确定 + val hdop = 1.0 + + //拼接不带校验位的参数 + val temp = + "\$GPGGA,$utc,${latitude.convert()},N,${longitude.convert()},E,1,$satelliteCount,$hdop,$formatAltitude,M,,,,0000*" + + //拼接校验位 + return "$temp${temp.calculateCheckDigit()}\r\n" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt b/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt new file mode 100644 index 0000000..d9156da --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt @@ -0,0 +1,18 @@ +package com.casic.detector.common.utils.tcp + +enum class ConnectState { + /** + * 连接成功 + */ + SUCCESS, + + /** + * 关闭连接 + */ + CLOSED, + + /** + * 连接失败 + */ + ERROR +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/extensions/Double.kt b/app/src/main/java/com/casic/detector/common/extensions/Double.kt new file mode 100644 index 0000000..a91efd0 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/extensions/Double.kt @@ -0,0 +1,21 @@ +package com.casic.detector.common.extensions + +/** + * 经纬度转度分格式,ddmm.mmmm + * + * longitude: 116.25980833333332 + * latitude: 39.912785 + * + * 转换后的纬度 = dd + (mm.mmmm/60) + * 转换后的经度 = ddd + (mm.mmmm/60) + * */ +fun Double.convert(): String { + //整数部分,即度数 + val degree = this.toInt() + + //小数部分 + val temp = this - degree + val mm = "%.4f".format(temp * 60) + + return "$degree$mm" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/extensions/Location.kt b/app/src/main/java/com/casic/detector/common/extensions/Location.kt new file mode 100644 index 0000000..70392b8 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/extensions/Location.kt @@ -0,0 +1,67 @@ +package com.casic.detector.common.extensions + +import android.location.Location +import com.casic.detector.common.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.appendZero +import com.pengxh.kt.lite.utils.SaveKeyValues +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale +import java.util.TimeZone + +/** + * 组装千寻报文 + *

+ * $GPGGA,000001,3112.518576,N,12127.901251,E,1,8,1,0,M,-32,M,3,0*4B + * $GPGGA,092204.999,4250.5589,N,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F + * + * 字段0:$GPGGA,语句ID,表明该语句为Global Positioning System Fix Data(GGA)GPS定位信息 + * 字段1:UTC 时间,hhmmss.sss,时分秒格式 + * 字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0) + * 字段3:纬度N(北纬)或S(南纬) + * 字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0) + * 字段5:经度E(东经)或W(西经) + * 字段6:GPS状态,0=未定位,1=单点定位,2=伪距/SBAS,3=无效PPS,4=RTK固定,5=RTK浮动,6=正在估算 + * 7=手动启动基准站,8=RTK宽巷解,9=伪距(诺瓦泰615) + * 字段7:正在使用的卫星数量(00 - 12)(前导位数不足则补0) + * 字段8:HDOP水平精度因子(0.5 - 99.9) + * 字段9:海拔高度(-9999.9 - 99999.9) + * 字段10:地球椭球面相对大地水准面的高度 + * 字段11:差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空) + * 字段12:差分站ID号0000 - 1023(前导位数不足则补0,如果不是差分定位将为空) + * 字段13:校验值 + * */ +fun Location.convertToGPGGA(): String { + /** + * 获取UTC时间 + * */ + val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + val sdf = SimpleDateFormat("HHmmss", Locale.CHINA) + sdf.timeZone = TimeZone.getTimeZone("UTC") + // 获取时间(不包含毫秒) + val timeStr = sdf.format(calendar.time) + // 获取毫秒部分 + val millis = calendar[Calendar.MILLISECOND] + // 拼接毫秒部分,注意要转换为字符串并保留三位小数 + val millisStr = "%03d".format(millis) + val utc = "$timeStr.$millisStr" + + val count = SaveKeyValues.getValue(LocaleConstant.SATELLITE_EFFECTIVE_COUNT, 0) as Int + val satelliteCount = if (count >= 12) { + 12 + } else { + count.appendZero() + } + + val formatAltitude = "%.1f".format(altitude) + + //TODO HDOP水平精度因子(0.5 - 99.9)不确定 + val hdop = 1.0 + + //拼接不带校验位的参数 + val temp = + "\$GPGGA,$utc,${latitude.convert()},N,${longitude.convert()},E,1,$satelliteCount,$hdop,$formatAltitude,M,,,,0000*" + + //拼接校验位 + return "$temp${temp.calculateCheckDigit()}\r\n" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt b/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt new file mode 100644 index 0000000..d9156da --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt @@ -0,0 +1,18 @@ +package com.casic.detector.common.utils.tcp + +enum class ConnectState { + /** + * 连接成功 + */ + SUCCESS, + + /** + * 关闭连接 + */ + CLOSED, + + /** + * 连接失败 + */ + ERROR +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/utils/tcp/OnSocketConnectListener.kt b/app/src/main/java/com/casic/detector/common/utils/tcp/OnSocketConnectListener.kt new file mode 100644 index 0000000..b452461 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/utils/tcp/OnSocketConnectListener.kt @@ -0,0 +1,13 @@ +package com.casic.detector.common.utils.tcp + +interface OnSocketConnectListener { + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceConnectStatusChanged(status: ConnectState) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/extensions/Double.kt b/app/src/main/java/com/casic/detector/common/extensions/Double.kt new file mode 100644 index 0000000..a91efd0 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/extensions/Double.kt @@ -0,0 +1,21 @@ +package com.casic.detector.common.extensions + +/** + * 经纬度转度分格式,ddmm.mmmm + * + * longitude: 116.25980833333332 + * latitude: 39.912785 + * + * 转换后的纬度 = dd + (mm.mmmm/60) + * 转换后的经度 = ddd + (mm.mmmm/60) + * */ +fun Double.convert(): String { + //整数部分,即度数 + val degree = this.toInt() + + //小数部分 + val temp = this - degree + val mm = "%.4f".format(temp * 60) + + return "$degree$mm" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/extensions/Location.kt b/app/src/main/java/com/casic/detector/common/extensions/Location.kt new file mode 100644 index 0000000..70392b8 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/extensions/Location.kt @@ -0,0 +1,67 @@ +package com.casic.detector.common.extensions + +import android.location.Location +import com.casic.detector.common.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.appendZero +import com.pengxh.kt.lite.utils.SaveKeyValues +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale +import java.util.TimeZone + +/** + * 组装千寻报文 + *

+ * $GPGGA,000001,3112.518576,N,12127.901251,E,1,8,1,0,M,-32,M,3,0*4B + * $GPGGA,092204.999,4250.5589,N,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F + * + * 字段0:$GPGGA,语句ID,表明该语句为Global Positioning System Fix Data(GGA)GPS定位信息 + * 字段1:UTC 时间,hhmmss.sss,时分秒格式 + * 字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0) + * 字段3:纬度N(北纬)或S(南纬) + * 字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0) + * 字段5:经度E(东经)或W(西经) + * 字段6:GPS状态,0=未定位,1=单点定位,2=伪距/SBAS,3=无效PPS,4=RTK固定,5=RTK浮动,6=正在估算 + * 7=手动启动基准站,8=RTK宽巷解,9=伪距(诺瓦泰615) + * 字段7:正在使用的卫星数量(00 - 12)(前导位数不足则补0) + * 字段8:HDOP水平精度因子(0.5 - 99.9) + * 字段9:海拔高度(-9999.9 - 99999.9) + * 字段10:地球椭球面相对大地水准面的高度 + * 字段11:差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空) + * 字段12:差分站ID号0000 - 1023(前导位数不足则补0,如果不是差分定位将为空) + * 字段13:校验值 + * */ +fun Location.convertToGPGGA(): String { + /** + * 获取UTC时间 + * */ + val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + val sdf = SimpleDateFormat("HHmmss", Locale.CHINA) + sdf.timeZone = TimeZone.getTimeZone("UTC") + // 获取时间(不包含毫秒) + val timeStr = sdf.format(calendar.time) + // 获取毫秒部分 + val millis = calendar[Calendar.MILLISECOND] + // 拼接毫秒部分,注意要转换为字符串并保留三位小数 + val millisStr = "%03d".format(millis) + val utc = "$timeStr.$millisStr" + + val count = SaveKeyValues.getValue(LocaleConstant.SATELLITE_EFFECTIVE_COUNT, 0) as Int + val satelliteCount = if (count >= 12) { + 12 + } else { + count.appendZero() + } + + val formatAltitude = "%.1f".format(altitude) + + //TODO HDOP水平精度因子(0.5 - 99.9)不确定 + val hdop = 1.0 + + //拼接不带校验位的参数 + val temp = + "\$GPGGA,$utc,${latitude.convert()},N,${longitude.convert()},E,1,$satelliteCount,$hdop,$formatAltitude,M,,,,0000*" + + //拼接校验位 + return "$temp${temp.calculateCheckDigit()}\r\n" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt b/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt new file mode 100644 index 0000000..d9156da --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt @@ -0,0 +1,18 @@ +package com.casic.detector.common.utils.tcp + +enum class ConnectState { + /** + * 连接成功 + */ + SUCCESS, + + /** + * 关闭连接 + */ + CLOSED, + + /** + * 连接失败 + */ + ERROR +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/utils/tcp/OnSocketConnectListener.kt b/app/src/main/java/com/casic/detector/common/utils/tcp/OnSocketConnectListener.kt new file mode 100644 index 0000000..b452461 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/utils/tcp/OnSocketConnectListener.kt @@ -0,0 +1,13 @@ +package com.casic.detector.common.utils.tcp + +interface OnSocketConnectListener { + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceConnectStatusChanged(status: ConnectState) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/utils/tcp/SocketChannelHandler.kt b/app/src/main/java/com/casic/detector/common/utils/tcp/SocketChannelHandler.kt new file mode 100644 index 0000000..1a5a99f --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/utils/tcp/SocketChannelHandler.kt @@ -0,0 +1,72 @@ +package com.casic.detector.common.utils.tcp + +import android.os.Looper +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.lifecycleScope +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class SocketChannelHandler(private val listener: OnSocketConnectListener) : + SimpleChannelInboundHandler(), LifecycleOwner { + + private fun isOnMainThread(): Boolean { + return Looper.getMainLooper().thread == Thread.currentThread() + } + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + if (isOnMainThread()) { + listener.onServiceConnectStatusChanged(ConnectState.SUCCESS) + } else { + lifecycleScope.launch(Dispatchers.Main) { + listener.onServiceConnectStatusChanged(ConnectState.SUCCESS) + } + } + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + if (isOnMainThread()) { + listener.onServiceConnectStatusChanged(ConnectState.CLOSED) + } else { + lifecycleScope.launch(Dispatchers.Main) { + listener.onServiceConnectStatusChanged(ConnectState.CLOSED) + } + } + } + + override fun channelRead0(channelHandlerContext: ChannelHandlerContext, bytes: ByteArray?) { + if (bytes == null) { + return + } + if (isOnMainThread()) { + listener.onMessageResponse(bytes) + } else { + lifecycleScope.launch(Dispatchers.Main) { + listener.onMessageResponse(bytes) + } + } + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + ctx.close() + if (isOnMainThread()) { + listener.onServiceConnectStatusChanged(ConnectState.ERROR) + } else { + lifecycleScope.launch(Dispatchers.Main) { + listener.onServiceConnectStatusChanged(ConnectState.ERROR) + } + } + } + + private val registry = LifecycleRegistry(this) + + override fun getLifecycle(): Lifecycle { + return registry + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/extensions/Double.kt b/app/src/main/java/com/casic/detector/common/extensions/Double.kt new file mode 100644 index 0000000..a91efd0 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/extensions/Double.kt @@ -0,0 +1,21 @@ +package com.casic.detector.common.extensions + +/** + * 经纬度转度分格式,ddmm.mmmm + * + * longitude: 116.25980833333332 + * latitude: 39.912785 + * + * 转换后的纬度 = dd + (mm.mmmm/60) + * 转换后的经度 = ddd + (mm.mmmm/60) + * */ +fun Double.convert(): String { + //整数部分,即度数 + val degree = this.toInt() + + //小数部分 + val temp = this - degree + val mm = "%.4f".format(temp * 60) + + return "$degree$mm" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/extensions/Location.kt b/app/src/main/java/com/casic/detector/common/extensions/Location.kt new file mode 100644 index 0000000..70392b8 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/extensions/Location.kt @@ -0,0 +1,67 @@ +package com.casic.detector.common.extensions + +import android.location.Location +import com.casic.detector.common.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.appendZero +import com.pengxh.kt.lite.utils.SaveKeyValues +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale +import java.util.TimeZone + +/** + * 组装千寻报文 + *

+ * $GPGGA,000001,3112.518576,N,12127.901251,E,1,8,1,0,M,-32,M,3,0*4B + * $GPGGA,092204.999,4250.5589,N,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F + * + * 字段0:$GPGGA,语句ID,表明该语句为Global Positioning System Fix Data(GGA)GPS定位信息 + * 字段1:UTC 时间,hhmmss.sss,时分秒格式 + * 字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0) + * 字段3:纬度N(北纬)或S(南纬) + * 字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0) + * 字段5:经度E(东经)或W(西经) + * 字段6:GPS状态,0=未定位,1=单点定位,2=伪距/SBAS,3=无效PPS,4=RTK固定,5=RTK浮动,6=正在估算 + * 7=手动启动基准站,8=RTK宽巷解,9=伪距(诺瓦泰615) + * 字段7:正在使用的卫星数量(00 - 12)(前导位数不足则补0) + * 字段8:HDOP水平精度因子(0.5 - 99.9) + * 字段9:海拔高度(-9999.9 - 99999.9) + * 字段10:地球椭球面相对大地水准面的高度 + * 字段11:差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空) + * 字段12:差分站ID号0000 - 1023(前导位数不足则补0,如果不是差分定位将为空) + * 字段13:校验值 + * */ +fun Location.convertToGPGGA(): String { + /** + * 获取UTC时间 + * */ + val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + val sdf = SimpleDateFormat("HHmmss", Locale.CHINA) + sdf.timeZone = TimeZone.getTimeZone("UTC") + // 获取时间(不包含毫秒) + val timeStr = sdf.format(calendar.time) + // 获取毫秒部分 + val millis = calendar[Calendar.MILLISECOND] + // 拼接毫秒部分,注意要转换为字符串并保留三位小数 + val millisStr = "%03d".format(millis) + val utc = "$timeStr.$millisStr" + + val count = SaveKeyValues.getValue(LocaleConstant.SATELLITE_EFFECTIVE_COUNT, 0) as Int + val satelliteCount = if (count >= 12) { + 12 + } else { + count.appendZero() + } + + val formatAltitude = "%.1f".format(altitude) + + //TODO HDOP水平精度因子(0.5 - 99.9)不确定 + val hdop = 1.0 + + //拼接不带校验位的参数 + val temp = + "\$GPGGA,$utc,${latitude.convert()},N,${longitude.convert()},E,1,$satelliteCount,$hdop,$formatAltitude,M,,,,0000*" + + //拼接校验位 + return "$temp${temp.calculateCheckDigit()}\r\n" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt b/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt new file mode 100644 index 0000000..d9156da --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/utils/tcp/ConnectState.kt @@ -0,0 +1,18 @@ +package com.casic.detector.common.utils.tcp + +enum class ConnectState { + /** + * 连接成功 + */ + SUCCESS, + + /** + * 关闭连接 + */ + CLOSED, + + /** + * 连接失败 + */ + ERROR +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/utils/tcp/OnSocketConnectListener.kt b/app/src/main/java/com/casic/detector/common/utils/tcp/OnSocketConnectListener.kt new file mode 100644 index 0000000..b452461 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/utils/tcp/OnSocketConnectListener.kt @@ -0,0 +1,13 @@ +package com.casic.detector.common.utils.tcp + +interface OnSocketConnectListener { + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceConnectStatusChanged(status: ConnectState) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/utils/tcp/SocketChannelHandler.kt b/app/src/main/java/com/casic/detector/common/utils/tcp/SocketChannelHandler.kt new file mode 100644 index 0000000..1a5a99f --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/utils/tcp/SocketChannelHandler.kt @@ -0,0 +1,72 @@ +package com.casic.detector.common.utils.tcp + +import android.os.Looper +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.lifecycleScope +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class SocketChannelHandler(private val listener: OnSocketConnectListener) : + SimpleChannelInboundHandler(), LifecycleOwner { + + private fun isOnMainThread(): Boolean { + return Looper.getMainLooper().thread == Thread.currentThread() + } + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + if (isOnMainThread()) { + listener.onServiceConnectStatusChanged(ConnectState.SUCCESS) + } else { + lifecycleScope.launch(Dispatchers.Main) { + listener.onServiceConnectStatusChanged(ConnectState.SUCCESS) + } + } + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + if (isOnMainThread()) { + listener.onServiceConnectStatusChanged(ConnectState.CLOSED) + } else { + lifecycleScope.launch(Dispatchers.Main) { + listener.onServiceConnectStatusChanged(ConnectState.CLOSED) + } + } + } + + override fun channelRead0(channelHandlerContext: ChannelHandlerContext, bytes: ByteArray?) { + if (bytes == null) { + return + } + if (isOnMainThread()) { + listener.onMessageResponse(bytes) + } else { + lifecycleScope.launch(Dispatchers.Main) { + listener.onMessageResponse(bytes) + } + } + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + ctx.close() + if (isOnMainThread()) { + listener.onServiceConnectStatusChanged(ConnectState.ERROR) + } else { + lifecycleScope.launch(Dispatchers.Main) { + listener.onServiceConnectStatusChanged(ConnectState.ERROR) + } + } + } + + private val registry = LifecycleRegistry(this) + + override fun getLifecycle(): Lifecycle { + return registry + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/utils/tcp/SocketClient.kt b/app/src/main/java/com/casic/detector/common/utils/tcp/SocketClient.kt new file mode 100644 index 0000000..908ad55 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/utils/tcp/SocketClient.kt @@ -0,0 +1,123 @@ +package com.casic.detector.common.utils.tcp + +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.AdaptiveRecvByteBufAllocator +import io.netty.channel.Channel +import io.netty.channel.ChannelFuture +import io.netty.channel.ChannelFutureListener +import io.netty.channel.ChannelInitializer +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler +import java.util.concurrent.TimeUnit + +class SocketClient(builder: Builder) { + + private val kTag = "SocketClient" + private val hostname = builder.hostname + private val port = builder.port + private val timeout = builder.timeout + private val listener = builder.listener + + private var bootstrap: Bootstrap + private lateinit var channel: Channel + + class Builder { + lateinit var hostname: String + var port = 0 + var timeout = 0L + lateinit var listener: OnSocketConnectListener + + fun setHostname(hostname: String): Builder { + this.hostname = hostname + return this + } + + fun setPort(port: Int): Builder { + this.port = port + return this + } + + fun setTimeout(timeout: Long): Builder { + this.timeout = timeout + return this + } + + fun setOnSocketListener(listener: OnSocketConnectListener): Builder { + this.listener = listener + return this + } + + fun build(): SocketClient { + return SocketClient(this) + } + } + + init { + val eventLoopGroup = NioEventLoopGroup() //设置的连接group + bootstrap = Bootstrap() + bootstrap.group(eventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10 * 1000) //连接超时时间 + .option( + ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator(64, 1024, 65536) + ) + .handler(object : ChannelInitializer() { + override fun initChannel(socketChannel: SocketChannel) { + val pipeline = socketChannel.pipeline() + pipeline.addLast(IdleStateHandler(60, 10, 0, TimeUnit.SECONDS)) + .addLast(ByteArrayDecoder()) + .addLast(ByteArrayEncoder()) + .addLast(SocketChannelHandler(listener)) + } + }) + } + + fun connect() { + synchronized(this) { + try { + Log.d(kTag, "connect: 开始连接TCP服务器") + //连接监听 + val channelFuture = bootstrap.connect(hostname, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + channel = channelFuture.channel() + } + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + retryConnect() + } + } + } + + private fun retryConnect() { + try { + Thread.sleep(timeout) + connect() + } catch (ex: InterruptedException) { + Log.d(kTag, "retryConnect: ${ex.localizedMessage}") + } + } + + fun sendData(message: String) { + val byteBuf = Unpooled.wrappedBuffer(message.toByteArray()) + sendData(byteBuf) + } + + fun sendData(buffer: ByteBuf) { + channel.writeAndFlush(buffer) + } +} \ No newline at end of file