diff --git a/Client/Client.cs b/Client/Client.cs index 7e36c2f..5696071 100644 --- a/Client/Client.cs +++ b/Client/Client.cs @@ -31,22 +31,46 @@ namespace Client client.BeginConnect(adress, port, new AsyncCallback(OnConnect), null); } + /// + /// initializes the VR engine and sets the callbacks + /// private void initEngine() { engineConnection = EngineConnection.INSTANCE; engineConnection.OnNoTunnelId = retryEngineConnection; + engineConnection.OnSuccessFullConnection = engineConnected; if (!engineConnection.Connected) engineConnection.Connect(); } + /// + /// retries to connect to the VR engine if no tunnel id was found + /// private void retryEngineConnection() { Console.WriteLine("-- Could not connect to the VR engine. Please make sure you are running the simulation!"); - Console.WriteLine("-- Press any key to retry connecting to the VR engine."); - Console.ReadKey(); - - engineConnection.CreateConnection(); + Console.WriteLine("-- Press ENTER to retry connecting to the VR engine."); + Console.WriteLine("-- Press 'q' and then ENTER to not connect to the VR engine"); + string input = Console.ReadLine(); + if (input == string.Empty) engineConnection.CreateConnection(); + else + { + Console.WriteLine("Skipping connecting to VR engine..."); + engineConnection.Stop(); + } + } + private void engineConnected() + { + Console.WriteLine("successfully connected to VR engine"); + engineConnection.initScene(); + if (engineConnection.Connected && sessionRunning && !engineConnection.FollowingRoute) engineConnection.StartRouteFollow(); + } + + /// + /// callback method for when the TCP client is connected + /// + /// the result of the async read private void OnConnect(IAsyncResult ar) { this.client.EndConnect(ar); @@ -60,6 +84,10 @@ namespace Client this.stream.BeginRead(this.buffer, 0, this.buffer.Length, new AsyncCallback(OnRead), null); } + /// + /// callback method for when there is a message read + /// + /// the result of the async read private void OnRead(IAsyncResult ar) { int receivedBytes = this.stream.EndRead(ar); @@ -93,6 +121,7 @@ namespace Client string responseStatus = DataParser.getResponseStatus(payloadbytes); if (responseStatus == "OK") { + Console.WriteLine("Username and password correct!"); this.connected = true; //initEngine(); } @@ -103,14 +132,18 @@ namespace Client } break; case DataParser.START_SESSION: + Console.WriteLine("Session started!"); this.sessionRunning = true; + if (engineConnection.Connected && !engineConnection.FollowingRoute) engineConnection.StartRouteFollow(); sendMessage(DataParser.getStartSessionJson()); break; case DataParser.STOP_SESSION: + Console.WriteLine("Stop session identifier"); this.sessionRunning = false; sendMessage(DataParser.getStopSessionJson()); break; case DataParser.SET_RESISTANCE: + Console.WriteLine("Set resistance identifier"); if (this.handler == null) { Console.WriteLine("handler is null"); @@ -140,11 +173,19 @@ namespace Client } + /// + /// starts sending a message to the server + /// + /// the message to send private void sendMessage(byte[] message) { stream.BeginWrite(message, 0, message.Length, new AsyncCallback(OnWrite), null); } + /// + /// callback method for when a message is fully written to the server + /// + /// the async result representing the asynchronous call private void OnWrite(IAsyncResult ar) { this.stream.EndWrite(ar); @@ -152,6 +193,10 @@ namespace Client #region interface //maybe move this to other place + /// + /// bpm method for receiving the BPM value from the bluetooth bike or the simulation + /// + /// the message public void BPM(byte[] bytes) { if (!sessionRunning) @@ -163,9 +208,21 @@ namespace Client throw new ArgumentNullException("no bytes"); } byte[] message = DataParser.GetRawDataMessage(bytes); + + if (engineConnection.Connected && engineConnection.FollowingRoute) + { + engineConnection.BikeBPM = bytes[1]; + engineConnection.UpdateInfoPanel(); + } + + this.stream.BeginWrite(message, 0, message.Length, new AsyncCallback(OnWrite), null); } + /// + /// method for receiving the bike message from the bluetooth bike or the simulation + /// + /// the message public void Bike(byte[] bytes) { if (!sessionRunning) @@ -177,15 +234,35 @@ namespace Client throw new ArgumentNullException("no bytes"); } byte[] message = DataParser.GetRawDataMessage(bytes); + switch (bytes[0]) + { + case 0x10: + + engineConnection.BikeSpeed = (bytes[4] | (bytes[5] << 8)) * 0.01f; + break; + case 0x19: + engineConnection.BikePower = (bytes[5]) | (bytes[6] & 0b00001111) << 8; + break; + } + if (engineConnection.Connected && engineConnection.FollowingRoute) + engineConnection.UpdateInfoPanel(); + this.stream.BeginWrite(message, 0, message.Length, new AsyncCallback(OnWrite), null); } #endregion + /// + /// wether or not the client stream is connected + /// + /// true if it's connected, false if not public bool IsConnected() { return this.connected; } + /// + /// tries to log in to the server by asking for a username and password + /// private void tryLogin() { //TODO File in lezen @@ -203,17 +280,10 @@ namespace Client this.stream.BeginWrite(message, 0, message.Length, new AsyncCallback(OnWrite), null); } - public void tryLoginDoctor(string username, string password) - { - string hashUser = Hashing.Hasher.HashString(username); - string hashPassword = Hashing.Hasher.HashString(password); - - byte[] message = DataParser.getJsonMessage(DataParser.GetLoginJson(hashUser, hashPassword)); - - - stream.BeginWrite(message, 0, message.Length, new AsyncCallback(OnWrite), null); - } - + /// + /// sets the handler for the client, so either the bike simulator or the bluetooth bike handler + /// + /// public void setHandler(IHandler handler) { this.handler = handler; diff --git a/Client/EngineConnection.cs b/Client/EngineConnection.cs index 4905911..9d0d4e5 100644 --- a/Client/EngineConnection.cs +++ b/Client/EngineConnection.cs @@ -8,12 +8,14 @@ namespace Client { public delegate void HandleSerial(string message); public delegate void HandleNoTunnelId(); + public delegate void OnSuccessfullConnection(); public sealed class EngineConnection { private static EngineConnection instance = null; private static readonly object padlock = new object(); public HandleNoTunnelId OnNoTunnelId; + public OnSuccessfullConnection OnSuccessFullConnection; private static PC[] PCs = { @@ -29,9 +31,18 @@ namespace Client private static ServerResponseReader serverResponseReader; private static string sessionId = string.Empty; private static string tunnelId = string.Empty; + private static string cameraId = string.Empty; private static string routeId = string.Empty; private static string panelId = string.Empty; private static string bikeId = string.Empty; + private static string headId = string.Empty; + + public float BikeSpeed { get; set; } + public float BikePower { get; set; } + public float BikeBPM { get; set; } + public float BikeResistance { get; set; } + + public bool FollowingRoute = false; private static NetworkStream stream; @@ -42,9 +53,15 @@ namespace Client EngineConnection() { - + BikeSpeed = 0; + BikePower = 0; + BikeBPM = 0; + BikeResistance = 50; } + /// + /// Singleton constructor + /// public static EngineConnection INSTANCE { get @@ -60,6 +77,11 @@ namespace Client } } + + + /// + /// connects to the vr engine and initalizes the serverResponseReader + /// public void Connect() { TcpClient client = new TcpClient("145.48.6.10", 6666); @@ -68,6 +90,18 @@ namespace Client CreateConnection(); } + /// + /// initializes and starts the reading of the responses from the vr server + /// + /// the networkstream + private void initReader() + { + serverResponseReader = new ServerResponseReader(stream); + serverResponseReader.callback = HandleResponse; + serverResponseReader.StartRead(); + } + + #region VR Message traffic /// /// connects to the server and creates the tunnel /// @@ -84,26 +118,8 @@ namespace Client WriteTextMessage(tunnelCreate); - // wait until we have a tunnel id - while (tunnelId == string.Empty) { } - if (tunnelId != null) - { - Write("got tunnel id! " + tunnelId); - } - mainCommand = new Command(tunnelId); } - /// - /// initializes and starts the reading of the responses from the vr server - /// - /// the networkstream - private void initReader() - { - serverResponseReader = new ServerResponseReader(stream); - serverResponseReader.callback = HandleResponse; - serverResponseReader.StartRead(); - Connected = true; - } /// /// callback method that handles responses from the server @@ -121,12 +137,19 @@ namespace Client else if (id == "tunnel/create") { tunnelId = JSONParser.GetTunnelID(message); + Console.WriteLine("set tunnel id to " + tunnelId); if (tunnelId == null) { Write("could not find a valid tunnel id!"); OnNoTunnelId?.Invoke(); Connected = false; + FollowingRoute = false; return; + } else + { + Write("got tunnel id! " + tunnelId); + Connected = true; + OnSuccessFullConnection?.Invoke(); } } @@ -139,6 +162,102 @@ namespace Client } } + public void initScene() + { + Write("initializing scene..."); + mainCommand = new Command(tunnelId); + + // reset the scene + WriteTextMessage(mainCommand.ResetScene()); + + //Get sceneinfo and set the id's + SendMessageAndOnResponse(mainCommand.GetSceneInfoCommand("sceneinfo"), "sceneinfo", + (message) => + { + //Console.WriteLine("\r\n\r\n\r\nscene info" + message); + cameraId = JSONParser.GetIdSceneInfoChild(message, "Camera"); + string headId = JSONParser.GetIdSceneInfoChild(message, "Head"); + string handLeftId = JSONParser.GetIdSceneInfoChild(message, "LeftHand"); + string handRightId = JSONParser.GetIdSceneInfoChild(message, "RightHand"); + + //Force(stream, mainCommand.DeleteNode(handLeftId, "deleteHandL"), "deleteHandL", (message) => Console.WriteLine("Left hand deleted")); + //Force(stream, mainCommand.DeleteNode(handRightId, "deleteHandR"), "deleteHandR", (message) => Console.WriteLine("Right hand deleted")); + }); + // add the route and set the route id + SendMessageAndOnResponse(mainCommand.RouteCommand("routeID"), "routeID", (message) => routeId = JSONParser.GetResponseUuid(message)); + } + + internal void StartRouteFollow() + { + Write("Starting route follow..."); + FollowingRoute = true; + + SendMessageAndOnResponse(mainCommand.AddBikeModel("bikeID"), "bikeID", + (message) => + { + bikeId = JSONParser.GetResponseUuid(message); + SendMessageAndOnResponse(mainCommand.addPanel("panelAdd", bikeId), "panelAdd", + (message) => + { + + panelId = JSONParser.getPanelID(message); + + WriteTextMessage(mainCommand.ColorPanel(panelId)); + UpdateInfoPanel(); + + while (cameraId == string.Empty) { } + SetFollowSpeed(5.0f); + }); + }); + } + + public void UpdateInfoPanel() + { + ShowPanel(BikeSpeed, (int)BikeBPM, (int)BikePower, (int)BikeResistance); + WriteTextMessage(mainCommand.RouteSpeed(BikeSpeed, bikeId)); + WriteTextMessage(mainCommand.RouteSpeed(BikeSpeed, cameraId)); + } + + public void ShowPanel(double bikeSpeed, int bpm, int power, int resistance) + { + WriteTextMessage(mainCommand.ClearPanel(panelId)); + + SendMessageAndOnResponse(mainCommand.showBikespeed(panelId, "bikeSpeed", bikeSpeed), "bikeSpeed", + (message) => + { + // TODO check if is drawn + }); + SendMessageAndOnResponse(mainCommand.showHeartrate(panelId, "bpm", bpm), "bpm", + (message) => + { + // TODO check if is drawn + }); + SendMessageAndOnResponse(mainCommand.showPower(panelId, "power", power), "power", + (message) => + { + // TODO check if is drawn + }); + SendMessageAndOnResponse(mainCommand.showResistance(panelId, "resistance", resistance), "resistance", + (message) => + { + // TODO check if is drawn + }); + + // Check if every text is drawn! + + WriteTextMessage(mainCommand.SwapPanel(panelId)); + } + + private void SetFollowSpeed(float speed) + { + WriteTextMessage(mainCommand.RouteFollow(routeId, bikeId, speed, new float[] { 0, -(float)Math.PI / 2f, 0 }, new float[] { 0, 0, 0 })); + WriteTextMessage(mainCommand.RouteFollow(routeId, cameraId, speed)); + } + + #endregion + + #region message send/receive + /// /// method that sends the speciefied message with the specified serial, and executes the given action upon receivind a reply from the server with this serial. /// @@ -148,7 +267,13 @@ namespace Client /// the code to be executed upon reveiving a reply from the server with the specified serial public void SendMessageAndOnResponse(string message, string serial, HandleSerial action) { - serialResponses.Add(serial, action); + if (serialResponses.ContainsKey(serial)) + { + serialResponses[serial] = action; + } else + { + serialResponses.Add(serial, action); + } WriteTextMessage(message); } @@ -169,6 +294,14 @@ namespace Client //Write("sent message " + message); } + + #endregion + + public void Stop() + { + serverResponseReader.Stop(); + + } public void Write(string msg) { Console.WriteLine( "[ENGINECONNECT] " + msg); diff --git a/Client/Program.cs b/Client/Program.cs index aa63eab..d96347b 100644 --- a/Client/Program.cs +++ b/Client/Program.cs @@ -12,7 +12,7 @@ namespace Client { static void Main(string[] args) { - Console.WriteLine("Hello World!"); + Console.WriteLine("// Connecting... //"); //connect fiets? Thread.Sleep(20000); @@ -23,18 +23,18 @@ namespace Client { } - BLEHandler bLEHandler = new BLEHandler(client); + //BLEHandler bLEHandler = new BLEHandler(client); - bLEHandler.Connect(); + //bLEHandler.Connect(); - client.setHandler(bLEHandler); + //client.setHandler(bLEHandler); - //BikeSimulator bikeSimulator = new BikeSimulator(client); + BikeSimulator bikeSimulator = new BikeSimulator(client); - //bikeSimulator.StartSimulation(); + bikeSimulator.StartSimulation(); - //client.setHandler(bikeSimulator); + client.setHandler(bikeSimulator); while (true) { diff --git a/ClientApp/App.xaml b/ClientApp/App.xaml new file mode 100644 index 0000000..1400927 --- /dev/null +++ b/ClientApp/App.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/ClientApp/App.xaml.cs b/ClientApp/App.xaml.cs new file mode 100644 index 0000000..03d8e97 --- /dev/null +++ b/ClientApp/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace ClientApp +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/ClientApp/AssemblyInfo.cs b/ClientApp/AssemblyInfo.cs new file mode 100644 index 0000000..8b5504e --- /dev/null +++ b/ClientApp/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/ClientApp/ClientApp.csproj b/ClientApp/ClientApp.csproj new file mode 100644 index 0000000..d8ae036 --- /dev/null +++ b/ClientApp/ClientApp.csproj @@ -0,0 +1,23 @@ + + + + WinExe + netcoreapp3.1 + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ClientApp/FodyWeavers.xml b/ClientApp/FodyWeavers.xml new file mode 100644 index 0000000..d5abfed --- /dev/null +++ b/ClientApp/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ClientApp/FodyWeavers.xsd b/ClientApp/FodyWeavers.xsd new file mode 100644 index 0000000..69dbe48 --- /dev/null +++ b/ClientApp/FodyWeavers.xsd @@ -0,0 +1,74 @@ + + + + + + + + + + + Used to control if the On_PropertyName_Changed feature is enabled. + + + + + Used to control if the Dependent properties feature is enabled. + + + + + Used to control if the IsChanged property feature is enabled. + + + + + Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form. + + + + + Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project. + + + + + Used to control if equality checks should use the Equals method resolved from the base class. + + + + + Used to control if equality checks should use the static Equals method resolved from the base class. + + + + + Used to turn off build warnings from this weaver. + + + + + Used to turn off build warnings about mismatched On_PropertyName_Changed methods. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/ClientApp/Models/Info.cs b/ClientApp/Models/Info.cs new file mode 100644 index 0000000..e976bda --- /dev/null +++ b/ClientApp/Models/Info.cs @@ -0,0 +1,14 @@ +using ClientApp.Utils; +using System; +using System.Collections.Generic; +using System.Text; + +namespace ClientApp.Models +{ + class Info : ObservableObject + { + public bool ConnectedToServer { get; set; } + public bool ConnectedToVREngine { get; set; } + public bool DoctorConnected { get; set; } + } +} diff --git a/ClientApp/Utils/Client.cs b/ClientApp/Utils/Client.cs new file mode 100644 index 0000000..8dd0ad1 --- /dev/null +++ b/ClientApp/Utils/Client.cs @@ -0,0 +1,291 @@ +using System; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using ClientApp.ViewModels; +using ProftaakRH; + +namespace ClientApp.Utils +{ + public class Client : IDataReceiver + { + private TcpClient client; + private NetworkStream stream; + private byte[] buffer = new byte[1024]; + private bool connected; + private byte[] totalBuffer = new byte[1024]; + private int totalBufferReceived = 0; + private EngineConnection engineConnection; + private bool sessionRunning = false; + private IHandler handler = null; + private LoginViewModel LoginViewModel; + + + public Client() : this("localhost", 5555) + { + + } + + public Client(string adress, int port) + { + this.client = new TcpClient(); + this.connected = false; + client.BeginConnect(adress, port, new AsyncCallback(OnConnect), null); + } + + /// + /// initializes the VR engine and sets the callbacks + /// + private void initEngine() + { + engineConnection = EngineConnection.INSTANCE; + engineConnection.OnNoTunnelId = retryEngineConnection; + engineConnection.OnSuccessFullConnection = engineConnected; + if (!engineConnection.Connected) engineConnection.Connect(); + } + + /// + /// retries to connect to the VR engine if no tunnel id was found + /// + private void retryEngineConnection() + { + Console.WriteLine("-- Could not connect to the VR engine. Please make sure you are running the simulation!"); + Console.WriteLine("-- Press ENTER to retry connecting to the VR engine."); + Console.WriteLine("-- Press 'q' and then ENTER to not connect to the VR engine"); + string input = Console.ReadLine(); + if (input == string.Empty) engineConnection.CreateConnection(); + else + { + Console.WriteLine("Skipping connecting to VR engine..."); + engineConnection.Stop(); + } + + } + + private void engineConnected() + { + Console.WriteLine("successfully connected to VR engine"); + engineConnection.initScene(); + if (engineConnection.Connected && sessionRunning && !engineConnection.FollowingRoute) engineConnection.StartRouteFollow(); + } + + /// + /// callback method for when the TCP client is connected + /// + /// the result of the async read + private void OnConnect(IAsyncResult ar) + { + this.client.EndConnect(ar); + Console.WriteLine("TCP client Verbonden!"); + + + this.stream = this.client.GetStream(); + + this.stream.BeginRead(this.buffer, 0, this.buffer.Length, new AsyncCallback(OnRead), null); + } + + /// + /// callback method for when there is a message read + /// + /// the result of the async read + private void OnRead(IAsyncResult ar) + { + int receivedBytes = this.stream.EndRead(ar); + + if (totalBufferReceived + receivedBytes > 1024) + { + throw new OutOfMemoryException("buffer too small"); + } + Array.Copy(buffer, 0, totalBuffer, totalBufferReceived, receivedBytes); + totalBufferReceived += receivedBytes; + + int expectedMessageLength = BitConverter.ToInt32(totalBuffer, 0); + while (totalBufferReceived >= expectedMessageLength) + { + //volledig packet binnen + byte[] messageBytes = new byte[expectedMessageLength]; + Array.Copy(totalBuffer, 0, messageBytes, 0, expectedMessageLength); + + + byte[] payloadbytes = new byte[BitConverter.ToInt32(messageBytes, 0) - 5]; + + Array.Copy(messageBytes, 5, payloadbytes, 0, payloadbytes.Length); + + string identifier; + bool isJson = DataParser.getJsonIdentifier(messageBytes, out identifier); + if (isJson) + { + switch (identifier) + { + case DataParser.LOGIN_RESPONSE: + string responseStatus = DataParser.getResponseStatus(payloadbytes); + if (responseStatus == "OK") + { + Console.WriteLine("Username and password correct!"); + this.LoginViewModel.setLoginStatus(true); + this.connected = true; + initEngine(); + } + else + { + Console.WriteLine($"login failed \"{responseStatus}\""); + } + break; + case DataParser.START_SESSION: + Console.WriteLine("Session started!"); + this.sessionRunning = true; + if (engineConnection.Connected && !engineConnection.FollowingRoute) engineConnection.StartRouteFollow(); + sendMessage(DataParser.getStartSessionJson()); + break; + case DataParser.STOP_SESSION: + Console.WriteLine("Stop session identifier"); + this.sessionRunning = false; + sendMessage(DataParser.getStopSessionJson()); + break; + case DataParser.SET_RESISTANCE: + Console.WriteLine("Set resistance identifier"); + if (this.handler == null) + { + Console.WriteLine("handler is null"); + sendMessage(DataParser.getSetResistanceResponseJson(false)); + } + else + { + this.handler.setResistance(DataParser.getResistanceFromJson(payloadbytes)); + sendMessage(DataParser.getSetResistanceResponseJson(true)); + } + break; + default: + Console.WriteLine($"Received json with identifier {identifier}:\n{Encoding.ASCII.GetString(payloadbytes)}"); + break; + } + } + else if (DataParser.isRawData(messageBytes)) + { + Console.WriteLine($"Received data: {BitConverter.ToString(payloadbytes)}"); + } + + totalBufferReceived -= expectedMessageLength; + expectedMessageLength = BitConverter.ToInt32(totalBuffer, 0); + } + + this.stream.BeginRead(this.buffer, 0, this.buffer.Length, new AsyncCallback(OnRead), null); + + } + + /// + /// starts sending a message to the server + /// + /// the message to send + private void sendMessage(byte[] message) + { + stream.BeginWrite(message, 0, message.Length, new AsyncCallback(OnWrite), null); + } + + /// + /// callback method for when a message is fully written to the server + /// + /// the async result representing the asynchronous call + private void OnWrite(IAsyncResult ar) + { + this.stream.EndWrite(ar); + } + + #region interface + //maybe move this to other place + /// + /// bpm method for receiving the BPM value from the bluetooth bike or the simulation + /// + /// the message + public void BPM(byte[] bytes) + { + if (!sessionRunning) + { + return; + } + if (bytes == null) + { + throw new ArgumentNullException("no bytes"); + } + byte[] message = DataParser.GetRawDataMessage(bytes); + + if (engineConnection.Connected && engineConnection.FollowingRoute) + { + engineConnection.BikeBPM = bytes[1]; + engineConnection.UpdateInfoPanel(); + } + + + this.stream.BeginWrite(message, 0, message.Length, new AsyncCallback(OnWrite), null); + } + + /// + /// method for receiving the bike message from the bluetooth bike or the simulation + /// + /// the message + public void Bike(byte[] bytes) + { + if (!sessionRunning) + { + return; + } + if (bytes == null) + { + throw new ArgumentNullException("no bytes"); + } + byte[] message = DataParser.GetRawDataMessage(bytes); + switch (bytes[0]) + { + case 0x10: + + engineConnection.BikeSpeed = (bytes[4] | (bytes[5] << 8)) * 0.01f; + break; + case 0x19: + engineConnection.BikePower = (bytes[5]) | (bytes[6] & 0b00001111) << 8; + break; + } + if (engineConnection.Connected && engineConnection.FollowingRoute) + engineConnection.UpdateInfoPanel(); + + this.stream.BeginWrite(message, 0, message.Length, new AsyncCallback(OnWrite), null); + } + + #endregion + + /// + /// wether or not the client stream is connected + /// + /// true if it's connected, false if not + public bool IsConnected() + { + return this.connected; + } + /// + /// tries to log in to the server by asking for a username and password + /// + public void tryLogin(string username, string password) + { + string hashUser = Hashing.Hasher.HashString(username); + string hashPassword = Hashing.Hasher.HashString(password); + + byte[] message = DataParser.getJsonMessage(DataParser.GetLoginJson(hashUser, hashPassword)); + + + this.stream.BeginWrite(message, 0, message.Length, new AsyncCallback(OnWrite), null); + } + + /// + /// sets the handler for the client, so either the bike simulator or the bluetooth bike handler + /// + /// + public void SetHandler(IHandler handler) + { + this.handler = handler; + } + + internal void SetLoginViewModel(LoginViewModel loginViewModel) + { + this.LoginViewModel = loginViewModel; + } + } +} diff --git a/ClientApp/Utils/DataParser.cs b/ClientApp/Utils/DataParser.cs new file mode 100644 index 0000000..6c1e4b7 --- /dev/null +++ b/ClientApp/Utils/DataParser.cs @@ -0,0 +1,206 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Text; + +namespace ClientApp.Utils +{ + public class DataParser + { + public const string LOGIN = "LOGIN"; + public const string LOGIN_RESPONSE = "LOGIN RESPONSE"; + public const string START_SESSION = "START SESSION"; + public const string STOP_SESSION = "STOP SESSION"; + public const string SET_RESISTANCE = "SET RESISTANCE"; + /// + /// makes the json object with LOGIN identifier and username and password + /// + /// username + /// password + /// json object to ASCII to bytes + public static byte[] GetLoginJson(string mUsername, string mPassword) + { + dynamic json = new + { + identifier = LOGIN, + data = new + { + username = mUsername, + password = mPassword, + } + }; + + return Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(json)); + } + + public static bool GetUsernamePassword(byte[] jsonbytes, out string username, out string password) + { + dynamic json = JsonConvert.DeserializeObject(Encoding.ASCII.GetString(jsonbytes)); + try + { + username = json.data.username; + password = json.data.password; + return true; + } + catch + { + username = null; + password = null; + return false; + } + } + + private static byte[] getJsonMessage(string mIdentifier, dynamic data) + { + dynamic json = new + { + identifier = mIdentifier, + data + }; + return getMessage(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(json)), 0x01); + } + + private static byte[] getJsonMessage(string mIdentifier) + { + dynamic json = new + { + identifier = mIdentifier, + }; + return getMessage(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(json)), 0x01); + } + + public static byte[] getLoginResponse(string mStatus) + { + return getJsonMessage(LOGIN_RESPONSE, new { status = mStatus }); + } + + public static string getResponseStatus(byte[] json) + { + return ((dynamic)JsonConvert.DeserializeObject(Encoding.ASCII.GetString(json))).data.status; + } + + /// + /// get the identifier from json + /// + /// json in ASCII + /// gets the identifier + /// if it sucseeded + public static bool getJsonIdentifier(byte[] bytes, out string identifier) + { + if (bytes.Length <= 5) + { + throw new ArgumentException("bytes to short"); + } + byte messageId = bytes[4]; + + if (messageId == 0x01) + { + dynamic json = JsonConvert.DeserializeObject(Encoding.ASCII.GetString(bytes.Skip(5).ToArray())); + identifier = json.identifier; + return true; + } + else + { + identifier = ""; + return false; + } + } + + /// + /// checks if the de message is raw data according to the protocol + /// + /// message + /// if message contains raw data + public static bool isRawData(byte[] bytes) + { + if (bytes.Length <= 5) + { + throw new ArgumentException("bytes to short"); + } + return bytes[4] == 0x02; + } + + /// + /// constructs a message with the payload, messageId and clientId + /// + /// + /// + /// + /// the message ready for sending + private static byte[] getMessage(byte[] payload, byte messageId) + { + byte[] res = new byte[payload.Length + 5]; + + Array.Copy(BitConverter.GetBytes(payload.Length + 5), 0, res, 0, 4); + res[4] = messageId; + Array.Copy(payload, 0, res, 5, payload.Length); + + return res; + } + + /// + /// constructs a message with the payload and clientId and assumes the payload is raw data + /// + /// + /// + /// the message ready for sending + public static byte[] GetRawDataMessage(byte[] payload) + { + return getMessage(payload, 0x02); + } + + /// + /// constructs a message with the payload and clientId and assumes the payload is json + /// + /// + /// + /// the message ready for sending + public static byte[] getJsonMessage(byte[] payload) + { + return getMessage(payload, 0x01); + } + + public static byte[] getStartSessionJson() + { + return getJsonMessage(START_SESSION); + } + + public static byte[] getStopSessionJson() + { + return getJsonMessage(STOP_SESSION); + } + + public static byte[] getSetResistanceJson(float mResistance) + { + dynamic data = new + { + resistance = mResistance + }; + return getJsonMessage(SET_RESISTANCE, data); + } + + public static byte[] getSetResistanceResponseJson(bool mWorked) + { + dynamic data = new + { + worked = mWorked + }; + return getJsonMessage(SET_RESISTANCE, data); + } + + public static float getResistanceFromJson(byte[] json) + { + return ((dynamic)JsonConvert.DeserializeObject(Encoding.ASCII.GetString(json))).data.resistance; + } + + public static bool getResistanceFromResponseJson(byte[] json) + { + return ((dynamic)JsonConvert.DeserializeObject(Encoding.ASCII.GetString(json))).data.worked; + } + + + } +} diff --git a/ClientApp/Utils/EngineConnection.cs b/ClientApp/Utils/EngineConnection.cs new file mode 100644 index 0000000..a4dd657 --- /dev/null +++ b/ClientApp/Utils/EngineConnection.cs @@ -0,0 +1,315 @@ +using System; +using System.Collections.Generic; +using System.Text; +using RH_Engine; +using System.Net.Sockets; + +namespace ClientApp.Utils +{ + public delegate void HandleSerial(string message); + public delegate void HandleNoTunnelId(); + public delegate void OnSuccessfullConnection(); + + public sealed class EngineConnection + { + private static EngineConnection instance = null; + private static readonly object padlock = new object(); + public HandleNoTunnelId OnNoTunnelId; + public OnSuccessfullConnection OnSuccessFullConnection; + + + private static PC[] PCs = { + //new PC("DESKTOP-M2CIH87", "Fabian"), + //new PC("T470S", "Shinichi"), + //new PC("DESKTOP-DHS478C", "semme"), + new PC("HP-ZBOOK-SEM", "Sem") + //new PC("DESKTOP-TV73FKO", "Wouter"), + //new PC("DESKTOP-SINMKT1", "Ralf van Aert"), + //new PC("NA", "Bart") + }; + + private static ServerResponseReader serverResponseReader; + private static string sessionId = string.Empty; + private static string tunnelId = string.Empty; + private static string cameraId = string.Empty; + private static string routeId = string.Empty; + private static string panelId = string.Empty; + private static string bikeId = string.Empty; + private static string headId = string.Empty; + + public float BikeSpeed { get; set; } + public float BikePower { get; set; } + public float BikeBPM { get; set; } + public float BikeResistance { get; set; } + + public bool FollowingRoute = false; + + private static NetworkStream stream; + + private static Dictionary serialResponses = new Dictionary(); + private Command mainCommand; + + public bool Connected = false; + + EngineConnection() + { + BikeSpeed = 0; + BikePower = 0; + BikeBPM = 0; + BikeResistance = 50; + } + + /// + /// Singleton constructor + /// + public static EngineConnection INSTANCE + { + get + { + lock (padlock) + { + if (instance == null) + { + instance = new EngineConnection(); + } + } + return instance; + } + } + + + + /// + /// connects to the vr engine and initalizes the serverResponseReader + /// + public void Connect() + { + TcpClient client = new TcpClient("145.48.6.10", 6666); + stream = client.GetStream(); + initReader(); + CreateConnection(); + } + + /// + /// initializes and starts the reading of the responses from the vr server + /// + /// the networkstream + private void initReader() + { + serverResponseReader = new ServerResponseReader(stream); + serverResponseReader.callback = HandleResponse; + serverResponseReader.StartRead(); + } + + #region VR Message traffic + /// + /// connects to the server and creates the tunnel + /// + /// the network stream to use + public void CreateConnection() + { + + WriteTextMessage("{\r\n\"id\" : \"session/list\",\r\n\"serial\" : \"list\"\r\n}"); + + // wait until we have got a sessionId + while (sessionId == string.Empty) { } + + string tunnelCreate = "{\"id\" : \"tunnel/create\", \"data\" : {\"session\" : \"" + sessionId + "\"}}"; + + WriteTextMessage(tunnelCreate); + + + } + + /// + /// callback method that handles responses from the server + /// + /// the response message from the server + public void HandleResponse(string message) + { + string id = JSONParser.GetID(message); + + // because the first messages don't have a serial, we need to check on the id + if (id == "session/list") + { + sessionId = JSONParser.GetSessionID(message, PCs); + } + else if (id == "tunnel/create") + { + tunnelId = JSONParser.GetTunnelID(message); + Console.WriteLine("set tunnel id to " + tunnelId); + if (tunnelId == null) + { + Write("could not find a valid tunnel id!"); + OnNoTunnelId?.Invoke(); + Connected = false; + FollowingRoute = false; + return; + } + else + { + Write("got tunnel id! " + tunnelId); + Connected = true; + OnSuccessFullConnection?.Invoke(); + } + } + + if (message.Contains("serial")) + { + //Console.WriteLine("GOT MESSAGE WITH SERIAL: " + message + "\n\n\n"); + string serial = JSONParser.GetSerial(message); + //Console.WriteLine("Got serial " + serial); + if (serialResponses.ContainsKey(serial)) serialResponses[serial].Invoke(message); + } + } + + public void initScene() + { + Write("initializing scene..."); + mainCommand = new Command(tunnelId); + + // reset the scene + WriteTextMessage(mainCommand.ResetScene()); + + //Get sceneinfo and set the id's + SendMessageAndOnResponse(mainCommand.GetSceneInfoCommand("sceneinfo"), "sceneinfo", + (message) => + { + //Console.WriteLine("\r\n\r\n\r\nscene info" + message); + cameraId = JSONParser.GetIdSceneInfoChild(message, "Camera"); + string headId = JSONParser.GetIdSceneInfoChild(message, "Head"); + string handLeftId = JSONParser.GetIdSceneInfoChild(message, "LeftHand"); + string handRightId = JSONParser.GetIdSceneInfoChild(message, "RightHand"); + + //Force(stream, mainCommand.DeleteNode(handLeftId, "deleteHandL"), "deleteHandL", (message) => Console.WriteLine("Left hand deleted")); + //Force(stream, mainCommand.DeleteNode(handRightId, "deleteHandR"), "deleteHandR", (message) => Console.WriteLine("Right hand deleted")); + }); + // add the route and set the route id + SendMessageAndOnResponse(mainCommand.RouteCommand("routeID"), "routeID", (message) => routeId = JSONParser.GetResponseUuid(message)); + } + + internal void StartRouteFollow() + { + Write("Starting route follow..."); + FollowingRoute = true; + + SendMessageAndOnResponse(mainCommand.AddBikeModel("bikeID"), "bikeID", + (message) => + { + bikeId = JSONParser.GetResponseUuid(message); + SendMessageAndOnResponse(mainCommand.addPanel("panelAdd", bikeId), "panelAdd", + (message) => + { + + panelId = JSONParser.getPanelID(message); + + WriteTextMessage(mainCommand.ColorPanel(panelId)); + UpdateInfoPanel(); + + while (cameraId == string.Empty) { } + SetFollowSpeed(5.0f); + }); + }); + } + + public void UpdateInfoPanel() + { + ShowPanel(BikeSpeed, (int)BikeBPM, (int)BikePower, (int)BikeResistance); + WriteTextMessage(mainCommand.RouteSpeed(BikeSpeed, bikeId)); + WriteTextMessage(mainCommand.RouteSpeed(BikeSpeed, cameraId)); + } + + public void ShowPanel(double bikeSpeed, int bpm, int power, int resistance) + { + WriteTextMessage(mainCommand.ClearPanel(panelId)); + + SendMessageAndOnResponse(mainCommand.showBikespeed(panelId, "bikeSpeed", bikeSpeed), "bikeSpeed", + (message) => + { + // TODO check if is drawn + }); + SendMessageAndOnResponse(mainCommand.showHeartrate(panelId, "bpm", bpm), "bpm", + (message) => + { + // TODO check if is drawn + }); + SendMessageAndOnResponse(mainCommand.showPower(panelId, "power", power), "power", + (message) => + { + // TODO check if is drawn + }); + SendMessageAndOnResponse(mainCommand.showResistance(panelId, "resistance", resistance), "resistance", + (message) => + { + // TODO check if is drawn + }); + + // Check if every text is drawn! + + WriteTextMessage(mainCommand.SwapPanel(panelId)); + } + + private void SetFollowSpeed(float speed) + { + WriteTextMessage(mainCommand.RouteFollow(routeId, bikeId, speed, new float[] { 0, -(float)Math.PI / 2f, 0 }, new float[] { 0, 0, 0 })); + WriteTextMessage(mainCommand.RouteFollow(routeId, cameraId, speed)); + } + + #endregion + + #region message send/receive + + /// + /// method that sends the speciefied message with the specified serial, and executes the given action upon receivind a reply from the server with this serial. + /// + /// the networkstream to use + /// the message to send + /// the serial to check for + /// the code to be executed upon reveiving a reply from the server with the specified serial + public void SendMessageAndOnResponse(string message, string serial, HandleSerial action) + { + if (serialResponses.ContainsKey(serial)) + { + serialResponses[serial] = action; + } + else + { + serialResponses.Add(serial, action); + } + WriteTextMessage(message); + } + + /// + /// writes a message to the server + /// + /// the network stream to use + /// the message to send + public void WriteTextMessage(string message) + { + byte[] msg = Encoding.ASCII.GetBytes(message); + byte[] res = new byte[msg.Length + 4]; + + Array.Copy(BitConverter.GetBytes(msg.Length), 0, res, 0, 4); + Array.Copy(msg, 0, res, 4, msg.Length); + + stream.Write(res); + + //Write("sent message " + message); + } + + #endregion + + public void Stop() + { + serverResponseReader.Stop(); + + } + public void Write(string msg) + { + Console.WriteLine("[ENGINECONNECT] " + msg); + } + + } + + +} diff --git a/ClientApp/Utils/ObservableObject.cs b/ClientApp/Utils/ObservableObject.cs new file mode 100644 index 0000000..3809fa1 --- /dev/null +++ b/ClientApp/Utils/ObservableObject.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; + +namespace ClientApp.Utils +{ + public abstract class ObservableObject : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + } +} diff --git a/ClientApp/Utils/Program.cs b/ClientApp/Utils/Program.cs new file mode 100644 index 0000000..ecab577 --- /dev/null +++ b/ClientApp/Utils/Program.cs @@ -0,0 +1,42 @@ +using System; +using Hardware; +using Hardware.Simulators; +using RH_Engine; +using System.Security.Cryptography; +using System.Text; + +namespace ClientApp.Utils +{ + class Program + { + //static void Main(string[] args) + //{ + // Console.WriteLine("// Connecting... //"); + // //connect fiets? + + // Client client = new Client(); + + + // while (!client.IsConnected()) + // { + + // } + // //BLEHandler bLEHandler = new BLEHandler(client); + + // //bLEHandler.Connect(); + + // //client.setHandler(bLEHandler); + + + // BikeSimulator bikeSimulator = new BikeSimulator(client); + + // bikeSimulator.StartSimulation(); + + // client.setHandler(bikeSimulator); + + // while (true) + // { + // } + //} + } +} diff --git a/ClientApp/ViewModels/LoginViewModel.cs b/ClientApp/ViewModels/LoginViewModel.cs new file mode 100644 index 0000000..c714b3c --- /dev/null +++ b/ClientApp/ViewModels/LoginViewModel.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Input; +using ClientApp.Utils; +using GalaSoft.MvvmLight.Command; + +namespace ClientApp.ViewModels +{ + class LoginViewModel : ObservableObject + { + public string Username { get; set; } + private MainWindowViewModel mainWindowViewModel; + public LoginViewModel(MainWindowViewModel mainWindowViewModel) + { + this.mainWindowViewModel = mainWindowViewModel; + LoginCommand = new RelayCommand((parameter) => + { + //TODO send username and password to server + this.mainWindowViewModel.client.tryLogin(Username, ((PasswordBox)parameter).Password); + }); + } + + public ICommand LoginCommand { get; set; } + + internal void setLoginStatus(bool status) + { + this.mainWindowViewModel.InfoModel.ConnectedToServer = true; + if (status) + { + this.mainWindowViewModel.SelectedViewModel = new MainViewModel(mainWindowViewModel); + } + } + } +} diff --git a/ClientApp/ViewModels/MainViewModel.cs b/ClientApp/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..872b33a --- /dev/null +++ b/ClientApp/ViewModels/MainViewModel.cs @@ -0,0 +1,30 @@ +using ClientApp.Models; +using ClientApp.Utils; +using GalaSoft.MvvmLight.Command; +using System.Windows.Input; + +namespace ClientApp.ViewModels +{ + class MainViewModel : ObservableObject + { + public ICommand RetryServerCommand { get; set; } + public ICommand RetryVREngineCommand { get; set; } + public MainWindowViewModel MainWindowViewModel; + + + public MainViewModel(MainWindowViewModel mainWindowViewModel) + { + this.MainWindowViewModel = mainWindowViewModel; + this.RetryServerCommand = new RelayCommand(() => + { + //try connect server + this.MainWindowViewModel.InfoModel.ConnectedToServer = true; + }); + this.RetryVREngineCommand = new RelayCommand(() => + { + //try connect vr-engine + this.MainWindowViewModel.InfoModel.ConnectedToVREngine = true; + }); + } + } +} diff --git a/ClientApp/ViewModels/MainWindowViewModel.cs b/ClientApp/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..f30bf73 --- /dev/null +++ b/ClientApp/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,26 @@ +using ClientApp.Models; +using ClientApp.Utils; +using System; +using System.Collections.Generic; +using System.Text; + +namespace ClientApp.ViewModels +{ + class MainWindowViewModel : ObservableObject + { + public Info InfoModel { get; set; } + + public ObservableObject SelectedViewModel { get; set; } + public Client client { get; } + + public MainWindowViewModel(Client client) + { + this.InfoModel = new Info(); + this.client = client; + LoginViewModel loginViewModel = new LoginViewModel(this); + SelectedViewModel = loginViewModel; + this.client.SetLoginViewModel(loginViewModel); + } + + } +} diff --git a/ClientApp/Views/LoginView.xaml b/ClientApp/Views/LoginView.xaml new file mode 100644 index 0000000..81ea950 --- /dev/null +++ b/ClientApp/Views/LoginView.xaml @@ -0,0 +1,18 @@ + + + + + diff --git a/ClientApp/Views/MainView.xaml.cs b/ClientApp/Views/MainView.xaml.cs new file mode 100644 index 0000000..94161b9 --- /dev/null +++ b/ClientApp/Views/MainView.xaml.cs @@ -0,0 +1,27 @@ +using ClientApp.ViewModels; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace ClientApp.Views +{ + /// + /// Interaction logic for MainView.xaml + /// + public partial class MainView : UserControl + { + public MainView() + { + InitializeComponent(); + } + } +} diff --git a/ClientApp/Views/MainWindow.xaml b/ClientApp/Views/MainWindow.xaml new file mode 100644 index 0000000..025cf3f --- /dev/null +++ b/ClientApp/Views/MainWindow.xaml @@ -0,0 +1,16 @@ + + + + + + diff --git a/ClientApp/Views/MainWindow.xaml.cs b/ClientApp/Views/MainWindow.xaml.cs new file mode 100644 index 0000000..d718f1e --- /dev/null +++ b/ClientApp/Views/MainWindow.xaml.cs @@ -0,0 +1,54 @@ +using ClientApp.ViewModels; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using ClientApp.Utils; +using Hardware.Simulators; +using System.Threading; + +namespace ClientApp +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + Client client = new Client(); + + + InitializeComponent(); + DataContext = new MainWindowViewModel(client); + + + //BLEHandler bLEHandler = new BLEHandler(client); + + //bLEHandler.Connect(); + + //client.setHandler(bLEHandler); + + + BikeSimulator bikeSimulator = new BikeSimulator(client); + + Thread newThread = new Thread(new ThreadStart(bikeSimulator.StartSimulation)); + newThread.Start(); + + + client.SetHandler(bikeSimulator); + + + } + } +} diff --git a/ProftaakRH/BLEHandler.cs b/ProftaakRH/BLEHandler.cs index 269b0a1..c368072 100644 --- a/ProftaakRH/BLEHandler.cs +++ b/ProftaakRH/BLEHandler.cs @@ -44,6 +44,7 @@ namespace Hardware /// public void Connect() { + BLE bleBike = new BLE(); Thread.Sleep(1000); // We need some time to list available devices diff --git a/ProftaakRH/BikeSimulator.cs b/ProftaakRH/BikeSimulator.cs index 3d11209..4dad948 100644 --- a/ProftaakRH/BikeSimulator.cs +++ b/ProftaakRH/BikeSimulator.cs @@ -47,6 +47,7 @@ namespace Hardware.Simulators public void StartSimulation() { + Console.WriteLine("simulating bike..."); //Example BLE Message //4A-09-4E-05-19-16-00-FF-28-00-00-20-F0 diff --git a/ProftaakRH/ProftaakRH.sln b/ProftaakRH/ProftaakRH.sln index 00586c6..05dab66 100644 --- a/ProftaakRH/ProftaakRH.sln +++ b/ProftaakRH/ProftaakRH.sln @@ -9,17 +9,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RH-Engine", "..\RH-Engine\R EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "..\Server\Server.csproj", "{B1AB6F51-A20D-4162-9A7F-B3350B7510FD}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "..\Client\Client.csproj", "{5759DD20-7A4F-4D8D-B986-A70A7818C112}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Message", "..\Message\Message.csproj", "{9ED6832D-B0FB-4460-9BCD-FAA58863B0CE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DokterApp", "..\DokterApp\DokterApp.csproj", "{B150F08B-13DA-4D17-BD96-7E89F52727C6}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Hashing", "..\Hashing\Hashing.shproj", "{70277749-D423-4871-B692-2EFC5A6ED932}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientApp", "..\ClientApp\ClientApp.csproj", "{013AADBA-1D27-4A52-81D8-217697E91039}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution - ..\Hashing\Hashing.projitems*{5759dd20-7a4f-4d8d-b986-a70a7818c112}*SharedItemsImports = 5 + ..\Hashing\Hashing.projitems*{013aadba-1d27-4a52-81d8-217697e91039}*SharedItemsImports = 5 ..\Hashing\Hashing.projitems*{70277749-d423-4871-b692-2efc5a6ed932}*SharedItemsImports = 13 ..\Hashing\Hashing.projitems*{b150f08b-13da-4d17-bd96-7e89f52727c6}*SharedItemsImports = 5 ..\Hashing\Hashing.projitems*{b1ab6f51-a20d-4162-9a7f-b3350b7510fd}*SharedItemsImports = 5 @@ -41,10 +41,6 @@ Global {B1AB6F51-A20D-4162-9A7F-B3350B7510FD}.Debug|Any CPU.Build.0 = Debug|Any CPU {B1AB6F51-A20D-4162-9A7F-B3350B7510FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1AB6F51-A20D-4162-9A7F-B3350B7510FD}.Release|Any CPU.Build.0 = Release|Any CPU - {5759DD20-7A4F-4D8D-B986-A70A7818C112}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5759DD20-7A4F-4D8D-B986-A70A7818C112}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5759DD20-7A4F-4D8D-B986-A70A7818C112}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5759DD20-7A4F-4D8D-B986-A70A7818C112}.Release|Any CPU.Build.0 = Release|Any CPU {9ED6832D-B0FB-4460-9BCD-FAA58863B0CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9ED6832D-B0FB-4460-9BCD-FAA58863B0CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {9ED6832D-B0FB-4460-9BCD-FAA58863B0CE}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -53,6 +49,10 @@ Global {B150F08B-13DA-4D17-BD96-7E89F52727C6}.Debug|Any CPU.Build.0 = Debug|Any CPU {B150F08B-13DA-4D17-BD96-7E89F52727C6}.Release|Any CPU.ActiveCfg = Release|Any CPU {B150F08B-13DA-4D17-BD96-7E89F52727C6}.Release|Any CPU.Build.0 = Release|Any CPU + {013AADBA-1D27-4A52-81D8-217697E91039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {013AADBA-1D27-4A52-81D8-217697E91039}.Debug|Any CPU.Build.0 = Debug|Any CPU + {013AADBA-1D27-4A52-81D8-217697E91039}.Release|Any CPU.ActiveCfg = Release|Any CPU + {013AADBA-1D27-4A52-81D8-217697E91039}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/RH-Engine/Command.cs b/RH-Engine/Command.cs index 110965f..18a93ec 100644 --- a/RH-Engine/Command.cs +++ b/RH-Engine/Command.cs @@ -106,8 +106,21 @@ namespace RH_Engine data = new { name = "dashboard", + parent = uuidBike, components = new { + transform = new + { + position = new float[] + { + -1.5f, 1f, 0f + }, + scale = 1, + rotation = new int[] + { + -30, 90,0 + } + }, panel = new { size = new int[] { 1, 1 }, @@ -130,7 +143,7 @@ namespace RH_Engine data = new { id = uuidPanel, - color = new int[] { 1, 1, 1, 1 } + color = new float[] { 0f, 0f, 0f, 0f } } }; @@ -151,7 +164,7 @@ namespace RH_Engine return JsonConvert.SerializeObject(Payload(payload)); } - public string bikeSpeed(string uuidPanel, string serialCode, double speed) + private string showOnPanel(string uuidPanel, string serialCode, string mText, int index) { dynamic payload = new { @@ -160,9 +173,9 @@ namespace RH_Engine data = new { id = uuidPanel, - text = "Speed: " + speed.ToString(), - position = new int[] { 4, 24 }, - size = 36.0, + text = mText, + position = new int[] { 4, 24 + index * 32 }, + size = 32.0, color = new int[] { 0, 0, 0, 1 }, font = "segoeui" } @@ -171,6 +184,43 @@ namespace RH_Engine return JsonConvert.SerializeObject(Payload(payload)); } + + public string showBikespeed(string uuidPanel, string serialCode, double speed) + { + //dynamic payload = new + //{ + // id = "scene/panel/drawtext", + // serial = serialCode, + // data = new + // { + // id = uuidPanel, + // text = "Speed: " + speed + " m/s", + // position = new int[] { 4, 24 }, + // size = 36.0, + // color = new int[] { 0, 0, 0, 1 }, + // font = "segoeui" + // } + //}; + + //return JsonConvert.SerializeObject(Payload(payload)); + return showOnPanel(uuidPanel, serialCode, "Speed: " + speed + " m/s", 0); + } + + public string showHeartrate(string uuidPanel, string serialCode, int bpm) + { + return showOnPanel(uuidPanel, serialCode, "Heartrate: " + bpm + " bpm", 1); + } + + public string showPower(string uuidPanel, string serialCode, double power) + { + return showOnPanel(uuidPanel, serialCode, "Inst. Power: " + power + " W", 2); + } + + public string showResistance(string uuidPanel, string serialCode, double resistance) + { + return showOnPanel(uuidPanel, serialCode, "Resistance: " + resistance + " %", 3); + } + public string SwapPanelCommand(string uuid) { dynamic payload = new @@ -374,6 +424,20 @@ namespace RH_Engine return JsonConvert.SerializeObject(Payload(payload)); } + public string RouteSpeed(float speedValue,string nodeID) + { + dynamic payload = new + { + id = "route/follow/speed", + data = new + { + node = nodeID, + speed = speedValue + } + }; + return JsonConvert.SerializeObject(Payload(payload)); + } + public string RoadCommand(string uuid_route) { Console.WriteLine("road"); diff --git a/RH-Engine/Program.cs b/RH-Engine/Program.cs index 25a66ee..dc33e25 100644 --- a/RH-Engine/Program.cs +++ b/RH-Engine/Program.cs @@ -17,9 +17,9 @@ namespace RH_Engine //new PC("DESKTOP-M2CIH87", "Fabian"), //new PC("T470S", "Shinichi"), //new PC("DESKTOP-DHS478C", "semme"), - //new PC("HP-ZBOOK-SEM", "Sem"), + new PC("HP-ZBOOK-SEM", "Sem"), //new PC("DESKTOP-TV73FKO", "Wouter"), - new PC("DESKTOP-SINMKT1", "Ralf van Aert"), + //new PC("DESKTOP-SINMKT1", "Ralf van Aert"), //new PC("NA", "Bart") }; @@ -81,7 +81,11 @@ namespace RH_Engine //Console.WriteLine("GOT MESSAGE WITH SERIAL: " + message + "\n\n\n"); string serial = JSONParser.GetSerial(message); //Console.WriteLine("Got serial " + serial); - if (serialResponses.ContainsKey(serial)) serialResponses[serial].Invoke(message); + if (serialResponses.ContainsKey(serial)) + { + serialResponses[serial].Invoke(message); + serialResponses.Remove(serial); + } } } @@ -179,19 +183,11 @@ namespace RH_Engine bool speedReplied = false; bool moveReplied = true; panelId = JSONParser.getPanelID(message); + WriteTextMessage(stream, mainCommand.ColorPanel(panelId)); WriteTextMessage(stream, mainCommand.ClearPanel(panelId)); - - SendMessageAndOnResponse(stream, mainCommand.MoveTo(panelId, "panelMove", new float[] { 0f, 0f, 0f }, "Z", 1, 5), "panelMove", - (message) => - { - Console.WriteLine(message); - SendMessageAndOnResponse(stream, mainCommand.bikeSpeed(panelId, "bikeSpeed", 5.0), "bikeSpeed", - (message) => - { - WriteTextMessage(stream, mainCommand.SwapPanel(panelId)); - }); - }); + + showPanel(stream, mainCommand, 5.3, 83, 52, 53); //while (!(speedReplied && moveReplied)) { } @@ -224,6 +220,26 @@ namespace RH_Engine //WriteTextMessage(stream, mainCommand.TerrainCommand(new int[] { 256, 256 }, null)); //string command; + + + //Console.WriteLine("id of head " + GetId(Command.STANDARD_HEAD, stream, mainCommand)); + + //command = mainCommand.AddModel("car", "data\\customModels\\TeslaRoadster.fbx"); + //WriteTextMessage(stream, command); + + //command = mainCommand.addPanel(); + // WriteTextMessage(stream, command); + // string response = ReadPrefMessage(stream); + // Console.WriteLine("add Panel response: \n\r" + response); + // string uuidPanel = JSONParser.getPanelID(response); + // WriteTextMessage(stream, mainCommand.ClearPanel(uuidPanel)); + // Console.WriteLine(ReadPrefMessage(stream)); + // WriteTextMessage(stream, mainCommand.bikeSpeed(uuidPanel, 2.42)); + // Console.WriteLine(ReadPrefMessage(stream)); + // WriteTextMessage(stream, mainCommand.ColorPanel(uuidPanel)); + // Console.WriteLine("Color panel: " + ReadPrefMessage(stream)); + // WriteTextMessage(stream, mainCommand.SwapPanel(uuidPanel)); + // Console.WriteLine("Swap panel: " + ReadPrefMessage(stream)); Console.WriteLine("id of head " + GetId(Command.STANDARD_HEAD, stream, mainCommand)); } @@ -303,11 +319,39 @@ namespace RH_Engine return res; } + + private static void showPanel(NetworkStream stream, Command mainCommand, double bikeSpeed, int bpm, int power, int resistance) + { + SendMessageAndOnResponse(stream, mainCommand.showBikespeed(panelId, "bikeSpeed", bikeSpeed), "bikeSpeed", + (message) => + { + // TODO check if is drawn + }); + SendMessageAndOnResponse(stream, mainCommand.showHeartrate(panelId, "bpm", bpm), "bpm", + (message) => + { + // TODO check if is drawn + }); + SendMessageAndOnResponse(stream, mainCommand.showPower(panelId, "power", power), "power", + (message) => + { + // TODO check if is drawn + }); + SendMessageAndOnResponse(stream, mainCommand.showResistance(panelId, "resistance", resistance), "resistance", + (message) => + { + // TODO check if is drawn + }); + + // Check if every text is drawn! + + WriteTextMessage(stream, mainCommand.SwapPanel(panelId)); + } + private static void SetFollowSpeed(float speed, NetworkStream stream, Command mainCommand) { WriteTextMessage(stream, mainCommand.RouteFollow(routeId, bikeId, speed, new float[] { 0, -(float)Math.PI / 2f, 0 }, new float[] { 0, 0, 0 })); WriteTextMessage(stream, mainCommand.RouteFollow(routeId, cameraId, speed)); - WriteTextMessage(stream, mainCommand.RouteFollow(routeId, panelId, speed, 0, "XYZ", 1, false, new float[] { 0, 0, 0 }, new float[] { 0f, 0f, 150f })); } //string routeID, string nodeID, float speedValue, float offsetValue, string rotateValue, float smoothingValue, bool followHeightValue, float[] rotateOffsetVector, float[] positionOffsetVector) private static void Force(NetworkStream stream, string message, string serial, HandleSerial action) diff --git a/RH-Engine/ServerResponseReader.cs b/RH-Engine/ServerResponseReader.cs index 17e86ba..bafa442 100644 --- a/RH-Engine/ServerResponseReader.cs +++ b/RH-Engine/ServerResponseReader.cs @@ -9,6 +9,8 @@ namespace RH_Engine public class ServerResponseReader { + private bool running; + private Thread t; public OnResponse callback { get; set; @@ -23,7 +25,7 @@ namespace RH_Engine public void StartRead() { - Thread t = new Thread(() => + t = new Thread(() => { if (this.callback == null) { @@ -31,8 +33,9 @@ namespace RH_Engine } else { + running = true; Console.WriteLine("[SERVERRESPONSEREADER] Starting loop for reading"); - while (true) + while (running) { string res = ReadPrefMessage(Stream); //Console.WriteLine("[SERVERRESPONSEREADER] got message from server: " + res); @@ -44,6 +47,13 @@ namespace RH_Engine t.Start(); } + public void Stop() + { + running = false; + t.Join(); + Stream.Close(); + } + /// /// reads a response from the server /// diff --git a/Server/Client.cs b/Server/Client.cs index da264b6..bc57b00 100644 --- a/Server/Client.cs +++ b/Server/Client.cs @@ -1,11 +1,9 @@ using System; using System.IO; -using System.Linq; using System.Net.Sockets; using System.Text; -using Client; using Newtonsoft.Json; -using System.Security.Cryptography; +using ClientApp.Utils; namespace Server { @@ -77,7 +75,7 @@ namespace Server /// - /// TODO + /// handles all incoming data from the client /// /// including message length and messageId (can be changed) private void HandleData(byte[] message) @@ -143,7 +141,9 @@ namespace Server } else if (DataParser.isRawData(message)) { + // print the raw data Console.WriteLine(BitConverter.ToString(payloadbytes)); + // TODO change, checking for length is not that safe if (payloadbytes.Length == 8) { saveData?.WriteDataRAWBike(payloadbytes); @@ -168,7 +168,6 @@ namespace Server private bool verifyLogin(string username, string password) { - Console.WriteLine("got hashes " + username + "\n" + password); if (!File.Exists(fileName)) @@ -217,10 +216,6 @@ namespace Server } } - - - - public static string ByteArrayToString(byte[] ba) { StringBuilder hex = new StringBuilder(ba.Length * 2); diff --git a/Server/SaveData.cs b/Server/SaveData.cs index bcabb1f..2e78392 100644 --- a/Server/SaveData.cs +++ b/Server/SaveData.cs @@ -63,12 +63,20 @@ namespace Server { // do nothing } - using (BinaryWriter sw = new BinaryWriter(File.Open(fileLocation, FileMode.Create))) + using (var fileStream = new FileStream(fileLocation, FileMode.Append, FileAccess.Write, FileShare.None)) + using (var bw = new BinaryWriter(fileStream)) { - sw.Seek(length, SeekOrigin.End); - sw.Write(data); - sw.Flush(); + //sw.BaseStream.Seek(length, SeekOrigin.Begin); + bw.Write(data); + bw.Flush(); + //Console.WriteLine("wrote at " + bw.BaseStream.Position); } + //using (BinaryReader binaryReader = new BinaryReader(File.Open(fileLocation, FileMode.Open))) + //{ + // byte[] totalArray = new byte[binaryReader.BaseStream.Length]; + // binaryReader.BaseStream.Read(totalArray, 0, (int)binaryReader.BaseStream.Length); + // Console.WriteLine("all data is " + BitConverter.ToString(totalArray)); + //} } /// @@ -86,6 +94,7 @@ namespace Server { FileInfo fi = new FileInfo(this.path + rawBPMFilename); int length = (int)fi.Length; + //Console.WriteLine("length " + length); byte[] output = new byte[outputSize]; @@ -93,15 +102,20 @@ namespace Server int readSize = messageSize * averageOver; byte[] readBuffer = new byte[readSize]; - using (FileStream fileStream = new FileStream(this.path + rawBPMFilename, FileMode.Open, FileAccess.Read)) + using (BinaryReader binaryReader = new BinaryReader(File.Open(this.path + rawBPMFilename, FileMode.Open))) { - for (int i = 1; i >= outputSize; i++) + //byte[] totalArray = new byte[binaryReader.BaseStream.Length]; + //binaryReader.BaseStream.Read(totalArray, 0, (int)binaryReader.BaseStream.Length); + //Console.WriteLine("all data is " + BitConverter.ToString(totalArray)); + for (int i = 1; i <= outputSize; i++) { if (length - (i * readSize) < 0) { break; } - fileStream.Read(readBuffer, length - (i * readSize), readSize); + binaryReader.BaseStream.Seek(length - (i * readSize), SeekOrigin.Begin); + binaryReader.BaseStream.Read(readBuffer, 0, readSize); + //Console.WriteLine("read " + binaryReader.BaseStream.Position + " and size " + readSize + " with value " + BitConverter.ToString(readBuffer)); //handling data int total = 0; diff --git a/Server/Server.csproj b/Server/Server.csproj index cd7c7da..0338a27 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -10,7 +10,7 @@ - +