FitnessEquipmentDisplay.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using ANT_Managed_Library;
  5. using System;
  6. public class TrainerCapabilities
  7. {
  8. public int maximumResistance;
  9. public bool basicResistanceNodeSupport;
  10. public bool targetPowerModeSupport;
  11. public bool simulationModeSupport;
  12. }
  13. public class CommandStatus
  14. {
  15. public int lastReceivedCommandId;
  16. public int status;
  17. public int lastReceivedSequenceNumber;
  18. public byte byte_4;
  19. public byte byte_5;
  20. public byte byte_6;
  21. public byte byte_7;
  22. }
  23. public class UserConfiguration
  24. {
  25. public float bicycleWeight;
  26. public float userWeight;
  27. }
  28. public class FitnessEquipmentDisplay : MonoBehaviour
  29. {
  30. public bool autoStartScan = true; //start scan on play
  31. public bool connected = false; //will be set to true once connected
  32. //windows and mac settings
  33. public bool autoConnectToFirstSensorFound = true; //for windows and mac, either connect to the first sensor found
  34. public List<AntDevice> scanResult; //or let you pick a sensor manually in the scanResult list with your own UI and call ConnectToDevice(AntDevice device)
  35. //android settings
  36. public bool useAndroidUI = true; //will open the unified ant+ UI on the android app if set to true, otherwise will connect to the first found device
  37. public bool skipPreferredSearch = true; //- True = Don't automatically connect to user's preferred device, but always go to search for other devices.
  38. //the sensor values we receive fron the onReceiveData event
  39. public float speed; //Instantaneous speed
  40. public float elapsedTime; //Accumulated value of the elapsed time since start of workout in seconds
  41. public int heartRate; //0xFF indicates invalid
  42. public int distanceTraveled; //Accumulated value of the distance traveled since start of workout in meters
  43. public int instantaneousPower; //Stationary Bike specific
  44. public int cadence; //Specific Trainer Data
  45. private TrainerCapabilities trainerCapabilities = new TrainerCapabilities();
  46. private AntChannel backgroundScanChannel;
  47. private AntChannel deviceChannel;
  48. private byte[] pageToSend;
  49. private bool request_page_54 = false;
  50. private bool request_page_55 = false;
  51. private bool request_page_71 = false;
  52. public int deviceID = 0; //set this to connect to a specific device ID
  53. void Start()
  54. {
  55. if (autoStartScan)
  56. StartScan();
  57. }
  58. //Start a background Scan to find the device
  59. public void StartScan()
  60. {
  61. Debug.Log("Looking for ANT + Fitness Equipment sensor");
  62. #if UNITY_ANDROID && !UNITY_EDITOR
  63. //java : connect_fitness(String gameobjectName, boolean useAndroidUI, boolean skipPreferredSearch, int deviceID)
  64. AndroidJNI.AttachCurrentThread();
  65. using (AndroidJavaClass javaClass = new AndroidJavaClass("com.ant.plugin.Ant_Connector")) {
  66. using (AndroidJavaObject activity = javaClass.GetStatic<AndroidJavaObject>("mContext")) {
  67. activity.Call("connect_fitness", this.gameObject.name, useAndroidUI, skipPreferredSearch,deviceID);
  68. }
  69. }
  70. #else
  71. AntManager.Instance.Init();
  72. AntManager.Instance.onDeviceResponse += OnDeviceResponse;
  73. AntManager.Instance.onSerialError += OnSerialError; //if usb dongle is unplugged for example
  74. scanResult = new List<AntDevice>();
  75. backgroundScanChannel = AntManager.Instance.OpenBackgroundScanChannel(0);
  76. backgroundScanChannel.onReceiveData += ReceivedBackgroundScanData;
  77. #endif
  78. }
  79. void OnDeviceResponse(ANT_Response response)
  80. {
  81. Debug.Log("device:" + response.getMessageID().ToString());
  82. }
  83. void OnSerialError(SerialError serialError)
  84. {
  85. Debug.Log("Error:" + serialError.error.ToString());
  86. }
  87. //Android function
  88. void ANTPLUG_ConnectEvent(string resultCode)
  89. {
  90. switch (resultCode)
  91. {
  92. case "SUCCESS":
  93. connected = true;
  94. break;
  95. case "CHANNEL_NOT_AVAILABLE":
  96. //Channel Not Available
  97. break;
  98. case "ADAPTER_NOT_DETECTED":
  99. //ANT Adapter Not Available. Built-in ANT hardware or external adapter required.
  100. Debug.Log("ANT Adapter Not Available. Built-in ANT hardware or external adapter required.");
  101. break;
  102. case "BAD_PARAMS":
  103. //Bad request parameters.
  104. break;
  105. case "OTHER_FAILUR":
  106. //RequestAccess failed. See logcat for details.
  107. break;
  108. case "DEPENDENCY_NOT_INSTALLED":
  109. //You need to install the ANT+ Plugins service or you may need to update your existing version if you already have it.
  110. case "USER_CANCELLED":
  111. //USER_CANCELLED
  112. break;
  113. case "UNRECOGNIZED":
  114. //UNRECOGNIZED. PluginLib Upgrade Required?",
  115. break;
  116. default:
  117. //UNRECOGNIZED
  118. break;
  119. }
  120. }
  121. void ANTPLUG_StateChange(string newDeviceState)
  122. {
  123. switch (newDeviceState)
  124. {
  125. case "DEAD":
  126. connected = false;
  127. break;
  128. case "CLOSED":
  129. break;
  130. case "SEARCHING":
  131. //searching
  132. break;
  133. case "TRACKING":
  134. //tracking
  135. break;
  136. case "PROCESSING_REQUEST":
  137. break;
  138. default:
  139. //UNRECOGNIZED
  140. break;
  141. }
  142. }
  143. void ANTPLUG_Receive_instantaneousHeartRate(string s)
  144. {
  145. heartRate = (int)float.Parse(s) * 2;
  146. }
  147. void ANTPLUG_Receive_elapsedTime(string s)
  148. {
  149. elapsedTime = float.Parse(s);
  150. }
  151. void ANTPLUG_Receive_instantaneousSpeed(string s)
  152. {
  153. speed = float.Parse(s) * 3.6f;
  154. }
  155. void ANTPLUG_Receive_cumulativeDistance(string s)
  156. {
  157. distanceTraveled = (int)float.Parse(s);
  158. }
  159. //Bike trainer
  160. void ANTPLUG_Receive_trainer_instantaneousPower(string s)
  161. {
  162. instantaneousPower = (int)float.Parse(s);
  163. }
  164. void ANTPLUG_Receive_trainer_instantaneousCadence(string s)
  165. {
  166. cadence = (int)float.Parse(s);
  167. }
  168. //receive user config from android
  169. void ANTPLUG_Receive_trainer_userconfig(string jsonstring)
  170. {
  171. // cadence = (int)float.Parse(s);
  172. }
  173. //receive trainer Capabilities from android
  174. void ANTPLUG_Receive_trainer_capabilities(string jsonstring)
  175. {
  176. if (request_page_54 == true)
  177. {
  178. request_page_54 = false;
  179. trainerCapabilities = JsonUtility.FromJson<TrainerCapabilities>(jsonstring);
  180. Debug.Log("max resistance is: " + trainerCapabilities.maximumResistance);
  181. Debug.Log("basicResistanceNodeSupport: " + trainerCapabilities.basicResistanceNodeSupport);
  182. Debug.Log("targetPowerModeSupport: " + trainerCapabilities.targetPowerModeSupport);
  183. Debug.Log("simulationModeSupport: " + trainerCapabilities.simulationModeSupport);
  184. }
  185. }
  186. //receive command status from android
  187. void ANTPLUG_Receive_Trainer_commandStatus(string jsonstring)
  188. {
  189. if (request_page_71 == true)
  190. {
  191. request_page_71 = false;
  192. CommandStatus status = JsonUtility.FromJson<CommandStatus>(jsonstring);
  193. ReadCommandStatus(status);
  194. }
  195. }
  196. //receive user config from android
  197. void ANTPLUG_Receive_userConfiguration(string jsonstring)
  198. {
  199. if (request_page_55 == true)
  200. {
  201. request_page_55 = false;
  202. UserConfiguration conf = JsonUtility.FromJson<UserConfiguration>(jsonstring);
  203. ReadUserConFig(conf);
  204. }
  205. }
  206. //Windows and mac
  207. //If the device is found
  208. void ReceivedBackgroundScanData(Byte[] data)
  209. {
  210. byte deviceType = (data[12]); // extended info Device Type byte
  211. Debug.Log("background scan found device type:" + deviceType);
  212. switch (deviceType)
  213. {
  214. case AntplusDeviceType.FitnessEquipment:
  215. {
  216. int deviceNumber = (data[10]) | data[11] << 8;
  217. byte transType = data[13];
  218. foreach (AntDevice d in scanResult)
  219. {
  220. if (d.deviceNumber == deviceNumber && d.transType == transType) //device already found
  221. return;
  222. }
  223. Debug.Log("FitnessEquipmentfound " + deviceNumber);
  224. AntDevice foundDevice = new AntDevice();
  225. foundDevice.deviceType = deviceType;
  226. foundDevice.deviceNumber = deviceNumber;
  227. foundDevice.transType = transType;
  228. foundDevice.period = 8192;
  229. foundDevice.radiofreq = 57;
  230. foundDevice.name = "FitnessEquipment(" + foundDevice.deviceNumber + ")";
  231. scanResult.Add(foundDevice);
  232. if (autoConnectToFirstSensorFound)
  233. {
  234. ConnectToDevice(foundDevice);
  235. }
  236. break;
  237. }
  238. default:
  239. {
  240. break;
  241. }
  242. }
  243. }
  244. void ConnectToDevice(AntDevice device)
  245. {
  246. AntManager.Instance.CloseBackgroundScanChannel();
  247. byte channelID = AntManager.Instance.GetFreeChannelID();
  248. deviceChannel = AntManager.Instance.OpenChannel(ANT_ReferenceLibrary.ChannelType.BASE_Slave_Receive_0x00, channelID, (ushort)device.deviceNumber, device.deviceType, device.transType, (byte)device.radiofreq, (ushort)device.period, false);
  249. connected = true;
  250. deviceChannel.onReceiveData += Data;
  251. deviceChannel.onChannelResponse += ChannelResponse;
  252. deviceChannel.hideRXFAIL = true;
  253. }
  254. //Deal with the received Data
  255. int prevDistance = 0;
  256. float prevTime = 0;
  257. bool firstDistanceInfo;
  258. bool firstTimeInfo;
  259. float pTime = 0;
  260. public void Data(Byte[] data)
  261. {
  262. Debug.Log(Time.time - pTime);
  263. pTime = Time.time;
  264. // General FE Data
  265. if (data[0] == 16)
  266. {
  267. if (prevDistance < data[3])
  268. distanceTraveled += data[3] - prevDistance;
  269. if (firstDistanceInfo == false)
  270. {
  271. distanceTraveled -= data[3];
  272. firstDistanceInfo = true;
  273. }
  274. prevDistance = data[3];
  275. // elapsed time
  276. if (prevTime < data[2])
  277. elapsedTime += (data[2] - prevTime) * 0.25f;
  278. if (firstTimeInfo == false)
  279. {
  280. elapsedTime -= data[2] * 0.25f;
  281. firstTimeInfo = true;
  282. }
  283. prevTime = data[2];
  284. //heart rate
  285. heartRate = data[6];
  286. //speed
  287. speed = ((data[4]) | data[5] << 8) * 0.0036f;
  288. }
  289. if (data[0] == 25)
  290. { // Specific Trainer Data
  291. cadence = data[2];
  292. int nibble2 = (byte)(data[6] & 0xf);
  293. instantaneousPower = (data[5]) | nibble2 << 8;
  294. }
  295. else if (data[0] == 54 && request_page_54 == true)
  296. { //response to trainer capabilities request from PC & MAC
  297. request_page_54 = false;
  298. trainerCapabilities.maximumResistance = (data[5]) | data[6] << 8;
  299. trainerCapabilities.basicResistanceNodeSupport = (data[7] >> 0) != 0;
  300. trainerCapabilities.targetPowerModeSupport = (data[7] >> 1) != 0;
  301. trainerCapabilities.simulationModeSupport = (data[7] >> 2) != 0;
  302. Debug.Log("max resistance is: " + trainerCapabilities.maximumResistance + " N");
  303. Debug.Log("basicResistanceNodeSupport: " + trainerCapabilities.basicResistanceNodeSupport);
  304. Debug.Log("targetPowerModeSupport: " + trainerCapabilities.targetPowerModeSupport);
  305. Debug.Log("simulationModeSupport: " + trainerCapabilities.simulationModeSupport);
  306. }
  307. else if (data[0] == 55 && request_page_55 == true)
  308. { //user configuration data page
  309. request_page_55 = false;
  310. int bikeWeight = (data[4] >> 4) | (data[5] << 4);
  311. int riderWeight = (data[1]) | (data[2] << 8);
  312. UserConfiguration conf = new UserConfiguration();
  313. conf.userWeight = riderWeight * 0.01f;
  314. conf.bicycleWeight = bikeWeight * 0.05f;
  315. ReadUserConFig(conf);
  316. }
  317. else if (data[0] == 71 & request_page_71 == true)
  318. { //command status data page
  319. request_page_71 = false;
  320. CommandStatus status = new CommandStatus();
  321. status.lastReceivedCommandId = data[1];
  322. status.lastReceivedSequenceNumber = data[2];
  323. status.status = data[3];
  324. status.byte_4 = data[4];
  325. status.byte_5 = data[5];
  326. status.byte_6 = data[6];
  327. status.byte_7 = data[7];
  328. ReadCommandStatus(status);
  329. }
  330. }
  331. private static void ReadCommandStatus(CommandStatus status)
  332. {
  333. Debug.Log("Last_Received_Command_ID: " + status.lastReceivedCommandId);
  334. Debug.Log("sequence_number: " + status.lastReceivedSequenceNumber);
  335. Debug.Log("command_status: " + status.status);
  336. if (status.lastReceivedCommandId == 51)
  337. { //we are in Track resistance Mode
  338. int rawSlopeValue = (status.byte_5) | (status.byte_6 << 8);
  339. float slope = (rawSlopeValue - 20000) / 100;
  340. Debug.Log("current slope is: " + slope);
  341. }
  342. else if (status.lastReceivedCommandId == 48)
  343. { //we are in basic resistance Mode
  344. int resistance = status.byte_7;
  345. Debug.Log("Total Resistance is: " + (resistance / 2) + "%");
  346. }
  347. else if (status.lastReceivedCommandId == 49)
  348. { //we are in target power Mode
  349. int power = (status.byte_6) | (status.byte_7 << 8);
  350. Debug.Log("Target power: " + power / 4);
  351. }
  352. }
  353. private static void ReadUserConFig(UserConfiguration config)
  354. {
  355. Debug.Log("rider weight is: " + config.userWeight);
  356. Debug.Log("bike weight is: " + config.bicycleWeight);
  357. }
  358. //set the trainer in resistance mode, if the trainer has basicResistanceNodeSupport
  359. public void SetTrainerResistance(int resistance)
  360. {
  361. if (!connected)
  362. return;
  363. #if UNITY_ANDROID && !UNITY_EDITOR
  364. AndroidJNI.AttachCurrentThread();
  365. using (AndroidJavaClass javaClass = new AndroidJavaClass("com.ant.plugin.Ant_Connector")) {
  366. using (AndroidJavaObject activity = javaClass.GetStatic<AndroidJavaObject>("mContext")) {
  367. activity.Call("Set_fitness_Resistance",resistance);
  368. }
  369. }
  370. #else
  371. pageToSend = new byte[8] { 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, (byte)(resistance * 2) };//unit is 0.50%
  372. deviceChannel.sendAcknowledgedData(pageToSend);
  373. #endif
  374. }
  375. //set the trainer in target power if the trainer has targetPowerModeSupport
  376. public void SetTrainerTargetPower(int targetpower)
  377. {
  378. if (!connected)
  379. return;
  380. #if UNITY_ANDROID && !UNITY_EDITOR
  381. AndroidJNI.AttachCurrentThread();
  382. using (AndroidJavaClass javaClass = new AndroidJavaClass("com.ant.plugin.Ant_Connector")) {
  383. using (AndroidJavaObject activity = javaClass.GetStatic<AndroidJavaObject>("mContext")) {
  384. activity.Call("Set_fitness_TargetPower",targetpower);
  385. }
  386. }
  387. #else
  388. Debug.Log("sending target power of " + targetpower + " watts to trainer");
  389. byte LSB = (byte)(targetpower * 4);
  390. byte MSB = (byte)((targetpower * 4 >> 8));
  391. pageToSend = new byte[8] { 0x31, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, LSB, MSB };//unit is 0.25w
  392. deviceChannel.sendAcknowledgedData(pageToSend);
  393. #endif
  394. }
  395. //set the trainer in simulation mode if the trainer has simulationModeSupport
  396. public void SetTrainerSlope(int slope)
  397. {
  398. if (!connected)
  399. return;
  400. slope = Mathf.Clamp(slope, -200, 200);
  401. #if UNITY_ANDROID && !UNITY_EDITOR
  402. AndroidJNI.AttachCurrentThread();
  403. using (AndroidJavaClass javaClass = new AndroidJavaClass("com.ant.plugin.Ant_Connector")) {
  404. using (AndroidJavaObject activity = javaClass.GetStatic<AndroidJavaObject>("mContext")) {
  405. activity.Call("Set_fitness_SimulationMode",slope);
  406. }
  407. }
  408. #else
  409. slope += 200;
  410. // Units are 0.01%
  411. slope *= 100;
  412. int grade = (int)slope;
  413. byte gradeLsb = (byte)(grade);
  414. byte gradeMsb = (byte)(grade >> 8);
  415. byte[] pageToSend = new byte[8] { 0x33, 0xFF, 0xFF, 0xFF, 0xFF, gradeLsb, gradeMsb, 0xFF };
  416. deviceChannel.sendAcknowledgedData(pageToSend);
  417. #endif
  418. }
  419. //send use configuration
  420. public void SetTrainerUserConfiguration(float bikeWeight, float userWeight)
  421. {
  422. if (!connected)
  423. return;
  424. #if UNITY_ANDROID && !UNITY_EDITOR
  425. AndroidJNI.AttachCurrentThread();
  426. using (AndroidJavaClass javaClass = new AndroidJavaClass("com.ant.plugin.Ant_Connector")) {
  427. using (AndroidJavaObject activity = javaClass.GetStatic<AndroidJavaObject>("mContext")) {
  428. activity.Call("Set_fitness_UserConfiguration",bikeWeight,userWeight);
  429. }
  430. }
  431. #else
  432. int rawBikeWeight = Mathf.FloorToInt((bikeWeight / 0.05f));
  433. int rawUserWeight = Mathf.FloorToInt((userWeight / 0.01f));
  434. byte weightLSB = (byte)(rawUserWeight);
  435. byte weightMSB = (byte)(rawUserWeight >> 8);
  436. byte bikeweightLSB = (byte)(rawBikeWeight << 4 | 0xF);
  437. byte bikeweightMSB = (byte)(rawBikeWeight >> 4);
  438. pageToSend = new byte[8] { 0x37, weightLSB, weightMSB, 0xFF, bikeweightLSB, bikeweightMSB, 0xFF, 0x00 };
  439. deviceChannel.sendAcknowledgedData(pageToSend);
  440. #endif
  441. }
  442. //check if the last command was successfull and get current mode data (target power, resistandce, slope)
  443. public void RequestCommandStatus()
  444. {
  445. if (!connected)
  446. return;
  447. request_page_71 = true;
  448. #if UNITY_ANDROID && !UNITY_EDITOR
  449. AndroidJNI.AttachCurrentThread();
  450. using (AndroidJavaClass javaClass = new AndroidJavaClass("com.ant.plugin.Ant_Connector")) {
  451. using (AndroidJavaObject activity = javaClass.GetStatic<AndroidJavaObject>("mContext")) {
  452. activity.Call("Request_CommandStatus");
  453. }
  454. }
  455. #else
  456. byte[] pageToSend = new byte[8] { 0x46, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x47, 0x01 };
  457. deviceChannel.sendAcknowledgedData(pageToSend);
  458. #endif
  459. }
  460. //check the supported mode
  461. public void RequestTrainerCapabilities()
  462. {
  463. if (!connected)
  464. return;
  465. request_page_54 = true;
  466. #if UNITY_ANDROID && !UNITY_EDITOR
  467. AndroidJNI.AttachCurrentThread();
  468. using (AndroidJavaClass javaClass = new AndroidJavaClass("com.ant.plugin.Ant_Connector")) {
  469. using (AndroidJavaObject activity = javaClass.GetStatic<AndroidJavaObject>("mContext")) {
  470. activity.Call("Request_trainer_capabilities");
  471. }
  472. }
  473. #else
  474. byte[] pageToSend = new byte[8] { 0x46, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x36, 0x01 };
  475. deviceChannel.sendAcknowledgedData(pageToSend);
  476. #endif
  477. }
  478. //get the user config
  479. public void RequestUserConfig()
  480. {
  481. if (!connected)
  482. return;
  483. request_page_55 = true;
  484. #if UNITY_ANDROID && !UNITY_EDITOR
  485. AndroidJNI.AttachCurrentThread();
  486. using (AndroidJavaClass javaClass = new AndroidJavaClass("com.ant.plugin.Ant_Connector")) {
  487. using (AndroidJavaObject activity = javaClass.GetStatic<AndroidJavaObject>("mContext")) {
  488. activity.Call("Request_UserConfig");
  489. }
  490. }
  491. #else
  492. byte[] pageToSend = new byte[8] { 0x46, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x37, 0x01 };
  493. deviceChannel.sendAcknowledgedData(pageToSend);
  494. #endif
  495. }
  496. void ChannelResponse(ANT_Response response)
  497. {
  498. if (response.getChannelEventCode() == ANT_ReferenceLibrary.ANTEventID.EVENT_TRANSFER_TX_FAILED_0x06)
  499. {
  500. if (pageToSend != null)
  501. deviceChannel.sendAcknowledgedData(pageToSend); //send the page again if the transfer failed
  502. }
  503. }
  504. }