From 156586d63e1e5925ba02498ec4a99eca933ddc9d Mon Sep 17 00:00:00 2001 From: tongchenfei Date: Mon, 21 Sep 2020 17:39:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E8=A7=A3=E6=9E=90=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../zhidao/mogo/module/obu/ObuConstant.java | 5 + .../com/zhidao/mogo/module/obu/obu/BaseObu.kt | 2 +- .../zhidao/mogo/module/obu/obu/HualiObu.kt | 166 ++++++++++++++++-- .../com/zhidao/mogo/module/obu/obu/IObu.kt | 1 + .../module/obu/obu/bean/MogoObuEventInfo.kt | 20 +++ .../com/mogo/module/v2x/V2XObuManager.java | 30 +++- .../scenario/scene/obu/V2XObuEventWindow.java | 6 + .../module/v2x/utils/TestOnLineCarUtils.java | 33 ++++ .../v2x_icon_obu_rush_red_light.png | Bin 0 -> 12104 bytes .../res/raw/scenario_push_cross_crash.json | 21 +++ 10 files changed, 261 insertions(+), 23 deletions(-) create mode 100644 modules/mogo-module-v2x/src/main/res/drawable-xhdpi/v2x_icon_obu_rush_red_light.png create mode 100644 modules/mogo-module-v2x/src/main/res/raw/scenario_push_cross_crash.json diff --git a/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/ObuConstant.java b/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/ObuConstant.java index 7ced90581f..d6b9513901 100644 --- a/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/ObuConstant.java +++ b/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/ObuConstant.java @@ -35,4 +35,9 @@ public class ObuConstant { * 为vip车辆变灯提醒 */ public static final int TYPE_CHANGE_LIGHT_FOR_VIP = 116; + + /** + * 闯红灯预警 + */ + public static final int TYPE_RUSH_RED_LIGHT = 117; } diff --git a/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/BaseObu.kt b/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/BaseObu.kt index aea1585daf..206ff73683 100644 --- a/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/BaseObu.kt +++ b/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/BaseObu.kt @@ -1,6 +1,6 @@ package com.zhidao.mogo.module.obu.obu -open class BaseObu : IObu { +abstract class BaseObu : IObu { protected var callback: IObuCallback? = null override fun init() { } diff --git a/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/HualiObu.kt b/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/HualiObu.kt index b6e108a7f4..28fd9e72e2 100644 --- a/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/HualiObu.kt +++ b/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/HualiObu.kt @@ -1,47 +1,177 @@ package com.zhidao.mogo.module.obu.obu import com.mogo.utils.logger.Logger +import com.zhidao.mogo.module.obu.ObuConstant +import com.zhidao.mogo.module.obu.obu.bean.MogoObuEventInfo import com.zhidao.mogo.module.obu.socket.IUdpSocketCallback import com.zhidao.mogo.module.obu.socket.UdpSocketManager +import org.json.JSONObject +import java.lang.StringBuilder import kotlin.concurrent.thread +import kotlin.experimental.and +import kotlin.experimental.xor /** * 华砺智行obu 苏州演示项目用到的 * * @author tongchenfei */ -class HualiObu : BaseObu(),IUdpSocketCallback { +class HualiObu : BaseObu(), IUdpSocketCallback { private val socketManager = UdpSocketManager(this) - companion object{ + + companion object { const val TAG = "HualiObu" const val IP_ADDRESS = "172.30.31.44" const val PORT = 10005 } + override fun init() { super.init() socketManager.receiveMsgFrom(IP_ADDRESS, PORT) - socketManager.sendMsgTo("Hello ssokit", IP_ADDRESS, PORT) Logger.d(TAG, "init") - thread { - var count = 0 - while (true) { - Logger.d(TAG,"准备发送测试数据===") - socketManager.sendMsgTo("Hello ssokit===$count", IP_ADDRESS, PORT) - count++ - Thread.sleep(1000) - if (count == 1000) { - Logger.d(TAG, "测试数据结束") - break - } - } - } } override fun onMessageReceived(msg: ByteArray) { // todo 处理数据 val m = String(msg) - Logger.d(TAG, "onMessageReceived: $msg") - + Logger.d(TAG, "onMessageReceived: $m") + parseObuProtocol(msg) } + /** + * 解析obu数据 + * + * @param ori 原始数据 + */ + private fun parseObuProtocol(ori: ByteArray) { + val effectiveData = getEffectiveData(ori) + if (effectiveData != null) { + val msg = String(effectiveData) + Logger.d(TAG, "收到obu数据: \n $msg") + val json = JSONObject(msg) + val rushRedLight = json.optJSONObject("rush_redlight") + rushRedLight?.let { + // 有闯红灯告警信息,绿波车速信息 + Logger.d(TAG, "收到闯红灯告警信息: \n $it") + val glosaInfo = it.optJSONArray("glosa_info") + glosaInfo?.let { info -> + Logger.d(TAG, "收到红绿灯信息: $info") + val lastInfo = info.getJSONObject(0) + val alert = lastInfo.optInt("rush_redlight_alarm", -1) + val eventInfo = MogoObuEventInfo() + when (alert) { + 0 -> { + // 有闯红灯风险 + Logger.d(TAG,"准备预警--闯红灯") + eventInfo.mogoEventId = ObuConstant.TYPE_RUSH_RED_LIGHT + eventInfo.describe = "当前车速经过路口会闯红灯,请减速!" + callback?.onEventInfoCallback(eventInfo) + } + 1 -> { + // 没有风险,给出建议车速,绿波车速 + Logger.d(TAG,"准备预警--绿波车速") + eventInfo.mogoEventId = ObuConstant.TYPE_OPTIMAL_SPEED_ADVISORY + val optSpeed = lastInfo.optInt("advisory_speed") + eventInfo.describe = "前方路口,建议车速 $optSpeed km/h" + callback?.onEventInfoCallback(eventInfo) + } + else -> { + Logger.e(TAG,"没有有效红绿灯预警信息") + } + } + } + } + val intersectionCrash = json.optJSONObject("intersection_crash") + intersectionCrash?.let { + // 有交叉路口碰撞预警信息 + Logger.d(TAG, "收到交叉路口碰撞信息: \n $it") + val alert = it.optInt("intersection_crash_alarm", 0) + if (alert == 1) { + // 有碰撞预警 + Logger.d(TAG,"准备预警--交叉口碰撞") + val eventInfo = MogoObuEventInfo() + eventInfo.mogoEventId = ObuConstant.TYPE_CROSS_COLLISION_WARNING + callback?.onEventInfoCallback(eventInfo) + } + + } + val pedestrainInformation = json.optJSONObject("pedestrain_information") + pedestrainInformation?.let { + // 有人车冲突信息 + Logger.d(TAG, "收到人车冲突信息: \n $it") + val alert = it.optInt("pedestrian_crash_alarm", 0) + if (alert == 1) { + // 有人车冲突 + Logger.d(TAG,"准备预警--行人碰撞") + val eventInfo = MogoObuEventInfo() + eventInfo.mogoEventId = ObuConstant.TYPE_ROAD_USER_COLLISION_WARNING + } + + } + val position = json.optJSONObject("position_3d") + position?.let { + // 定位信息 + Logger.d(TAG, "收到定位信息: \n $it") + + } + + val motion = json.optJSONObject("motion") + motion?.let { + // 行驶信息 + Logger.d(TAG, "收到行驶信息: \n $it") + + } + } else { + Logger.e(TAG, "有效数据为空,无法解析") + } + } + + /** + * 从原始数据中,根据数据长度字节,取出有效数据 + * + * @param oriMsg 原始数据 + * @return 有效数据 + */ + private fun getEffectiveData(oriMsg: ByteArray): ByteArray? { + val dataLengthInfo = ByteArray(4) + System.arraycopy(oriMsg, 16, dataLengthInfo, 0, 4) + printByteArray(dataLengthInfo) + val dataLength = convertTwoUnSignInt(dataLengthInfo) + Logger.d(TAG, "解析后的长度: $dataLength") + val parseData = ByteArray(dataLength + 21) + System.arraycopy(oriMsg, 0, parseData, 0, parseData.size) + return if (isAvailable(parseData)) { + val msg = ByteArray(dataLength) + System.arraycopy(parseData, 20, msg, 0, dataLength) + msg + } else { + null + } + } + + private fun isAvailable(msg: ByteArray): Boolean { + val checkSum = msg.last() + var check = msg[0].xor(0xff.toByte()) + for (i in 1 until msg.size - 1) { + check = check.xor(msg[i]) + } + return checkSum == check + } + + private fun printByteArray(array: ByteArray) { + val arrayBuilder = StringBuilder() + array.forEach { + val res = it.toInt() and 0xff + arrayBuilder.append("0x") + if (res < 10) { + arrayBuilder.append("0") + } + arrayBuilder.append(it.toString(16)).append(", ") + } + Logger.d(TAG, arrayBuilder.toString()) + } + + private fun convertTwoUnSignInt(byteArray: ByteArray): Int = + (byteArray[0].toInt() shl 24) or (byteArray[1].toInt() and 0xFF) or (byteArray[2].toInt() shl 8) or (byteArray[3].toInt() and 0xFF) + } \ No newline at end of file diff --git a/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/IObu.kt b/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/IObu.kt index e0e350c9f0..7e99c1c6c5 100644 --- a/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/IObu.kt +++ b/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/IObu.kt @@ -14,4 +14,5 @@ interface IObu { * 注册数据回调 */ fun registerObuCallback(callback: IObuCallback) + } \ No newline at end of file diff --git a/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/bean/MogoObuEventInfo.kt b/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/bean/MogoObuEventInfo.kt index 0ff33809eb..549ea38278 100644 --- a/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/bean/MogoObuEventInfo.kt +++ b/modules/mogo-module-obu/src/main/java/com/zhidao/mogo/module/obu/obu/bean/MogoObuEventInfo.kt @@ -1,5 +1,6 @@ package com.zhidao.mogo.module.obu.obu.bean +import com.zhidao.mogo.module.obu.ObuConstant import com.zhidao.smartv2x.model.obu.CarEventInfo /** @@ -9,8 +10,13 @@ import com.zhidao.smartv2x.model.obu.CarEventInfo */ class MogoObuEventInfo(){ var typeCode:String? = null + set(value){ + mogoEventId = parseObuEvent(value) + field = value + } var type:String? = null var describe:String? = null + var mogoEventId:Int = 0 override fun toString(): String { return "MogoObuEventInfo(typeCode=$typeCode, type=$type, describe=$describe)" } @@ -21,4 +27,18 @@ class MogoObuEventInfo(){ this.describe = info.describe } + private fun parseObuEvent(type: String?): Int { + return when (type) { + "06" -> // 紧急制动预警 + ObuConstant.TYPE_URGENCY_COLLISION_WARNING + "13" -> // 绿波车速引导 + ObuConstant.TYPE_OPTIMAL_SPEED_ADVISORY + "39" -> // 行人碰撞预警 + ObuConstant.TYPE_ROAD_USER_COLLISION_WARNING + "vip变灯提醒" -> ObuConstant.TYPE_CHANGE_LIGHT_FOR_VIP + null -> -1 + else -> type.toInt() + } + } + } \ No newline at end of file diff --git a/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/V2XObuManager.java b/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/V2XObuManager.java index 2929404f97..40163e0d60 100644 --- a/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/V2XObuManager.java +++ b/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/V2XObuManager.java @@ -18,6 +18,7 @@ import com.mogo.module.v2x.scenario.scene.obu.V2XObuEventScenario; import com.mogo.module.v2x.utils.ADASUtils; import com.mogo.module.v2x.utils.DrivingDirectionUtils; import com.mogo.module.v2x.utils.ObuConfig; +import com.mogo.module.v2x.utils.TestOnLineCarUtils; import com.mogo.utils.logger.Logger; import com.zhidao.mogo.module.obu.ObuConstant; import com.zhidao.mogo.module.obu.ObuManager; @@ -31,7 +32,10 @@ import org.json.JSONObject; import java.util.Map; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + import static com.mogo.module.v2x.V2XConst.MODULE_NAME; +import static com.mogo.module.v2x.V2XServiceManager.getContext; import static com.mogo.module.v2x.scenario.scene.obu.V2XObuEventScenario.ACTION_LAUNCHER_ADAS_APP_BIZ; /** @@ -143,7 +147,7 @@ public class V2XObuManager implements IObuCallback, Handler.Callback { Logger.d(MODULE_NAME, "发送红绿灯广播: " + data); intent.putExtra("data", data); intent.putExtra("type", 2); - V2XServiceManager.getContext().sendBroadcast(intent); + getContext().sendBroadcast(intent); } catch (Exception e) { e.printStackTrace(); Logger.e(MODULE_NAME, e, "发送红绿灯广播异常=="); @@ -166,7 +170,8 @@ public class V2XObuManager implements IObuCallback, Handler.Callback { if (last == null) { last = 0L; } - int eventType = parseObuEvent(info.getTypeCode()); +// int eventType = parseObuEvent(info.getTypeCode()); + int eventType = info.getMogoEventId(); if (eventType == ObuConstant.TYPE_OPTIMAL_SPEED_ADVISORY) { // 加一个容错机制,如果已经驶过绿波车速路口,那么再收到绿波车速obu事件,就不再上报 MogoLocation currentLocation = V2XLocationListener.getInstance().getLastCarLocation(); @@ -213,7 +218,7 @@ public class V2XObuManager implements IObuCallback, Handler.Callback { entity.setTts("前方行人,注意减速"); entity.setExpireTime(30_000); entity.setAlarmContent("前方行人,注意减速"); - ADASUtils.broadcastToADAS(V2XServiceManager.getContext(), entity); + ADASUtils.broadcastToADAS(getContext(), entity); break; case ObuConstant.TYPE_CHANGE_LIGHT_FOR_VIP: // vip变灯提醒 @@ -223,6 +228,23 @@ public class V2XObuManager implements IObuCallback, Handler.Callback { messageEntity.setContent(changeLightEvent); V2XObuEventScenario.getInstance().init(messageEntity); break; + case ObuConstant.TYPE_RUSH_RED_LIGHT: + // 闯红灯预警 + V2XObuEventEntity rushRedLightEvent = new V2XObuEventEntity(); + rushRedLightEvent.setType(ObuConstant.TYPE_RUSH_RED_LIGHT); + rushRedLightEvent.setDesc(info.getDescribe()); + messageEntity.setContent(rushRedLightEvent); + V2XObuEventScenario.getInstance().init(messageEntity); + break; + case ObuConstant.TYPE_CROSS_COLLISION_WARNING: + // 交叉口碰撞预警 + V2XMessageEntity v2XMessageEntity = + TestOnLineCarUtils.getV2XScenarioCrossCrash(); + + Intent intent = new Intent(V2XConst.BROADCAST_SCENE_HANDLER_ACTION); + intent.putExtra(V2XConst.BROADCAST_SCENE_EXTRA_KEY, v2XMessageEntity); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); + break; default: break; } @@ -234,7 +256,7 @@ public class V2XObuManager implements IObuCallback, Handler.Callback { if (ObuConfig.useObuLocation) { MogoLocation currentLocation = new MogoLocation(); - CoordinateConverter converter = new CoordinateConverter(V2XServiceManager.getContext()); + CoordinateConverter converter = new CoordinateConverter(getContext()); converter.from(CoordinateConverter.CoordType.GPS); LatLng latLng = new LatLng(locationInfo.getLat(), locationInfo.getLon()); converter.coord(latLng); diff --git a/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/scenario/scene/obu/V2XObuEventWindow.java b/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/scenario/scene/obu/V2XObuEventWindow.java index 07c48d8709..2bf04a71f1 100644 --- a/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/scenario/scene/obu/V2XObuEventWindow.java +++ b/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/scenario/scene/obu/V2XObuEventWindow.java @@ -88,6 +88,12 @@ public class V2XObuEventWindow extends FrameLayout implements IV2XWindow getV2XScenarioCrossCrash() { + try { + InputStream inputStream = V2XUtils.getApp() + .getResources() + .openRawResource(R.raw.scenario_push_cross_crash); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int len = -1; + byte[] buffer = new byte[1024]; + while ((len = inputStream.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + inputStream.close(); + + // 加载数据源 + V2XPushMessageEntity v2xRoadEventEntity = GsonUtil.objectFromJson(baos.toString(), V2XPushMessageEntity.class); + + V2XMessageEntity v2xMessageEntity = new V2XMessageEntity<>(); + // 控制类型 + v2xMessageEntity.setType(V2XMessageEntity.V2XTypeEnum.ALERT_ANIMATION_WARNING); + // 设置数据 + v2xMessageEntity.setContent(v2xRoadEventEntity); + // 控制展示状态 + v2xMessageEntity.setShowState(true); + return v2xMessageEntity; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + /** * 模拟 疲劳驾驶 */ diff --git a/modules/mogo-module-v2x/src/main/res/drawable-xhdpi/v2x_icon_obu_rush_red_light.png b/modules/mogo-module-v2x/src/main/res/drawable-xhdpi/v2x_icon_obu_rush_red_light.png new file mode 100644 index 0000000000000000000000000000000000000000..885d5dfeb789e3d4685bff4fb623ecc657c9e7c9 GIT binary patch literal 12104 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