Skip to content

Commit e6b0b69

Browse files
sevenhhearchurtan
authored andcommitted
局域网探测
http://tapd.oa.com/NEW_IOT/prong/stories/view/1020393192869350657 Change-Id: I7c011ea794319c87b28de46b3a0b1a0f00842740 (cherry picked from commit 181ac04)
1 parent 57072b9 commit e6b0b69

File tree

8 files changed

+255
-0
lines changed

8 files changed

+255
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.tencent.iot.video.link.callback
2+
3+
interface DetectMesssageCallback {
4+
fun onMessage(version: String, message: String): Boolean
5+
}

sdk/video-link-android/src/main/java/com/tencent/iot/video/link/consts/VideoConst.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,10 @@ object VideoConst {
1818
const val VIDEO_URLS = "VideoUrls"
1919
const val VIDEO_NVR_INFO = "VideoNvrInfo"
2020
const val VIDEO_PAGE_INDEX = "VideoPageIndex"
21+
const val VIDEO_WLAN_METHOD = "method"
22+
const val VIDEO_WLAN_CLIENT_TOKEN = "clientToken"
23+
const val VIDEO_WLAN_TIME_STAMP = "timestamp"
24+
const val VIDEO_WLAN_TIMEOUT_MS = "timeoutMs"
25+
const val VIDEO_WLAN_PARAMS = "params"
26+
const val VIDEO_WLAN_DEV_NAMES = "deviceName"
2127
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.tencent.iot.video.link.entity
2+
3+
import android.text.TextUtils
4+
5+
class DeviceServerInfo {
6+
var deviceName = ""
7+
var address = ""
8+
var port = 0
9+
10+
fun isReady(): Boolean {
11+
if (TextUtils.isEmpty(address)) {
12+
return false
13+
}
14+
15+
if (TextUtils.isEmpty(deviceName)) {
16+
return false
17+
}
18+
19+
if (port <= 0) {
20+
return false
21+
}
22+
23+
return true
24+
}
25+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.tencent.iot.video.link.entity
2+
3+
enum class VideoMessageType(value: Int) {
4+
DETECT_BODY(1), // 探测消息
5+
DETECT_RESP_BODY(2); // 探测响应消息
6+
7+
private var value = 0
8+
9+
init {
10+
this.value = value
11+
}
12+
13+
fun getValue(): Int {
14+
return this.value
15+
}
16+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.tencent.iot.video.link.entity
2+
3+
class WlanDetectBody {
4+
var clientToken = ""
5+
var productId = ""
6+
var deviceNames = ArrayList<String>()
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.tencent.iot.video.link.entity
2+
3+
class WlanRespBody {
4+
5+
var method = ""
6+
var clientToken = ""
7+
var timestamp = 0L
8+
var params: DeviceServerInfo = DeviceServerInfo()
9+
var code = 0
10+
var status = ""
11+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package com.tencent.iot.video.link.service
2+
3+
import android.text.TextUtils
4+
import android.util.Log
5+
import com.alibaba.fastjson.JSON
6+
import com.alibaba.fastjson.JSONObject
7+
import com.tencent.iot.video.link.callback.DetectMesssageCallback
8+
import com.tencent.iot.video.link.consts.VideoConst
9+
import com.tencent.iot.video.link.entity.VideoMessageType
10+
import com.tencent.iot.video.link.entity.WlanDetectBody
11+
import com.tencent.iot.video.link.entity.WlanRespBody
12+
import com.tencent.xnet.XP2P
13+
import kotlinx.coroutines.*
14+
import java.net.DatagramPacket
15+
import java.net.DatagramSocket
16+
import java.net.InetAddress
17+
import java.net.SocketException
18+
19+
/**
20+
* 设备探测服务
21+
*/
22+
class DetectService private constructor(): CoroutineScope by MainScope() {
23+
24+
companion object {
25+
private var instance: DetectService? = null
26+
27+
@Synchronized
28+
fun getInstance(): DetectService {
29+
instance?.let {
30+
return it
31+
}
32+
instance = DetectService()
33+
return instance!!
34+
}
35+
}
36+
37+
private var TAG = DetectService::class.java.simpleName
38+
@Volatile
39+
private var socket: DatagramSocket? = null
40+
var port = 3072
41+
var overTime = 5000
42+
@Volatile
43+
var groupAddress = "255.255.255.255"
44+
private val BUFFER_SIZE = 2048
45+
private val sdkVarsion = XP2P.getVersion()
46+
var detectMesssageCallback: DetectMesssageCallback? = null
47+
var adapterCallback: DetectMesssageCallback = object: DetectMesssageCallback {
48+
override fun onMessage(version: String, message: String): Boolean {
49+
Log.e(TAG, "version $version, message $message")
50+
if (detectMesssageCallback?.onMessage(version, message) == true) { // 使用集成 sdk 方的处理逻辑
51+
52+
} else { // 使用内部处理逻辑
53+
var resp = JSONObject.parseObject(message, WlanRespBody::class.java)
54+
resp.let {
55+
if (it.method == "probeMatch" && it.params != null && it.params.isReady()) {
56+
// cancel() // 查询到设备的 IP 地址信息,停止广播发送和接收内容
57+
}
58+
}
59+
}
60+
return true
61+
}
62+
}
63+
64+
// 尝试发一次广播
65+
fun startSendBroadcast(body: WlanDetectBody) {
66+
startSendBroadcast(body, 1)
67+
}
68+
69+
fun clear() {
70+
cancel() // 关闭所有的协程
71+
resetSocket()
72+
}
73+
74+
fun resetSocket() {
75+
socket?.let { // 尝试关闭 socket
76+
it.close()
77+
}
78+
socket = null
79+
}
80+
81+
// 尝试指定次数的广播
82+
fun startSendBroadcast(body: WlanDetectBody, times: Int) {
83+
Log.e(TAG, "startSendBroadcast times $times")
84+
resetSocket()
85+
86+
socket = DatagramSocket(port)
87+
openReceiver()
88+
launch(Dispatchers.IO) {
89+
for (i in 0 until times) { // 尝试在协程中发送指定次数的广播
90+
sendBroadcast(body)
91+
delay(1000) // 每秒发一次广播
92+
}
93+
}
94+
}
95+
96+
private fun sendBroadcast(body: WlanDetectBody) {
97+
Log.e(TAG, "sendBroadcast body ${JSON.toJSONString(body)}")
98+
var json = JSONObject()
99+
json[VideoConst.VIDEO_WLAN_METHOD] = "probe"
100+
json[VideoConst.VIDEO_WLAN_CLIENT_TOKEN] = body.clientToken
101+
json[VideoConst.VIDEO_WLAN_TIME_STAMP] = System.currentTimeMillis() / 1000
102+
json[VideoConst.VIDEO_WLAN_TIMEOUT_MS] = overTime
103+
104+
var paramsJson = JSONObject()
105+
paramsJson[VideoConst.MULTI_VIDEO_PROD_ID] = body.productId
106+
var devs = ""
107+
if (body != null && body.deviceNames.isNotEmpty()) {
108+
for (i in 0 until body.deviceNames.size) {
109+
devs = "$devs,${body.deviceNames.get(i)}"
110+
}
111+
}
112+
if (devs.endsWith(",")) { // 清理多余的分割逗号
113+
devs = devs.substring(0, devs.length - 1)
114+
}
115+
if (!TextUtils.isEmpty(devs)) { paramsJson[VideoConst.VIDEO_WLAN_DEV_NAMES] = devs } // 存在数据的时候发送该内容
116+
json[VideoConst.VIDEO_WLAN_PARAMS] = paramsJson
117+
var dataPayload = json.toJSONString().toByteArray()
118+
var headerData = buildHeader(VideoMessageType.DETECT_BODY, sdkVarsion, dataPayload.size)
119+
var data2Send = ByteArray(headerData.size + dataPayload.size)
120+
System.arraycopy(headerData, 0, data2Send, 0, headerData.size)
121+
System.arraycopy(dataPayload, 0, data2Send, headerData.size, dataPayload.size)
122+
sendBroadcast(data2Send)
123+
}
124+
125+
private fun buildHeader(type: VideoMessageType, version: String, len: Int): ByteArray {
126+
var headerBytes = ByteArray(4)
127+
headerBytes[0] = type.getValue().toByte()
128+
var versionParts = version.split(".")
129+
versionParts?.let { oriParts ->
130+
if (oriParts.size < 2) return@let
131+
oriParts[0].toBigIntegerOrNull()?.let { versionPrefix ->
132+
oriParts[1].toBigIntegerOrNull()?.let { versionsuffix ->
133+
headerBytes[1] = ((versionPrefix.toInt() shl 4) or versionsuffix.toInt()).toByte()
134+
}
135+
}
136+
}
137+
len?.let {
138+
headerBytes[3] = (len / Math.pow(2.0, 8.0).toInt()).toByte()
139+
headerBytes[2] = (len % Math.pow(2.0, 8.0).toInt()).toByte()
140+
}
141+
return headerBytes
142+
}
143+
144+
private fun sendBroadcast(dataStr: String) {
145+
sendBroadcast(dataStr.toByteArray())
146+
}
147+
148+
private fun sendBroadcast(data: ByteArray) {
149+
socket?.let {
150+
val packet = DatagramPacket(data, data.size, InetAddress.getByName(groupAddress), port)
151+
it.send(packet)
152+
}
153+
}
154+
155+
// 开启广播接收器
156+
fun openReceiver() {
157+
//在子线程中循环接收数据
158+
Thread {
159+
launch (Dispatchers.Default) {
160+
val recvbuffer = ByteArray(BUFFER_SIZE)
161+
val dataPacket = DatagramPacket(recvbuffer, BUFFER_SIZE)
162+
socket?.let {
163+
while (!it.isClosed) {
164+
try {
165+
it.receive(dataPacket)
166+
var headerBytes = ByteArray(4)
167+
System.arraycopy(recvbuffer, 0, headerBytes, 0, headerBytes.size)
168+
169+
// 非标准响应 body 不做处理
170+
if (headerBytes[0] != VideoMessageType.DETECT_RESP_BODY.getValue().toByte()) continue
171+
var version = (headerBytes[1].toInt() shr 4).toString()
172+
version = "$version.${(headerBytes[1].toInt() and 0x0F)}"
173+
adapterCallback?.onMessage(version, String(recvbuffer, 4, dataPacket.length - 4))
174+
} catch (e: SocketException) {
175+
e.printStackTrace()
176+
}
177+
}
178+
}
179+
}
180+
}.start()
181+
}
182+
}

sdkdemo/src/main/java/com/tencent/iot/explorer/link/demo/video/VideoInputAuthorizeActivity.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import com.tencent.iot.explorer.link.demo.BaseActivity
1414
import com.tencent.iot.explorer.link.core.utils.SharePreferenceUtil
1515
import com.tencent.iot.explorer.link.demo.VideoBaseActivity
1616
import com.tencent.iot.video.link.consts.VideoConst
17+
import com.tencent.iot.video.link.entity.WlanDetectBody
18+
import com.tencent.iot.video.link.service.DetectService
1719
import kotlinx.android.synthetic.main.activity_video_input_authorize.*
1820
import kotlinx.android.synthetic.main.blue_title_layout.*
1921
import kotlinx.android.synthetic.main.input_item_layout.view.*
@@ -26,6 +28,7 @@ class VideoInputAuthorizeActivity : VideoBaseActivity() , CoroutineScope by Main
2628
}
2729

2830
override fun initView() {
31+
2932
tv_title.setText(R.string.iot_demo_name)
3033
access_id_layout.tv_tip.setText(R.string.access_id)
3134
access_token_layout.tv_tip.setText(R.string.access_token)

0 commit comments

Comments
 (0)