diff --git a/ProftaakRH/BLEHandler.cs b/ProftaakRH/BLEHandler.cs new file mode 100644 index 0000000..da2d90a --- /dev/null +++ b/ProftaakRH/BLEHandler.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avans.TI.BLE; +using System.Threading; +using System.Security.Cryptography; + +namespace Hardware +{ + /// + /// BLEHandler class that handles connection and traffic to and from the bike + /// + class BLEHandler + { + IDataConverter dataConverter; + private BLE bleBike; + private BLE bleHeart; + public bool Running { get; set; } + + /// + /// Makes a new BLEHandler object + /// + /// the dataconverter object + public BLEHandler(IDataConverter dataConverter) + { + this.dataConverter = dataConverter; + bool running = false; + } + + /// + /// Checks for available devices to connect to, and if one is found, it connects to it + /// + public void Connect() + { + BLE bleBike = new BLE(); + Thread.Sleep(1000); // We need some time to list available devices + + // List available devices + List bleBikeList = bleBike.ListDevices(); + Console.WriteLine("Devices found: "); + foreach (var name in bleBikeList) + { + Console.WriteLine(name); + if (name.Contains("Avans Bike")) + { + Console.WriteLine("connecting to {0}", name); + Connect(name); + break; + + } + } + } + + /// + /// Connects to the device with the given name + /// + /// The name of the device to connect to + public async void Connect(string deviceName) + { + int errorCode = 0; + bleBike = new BLE(); + bleHeart = new BLE(); + + errorCode = errorCode = await bleBike.OpenDevice(deviceName); + if (errorCode > 0) + { + disposeBLE(); + return; + } + + // Set service + errorCode = await bleBike.SetService("6e40fec1-b5a3-f393-e0a9-e50e24dcca9e"); + if (errorCode > 0) + { + disposeBLE(); + return; + } + + // Subscribe + bleBike.SubscriptionValueChanged += BleBike_SubscriptionValueChanged; + errorCode = await bleBike.SubscribeToCharacteristic("6e40fec2-b5a3-f393-e0a9-e50e24dcca9e"); + if (errorCode > 0) + { + disposeBLE(); + return; + } + + // Heart rate + errorCode = await bleHeart.OpenDevice(deviceName); + if (errorCode > 0) + { + disposeBLE(); + return; + } + + errorCode = await bleHeart.SetService("HeartRate"); + if (errorCode > 0) + { + disposeBLE(); + return; + } + + bleHeart.SubscriptionValueChanged += BleBike_SubscriptionValueChanged; + errorCode = await bleHeart.SubscribeToCharacteristic("HeartRateMeasurement"); + if (errorCode > 0) + { + disposeBLE(); + return; + } + + Console.WriteLine("connected to BLE"); + this.Running = true; + + } + + /// + /// Callback for when the subscription value of the ble bike has changed + /// + /// the sender object + /// the value changed event + private void BleBike_SubscriptionValueChanged(object sender, BLESubscriptionValueChangedEventArgs e) + { + + if (e.ServiceName == "6e40fec2-b5a3-f393-e0a9-e50e24dcca9e") + { + byte[] payload = new byte[8]; + Array.Copy(e.Data, 4, payload, 0, 8); + this.dataConverter.Bike(payload); + } + else if (e.ServiceName == "00002a37-0000-1000-8000-00805f9b34fb") + { + this.dataConverter.BPM(e.Data); + } + else + { + Console.WriteLine("received data from unknown source {0}", e.ServiceName); + } + + } + + /// + /// Disposes of the current BLE object, if it exists. + /// + private void disposeBLE() + { + this.bleBike?.Dispose(); + this.bleHeart?.Dispose(); + this.Running = false; + } + + /// + /// Method setResistance converts the input percentage to bytes and sends it to the bike. + /// + /// The precentage of resistance to set + public void setResistance(float percentage) + { + byte[] antMessage = new byte[13]; + antMessage[0] = 0x4A; + antMessage[1] = 0x09; + antMessage[2] = 0x4E; + antMessage[3] = 0x05; + antMessage[4] = 0x30; + for (int i = 5; i < 11; i++) + { + antMessage[i] = 0xFF; + } + antMessage[11] = (byte)Math.Max(Math.Min(Math.Round(percentage / 0.5), 255), 0); + + + byte checksum = 0; + for (int i = 0; i < 12; i++) + { + checksum ^= antMessage[i]; + } + + antMessage[12] = checksum;//reminder that i am dumb :P + + + bleBike.WriteCharacteristic("6e40fec3-b5a3-f393-e0a9-e50e24dcca9e", antMessage); + } + } +} diff --git a/ProftaakRH/BikeSimulator.cs b/ProftaakRH/BikeSimulator.cs new file mode 100644 index 0000000..5664f9e --- /dev/null +++ b/ProftaakRH/BikeSimulator.cs @@ -0,0 +1,147 @@ +using LibNoise.Primitive; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + + + +namespace Hardware.Simulators +{ + class BikeSimulator : IHandler + { + IDataConverter dataConverter; + private int elapsedTime = 0; + private int eventCounter = 0; + private double distanceTraveled = 0; + private int equipmentType = 25; + private double speed = 0; + private int BPM = 0; + private int cadence = 0; + private double resistance = 0; + private double power; + private double accPower; + + byte[] speedArray; + byte[] powerArray; + byte[] accPowerArray; + + + + public BikeSimulator(IDataConverter dataConverter) + { + this.dataConverter = dataConverter; + } + public void StartSimulation() + { + //Example BLE Message + //4A-09-4E-05-19-16-00-FF-28-00-00-20-F0 + + float x = 0.0f; + + //Perlin for Random values + ImprovedPerlin improvedPerlin = new ImprovedPerlin(0,LibNoise.NoiseQuality.Best); + + while (true) + { + CalculateVariables(improvedPerlin.GetValue(x)+1); + + //Simulate sending data + dataConverter.Bike(GenerateBike0x19()); + dataConverter.Bike(GenerateBike0x10()); + dataConverter.BPM(GenerateHeart()); + + Thread.Sleep(1000); + + x += 0.1f; + eventCounter++; + elapsedTime++; + } + + } + + //Generate an ANT message for page 0x19 + private byte[] GenerateBike0x19() + { + byte statByte = (byte)(powerArray[1] >> 4); + byte[] bikeByte = { 0x19, Convert.ToByte(eventCounter%256), Convert.ToByte(cadence%254), accPowerArray[0], accPowerArray[1], powerArray[0], statByte, 0x20 }; + return bikeByte; + } + + //Generate an ANT message for page 0x10 + private byte[] GenerateBike0x10() + { + byte[] bikeByte = { 0x10, Convert.ToByte(equipmentType), Convert.ToByte(elapsedTime*4%64), Convert.ToByte(distanceTraveled), speedArray[0], speedArray[1], Convert.ToByte(BPM), 0xFF }; + return bikeByte; + } + + //Generate an ANT message for BPM + private byte[] GenerateHeart() + { + byte[] hartByte = { 0x00, Convert.ToByte(BPM)}; + return hartByte; + } + + //Generate an ANT message for resistance + public byte[] GenerateResistance(float percentage) + { + byte[] antMessage = new byte[13]; + antMessage[0] = 0x4A; + antMessage[1] = 0x09; + antMessage[2] = 0x4E; + antMessage[3] = 0x05; + antMessage[4] = 0x30; + for (int i = 5; i < 11; i++) + { + antMessage[i] = 0xFF; + } + antMessage[11] = (byte)Math.Max(Math.Min(Math.Round(percentage / 0.5), 255), 0); + //antMessage[11] = 50; //hardcoded for testing + + byte checksum = 0; + for (int i = 0; i < 12; i++) + { + checksum ^= antMessage[i]; + } + + antMessage[12] = checksum;//reminder that i am dumb :P + + return antMessage; + } + + //Calculates the needed variables + //Input perlin value + private void CalculateVariables(float perlin) + { + this.speed = perlin * 5 / 0.01 ; + short sped = (short)speed; + speedArray = BitConverter.GetBytes(sped); + this.distanceTraveled = (distanceTraveled+(speed*0.01)) % 256; + this.BPM = (int) (perlin * 80); + this.cadence = (int)speed/6; + this.power = ((1 + resistance) * speed)/14 % 4094; + this.accPower = (this.accPower + this.power) % 65536; + // TO DO power to power LSB & MSN + powerArray = BitConverter.GetBytes((short)this.power); + accPowerArray = BitConverter.GetBytes((short)accPower); + } + + //Set resistance in simulated bike + public void setResistance(byte[] bytes) + { + //TODO check if message is correct + if(bytes.Length == 13) + { + this.resistance = Convert.ToDouble(bytes[11])/2; + } + } + } + + //Interface for receiving a message on the simulated bike + interface IHandler + { + void setResistance(byte[] bytes); + + } +} diff --git a/ProftaakRH/DataConverter.cs b/ProftaakRH/DataConverter.cs new file mode 100644 index 0000000..7c93ff2 --- /dev/null +++ b/ProftaakRH/DataConverter.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Hardware +{ + /// + /// DataConverter class that handles all conversion of received data from the BLE bike. + /// + class DataConverter : IDataConverter + { + /// + /// Receives, parses and displays any incoming data from the bike. + /// + /// the array of bytes that was received + public void Bike(byte[] bytes) + { + if (bytes == null) + { + Console.WriteLine("HEY, didn't get bytes!\n-Bike DataConverter"); + } + else + if (bytes.Length == 8) + { + + switch (bytes[0]) + { + case 0x10: + if (bytes[1] != 25) + { + Console.WriteLine("WTF this isn't a bike"); + } + Console.WriteLine($"Time since start is: {bytes[2] / 4}s (Rollover every 4s)"); + Console.WriteLine($"Distance Traveled is : {bytes[3]}m (Rollover every 256m)"); + + int input = bytes[4] | (bytes[5] << 8); + Console.WriteLine($"Speed is : {input * 0.01}m/s (Range 65.534m/4)"); + if (bytes[6] != 0xFF) + { + Console.WriteLine("Heart rate byte: {0}", Convert.ToString(bytes[6],2)); + } + break; + case 0x19: + Console.WriteLine($"Event count: {bytes[1]} (Rollover 256)"); + if (bytes[2] != 0xFF) + { + Console.WriteLine($"Instantaneous cadence: {bytes[2]} RPM (Range 0-254)"); + + } + int accumPower = bytes[3] | (bytes[4] << 8); + + Console.WriteLine($"Accumulated power: {accumPower} watt (Rollover 65536)"); + + int instantPower = (bytes[5]) | (bytes[6] & 0b00001111)<<8; + + + if (instantPower != 0xFFF) + Console.WriteLine($"Instant power: {instantPower} watt (Range 0-4094)"); + + int trainerStatus = bytes[6] & 0b11110000; // bit 4-7 + int flags = bytes[7] >> 4; + int FEState = bytes[7] & 0b00001111; + + + break; + + default: + Console.WriteLine("HEY, I never heard of data page {0}\n-DataConverter", bytes[0]); + break; + } + } + else + { + Console.WriteLine("HEY, I didn't get 8 bytes!\n-DataConverter"); + } + Console.WriteLine(); + } + + /// + /// Gets and prints the BPM from the message received from the bike. + /// + /// The array with bytes that was received + public void BPM(byte[] bytes) + { + if (bytes == null) + { + Console.WriteLine("HEY, didn't get bytes!\n-BPM DataConverter"); + return; + } + if (bytes[0] != 0) + { + Console.WriteLine("HOLY SHIT i got flags!!! {0} now i can't do anything\n-BPM DataConverter", bytes[0]); + } + else if (bytes.Length != 2) + { + Console.WriteLine("bytes length is: {0}", bytes.Length); + } + else + { + Console.WriteLine("BPM: {0}", bytes[1]); + + } + Console.WriteLine(); + } + } + + /// + /// Dataconverter interface for handling data received from the bike + /// + interface IDataConverter + { + void BPM(byte[] bytes); + void Bike(byte[] bytes); + } +} diff --git a/ProftaakRH/FietsDemo.cs b/ProftaakRH/FietsDemo.cs index 4edbdc1..120292e 100644 --- a/ProftaakRH/FietsDemo.cs +++ b/ProftaakRH/FietsDemo.cs @@ -55,57 +55,104 @@ namespace FietsDemo private static void BleBike_SubscriptionValueChanged(object sender, BLESubscriptionValueChangedEventArgs e) { - Console.WriteLine("Received from {0}: {1}, {2}", e.ServiceName, - BitConverter.ToString(e.Data).Replace("-", " "), - Encoding.UTF8.GetString(e.Data)); + //Console.WriteLine("Received from {0}: {1}, {2}", e.ServiceName, + // BitConverter.ToString(e.Data).Replace("-", " "), + // Encoding.UTF8.GetString(e.Data)); string[] bytes = BitConverter.ToString(e.Data).Split('-'); string[] ANT = new string[5]; if (e.ServiceName == "6e40fec2-b5a3-f393-e0a9-e50e24dcca9e") { - Console.WriteLine("SYNC : " + bytes[0]); - ANT[0] = bytes[0]; - Console.WriteLine("LENGTH : " + bytes[1]); + //Console.WriteLine("SYNC : " + bytes[0]); + //ANT[0] = bytes[0]; + //Console.WriteLine("LENGTH : " + bytes[1]); int length = Convert.ToInt32(bytes[1], 16); - ANT[1] = length.ToString(); - Console.WriteLine("MSG ID : " + bytes[2]); - ANT[2] = bytes[2]; - string msg = string.Empty; - for (int i = 3; i < 3 + length; i++) - { - msg += bytes[i]; - } - ANT[3] = msg; + //ANT[1] = length.ToString(); + //Console.WriteLine("MSG ID : " + bytes[2]); + //ANT[2] = bytes[2]; + //string msg = string.Empty; + //for (int i = 3; i < 3 + length; i++) + //{ + // msg += bytes[i]; + //} + //ANT[3] = msg; - byte[] message = new byte[length]; + byte[] message = new byte[length - 1]; - Array.Copy(e.Data, 3, message, 0, length); + Array.Copy(e.Data, 4, message, 0, length - 1); DoCrazyShitWithMsg(message); - Console.WriteLine("MSG : " + msg); - string checksum = bytes[3 + length]; - ANT[4] = checksum; - Console.WriteLine("CHECKSUM : " + checksum); + //Console.WriteLine("MSG : " + msg); + //string checksum = bytes[3 + length]; + //ANT[4] = checksum; + //Console.WriteLine("CHECKSUM : " + checksum); + + //byte calcChecksum = 0; + + //for (int i = 0; i < e.Data.Length - 1; i++) + //{ + // calcChecksum ^= e.Data[i]; + //} + + //Console.WriteLine("Calculated checksum : " + Convert.ToString(calcChecksum,16).ToUpper()); - Console.WriteLine(BitConverter.ToString(e.Data)); - - } else + //Console.WriteLine(BitConverter.ToString(e.Data)); + + } + else { - Console.WriteLine("BPM: " + Convert.ToInt32(bytes[1], 16)); + //Console.WriteLine("BPM: " + Convert.ToInt32(bytes[1], 16)); } Console.WriteLine(); } private static void DoCrazyShitWithMsg(byte[] bytes) { + Console.WriteLine("doing crazy shit with {0}", bytes); String[] hexvalues = BitConverter.ToString(bytes).Split('-'); for (int i = 0; i < hexvalues.Length; i++) { - Console.WriteLine("Byte {0}: {1}" , i, hexvalues[i]); + Console.WriteLine("Byte {0}: {1}", i, hexvalues[i]); } + switch (bytes[0]) + { + case 0x10: + Console.WriteLine(); + Console.WriteLine("Data Page Number\t\t: " + hexvalues[0]); + Console.WriteLine("Equipment Type Bit Field\t: " + hexvalues[1]); + Console.WriteLine("Elapsed Time\t\t\t: " + hexvalues[2]); + Console.WriteLine("Distance Traveled\t\t: " + hexvalues[3]); + Console.WriteLine("Speed LSB\t\t\t: " + hexvalues[4]); + Console.WriteLine("Speed LSB\t\t\t: " + hexvalues[5]); + Console.WriteLine("Heart Rate\t\t\t: " + hexvalues[6]); + Console.WriteLine("Capabilities FE State Bit Field\t: " + Convert.ToString(bytes[7], 2)); + break; + case 0x19: + Console.WriteLine(); + Console.WriteLine("Data Page Number\t\t: " + hexvalues[0]); + Console.WriteLine("Update Event Count\t\t: " + hexvalues[1]); + Console.WriteLine("Instantaneous Cadence\t\t: " + hexvalues[2]); + Console.WriteLine("Accumulated Power LSB\t\t: " + hexvalues[3]); + Console.WriteLine("Accumulated Power MSB\t\t: " + hexvalues[4]); + Console.WriteLine("Instant Power LSB\t\t: " + hexvalues[5]); + Console.WriteLine("Instant Power MSN and status\t: " + Convert.ToString(bytes[6], 2)); + Console.WriteLine("Flags and FE state BitField\t: " + Convert.ToString(bytes[7], 2)); + break; + default: + Console.WriteLine("data page number not recognized"); + break; + + + } + + + //Console.WriteLine("Speed might be {0} m/s", bytes[3] * 0.093294); + + + } } } \ No newline at end of file diff --git a/ProftaakRH/Main.cs b/ProftaakRH/Main.cs new file mode 100644 index 0000000..0938089 --- /dev/null +++ b/ProftaakRH/Main.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Hardware; +using Hardware.Simulators; + + +namespace ProftaakRH +{ + class Program + { + static void Main(string[] agrs) + { + IDataConverter dataConverter = new DataConverter(); + BLEHandler bLEHandler = new BLEHandler(dataConverter); + //BikeSimulator bikeSimulator = new BikeSimulator(dataConverter); + //bikeSimulator.setResistance(bikeSimulator.GenerateResistance(1f)); + //bikeSimulator.StartSimulation(); + + + bool running = true; + while (running) + { + string input = Console.ReadLine(); + input.ToLower(); + input.Trim(); + if(input == "quit") + { + running = false; + break; + } + try + { + int resistance = Int32.Parse(input); + bLEHandler.setResistance(resistance); + } + catch + { + //do nothing + } + } + } + } +} diff --git a/ProftaakRH/ProftaakRH.csproj b/ProftaakRH/ProftaakRH.csproj index ca49088..8f98a2c 100644 --- a/ProftaakRH/ProftaakRH.csproj +++ b/ProftaakRH/ProftaakRH.csproj @@ -4,6 +4,10 @@ Exe netcoreapp3.1 + + + + .\dll\BLELibrary.dll