diff --git a/.gitignore b/.gitignore index d5ffced..e2adc78 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,9 @@ *__pycache__* *.pyc +*.vs + +csharp/*.sln +csharp/*.csproj +csharp/bin +csharp/obj \ No newline at end of file diff --git a/csharp/Client.cs b/csharp/Client.cs new file mode 100644 index 0000000..3327568 --- /dev/null +++ b/csharp/Client.cs @@ -0,0 +1,490 @@ +using Comm.communication; +using Comm.data; +using Comm.socket; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Comm { + public class Client : CommunicatorApplication { + public Client(string serverIP = "127.0.0.1", int port = 8348) { + this.serverIP = serverIP; + this.port = port; + this.serverPartner = null; + this.commQuit = false; + this.beActive = null; + this.getter = new CoordinateGetter(); + /* + this.controlRequestView = null; + this.controlSelectView = null; + this.robotView = null; + this.uploadView = null; + //*/ + } + + ~Client() { + if (this.serverPartner != null) { + this.serverPartner.cleanup(); + this.serverPartner = null; + } + } + + public override void main() { + int i = 0; + Console.WriteLine("Entering Client::main() loop!"); + while (this.continueCommunicationLoop(0) && i++ < 1) { + // try to connect to the server + try { + if (this.serverPartner != null) { + this.serverPartner.cleanup(); + } + this.serverPartner = new Communication(); + this.serverPartner.createSocket(SocketType.TCP, new SocketPartner(this.serverIP, this.port, false), 0, 2000, 50); + Console.WriteLine("Created server partner; before entering Client::process"); + this.process(); + } catch (Exception e) { + Console.WriteLine("Caught exception: " + e.ToString()); + } + this.serverPartner.cleanup(); + this.serverPartner = null; + if (this.quit) { + break; + } + Console.WriteLine("Waiting for two seconds..."); + Thread.Sleep(2000); // 2000ms = 2s + } + Console.WriteLine("Exiting Client::main() loop!"); + } + + public CoordinateGetter getCoordinateGetter() { + return this.getter; + } + + /* + public void setControlRequestView(ControlRequestView controlRequestView) { + this.controlRequestView = controlRequestView; + } + + public void setControlSelectView(ControlSelectView controlSelectView) { + this.controlSelectView = controlSelectView; + } + + public void setControlRobotView(ControlRobotView controlRobotView) { + this.robotView = controlRobotView; + } + + public void setControlUploadView(ControlUploadView controlUploadView) { + this.uploadView = controlUploadView; + } + //*/ + + public bool connect(ref string errorMessage) { + try { + if (this.serverPartner != null) { + this.serverPartner.cleanup(); + } + this.serverPartner = new Communication(); + Console.WriteLine("Connecting to " + this.serverIP + ":" + this.port); + this.serverPartner.createSocket(SocketType.TCP, new SocketPartner(this.serverIP, this.port, false), 0, 2000, 100); + return true; + } catch (Exception e) { + Console.WriteLine("Exception in Client.connect: " + e.ToString()); + errorMessage = e.Message; + } + return false; + } + + public bool startCommunication() { + bool success = false; + try { + this.process(); + success = true; + } catch (System.Net.Sockets.SocketException se) { + Console.WriteLine("Unhandled socket exception: " + se.ToString()); + } catch (Exception e) { + Console.WriteLine("Unhandled exception: " + e.ToString()); + } + /* + if (this.controlSelectView != null) { + this.controlSelectView.stopServerCommunication(); + } else if (this.controlRequestView != null) { + this.controlRequestView = null; + } + //*/ + this.serverPartner.cleanup(); + this.serverPartner = null; + return success; + } + + private bool continueCommunicationLoop(int level = 0) { + // Console.WriteLine("Quit: " + this.quit + "; commQuit: " + this.commQuit + "; beActive: " + ((this.beActive == null) ? "???" : this.beActive)); + return (level < 0 || + (!this.quit && + (level < 1 || + (!this.commQuit && + (level < 2 || + (this.beActive == null || Comm.utils.Utils.strcmp(this.beActive, Comm.data.Messages.STOP_MESSAGE) != 0)))))); + } + + private void sendToServerLoop(Communication partner, DataCollection dataCollection) { + bool sent = true; + int messageID = 0, verboseMod = 100; + + CoordinateData coordinate = (CoordinateData) dataCollection.get(MessageType.COORDINATE); + + this.getter.resetDrawingArea(); + while (this.continueCommunicationLoop(2) && this.getter.getData(coordinate)) { + coordinate.set("id", messageID); + if (messageID % verboseMod == 0) { + Console.WriteLine("Send coordinate data: " + coordinate.getID() + ", " + coordinate.getX() + ", " + coordinate.getY() + ", " + coordinate.getTime() + ", " + coordinate.getTouch()); + } + sent = this.send(partner, SocketType.TCP, coordinate); + if (!sent) { + Console.WriteLine("Error when sending coordinate data!"); + this.commQuit = true; + break; + } + Thread.Sleep(5); // sleep for 200Hz period + messageID++; + } + if (!sent) { + Console.WriteLine("Send problems"); + } else if (this.commQuit) { + Console.WriteLine("Server sent quit"); + } else if (!this.continueCommunicationLoop(2)) { + Console.WriteLine("Don't continue...: " + Comm.utils.Utils.strcmp(this.beActive, Comm.data.Messages.STOP_MESSAGE) + "; " + this.beActive); + } else { + Console.WriteLine("CoordinateGetter problems!!!"); + } + this.getter.resetDrawingArea(); + } + + /* + private static StreamImageSource GenerateImageSource(BitmapModel image) { + // Create MemoryStream from buffer with bitmap; Convert to StreamImageSource. + return (StreamImageSource) StreamImageSource.FromStream(() => { + return new MemoryStream(image.buffer); + }); + } + //*/ + + private void recvControlFromServerLoop(Communication partner, DataCollection dataCollection) { + MessageType messageType; + int messageID = 0, verboseMod = 10; // k + BitmapModel receivedImage; + + ImageData image = (ImageData) dataCollection.get(MessageType.IMAGE); + StatusData status = (StatusData) dataCollection.get(MessageType.STATUS); + + Console.WriteLine("Before communication loop in recvControlFromServerLoop!"); + Debug.Assert(this.beActive != null); + while (this.continueCommunicationLoop(2)) { + messageType = this.processIncomingMessage(partner, SocketType.TCP, ref dataCollection); + if (!this.continueCommunicationLoop(2)) { + Console.WriteLine("Don't continue communication loop...: " + Comm.utils.Utils.strcmp(this.beActive, Comm.data.Messages.STOP_MESSAGE) + "; " + this.beActive); + break; + } + if (messageType == MessageType.STATUS) { + Console.WriteLine("Received from server: \"" + status.getData() + "\"; beActive = " + this.beActive); + if (Comm.utils.Utils.strcmp(this.beActive, Comm.data.Messages.STOP_MESSAGE) == 0) { + Console.WriteLine("Stopping communicationLoop!"); + break; + } + } else if (messageType == MessageType.IMAGE) { + if (image.isImageDeserialized()) { + // Console.WriteLine("Image deserialized correctly!"); + receivedImage = image.getImage(); + // TODO: show image! + if (messageID % verboseMod == 0) { + Console.WriteLine("Image " + messageID + " is deserialized correctly!\n\t" + receivedImage.height + "x" + receivedImage.width + ": " + receivedImage.type + "; " + receivedImage.opencvBytes + "B"); + } + // Console.WriteLine("Previous height: " + this.robotView.recvImage.Height + "; width: " + this.robotView.recvImage.Width); + /* + this.robotView.recvImage.MinimumHeightRequest = this.robotView.recvImage.Height; + this.robotView.recvImage.MinimumWidthRequest = this.robotView.recvImage.Width; + this.robotView.recvImageStream.Value = GenerateImageSource(receivedImage); + //*/ + Console.WriteLine("Image " + messageID + " deserialized correctly!"); + } else { + Console.WriteLine("Error in deserializing image..."); + } + messageID++; + } + } + Console.WriteLine("After communication loop in recvControlFromServerLoop!"); + if (this.commQuit) { + Console.WriteLine("Server quit"); + } else if (this.beActive != null && Comm.utils.Utils.strcmp(this.beActive, Comm.data.Messages.STOP_MESSAGE) == 0) { + Console.WriteLine("Server stopped"); + } else { + Console.WriteLine("User quit!"); + } + } + + private void recvDataFromServerLoop(Communication partner, DataCollection dataCollection) { + int messageID = 0, verboseMod = 10; // k + MessageType messageType; + // string windowName = "Received image!"; + + ImageData image = (ImageData) dataCollection.get(MessageType.IMAGE); + StatusData status = (StatusData) dataCollection.get(MessageType.STATUS); + + Console.WriteLine("Before communication loop in recvDataFromServerLoop!"); + while (this.continueCommunicationLoop(2)) { + // messageType = this.processIncomingMessage(partner, SocketType.UDP, ref dataCollection); + messageType = this.processIncomingMessage(partner, SocketType.UDP_HEADER, ref dataCollection); + if (messageType != MessageType.NOTHING) { + Console.WriteLine("Received on UDP_HEADER message type: " + MessageTypeConverter.messageTypeToString(messageType)); + } + if (messageType == MessageType.IMAGE) { + if (image.isImageDeserialized()) { + // Console.WriteLine("Image deserialized correctly!"); + BitmapModel receivedImage = image.getImage(); + // TODO: show image! + if (messageID % verboseMod == 0) { + Console.WriteLine("Image " + messageID + " is deserialized correctly!\n\t" + receivedImage.height + "x" + receivedImage.width + ": " + receivedImage.type + "; " + receivedImage.opencvBytes + "B"); + } + /* + this.robotView.recvImageStream.Value = GenerateImageSource(receivedImage); + //*/ + } else { + Console.WriteLine("Error in deserializing image..."); + } + messageID++; + } + } + Console.WriteLine("After communication loop in recvDataFromServerLoop!"); + if (this.quit) { + Console.WriteLine("User quit"); + status.setCommand("quit"); + this.send(this.serverPartner, SocketType.TCP, status); + } else if (this.commQuit) { + Console.WriteLine("Server quit"); + } else if (this.beActive != null && Comm.utils.Utils.strcmp(this.beActive, Comm.data.Messages.STOP_MESSAGE) == 0) { + Console.WriteLine("Server stopped"); + } else { + Console.WriteLine("CoordinateGetter problems!!!"); + } + } + + private void communicationLoop() { + Console.WriteLine("Entering communication loop!"); + + StatusData s = new StatusData(); + s.setCommand("control"); + // s.setCommand("stop"); + if (!this.send(this.serverPartner, SocketType.UDP_HEADER, s)) { + Console.WriteLine("Can not send ready to udp: " + this.serverPartner.getPartnerString(SocketType.UDP_HEADER) + ". error = " + this.serverPartner.getErrorString()); + this.commQuit = true; + return; + } + + Debug.Assert(this.quit || this.continueCommunicationLoop(2)); + + Communication controlServerPartner = this.serverPartner.copy(); + Communication dataServerPartner = this.serverPartner.copy(); + DataCollection dataCollectionCopy = new DataCollection(); + + // Task dataThread = Task.Run(() => { this.recvDataFromServerLoop(dataServerPartner, dataCollectionCopy); }); + Task controlThread = Task.Run(() => { this.recvControlFromServerLoop(controlServerPartner, dataCollectionCopy); }); + + this.sendToServerLoop(this.serverPartner, this.dataCollection); + Debug.Assert(!this.continueCommunicationLoop(2)); + + // dataThread.Wait(); + controlThread.Wait(); + + Debug.Assert(!this.continueCommunicationLoop(2)); + controlServerPartner.cleanup(); + dataServerPartner.cleanup(); + Console.WriteLine("Exiting communication loop!"); + } + + private void uploadLoop() { + ; + } + + private void process() { + StatusData status = (StatusData) this.dataCollection.get(MessageType.STATUS); + this.state = CommunicatorState.COMMUNICATOR_IDLE; + string statusResult; + + this.commQuit = false; + while (this.continueCommunicationLoop(1)) { + // loop for ping messages + while (this.continueCommunicationLoop(1)) { + this.processIncomingMessage(this.serverPartner, SocketType.TCP, ref this.dataCollection); + statusResult = status.getData(); + if (statusResult != null) { + Console.WriteLine("Received from server: " + statusResult); + if (Comm.utils.Utils.strcmp(statusResult, Comm.data.Messages.START_MESSAGE) == 0) { + break; + } + } + } + + /* + // switch to control select view + Console.WriteLine("Starting control select view!"); + try { + this.controlRequestView.startServerCommunication(); + } catch (Exception e) { + Console.WriteLine("Caught exception when changing to new page: " + e.ToString()); + this.controlRequestView.debugOutput.Value = e.Message; + this.quit = true; + break; + } + while (this.controlSelectView == null) { } + //*/ + this.state = CommunicatorState.COMMUNICATOR_ACTIVE; + + // recv the udp port + StatusData s = new StatusData(); + if (!this.listenFor(this.serverPartner, SocketType.TCP, s, null, -1, -1, 10)) { + this.commQuit = true; + return; + } + + MessageType messageType = MessageType.NOTHING; + while (this.continueCommunicationLoop(1)) { + if (!this.listen(this.serverPartner, SocketType.TCP, ref messageType, ref this.dataCollection, 10)) { + Console.WriteLine("Can not receive data from " + this.serverPartner.getPartnerString(SocketType.TCP) + + "; error " + this.serverPartner.getErrorString()); + Console.WriteLine("Can not receive the expected udp port from " + this.serverPartner.getPartnerString(SocketType.TCP) + + ". error = " + this.serverPartner.getErrorString()); + this.commQuit = true; + return; + } + if (messageType == MessageType.NOTHING) { + continue; + } else if (messageType != MessageType.STATUS) { + Console.WriteLine("Error in communication protocol with " + this.serverPartner.getPartnerString(SocketType.TCP) + + ". Expected status data containing udp port not " + MessageTypeConverter.messageTypeToString(messageType)); + this.commQuit = true; + return; + } + break; + } + if (!this.continueCommunicationLoop(1)) { + Console.WriteLine("Stopped because don't continue communication loop!"); + break; + } + + int serverUDPPort; + try { + serverUDPPort = Int32.Parse(s.getData()); + } catch (Exception e) { + Console.WriteLine("Caught exception when trying to convert the received string udp port to int: " + e.ToString()); + this.commQuit = true; + return; + } + Console.WriteLine("Received the udp port to communicate on: " + serverUDPPort); + this.serverPartner.createSocket(SocketType.UDP_HEADER, new SocketPartner(this.serverIP, serverUDPPort, false), 0, 2000, 50); + + // second loop for ping messages until control mode has been selected + string controlMode = ""; + while (this.continueCommunicationLoop(1)) { + this.processIncomingMessage(this.serverPartner, SocketType.TCP, ref this.dataCollection); + statusResult = status.getData(); + if (statusResult != null) { + Console.WriteLine("Received from server: " + statusResult); + if (Comm.utils.Utils.strcmp(statusResult, Comm.data.Messages.STOP_MESSAGE) == 0) { + break; + } + } + controlMode = "control"; + break; + /* + if (this.robotView != null) { + controlMode = "control"; + break; + } + if (this.uploadView != null) { + controlMode = "upload"; + break; + } + //*/ + } + if (controlMode == "") { + continue; + } else if (controlMode == "control") { + this.communicationLoop(); + } else if (controlMode == "upload") { + this.uploadLoop(); + } + + this.state = CommunicatorState.COMMUNICATOR_IDLE; + } + + /* + if (this.continueCommunicationLoop(0)) { + try { + this.controlSelectView.stopServerCommunication(); + } catch (Exception e) { + Console.WriteLine("Caught exception when changing to new page: " + e.ToString()); + // this.controlRequestView.debugOutput.Value = e.Message; + Console.WriteLine("STOPPED SERVER COMMUNICATION!"); + } + } + //*/ + this.state = CommunicatorState.COMMUNICATOR_DONE; + } + + private MessageType processIncomingMessage(Communication partner, SocketType type, ref DataCollection dataCollection) { + MessageType messageType = MessageType.NOTHING; + // cout << "Listening for data..." << endl; + StatusData status = (StatusData) dataCollection.get(MessageType.STATUS); + if (!this.listen(partner, type, ref messageType, ref dataCollection)) { + status.reset(); + return messageType; + } + + if (messageType != MessageType.NOTHING && messageType != MessageType.STATUS && messageType != MessageType.IMAGE) { + throw new Exception("Can only process nothing, status or image data.Messages..."); + } + if (messageType == MessageType.NOTHING || messageType == MessageType.IMAGE) { + status.reset(); + return messageType; + } + + Console.WriteLine("Received from server: " + status.getData() + " " + MessageTypeConverter.messageTypeToString(messageType)); + + string listenResult = status.getData(); + if (Comm.utils.Utils.strcmp(listenResult, Comm.data.Messages.PING_MESSAGE) == 0) { + status.setStatus(CommunicatorStateConverter.convertCommunicatorStateToStatus(this.state)); + Console.WriteLine("Sending to server " + status.getData()); + if (!this.send(partner, SocketType.TCP, status)) { + Console.WriteLine("Can not send ping response to " + partner.getPartnerString(SocketType.TCP)); + } else { + Console.WriteLine("Sent!"); + } + } else if (Comm.utils.Utils.strcmp(listenResult, Comm.data.Messages.QUIT_MESSAGE) == 0) { + this.commQuit = true; + } else if (Comm.utils.Utils.strcmp(listenResult, Comm.data.Messages.START_MESSAGE) == 0 || Comm.utils.Utils.strcmp(listenResult, Comm.data.Messages.STOP_MESSAGE) == 0) { + this.beActive = listenResult; + return messageType; + } else if (Comm.utils.Utils.strcmp(listenResult, Comm.data.Messages.WAIT_MESSAGE) == 0) { + } else { + Console.WriteLine("Unknown status command " + listenResult); + this.commQuit = true; + } + status.reset(); + return MessageType.NOTHING; + } + + private string serverIP; + private string beActive; + private int port; + private bool commQuit; + private CoordinateGetter getter; + private Communication serverPartner; + /* + private ControlRequestView controlRequestView; + private ControlSelectView controlSelectView; + private ControlRobotView robotView; + private ControlUploadView uploadView; + //*/ + }; +} diff --git a/csharp/Comm/communication/Communicator.cs b/csharp/Comm/communication/Communicator.cs index 88d1097..d20432f 100644 --- a/csharp/Comm/communication/Communicator.cs +++ b/csharp/Comm/communication/Communicator.cs @@ -1,6 +1,6 @@ using Comm.data; using Comm.socket; -using DesignAIRobotics.Utils; +using Comm.utils; using System; using System.Diagnostics; diff --git a/csharp/Comm/socket/Socket.cs b/csharp/Comm/socket/Socket.cs index 830feac..2a0d462 100644 --- a/csharp/Comm/socket/Socket.cs +++ b/csharp/Comm/socket/Socket.cs @@ -1,5 +1,5 @@ using Comm.utils; -using DesignAIRobotics.Utils; +// using DesignAIRobotics.Utils; using System; using System.Diagnostics; using System.Net; diff --git a/csharp/Comm/utils/DebugUtils.cs b/csharp/Comm/utils/DebugUtils.cs new file mode 100644 index 0000000..c79502a --- /dev/null +++ b/csharp/Comm/utils/DebugUtils.cs @@ -0,0 +1,28 @@ +using System; + +namespace Comm.utils { + public class DebugUtils { + private static DebugUtils defaultDebugUtilsImplementation = new DebugUtils(); + private static DebugUtils debugUtilsImplementation; + + public virtual void SetOutputImpl(string message) { + Console.WriteLine(message); + } + + public static void SetDebugImplementation(DebugUtils debugUtilsImplementation) { + DebugUtils.debugUtilsImplementation = debugUtilsImplementation; + } + + public static void ResetDebugImplementation() { + DebugUtils.debugUtilsImplementation = null; + } + + public static void SetOutput(string message) { + if (DebugUtils.debugUtilsImplementation == null) { + DebugUtils.defaultDebugUtilsImplementation.SetOutputImpl(message); + } else { + DebugUtils.debugUtilsImplementation.SetOutputImpl(message); + } + } + } +} diff --git a/csharp/Comm/utils/Reference.cs b/csharp/Comm/utils/Reference.cs new file mode 100644 index 0000000..7172808 --- /dev/null +++ b/csharp/Comm/utils/Reference.cs @@ -0,0 +1,13 @@ +namespace Comm.utils { + public class Reference { + private T v; + public T Value { + get => this.v; + set { + this.v = value; + } + } + + public Reference() { } + } +} diff --git a/csharp/CommunicatorApplication.cs b/csharp/CommunicatorApplication.cs new file mode 100644 index 0000000..c788ef9 --- /dev/null +++ b/csharp/CommunicatorApplication.cs @@ -0,0 +1,65 @@ +using Comm.communication; +using Comm.data; +using Comm.socket; +using Comm.utils; +using System; + +namespace Comm { + public class CommunicatorApplication { + public CommunicatorApplication() { + this.quit = false; + this.state = CommunicatorState.COMMUNICATOR_IDLE; + this.dataCollection = new DataCollection(); + } + + ~CommunicatorApplication() { } + + public virtual void main() { + while (!this.quit) { + this._preMain(); + try { + this._main(); + } catch (Exception e) { + Console.WriteLine("Exception caught: " + e.ToString()); + } + this._postMain(); + } + } + + public virtual void stop() { + this.quit = true; + } + + protected bool send(Communication comm, SocketType socketType, CommunicationData data, int retries = 0, bool verbose = false) { + return Communicator.send(comm, socketType, data, false, true, retries, verbose); + } + + protected bool send(Communication comm, SocketType socketType, byte[] data, int dataSize, int retries = 0, bool verbose = false) { + return Communicator.send(comm, socketType, data, dataSize, retries, verbose); + } + + protected bool syphon(Communication comm, SocketType socketType, ref MessageType messageType, CommunicationData data, int retries = 0, bool verbose = false, int syphonRetries = 10) { + return Communicator.syphon(comm, socketType, ref messageType, data, ref this.quit, retries, verbose); + } + + protected bool listen(Communication comm, SocketType socketType, ref MessageType messageType, ref DataCollection _dataCollection, int retries = 0, bool verbose = false) { + return Communicator.listen(comm, socketType, ref messageType, ref _dataCollection, ref this.quit, retries, verbose); + } + + protected bool listenFor(Communication comm, SocketType socketType, CommunicationData data, + Reference timeoutResult = null, int countIgnoreOther = -1, + int countOther = -1, int retries = 0, bool verbose = false) { + return Communicator.listenFor(comm, socketType, data, ref this.quit, timeoutResult, countIgnoreOther, countOther, retries, verbose); + } + + protected virtual void _preMain() { } + + protected virtual void _main() { } + + protected virtual void _postMain() { } + + protected bool quit; + protected CommunicatorState state; + protected DataCollection dataCollection; + }; +} diff --git a/csharp/CoordinateGetter.cs b/csharp/CoordinateGetter.cs new file mode 100644 index 0000000..72aef6e --- /dev/null +++ b/csharp/CoordinateGetter.cs @@ -0,0 +1,43 @@ +using Comm.data; +using System; + +namespace Comm { + public class CoordinateGetter { + public CoordinateGetter() { + this.x = 0.5; + this.y = 0.5; + this.touch = true; + this.buttonFwd = false; + this.buttonDwn = false; + this.startTime = DateTime.Now; + } + + ~CoordinateGetter() { } + + public void resetDrawingArea(bool withTimeReset = true) { + if (withTimeReset) { + this.startTime = DateTime.Now; + } + } + + public void setData(float x, float y, bool touch) { + this.x = x; + this.y = y; + this.touch = touch; + } + + public bool getData(CoordinateData data) { + data.set("touch", this.touch); + data.set("buttonFwd", this.buttonFwd); + data.set("buttonDwn", this.buttonDwn); + data.set("x", x); + data.set("y", y); + data.set("time", (long)(DateTime.Now - this.startTime).Milliseconds); + return true; + } + + double x, y; + bool buttonFwd, buttonDwn, touch; + DateTime startTime; + }; +} diff --git a/csharp/Program.cs b/csharp/Program.cs index 5a5be46..48d0a15 100644 --- a/csharp/Program.cs +++ b/csharp/Program.cs @@ -1,7 +1,6 @@ using Comm.communication; using Comm.data; using Comm.socket; -using DesignAIRobotics.Client; using System; using System.Diagnostics; using System.Net; @@ -671,7 +670,7 @@ public static void test() { } public static class Tester { - static void test(string[] args) { + static void Main(string[] args) { Console.WriteLine("Hello World!"); TestClient.test();