diff --git a/Assets/Scripts/Devices/Ant/Interfaces/IRowerDevice.cs b/Assets/Scripts/Devices/Ant/Interfaces/IRowerDevice.cs index 5f626e4d..c820b82d 100644 --- a/Assets/Scripts/Devices/Ant/Interfaces/IRowerDevice.cs +++ b/Assets/Scripts/Devices/Ant/Interfaces/IRowerDevice.cs @@ -1,4 +1,5 @@ -using Assets.Scripts.Devices.Ble.Characteristic; +using Assets.Scripts.Ble.Commands; +using Assets.Scripts.Devices.Ble.Characteristic; using System; using System.Collections.Generic; using System.Linq; @@ -14,5 +15,6 @@ namespace Assets.Scripts.Devices.Ant.Interfaces void Reset(); void SetResistanceLevel(ushort v); void C2GetStatus(byte[] bs); + void SendCommand(RowerCommand command,int data = 0); } } diff --git a/Assets/Scripts/Devices/Ble/BitConvertHelper.cs b/Assets/Scripts/Devices/Ble/BitConvertHelper.cs index 1c2b7473..a52b7229 100644 --- a/Assets/Scripts/Devices/Ble/BitConvertHelper.cs +++ b/Assets/Scripts/Devices/Ble/BitConvertHelper.cs @@ -48,5 +48,13 @@ namespace Assets.Scripts { return (uint)((int)bytes[startIndex] | (int)bytes[startIndex + 1] << 8 | (int)bytes[startIndex + 2] << 16); } + public static byte[] HexToByteArray(string hex) + { + hex=hex.Replace(" ", ""); + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); + } } } diff --git a/Assets/Scripts/Devices/Ble/Characteristic/C2RowerData.cs b/Assets/Scripts/Devices/Ble/Characteristic/C2RowerData.cs index 93e63b7b..9d7103f4 100644 --- a/Assets/Scripts/Devices/Ble/Characteristic/C2RowerData.cs +++ b/Assets/Scripts/Devices/Ble/Characteristic/C2RowerData.cs @@ -11,6 +11,11 @@ namespace Assets.Scripts.Devices.Ble.Characteristic { public class C2RowerData : ICharacteristic, IRowerCommonData { + public static string JUSTROW_HEX = "F1 76 07 01 01 01 13 02 01 01 61 F2"; + public static string TERMINATEWORKOUT_HEX = "F1 76 04 13 02 01 02 62 F2"; + public static string FIXEDDISTANCE_HEX = "F1 76 18 01 01 03 03 05 80 00 00 07 D0 05 05 80 00 00 01 90 14 01 01 13 02 01 01 28 F2"; + public static string FIXEDTIME_HEX = "F1 76 07 01 01 01 13 02 01 01 61 F2"; + public Guid Uuid => ServiceUuids.Characteristics.C2RowerData35; public Guid ServiceUuid => ServiceUuids.Characteristics.C2Service; @@ -133,17 +138,19 @@ namespace Assets.Scripts.Devices.Ble.Characteristic InstantaneousPower = BitConvertHelper.ToUInt16(data, 4); //TotalEnergy = BitConvertHelper.ToUInt16(data, 6); } - //else if (data[0] == 0x39) - //{ - // AverageStrokeRate = (int)data[11]; - // ResistanceLevel = (int)data[16]; - //} + else if (data[0] == 0x39) + { + ElapsedTime = (int)(Convert.ToDouble(BitConvertHelper.ToUInt24(data, 1)) / 100); + TotalDistance = (uint)(Convert.ToDouble(BitConvertHelper.ToUInt24(data, 4)) / 10); + CompleteEvent?.Invoke(this, null); + } //else if (data[0] == 34) //{ // isReadyStatus = data[2] == 1 || data[2] == 129; //} } public event EventHandler StartEvent; + public event EventHandler CompleteEvent; private double LbsToNewton(double lbs,bool isKg = false) => 4.4482216 * lbs * (isKg ? (1f / 9.8f) : 1f); public void SetUnavailable() { diff --git a/Assets/Scripts/Devices/Ble/Characteristic/FtmsRowerData.cs b/Assets/Scripts/Devices/Ble/Characteristic/FtmsRowerData.cs index 804d86fd..109160b2 100644 --- a/Assets/Scripts/Devices/Ble/Characteristic/FtmsRowerData.cs +++ b/Assets/Scripts/Devices/Ble/Characteristic/FtmsRowerData.cs @@ -94,6 +94,8 @@ namespace Assets.Scripts.Devices.Ble.Characteristic public event EventHandler PullChanged; //开始事件 public event EventHandler StartEvent; + //结束事件 + public event EventHandler CompleteEvent; //日志事件 public event EventHandler LogEvent; private ushort _pullValue; diff --git a/Assets/Scripts/Devices/Ble/Commands/CommandResponseCode.cs b/Assets/Scripts/Devices/Ble/Commands/CommandResponseCode.cs index 338b40fd..7d5acc7a 100644 --- a/Assets/Scripts/Devices/Ble/Commands/CommandResponseCode.cs +++ b/Assets/Scripts/Devices/Ble/Commands/CommandResponseCode.cs @@ -6,7 +6,14 @@ using System.Threading.Tasks; namespace Assets.Scripts.Ble.Commands { - public enum CommandResponseCode + public enum RowerCommand + { + JustRow = 1, + FixedDistance=2, + FixedTime = 3, + } + + public enum CommandResponseCode { // Token: 0x04000FEB RID: 4075 Success = 1, diff --git a/Assets/Scripts/Devices/Ble/Devices/FtmsRower.cs b/Assets/Scripts/Devices/Ble/Devices/FtmsRower.cs index 1779201c..2bebb7af 100644 --- a/Assets/Scripts/Devices/Ble/Devices/FtmsRower.cs +++ b/Assets/Scripts/Devices/Ble/Devices/FtmsRower.cs @@ -1,4 +1,5 @@ using Assets.Scripts.Ble; +using Assets.Scripts.Ble.Commands; using Assets.Scripts.Devices.Ant.Interfaces; using Assets.Scripts.Devices.Ble.Characteristic; using Assets.Scripts.Devices.Ble.Interfaces; @@ -21,7 +22,7 @@ namespace Assets.Scripts.Devices.Ble.Devices public C2RowerData c2RowerData { get => _c2RowerData; } private List Services; private BleCharacteristicInfo controlPointCharacteristic; - private BleCharacteristicInfo c2Control; + private BleCharacteristicInfo c2Control { get; set; } public FtmsRower(BlePeripheralInfo peripheralInfo, IBleWinHwInterface bleWinHwInterface) : base(peripheralInfo, bleWinHwInterface, Ant.SensorType.Rower) { @@ -103,7 +104,7 @@ namespace Assets.Scripts.Devices.Ble.Devices } else if (character.MatchGuid(ServiceUuids.Characteristics.C2RowerControl)) { - Debug.Log("c2划船机控制台"); + Debug.Log($"c2划船机控制台{character.Id}"); this.c2Control = character; } } @@ -173,6 +174,26 @@ namespace Assets.Scripts.Devices.Ble.Devices } } + public void SendCommand(RowerCommand command,int data = 0) + { + if (!C2RowerData.IsEnabled) + return; + switch (command) + { + case RowerCommand.JustRow: + SetJustRow(); + break; + case RowerCommand.FixedDistance: + SetFixedDistance(data); + break; + case RowerCommand.FixedTime: + SetFixedTime(data); + break; + default: + break; + } + } + public void Reset() { if (C2RowerData.IsEnabled == true) @@ -196,18 +217,97 @@ namespace Assets.Scripts.Devices.Ble.Devices } } + /// + /// + /// + /// 训练类型:0,1,2(里程不分段),3(里程分段),4(时间不分段),5,6,7,8~13 + public void SetWorkoutType(int workoutType) + { + var typeHex = workoutType.ToString("X2"); + var checkSum = "00"; + var data = BitConvertHelper.HexToByteArray($"F1 76 03 01 01 {typeHex} {checkSum} F2");//"F1 76 03 01 01 03 76 F2"//03? + checkSum = GetChecksumHexString(data); + data = BitConvertHelper.HexToByteArray($"F1 76 03 01 01 {typeHex} {checkSum} F2"); + hwInterface.WriteCharacteristic(this.c2Control, data); + } + public void SetConfigureWorkout() + { + var data = BitConvertHelper.HexToByteArray("F1 76 03 14 01 01 61 F2"); //03? + hwInterface.WriteCharacteristic(this.c2Control, data); + } + public void SetScreenState() + { + var data = BitConvertHelper.HexToByteArray("F1 76 04 13 02 01 01 63 F2"); //04? + hwInterface.WriteCharacteristic(this.c2Control, data); + } + public void SetPoll() + { + var data = BitConvertHelper.HexToByteArray($"F1 76 07 8D 93 A3 A0 A8 B3 B6 C1 F2"); + hwInterface.WriteCharacteristic(this.c2Control, data); + } + public void SetJustRow() + { + var data = BitConvertHelper.HexToByteArray("F1 76 07 01 01 01 13 02 01 01 61 F2"); + Debug.Log($"SetJustRow:{string.Join(",", data)}"); + hwInterface.WriteCharacteristic(this.c2Control, data); + SetScreenState(); + } + public void SetFixedDistance(int distance) + { + if (distance < 100) + return; + + SetWorkoutType(3); + var hexditance = distance.ToString("X4"); + var checkSum = "00"; + var data = BitConvertHelper.HexToByteArray($"F1 76 07 03 05 80 00 00 {hexditance} {checkSum} F2");//F1 76 07 03 05 80 00 00 {hexditance} {checkSum} F2 + checkSum = GetChecksumHexString(data); + data = BitConvertHelper.HexToByteArray($"F1 76 07 03 05 80 00 00 {hexditance} {checkSum} F2");//07? + hwInterface.WriteCharacteristic(this.c2Control, data); + + SetConfigureWorkout(); + SetScreenState(); + + } + private string GetChecksumHexString(byte[] data) + { + var res = data[1]; + for (int i = 2; i < data.Length-2; i++) + { + res ^= data[i]; + } + return res.ToString("X2"); + } + public void SetFixedTime(int mins) + { + SetWorkoutType(5); + + var timeHex = (mins * 100).ToString("X6"); + var checkNum = "00"; + var data = BitConvertHelper.HexToByteArray($"F1 76 07 03 05 00 00 {timeHex} {checkNum} F2"); + checkNum = GetChecksumHexString(data); + data = BitConvertHelper.HexToByteArray($"F1 76 07 03 05 00 00 {timeHex} {checkNum} F2"); + hwInterface.WriteCharacteristic(this.c2Control, data); + + SetConfigureWorkout(); + SetScreenState(); + } + public void SetResistanceLevel(ushort v) { if (C2RowerData.IsEnabled == true) { - //等对csafe协议研究透彻后写 + ////等对csafe协议研究透彻后写 //if (this.c2Control != null) //{ - // var data = new byte[] { 0x29 }.Concat(BitConverter.GetBytes(v)).ToArray(); - // byte checksum = (byte)((byte)(data[0] ^ data[1]) ^ data[2]); - // var r = new byte[] { 0xF1 }.Concat(data).Concat(new byte[] { checksum, 0xf2 }) .ToArray(); - // Debug.Log($"设置阻力{v}, {string.Join(",", r)}"); - // hwInterface.WriteCharacteristic(this.c2Control, r); + //02312131 = > 03010103 + //var data = new byte[] { 0x76 }.Concat(BitConverter.GetBytes(v)).ToArray(); + //byte checksum = (byte)((byte)(data[0] ^ data[1]) ^ data[2]); + //var r = new byte[] { 0xF1 }.Concat(data).Concat(new byte[] { checksum, 0xf2 }).ToArray(); + //Debug.Log($"设置阻力{v}, {string.Join(",", r)}"); + // + // var data = BitConvertHelper.HexToByteArray("F1 76 07 01 01 01 13 02 01 01 61 F2"); + // hwInterface.WriteCharacteristic(this.c2Control, data); //} } else diff --git a/Assets/Scripts/Devices/Ble/Interfaces/IRowerCommonData.cs b/Assets/Scripts/Devices/Ble/Interfaces/IRowerCommonData.cs index 8bbb6c70..1bd1dad3 100644 --- a/Assets/Scripts/Devices/Ble/Interfaces/IRowerCommonData.cs +++ b/Assets/Scripts/Devices/Ble/Interfaces/IRowerCommonData.cs @@ -45,6 +45,7 @@ namespace Assets.Scripts.Devices.Ble.Interfaces int ResistanceLevel { get; set; } void Reset(); event EventHandler StartEvent; + event EventHandler CompleteEvent;//结束事件 event EventHandler RowerResChanged; } } diff --git a/Assets/Scripts/UI/Prefab/Panel/RowerHomeScript.cs b/Assets/Scripts/UI/Prefab/Panel/RowerHomeScript.cs index fab8d476..5c51a796 100644 --- a/Assets/Scripts/UI/Prefab/Panel/RowerHomeScript.cs +++ b/Assets/Scripts/UI/Prefab/Panel/RowerHomeScript.cs @@ -20,6 +20,7 @@ using UnityEngine; using UnityEngine.Android; using UnityEngine.UI; using static RowerTaskPanel; +using Assets.Scripts.Ble.Commands; public class RowerHomeScript : PFUIPanel { @@ -57,6 +58,7 @@ public class RowerHomeScript : PFUIPanel { RowerData.PullChanged -= PaintPullCurve; RowerData.StartEvent -= StartFunc; + RowerData.CompleteEvent -= CompelteFunc; RowerData.RowerResChanged -= ResChanged; } C2RowerData.EnableChanged -= ModeChanged; @@ -256,10 +258,12 @@ public class RowerHomeScript : PFUIPanel //UIManager.ShowRowerWelldone("C0F81E83-120B-4A2C-AD0E-8BC1B8EB3E74", Init); //return; if (checkRowing()) return; - if (C2RowerData.IsEnabled == true && C2RowerData.rowerType != null) return; + //if (C2RowerData.IsEnabled == true && C2RowerData.rowerType != null) return; UIManager.ShowRowerTaskPanel(type => { rowerType = type; + if(C2RowerData.IsEnabled) + HandleC2RowerTaskPanel(type);//处理app自定义课程同步到c2划船机 HandleSelectType(); }, rowerType); }, false); @@ -292,12 +296,33 @@ public class RowerHomeScript : PFUIPanel RowerData.PullChanged += PaintPullCurve; RowerData.StartEvent -= StartFunc; RowerData.StartEvent += StartFunc; + RowerData.CompleteEvent -= CompelteFunc; + RowerData.CompleteEvent += CompelteFunc; } rowerType = new RowerType { type = 1, value = 500 }; HandleSelectType(); Init(); isFirstReset = false; } + void HandleC2RowerTaskPanel(RowerType rowerType) + { + if (!C2RowerData.IsEnabled) + return; + RowerCommand currentCommand = RowerCommand.JustRow; + if (rowerType.type == 0 && rowerType.value == 0) + { + currentCommand = RowerCommand.JustRow; + } + if (rowerType.type == 1 && rowerType.value >= 100) + { + currentCommand = RowerCommand.FixedDistance; + } + if (rowerType.type == 2 && rowerType.value > 0) + { + currentCommand = RowerCommand.FixedTime; + } + Rower?.SendCommand(currentCommand, (int)rowerType.value); + } IEnumerator SetResistanceLevel(ushort res) { @@ -523,6 +548,26 @@ public class RowerHomeScript : PFUIPanel btnStart.GetComponent().sprite = spriteDict["Untagged"]; btnStart.tag = "Untagged"; } + + private void CompelteFunc(object sender, EventArgs e) + { + var rowdata = (IRowerCommonData)sender; + if (rowdata != RowerData) + return; + + var heartRate = HeartRate ?? 0; + var energy = RowerData.TotalEnergy; + var strokeCount = RowerData.StrokeCount; + var power = RowerData.InstantaneousPower; + var rate = RowerData.StrokeRate; + + KMText.text = "0"; + records.Add($"{strokeCount},{RowerData.ElapsedTime},{rowerType.value},{RowerData.InstantaneousPower},{RowerData.InstantaneousPace},{RowerData.StrokeRate},{RowerData.ResistanceLevel},{heartRate},{energy},{RowerData.AveragePower},{RowerData.ElapsedTime}"); + var tmpdata = new TempRowerCalc() { strokeCount = strokeCount, pace = RowerData.InstantaneousPace, power = power, rate = rate, heartRate = heartRate, distance = (int)rowerType.value, energy = energy }; + values.Add(tmpdata); + SendDataToRace(tmpdata); + HandleSaveDirect(); + } private bool SaveFunc(RowerRecordModel model, List files) { if (Application.internetReachability == NetworkReachability.NotReachable) @@ -764,6 +809,8 @@ public class RowerHomeScript : PFUIPanel RowerData.PullChanged += PaintPullCurve; RowerData.StartEvent -= StartFunc; RowerData.StartEvent += StartFunc; + RowerData.CompleteEvent -= CompelteFunc; + RowerData.CompleteEvent += CompelteFunc; RowerData.RowerResChanged -= ResChanged; RowerData.RowerResChanged += ResChanged; } @@ -868,23 +915,12 @@ public class RowerHomeScript : PFUIPanel var power = RowerData.InstantaneousPower; var rate = RowerData.StrokeRate; - truelyTime++; + Debug.Log($"ElapsedTime :{RowerData.ElapsedTime} : {truelyTime}-{RowerData.TotalDistance}"); TempRowerCalc tmpdata = null; - //解决里程训练无法结束的问题 - var c2notStop = rowerType.type == 1 && (rowerType.value-totalDistance) <= 5; - if (c2notStop) - { - distance = (int)rowerType.value; - KMText.text = "0"; - records.Add($"{strokeCount},{RowerData.ElapsedTime},{rowerType.value},{RowerData.InstantaneousPower},{RowerData.InstantaneousPace},{RowerData.StrokeRate},{RowerData.ResistanceLevel},{heartRate},{energy},{RowerData.AveragePower},{truelyTime}"); - tmpdata = new TempRowerCalc() { strokeCount = strokeCount, pace = RowerData.InstantaneousPace, power = power, rate = rate, heartRate = heartRate, distance = (int)rowerType.value, energy = energy }; - values.Add(tmpdata); - SendDataToRace(tmpdata); - HandleSaveDirect(); - return; - } + //解决C2里程训练无法结束的问题 + var c2notStop = C2RowerData.IsEnabled && rowerType.type == 1 && (rowerType.value-totalDistance) <= 5; //里程停止逻辑 if (totalDistance == RowerData.TotalDistance && !c2notStop) { @@ -1024,15 +1060,19 @@ public class RowerHomeScript : PFUIPanel KMText.text = totalDistance.ToString(); } - records.Add($"{strokeCount},{RowerData.ElapsedTime},{distance},{RowerData.InstantaneousPower},{RowerData.InstantaneousPace},{RowerData.StrokeRate},{RowerData.ResistanceLevel},{heartRate},{energy},{RowerData.AveragePower},{truelyTime}"); - tmpdata = new TempRowerCalc() { strokeCount = strokeCount, pace = pace, power = power, rate = rate, heartRate = heartRate, distance = distance, energy = energy }; - values.Add(tmpdata); - SendDataToRace(tmpdata); + if (truelyTime > 0) + { + records.Add($"{strokeCount},{RowerData.ElapsedTime},{distance},{RowerData.InstantaneousPower},{RowerData.InstantaneousPace},{RowerData.StrokeRate},{RowerData.ResistanceLevel},{heartRate},{energy},{RowerData.AveragePower},{truelyTime}"); + tmpdata = new TempRowerCalc() { strokeCount = strokeCount, pace = pace, power = power, rate = rate, heartRate = heartRate, distance = distance, energy = energy }; + values.Add(tmpdata); + SendDataToRace(tmpdata); + } if (ticks % 5 == 0) { SaveRealTimes();//实时保存数据 } + truelyTime++; } //检查本地数据 int historyDistance = 0,historyStrokeCount = 0,historyEnergy = 0; @@ -1337,10 +1377,9 @@ public class RowerHomeScript : PFUIPanel var rate = RowerData.StrokeRate; var pace = RowerData.InstantaneousPace; var strokeCount = RowerData.StrokeCount + historyStrokeCount; - var distance = (int)RowerData.TotalDistance + historyDistance; var energy = RowerData.TotalEnergy + historyEnergy; var heartRate = HeartRate ?? 0; - if (rowerType.type == 1 && (rowerType.value - totalDistance) <= 5 && !createTime.HasValue) + if (rowerType.type == 1 && totalDistance >= rowerType.value && !createTime.HasValue) { openTimer = false; KMText.text = "0";