From 063927653a4d842709b8191b6a0410cff191ffe4 Mon Sep 17 00:00:00 2001 From: qiji4215 Date: Tue, 18 Jul 2023 18:00:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AE=A4=E5=86=85=E6=95=B4?= =?UTF-8?q?=E7=90=86=E5=B7=A5=E5=85=B7=E5=8F=8D=E5=90=91=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../omqs/ui/activity/map/MainViewModel.kt | 55 +- .../omqs/ui/activity/map/SocketServer.kt | 583 ++++++++++++++++++ .../library/data/dao/impl/INiLocationDao.java | 3 + 3 files changed, 637 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/navinfo/omqs/ui/activity/map/SocketServer.kt diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt index 8e7dae95..d00460e5 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt @@ -26,7 +26,6 @@ import com.navinfo.collect.library.data.dao.impl.TraceDataBase import com.navinfo.collect.library.data.entity.* import com.navinfo.collect.library.map.NIMapController import com.navinfo.collect.library.map.handler.OnQsRecordItemClickListener -import com.navinfo.collect.library.map.handler.OnTaskLinkItemClickListener import com.navinfo.collect.library.utils.GeometryTools import com.navinfo.collect.library.utils.GeometryToolsKt import com.navinfo.omqs.Constant @@ -75,7 +74,7 @@ class MainViewModel @Inject constructor( private val realmOperateHelper: RealmOperateHelper, private val networkService: NetworkService, private val sharedPreferences: SharedPreferences -) : ViewModel() { +) : ViewModel(),SocketServer.OnConnectSinsListener{ private var mCameraDialog: CommonDialog? = null @@ -133,6 +132,9 @@ class MainViewModel @Inject constructor( //状态 val qrCodeStatus: MutableLiveData = MutableLiveData() + //状态 + val indoorToolsStatus: MutableLiveData = MutableLiveData() + /** * 是不是线选择模式 */ @@ -157,7 +159,9 @@ class MainViewModel @Inject constructor( private var lastNiLocaion: NiLocation? = null - var currentIndexNiLocation: Int = 0; + var currentIndexNiLocation: Int = 0 + + private var socketServer:SocketServer? = null init { mapController.mMapView.vtmMap.events.bind(Map.UpdateListener { e, mapPosition -> @@ -203,6 +207,8 @@ class MainViewModel @Inject constructor( initNoteData() initNILocationData() } + + socketServer = SocketServer(mapController,traceDataBase,sharedPreferences) } /** @@ -343,7 +349,6 @@ class MainViewModel @Inject constructor( } //显示轨迹图层 mapController.layerManagerHandler.showNiLocationLayer() - } /** @@ -724,6 +729,17 @@ class MainViewModel @Inject constructor( Toast.LENGTH_LONG ).show() qrCodeStatus.postValue(QrCodeStatus.QR_CODE_STATUS_UPDATE_VIDEO_INFO_SUCCESS) + + //启动双向控制服务 + + //启动双向控制服务 + if (socketServer != null && socketServer!!.isServerClose) { + socketServer!!.connect( + Constant.INDOOR_IP, + this@MainViewModel + ) + } + } } else { withContext(Dispatchers.Main) { @@ -804,4 +820,35 @@ class MainViewModel @Inject constructor( fun cancelTrace() { } + + override fun onConnect(success: Boolean) { + if (!success && socketServer != null) { + BaseToast.makeText( + mapController.mMapView.context, + "轨迹反向控制服务失败,请确认连接是否正常!", + Toast.LENGTH_SHORT + ).show() + } + } + + override fun onIndexing() { + //切换为暂停状态 + indoorToolsStatus.postValue(IndoorToolsStatus.PAUSE) + } + + override fun onStop() { + TODO("Not yet implemented") + } + + override fun onPlay() { + TODO("Not yet implemented") + } + + override fun onParseEnd() { + TODO("Not yet implemented") + } + + override fun onReceiveLocation(mNiLocation: NiLocation?) { + TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/map/SocketServer.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/map/SocketServer.kt new file mode 100644 index 00000000..1f6cbb10 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/map/SocketServer.kt @@ -0,0 +1,583 @@ +package com.navinfo.omqs.ui.activity.map + +import android.app.Service +import android.content.Intent +import android.content.SharedPreferences +import android.os.Binder +import android.os.Handler +import android.os.IBinder +import android.os.Message +import android.text.TextUtils +import android.util.Log +import com.navinfo.collect.library.data.dao.impl.TraceDataBase +import com.navinfo.collect.library.data.entity.NiLocation +import com.navinfo.collect.library.map.NIMapController +import com.navinfo.omqs.Constant +import com.navinfo.omqs.util.DateTimeUtil +import org.json.JSONObject +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.io.Serializable +import java.net.Socket +import java.util.Collections +import kotlin.math.abs + + +enum class IndoorToolsStatus { + PAUSE, + PLAY, + NEXT, + REWIND +} + +/** + * @author qj + * @version V1.0 + * @Date 2018/4/18. + * @Description: 轨迹反向控制服务 + */ +class SocketServer( + private val mapController: NIMapController, + private val traceDataBase: TraceDataBase, + private val sharedPreferences: SharedPreferences +) : Service() { + //类标识 + private val TAG = "SocketServer" + + //线程池 + private val threadConnect = ThreadLocal() + + //读的线程 + private var tRecv: RecvThread? = null + + //解析线程 + private var tParse: ParseThread? = null + + //输出流 + private var outStr: OutputStream? = null + + //输入流 + private var inStr: InputStream? = null + + //状态 + var connectstatus = false + + //socket + private var client: Socket? = null + + //接收缓存 + private val sData = ByteArray(512) + + //反馈接口 + private var mListener: OnConnectSinsListener? = null + + //服务 + private val mBinder: MyBinder = MyBinder() + + //接收集合 + private val mTaskList = Collections.synchronizedList(ArrayList()) + + //连接线程 + private var connectThread: Thread? = null + + //缓存ip + private var lastIp = "" + private val mHandler: Handler = object : Handler() { + override fun handleMessage(msg: Message) { + when (msg.what) { + 0x11 -> if (mListener != null) { + if (msg.obj != null && msg.obj is NiLocation) { + mListener!!.onReceiveLocation(msg.obj as NiLocation) + } else { + mListener!!.onReceiveLocation(null) + } + } + + 0x22 -> //索引定位中 + if (mListener != null) { + mListener!!.onIndexing() + } + + 0x33 -> if (mListener != null) { + mListener!!.onConnect(true) + } + + 0x44 -> if (mListener != null) { + mListener!!.onConnect(false) + } + + 0x55 -> if (mListener != null) { + mListener!!.onPlay() + } + + 0x66 -> if (mListener != null) { + mListener!!.onStop() + } + + 0x99 -> if (mListener != null) { + mListener!!.onParseEnd() + } + + 0x999 -> if (mListener != null) { + mListener!!.onConnect(false) + disconnect() + } + } + } + } + + override fun onCreate() { + super.onCreate() + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + return super.onStartCommand(intent, flags, startId) + } + + override fun onDestroy() { + super.onDestroy() + } + + override fun onBind(intent: Intent): IBinder? { + return mBinder + } + + inner class MyBinder : Binder() { + // 返回Activity所关联的Service对象,这样在Activity里,就可调用Service里的一些公用方法 和公用属性 + val service: SocketServer + get() =// 返回Activity所关联的Service对象,这样在Activity里,就可调用Service里的一些公用方法 和公用属性 + this@SocketServer + } + + /** + * 启动sock连接 + * + * @param ip + * @param listener 结果回调 + */ + fun connect(ip: String, listener: OnConnectSinsListener?) { + if (connectThread != null && connectThread!!.isAlive && TextUtils.equals(lastIp, ip)) { + return + } + mListener = listener + lastIp = ip + connectThread = object : Thread() { + override fun run() { + try { + client = threadConnect.get() + if (client == null) { + client = Socket(ip, 8010) + client!!.soTimeout = 3000000 + client!!.keepAlive = true + threadConnect.set(client) + } + outStr = client!!.getOutputStream() + inStr = client!!.getInputStream() + if (tRecv != null) { + tRecv!!.cancel() + } + tRecv = RecvThread() + val thread = Thread(tRecv) + thread.start() + + //解析线程 + if (tParse != null) { + tParse!!.cancel() + } + tParse = ParseThread() + val parsethread = Thread(tParse) + parsethread.start() + + //socket启动成功 + val msg = Message() + msg.what = 0x33 + mHandler.sendMessage(msg) + if (!connectstatus) { + connectstatus = true // 更改连接状态 + } + } catch (e: Exception) { + e.printStackTrace() + //启动失败 + val msg = Message() + msg.what = 0x44 + mHandler.sendMessage(msg) + } + } + } + (connectThread as Thread).start() + } + + /** + * sock是否启动 + * + * @return true 启动 false停止 + */ + val isStart: Boolean + get() = if (connectThread != null && connectThread!!.isAlive) { + true + } else false + + /** + * 销毁连接 + */ + fun disconnect() { + try { + + //销毁线程 + if (tRecv != null) { + tRecv!!.cancel() + } + + //销毁线程 + if (tParse != null) { + tParse!!.cancel() + } + } catch (e: Exception) { + } + try { + if (outStr != null) outStr!!.close() + if (inStr != null) inStr!!.close() + if (client != null) client!!.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + + /** + * 解析接收到得线程 + */ + private inner class ParseThread : Runnable { + private var runFlag = true + + //轨迹时间buffer + private val traceTimeBuffer = 1500 + private var timeIndex = 0 + fun cancel() { + runFlag = false + } + + override fun run() { + try { + while (runFlag) { + if (mTaskList.size > 0) { + timeIndex = mTaskList.size - 1 + val result = parseResult(mTaskList[timeIndex]) + var resultNiLocation: NiLocation? = null + if (result != null) { + when (result.type) { + 1 -> { + //先暂停播放 + val msg = Message() + msg.what = 0x22 + mHandler.sendMessage(msg) + val currentTime: Long = DateTimeUtil.getTimePointSSS( + result.data + ) + val currentTimeStr: String = DateTimeUtil.TimePointSSSToTime( + result.data + ) + val startTime = currentTime - traceTimeBuffer + val endTme = currentTime + traceTimeBuffer + + //转换为数据库时间 + val startTimeStr: String = + DateTimeUtil.getDateSimpleTime(startTime) + + //转换为数据库时间 + val endTimeStr: String = + DateTimeUtil.getDateSimpleTime(endTme) + if (!TextUtils.isEmpty(startTimeStr) && !TextUtils.isEmpty( + endTimeStr + ) + ) { + Log.e(TAG, "getTraceData开始") + val list: List? = + getTrackList(startTimeStr, endTimeStr, currentTimeStr) + Log.e(TAG, "getTraceData结束") + if (list != null && list.size > 0) { + var disTime: Long = 0 + + + //只有一个点不进行判断直接返回结果 + if (list.size == 1) { + resultNiLocation = list[0] + } else { + + //遍历集合取最近时间的轨迹点 + b@ for (nilocation in list) { + if (!TextUtils.isEmpty(nilocation.time)) { + + //只获取到秒的常量 + val time: Long = + nilocation.timeStamp.toLong() + + val disTimeTemp = abs(time - currentTime) + + //如果时间相同直接返回该点 + if (disTimeTemp == 0L) { + resultNiLocation = nilocation + break@b + } else { + + //第一次不对比,取当前值 + if (disTime == 0L) { + disTime = disTimeTemp + resultNiLocation = + nilocation + } else { + + //前一个差值大于当前差值则取当前相对小的值 + if (disTime - disTimeTemp > 0) { + disTime = disTimeTemp + resultNiLocation = + nilocation + } + } + } + } + } + } + } + } + val msg1 = Message() + msg1.what = 0x11 + msg1.obj = resultNiLocation + mHandler.sendMessage(msg1) + } + + 2 -> { + val msg4 = Message() + msg4.what = 0x55 + mHandler.sendMessage(msg4) + } + + 3 -> { + val msg5 = Message() + msg5.what = 0x66 + mHandler.sendMessage(msg5) + } + } + } + + + //解析时索引与集合索引对比,如果不相同代表有新命令,需要继续解析最后一条,否则清空集合不在解析 + try { + if (timeIndex == mTaskList.size - 1) { + mTaskList.clear() + } + } catch (e: Exception) { + } + val msg2 = Message() + msg2.what = 0x99 + mHandler.sendMessage(msg2) + } + } + } catch (e: Exception) { + e.printStackTrace() + val msg = Message() + msg.what = 0x99 + mHandler.sendMessage(msg) + } + } + } + + /** + * 获取轨迹数据 + * + * @param startTimeStr 起始时间 + * @param endTimeStr 结束时间 + * @param currentTimeStr 当前点时间,如果存在便直接获取一个点 + * @return list 数据集合 + */ + private fun getTrackList( + startTimeStr: String, + endTimeStr: String, + currentTimeStr: String + ): List? { + if (!TextUtils.isEmpty(startTimeStr) && !TextUtils.isEmpty(endTimeStr)) { + var startTime: Long = 0 + var endTime: Long = 0 + try { + startTime = startTimeStr.toLong() + endTime = endTimeStr.toLong() + } catch (e: java.lang.Exception) { + } + if (startTime != 0L && endTime != 0L) { + + val id = sharedPreferences.getInt(Constant.SELECT_TASK_ID, -1) + + val list: MutableList = traceDataBase.niLocationDao.taskIdAndTimeTofindList(id.toString(),startTime,endTime) + + if (list.size > 0) return list + } + } + return null + } + + /** + * 接收管道数据 + */ + private inner class RecvThread : Runnable { + private var runFlag = true + fun cancel() { + runFlag = false + } + + override fun run() { + var rlRead: Int + try { + while (runFlag) { + var line: String = "" + if (!isServerClose) { + rlRead = inStr!!.read(sData) //对方断开返回-1 + if (rlRead > 0) { + Log.e(TAG, sData.toString() + "") + line = String(sData, 0, rlRead) + mTaskList.add(line) + } else { + connectFaild("连接断开") + } + } else { + connectFaild("连接断开") + } + } + } catch (e: IOException) { + connectFaild(e.toString()) + e.printStackTrace() + } + } + } + + /** + * 连接失败 + * @param e 原因 + */ + private fun connectFaild(e: String) { + val msg2 = Message() + msg2.what = 0x999 + mHandler.sendMessage(msg2) + } + + /** + * 判断是否断开连接,断开返回true,没有返回false + * @return + */ + val isServerClose: Boolean + get() { + return try { + client!!.sendUrgentData(0) //发送1个字节的紧急数据,默认情况下,服务器端没有开启紧急数据处理,不影响正常通信 + false + } catch (se: Exception) { + true + } + } + + /** + * 停止接收管道数据 + */ + fun stop() { + Log.e(TAG, "stop!") + connectstatus = false + if (tRecv != null) { + tRecv!!.cancel() + } + if (tParse != null) { + tParse!!.cancel() + } + } + + /** + * 开始接收管道数据 + */ + fun start() { + Log.e(TAG, "start!") + if (tRecv != null) { + tRecv!!.cancel() + } + tRecv = RecvThread() + val thread = Thread(tRecv) + thread.start() + + //解析线程 + if (tParse != null) { + tParse!!.cancel() + } + tParse = ParseThread() + val parsethread = Thread(tParse) + parsethread.start() + } + + fun setTraceMap() { + + } + + /** + * 轨迹反向控制回调接口 + */ + interface OnConnectSinsListener { + /** + * 连接状态 + * + * @param success true 连接成功 false 连接失败 + */ + fun onConnect(success: Boolean) + + /** + * 索引中 + */ + fun onIndexing() + + /** + * 暂停 + */ + fun onStop() + + /** + * 播放 + */ + fun onPlay() + + /** + * 结束完成 + */ + fun onParseEnd() + + /** + * 轨迹点 + * + * @param mNiLocation + */ + fun onReceiveLocation(mNiLocation: NiLocation?) + } + + /** + * 解析返回值 + * + * @return 时间信息 + */ + private fun parseResult(data: String): Result? { + var data = data + if (!TextUtils.isEmpty(data)) { + try { + data = data.replace("\n".toRegex(), "") + val json = JSONObject(data) + val type = json.optInt("type") + val mResult: Result = Result() + mResult.type = type + if (type == 1) { + mResult.data = json.optString("data", "") + } + return mResult + } catch (e: Exception) { + } + } + return null + } + + //结果类对象 + internal inner class Result : Serializable { + var type = 0 + var data: String? = null + } +} \ No newline at end of file diff --git a/collect-library/src/main/java/com/navinfo/collect/library/data/dao/impl/INiLocationDao.java b/collect-library/src/main/java/com/navinfo/collect/library/data/dao/impl/INiLocationDao.java index 9d362617..c4fa6f57 100644 --- a/collect-library/src/main/java/com/navinfo/collect/library/data/dao/impl/INiLocationDao.java +++ b/collect-library/src/main/java/com/navinfo/collect/library/data/dao/impl/INiLocationDao.java @@ -47,6 +47,9 @@ public interface INiLocationDao { @Query("SELECT * FROM niLocation") List findAll(); + @Query("SELECT * FROM niLocation where time>=:startTime and time<=:endTime and taskId=:taskId") + List taskIdAndTimeTofindList(String taskId,long startTime,long endTime); + @Query("SELECT * FROM niLocation where taskId =:taskId") List findToTaskIdAll(String taskId); }