unity+PICO VR开发总结
一.程序开机自启
把应用的包名写在创建的config.txt文件中并将config.txt文件放在设备根目录,安装应用,重启设备。就可以通过配置文件开机自启动第三方应用的.
具体如下:
1.创建config.txt
新版本(config.txt)
open_guide:1
------
home_pkg:com.XKC.NYS_Hall
------
2.将config.txt放入设备根目录
这样就能在pico上自启动了。
二。开发者模式开启
pico4打开开发者模式打开手机桌面【设置】,在设置主界面,找到【我的设备】,进入到我的设备界面,向下滑动找到【全部参数】,在全部参数界面,可以看到手机的【型号】【运行内存】等;
下滑找到一个【MIUI版本】,快速点击MIUI版本七次,在屏幕下方会弹出,一个【您现在处于开发者模式!】,即可成功进入开发者模式,然后返回到设置主界面,在上方搜索框中搜索【开发者选项】,点击进入开发者选项,可以看到上方【开启开发者选项】已开启,点击右侧的【开关按钮】即可关闭;
三.机器卡死的情况下恢复出厂设置,如下图所示操作:
Android权限:
<!--<meta-data android:name="pico_advance_interface" android:value="0"/>-->//这行代码可以支持远程控制设备重启或者自启动
项目的AndroidManifest.xml如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:compileSdkVersion="29" android:compileSdkVersionCodename="\ 10" android:installLocation="preferExternal" package="com.XKC.NYS_Hall" platformBuildVersionCode="29" platformBuildVersionName="10"> <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true" android:xlargeScreens="true"/> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.REORDER_TASKS"/> <uses-permission android:name="android.permission.GET_TASKS"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.REMOVE_TASKS"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE"/> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.DELETE_PACKAGES"/> <uses-permission android:name="pvr.permission.ACCESS_VOICE_ASSISTANT"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.INJECT_EVENTS"/> <uses-permission android:name="android.permission.WRITE_SETTINGS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.BATTERY_STATS"/> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <application android:allowBackup="false" android:icon="@mipmap/app_icon" android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" android:usesCleartextTraffic="true" > <activity android:configChanges="locale|fontScale|keyboard|keyboardHidden|layoutDirection|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode" android:hardwareAccelerated="false" android:launchMode="singleTask" android:name="com.unity3d.player.UnityPlayerActivity" android:screenOrientation="landscape" android:theme="@style/UnityThemeSelector"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> <meta-data android:name="unityplayer.UnityActivity" android:value="true"/> <meta-data android:name="android.notch_support" android:value="true"/> </activity> <meta-data android:name="unity.splash-mode" android:value="0"/> <meta-data android:name="unity.splash-enable" android:value="true"/> <meta-data android:name="notch.config" android:value="portrait|landscape"/> <meta-data android:name="unity.build-id" android:value="8efff4e7-1044-4c65-88b6-20d207b9c8b9"/> <meta-data android:name="pvr.app.type" android:value="vr"/> <meta-data android:name="pvr.sdk.version" android:value="XR Platform_1.2.4.7"/> <meta-data android:name="enable_cpt" android:value="0"/> <meta-data android:name="enable_entitlementcheck" android:value="0"/> <meta-data android:name="pico_advance_interface" android:value="0"/> <meta-data android:name="pico_scope" android:value="get_user_info"/> <meta-data android:name="com.pvr.instructionset" android:value="32"/> <activity android:excludeFromRecents="true" android:exported="true" android:launchMode="singleTask" android:name="com.pico.loginpaysdk.auth.TransferStationActivity" android:theme="@android:style/Theme.Translucent.NoTitleBar"> <intent-filter> <action android:name="com.pico.loginpaysdk.intent.action.AuthCallback"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> </application> </manifest>
服务端发送指令重启设备的相关代码
:
case "RESTART": Debug.Log("设备重启"); PXR_Enterprise.ControlSetDeviceAction(DeviceControlEnum.DEVICE_CONTROL_REBOOT, (k) => { Debug.LogError(k == 1 ? $"错误代码{k}:重启失败" : $"错误代码{k}:没有权限"); }); return;
关于服务端控制的所有代码:
using BestHTTP; using BestHTTP.JSON.LitJson; using BestHTTP.WebSocket; using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.Timers; using Unity.XR.PXR; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.XR; public class NYSWebController : MonoBehaviour { private WEBGameTypeEnum WEBGameType; private const string ProdServer = "wss://vr.66nao.com/ws"; private const string DevServer = "wss://vr-test.66nao.com/ws"; // private const string DevServer = "wss://dev.drbrain.net/ws"; protected const string CompleteCodeURL = "/brain-service-api/bh-screening-report/complete_types/"; WebSocket webSocket; private float lastHeartTime; public static NYSWebController Instance; private void Awake() { Instance = this; } private void Start() { SceneManager.sceneLoaded += SceneLoaded; Timer loopKeepConnectTimer = new Timer(3000); loopKeepConnectTimer.Elapsed += LoopKeepConnectTimer_Elapsed; loopKeepConnectTimer.AutoReset = true; loopKeepConnectTimer.Enabled = true; } private void Update() { if (webSocket != null && webSocket.IsOpen) { if (Time.time - lastHeartTime > 5) { CloseWebSocket(); } } #region DebugCode if (PicoManager.Input_GetKeyDown(XRNode.RightHand, CommonUsages.primaryButton)) { //暂停键 //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_DBQ_A\"}"); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_SYMBOL_SEARCH\"}"); //EventDispatcher.Instance.Dispatch((int)EventID.HALL_STARTRAPIDTRAINING); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_EMCA\"}"); } if (PicoManager.Input_GetKeyDown(XRNode.RightHand, CommonUsages.secondaryButton)) { //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"VOICE_MAN\"}"); //EventDispatcher.Instance.Dispatch((int)EventID.HALL_STARTRAPIDTRAINING); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_VOCABULARY_TEST\"}"); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_SYMBOL_SEARCH\"}"); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_SYMBOL_SEARCH\"}"); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"SKIP_HOME\"}"); } if (Input.GetKeyDown(KeyCode.Z)) { //EventDispatcher.Instance.Dispatch((int)EventID.HALL_STARTRAPIDTRAINING); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_CONTINUE_SCREEN_PCAT1692323300085\"}"); OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_SCREEN_MULTIPLE_SYMBOL_SEARCH,SYMMETRY_SPAN,PORTRAIT_MEMORY,ORIGAMI_TEST,VOCABULARY_TEST\"}"); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_SCREEN_MULTIPLE_PORTRAIT_MEMORY,ORIGAMI_TEST,VOCABULARY_TEST\"}"); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"VOICE_MAN\"}"); // OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"SKIP_HOME\"}"); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_EMCA\"}"); //GameEventArg arg = EventDispatcher.Instance.GetEventArg((int)EventID.HALL_STARTRAPIDTRAINING); //arg.SetArg(0, "测试ID"); //var str = "SYMBOL_SEARCH,SYMMETRY_SPAN,PORTRAIT_MEMORY".Split(','); //List<string> sceneList = new List<string>(); //for (int i = 0; i < str.Length; i++) //{ // sceneList.Add(Enums.GetDesc((WEBGameTypeEnum)Enum.Parse(typeof(WEBGameTypeEnum), str[i]))); //} //arg.SetArg(1, sceneList); //EventDispatcher.Instance.Dispatch((int)EventID.HALL_STARTRAPIDTRAINING); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_EMCA\"}"); } if (Input.GetKeyDown(KeyCode.X)) { OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"CHANGE_GAMESCENE_0\"}"); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"PAUSE_EMCA\"}"); // EventDispatcher.Instance.Dispatch((int)EventID.HALL_STARTRAPIDTRAINING); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"PAUSE_SCREEN\"}"); //EventDispatcher.Instance.Dispatch((int)EventID.WEBCONTROL_RECALIBRATE); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_SYMBOL_SEARCH\"}"); } if (Input.GetKeyDown(KeyCode.C)) { // OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"PLAY_GUIDEVIDEO_1004\"}"); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"CONTINUE_SCREEN\"}"); // OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"CHANGE_USER\"}"); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_MMSE\"}"); //OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"OVER_SCREEN\", \"no\":\"\"}"); OnMessage(webSocket, "{\"message\":\"设备控制消息\", \"type\":\"START_MOCA\"}"); } #endregion } void InitWebsocket() { webSocket = new WebSocket(new Uri(NYSNetManager.Instance.OperatingEnvironmentType == NYSNetManager.OperatingEnvironmentTypeEnum.生产环境 ? ProdServer : DevServer)); webSocket.OnOpen += WebOnOpen; webSocket.OnBinary += OnBinary; webSocket.OnMessage += OnMessage; webSocket.OnClosed += OnClosed; webSocket.OnError += OnError; } void WebSocketConnect() { if (NYSManager.Instance.DeviceData.ID == 0) return; try { InitWebsocket(); webSocket.Open(); } catch (Exception e) { Debug.Log("连接异常" + e.Message); CloseWebSocket(); } } private void OnError(WebSocket webSocket, string reason) { string errorMsg = string.Empty; CloseWebSocket(); } private void OnClosed(WebSocket webSocket, ushort code, string message) { CloseWebSocket(); } private void WebOnOpen(WebSocket webSocket) { lastHeartTime = Time.time; if (NYSManager.Instance.DeviceData.ID != 0) { SendLoginMsg(NYSManager.Instance.DeviceData.ID); } } private void LoopKeepConnectTimer_Elapsed(object sender, ElapsedEventArgs e) { if (webSocket == null || webSocket.IsOpen == false) { WebSocketConnect(); } SendMsg("ping"); } private void OnMessage(WebSocket webSocket, string message) { WebMessage data = JsonMapper.ToObject<WebMessage>(message); switch (data.message) { case "登录成功": break; case "pong": lastHeartTime = Time.time; break; case "设备控制消息": int subtemp = data.type.IndexOf("_"); string firstField = data.type.Substring(0, subtemp); string secondField = data.type.Substring(subtemp + 1); string lastField = data.type.Substring(data.type.LastIndexOf("_") + 1); Debug.Log("首字符:" + firstField + "中间字符:" + secondField + "最后字符:" + lastField); if (data.type.Contains("GUIDEVIDEO")) { int firstUnderscoreIndex = data.type.IndexOf("_"); string firststr = data.type.Substring(0, firstUnderscoreIndex); int lastUnderscoreIndex = data.type.LastIndexOf("_"); string laststr = data.type.Substring(lastUnderscoreIndex + 1); HJVideoManger.Instance.PlayVideo(firststr, laststr); return; } //暂时断续处理 //START_CONTINUE_SCREEN_PCAT1693184485645 if (data.type.Contains("START_CONTINUE_SCREEN_")) { GetRapidContinue(lastField); return; } //暂时多选处理 if (data.type.Contains("START_SCREEN_MULTIPLE_")) { var quizlist = data.type.Replace("START_SCREEN_MULTIPLE_", "").Split(','); //GameEventArg arg = EventDispatcher.Instance.GetEventArg((int)EventID.HALL_STARTRAPIDTRAINING); List<string> sceneList = new List<string>(); for (int i = 0; i < quizlist.Length; i++) { sceneList.Add(Enums.GetDesc((WEBGameTypeEnum)Enum.Parse(typeof(WEBGameTypeEnum), quizlist[i]))); } //arg.SetArg(1, sceneList); //EventDispatcher.Instance.Dispatch((int)EventID.HALL_STARTRAPIDTRAINING); NYSWebTrainManager.Instance.ChangeState(NYSWebTrainState.RAPIDTRAIN_MULTIPLE, null, sceneList); return; } //设备控制 switch (firstField) { case "CHANGE"://CHANGE_USER if (secondField == "USER") { NYSNetManager.GetDeviceData(NYSManager.Instance.TokenData.Token); if (SceneLoader.Instance.currentSceneIndex == 1) { HallAudio.Instance.PlayAudio("man_prompt"); HJVideoManger.Instance.OffVideo(); } } else if (secondField.Contains("GAMESCENE")) { SkinManager.SaveGameSkin(int.Parse(lastField)); } return; case "RESET"://"RESET_VIEW": InputDevices.GetDeviceAtXRNode(XRNode.Head).subsystem.TryRecenter(); return; case "SKIP"://"SKIP_HOME": //if (SceneLoader.Instance.currentGameSceneName == Enums.GetDesc(WEBGameTypeEnum.EMCA)) //{ // EventDispatcher.Instance.Dispatch((int)EventID.WEBCONTROL_BACKHOMETRIGGER); //} //else //{ // SceneLoader.Instance.BackHall(); //} SceneLoader.Instance.BackHall(); return; case "RESTART": Debug.Log("设备重启"); PXR_Enterprise.ControlSetDeviceAction(DeviceControlEnum.DEVICE_CONTROL_REBOOT, (k) => { Debug.LogError(k == 1 ? $"错误代码{k}:重启失败" : $"错误代码{k}:没有权限"); }); return; case "VOICE": if (lastField.Equals("MAN")) { GameAudioManager.VoiceActor = VoiceActorType.Male; } else { GameAudioManager.VoiceActor = VoiceActorType.Female; } break; } //Debug.Log(firstField+secondField); //游戏控制 switch (secondField) { case "SCREEN": if (firstField == "START") NYSWebTrainManager.Instance.ChangeState(NYSWebTrainState.RAPIDTRAIN_AUTO); else if (firstField == "PAUSE") EventDispatcher.Instance.Dispatch((int)EventID.WEBCONTROL_SCENEPAUSE); else if (firstField == "OVER") { if (data.no == "" || data.no == MmseWriteAnsers.Instance.no) { QuestionManage.Instance.GameOver(); } } else EventDispatcher.Instance.Dispatch((int)EventID.WEBCONTROL_SCENECONTINUE); return; case "SYMBOL_SEARCH": WEBGameType = WEBGameTypeEnum.SYMBOL_SEARCH; break; case "SYMMETRY_SPAN": WEBGameType = WEBGameTypeEnum.SYMMETRY_SPAN; break; case "PORTRAIT_MEMORY": WEBGameType = WEBGameTypeEnum.PORTRAIT_MEMORY; break; case "ORIGAMI_TEST": WEBGameType = WEBGameTypeEnum.ORIGAMI_TEST; break; case "VOCABULARY_TEST": WEBGameType = WEBGameTypeEnum.VOCABULARY_TEST; break; case "MMSE": WEBGameType = WEBGameTypeEnum.MMSE; break; case "MOCA": WEBGameType = WEBGameTypeEnum.MOCA; break; case "EMCA": WEBGameType = WEBGameTypeEnum.EMCA; break; } switch (firstField) { case "START": if (SceneLoader.Instance.currentGameSceneName == Enums.GetDesc(WEBGameTypeEnum.EMCA) && WEBGameType == WEBGameTypeEnum.EMCA) { EventDispatcher.Instance.Dispatch((int)EventID.WEBCONTROL_STARTHOMETRIGGER); } else { NYSWebTrainManager.Instance.ChangeState(NYSWebTrainState.WEBSTART, Enums.GetDesc(WEBGameType), true); } return; case "PAUSE": EventDispatcher.Instance.Dispatch((int)EventID.WEBCONTROL_SCENEPAUSE); break; case "CONTINUE": EventDispatcher.Instance.Dispatch((int)EventID.WEBCONTROL_SCENECONTINUE); break; case "RECALIBRATE": EventDispatcher.Instance.Dispatch((int)EventID.WEBCONTROL_RECALIBRATE); break; case "OVER": //EventDispatcher.Instance.Dispatch((int)EventID.WEBCONTROL_ENDGAME); SceneLoader.Instance.BackHall(); break; } WEBGameType = WEBGameTypeEnum.NULL; break; } } private void GetRapidContinue(string ssid) { NYSNetManager.NYSHTTPRequest($"{NYSNetManager.Instance.GetFullServerURL(CompleteCodeURL + ssid)}", (bool isSuccess, JsonData jsonData) => { if (isSuccess) { if (jsonData != null) { RapidDataPack rdp = JsonMapper.ToObject<RapidDataPack>(jsonData.ToJson()); //GameEventArg arg = EventDispatcher.Instance.GetEventArg((int)EventID.HALL_STARTRAPIDTRAINING); //arg.SetArg(0, ssid); var str = rdp.types.Split(','); List<string> sceneList = new List<string>(); for (int i = 0; i < str.Length; i++) { sceneList.Add(Enums.GetDesc((WEBGameTypeEnum)Enum.Parse(typeof(WEBGameTypeEnum), str[i]))); } //arg.SetArg(1, sceneList); //EventDispatcher.Instance.Dispatch((int)EventID.HALL_STARTRAPIDTRAINING); NYSWebTrainManager.Instance.ChangeState(NYSWebTrainState.RAPIDTRAIN_CONTINUE, ssid, sceneList); } } else { Debug.Log("get RapidContinue failed"); } }, HTTPMethods.Get, "Authorization", NYSManager.Instance.TokenData.Token); } private void OnBinary(WebSocket webSocket, byte[] data) { Debug.Log("接收二进制" + data.Length); } public void SendLoginMsg(int userid) { LonginMsg longinMsg = new LonginMsg(); longinMsg.tempevent = "login"; longinMsg.formUserId = userid; longinMsg.platform = "DEVICE"; string datahead = "brain" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); longinMsg.sign = datahead + NYSUtils.GetMD5("{}".Length + "{}"); string data = longinMsg.SaveToString(); data = data.Remove(data.IndexOf("temp"), 4); webSocket?.Send(data); } /// <summary> /// 发送录制消息给服务器 /// </summary> /// <param name="type"></param> public void SendRecordMsg(string type) { RecordMsg recordMsg = new RecordMsg(); recordMsg.tempevent = "screenRecordTips"; recordMsg.formUserId = NYSManager.Instance.DeviceData.ID; recordMsg.platform = "DEVICE"; recordMsg.type = type; string datahead = "brain" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); recordMsg.sign = datahead + NYSUtils.GetMD5("{}".Length + "{}"); string data = recordMsg.SaveToString(); data = data.Remove(data.IndexOf("temp"), 4); webSocket?.Send(data); } private void SendMsg(string msg) { if (webSocket != null && webSocket.IsOpen) { webSocket.Send(msg); } } private void SceneLoaded(Scene arg0, LoadSceneMode arg1) { if (WEBGameType == WEBGameTypeEnum.NULL || arg0.name.Equals(Enums.GetDesc(WEBGameType)) == false) return; WEBGameType = WEBGameTypeEnum.NULL; } private void OnDestroy() { CloseWebSocket(); } private void CloseWebSocket() { if (webSocket != null) { webSocket.OnOpen -= WebOnOpen; webSocket.OnBinary -= OnBinary; webSocket.OnMessage -= OnMessage; webSocket.OnClosed -= OnClosed; webSocket.OnError -= OnError; webSocket.Close(); webSocket = null; } } private struct LonginMsg { public string tempevent; public int formUserId; public string platform; public string sign; public string SaveToString() { return JsonUtility.ToJson(this); } } private struct RecordMsg { public string tempevent; public int formUserId; public string platform; public string sign; public string type; public string SaveToString() { return JsonUtility.ToJson(this); } } private struct WebMessage { public string message; public string type; public string no; } public enum WEBControlTypeEnum { NULL, CHANGE, RESET, SKIP, START, PAUSE, CONTINUE, RECALIBRATE } private class RapidDataPack { public string types; } } public enum WEBGameTypeEnum { NULL, [Description("QuizEMCA")] EMCA, [Description("QuizSymbolSearch")] SYMBOL_SEARCH, [Description("QuizSymmetrySpan")] SYMMETRY_SPAN, [Description("QuizPortraitMemory")] PORTRAIT_MEMORY, [Description("QuizPaperFolding")] ORIGAMI_TEST, [Description("QuizVocabulary")] VOCABULARY_TEST, [Description("MMSE")] MMSE, [Description("智能MoCA")] MOCA, }