diff --git a/Client/Client.cs b/Client/Client.cs index 123d222..2eda6bd 100644 --- a/Client/Client.cs +++ b/Client/Client.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Net.Sockets; using System.Text; @@ -6,7 +6,7 @@ using ProftaakRH; namespace Client { - class Client : IDataReceiver + public class Client : IDataReceiver { private TcpClient client; private NetworkStream stream; @@ -14,6 +14,9 @@ namespace Client private bool connected; private byte[] totalBuffer = new byte[1024]; private int totalBufferReceived = 0; + private EngineConnection engineConnection; + private bool sessionRunning = false; + private IHandler handler = null; public Client() : this("localhost", 5555) @@ -28,10 +31,50 @@ 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 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("Verbonden!"); + Console.WriteLine("TCP client Verbonden!"); this.stream = this.client.GetStream(); @@ -41,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); @@ -74,7 +121,9 @@ namespace Client string responseStatus = DataParser.getResponseStatus(payloadbytes); if (responseStatus == "OK") { + Console.WriteLine("Username and password correct!"); this.connected = true; + initEngine(); } else { @@ -82,6 +131,30 @@ namespace Client tryLogin(); } 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; @@ -100,39 +173,97 @@ 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); + } #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]; + + } + + 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) { + bool canSendToEngine = engineConnection.Connected && engineConnection.FollowingRoute; + if (!sessionRunning) + { + return; + } if (bytes == null) { throw new ArgumentNullException("no bytes"); } byte[] message = DataParser.GetRawDataMessage(bytes); + switch (bytes[0]) + { + case 0x10: + + if (canSendToEngine) engineConnection.BikeSpeed = (bytes[4] | (bytes[5] << 8)) * 0.01f; + break; + case 0x19: + if (canSendToEngine) engineConnection.BikePower = (bytes[5]) | (bytes[6] & 0b00001111) << 8; + break; + } + + 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 @@ -141,9 +272,22 @@ namespace Client Console.WriteLine("enter password"); string password = Console.ReadLine(); - byte[] message = DataParser.getJsonMessage(DataParser.GetLoginJson(username, 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; + } } } diff --git a/Client/Client.csproj b/Client/Client.csproj index dd9af6e..6aa0290 100644 --- a/Client/Client.csproj +++ b/Client/Client.csproj @@ -12,6 +12,9 @@ + + + diff --git a/Client/DataParser.cs b/Client/DataParser.cs index c83b86a..8eddb4c 100644 --- a/Client/DataParser.cs +++ b/Client/DataParser.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json.Serialization; using System; using System.Globalization; using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; using System.Text; namespace Client @@ -10,7 +11,10 @@ namespace Client public class DataParser { public const string LOGIN = "LOGIN"; - public const string LOGIN_RESPONSE = "LOGIN_RESPONSE"; + 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 /// @@ -59,6 +63,15 @@ namespace Client 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 }); @@ -150,15 +163,42 @@ namespace Client return getMessage(payload, 0x01); } - /// - /// constructs a message with the message and clientId - /// - /// - /// - /// the message ready for sending - public static byte[] getJsonMessage(string message) + public static byte[] getStartSessionJson() { - return getJsonMessage(Encoding.ASCII.GetBytes(message)); + 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/Client/EngineConnection.cs b/Client/EngineConnection.cs new file mode 100644 index 0000000..fe3224e --- /dev/null +++ b/Client/EngineConnection.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.Text; +using RH_Engine; +using System.Net.Sockets; + +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(); + private static System.Timers.Timer updateTimer; + 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; + updateTimer = new System.Timers.Timer(1000); + updateTimer.Elapsed += UpdateTimer_Elapsed; + updateTimer.AutoReset = true; + updateTimer.Enabled = false; + } + + private void UpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + UpdateInfoPanel(); + } + + /// + /// 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"); + }); + // 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(); + updateTimer.Enabled = true; + + 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/Client/Program.cs b/Client/Program.cs index 7b722f5..51dda1f 100644 --- a/Client/Program.cs +++ b/Client/Program.cs @@ -1,6 +1,10 @@ using System; using Hardware; using Hardware.Simulators; +using RH_Engine; +using System.Security.Cryptography; +using System.Text; +using System.Threading; namespace Client { @@ -8,10 +12,8 @@ namespace Client { static void Main(string[] args) { - Console.WriteLine("Hello World!"); + Console.WriteLine("// Connecting... //"); //connect fiets? - - Client client = new Client(); @@ -19,17 +21,22 @@ namespace Client { } - //BLEHandler bLEHandler = new BLEHandler(client); + BLEHandler bLEHandler = new BLEHandler(client); - //bLEHandler.Connect(); + bLEHandler.Connect(); - BikeSimulator bikeSimulator = new BikeSimulator(client); + client.setHandler(bLEHandler); - bikeSimulator.StartSimulation(); - while (true) - { - } + //BikeSimulator bikeSimulator = new BikeSimulator(client); + + //bikeSimulator.StartSimulation(); + + //client.setHandler(bikeSimulator); + + //while (true) + //{ + //} } } } diff --git a/ClientApp/App.xaml b/ClientApp/App.xaml new file mode 100644 index 0000000..daae7b7 --- /dev/null +++ b/ClientApp/App.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + 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..edc95d3 --- /dev/null +++ b/ClientApp/ClientApp.csproj @@ -0,0 +1,66 @@ + + + + WinExe + netcoreapp3.1 + true + Images\Logo\icon1.ico + + + + true + + + + true + + + + + + + + + + + + + + Always + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + Always + + + Always + + + + + + + + + + + + + + + + + + + + + + \ 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/Images/CoolBackground.jpg b/ClientApp/Images/CoolBackground.jpg new file mode 100644 index 0000000..b78d1da Binary files /dev/null and b/ClientApp/Images/CoolBackground.jpg differ diff --git a/ClientApp/Images/Icons/CheckMark.png b/ClientApp/Images/Icons/CheckMark.png new file mode 100644 index 0000000..6bb2ed3 Binary files /dev/null and b/ClientApp/Images/Icons/CheckMark.png differ diff --git a/ClientApp/Images/Icons/CrossMark.png b/ClientApp/Images/Icons/CrossMark.png new file mode 100644 index 0000000..4a30bac Binary files /dev/null and b/ClientApp/Images/Icons/CrossMark.png differ diff --git a/ClientApp/Images/Logo/icon1.ico b/ClientApp/Images/Logo/icon1.ico new file mode 100644 index 0000000..faa56b4 Binary files /dev/null and b/ClientApp/Images/Logo/icon1.ico differ diff --git a/ClientApp/Images/Logo/icon1_small.ico b/ClientApp/Images/Logo/icon1_small.ico new file mode 100644 index 0000000..95760d6 Binary files /dev/null and b/ClientApp/Images/Logo/icon1_small.ico differ diff --git a/ClientApp/Images/re15.jpg b/ClientApp/Images/re15.jpg new file mode 100644 index 0000000..34dc9f1 Binary files /dev/null and b/ClientApp/Images/re15.jpg differ diff --git a/ClientApp/Images/stone.png b/ClientApp/Images/stone.png new file mode 100644 index 0000000..5f4b1d0 Binary files /dev/null and b/ClientApp/Images/stone.png differ diff --git a/ClientApp/MagicCode/WindowResizer.cs b/ClientApp/MagicCode/WindowResizer.cs new file mode 100644 index 0000000..c997d89 --- /dev/null +++ b/ClientApp/MagicCode/WindowResizer.cs @@ -0,0 +1,212 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Interop; + +namespace ClientApp.MagicCode +{ + /// + /// Fixes the issue with Windows of Style covering the taskbar + /// + public class WindowResizer + { + #region Private Members + + /// + /// The window to handle the resizing for + /// + private Window mWindow; + + #endregion + + #region Dll Imports + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool GetCursorPos(out POINT lpPoint); + + [DllImport("user32.dll")] + static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi); + + [DllImport("user32.dll", SetLastError = true)] + static extern IntPtr MonitorFromPoint(POINT pt, MonitorOptions dwFlags); + + #endregion + + #region Constructor + + /// + /// Default constructor + /// + /// The window to monitor and correctly maximize + /// The callback for the host to adjust the maximum available size if needed + public WindowResizer(Window window) + { + mWindow = window; + + // Listen out for source initialized to setup + mWindow.SourceInitialized += Window_SourceInitialized; + } + + #endregion + + #region Initialize + + /// + /// Initialize and hook into the windows message pump + /// + /// + /// + private void Window_SourceInitialized(object sender, System.EventArgs e) + { + // Get the handle of this window + var handle = (new WindowInteropHelper(mWindow)).Handle; + var handleSource = HwndSource.FromHwnd(handle); + + // If not found, end + if (handleSource == null) + return; + + // Hook into it's Windows messages + handleSource.AddHook(WindowProc); + } + + #endregion + + #region Windows Proc + + /// + /// Listens out for all windows messages for this window + /// + /// + /// + /// + /// + /// + /// + private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + switch (msg) + { + // Handle the GetMinMaxInfo of the Window + case 0x0024:/* WM_GETMINMAXINFO */ + WmGetMinMaxInfo(hwnd, lParam); + handled = true; + break; + } + + return (IntPtr)0; + } + + #endregion + + /// + /// Get the min/max window size for this window + /// Correctly accounting for the taskbar size and position + /// + /// + /// + private void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam) + { + POINT lMousePosition; + GetCursorPos(out lMousePosition); + + IntPtr lPrimaryScreen = MonitorFromPoint(new POINT(0, 0), MonitorOptions.MONITOR_DEFAULTTOPRIMARY); + MONITORINFO lPrimaryScreenInfo = new MONITORINFO(); + if (GetMonitorInfo(lPrimaryScreen, lPrimaryScreenInfo) == false) + { + return; + } + + IntPtr lCurrentScreen = MonitorFromPoint(lMousePosition, MonitorOptions.MONITOR_DEFAULTTONEAREST); + + MINMAXINFO lMmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO)); + + if (lPrimaryScreen.Equals(lCurrentScreen) == true) + { + lMmi.ptMaxPosition.X = lPrimaryScreenInfo.rcWork.Left; + lMmi.ptMaxPosition.Y = lPrimaryScreenInfo.rcWork.Top; + lMmi.ptMaxSize.X = lPrimaryScreenInfo.rcWork.Right - lPrimaryScreenInfo.rcWork.Left; + lMmi.ptMaxSize.Y = lPrimaryScreenInfo.rcWork.Bottom - lPrimaryScreenInfo.rcWork.Top; + } + else + { + lMmi.ptMaxPosition.X = lPrimaryScreenInfo.rcMonitor.Left; + lMmi.ptMaxPosition.Y = lPrimaryScreenInfo.rcMonitor.Top; + lMmi.ptMaxSize.X = lPrimaryScreenInfo.rcMonitor.Right - lPrimaryScreenInfo.rcMonitor.Left; + lMmi.ptMaxSize.Y = lPrimaryScreenInfo.rcMonitor.Bottom - lPrimaryScreenInfo.rcMonitor.Top; + } + + // Now we have the max size, allow the host to tweak as needed + Marshal.StructureToPtr(lMmi, lParam, true); + } + } + + #region Dll Helper Structures + + enum MonitorOptions : uint + { + MONITOR_DEFAULTTONULL = 0x00000000, + MONITOR_DEFAULTTOPRIMARY = 0x00000001, + MONITOR_DEFAULTTONEAREST = 0x00000002 + } + + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public class MONITORINFO + { + public int cbSize = Marshal.SizeOf(typeof(MONITORINFO)); + public Rectangle rcMonitor = new Rectangle(); + public Rectangle rcWork = new Rectangle(); + public int dwFlags = 0; + } + + + [StructLayout(LayoutKind.Sequential)] + public struct Rectangle + { + public int Left, Top, Right, Bottom; + + public Rectangle(int left, int top, int right, int bottom) + { + this.Left = left; + this.Top = top; + this.Right = right; + this.Bottom = bottom; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct MINMAXINFO + { + public POINT ptReserved; + public POINT ptMaxSize; + public POINT ptMaxPosition; + public POINT ptMinTrackSize; + public POINT ptMaxTrackSize; + }; + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + /// + /// x coordinate of point. + /// + public int X; + /// + /// y coordinate of point. + /// + public int Y; + + /// + /// Construct a point of coordinates (x,y). + /// + public POINT(int x, int y) + { + this.X = x; + this.Y = y; + } + } + + #endregion +} \ No newline at end of file diff --git a/ClientApp/Models/Info.cs b/ClientApp/Models/Info.cs new file mode 100644 index 0000000..262032d --- /dev/null +++ b/ClientApp/Models/Info.cs @@ -0,0 +1,17 @@ +using ClientApp.Utils; +using System; +using System.Collections.Generic; +using System.Text; +using Util; + +namespace ClientApp.Models +{ + class Info : ObservableObject + { + public bool ConnectedToServer { get; set; } + public bool ConnectedToVREngine { get; set; } + public bool DoctorConnected { get; set; } + + public bool CanConnectToVR { get; set; } + } +} diff --git a/ClientApp/Utils/Client.cs b/ClientApp/Utils/Client.cs new file mode 100644 index 0000000..5dc8291 --- /dev/null +++ b/ClientApp/Utils/Client.cs @@ -0,0 +1,331 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using ClientApp.ViewModels; +using ProftaakRH; +using Util; + +namespace ClientApp.Utils +{ + public delegate void EngineCallback(); + public class Client : IDataReceiver, IDisposable + { + public EngineCallback engineConnectFailed; + public EngineCallback engineConnectSuccess; + 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; + public EngineConnection engineConnection; + private bool sessionRunning = true; + 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() + { + Debug.WriteLine("init engine"); + engineConnection = EngineConnection.INSTANCE; + engineConnection.OnNoTunnelId = RetryEngineConnection; + engineConnection.OnSuccessFullConnection = engineConnected; + engineConnection.OnEngineDisconnect = RetryEngineConnection; + if (!engineConnection.Connected) engineConnection.Connect(); + } + + /// + /// retries to connect to the VR engine if no tunnel id was found + /// + public void RetryEngineConnection() + { + engineConnectFailed?.Invoke(); + + } + + private void engineConnected() + { + Console.WriteLine("successfully connected to VR engine"); + engineConnectSuccess?.Invoke(); + 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) + { + Debug.WriteLine("got message in client app"); + if (ar == null || (!ar.IsCompleted) || (!this.stream.CanRead)) + return; + + + 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") + { + Debug.WriteLine("Username and password correct!"); + this.LoginViewModel.setLoginStatus(true); + this.connected = true; + initEngine(); + + } + else + { + this.LoginViewModel.setLoginStatus(false); + Debug.WriteLine($"login failed \"{responseStatus}\""); + } + break; + case DataParser.START_SESSION: + Debug.WriteLine("Session started!"); + this.sessionRunning = true; + if (engineConnection.Connected && !engineConnection.FollowingRoute) engineConnection.StartRouteFollow(); + Debug.WriteLine("start"); + break; + case DataParser.STOP_SESSION: + Console.WriteLine("Stop session identifier"); + this.sessionRunning = false; + stopBikeInSimulation(); + Debug.WriteLine("stop"); + break; + case DataParser.SET_RESISTANCE: + Debug.WriteLine("Set resistance identifier"); + if (this.handler == null) + { + // send that the operation was not successful if the handler is null + Debug.WriteLine("handler is null"); + sendMessage(DataParser.getSetResistanceResponseJson(false)); + } + else + { + // set the resistance in the vr scene and send that it was successful + float resistance = DataParser.getResistanceFromJson(payloadbytes); + Debug.WriteLine("resistance set was " + resistance); + this.handler.setResistance(resistance); + engineConnection.BikeResistance = resistance; + sendMessage(DataParser.getSetResistanceResponseJson(true)); + } + break; + case DataParser.MESSAGE: + Debug.WriteLine("client has received message from doctor"); + engineConnection.DoctorMessage = DataParser.getChatMessageFromJson(payloadbytes); + break; + case DataParser.NEW_CONNECTION: + this.LoginViewModel.DoctorConnected(DataParser.getUsernameFromJson(payloadbytes)); + break; + case DataParser.DISCONNECT: + this.LoginViewModel.DoctorDisconnected(DataParser.getUsernameFromJson(payloadbytes)); + break; + default: + Console.WriteLine($"Received json with identifier {identifier}:\n{Encoding.ASCII.GetString(payloadbytes)}"); + break; + } + } + else if (DataParser.isRawDataBikeServer(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 + public 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.GetRawBPMDataMessageServer(bytes); + + if (engineConnection != null && engineConnection.Connected && engineConnection.FollowingRoute) + { + engineConnection.BikeBPM = bytes[1]; + } + + 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.GetRawBikeDataMessageServer(bytes); + bool canSendToEngine = engineConnection != null && engineConnection.Connected && engineConnection.FollowingRoute && this.sessionRunning; + switch (bytes[0]) + { + + case 0x10: + + if (canSendToEngine) engineConnection.BikeSpeed = (bytes[4] | (bytes[5] << 8)) * 0.01f; + else if (engineConnection != null) engineConnection.BikeSpeed = 0; + break; + case 0x19: + if (canSendToEngine) engineConnection.BikePower = (bytes[5]) | (bytes[6] & 0b00001111) << 8; + else if (engineConnection != null) engineConnection.BikePower = 0; + break; + } + + + this.stream?.BeginWrite(message, 0, message.Length, new AsyncCallback(OnWrite), null); + } + + private void stopBikeInSimulation() + { + engineConnection.BikeSpeed = 0; + engineConnection.BikePower = 0; + } + + #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 hashPassword = Util.Hasher.HashString(password); + + byte[] message = DataParser.getJsonMessage(DataParser.GetLoginJson(username, 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; + } + + public void Dispose() + { + Debug.WriteLine("client dispose called"); + sendMessage(DataParser.getDisconnectJson(LoginViewModel.Username)); + this.stream.Dispose(); + this.client.Dispose(); + this.handler.stop(); + this.engineConnection.Stop(); + } + } +} diff --git a/ClientApp/Utils/EngineConnection.cs b/ClientApp/Utils/EngineConnection.cs new file mode 100644 index 0000000..a717d0c --- /dev/null +++ b/ClientApp/Utils/EngineConnection.cs @@ -0,0 +1,437 @@ +using System; +using System.Collections.Generic; +using System.Text; +using RH_Engine; +using System.Net.Sockets; +using Newtonsoft.Json.Linq; +using System.Diagnostics; +using LibNoise.Primitive; + +namespace ClientApp.Utils +{ + public delegate void HandleSerial(string message); + public delegate void EngineDelegate(); + + public sealed class EngineConnection + { + private static EngineConnection instance = null; + private static readonly object padlock = new object(); + private static System.Timers.Timer updateTimer; + private static System.Timers.Timer noVRResponseTimer; + public EngineDelegate OnNoTunnelId; + public EngineDelegate OnSuccessFullConnection; + public EngineDelegate OnEngineDisconnect; + + + 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; + private static string groundPlaneId = string.Empty; + private static string terrainId = string.Empty; + + public string DoctorMessage { get; set; } + 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; + DoctorMessage = "No message received yet"; + updateTimer = new System.Timers.Timer(1000); + updateTimer.Elapsed += UpdateTimer_Elapsed; + updateTimer.AutoReset = true; + updateTimer.Enabled = false; + + noVRResponseTimer = new System.Timers.Timer(15000); + noVRResponseTimer.Elapsed += noVRResponseTimeout; + noVRResponseTimer.AutoReset = false; + noVRResponseTimer.Enabled = false; + + } + + private void UpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + UpdateInfoPanel(); + } + + private void noVRResponseTimeout(object sender, System.Timers.ElapsedEventArgs e) + { + Write("VR RESPONSE TIMEOUT"); + noVRResponseTimer.Stop(); + sessionId = string.Empty; + tunnelId = string.Empty; + cameraId = string.Empty; + routeId = string.Empty; + panelId = string.Empty; + bikeId = string.Empty; + groundPlaneId = string.Empty; + terrainId = string.Empty; + OnEngineDisconnect?.Invoke(); + } + + /// + /// 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; + noVRResponseTimer.Enabled = 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); + } + + noVRResponseTimer.Stop(); + noVRResponseTimer.Start(); + } + + 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"); + groundPlaneId = JSONParser.GetIdSceneInfoChild(message, "GroundPlane"); + }); + // add the route and set the route id + CreateTerrain(); + SendMessageAndOnResponse(mainCommand.RouteCommand("routeID"), "routeID", (message) => routeId = JSONParser.GetResponseUuid(message)); + } + + internal void StartRouteFollow() + { + Write("Starting route follow..."); + FollowingRoute = true; + + SendMessageAndOnResponse(mainCommand.AddBikeModelAnim("bikeID", 0.01f), "bikeID", + (message) => + { + bikeId = JSONParser.GetResponseUuid(message); + SendMessageAndOnResponse(mainCommand.addPanel("panelAdd", bikeId), "panelAdd", + (message) => + { + + panelId = JSONParser.getPanelID(message); + + WriteTextMessage(mainCommand.ColorPanel(panelId)); + UpdateInfoPanel(); + updateTimer.Enabled = true; + + while (cameraId == string.Empty) { } + SetFollowSpeed(5.0f); + WriteTextMessage(mainCommand.RoadCommand(routeId, "road")); + WriteTextMessage(mainCommand.ShowRoute("showRouteFalse", false)); + }); + }); + setEnvironment(); + + + } + + private void setEnvironment() + { + Write("Setting environment"); + WriteTextMessage(mainCommand.DeleteNode(groundPlaneId, "none")); + + PlaceHouses(); + + WriteTextMessage(mainCommand.SkyboxCommand(DateTime.Now.Hour)); + } + + private void PlaceHouses() + { + PlaceHouse(2, new float[] { 10f, 1f, 30f }, 1); + PlaceHouse(1, new float[] { 42f, 1f, 22f }, new float[] { 0f, 90f, 0f }, 2); + PlaceHouse(11, new float[] { -20f, 1f, 0f }, new float[] { 0f, -35f, 0f }, 3); + PlaceHouse(7, new float[] { -15f, 1f, 50f }, new float[] { 0f, -50f, 0f }, 4); + PlaceHouse(24, new float[] { 40f, 1f, 40f }, new float[] { 0f, 75f, 0f }, 5); + PlaceHouse(22, new float[] { 34f, 1f, -20f }, 6); + PlaceHouse(14, new float[] { 0f, 1f, -20f }, new float[] { 0f, 210f, 0f }, 7); + } + + private void PlaceHouse(int numberHousemodel, float[] position, int serialNumber) + { + PlaceHouse(numberHousemodel, position, new float[] { 0f, 0f, 0f }, serialNumber); + } + + private void PlaceHouse(int numberHousemodel, float[] position, float[] rotation, int serialNumber) + { + string folderHouses = @"data\NetworkEngine\models\houses\set1\"; + SendMessageAndOnResponse(mainCommand.AddModel("House1", "housePlacement" + serialNumber, folderHouses + "house" + numberHousemodel + ".obj", position, 4, rotation), "housePlacement" + serialNumber, (message) => Console.WriteLine(message)); + } + + 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 + }); + SendMessageAndOnResponse(mainCommand.showMessage(panelId, "message", DoctorMessage), "message", + (message) => + { + // TODO check if is drawn + }); + + // Check if every text is drawn! + + WriteTextMessage(mainCommand.SwapPanel(panelId)); + } + + private void SetFollowSpeed(float speed) + { + Write("starting route follow"); + 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)); + } + + public void CreateTerrain() + { + float x = 0f; + float[] height = new float[256 * 256]; + ImprovedPerlin improvedPerlin = new ImprovedPerlin(0, LibNoise.NoiseQuality.Best); + for (int i = 0; i < 256 * 256; i++) + { + height[i] = improvedPerlin.GetValue(x / 10, x / 10, x * 100) / 3.5f + 1; + + //if (height[i] > 1.1f) + //{ + // height[i] = height[i] * 0.8f; + //} + //else if (height[i] < 0.9f) + //{ + // height[i] = height[i] * 1.2f; + //} + x += 0.001f; + } + + SendMessageAndOnResponse(mainCommand.TerrainAdd(new int[] { 256, 256 }, height, "terrain"), "terrain", + (message) => + { + + SendMessageAndOnResponse(mainCommand.renderTerrain("renderTerrain"), "renderTerrain", + (message) => + { + terrainId = JSONParser.GetTerrainID(message); + string addLayerMsg = mainCommand.AddLayer(terrainId, "addLayer"); + SendMessageAndOnResponse(addLayerMsg, "addLayer", (message) => Console.WriteLine("")); + + }); + }); + } + + #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) + { + Debug.WriteLine("[ENGINECONNECT] " + msg); + } + + } + + +} 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/ValueConverters/BoolToMarkConverter.cs b/ClientApp/ValueConverters/BoolToMarkConverter.cs new file mode 100644 index 0000000..ea03634 --- /dev/null +++ b/ClientApp/ValueConverters/BoolToMarkConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Data; +using System.Windows.Media.Imaging; + +namespace ClientApp.ValueConverters +{ + //[ValueConversion(typeof(bool), typeof(BitmapImage))] + + class BoolToMarkConverter : IValueConverter + { + public BitmapImage TrueImage { get; set; } = new BitmapImage(new Uri("pack://application:,,,/Images/Icons/CheckMark.png")); + public BitmapImage FalseImage { get; set; } = new BitmapImage(new Uri("pack://application:,,,/Images/Icons/CrossMark.png")); + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (!(value is bool)) + { + return null; + } + + bool b = (bool)value; + if (b) + { + return this.TrueImage; + } + else + { + return this.FalseImage; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/ClientApp/ViewModels/LoginViewModel.cs b/ClientApp/ViewModels/LoginViewModel.cs new file mode 100644 index 0000000..3ca025d --- /dev/null +++ b/ClientApp/ViewModels/LoginViewModel.cs @@ -0,0 +1,56 @@ +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; +using Util; + +namespace ClientApp.ViewModels +{ + class LoginViewModel : ObservableObject + { + public string Username { get; set; } + public ICommand LoginCommand { get; set; } + + public bool LoginStatus { get; set; } = false; + + public bool InvertedLoginStatus { 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); + }); + } + + + + internal void setLoginStatus(bool status) + { + this.MainWindowViewModel.InfoModel.ConnectedToServer = status; + this.InvertedLoginStatus = !status; + if (status) + { + this.MainWindowViewModel.SelectedViewModel = new MainViewModel(MainWindowViewModel); + } + } + + internal void DoctorConnected(string name) + { + this.MainWindowViewModel.InfoModel.DoctorConnected = true; + } + + internal void DoctorDisconnected(string name) + { + this.MainWindowViewModel.InfoModel.DoctorConnected = false; + } + } +} diff --git a/ClientApp/ViewModels/MainViewModel.cs b/ClientApp/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..0e5d601 --- /dev/null +++ b/ClientApp/ViewModels/MainViewModel.cs @@ -0,0 +1,46 @@ +using ClientApp.Models; +using ClientApp.Utils; +using GalaSoft.MvvmLight.Command; +using System.Diagnostics; +using System.Windows.Input; +using Util; + +namespace ClientApp.ViewModels +{ + class MainViewModel : ObservableObject + { + public ICommand RetryServerCommand { get; set; } + public MainWindowViewModel MainWindowViewModel { get; set; } + + private Client client; + + public MainViewModel(MainWindowViewModel mainWindowViewModel) + { + this.MainWindowViewModel = mainWindowViewModel; + client = this.MainWindowViewModel.client; + client.engineConnectFailed = retryEngineConnection; + client.engineConnectSuccess = succesEngineConnection; + this.RetryServerCommand = new RelayCommand(() => + { + //try connect server + this.MainWindowViewModel.InfoModel.ConnectedToServer = true; + }); + } + + private void retryEngineConnection() + { + this.MainWindowViewModel.InfoModel.ConnectedToVREngine = false; + this.MainWindowViewModel.InfoModel.CanConnectToVR = true; + client.engineConnection.CreateConnection(); + } + + private void succesEngineConnection() + { + this.MainWindowViewModel.InfoModel.ConnectedToVREngine = true; + this.MainWindowViewModel.InfoModel.CanConnectToVR = false; + + } + + + } +} diff --git a/ClientApp/ViewModels/MainWindowViewModel.cs b/ClientApp/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..5bcc936 --- /dev/null +++ b/ClientApp/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,137 @@ +using ClientApp.MagicCode; +using ClientApp.Models; +using ClientApp.Utils; +using GalaSoft.MvvmLight.Command; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using Util; +using System.Windows; +using System.Windows.Input; + +namespace ClientApp.ViewModels +{ + class MainWindowViewModel : ObservableObject + { + #region private members + + private Window mWindow; + + private int mOuterMarginSize = 10; + private int mWindowRadius = 10; + + #endregion + + #region commands + + public ICommand MinimizeCommand { get; set; } + + public ICommand MaximizeCommand { get; set; } + + public ICommand CloseCommand { get; set; } + + public ICommand MenuCommand { get; set; } + + #endregion + + #region public properties + public Info InfoModel { get; set; } + + public ObservableObject SelectedViewModel { get; set; } + + public Client client { get; } + + /// + /// size of the resize border around the window + /// + + public double MinimumWidth { get; set; } = 250; + + public double MinimumHeight { get; set; } = 250; + + + + public int ResizeBorder { get; set; } = 6; + + public Thickness ResizeBorderThickness { get { return new Thickness(ResizeBorder + OuterMarginSize); } } + + public Thickness InnerContentPadding { get { return new Thickness(ResizeBorder); } } + + + public Thickness OuterMarginThickness { get { return new Thickness(OuterMarginSize); } } + + public CornerRadius WindowCornerRadius { get { return new CornerRadius(WindowRadius); } } + + public int OuterMarginSize + { + get + { + return mWindow.WindowState == WindowState.Maximized ? 0 : mOuterMarginSize; + } + set + { + mOuterMarginSize = value; + } + } + + public int WindowRadius + { + get + { + return mWindow.WindowState == WindowState.Maximized ? 0 : mWindowRadius; + } + set + { + mWindowRadius = value; + } + } + + public int TitleHeight { get; set; } = 42; + + public GridLength TitleHeightGridLength { get { return new GridLength(TitleHeight + ResizeBorder); } } + + #endregion + + public MainWindowViewModel(Window window, Client client) + { + this.mWindow = window; + + this.mWindow.StateChanged += (sender, e) => + { + OnPropertyChanged(nameof(ResizeBorderThickness)); + OnPropertyChanged(nameof(OuterMarginThickness)); + OnPropertyChanged(nameof(WindowCornerRadius)); + OnPropertyChanged(nameof(OuterMarginSize)); + OnPropertyChanged(nameof(WindowRadius)); + }; + + this.InfoModel = new Info(); + this.client = client; + LoginViewModel loginViewModel = new LoginViewModel(this); + SelectedViewModel = loginViewModel; + this.client.SetLoginViewModel(loginViewModel); + + this.MinimizeCommand = new RelayCommand(() => this.mWindow.WindowState = WindowState.Minimized); + this.MaximizeCommand = new RelayCommand(() => this.mWindow.WindowState ^= WindowState.Maximized); + this.CloseCommand = new RelayCommand(() => this.mWindow.Close()); + this.MenuCommand = new RelayCommand(() => SystemCommands.ShowSystemMenu(this.mWindow, GetMousePosition())); + + var resizer = new WindowResizer(this.mWindow); + + this.mWindow.Closed += (sender, e) => this.client.Dispose(); + } + + + #region helper + + private Point GetMousePosition() + { + Debug.WriteLine("getmousePosition called"); + var p = Mouse.GetPosition(this.mWindow); + return new Point(p.X + this.mWindow.Left, p.Y + this.mWindow.Top); + } + + #endregion + } +} diff --git a/ClientApp/Views/LoginView.xaml b/ClientApp/Views/LoginView.xaml new file mode 100644 index 0000000..c00a1e3 --- /dev/null +++ b/ClientApp/Views/LoginView.xaml @@ -0,0 +1,25 @@ + + + + + + +