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