From 7e9c9c705e38c54a848d8cfa00a708e535cab088 Mon Sep 17 00:00:00 2001 From: Kenneth van Ewijk Date: Sun, 1 Nov 2015 12:33:34 +0100 Subject: [PATCH] init --- .gitattributes | 63 +++ ErgometerIPR/ErgometerApplication/App.config | 6 + .../ErgometerApplication/ChartPanel.cs | 105 +++++ .../ClientApplicatie.Designer.cs | 187 ++++++++ .../ErgometerApplication/ClientApplicatie.cs | 150 +++++++ .../ClientApplicatie.resx | 126 ++++++ .../ErgometerApplication.csproj | 127 ++++++ .../ErgometerApplication/MainClient.cs | 284 ++++++++++++ .../ErgometerApplication/PanelClientChat.cs | 202 +++++++++ .../PanelClientContainer.cs | 35 ++ .../ErgometerApplication/PanelClientData.cs | 115 +++++ .../PanelClientDataView.cs | 71 +++ .../ErgometerApplication/PanelLogin.cs | 158 +++++++ ErgometerIPR/ErgometerApplication/Program.cs | 23 + .../Properties/AssemblyInfo.cs | 36 ++ .../Properties/Resources.Designer.cs | 73 +++ .../Properties/Resources.resx | 124 +++++ .../Properties/Settings.Designer.cs | 30 ++ .../Properties/Settings.settings | 7 + .../Resources/flatbike.png | Bin 0 -> 56275 bytes .../ErgometerApplication/packages.config | 4 + .../ErgometerDoctorApplication/App.config | 6 + .../Client/ChartPanel.cs | 105 +++++ .../Client/ClientThread.cs | 78 ++++ .../Client/PanelClientChat.cs | 206 +++++++++ .../Client/PanelClientData.cs | 115 +++++ .../Client/PanelClientSetData.cs | 287 ++++++++++++ .../Client/PanelGraphView.cs | 71 +++ .../Client/SessionWindow.Designer.cs | 175 ++++++++ .../Client/SessionWindow.cs | 103 +++++ .../Client/SessionWindow.resx | 120 +++++ .../ConActiveSessions.cs | 84 ++++ .../ConClientBroadcast.cs | 72 +++ .../ConClientData.cs | 150 +++++++ .../ConPanelLogin.cs | 116 +++++ .../ConSessionHistory.cs | 65 +++ .../ErgometerDoctorApplication.csproj | 144 ++++++ .../ErgometerDoctorApplication/MainClient.cs | 276 ++++++++++++ .../MainWindow.Designer.cs | 320 +++++++++++++ .../ErgometerDoctorApplication/MainWindow.cs | 147 ++++++ .../MainWindow.resx | 126 ++++++ .../ErgometerDoctorApplication/Program.cs | 22 + .../Properties/AssemblyInfo.cs | 36 ++ .../Properties/Resources.Designer.cs | 73 +++ .../Properties/Resources.resx | 124 +++++ .../Properties/Settings.Designer.cs | 30 ++ .../Properties/Settings.settings | 7 + .../Resources/doctorImage.png | Bin 0 -> 12999 bytes .../SessionPanel.cs | 91 ++++ ErgometerIPR/ErgometerIPR.sln | 22 + ErgometerIPR/ErgometerLibrary/AESEncrypt.cs | 162 +++++++ .../ErgometerLibrary/Chat/ChatItem.cs | 63 +++ ErgometerIPR/ErgometerLibrary/ChatMessage.cs | 23 + ErgometerIPR/ErgometerLibrary/ComPort.cs | 77 ++++ .../ErgometerLibrary/ErgometerLibrary.csproj | 72 +++ ErgometerIPR/ErgometerLibrary/Helper.cs | 32 ++ ErgometerIPR/ErgometerLibrary/Meting.cs | 103 +++++ ErgometerIPR/ErgometerLibrary/NetCommand.cs | 424 ++++++++++++++++++ ErgometerIPR/ErgometerLibrary/NetHelper.cs | 135 ++++++ .../Properties/AssemblyInfo.cs | 36 ++ ErgometerIPR/ErgometerLibrary/packages.config | 4 + ErgometerIPR/ErgometerServer/App.config | 6 + ErgometerIPR/ErgometerServer/ClientThread.cs | 137 ++++++ ErgometerIPR/ErgometerServer/DoctorThread.cs | 121 +++++ .../ErgometerServer/ErgometerServer.csproj | 72 +++ ErgometerIPR/ErgometerServer/FileHandler.cs | 204 +++++++++ .../Properties/AssemblyInfo.cs | 36 ++ ErgometerIPR/ErgometerServer/Server.cs | 167 +++++++ ErgometerIPR/ErgometerServer/packages.config | 4 + 69 files changed, 6975 insertions(+) create mode 100644 .gitattributes create mode 100644 ErgometerIPR/ErgometerApplication/App.config create mode 100644 ErgometerIPR/ErgometerApplication/ChartPanel.cs create mode 100644 ErgometerIPR/ErgometerApplication/ClientApplicatie.Designer.cs create mode 100644 ErgometerIPR/ErgometerApplication/ClientApplicatie.cs create mode 100644 ErgometerIPR/ErgometerApplication/ClientApplicatie.resx create mode 100644 ErgometerIPR/ErgometerApplication/ErgometerApplication.csproj create mode 100644 ErgometerIPR/ErgometerApplication/MainClient.cs create mode 100644 ErgometerIPR/ErgometerApplication/PanelClientChat.cs create mode 100644 ErgometerIPR/ErgometerApplication/PanelClientContainer.cs create mode 100644 ErgometerIPR/ErgometerApplication/PanelClientData.cs create mode 100644 ErgometerIPR/ErgometerApplication/PanelClientDataView.cs create mode 100644 ErgometerIPR/ErgometerApplication/PanelLogin.cs create mode 100644 ErgometerIPR/ErgometerApplication/Program.cs create mode 100644 ErgometerIPR/ErgometerApplication/Properties/AssemblyInfo.cs create mode 100644 ErgometerIPR/ErgometerApplication/Properties/Resources.Designer.cs create mode 100644 ErgometerIPR/ErgometerApplication/Properties/Resources.resx create mode 100644 ErgometerIPR/ErgometerApplication/Properties/Settings.Designer.cs create mode 100644 ErgometerIPR/ErgometerApplication/Properties/Settings.settings create mode 100644 ErgometerIPR/ErgometerApplication/Resources/flatbike.png create mode 100644 ErgometerIPR/ErgometerApplication/packages.config create mode 100644 ErgometerIPR/ErgometerDoctorApplication/App.config create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Client/ChartPanel.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Client/ClientThread.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientChat.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientData.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientSetData.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Client/PanelGraphView.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.Designer.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.resx create mode 100644 ErgometerIPR/ErgometerDoctorApplication/ConActiveSessions.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/ConClientBroadcast.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/ConClientData.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/ConPanelLogin.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/ConSessionHistory.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/ErgometerDoctorApplication.csproj create mode 100644 ErgometerIPR/ErgometerDoctorApplication/MainClient.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/MainWindow.Designer.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/MainWindow.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/MainWindow.resx create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Program.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Properties/AssemblyInfo.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Properties/Resources.Designer.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Properties/Resources.resx create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Properties/Settings.Designer.cs create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Properties/Settings.settings create mode 100644 ErgometerIPR/ErgometerDoctorApplication/Resources/doctorImage.png create mode 100644 ErgometerIPR/ErgometerDoctorApplication/SessionPanel.cs create mode 100644 ErgometerIPR/ErgometerIPR.sln create mode 100644 ErgometerIPR/ErgometerLibrary/AESEncrypt.cs create mode 100644 ErgometerIPR/ErgometerLibrary/Chat/ChatItem.cs create mode 100644 ErgometerIPR/ErgometerLibrary/ChatMessage.cs create mode 100644 ErgometerIPR/ErgometerLibrary/ComPort.cs create mode 100644 ErgometerIPR/ErgometerLibrary/ErgometerLibrary.csproj create mode 100644 ErgometerIPR/ErgometerLibrary/Helper.cs create mode 100644 ErgometerIPR/ErgometerLibrary/Meting.cs create mode 100644 ErgometerIPR/ErgometerLibrary/NetCommand.cs create mode 100644 ErgometerIPR/ErgometerLibrary/NetHelper.cs create mode 100644 ErgometerIPR/ErgometerLibrary/Properties/AssemblyInfo.cs create mode 100644 ErgometerIPR/ErgometerLibrary/packages.config create mode 100644 ErgometerIPR/ErgometerServer/App.config create mode 100644 ErgometerIPR/ErgometerServer/ClientThread.cs create mode 100644 ErgometerIPR/ErgometerServer/DoctorThread.cs create mode 100644 ErgometerIPR/ErgometerServer/ErgometerServer.csproj create mode 100644 ErgometerIPR/ErgometerServer/FileHandler.cs create mode 100644 ErgometerIPR/ErgometerServer/Properties/AssemblyInfo.cs create mode 100644 ErgometerIPR/ErgometerServer/Server.cs create mode 100644 ErgometerIPR/ErgometerServer/packages.config diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/ErgometerIPR/ErgometerApplication/App.config b/ErgometerIPR/ErgometerApplication/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerApplication/ChartPanel.cs b/ErgometerIPR/ErgometerApplication/ChartPanel.cs new file mode 100644 index 0000000..a9b459a --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/ChartPanel.cs @@ -0,0 +1,105 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.DataVisualization.Charting; + +namespace ErgometerApplication +{ + public class ChartPanel : Panel + { + private Chart chart; + private MetingType type; + private ChartArea chartArea; + private Series series; + private SeriesChartType ChartType; + + public ChartPanel(MetingType type, SeriesChartType charttype) : base() + { + this.type = type; + this.ChartType = charttype; + + this.chart = new Chart(); + this.chartArea = new ChartArea(); + this.chart.Titles.Add(new Title(type.ToString())); + + this.Location = new System.Drawing.Point(0, 0); + + this.Size = new System.Drawing.Size(400, 250); + this.Controls.Add(chart); + + this.series = createSerie(); + this.chartArea.Name = "chartArea"; + + this.chart.Size = new System.Drawing.Size(400, 250); + + this.chart.Dock = DockStyle.Fill; + this.chart.Series.Add(series); + this.chart.Text = "chart"; + + + this.chart.ChartAreas.Add(chartArea); + + } + + public void updateChart(List metingen) + { + chart.Series[0] = createSerie(); + for (int i = 0; i < metingen.Count; i++) + { + chart.Series[0].Points.Add(getMetingType(metingen[i])); + } + chart.Update(); + } + + public Series createSerie() + { + Series serie = new Series(); + serie.Name = "series"; + serie.ChartType = ChartType; + serie.ChartArea = "chartArea"; + serie.BorderWidth = 3; + return serie; + } + + public int getMetingType(Meting meting) + { + switch (type) + { + case MetingType.HEARTBEAT: + return meting.HeartBeat; + case MetingType.RPM: + return meting.RPM; + case MetingType.SPEED: + return (int)meting.Speed; + case MetingType.DISTANCE: + return (int)meting.Distance; + case MetingType.POWER: + return meting.Power; + case MetingType.ENERGY: + return meting.Energy; + case MetingType.SECONDS: + return meting.Seconds; + case MetingType.ACTUALPOWER: + return meting.ActualPower; + default: + return 0; + } + } + + public enum MetingType + { + HEARTBEAT, + RPM, + SPEED, + DISTANCE, + POWER, + ENERGY, + SECONDS, + ACTUALPOWER + } + } +} diff --git a/ErgometerIPR/ErgometerApplication/ClientApplicatie.Designer.cs b/ErgometerIPR/ErgometerApplication/ClientApplicatie.Designer.cs new file mode 100644 index 0000000..9b19b4f --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/ClientApplicatie.Designer.cs @@ -0,0 +1,187 @@ +using System.IO.Ports; +using System.Windows.Forms; + +namespace ErgometerApplication +{ + partial class ClientApplicatie + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.updateTimer = new System.Windows.Forms.Timer(this.components); + this.panelClientContainer = new System.Windows.Forms.Panel(); + this.panelDataViewLeft = new System.Windows.Forms.Panel(); + this.panelGraphView = new ErgometerApplication.PanelGraphView(); + this.panelClientChat = new ErgometerApplication.PanelClientChat(); + this.panelLogin = new ErgometerApplication.PanelLogin(this); + this.panelTopBar = new System.Windows.Forms.Panel(); + this.buttonLogOff = new System.Windows.Forms.Button(); + this.labelUsername = new System.Windows.Forms.Label(); + this.labelHallo = new System.Windows.Forms.Label(); + + this.heartBeat = new PanelClientData("Hartslag", 50, 220); + this.RPM = new PanelClientData("RPM", 0, 120); + this.speed = new PanelClientData("Snelheid", 0, 50); + this.distance = new PanelClientData("Afstand (km)", 0, 100); + this.power = new PanelClientData("Weerstand", 25, 400); + this.energy = new PanelClientData("Energie", 0, 200); + this.actualpower = new PanelClientData("Absolute Weerstand", 0, 400); + this.time = new PanelClientData("Tijd", 0, 400); + + this.panelClientContainer.SuspendLayout(); + this.panelTopBar.SuspendLayout(); + this.SuspendLayout(); + // + // updateTimer + // + this.updateTimer.Interval = 600; + this.updateTimer.Tick += new System.EventHandler(this.updateTimer_Tick); + // + // panelClientContainer + // + this.panelClientContainer.Controls.Add(this.panelGraphView); + this.panelClientContainer.Controls.Add(this.panelDataViewLeft); + + this.panelClientContainer.Controls.Add(this.panelClientChat); + this.panelClientContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelClientContainer.Location = new System.Drawing.Point(0, 0); + this.panelClientContainer.Name = "panelClientContainer"; + this.panelClientContainer.Size = new System.Drawing.Size(800, 600); + this.panelClientContainer.TabIndex = 0; + // + // panelDataViewLeft + // + this.panelDataViewLeft.Dock = System.Windows.Forms.DockStyle.Left; + this.panelDataViewLeft.Location = new System.Drawing.Point(0, 0); + this.panelDataViewLeft.Name = "panelDataViewLeft"; + this.panelDataViewLeft.Size = new System.Drawing.Size(18, 600); + this.panelDataViewLeft.TabIndex = 3; + this.panelDataViewLeft.BackColor = System.Drawing.Color.Gray; + this.panelDataViewLeft.Controls.Add(heartBeat); + this.panelDataViewLeft.Controls.Add(RPM); + this.panelDataViewLeft.Controls.Add(speed); + this.panelDataViewLeft.Controls.Add(distance); + this.panelDataViewLeft.Controls.Add(power); + this.panelDataViewLeft.Controls.Add(energy); + this.panelDataViewLeft.Controls.Add(actualpower); + this.panelDataViewLeft.Controls.Add(time); + + // + // panelClientChat + // + this.panelClientChat.BackColor = System.Drawing.SystemColors.ButtonHighlight; + this.panelClientChat.Dock = System.Windows.Forms.DockStyle.Right; + this.panelClientChat.Location = new System.Drawing.Point(400, 0); + this.panelClientChat.Name = "panelClientChat"; + this.panelClientChat.Size = new System.Drawing.Size(400, 600); + this.panelClientChat.TabIndex = 2; + // + // panelTopBar + // + this.panelTopBar.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.panelTopBar.Controls.Add(this.buttonLogOff); + this.panelTopBar.Controls.Add(this.labelUsername); + this.panelTopBar.Controls.Add(this.labelHallo); + this.panelTopBar.Dock = System.Windows.Forms.DockStyle.Top; + this.panelTopBar.Location = new System.Drawing.Point(0, 0); + this.panelTopBar.Name = "panelTopBar"; + this.panelTopBar.Size = new System.Drawing.Size(800, 30); + this.panelTopBar.TabIndex = 1; + this.panelTopBar.Visible = false; + // + // buttonLogOff + // + this.buttonLogOff.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.buttonLogOff.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); + this.buttonLogOff.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonLogOff.Font = new System.Drawing.Font("Segoe UI Light", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.buttonLogOff.Location = new System.Drawing.Point(722, 3); + this.buttonLogOff.Name = "buttonLogOff"; + this.buttonLogOff.Size = new System.Drawing.Size(75, 23); + this.buttonLogOff.TabIndex = 1; + this.buttonLogOff.Text = "Afmelden"; + this.buttonLogOff.UseVisualStyleBackColor = false; + this.buttonLogOff.Click += new System.EventHandler(this.buttonLogOff_Click); + // + // labelUsername + // + this.labelUsername.AutoSize = true; + this.labelUsername.Font = new System.Drawing.Font("Segoe UI Semilight", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelUsername.ForeColor = System.Drawing.Color.White; + this.labelUsername.Location = new System.Drawing.Point(49, 3); + this.labelUsername.Name = "labelUsername"; + this.labelUsername.Size = new System.Drawing.Size(144, 21); + this.labelUsername.TabIndex = 0; + this.labelUsername.Text = ""; + // + // labelHallo + // + this.labelHallo.AutoSize = true; + this.labelHallo.Font = new System.Drawing.Font("Segoe UI Semilight", 12F); + this.labelHallo.ForeColor = System.Drawing.Color.White; + this.labelHallo.Location = new System.Drawing.Point(3, 3); + this.labelHallo.Name = "labelHallo"; + this.labelHallo.Size = new System.Drawing.Size(54, 21); + this.labelHallo.TabIndex = 0; + this.labelHallo.Text = "Hallo, "; + // + // ClientApplicatie + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(700, 500); + this.MinimumSize = new System.Drawing.Size(550, 550); + this.Controls.Add(this.panelTopBar); + this.Controls.Add(this.panelLogin); + this.Controls.Add(this.panelClientContainer); + this.Name = "ClientApplicatie"; + this.Text = "Client Applicatie"; + this.WindowState = System.Windows.Forms.FormWindowState.Maximized; + this.Resize += new System.EventHandler(this.ClientApplicatie_Resize); + this.panelClientContainer.ResumeLayout(false); + this.panelTopBar.ResumeLayout(false); + this.panelTopBar.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.Timer updateTimer; + private System.Windows.Forms.Panel panelClientContainer; + private PanelClientChat panelClientChat; + private PanelGraphView panelGraphView; + private PanelLogin panelLogin; + private System.Windows.Forms.Panel panelDataViewLeft; + private System.Windows.Forms.Panel panelTopBar; + private System.Windows.Forms.Button buttonLogOff; + private System.Windows.Forms.Label labelUsername; + private System.Windows.Forms.Label labelHallo; + public PanelClientData heartBeat, RPM, speed, distance, power, energy, seconds, actualpower, time; + } + + +} diff --git a/ErgometerIPR/ErgometerApplication/ClientApplicatie.cs b/ErgometerIPR/ErgometerApplication/ClientApplicatie.cs new file mode 100644 index 0000000..96b84d5 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/ClientApplicatie.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Newtonsoft.Json; +using System.IO; +using System.Net.Sockets; +using System.Net; +using System.Timers; +using ErgometerLibrary; + +namespace ErgometerApplication +{ + public partial class ClientApplicatie : Form + { + public PanelClientChat chat; + private int count; + + public ClientApplicatie() + { + InitializeComponent(); + MainClient.Init(this); + count = 0; + } + + private void updateTimer_Tick(object sender, EventArgs e) + { + if(MainClient.Doctor.Connected) + { + MainClient.ComPort.Write("ST"); + string response = MainClient.ComPort.Read(); + if (response != "err") + { + Meting m = MainClient.SaveMeting(response); + + heartBeat.updateValue(m.HeartBeat); + RPM.updateValue(m.RPM); + speed.updateValue(m.Speed); + distance.updateValue(m.Distance); + power.updateValue(m.Power); + energy.updateValue(m.Energy); + actualpower.updateValue(m.ActualPower); + time.updateValue(m.Seconds); + + if (count >= 10) + { + count = 0; + panelGraphView.updateAllCharts(MainClient.Metingen); + } + + count++; + } + else + { + logout("De Ergometer is niet meer verbonden.", Color.Red); + } + } + } + + public void validateLogin(string username, string password) + { + if (username.Length > 0) + { + if (password.Length == 0) + { + panelLogin.lblVerification.Text = "Vul een wachtwoord in."; + panelLogin.lblVerification.ForeColor = Color.Red; + panelLogin.lblVerification.Visible = true; + } + if (password.Length > 0) + { + string error = ""; + bool connect = MainClient.Connect(SerialPort.GetPortNames()[0], username, password, out error); + + if (connect) + { + panelClientContainer.BringToFront(); + chat = panelClientChat; + this.labelUsername.Text = panelLogin.textBoxUsername.Text; + panelTopBar.Visible = true; + updateTimer.Start(); + } + else + { + panelLogin.lblVerification.Text = error; + panelLogin.lblVerification.ForeColor = Color.Red; + panelLogin.lblVerification.Visible = true; + } + + } + } + else + { + panelLogin.lblVerification.Text = "Vul een gebruikersnaam in."; + panelLogin.lblVerification.ForeColor = Color.Red; + panelLogin.lblVerification.Visible = true; + } + } + + private void ClientApplicatie_Resize(object sender, System.EventArgs e) + { + Control control = (Control)sender; + if (control.Size.Width < 980) + { + panelGraphView.Visible = false; + panelClientChat.Width = 400; + panelDataViewLeft.Dock = DockStyle.Fill; + } + if (control.Size.Width >= 980 && control.Size.Width < 1368) + { + panelGraphView.Visible = true; + panelDataViewLeft.Width = 250; + panelClientChat.Width = 400; + panelDataViewLeft.Dock = DockStyle.Left; + } + if (control.Size.Width >= 1368) + { + panelGraphView.Visible = true; + panelDataViewLeft.Width = 400; + panelDataViewLeft.Dock = DockStyle.Left; + } + + } + + private void buttonLogOff_Click(object sender, EventArgs e) + { + logout("U bent uitgelogd.", Color.Blue); + } + + private void logout(string message, System.Drawing.Color cl) + { + panelLogin.BringToFront(); + panelTopBar.Visible = false; + panelLogin.lblVerification.Text = message; + panelLogin.lblVerification.ForeColor = cl; + panelLogin.lblVerification.Visible = true; + panelLogin.textBoxUsername.Text = ""; + panelLogin.textBoxPassword.Text = ""; + MainClient.Disconnect(); + updateTimer.Stop(); + } + } +} \ No newline at end of file diff --git a/ErgometerIPR/ErgometerApplication/ClientApplicatie.resx b/ErgometerIPR/ErgometerApplication/ClientApplicatie.resx new file mode 100644 index 0000000..26f2a51 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/ClientApplicatie.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 25 + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerApplication/ErgometerApplication.csproj b/ErgometerIPR/ErgometerApplication/ErgometerApplication.csproj new file mode 100644 index 0000000..4e96fa2 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/ErgometerApplication.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {1191E100-AA1B-4F41-85E2-EC2BE8ED39FD} + WinExe + Properties + ErgometerApplication + ErgometerApplication + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\..\..\ErgometerLibrary\ErgometerLibrary\ErgometerLibrary\bin\Debug\ErgometerLibrary.dll + + + ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + Component + + + + Component + + + Component + + + Component + + + Component + + + Component + + + Form + + + ClientApplicatie.cs + + + + + ClientApplicatie.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerApplication/MainClient.cs b/ErgometerIPR/ErgometerApplication/MainClient.cs new file mode 100644 index 0000000..635b9a8 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/MainClient.cs @@ -0,0 +1,284 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ErgometerApplication +{ + class MainClient + { + + public static ComPort ComPort { get; } + public static TcpClient Doctor { get; set; } + public static List Metingen { get; } + public static List Chat { get; } + + public static string Name; + + public static int Session; + public static bool Loggedin; + + private static Thread t; + private static bool running; + + public static string HOST = "127.0.0.1"; + public static int PORT = 8888; + + public static ClientApplicatie Client; + + static MainClient() + { + ComPort = new ComPort(); + Doctor = new TcpClient(); + Metingen = new List(); + Chat = new List(); + + Name = "Unknown"; + Session = 0; + Loggedin = false; + } + + public static void Init(ClientApplicatie client) + { + Client = client; + } + + public static bool Connect(string comport, string name, string password, out string error) + { + error = "Succes"; + + if (!ComPort.IsOpen()) + { + if (ComPort.Connect(comport)) + { + ComPort.Write("RS"); + string temp = ComPort.Read(); + if (temp == "err") + { + ComPort.Disconnect(); + error = "De Ergometer is niet verbonden"; + return false; + } + Thread.Sleep(200); + ComPort.Write("CM"); + ComPort.Read(); + Thread.Sleep(200); + + ComPort.Write("ST"); + string response = ComPort.Read(); + + SaveMeting(response); + } + else + { + error = "De ergometer is niet verbonden"; + return false; + } + } + + if (Doctor == null || !Doctor.Connected) + { + if (Doctor == null) + Doctor = new TcpClient(); + + try + { + Doctor.Connect(HOST, PORT); + } + catch (Exception e) + { + error = "Server is niet online."; + return false; + } + + Name = name; + + NetCommand net = NetHelper.ReadNetCommand(Doctor); + if (net.Type == NetCommand.CommandType.SESSION) + Session = net.Session; + else + throw new Exception("Session not assigned"); + + running = true; + + t = new Thread(run); + t.IsBackground = true; + t.Start(); + } + + if(! Loggedin) + { + NetCommand command = new NetCommand(name, false, password, Session); + NetHelper.SendNetCommand(Doctor, command); + + NetCommand response = NetHelper.ReadNetCommand(Doctor); + if (response.Type == NetCommand.CommandType.RESPONSE && response.Response == NetCommand.ResponseType.LOGINWRONG) + { + Loggedin = false; + error = "De inloggegevens zijn onjuist."; + return false; + } + + Loggedin = true; + } + + return true; + } + + public static void Disconnect() + { + if (ComPort.IsOpen()) + { + if (ComPort.Disconnect()) + { + + } + else + throw new Exception("Comport was unable to disconnect"); + } + + if (Doctor != null && Doctor.Connected) + { + NetHelper.SendNetCommand(Doctor, new NetCommand(NetCommand.CommandType.LOGOUT, Session)); + Loggedin = false; + running = false; + Doctor.Close(); + Doctor = null; + } + } + + public static Meting SaveMeting(string meting) + { + Meting m = null; + + try + { + m = Meting.Parse(meting); + } + catch(Exception e) + { + return new Meting(0, 0, 0, 0, 0, 0, 0, 0, 0); + } + + + Metingen.Add(m); + if (Doctor.Connected) + { + NetHelper.SendNetCommand(Doctor, new NetCommand(m, Session)); + } + + return m; + } + + public static void run() + { + while(running) + { + if(Doctor.Connected && Doctor.Available > 0) + { + NetCommand command = NetHelper.ReadNetCommand(Doctor); + ParseCommand(command); + } + } + + if(Doctor != null) + Doctor.Close(); + } + + private static void ParseCommand(NetCommand command) + { + switch(command.Type) + { + case NetCommand.CommandType.VALUESET: + ParseValueSet(command); + break; + case NetCommand.CommandType.CHAT: + ChatMessage chat = new ChatMessage(command.DisplayName, command.ChatMessage, true); + Chat.Add(chat); + Client.chat.Invoke(Client.chat.passChatMessage, new Object[] { chat }); + break; + case NetCommand.CommandType.RESPONSE: + Console.WriteLine(command.Response.ToString()); + break; + case NetCommand.CommandType.SESSION: + Session = command.Session; + break; + case NetCommand.CommandType.ERROR: + Console.WriteLine("An error occured, ignoring"); + break; + default: + throw new FormatException("Error in Netcommand: Received command not recognized"); + } + } + + public static void SendNetCommand(NetCommand command) + { + if(! NetHelper.SendNetCommand(Doctor, command)) + { + Disconnect(); + } + } + + private static void ParseValueSet(NetCommand command) + { + switch(command.Value) + { + case NetCommand.ValueType.DISTANCE: + ComPort.Write("RS"); + ComPort.Read(); + Thread.Sleep(200); + ComPort.Write("CM"); + ComPort.Read(); + Thread.Sleep(700); + ComPort.Write("PD " + command.SetValue.ToString()); + ComPort.Read(); + break; + case NetCommand.ValueType.ENERGY: + ComPort.Write("RS"); + ComPort.Read(); + Thread.Sleep(200); + ComPort.Write("CM"); + ComPort.Read(); + Thread.Sleep(700); + ComPort.Write("PE " + command.SetValue.ToString()); + ComPort.Read(); + break; + case NetCommand.ValueType.POWER: + ComPort.Write("CM"); + ComPort.Read(); + Thread.Sleep(200); + ComPort.Write("PW " + command.SetValue.ToString()); + ComPort.Read(); + break; + case NetCommand.ValueType.TIME: + ComPort.Write("RS"); + ComPort.Read(); + Thread.Sleep(200); + ComPort.Write("CM"); + ComPort.Read(); + Thread.Sleep(700); + string temp = (command.SetValue / 60) + ""; + if(temp.Length < 2) + { + temp = "0" + temp; + } + string temp2 = (command.SetValue % 60) + ""; + if (temp2.Length < 2) + { + temp2 = "0" + temp2; + } + + string time = temp + temp2; + ComPort.Write("PT " + time); + ComPort.Read(); + break; + default: + throw new FormatException("Error in NetCommand: ValueSet is not recognized"); + } + } + } +} diff --git a/ErgometerIPR/ErgometerApplication/PanelClientChat.cs b/ErgometerIPR/ErgometerApplication/PanelClientChat.cs new file mode 100644 index 0000000..035353c --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/PanelClientChat.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using ErgometerLibrary; +using ErgometerLibrary.Chat; + +namespace ErgometerApplication +{ + public class PanelClientChat : Panel + { + + + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.RichTextBox richTextBox1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Panel panel3; + private System.Windows.Forms.Label connectionLabel; + + public PanelClientChat() : base() + { + this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.panel2 = new System.Windows.Forms.Panel(); + this.connectionLabel = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.panel1 = new System.Windows.Forms.Panel(); + this.button1 = new System.Windows.Forms.Button(); + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + + this.panel3 = new System.Windows.Forms.Panel(); + // + // flowLayoutPanel1 + // + this.flowLayoutPanel1.AutoSize = true; + this.flowLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; + this.flowLayoutPanel1.Controls.Add(this.panel2); + this.flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; + this.flowLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.flowLayoutPanel1.Name = "flowLayoutPanel1"; + this.flowLayoutPanel1.Padding = new System.Windows.Forms.Padding(3); + this.flowLayoutPanel1.Size = new System.Drawing.Size(390, 36); + this.flowLayoutPanel1.TabIndex = 0; + this.flowLayoutPanel1.WrapContents = false; + this.flowLayoutPanel1.SizeChanged += new System.EventHandler(this.FlowLayoutPanel1_SizeChanged); + // + // panel2 + // + this.panel2.Controls.Add(this.connectionLabel); + this.panel2.Dock = System.Windows.Forms.DockStyle.Left; + this.panel2.Location = new System.Drawing.Point(6, 6); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(385, 24); + this.panel2.TabIndex = 0; + // + // connectionLabel + // + this.connectionLabel.AutoSize = true; + this.connectionLabel.Font = new System.Drawing.Font("Segoe UI Semibold", 8F, System.Drawing.FontStyle.Bold); + this.connectionLabel.Location = new System.Drawing.Point(4, 4); + this.connectionLabel.Name = "connectionLabel"; + this.connectionLabel.Size = new System.Drawing.Size(112, 13); + this.connectionLabel.TabIndex = 0; + this.connectionLabel.Text = "Now connected with: Doctor"; + // + // label2 + // + this.label2.Location = new System.Drawing.Point(0, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(100, 23); + this.label2.TabIndex = 0; + // + // label1 + // + this.label1.Location = new System.Drawing.Point(0, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(100, 23); + this.label1.TabIndex = 0; + // + // panel1 + // + this.panel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.panel1.Controls.Add(this.button1); + this.panel1.Controls.Add(this.richTextBox1); + this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(400, 100); + this.panel1.TabIndex = 1; + // + // button1 + // + this.button1.BackColor = System.Drawing.Color.White; + this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.button1.Location = new System.Drawing.Point(310, 4); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(85, 90); + this.button1.TabIndex = 1; + this.button1.Text = "Send"; + this.button1.UseVisualStyleBackColor = false; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // richTextBox1 + // + this.richTextBox1.BackColor = System.Drawing.Color.White; + this.richTextBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.richTextBox1.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.richTextBox1.Location = new System.Drawing.Point(4, 4); + this.richTextBox1.MaxLength = 250; + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.None; + this.richTextBox1.Size = new System.Drawing.Size(300, 90); + this.richTextBox1.TabIndex = 0; + this.richTextBox1.Text = ""; + this.richTextBox1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.TextBox_KeyDown); + // + // panel3 + // + this.panel3.AutoScroll = true; + this.panel3.HorizontalScroll.Enabled = false; + this.panel3.HorizontalScroll.Visible = false; + this.panel3.BackColor = System.Drawing.Color.White; + this.panel3.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel3.Controls.Add(this.flowLayoutPanel1); + this.panel3.Location = new System.Drawing.Point(0, 0); + + this.panel3.Name = "panel3"; + this.panel3.TabIndex = 1; + // + // container + // + this.Controls.Add(this.panel3); + this.Controls.Add(this.panel1); + this.Dock = System.Windows.Forms.DockStyle.Fill; + this.Location = new System.Drawing.Point(0, 0); + this.Name = "container"; + this.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.panel3_MouseWheel); + + passChatMessage = new ChatDelegate(this.AddChatItem); + } + + private void button1_Click(object sender, EventArgs e) + { + if (richTextBox1.TextLength > 1) + { + AddChatItem(new ChatMessage("Anon", richTextBox1.Text, false)); + MainClient.SendNetCommand(new NetCommand(richTextBox1.Text, false, MainClient.Session)); + richTextBox1.ResetText(); + } + } + + public void AddChatItem(ChatMessage chat) + { + flowLayoutPanel1.Controls.Add(new ChatItem(chat)); + } + + private void panel3_MouseWheel(object sender, MouseEventArgs e) + { + panel3.Focus(); + } + + private void FlowLayoutPanel1_SizeChanged(object sender, System.EventArgs e) + { + if (flowLayoutPanel1.Size.Height > panel3.Size.Height) + { + panel2.Width = 375; + flowLayoutPanel1.Width = 380; + panel3.VerticalScroll.Value = panel3.VerticalScroll.Maximum; + } + } + + private bool textBoxIsEmpty() + { + string[] text = richTextBox1.Text.Split(); + foreach(string s in text) + { + if(s != " ") + { + return false; + } + } + return true; + } + + private void TextBox_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + button1_Click(this, new EventArgs()); + e.Handled = true; + } + } + + public delegate void ChatDelegate(ChatMessage chat); + public ChatDelegate passChatMessage; + } +} diff --git a/ErgometerIPR/ErgometerApplication/PanelClientContainer.cs b/ErgometerIPR/ErgometerApplication/PanelClientContainer.cs new file mode 100644 index 0000000..1595464 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/PanelClientContainer.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerApplication +{ + public class PanelClientContainer:Panel + { + + public PanelClientChat panelClientChat; + public PanelGraphView panelGraphView; + + public PanelClientContainer() : base() + { + this.panelClientChat = new PanelClientChat(); + this.panelGraphView = new PanelGraphView(); + + // + // panelClientContainer + // + this.Controls.Add(this.panelGraphView); + this.Controls.Add(this.panelClientChat); + this.Dock = System.Windows.Forms.DockStyle.Fill; + this.Location = new System.Drawing.Point(0, 0); + this.Name = "panelClientContainer"; + this.Size = new System.Drawing.Size(800, 600); + this.TabIndex = 0; + + } + + } +} diff --git a/ErgometerIPR/ErgometerApplication/PanelClientData.cs b/ErgometerIPR/ErgometerApplication/PanelClientData.cs new file mode 100644 index 0000000..0d4e07b --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/PanelClientData.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerApplication +{ + public class PanelClientData : Panel + { + public Label labelMetingCurrentValue; + public ProgressBar progressBarMeting; + public Label metingName; + + private int min; + private int max; + private string name; + + public PanelClientData(string name, int min, int max) : base() + { + this.min = min; + this.max = max; + this.name = name; + + this.metingName = new System.Windows.Forms.Label(); + this.progressBarMeting = new System.Windows.Forms.ProgressBar(); + this.labelMetingCurrentValue = new System.Windows.Forms.Label(); + // + // initialize panel + // + this.BackColor = System.Drawing.SystemColors.ControlLightLight; + this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.Controls.Add(this.labelMetingCurrentValue); + if(name != "Tijd") + this.Controls.Add(this.progressBarMeting); + this.Controls.Add(this.metingName); + this.Dock = System.Windows.Forms.DockStyle.Top; + this.Location = new System.Drawing.Point(0, 0); + this.Name = "panel1"; + this.Size = new System.Drawing.Size(284, 80); + // + // metingName + // + this.metingName.AutoSize = true; + this.metingName.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.metingName.Location = new System.Drawing.Point(12, 9); + this.metingName.Name = "metingName"; + this.metingName.Size = new System.Drawing.Size(105, 21); + this.metingName.TabIndex = 0; + this.metingName.Text = name; + // + // progressBarMeting + // + this.progressBarMeting.Location = new System.Drawing.Point(16, 39); + this.progressBarMeting.Name = "progressBarMeting"; + this.progressBarMeting.Size = new System.Drawing.Size(183, 23); + this.progressBarMeting.TabIndex = 1; + this.progressBarMeting.Value = 0; + // + // labelMetingCurrentValue + // + this.labelMetingCurrentValue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.labelMetingCurrentValue.AutoSize = true; + this.labelMetingCurrentValue.Font = new System.Drawing.Font("Segoe UI", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelMetingCurrentValue.Location = new System.Drawing.Point(215, 32); + this.labelMetingCurrentValue.Name = "labelMetingCurrentValue"; + this.labelMetingCurrentValue.Size = new System.Drawing.Size(57, 32); + this.labelMetingCurrentValue.TabIndex = 2; + this.labelMetingCurrentValue.Text = "0"; + } + + public void setText(string text) + { + this.metingName.Text = text; + } + + public void updateValue(int value) + { + if (name == "Tijd") + { + this.labelMetingCurrentValue.Text = (value / 60) + ":" + (value % 60); + } + else + { + this.labelMetingCurrentValue.Text = value.ToString(); + this.progressBarMeting.Value = ValueToPercentage(value); + } + + } + + public void updateValue(double value) + { + this.labelMetingCurrentValue.Text = value.ToString(); + this.progressBarMeting.Value = ValueToPercentage((int)value); + } + + public void updateValue(decimal value) + { + this.labelMetingCurrentValue.Text = value.ToString(); + this.progressBarMeting.Value = ValueToPercentage((int)value); + } + + private int ValueToPercentage(int value) + { + if (value < min) + return 0; + + if (value > max) + return 100; + + return ((value - min) * 100) / (max - min); + } + } +} diff --git a/ErgometerIPR/ErgometerApplication/PanelClientDataView.cs b/ErgometerIPR/ErgometerApplication/PanelClientDataView.cs new file mode 100644 index 0000000..4c27172 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/PanelClientDataView.cs @@ -0,0 +1,71 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.DataVisualization.Charting; + +namespace ErgometerApplication +{ + public class PanelGraphView : Panel + { + + private List charts; + private FlowLayoutPanel flowlayout; + + public PanelGraphView() : base() + { + createCharts(); + + flowlayout = new FlowLayoutPanel(); + + flowlayout.Dock = DockStyle.Fill; + flowlayout.BackColor = System.Drawing.Color.DarkGray; + flowlayout.Location = new System.Drawing.Point(0, 0); + flowlayout.Name = "flowlayout"; + flowlayout.Padding = new Padding(15); + flowlayout.AutoScroll = true; + foreach (ChartPanel chart in charts) + { + flowlayout.Controls.Add(chart); + } + + List metingen = new List(); + metingen.Add(new Meting(0, 0, 0, 0, 0, 0, 0, 0, 0)); + + updateAllCharts(metingen); + // + // panelGraphView + // + this.Dock = System.Windows.Forms.DockStyle.Fill; + this.Controls.Add(flowlayout); + + this.Location = new System.Drawing.Point(0, 0); + this.Name = "panelGraphView"; + this.Size = new System.Drawing.Size(400, 600); + this.TabIndex = 1; + } + + public void createCharts() + { + charts = new List(); + charts.Add(new ChartPanel(ChartPanel.MetingType.HEARTBEAT, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.RPM, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.SPEED, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.DISTANCE, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.ENERGY, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.POWER, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.ACTUALPOWER, SeriesChartType.Line)); + } + + public void updateAllCharts(List metingen) + { + foreach (ChartPanel chart in charts) + { + chart.updateChart(metingen); + } + } + } +} diff --git a/ErgometerIPR/ErgometerApplication/PanelLogin.cs b/ErgometerIPR/ErgometerApplication/PanelLogin.cs new file mode 100644 index 0000000..151a3a0 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/PanelLogin.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerApplication +{ + public class PanelLogin:Panel + { + private System.Windows.Forms.Label lblUsername; + private System.Windows.Forms.Label lblPassword; + public System.Windows.Forms.Label lblVerification; + public System.Windows.Forms.TextBox textBoxPassword; + public System.Windows.Forms.TextBox textBoxUsername; + public System.Windows.Forms.Button buttonLogin; + private System.Windows.Forms.Label lblLoginTitle; + private System.Windows.Forms.PictureBox pictureBoxBike; + private ClientApplicatie app; + + public PanelLogin(ClientApplicatie app):base() + { + this.app = app; + this.pictureBoxBike = new System.Windows.Forms.PictureBox(); + this.buttonLogin = new System.Windows.Forms.Button(); + this.textBoxPassword = new System.Windows.Forms.TextBox(); + this.textBoxUsername = new System.Windows.Forms.TextBox(); + this.lblLoginTitle = new System.Windows.Forms.Label(); + this.lblUsername = new System.Windows.Forms.Label(); + this.lblPassword = new System.Windows.Forms.Label(); + this.lblVerification = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBoxBike)).BeginInit(); + // + // panelLogin + // + this.Controls.Add(this.pictureBoxBike); + this.Controls.Add(this.buttonLogin); + this.Controls.Add(this.textBoxUsername); + this.Controls.Add(this.textBoxPassword); + this.Controls.Add(this.lblLoginTitle); + this.Controls.Add(this.lblUsername); + this.Controls.Add(this.lblPassword); + this.Controls.Add(this.lblVerification); + this.Dock = System.Windows.Forms.DockStyle.Fill; + this.Location = new System.Drawing.Point(0, 0); + this.Name = "panelLogin"; + this.Size = new System.Drawing.Size(800, 600); + this.TabIndex = 0; + // + // pictureBoxBike + // + this.pictureBoxBike.Anchor = System.Windows.Forms.AnchorStyles.None; + this.pictureBoxBike.Image = global::ErgometerApplication.Properties.Resources.flatbike; + this.pictureBoxBike.Location = new System.Drawing.Point(137, 131); + this.pictureBoxBike.Name = "pictureBoxBike"; + this.pictureBoxBike.Size = new System.Drawing.Size(250, 250); + this.pictureBoxBike.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; + this.pictureBoxBike.TabIndex = 4; + this.pictureBoxBike.TabStop = false; + // + // buttonLogin + // + this.buttonLogin.Anchor = System.Windows.Forms.AnchorStyles.None; + this.buttonLogin.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonLogin.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.buttonLogin.Location = new System.Drawing.Point(468, 338); + this.buttonLogin.Name = "buttonLogin"; + this.buttonLogin.Size = new System.Drawing.Size(168, 31); + this.buttonLogin.TabIndex = 3; + this.buttonLogin.Text = "Aanmelden"; + this.buttonLogin.UseVisualStyleBackColor = true; + this.buttonLogin.Click += new System.EventHandler(this.buttonLogin_Click); + // + // textBoxPassword + // + this.textBoxPassword.Anchor = System.Windows.Forms.AnchorStyles.None; + this.textBoxPassword.Location = new System.Drawing.Point(467, 287); + this.textBoxPassword.MaxLength = 16; + this.textBoxPassword.Name = "textBoxPassword"; + this.textBoxPassword.PasswordChar = '*'; + this.textBoxPassword.Size = new System.Drawing.Size(167, 20); + this.textBoxPassword.TabIndex = 2; + this.textBoxPassword.KeyDown += TextBoxPassword_KeyDown; + // + // textBoxUsername + // + this.textBoxUsername.Anchor = System.Windows.Forms.AnchorStyles.None; + this.textBoxUsername.Location = new System.Drawing.Point(468, 231); + this.textBoxUsername.MaxLength = 16; + this.textBoxUsername.Name = "textBoxUsername"; + this.textBoxUsername.Size = new System.Drawing.Size(167, 20); + this.textBoxUsername.TabIndex = 2; + this.textBoxUsername.KeyDown += TextBoxPassword_KeyDown; + // + // lblLoginTitle + // + this.lblLoginTitle.Anchor = System.Windows.Forms.AnchorStyles.None; + this.lblLoginTitle.AutoSize = true; + this.lblLoginTitle.Font = new System.Drawing.Font("Segoe UI", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblLoginTitle.Location = new System.Drawing.Point(461, 141); + this.lblLoginTitle.Name = "lblLoginTitle"; + this.lblLoginTitle.Size = new System.Drawing.Size(87, 32); + this.lblLoginTitle.TabIndex = 1; + this.lblLoginTitle.Text = "Log In"; + // + // lblVerification + // + this.lblVerification.Anchor = System.Windows.Forms.AnchorStyles.None; + this.lblVerification.AutoSize = true; + this.lblVerification.Font = new System.Drawing.Font("Segoe UI Light", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblVerification.Location = new System.Drawing.Point(140, 415); + this.lblVerification.Name = "lvlVerification"; + this.lblVerification.Size = new System.Drawing.Size(200, 21); + this.lblVerification.TabIndex = 1; + this.lblVerification.Text = "Verification label"; + this.lblVerification.Visible = false; + // + // lblUsername + // + this.lblUsername.Anchor = System.Windows.Forms.AnchorStyles.None; + this.lblUsername.AutoSize = true; + this.lblUsername.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblUsername.Location = new System.Drawing.Point(464, 207); + this.lblUsername.Name = "lblUsername"; + this.lblUsername.Size = new System.Drawing.Size(128, 21); + this.lblUsername.TabIndex = 1; + this.lblUsername.Text = "Gebruikersnaam"; + // + // lblPassword + // + this.lblPassword.Anchor = System.Windows.Forms.AnchorStyles.None; + this.lblPassword.AutoSize = true; + this.lblPassword.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold); + this.lblPassword.Location = new System.Drawing.Point(464, 263); + this.lblPassword.Name = "lblPassword"; + this.lblPassword.Size = new System.Drawing.Size(103, 21); + this.lblPassword.TabIndex = 0; + this.lblPassword.Text = "Wachtwoord"; + ((System.ComponentModel.ISupportInitialize)(this.pictureBoxBike)).EndInit(); + } + + private void TextBoxPassword_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + buttonLogin_Click(this, new EventArgs()); + } + } + + public void buttonLogin_Click(object sender, EventArgs e) + { + string username = textBoxUsername.Text; + string password = textBoxPassword.Text; + this.app.validateLogin(username,password); + } + } +} diff --git a/ErgometerIPR/ErgometerApplication/Program.cs b/ErgometerIPR/ErgometerApplication/Program.cs new file mode 100644 index 0000000..cb1e68e --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/Program.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerApplication +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new ClientApplicatie()); + } + } +} diff --git a/ErgometerIPR/ErgometerApplication/Properties/AssemblyInfo.cs b/ErgometerIPR/ErgometerApplication/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3bd3fe6 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ErgometerApplication")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ErgometerApplication")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1191e100-aa1b-4f41-85e2-ec2be8ed39fd")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ErgometerIPR/ErgometerApplication/Properties/Resources.Designer.cs b/ErgometerIPR/ErgometerApplication/Properties/Resources.Designer.cs new file mode 100644 index 0000000..9de517f --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ErgometerApplication.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ErgometerApplication.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap flatbike { + get { + object obj = ResourceManager.GetObject("flatbike", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/ErgometerIPR/ErgometerApplication/Properties/Resources.resx b/ErgometerIPR/ErgometerApplication/Properties/Resources.resx new file mode 100644 index 0000000..b226b08 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\flatbike.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerApplication/Properties/Settings.Designer.cs b/ErgometerIPR/ErgometerApplication/Properties/Settings.Designer.cs new file mode 100644 index 0000000..fb56e49 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ErgometerApplication.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/ErgometerIPR/ErgometerApplication/Properties/Settings.settings b/ErgometerIPR/ErgometerApplication/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/ErgometerIPR/ErgometerApplication/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ErgometerIPR/ErgometerApplication/Resources/flatbike.png b/ErgometerIPR/ErgometerApplication/Resources/flatbike.png new file mode 100644 index 0000000000000000000000000000000000000000..1a88ba60b57f85f539ed4c996aa1c4798ab02536 GIT binary patch literal 56275 zcmaHSb9g1qx^HaTwr$(Sijx)Fwr$(?#GKf+Cw3+iPm)RI=G*7&bM7B^-+tDr)m_hv zU%lwAx4Kreijp)U93C792neFAjD#8p2q?opUl^#b9&i5y_pcvZH%Wk-x}$}gr-_R> zh^Uz((40ip-o(;e&D_My+j-Jl00ab_%vuBB22fPsGj+6QGWmyw$;;m9iyH()K-kO4 z#MIW@jRa_JY3(3LcH8rvjKtbZkW8CXkwwu-+}z4q#>d56-A75o)W_D8*NjY9h(y4P z?@NHaxtj@zm%W{XE1#Dj*}vrSeVzZ)%}hq}FA_IfL9+h>1yEEW5qESkC*fpbXEbGD zWhLR_Vq)dsc4e;T?vv| zxw$#XaaO}cM~N0Li%4ruy^_&u@0{PZKf}SF?*RfF|#tU{4=C~6DlhH|4Hrb|A*Su zP0jp&@%}#ryJ~nlnKP@IyE?kNn0`6Vg8UyD-swxTgt?2ghq;-Q zi=#cszlO|b{hzUL{6~5J&1?3bv2gxJUgj?_%>Ov{|8n%dyS}35pVR*d+pmlN34C*h zuV{Dq3hSCWAsi5p4QW{kQ4O!H>pti#18r}nKMiflg@$FSnRUR;jMyFfE-{ z*8lp~UboC=Co@dav6uG!VMsikp?$w@TkF|-(KAAoqa9&%aE{&fvm%44>l|ftU^V_~ zoMZJ60AI}xa3Dr+=Mbv{r|GNd9KAsRUCS_lKI-`ua_{R=< z^3OIm6C-9_p-gIkao`(cAJGNpcI^s%a^U;((_&%2zwqikud|-<&7eDdPJr@AhuL7L zIz}A90tF+=NtI=l<&E;oE!Vc|^Glt7z#9(9>1N?K$W{UXBv#rV(;)TH&mJ@1WS>Eg z&px}Jch5zk?^xdRG*zP{?IK@j;gO37cqqvS(wae597L;@pP4RqpPkqFuU)qi1{(8nimm2l>hHz2}M$HRB{J^(q<$W)@1Sq`X#V0G%oV8UKf*=$N>tBZSfXYsbYRYegtnz>tK}y5g+;a@P zZL_OkB>TJ6!ctBna)1NkM#gaK)!ve6tBQruIx$7VUMpb+9Q@Y6x-DIWy!~^)ZiuZp z#lPOvozBAW%VRf>A!#kblDKK$)KKM=E6`QU?INq#pjG-|f|O*p)XHFSbLG8)SXGdy zBDK;004tbrDOPKOny}bM6C^t{P!&omvDfGyXK&L6rA0=N_AZ010O3`p$wk^GzyQ7w zMJ3mhdOrZcS0704{fWK-E`o0yi9%gPBn&E!a0m5ZQ zTo^YYB@ESo(=>NBdW>+Ek4iNP(l*9KGhWeMw@4I^bdPS+R;mL8K z9$CAfW^Y8D{VmgXP63D92s*3dZ`#CdBD!wlv*#ce!jP5AWm{DXjDl|S}4cDsv^ zeY}_NT53q`19+LKGq5G=pZW2E%Atg7%CC(8cyL*gjz6ES+v!>GpnmUW%#|bM%9}rN zR@iWMK`Y?SRn%hYH{+L=YWJ6ne`Iy~QdCLJO(wExN(rg_GamJV+gOyrX9~NcOSpX~ zK}UnF;TwKZY7DdP>8Ojd0GNK%AU#xF7gsSlRuy2czE#9M8Im5VX4DF2u`f$;cAIJu z25w%Gi)t^4sk!JW1d^%UH(qDIX1oX!d`-XElSitJjm?=k21L^rqUx}{iL2U!!!C^h zdwpo58>h9PW#&pG%z2dSA9LfXnAi2{nUoJwdVDi9E;p*&jW;dQ-H0Dw=Y88EL$#OlNw*Z@y9(1uNg zh%_y6=7$&h<&sJSfYK*xDDZs+6RT-$3w9$SLfdEO(4)@%lpvI!JT8{|d!@;>ex$A$ z@GHysXp7_kEV_2~C^J=D!=1F9L1vk*bRMcv+tZw9NC(&Yb5K(V{^92O{B;TpBp#tr zpjQ-BSkuDKHiq__E9qLFcL^Jb8<}(tjdtX2pud>fo`N77sOuHXkl$eG(U1N-up0L< z)B>U0iy>?18EWyG&>1knHFuPb^ka#R8QLnKp_n{Ec?eqSaheiFYc~29Ozq(_)xX1y z8JM*B3xx9citQ)~JC!p`$GkZ)w&k93W%=n}yXi)6Y9RpTKwD?oXvS+A#D=v_JO**9 z4n3&OisWfW9_rWU#)DzD`GA{i-ZxG@Lf{$nTwCTh&~_T%*S^)lGt|nB*U>pxqIRZa zn73IJpA_1qouLp5$=2mJ+FkiUGFW?Z6u?~1c(y^-#xfE*O6SRvI-DigHw&0$;$oL} zZ#>ttAXVn^GeKTX9mk)Ve@MI7j0mTRMaUQW*XIr@jS=qAc@o8Z=dcG{ zDua5(>pS8urLxoq%C(5bzEHcm|Eyq8F(yPknmxCv4#o{yO&4lzA>lyk+XQ6$yzylel zw{yCE${!>}6{W;K&P$bigqnXP{*~UpdfYb%qeU;NaENPHpYO~B{vL5D=dwF?+N|9? zQ_D-V+d5C>yonjCz$Xj!pkvKdIUzpfn&gRQeqs1}TA#YQ3WZ(W%q)s3uu+7LX1)(nFTU31*say6tyLROb27~ zhq?e}C)@_m@Yky;~IKbEQ_N=i@cQ3U+=$bKZB^nZsU?+g9{N z$GgZbhh$Vj;JIzdrovhvY0kgI!s)>b)l^2%a^^=CrMK1`9mT5C9~!XFu%5MDkehiJ zl-08sHFmEx*TM%j#cX>c>HgfmAmfmBvMan*pl6+j9%ZfFwg9dDn$AEmrXDFuh}h;r z)_Yq=DbFkq%8VOhc69dH$!~Eslip{2Q?NCh8h(WWsIG_0>zNsQerb)}t}9w41g$TE zY(2$X&QT6JL5NXs-L>9VL~W8Ab!GqZaa%cA(dhp1_FU0uHqmLk^dCPk252p8y&|AR zn-J8O=9@B+*mVWLrOlH;?~(B!_Z;h+aI7ka*1tdF2k9J~`DqtiB7W!GAm}m=Xq;A$ z20cyT6HmL4u#ZC-I)Yx2`5u|(YJS~7Y~)VJ;I%N}HT1JB!CVD6XZ4{Opy-wI2Wd@s z?ufSQHv&ebUU+uCe98ZMse?8dW&or!INgjAG)%B-gUWB=PobV`>sq*n_YJbPd!Vb+@#*wpo*#28PvJk%x;1CmddB2X7x?0M&jEbcp!Aovqfj17j=g06GC*9& z8eGpBoE93JRhPalt1;o356=xff9G8^iFSucC_nz*QR~>3t+TBchZzlBm$5oGNalYc zJ#|5#xL#B^cWY~NgeXI<>w?7o%U};zjnfUhLbQ3U!nIlA)RC@`O*_?)>qy6PGkMFG zKzc;Q4Xmqa3 z59kHqIshY#4KnTh%{6ZMDZ_;Ezm!!Lwaqk)I>YPw`Ao1zo~;zPH-N?})97cD32B*c zW8#0humZlO$!8d?E*^~cIRx!Rn$2o=d9Rj~b9ag+6ldSIuN_CFJdmcY#q2u%LB(m@ zh5ioD*n-z=tl5TkmnSpq3X8Bvq5wtT@OUugIQ})|=6_){Uq7aT2jubB21y-$e}HPr z!!VngEaoP_Yg%FZnlXG;mX+l%48cJZDU}7 z>q(-H&e{6YiBY^BE!@^I#$mpz2Bb5vrr+!aZ2h(M6xUM!!gOlmYMZfsi3+c)j@4>o z`7SnR@j#}Ql&G+IYXi~@FTQigm8tg_6t{j&io=h!-LioaxYoOmZuu%-d7WuNzLy?l zfb&%pyh7;Ctp>0EB#A>UFd&@Usy^6+sH+M+PuD^lbB8$kA^-<8%pHVdHFYW%CF2K( zf@IY4O-DSRutYp7W>}9hA$-Na9~;&5*1|WGxVt`Moi$u@UCUULt|B?EErVdGsjS}` zEMF;Sq*#(1a|#EqtAw{UHx^ll7_o}=8qy%34c2_~l~K8gOADw;@Z=C>H+FSrEues( zZU;bl@=WqMBD^nqcxId^2;^RGc~pKp;5FqH`y(p!?(4q$<|Q$I@&1^tziIQZZ{)?d zeOCb2TJa23&3d>l1JdGjbTYzNpT~^q$5|9kdBXzc_&xH=vEsmnEZ zwA0X@Un~j*17v88gvT1ynb}wV~LZWoj4(^ zgR;i+HU!|~pMm8@b-oQltDvPuG6}W8>j^WW6drh0oG-UC21n$AH-7B0c(>S8x|UGP z4M?^GoGgQ#DR_(_IKf-^+9U(<;sh#q&<5q1OP*)As+w7&eC$srmTfVEaYJ(yP}r4S zl+~3{HYkg~*(YT)NN_>7nxiZziw0uT`Y7>1js|7P44X!r&?5%GT;eTcCEw}Mw*s|M zkac$wB(?HVe?PETTgmanlk!KiI}J`>LpDlXQ}`TmE8}*JqA7WCFpQGL6#m6u>HlEF zySM)BPP`;1yh=fZh_3clxo|9vx&S*_7x(wZnkDq{vBjJZbaf4?F*Ty11>R7ZS;9#8 zcmU0dUTa9N9iyfa`CGj~Ad$QpBlj@r*S$JnGmrhkCKi-`kp{8i6wg?46Z2|=lC_5q zR)>bb(TqNG~VfRs}OhZJW@kXQ-npRmtNmJAO@cySU0jbWy- z>uUWFIt&$IHktPVvE9a?)hfA)C~rzqK7Nw*I?4N(+N54*cr<(_2(^(F8_tJMtpq%i ze=fZkH8z7#<*eMxh@8M#%8Eb7sox`RH3B+16O z;5P-jG)*`FQ5-)lqm+>a@r8$WP(i`^`GnUObGzA(ShPQE|E|7D=nu(p<|k}*u7lJm zp>X0z1-(@vMf(Z#R>Id>Lm$Glu4%Jzq`6KdWRuc*b7`%~DH9tLdmx-gT@VPlFh`Vb z8{F&O%kcM8zTvXk5eGkWio&FEoOUhpP6W{DUM1`23gC1aW~xG5?KXvckY=Kd zV^aGhK|z$quJpzLM0MhK&9vJOB*oD(uFrX8(;mL3vnW$R>9)sAa^f?@X_u3qgx-S+A0>P!~2QQ%55YX%7kz=9)(gw6uiac6%eKDgpbxy zm&LiDnb)EI4g_fQzao}3evb~Sg9kMzh5F8mtdd|t2a(}4bq-sjvZg2=(YWZz0rU9X ztW&g8k&>mdV`#^S6f!S1r(JZ*$Rd<#@YCfiQ8>zy)wpdmEYnyC;mQ@X>GvT|@j4yO zkJ^7iXbeql?0MPEQUI(LZ*pKb61EPGVm&1SR8e{HhfM$OGv0m*ebMK``vACbFrH8e zHr&SPal)_Pfx(G<%pC2GcY%+N)!3MzYD1+jvt$!y)@3dzT}V+}Z+)o)m9?>-yP%Ta zW7|DGvbUthX6TJXSU5phxguuT*1@vV`q#RMk!r|-%CD^}VMr&>5XEp#tLp?4lA;1$ zIPNL>S_f$rZOR47+UDCn9kzlcSV5KmkQv!p(QPSa1@@=(&W8DQd>L&cz#%%@>6(2^ ztU4aTpi#qy50Ho3_C73Xmxkz~Ed3|)3p1EN?DB~LImN)^ktX!;xE70KSqG)w_iyXz0Zfd$fu zJ)xC17?o~cCwsc!)sZwGS6(~4ekkNR!`PsYW}H8r?;|LH%j7>AE0T;XOz>LFxQ$*W z`LeUo<`$~N-$v?G=+L6s6~w}d`5r=7ZFpxCGWzFJqD|`f1)@GLtj%BnXb=KK{>;o( z}D(<%?Ap#x{|^~dIeAdhy?D9zLb1yhq?Lx^i{IQa}j2F2nZy2*iq(ke87 z_ekrb;fzF(iu`s~9irS)Xs~ojgs7PfUW@^9Lmv`w7piKpbfo|M+4H3&BMlJn$il>^ zB+7&n{7+Q9hfxSj_bd2_)w`~t*G~Z$8i2Tjpf2=r7q@Gmt zY@UfUt=S~~qTq#)aL|5UAo>K|^q1fTLKK(FfOLpidr5rp{qIr^CP}sNM}APD7L|wF zG>eK!GrSmpUCd2%HN~GhRSnN;vDfJF#m_fx5vpunZF{TKCS!L)DacMbek5WK)%imD z0W?OKsRN#_ECWLPx>lB=@I&;Z<4Eb&Svem=b^W3iXKT&Vpy>gEzKr+-3P~#K)TuVm zK780%tKwt^+69a6sh@o|H9-`?dX05O9ewztcKpfo^EjuHWNs>moBGFF9Pg&4LafPl z#KD@5jcqsZv+W8S30-)_r(EVN-|+G3vJBE;u3g_>N@|m&c~iyHMYfIU<`xFFX%xTEbWY}7h8law8vWv76Yb@N4eLnfBZvrN;2s3q?c zm7zM?JP(}sV!O@q$u256#~gR!Fo-q6IA-^JZqO@de4)qm=bH;$jH^woQ%8cbb-WFa z0}5VdH#?b|0L|D``jr9gps~zl+%BXyQSmni$)4!Lhvnkmm`JL%aBxNYR7wy9Ot8da zgT%YOvlDa}m7)ScN-rq*p#bq$#gX~FXFT405?I;zIe zRETw9&A}VvgA1|EGB!Xx5w)IKt1N^|yJqknZ%pR>Y1?iZ*m%PalS{*xW6X@j{_d9E zjH}iTBHxC^5cR$>D236Jjsaf%7lsLz;DgO+PA_q+nwJQjx z?e~!ja<92(q=SAX!y-o-=6#-BnyHBp2-a-VD6${gh(~yilKR!cQW-kNyIkgpI`xJG zOMg%%IJ@lKRQ5D-C|Q+M3F*y~z8G7rbZxKG7UIEGCU31aa~@>#;7xE@8d|aYD%j_k zU}oGghDSqdsP*zwmW+MFNAL*i(jy`Jqi@7d zgA9?nS>*Fd#~`_^ECv6>-}~f)!q%xK9jOq+W-2c!7NI8C81391?qUlOs1Q%M7I@B| zQqVkg0K4Wx%<$`cI%8J$tEQ^SKKgb8$!kqji_rm%#u6UB7`bAr(3E)PL@wmXv9JWN z%*i1Z*JH_P@CeAJRPtshpHI;g9!y3~K{xGMfLg;Awp>e7xrtSR zc6ttWrLM^z^ObPX7M*CkV3~t+7QY|VTvyi2H9@$^>7IrIdVxi^&LnXd2penE=4J51 zERv-UmX~MuJgIgNvhQ1;eLf7SwQC|CZs+QN#lx|MsqT4kJuPhrWG5ct^hb#GcVFWH*_JY_2;&_*yt#4&2Ad4Y}O9$@m*K&o!TPG?PHEOrsE!WBYG=egzZ$^@4Ng{_0vKT&g9*&@L$vG&y( zj2@`Rhj?_0OrfAkBoMWK=knSlP{TGr zn7r`4>~|K6hm^*!bitxGOzzVwIJDLG5cTN071!4XQr*179hnE%5u^iEr^T#6#}gO8 zsoTU*&=6lM5s!zi?;E%oRd9~Fk~7d~jZ8wy=94wh+opA9|{T($ql);t#a_kc=Gp6-t$8JmT745Gyj&ny>lJ~nl=`*c5y16%ob z)e@Ot*5pv-_~qlRBn7PR=7B1ovE8poH)U+L-CdEaK@6@(iMhsO*0%yVjfw|ds7r7N zUo~ttImGL+@Igw7$28f=BepKN&0b!KK+g{G%k^eVs`$5t-73Gc?YD=P#(wbXCwvr) z`!yznO&JAUA$$0p`zFgKc?3>TGZ@HlY#l{Nz5Dyhy;h}Vqh@gQl(^! zRi~0n5%Lg5*a?PTFP+Z;cmkKzGJ@oe3dp4kD|K3@6Co-GvP9Da9(aUG z<*X?)R__?#t2KVXP_q{F<$tZ$A(4FAU1)t2A7*8)Q8!j%y>GUuqE;_cRp5Jrcd^R^ zovMZjxnKxI2M}Td8|Bd<7jhV+YP5pNda~uwY2Ii)udr3S&KxH8h}?SLzmvF6npY_+ zS~snb)L3k79ac1#Yy-z>%aw(723(>~GxflAuYJpvSL~wTZ+@z)A62mcV23RrG}ud1 zO0pMujFZ+ZMnA>#3JstsrlYRa^9aU9AWNLtD(cmih>B|=(A)$mbw{9Gac@oxDg&8m zu@=;;FDN(HR^t_SKdkBW&WCyaMj5q>WSnsgwO z`{Swzd|(Svn(e1_??+nVFqI*$C!0*91dJCUWh>b((0v>U7yVfcT>pt=aO;EIz4xi) zuR9TZlvS>;JJX(HX3{g7aKaM~#!3CPK=TPGTrC1WizN_X+D;nFS1E*2QYg}@!KqZa zc@JC2;vfD&cc{h!&XB;m369!@wFtu42lE8p@8P)rg0Ik4Oo2W^gl0vK+tSJtui=Rt ztl8rnlQ)bo#ruQk7Vnj#NsG?J$4mk|2HXYHobxBhumk>eze>mce4;NOW+DfdBz?Al zztnICSngeV!Rj7ppbuR6_uuSk{cq+~x{!p9A3Bepmx1gKbOgT`Ox1tFP7Q&^lKS&Z z`jkhfLNI(b1;i$t#S;j8IyfrrR8&%1gQmC5E93m7&eON`Z;Ze}C;i&9`5RD#w*n zD$Lr50A8u`uyky!EM1TZ#<8m49@;wULp=<{b<_-<>WWSwSFj3ld5m`>+GBKBgbH?m zE^`Wl0&w#fVoSCLOHBtISfV_B3QfM?0Q>yjtZ_OV2ggc}IFDCnry*kOo&4=C#SR%k zVOLT)az+MXsVvK)w3q4SVCkgFN>TcGvE+=-2!C(&7fwvzo6d@%59+rLY{FYVY@fiJ z^@DO(6~87ihPB^~(Hpr3BN_&CzzMZjT6La51f;JJf{l%vzK+cASk2Rvm||_I(XQFr z*d9UHAi~UQW%wy$Ff*1T;p%@-=zJFPqcHUbNst7x!>g|}Kq%Ztwj6o8$#`l25mZn|ljf2$t9 zi5o8~`J+YThk!qp7|E%rg`};yD8g#@v!U|tvrYa&V`xy#8KdJeS96g@6WhXDM>S5p zl1jBXV+2D>mJ0?opp48Jjd)0|l>46a%#+hHr)l|>)__H9BbD}MlZXer^IL#Me9d&l zPviYd`uT2LZk!Ao-A*RXM)_rYg9TI~uu4(%RvP3AewJ*UK{6qm%1G{;AED23kgx_c znsfMU10-cP@@~p>uhxh%Q*X-QgGc)Fs@dv`V;2RJvvV_b4$3ONenz3#mN={EfQ^_f z6%z}%RHn@YOiK@vftlV3FJ!*6|I|b2ZF#TdQ-P%nuQcATbGh_11ke39Pw{C(st%wjF+6 zY8ey6sTn$qOM#IZFyZWxm_?I1T%o%8aTa8kxSsa~<7}!V{Y*;uMx%2Ti&6a66vmf& z)v%#KZG(4UHtSU_M$&yP4VBthzQ(i?d=MXbtz+=CbV)6G7bd-%i$rKLTzuasR+-54 zkW^6i^>r^xU-B< zGTF`zK2Y9cIMdf=ZZ663tWuu9=GV}$QaKj{39 z9V?kuTW-*!paq?%OSzf;mw%?`3kmg+;m5+vW&;BBwdxE^M8A6<5jf zFm3l4E|Z-63x)JR+GFU#&ERnbY-?-`ZX#c=0@jT@v>PWmRC9z$A1V>yyH38ED^&$0 zmR>|1p?p5lfJ;|4wjPJR({nBET!7M=WKf*~iAu4-=E^ZVo*gYv5)L<`=Rr+p!RC{o z+CAKF)0T}ZM`o0eb^AwY^if>$-|k7)$5r~g>YcUWhopxqucAs~z3uMg1N0_HC0Mr{ zg89`+EWA4HbT}{a$wP)S*izs1F)lij)DNfR_?*UU%t92O3kJRnro}cAcNr!9yEnJ( z<}JpGPF<8kH;|0!xdrUS+%sidJ$N_-jdC?wvJ*21fgH+=_YQiO8ocrw6bW9D5w8aI zf~ccBj>(7-XYVFF3!35U4Q<~Xtdkr+B5X{BKSgN$>5^(^PM5{Es^@YgSQC(lfHx)| zKKtVSzXTDf)VMtP3ixBR2ubE$_lPaPqYwBqi_67_ttg#lf@6pvDX1|TSKai$VWvGx zRtr4lqdh0Guh%IhjQ3+_+7_xg8<;??nFd`-Q`d-4sEt-IZ5TPy2bXqC&e{>)e%>-? zF(}WHas>roIJnOhXhSxhI5d7_8za3aJZJQtuFe^Qk&;V$tn!oqWG0aqV`sYYZ9RVK z0LvBzfAlwR!=_vbMfv+@pViL71%G)Ou8HM`Y#l@fDYEp4`LP!jD5xj{WMz6|b25pl zS)k&F8;DkBxx+4xtoGaMRF#;8xp|SRPSjRA1)h{=RTO$9WExAX6?|5g8@1}0iaM#m zWt}3M-#nH`E~81Ryoj_*g~Ga*cK{r|ocHg%`z)*j@*6@NWwT%1i>0kN;BKgT=OvPS zGKu9SZPZvBROnEy(1={qXDtYO*BbmP?CAW{lByrNS;xv2OXvE%GTKwH783(FbxQ2U zyp1oTJcD2}(db^wY5pxjdC)%*6pbZC%n(7Qjpy_Z#B5Y zgX)^YKw_G(ufR;hsj9i|b^_A@*Y6WD(T>XS)e?+i z0bz;4skcaw4s+t17AC#^`6H>AOf6d;;OL{Y=BX0^R!c{jA}~T=l`OQe8}F>^Cks|R z85a3j(>%{me&+S{@STMO9xR%Fy&4E^IvVI2IuF#`(vMjq3>Wueb1O_4X{!3m^SG+uS# zTjyzGmzB-?kq#Fk^D3ezBeunjg4&Z2m3A94yBv0^e>gX}0(r}kFt4beHm> zkVfAbGF=OOdLBTNKBi8%yS1U83`-zu5f#1K_F*Ag9)&%I3l@zgU@ zG|uCHLIUyZ(irvxY3;I3H?h=bb88ePL09Z*5KbGUJ7m8SN1%PULao0;&|k*b{#XD* z>cjghT8#@D@L+g9>oo&K^YE7rLTd+7SD-$;+I1Zc$fQoQK*K;vro$v|L~X)u+O|T1 zQNjx$E%~Rp$YNf-oZ#0YT-|0#L8h`Y3QGEL{l(o+!??<516p;=2=HP9rU#}J(Orm% z{tqElJv|J&GrMty#EW~Ycnnq;!Q-1~5JPgc%?w_+ zEU|ewaO}0<)k6aov^#4KY6V%rtO4SGSn#*B)9*J%SUr}=qt%BKo;?KC_pH&EVg1EC zc=E-=P0zg|euHLY$m7hMCLJ$JynOs-FSChO%?rIZkg^Yi(wOn*z?Vk>#WEpuh&2SG zmxt`NHbbG8qr^ay?Z)PW7^&%ei=R_-jY|_Mc5Mh!?6YG_XA7#9$-g+o_MZC`W_JMv zYPDk`imM&=R-2#qqWCQSx)V4>P{`ZQb!WzSU#&|W2+^!Jgg1cLVX*I{;lMwRx1Wl1 zzYjiiQJZX4imelO%1Um&)5DR5H2LsLGg|*9?lfxroOgQ9B`Rp<3^VP_NhcPW%>9g4 z#wG;d7!#_z)#IDL zr{E`LanUO@@GZkma>BU!I1}4o{Erkgg2xmD$J`%MxtULvShnYrl3^l_E=ih`uJHI; z)L){LO3+OJgz%6}77vLb1={Yr;_)-+25(%I=}nUdWlE{FFLGQH7VJsc7>qE=?+-NJ z!BlI83JEMTy`tn4vF}#UwE&GCNVhVS?x^6|)1*ZWKQbv5>*X3t4I8s+nCJGdcJMX6 zwgz!^z5;clu+JX&R>B6as^<)^=R7Xlrh0W>dt4R|*%+HjAG(a8`@5MQkj8L)e*#5#@lSWOi8&ZH6DZZR34o5b%YOOsBWGszBQ1d-57}gYp z;;i$qs%;dVqjaRgPT1u_Q4{$dEb}jqRcz2^;?x|Xo6YSExJD|I;SQJ8BPpUSPg__& zxrw-BXe)^iV^8Li#kbTn@k7p1_y0}S|9jSa(1#xnR$DpJq#sVuzUO&>L8m_>IRKZ> zL~dM;czrN;y>#p&HO5L095}q>wkJfXZhiJvOD+BNasw8DM2;f>LO>Xil?Qc?jxtI{ zu|4SJ0zFnJV7x*CHFiK$6VImz++0R(^8&#Ez%k$j;Q=Ik6*7}ETaze)W0-Iqzb$o1 zeD@jDJXhSdzBiJ+u8F7WS9TOmmoc@~PDB!&yxj6y;^|oOZR)dN$sVP2QEO>X>TWNP z)4?`;%5!C%Hg*rmUYYA+XoW^|$P057ZPHKvR3%I5EK(W3Pb3;xvVp`*3BTbaPIUJ$ zrAZx(M<=&}r#q(-*01`vQ`OVr-8qwyG;Q5mW z;3dKNHFAM$Dmj+ls#PSr0hVn-EPHfja;T0i$`Nrn@&lj>LR?0lj7JFKda{Q6^p#^K@4^9#g6#z1(t&M&&2 z4ER-iUFbtB+etUXbC=F(B>JOtP2}cbp93r(MAHMHIOb{`khlbV8S`dX2lIpIWv^$* zEpm(AuTg`lKP6G zYwcR#FaN6dK#*$+E=iK3mikbDVm_6(v^4fw!*>MR^@d@e95>swy0ODi`2|@u=Jf*c z3=n2fbbWl=S9Vwu-UFlPGKl~_Wpr;e=tv}!A3ysxnr*S4RfGk2!sr-t9-ZL2#KmPC zDhWTKU&IVNI9KBJNY)yplPKFYW=xk?c(gL)tM2ENf__sNJfQenOmWW@HF-DZ8N}pM zy_Q|WOb&fshCNFIhxy;_SbO9;4+~w5gCW-FFd0P`bENB%*ZX-DY9s=U8nahLA8nkJ zlLZ>~v!9v#G6(3GQ>~~`=-N6=TX({B%YzEoCVPZG5ai~+gUB}4W5{P!%*s%zonK!L zP-fbdG#kdcW9y3?^+J8bVwHv(7Lsda%q@*c({(f1T&^Xw(sJITyOpT2W8ZCr7+ z5}g*C2P#O1ndG-pxar_+VTFB6@mjH|AGmdskp)LtdtZ;YSb+#BZoF~#2fulKhwSpD zwvI)E<9LN@L*t7Y)OUYX?7WM(YJRP$bpln~@9&qi$fS%&rx5wILu+~k5vy^A&`RAa z7A;bz%SfuMW_)~(+WY(P!RxWP-s?~Ec(d)g(rWWort4DIjNa2k$Xi1N{+ z29oz-<|--u(|h{V1vl4-xasVBTQeNbBXKSEbO;rx8)=aq!RHMt3yKKNVH^45FOpKs z&pU`6p=A}l#U-zeis8J*eiw3XKh{y<`)toi_+ZNbP^VU@UKOE(Hn_p*5)r$qSWhKf z9ATyN!il=f&1G(2{;#yXUcW$Oj*BD59y1uZ58tjDN}c!O(kTw!-msT((D})@W_j-g zuarQ?6~v|2q$u?bdecAki*2h?R`T-lhE#zL^Fl8KMC3_Z-R|_yH`|r=!2A_wV{W*= z-o(f8+@v4Nj?y;E<$R6;=UWh!rV$9^TGRykJlE5k&07qo6Y6ay=41%Adq0Tsr?POv zR|B_xlxszv+f_Pdh5X%DR{6O9E+)`|XjmR53s$4n6|;(6H@xle9kkQ=NpgqcMW-7UsUTXyyJ*F=;XUPxS~gz}KMC0jHH{;YL}bOU1F zN+z>mT&y*9;4c2pk>o*9f#xUK9sgTFuk&JPCo?mMT?%$I(^46sCkn0~-qdf_<7Wfc zK@A7h4MYlMOIy(L;j$+h>SCf6R02iP8^qJgM8LHE(6A5_y>Ot07Si zRCH$ACDDxXtG{B8)YsTRfAOVU3JY2d)L8g0wYxtaIsp5<`~O1B!~mEJyuc3SjYe?# zd|xepidiaiLuu8GK;l#X>R#`K>evn7=d$ZW$=}WAn4_hDbX_RJJ@df~&@sso@(eju zD)0$EHqT#vUvQ^@#c*ZVyl~~SvP*fqHCxIfcy70B25zG1%OPCO3j1S2y%3KKCklo5 z{_KhzxN=rNExjar&U`yvPYV16N)dR8_*F*a|H@zx7crd=*WI+2V(dkiL^T0qtD68+ z@wIw5V`gnLp`d8BNkZaF@&r@9S4fK-AQOqb!UrbqzyA;q!E$QW9oHxhf``G_d&}t9 zN91tUs_zGp-HPTN7F{aS8jxs?G8hFjB*SXYHSFlnAG8{!ULeyELGLcPc9>=nL+6!* zXBB6@DTD2e193r|oNu$|wVN?-&hg>OrlQRj$Df>8wO!wT5`OZrqt^NT)EEfXwqPhFYamA?g8leIcTA348b4r<+_#>5fO^cw=QMgc`+FhslYULVDWhrBcBBaP zT}ZduSlR@FKUVXaR?8`<>^IHsUonH?Dd)Tx#*wl01W_ zEb_M6c#_2m@d_p}1pobeENlLmqHdhEuqJ*6yw99Y4p*ZxjHcB*Yb>!QN%o#II)JY+ns1$ar3!BcCcv-Svn|le4^KJ8 zPe7vvn1UsXf)^Yw-Q5aOw2c4YmO;nhik>!;ax83q&MQc+t`KW=)0}=t32=>Zp)R|?BdqXIB7n!`Kh``l_Wc>>l_HtDh{7XCVB|b`TEe^%B|6 z7w|C%3}oiZLWJZ5tHvY3D;x@iAlj~k<58HQHsXG5Mewy>kvRUF-MR+ax}DEoObPX8 zBP}|^cx_cacQm|})j-;`x4aS_8!gCu1GcRezOnm=v-7S3|88SA@$mNZfY|x(hp*InKz%y+lqj4{}yjJt%kWgN1L@phjG{)Iiugy5P$zjPVmWh<`i@~1{~%7n*RQW zRNw7R@|X3Y1@8?^-*Fd^=m#OuaN-zMQ-_9Bu!oa!|ouwVnvFx;Io6xZoqjUNEzlMoFK`z{&VBh?&j`W;g zI=;g;`yitIU55@<%(TIVnd2fkP!dKYKLD2ed8Gua){~O{l@m=~<#Yq{u85`Y?`H>&%W#*&Q*Ww8K4dSA ziUPQw#8+&Q8}t_KD&P8ky!TZ=e|mgv@x+2AH?$eF0eR?U=(ErkaNXMCM5q1y9vHVz z{}(Lrc*v_i>V%?A=&9#>Y@**WIbZ}SZxzqwvBVPI`_JYc`0pR@P_T7ltXjyes)C}i zlm|PYWh-b5C=IRNMF^&cN*p_S5tc?fc&w%($q zUl7t4^QJ(*;#t{?7;IO%p*2EQhS$Vt@@?;FLa0b){eCdpaMBQ8+-n8|b0peneDHT3 z5sfhWRChz}?=j;A16Eew!9oQ7^`7wUFZG}I%46PhgZ(^T4vc!~ed#;DyeO7682=QH z+)uCdO8I$2tgL;vKY~%wDTn(024^b9(&ii%t%-mR!PUSPHJ00b=b7N&&c->9>)$#2 zq+^ZXfhFJRX%>>TL%|aP1aEF6n4S(B!PK@Jt2O$d11@^o1)l0GiNR7N5J{a-3rm$W zBJW9JTi^PiLI6nmkr7UgR*cH};VbZ3|9eq38RPuF+YNG~yfZlk`vp1mU<{N3~iNmM**tMrQT z5Vp2bBX^~_?ai2l$vu>a=&CsYzq$7e%v>7QS=Zg)zAanT}0HKhFM zDodlL1*~5?)8&u-Y%uh_H($#_xhJqNOKKeD}LMrwLva5~Rgt;4!?P zoHP70zI38Ok*gjFjrwQSh)h(iDMz7H!tTP}(U)H6LHI9Dra?fLhv3ALpii~jK=WUa z=Ef2xjyn!e?#Sa`77|_!%Ids(k&ik&u7_V_nSKc<`pUMH4k^e*Nx`AB#^N|meGq+Gnuns8 zWGK5@$B$YAA<7vqtF5@P-eB?tFSKvBLzOIyDI_yrn1@bl8;8m%P#roCpsp)!+f08L zmZ3lLZWmM}78P$bE&65DrVyz0i)?#qr9A+qQjNB21O(RCvFE(4Ye@^^iKfo9gy|1z zb~h9-xwauK5q3Ga;O2}*$sl9vQf9@~fz4#nS z;DkQW{n*ET9l!C3-$0hws$xb;@Ba@6592rY>_b5JcTKg7HUjvAxlSBSr8SV7okTQZ z{X(gKY=^b*QQ4fb2O_Mr8-Zc-o!C&HpzAzHEc$!In0FI^T|)D7F^xT&1FpUHT3mJ2 zRoK358x9>AMv5MDOx;$8$EbbANwl!Qs%2!-23(tsj+^8LlcMu~?(fHOMevnpb25FLy&_!N@ zreY^(;&t;t1{oRX0Be|f^<)5x>)u~Mo%F?tC5WFBwyH24F^h+~F4I)ozaPavBpLYp z^QaJ&?j*_ZJD>a{-uvG7qEswu@9;;bium}8J8|FmINC@;q==>XVs?;fVo5QXg z$G~g~l4G(zH&n9W+pVRWwuwnBDHZXW(P8bGo}|7j*N(qR<*=_r1}wuO z*RyF0cw=Qn{I5Vruh7aS;!GjuL1JBss9_#MK_la30Io#&RAn@0J0lAZy zbRvUgO9t?VpZ+x7c;}tSPfsF4FT8tng2s6lo|?+jhLk{(?oEN->q5Gp9jb-|f!7Ni z-Kgu3Dw|-@vg#1Sn>MD<=wJXgx_LQ{)8f-6BCN~U#-u&bg^nZ4%A|IixMy?>MPK0> z8fOlWUPN;D=38#U{ylr}BC%@Dkm^772t;=e5|>Pcoy#b9A7FuMsV)=hN7fKE2~@tw7+^UoB!ejenK^dvC4{V;rI-%;I-r%konm1H@+qho~f+ zXc{Rxfp?otKU_H-gwucE8B4>#X@Gj{LVjU5DB91>K2>|uC( zQhdRBYqy-IhGuh8xR3e^FqNPyt$-zurq1$2(CXuwvc+4?`o`UNun<&_j#X-Hx!ZT-I;rQ-L3>dzEvQFmA%67v9oU*L zg3uR9+L=H3KYkZ)x$CD*GQUa{!599X^xg}Qp7qqWt_pdQnnRpIS!XVlSjzrboM9q7 zG&r-<{+MhFCY_GUfC2^}gk+wUZ{F@YTQi?+~hH(ze(F=W{r1~YCMM<6SpM%l`|(G+_vpxg9Lg$XQZ_bowM!CZZf+vUNsIQsnv1A#0WiJ*PlL_ zNaE419%KCo%rpB*?mkDL{4oNp$LMEJm`09(?34fFcX0bHx1vn15hQW(#lM3ZJED)q z^$90U=qkO#;Z!DERNd7QdN~21G?_il)`?)0nV5{9#;t+nIQxlRJ9r4MKeX3)5o+cc z;2~m}_c62L=dv^B5AK1y_ij{)8MWoI`0pS47+&|f*P%Q;g$j-H|Jr*1f4+Z^z%7L= z0ag%1oFr(x)AXK)GC52nQW_-J(7o(nXWE2p8h0tYS!qE7z(nZs;BxObEjB4zHmDYv zC<2>1P=wW(IB30}*x*mbr|}7rvOB6}lqbirWMBZSjy7+)7!~@vKRk@$pMI8v)M1?! zwKXVwK3H?ma9B?q3v=YLY@n8rC(v+{pW6c1M*8w(*U|D1JqY!cf2Nh6)Op%}=B~T& zUqAX$RCSg}A745;g3s(D)=4{bdy=zzqshM}2unyL8s+69Rh+{vBfFTiMt(ROkPO+1 zYTcQmOd}!%IjLKno_zV#q)z8s773kTZ(Cg~I zl~JYF_4f8_8{NelHlV_Z-N`Jhs{0aOeOrtG!SfWD+{(*Mt27_|b}0NJ~3;UwR(++Ls7iJe}?Hz3+Js z-ubidM45xw37G!s&{6!=&=HyjDP$zetsvZjO-_BT_zYdfeor>5Kewk=!kR)}yMIJC zl)z@tY!1}*Yk!qN;W@eqk^X^QS+ubkV)Z@iD`WD z#l1K{^SL@XfpzQFx#3!`Fq$-zj_rw(b=e|Sm39LG3BG(b!>4jaa z)|MnAJ7Z*$HQ68{r$s6v6GBPY!ps22sI1hmI#O4`A+YErDR==<^J#BMDg`S6GJskiFtK=oT+Mdb(Ihako%A~Bcq z1h!t5B%KIg%^^vsB9p^ND94D{W>qhc3?%C8-$xSRg=bOAWYJ55_#40Ze`E2Y#i)`* z*j}pOkMJBn{6gEx(d~(s}gywk}fHz z4D(m958!oEqqNJ{QT5oS;o;HlK8(|~axK9?h>%IT944%U7ZdP4I5CMo*>eyRJ~YF) zELe9b@lA;h!K5Flr;X4MLO{NtY>CY3~; zB;Aju^7!n5gE|VKjlNGkP>~cAhVA&aBpZ(dCpbKm$>{sR!9S}By!(l%2e!b(tZYy; zXOM6#3{hV#(b$~SY^*X;8?tFmIrj%;A|L{52Ixzt@a-d`_}qa(+Kho}sfeHdg?H(b z&?;A}CfQs1{9nNzI;blGA*?j6<^#i$uLQ8<@u=Ck`c9#3z|KEmzeQzg8pZJuVE
  • 3N7HLv5mXN@ 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 + + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerDoctorApplication/App.config b/ErgometerIPR/ErgometerDoctorApplication/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerDoctorApplication/Client/ChartPanel.cs b/ErgometerIPR/ErgometerDoctorApplication/Client/ChartPanel.cs new file mode 100644 index 0000000..43a0315 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Client/ChartPanel.cs @@ -0,0 +1,105 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.DataVisualization.Charting; + +namespace ErgometerDoctorApplication +{ + public class ChartPanel : Panel + { + private Chart chart; + private MetingType type; + private ChartArea chartArea; + private Series series; + private SeriesChartType ChartType; + + public ChartPanel(MetingType type, SeriesChartType charttype) : base() + { + this.type = type; + this.ChartType = charttype; + + this.chart = new Chart(); + this.chartArea = new ChartArea(); + this.chart.Titles.Add(new Title(type.ToString())); + + this.Location = new System.Drawing.Point(0, 0); + + this.Size = new System.Drawing.Size(400, 250); + this.Controls.Add(chart); + + this.series = createSerie(); + this.chartArea.Name = "chartArea"; + + this.chart.Size = new System.Drawing.Size(400, 250); + + this.chart.Dock = DockStyle.Fill; + this.chart.Series.Add(series); + this.chart.Text = "chart"; + + + this.chart.ChartAreas.Add(chartArea); + + } + + public void updateChart(List metingen) + { + chart.Series[0] = createSerie(); + for (int i = 0; i < metingen.Count; i++) + { + chart.Series[0].Points.Add(getMetingType(metingen[i])); + } + chart.Update(); + } + + public Series createSerie() + { + Series serie = new Series(); + serie.Name = "series"; + serie.ChartType = ChartType; + serie.ChartArea = "chartArea"; + serie.BorderWidth = 3; + return serie; + } + + public int getMetingType(Meting meting) + { + switch (type) + { + case MetingType.HEARTBEAT: + return meting.HeartBeat; + case MetingType.RPM: + return meting.RPM; + case MetingType.SPEED: + return (int)meting.Speed; + case MetingType.DISTANCE: + return (int)meting.Distance; + case MetingType.POWER: + return meting.Power; + case MetingType.ENERGY: + return meting.Energy; + case MetingType.SECONDS: + return meting.Seconds; + case MetingType.ACTUALPOWER: + return meting.ActualPower; + default: + return 0; + } + } + + public enum MetingType + { + HEARTBEAT, + RPM, + SPEED, + DISTANCE, + POWER, + ENERGY, + SECONDS, + ACTUALPOWER + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/Client/ClientThread.cs b/ErgometerIPR/ErgometerDoctorApplication/Client/ClientThread.cs new file mode 100644 index 0000000..92aae43 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Client/ClientThread.cs @@ -0,0 +1,78 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + public class ClientThread + { + public int Session { get; } + public string Name { get; } + + public bool IsOldData { get; } + + private SessionWindow window; + + public List Metingen { get; set; } + public List Chat { get; } + + public ClientThread(string name, int session, bool old) + { + Name = name; + Session = session; + IsOldData = old; + + window = new SessionWindow(Name, old, Session, this); + window.FormClosed += Window_FormClosed; + + Metingen = new List(); + Chat = new List(); + } + + private void Window_FormClosed(object sender, FormClosedEventArgs e) + { + MainClient.RemoveActiveClient(this); + } + + public void HandleCommand(NetCommand command) + { + switch (command.Type) + { + case NetCommand.CommandType.DATA: + lock (Metingen) + { + Metingen.Add(command.Meting); + } + window.Invoke(window.updateMetingen, new Object[] { command.Meting }); + break; + case NetCommand.CommandType.CHAT: + ChatMessage chat = new ChatMessage(command.DisplayName, command.ChatMessage, command.IsDoctor); + Chat.Add(chat); + window.panelClientChat.Invoke(window.panelClientChat.passChatMessage, new Object[] { chat }); + break; + } + } + + public void run() + { + Application.Run(window); + } + + public void stop() + { + window.Close(); + MainClient.RemoveActiveClient(this); + } + + private void sendCommand(NetCommand command) + { + if(! IsOldData) + MainClient.SendNetCommand(command); + } + + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientChat.cs b/ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientChat.cs new file mode 100644 index 0000000..3cf4c66 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientChat.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using ErgometerLibrary; +using ErgometerLibrary.Chat; + +namespace ErgometerDoctorApplication +{ + public class PanelClientChat : Panel + { + + + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Panel panel1; + public System.Windows.Forms.Button button1; + public System.Windows.Forms.RichTextBox richTextBox1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Panel panel3; + private System.Windows.Forms.Label connectionLabel; + + private int Session { get; } + + public PanelClientChat(int session, string name) : base() + { + this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.panel2 = new System.Windows.Forms.Panel(); + this.connectionLabel = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.panel1 = new System.Windows.Forms.Panel(); + this.button1 = new System.Windows.Forms.Button(); + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + + Session = session; + + this.panel3 = new System.Windows.Forms.Panel(); + // + // flowLayoutPanel1 + // + this.flowLayoutPanel1.AutoSize = true; + this.flowLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; + this.flowLayoutPanel1.Controls.Add(this.panel2); + this.flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; + this.flowLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.flowLayoutPanel1.Name = "flowLayoutPanel1"; + this.flowLayoutPanel1.Padding = new System.Windows.Forms.Padding(3); + this.flowLayoutPanel1.Size = new System.Drawing.Size(390, 36); + this.flowLayoutPanel1.TabIndex = 0; + this.flowLayoutPanel1.WrapContents = false; + this.flowLayoutPanel1.SizeChanged += new System.EventHandler(this.FlowLayoutPanel1_SizeChanged); + // + // panel2 + // + this.panel2.Controls.Add(this.connectionLabel); + this.panel2.Dock = System.Windows.Forms.DockStyle.Left; + this.panel2.Location = new System.Drawing.Point(6, 6); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(385, 24); + this.panel2.TabIndex = 0; + // + // connectionLabel + // + this.connectionLabel.AutoSize = true; + this.connectionLabel.Font = new System.Drawing.Font("Segoe UI Semibold", 8F, System.Drawing.FontStyle.Bold); + this.connectionLabel.Location = new System.Drawing.Point(4, 4); + this.connectionLabel.Name = "connectionLabel"; + this.connectionLabel.Size = new System.Drawing.Size(112, 13); + this.connectionLabel.TabIndex = 0; + this.connectionLabel.Text = "Now connected with: " + name; + // + // label2 + // + this.label2.Location = new System.Drawing.Point(0, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(100, 23); + this.label2.TabIndex = 0; + // + // label1 + // + this.label1.Location = new System.Drawing.Point(0, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(100, 23); + this.label1.TabIndex = 0; + // + // panel1 + // + this.panel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.panel1.Controls.Add(this.button1); + this.panel1.Controls.Add(this.richTextBox1); + this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(400, 100); + this.panel1.TabIndex = 1; + // + // button1 + // + this.button1.BackColor = System.Drawing.Color.White; + this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.button1.Location = new System.Drawing.Point(310, 4); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(85, 90); + this.button1.TabIndex = 1; + this.button1.Text = "Send"; + this.button1.UseVisualStyleBackColor = false; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // richTextBox1 + // + this.richTextBox1.BackColor = System.Drawing.Color.White; + this.richTextBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.richTextBox1.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.richTextBox1.Location = new System.Drawing.Point(4, 4); + this.richTextBox1.MaxLength = 250; + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.None; + this.richTextBox1.Size = new System.Drawing.Size(300, 90); + this.richTextBox1.TabIndex = 0; + this.richTextBox1.Text = ""; + this.richTextBox1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.TextBox_KeyDown); + // + // panel3 + // + this.panel3.AutoScroll = true; + this.panel3.HorizontalScroll.Enabled = false; + this.panel3.HorizontalScroll.Visible = false; + this.panel3.BackColor = System.Drawing.Color.White; + this.panel3.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel3.Controls.Add(this.flowLayoutPanel1); + this.panel3.Location = new System.Drawing.Point(0, 0); + + this.panel3.Name = "panel3"; + this.panel3.TabIndex = 1; + // + // container + // + this.Controls.Add(this.panel3); + this.Controls.Add(this.panel1); + this.Dock = System.Windows.Forms.DockStyle.Fill; + this.Location = new System.Drawing.Point(0, 0); + this.Name = "container"; + this.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.panel3_MouseWheel); + + passChatMessage = new ChatDelegate(this.AddChatItem); + } + + private void button1_Click(object sender, EventArgs e) + { + if (richTextBox1.TextLength > 1) + { + AddChatItem(new ChatMessage("Doctor", richTextBox1.Text, true)); + MainClient.SendNetCommand(new NetCommand(richTextBox1.Text, true, Session)); + richTextBox1.ResetText(); + } + } + + public void AddChatItem(ChatMessage chat) + { + flowLayoutPanel1.Controls.Add(new ChatItem(chat)); + } + + private void panel3_MouseWheel(object sender, MouseEventArgs e) + { + panel3.Focus(); + } + + private void FlowLayoutPanel1_SizeChanged(object sender, System.EventArgs e) + { + if (flowLayoutPanel1.Size.Height > panel3.Size.Height) + { + panel2.Width = 375; + flowLayoutPanel1.Width = 380; + panel3.VerticalScroll.Value = panel3.VerticalScroll.Maximum; + } + } + + private bool textBoxIsEmpty() + { + string[] text = richTextBox1.Text.Split(); + foreach (string s in text) + { + if (s != " ") + { + return false; + } + } + return true; + } + + private void TextBox_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + button1_Click(this, new EventArgs()); + e.Handled = true; + } + } + + public delegate void ChatDelegate(ChatMessage chat); + public ChatDelegate passChatMessage; + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientData.cs b/ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientData.cs new file mode 100644 index 0000000..076d6cb --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientData.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + public class PanelClientData : Panel + { + public Label labelMetingCurrentValue; + public ProgressBar progressBarMeting; + public Label metingName; + + private int min; + private int max; + private string name; + + public PanelClientData(string name, int min, int max) : base() + { + this.min = min; + this.max = max; + this.name = name; + + this.metingName = new System.Windows.Forms.Label(); + this.progressBarMeting = new System.Windows.Forms.ProgressBar(); + this.labelMetingCurrentValue = new System.Windows.Forms.Label(); + // + // initialize panel + // + this.BackColor = System.Drawing.SystemColors.ControlLightLight; + this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.Controls.Add(this.labelMetingCurrentValue); + if (name != "Tijd") + this.Controls.Add(this.progressBarMeting); + this.Controls.Add(this.metingName); + this.Dock = System.Windows.Forms.DockStyle.Top; + this.Location = new System.Drawing.Point(0, 0); + this.Name = "panel1"; + this.Size = new System.Drawing.Size(284, 80); + // + // metingName + // + this.metingName.AutoSize = true; + this.metingName.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.metingName.Location = new System.Drawing.Point(12, 9); + this.metingName.Name = "metingName"; + this.metingName.Size = new System.Drawing.Size(105, 21); + this.metingName.TabIndex = 0; + this.metingName.Text = name; + // + // progressBarMeting + // + this.progressBarMeting.Location = new System.Drawing.Point(16, 39); + this.progressBarMeting.Name = "progressBarMeting"; + this.progressBarMeting.Size = new System.Drawing.Size(183, 23); + this.progressBarMeting.TabIndex = 1; + this.progressBarMeting.Value = 0; + // + // labelMetingCurrentValue + // + this.labelMetingCurrentValue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.labelMetingCurrentValue.AutoSize = true; + this.labelMetingCurrentValue.Font = new System.Drawing.Font("Segoe UI", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelMetingCurrentValue.Location = new System.Drawing.Point(215, 32); + this.labelMetingCurrentValue.Name = "labelMetingCurrentValue"; + this.labelMetingCurrentValue.Size = new System.Drawing.Size(57, 32); + this.labelMetingCurrentValue.TabIndex = 2; + this.labelMetingCurrentValue.Text = "0"; + } + + public void setText(string text) + { + this.metingName.Text = text; + } + + public void updateValue(int value) + { + if (name == "Tijd") + { + this.labelMetingCurrentValue.Text = (value / 60) + ":" + (value % 60); + } + else + { + this.labelMetingCurrentValue.Text = value.ToString(); + this.progressBarMeting.Value = ValueToPercentage(value); + } + + } + + public void updateValue(double value) + { + this.labelMetingCurrentValue.Text = value.ToString(); + this.progressBarMeting.Value = ValueToPercentage((int)value); + } + + public void updateValue(decimal value) + { + this.labelMetingCurrentValue.Text = value.ToString(); + this.progressBarMeting.Value = ValueToPercentage((int)value); + } + + private int ValueToPercentage(int value) + { + if (value < min) + return 0; + + if (value > max) + return 100; + + return ((value - min) * 100) / (max - min); + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientSetData.cs b/ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientSetData.cs new file mode 100644 index 0000000..cdb4e31 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Client/PanelClientSetData.cs @@ -0,0 +1,287 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication.Client +{ + public class PanelClientSetData : Panel + { + + private Panel panel6; + private Label label3; + private Panel panel5; + private Label label4; + private Panel panel3; + private Label label2; + private Panel panel2; + private Label label1; + private Button buttonTrapsnelheid; + private TrackBar trackBar1; + private Label label6; + private Button buttonAfstand; + private TextBox textBox2; + private Label label5; + private Button buttonTijd; + private MaskedTextBox textBox1; + + private int Session; + + public PanelClientSetData(int session) : base() + { + Session = session; + this.panel6 = new System.Windows.Forms.Panel(); + this.label3 = new System.Windows.Forms.Label(); + this.panel5 = new System.Windows.Forms.Panel(); + this.label4 = new System.Windows.Forms.Label(); + this.panel3 = new System.Windows.Forms.Panel(); + this.label2 = new System.Windows.Forms.Label(); + this.panel2 = new System.Windows.Forms.Panel(); + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.MaskedTextBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.trackBar1 = new System.Windows.Forms.TrackBar(); + this.buttonTijd = new System.Windows.Forms.Button(); + this.buttonAfstand = new System.Windows.Forms.Button(); + this.label5 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.buttonTrapsnelheid = new System.Windows.Forms.Button(); + this.panel6.SuspendLayout(); + this.panel5.SuspendLayout(); + this.panel3.SuspendLayout(); + this.panel2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trackBar1)).BeginInit(); + this.SuspendLayout(); + // + // panel1 + // + + // + // panel6 + // + this.panel6.BackColor = System.Drawing.Color.White; + this.panel6.Controls.Add(this.buttonTrapsnelheid); + this.panel6.Controls.Add(this.trackBar1); + this.panel6.Controls.Add(this.label3); + this.panel6.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel6.Location = new System.Drawing.Point(0, 199); + this.panel6.Name = "panel6"; + this.panel6.Size = new System.Drawing.Size(370, 112); + this.panel6.TabIndex = 4; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Font = new System.Drawing.Font("Segoe UI Semibold", 10F, System.Drawing.FontStyle.Bold); + this.label3.Location = new System.Drawing.Point(4, 5); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(146, 19); + this.label3.TabIndex = 0; + this.label3.Text = "Trapsterkte aanpassen"; + // + // panel5 + // + this.panel5.BackColor = System.Drawing.Color.White; + this.panel5.Controls.Add(this.label6); + this.panel5.Controls.Add(this.buttonAfstand); + this.panel5.Controls.Add(this.textBox2); + this.panel5.Controls.Add(this.label4); + this.panel5.Dock = System.Windows.Forms.DockStyle.Top; + this.panel5.Location = new System.Drawing.Point(0, 124); + this.panel5.Name = "panel5"; + this.panel5.Size = new System.Drawing.Size(370, 75); + this.panel5.TabIndex = 3; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Font = new System.Drawing.Font("Segoe UI Semibold", 10F, System.Drawing.FontStyle.Bold); + this.label4.Location = new System.Drawing.Point(4, 26); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(114, 19); + this.label4.TabIndex = 0; + this.label4.Text = "Afstand instellen"; + // + // panel3 + // + this.panel3.BackColor = System.Drawing.Color.White; + this.panel3.Controls.Add(this.label5); + this.panel3.Controls.Add(this.buttonTijd); + this.panel3.Controls.Add(this.textBox1); + this.panel3.Controls.Add(this.label2); + this.panel3.Dock = System.Windows.Forms.DockStyle.Top; + this.panel3.Font = new System.Drawing.Font("Tunga", 8.25F); + this.panel3.Location = new System.Drawing.Point(0, 52); + this.panel3.Name = "panel3"; + this.panel3.Size = new System.Drawing.Size(370, 72); + this.panel3.TabIndex = 1; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Font = new System.Drawing.Font("Segoe UI Semibold", 10F, System.Drawing.FontStyle.Bold); + this.label2.Location = new System.Drawing.Point(3, 23); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(101, 19); + this.label2.TabIndex = 0; + this.label2.Text = "Tijd aanpassen"; + // + // panel2 + // + this.panel2.BackColor = System.Drawing.SystemColors.ControlDarkDark; + this.panel2.Controls.Add(this.label1); + this.panel2.Dock = System.Windows.Forms.DockStyle.Top; + this.panel2.Location = new System.Drawing.Point(0, 0); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(500, 52); + this.panel2.TabIndex = 0; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label1.ForeColor = System.Drawing.Color.White; + this.label1.Location = new System.Drawing.Point(3, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(247, 21); + this.label1.TabIndex = 0; + this.label1.Text = "Eigenschappen fiets aanpassen"; + // + // textBox1 + // + this.textBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.textBox1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.textBox1.Location = new System.Drawing.Point(123, 21); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(77, 25); + this.textBox1.TabIndex = 1; + this.textBox1.Mask = "00:00"; + this.textBox1.KeyDown += TextBox1_KeyDown; + // + // textBox2 + // + this.textBox2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.textBox2.Font = new System.Drawing.Font("Segoe UI", 9.75F); + this.textBox2.Location = new System.Drawing.Point(124, 24); + this.textBox2.Name = "textBox2"; + this.textBox2.Size = new System.Drawing.Size(77, 25); + this.textBox2.TabIndex = 1; + this.textBox2.KeyDown += TextBox2_KeyDown; + // + // trackBar1 + // + this.trackBar1.Location = new System.Drawing.Point(29, 42); + this.trackBar1.Maximum = 400; + this.trackBar1.Minimum = 25; + this.trackBar1.Name = "trackBar1"; + this.trackBar1.Size = new System.Drawing.Size(221, 45); + this.trackBar1.TabIndex = 1; + this.trackBar1.Value = 25; + // + // button1 + // + this.buttonTijd.BackColor = System.Drawing.Color.WhiteSmoke; + this.buttonTijd.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonTijd.Font = new System.Drawing.Font("Segoe UI", 9.75F); + this.buttonTijd.Location = new System.Drawing.Point(282, 19); + this.buttonTijd.Name = "button1"; + this.buttonTijd.Size = new System.Drawing.Size(75, 25); + this.buttonTijd.TabIndex = 2; + this.buttonTijd.Text = "Set"; + this.buttonTijd.UseVisualStyleBackColor = false; + this.buttonTijd.Click += new System.EventHandler(this.buttonTijd_Click); + // + // button2 + // + this.buttonAfstand.BackColor = System.Drawing.Color.WhiteSmoke; + this.buttonAfstand.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonAfstand.Font = new System.Drawing.Font("Segoe UI", 9.75F); + this.buttonAfstand.Location = new System.Drawing.Point(283, 24); + this.buttonAfstand.Name = "button2"; + this.buttonAfstand.Size = new System.Drawing.Size(75, 25); + this.buttonAfstand.TabIndex = 2; + this.buttonAfstand.Text = "Set"; + this.buttonAfstand.UseVisualStyleBackColor = false; + this.buttonAfstand.Click += new System.EventHandler(this.buttonAfstand_Click); + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label5.Location = new System.Drawing.Point(207, 26); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(23, 13); + this.label5.TabIndex = 3; + this.label5.Text = "sec"; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(208, 30); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(15, 13); + this.label6.TabIndex = 3; + this.label6.Text = "m"; + // + // button3 + // + this.buttonTrapsnelheid.BackColor = System.Drawing.Color.WhiteSmoke; + this.buttonTrapsnelheid.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonTrapsnelheid.Location = new System.Drawing.Point(283, 42); + this.buttonTrapsnelheid.Name = "button3"; + this.buttonTrapsnelheid.Size = new System.Drawing.Size(75, 25); + this.buttonTrapsnelheid.TabIndex = 2; + this.buttonTrapsnelheid.Text = "Set"; + this.buttonTrapsnelheid.UseVisualStyleBackColor = false; + this.buttonTrapsnelheid.Click += new System.EventHandler(this.buttonTrapsnelheid_Click); + + Controls.Add(this.panel6); + Controls.Add(this.panel5); + Controls.Add(this.panel3); + Controls.Add(this.panel2); + Dock = System.Windows.Forms.DockStyle.Fill; + Location = new System.Drawing.Point(0, 0); + BackColor = System.Drawing.Color.White; + Name = "panel1"; + Size = new System.Drawing.Size(370, 400); + + } + + private void TextBox2_KeyDown(object sender, KeyEventArgs e) + { + if(e.KeyCode == Keys.Enter) + { + buttonAfstand_Click(this, new EventArgs()); + } + } + + private void TextBox1_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + buttonTijd_Click(this, new EventArgs()); + } + } + + private void buttonTrapsnelheid_Click(object sender, EventArgs e) + { + MainClient.SendNetCommand(new NetCommand(NetCommand.ValueType.POWER, trackBar1.Value, Session)); + } + + private void buttonAfstand_Click(object sender, EventArgs e) + { + MainClient.SendNetCommand(new NetCommand(NetCommand.ValueType.DISTANCE, int.Parse(textBox2.Text), Session)); + textBox2.Text = ""; + } + + private void buttonTijd_Click(object sender, EventArgs e) + { + int seconds = (int.Parse(textBox1.Text.Split(':')[0]) * 60) + int.Parse(textBox1.Text.Split(':')[1]); + MainClient.SendNetCommand(new NetCommand(NetCommand.ValueType.TIME, seconds, Session)); + textBox1.Text = ""; + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/Client/PanelGraphView.cs b/ErgometerIPR/ErgometerDoctorApplication/Client/PanelGraphView.cs new file mode 100644 index 0000000..119c367 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Client/PanelGraphView.cs @@ -0,0 +1,71 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.DataVisualization.Charting; + +namespace ErgometerDoctorApplication +{ + public class PanelGraphView : Panel + { + + private List charts; + private FlowLayoutPanel flowlayout; + + public PanelGraphView() : base() + { + createCharts(); + + flowlayout = new FlowLayoutPanel(); + + flowlayout.Dock = DockStyle.Fill; + flowlayout.BackColor = System.Drawing.Color.DarkGray; + flowlayout.Location = new System.Drawing.Point(0, 0); + flowlayout.Name = "flowlayout"; + flowlayout.Padding = new Padding(15); + flowlayout.AutoScroll = true; + foreach (ChartPanel chart in charts) + { + flowlayout.Controls.Add(chart); + } + + List metingen = new List(); + metingen.Add(new Meting(0, 0, 0, 0, 0, 0, 0, 0, 0)); + + updateAllCharts(metingen); + // + // panelGraphView + // + this.Dock = System.Windows.Forms.DockStyle.Fill; + this.Controls.Add(flowlayout); + + this.Location = new System.Drawing.Point(0, 0); + this.Name = "panelGraphView"; + this.Size = new System.Drawing.Size(400, 600); + this.TabIndex = 1; + } + + public void createCharts() + { + charts = new List(); + charts.Add(new ChartPanel(ChartPanel.MetingType.HEARTBEAT, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.RPM, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.SPEED, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.DISTANCE, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.ENERGY, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.POWER, SeriesChartType.Line)); + charts.Add(new ChartPanel(ChartPanel.MetingType.ACTUALPOWER, SeriesChartType.Line)); + } + + public void updateAllCharts(List metingen) + { + foreach (ChartPanel chart in charts) + { + chart.updateChart(metingen); + } + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.Designer.cs b/ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.Designer.cs new file mode 100644 index 0000000..60a8354 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.Designer.cs @@ -0,0 +1,175 @@ +using ErgometerDoctorApplication.Client; +using System; + +namespace ErgometerDoctorApplication +{ + partial class SessionWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.SuspendLayout(); + + + this.components = new System.ComponentModel.Container(); + this.panelClientContainer = new System.Windows.Forms.Panel(); + this.panelDataViewLeft = new System.Windows.Forms.Panel(); + this.panelClientSetData = new PanelClientSetData(Session); + this.panelGraphView = new ErgometerDoctorApplication.PanelGraphView(); + this.panelClientChat = new ErgometerDoctorApplication.PanelClientChat(Session, ClientName); + this.panelTopBar = new System.Windows.Forms.Panel(); + this.labelUsername = new System.Windows.Forms.Label(); + this.labelHallo = new System.Windows.Forms.Label(); + + this.heartBeat = new PanelClientData("Hartslag", 50, 220); + this.RPM = new PanelClientData("RPM", 0, 120); + this.speed = new PanelClientData("Snelheid", 0, 50); + this.distance = new PanelClientData("Afstand (km)", 0, 100); + this.power = new PanelClientData("Weerstand", 25, 400); + this.energy = new PanelClientData("Energie", 0, 200); + this.actualpower = new PanelClientData("Absolute Weerstand", 0, 400); + this.time = new PanelClientData("Tijd", 0, 400); + + this.panelClientContainer.SuspendLayout(); + this.panelTopBar.SuspendLayout(); + // + // panelClientContainer + // + this.panelClientContainer.Controls.Add(this.panelGraphView); + this.panelClientContainer.Controls.Add(this.panelDataViewLeft); + + this.panelClientContainer.Controls.Add(this.panelClientChat); + this.panelClientContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelClientContainer.Location = new System.Drawing.Point(0, 0); + this.panelClientContainer.Name = "panelClientContainer"; + this.panelClientContainer.Size = new System.Drawing.Size(800, 600); + this.panelClientContainer.TabIndex = 0; + // + // panelDataViewLeft + // + //this.panelDataViewLeft.Dock = System.Windows.Forms.DockStyle.Left; + this.panelDataViewLeft.Location = new System.Drawing.Point(0, 0); + this.panelDataViewLeft.Name = "panelDataViewLeft"; + this.panelDataViewLeft.Size = new System.Drawing.Size(18, 600); + this.panelDataViewLeft.TabIndex = 3; + this.panelDataViewLeft.BackColor = System.Drawing.Color.Gray; + this.panelDataViewLeft.Controls.Add(panelClientSetData); + this.panelDataViewLeft.Controls.Add(heartBeat); + this.panelDataViewLeft.Controls.Add(RPM); + this.panelDataViewLeft.Controls.Add(speed); + this.panelDataViewLeft.Controls.Add(distance); + this.panelDataViewLeft.Controls.Add(power); + this.panelDataViewLeft.Controls.Add(energy); + this.panelDataViewLeft.Controls.Add(actualpower); + this.panelDataViewLeft.Controls.Add(time); + + + // + // panelClientChat + // + this.panelClientChat.BackColor = System.Drawing.SystemColors.ButtonHighlight; + this.panelClientChat.Dock = System.Windows.Forms.DockStyle.Right; + this.panelClientChat.Location = new System.Drawing.Point(400, 0); + this.panelClientChat.Name = "panelClientChat"; + this.panelClientChat.Size = new System.Drawing.Size(400, 600); + this.panelClientChat.TabIndex = 2; + // + // panelTopBar + // + this.panelTopBar.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.panelTopBar.Controls.Add(this.labelUsername); + this.panelTopBar.Controls.Add(this.labelHallo); + this.panelTopBar.Dock = System.Windows.Forms.DockStyle.Top; + this.panelTopBar.Location = new System.Drawing.Point(0, 0); + this.panelTopBar.Name = "panelTopBar"; + this.panelTopBar.Size = new System.Drawing.Size(800, 30); + this.panelTopBar.TabIndex = 1; + this.panelTopBar.Visible = false; + // + // labelUsername + // + this.labelUsername.AutoSize = true; + this.labelUsername.Font = new System.Drawing.Font("Segoe UI Semilight", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelUsername.ForeColor = System.Drawing.Color.White; + this.labelUsername.Location = new System.Drawing.Point(49, 3); + this.labelUsername.Name = "labelUsername"; + this.labelUsername.Size = new System.Drawing.Size(144, 21); + this.labelUsername.TabIndex = 0; + this.labelUsername.Text = ""; + // + // labelHallo + // + this.labelHallo.AutoSize = true; + this.labelHallo.Font = new System.Drawing.Font("Segoe UI Semilight", 12F); + this.labelHallo.ForeColor = System.Drawing.Color.White; + this.labelHallo.Location = new System.Drawing.Point(3, 3); + this.labelHallo.Name = "labelHallo"; + this.labelHallo.Size = new System.Drawing.Size(54, 21); + this.labelHallo.TabIndex = 0; + this.labelHallo.Text = "Hallo, "; + // + // SessionWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 600); + this.Controls.Add(this.panelTopBar); + this.Controls.Add(this.panelClientContainer); + this.Name = "SessionWindow"; + this.Text = "Sessie: " + ClientName; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Resize += new System.EventHandler(this.ClientApplicatie_Resize); + this.panelClientContainer.ResumeLayout(false); + this.panelTopBar.ResumeLayout(false); + this.panelTopBar.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.Panel panelClientContainer; + public PanelClientChat panelClientChat; + public PanelGraphView panelGraphView; + private System.Windows.Forms.Panel panelDataViewLeft; + private System.Windows.Forms.Panel panelTopBar; + private System.Windows.Forms.Label labelUsername; + private System.Windows.Forms.Label labelHallo; + public PanelClientData heartBeat, RPM, speed, distance, power, energy, seconds, actualpower, time; + public PanelClientSetData panelClientSetData; + + private void createTitle() + { + if (ActiveSession) + { + this.Text = "Actieve Sessie: " + ClientName; + } + else + { + this.Text = "Afgelopen Sessie: " + ClientName; + } + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.cs b/ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.cs new file mode 100644 index 0000000..43bb154 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.cs @@ -0,0 +1,103 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + public partial class SessionWindow : Form + { + + private bool ActiveSession { get; } + private string ClientName { get; set; } + public int Session { get; } + + private ClientThread client; + + private int count; + + public delegate void UpdateMetingen(Meting m); + public UpdateMetingen updateMetingen; + + public delegate void UpdateGraph(); + public UpdateGraph updateGraph; + + public SessionWindow(string ClientName, bool old, int session, ClientThread parentthread) + { + this.ActiveSession = !old; + this.ClientName = ClientName; + this.client = parentthread; + Session = session; + + count = 0; + + updateMetingen = new UpdateMetingen(this.SaveMeting); + updateGraph = new UpdateGraph(this.LoadGraph); + + InitializeComponent(); + + if(! ActiveSession) + { + panelClientChat.richTextBox1.Enabled = false; + panelClientChat.button1.Enabled = false; + panelDataViewLeft.Visible = false; + } + } + + public void ClientApplicatie_Resize(object sender, EventArgs e) + { + Control control = (Control)sender; + if (control.Size.Width < 980) + { + panelGraphView.Visible = false; + panelClientChat.Width = 400; + panelDataViewLeft.Dock = DockStyle.Fill; + } + if (control.Size.Width >= 980 && control.Size.Width < 1368) + { + panelGraphView.Visible = true; + panelDataViewLeft.Width = 250; + panelClientChat.Width = 400; + panelDataViewLeft.Dock = DockStyle.Left; + } + if (control.Size.Width >= 1368) + { + panelGraphView.Visible = true; + panelDataViewLeft.Width = 400; + panelDataViewLeft.Dock = DockStyle.Left; + } + } + + public void SaveMeting(Meting m) + { + + heartBeat.updateValue(m.HeartBeat); + RPM.updateValue(m.RPM); + speed.updateValue(m.Speed*100); + distance.updateValue(m.Distance*100); + power.updateValue(m.Power); + energy.updateValue(m.Energy); + actualpower.updateValue(m.ActualPower); + time.updateValue(m.Seconds); + + if (count >= 10) + { + count = 0; + panelGraphView.updateAllCharts(client.Metingen); + } + + count++; + } + + public void LoadGraph() + { + panelGraphView.updateAllCharts(client.Metingen); + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.resx b/ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Client/SessionWindow.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerDoctorApplication/ConActiveSessions.cs b/ErgometerIPR/ErgometerDoctorApplication/ConActiveSessions.cs new file mode 100644 index 0000000..e8389cc --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/ConActiveSessions.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.AccessControl; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + public class ConActiveSessions : Panel + { + + private FlowLayoutPanel flowlayout; + + public ConActiveSessions() : base() + { + labelActiveSessions = new Label(); + // + // ConActiveSessions + // + this.Dock = System.Windows.Forms.DockStyle.Fill; + this.Location = new System.Drawing.Point(0, 0); + this.Name = "ConActiveSessions"; + this.Size = new System.Drawing.Size(584, 459); + this.TabIndex = 0; + // + // labelActiveSessions + // + this.labelActiveSessions.Anchor = System.Windows.Forms.AnchorStyles.Top | AnchorStyles.Left; + this.labelActiveSessions.AutoSize = true; + this.labelActiveSessions.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelActiveSessions.Location = new System.Drawing.Point(20, 20); + this.labelActiveSessions.Name = "labelActiveSessions"; + this.labelActiveSessions.Size = new System.Drawing.Size(103, 21); + this.labelActiveSessions.TabIndex = 3; + this.labelActiveSessions.Text = "Er zijn geen actieve sessies."; + + flowlayout = new FlowLayoutPanel(); + + flowlayout.Dock = DockStyle.Fill; + flowlayout.BackColor = System.Drawing.Color.WhiteSmoke; + flowlayout.Location = new System.Drawing.Point(0, 0); + flowlayout.Name = "flowlayout"; + flowlayout.Padding = new Padding(15); + flowlayout.AutoScroll = true; + + this.Controls.Add(labelActiveSessions); + this.Controls.Add(flowlayout); + + //this.Controls.Add(data); + + updateActiveSessions(MainClient.activesessions); + + } + + public System.Windows.Forms.Label labelActiveSessions; + + public void updateActiveSessions(Dictionary actives) + { + flowlayout.Controls.Clear(); + + foreach (KeyValuePair pair in actives) + { + + flowlayout.Controls.Add(new SessionPanel(pair.Key, pair.Value, true,0)); + } + } + + /* + private void data_CellContentClick(object sender, DataGridViewCellEventArgs e) + { + if (data.Rows[e.RowIndex].Cells[1].Value != null) + { + MainClient.StartNewCLient(data.Rows[e.RowIndex].Cells[0].Value + "", int.Parse(data.Rows[e.RowIndex].Cells[1].Value + "")); + } + } + + public System.Windows.Forms.DataGridViewTextBoxColumn name; + public System.Windows.Forms.DataGridViewTextBoxColumn sessionId; + public System.Windows.Forms.DataGridView data; + */ + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/ConClientBroadcast.cs b/ErgometerIPR/ErgometerDoctorApplication/ConClientBroadcast.cs new file mode 100644 index 0000000..6013746 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/ConClientBroadcast.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + public class ConClientBroadcast : Panel + { + + public ConClientBroadcast() : base() + { + + this.Dock = System.Windows.Forms.DockStyle.Fill; + this.Location = new System.Drawing.Point(0, 0); + this.Name = "ConClientBroadcast"; + this.Size = new System.Drawing.Size(584, 459); + this.TabIndex = 0; + + buttonBroadcast = new Button(); + textBoxBroadcast = new TextBox(); + + // + // buttonLogin + // + this.buttonBroadcast.Anchor = AnchorStyles.Left | AnchorStyles.Top; + this.buttonBroadcast.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonBroadcast.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.buttonBroadcast.Location = new System.Drawing.Point(200, 20); + this.buttonBroadcast.Name = "buttonCreate"; + this.buttonBroadcast.Size = new System.Drawing.Size(168, 31); + this.buttonBroadcast.TabIndex = 3; + this.buttonBroadcast.Text = "Broadcast"; + this.buttonBroadcast.UseVisualStyleBackColor = true; + this.buttonBroadcast.Click += new System.EventHandler(this.buttonBroadcast_Click); + // + // textBoxPassword + // + this.textBoxBroadcast.Anchor = AnchorStyles.Left | AnchorStyles.Top; + this.textBoxBroadcast.Location = new System.Drawing.Point(20, 20); + this.textBoxBroadcast.MaxLength = 16; + this.textBoxBroadcast.Name = "textBoxPassword"; + this.textBoxBroadcast.Size = new System.Drawing.Size(167, 20); + this.textBoxBroadcast.TabIndex = 2; + this.textBoxBroadcast.KeyDown += TextBoxBroadcast_KeyDown; + + this.Controls.Add(textBoxBroadcast); + this.Controls.Add(buttonBroadcast); + + + } + + private void TextBoxBroadcast_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + buttonBroadcast_Click(this, new EventArgs()); + } + } + + private void buttonBroadcast_Click(object sender, EventArgs e) + { + MainClient.SendNetCommand(new ErgometerLibrary.NetCommand(textBoxBroadcast.Text, MainClient.Session)); + textBoxBroadcast.Text = ""; + } + + public System.Windows.Forms.TextBox textBoxBroadcast; + public System.Windows.Forms.Button buttonBroadcast; + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/ConClientData.cs b/ErgometerIPR/ErgometerDoctorApplication/ConClientData.cs new file mode 100644 index 0000000..293d3b4 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/ConClientData.cs @@ -0,0 +1,150 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Security.AccessControl; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + public class ConClientData : Panel + { + public System.Windows.Forms.TextBox textBoxPassword; + public System.Windows.Forms.TextBox textBoxUsername; + public System.Windows.Forms.Button buttonCreate; + public System.Windows.Forms.ListBox listUsers; + public System.Windows.Forms.Label newUsername; + public System.Windows.Forms.Label newPassword; + + public ConClientData() : base() + { + this.buttonCreate = new System.Windows.Forms.Button(); + this.textBoxPassword = new System.Windows.Forms.TextBox(); + this.textBoxUsername = new System.Windows.Forms.TextBox(); + this.listUsers = new System.Windows.Forms.ListBox(); + this.newUsername = new System.Windows.Forms.Label(); + this.newPassword = new System.Windows.Forms.Label(); + + // + // buttonLogin + // + this.buttonCreate.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.buttonCreate.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonCreate.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.buttonCreate.Location = new System.Drawing.Point(20, 215); + this.buttonCreate.Name = "buttonCreate"; + this.buttonCreate.Size = new System.Drawing.Size(168, 31); + this.buttonCreate.TabIndex = 3; + this.buttonCreate.Text = "Aanmaken"; + this.buttonCreate.BackColor = Color.LightGray; + this.buttonCreate.Click += new System.EventHandler(this.buttonCreate_Click); + // + // textBoxPassword + // + this.textBoxPassword.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.textBoxPassword.Location = new System.Drawing.Point(170, 180); + this.textBoxPassword.MaxLength = 16; + this.textBoxPassword.Name = "textBoxPassword"; + this.textBoxPassword.Size = new System.Drawing.Size(167, 60); + this.textBoxPassword.TabIndex = 2; + this.textBoxPassword.KeyDown += TextBoxEnterPress; + // + // textBoxUsername + // + this.textBoxUsername.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.textBoxUsername.Location = new System.Drawing.Point(170, 150); + this.textBoxUsername.MaxLength = 16; + this.textBoxUsername.Name = "textBoxUsername"; + this.textBoxUsername.Size = new System.Drawing.Size(167, 20); + this.textBoxUsername.TabIndex = 2; + this.textBoxUsername.KeyDown += TextBoxEnterPress; + // + // listUsers + // + this.listUsers.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.listUsers.Location = new System.Drawing.Point(20, -150); + this.listUsers.Name = "listUsers"; + this.listUsers.Size = new System.Drawing.Size(200, 280); + this.listUsers.Items.Add("Geen gebruikers"); + // + // newPassword + // + this.newPassword.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.newPassword.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.newPassword.ForeColor = Color.Black; + this.newPassword.Location = new System.Drawing.Point(20, 182); + this.newPassword.Name = "newPassword"; + this.newPassword.Size = new System.Drawing.Size(167, 20); + this.newPassword.TabIndex = 3; + this.newPassword.Text = "Nieuw wachtwoord"; + // + // newUsername + // + this.newUsername.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.newUsername.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.newUsername.Location = new System.Drawing.Point(20, 152); + this.newUsername.Name = "newUsername"; + this.newUsername.Size = new System.Drawing.Size(167, 20); + this.newUsername.TabIndex = 3; + this.newUsername.Text = "Nieuwe gebruikersnaam"; + + this.Controls.Add(this.listUsers); + this.Controls.Add(this.buttonCreate); + this.Controls.Add(this.textBoxUsername); + this.Controls.Add(this.textBoxPassword); + this.Controls.Add(this.newUsername); + this.Controls.Add(this.newPassword); + // + // ConClientData + // + this.Dock = System.Windows.Forms.DockStyle.Fill; + this.Location = new System.Drawing.Point(20, 0); + this.Name = "ConClientData"; + this.Size = new System.Drawing.Size(584, 459); + this.TabIndex = 0; + + + + this.BackColor = System.Drawing.Color.White; + } + + private void TextBoxEnterPress(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + buttonCreate_Click(this, new EventArgs()); + } + } + + internal void updateUsers(Dictionary users) + { + this.listUsers.Items.Clear(); + + foreach(KeyValuePair user in users) + { + this.listUsers.Items.Add(user.Key + ": " + user.Value); + } + } + + private void buttonCreate_Click(object sender, EventArgs e) + { + if (!MainClient.users.ContainsKey(textBoxUsername.Text)) + { + MainClient.SendNetCommand(new NetCommand(textBoxUsername.Text, textBoxPassword.Text, MainClient.Session)); + MainClient.SendNetCommand(new NetCommand(NetCommand.RequestType.USERS, MainClient.Session)); + + textBoxUsername.Text = ""; + + Thread.Sleep(250); + updateUsers(MainClient.users); + } + + + textBoxPassword.Text = ""; + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/ConPanelLogin.cs b/ErgometerIPR/ErgometerDoctorApplication/ConPanelLogin.cs new file mode 100644 index 0000000..3bf2ab4 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/ConPanelLogin.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + public class ConPanelLogin:Panel + { + + MainWindow mainWindow; + + public ConPanelLogin(Form mainWindow) : base() + { + this.mainWindow = (MainWindow)mainWindow; + this.textBoxPassword = new System.Windows.Forms.TextBox(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.buttonLogin = new System.Windows.Forms.Button(); + this.labelPassword = new System.Windows.Forms.Label(); + this.labelLoginInfo = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + // + // Container + // + this.BackColor = System.Drawing.Color.White; + this.Controls.Add(this.labelLoginInfo); + this.Controls.Add(this.labelPassword); + this.Controls.Add(this.buttonLogin); + this.Controls.Add(this.pictureBox1); + this.Controls.Add(this.textBoxPassword); + this.Dock = System.Windows.Forms.DockStyle.Fill; + this.Location = new System.Drawing.Point(0, 0); + // + // textBoxPassword + // + this.textBoxPassword.Anchor = System.Windows.Forms.AnchorStyles.None; + this.textBoxPassword.Location = new System.Drawing.Point(15, 70); + this.textBoxPassword.Name = "textBoxPassword"; + this.textBoxPassword.PasswordChar = '*'; + this.textBoxPassword.Size = new System.Drawing.Size(150, 20); + this.textBoxPassword.TabIndex = 0; + this.textBoxPassword.KeyDown += TextBoxPassword_KeyDown; + // + // pictureBox1 + // + this.pictureBox1.Image = ((System.Drawing.Image)Properties.Resources.imageDoctor); + this.pictureBox1.Anchor = System.Windows.Forms.AnchorStyles.None; + this.pictureBox1.Location = new System.Drawing.Point(0, -150); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(175, 175); + this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; + + // + // buttonLogin + // + this.buttonLogin.Anchor = System.Windows.Forms.AnchorStyles.None; + this.buttonLogin.BackColor = System.Drawing.SystemColors.Highlight; + this.buttonLogin.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonLogin.Font = new System.Drawing.Font("Segoe UI Semibold", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.buttonLogin.ForeColor = System.Drawing.Color.White; + this.buttonLogin.Location = new System.Drawing.Point(15, 105); + this.buttonLogin.Name = "buttonLogin"; + this.buttonLogin.Size = new System.Drawing.Size(150, 30); + this.buttonLogin.TabIndex = 2; + this.buttonLogin.Text = "Log In"; + this.buttonLogin.UseVisualStyleBackColor = false; + this.buttonLogin.Click += new System.EventHandler(this.buttonLogin_Click); + // + // labelPassword + // + this.labelPassword.Anchor = System.Windows.Forms.AnchorStyles.None; + this.labelPassword.AutoSize = true; + this.labelPassword.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelPassword.Location = new System.Drawing.Point(11, 40); + this.labelPassword.Name = "labelPassword"; + this.labelPassword.Size = new System.Drawing.Size(103, 21); + this.labelPassword.TabIndex = 3; + this.labelPassword.Text = "Wachtwoord"; + // + // labelLoginInfo + // + this.labelLoginInfo.Anchor = System.Windows.Forms.AnchorStyles.None; + this.labelLoginInfo.AutoSize = true; + this.labelLoginInfo.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelLoginInfo.ForeColor = System.Drawing.Color.Red; + this.labelLoginInfo.Location = new System.Drawing.Point(11, 140); + this.labelLoginInfo.Name = "labelLoginInfo"; + this.labelLoginInfo.Size = new System.Drawing.Size(103, 21); + this.labelLoginInfo.TabIndex = 4; + this.labelLoginInfo.Text = ""; + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + + } + + private void TextBoxPassword_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + buttonLogin_Click(this, new EventArgs()); + } + } + + private void buttonLogin_Click(object sender, EventArgs e) + { + mainWindow.validateLogin(); + } + public System.Windows.Forms.TextBox textBoxPassword; + private PictureBox pictureBox1; + private System.Windows.Forms.Button buttonLogin; + private System.Windows.Forms.Label labelPassword; + public System.Windows.Forms.Label labelLoginInfo; + } + +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/ConSessionHistory.cs b/ErgometerIPR/ErgometerDoctorApplication/ConSessionHistory.cs new file mode 100644 index 0000000..1326d43 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/ConSessionHistory.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + public class ConSessionHistory : Panel + { + private FlowLayoutPanel flowlayout; + + public ConSessionHistory() : base() + { + labelSessionHistory = new Label(); + + this.Dock = System.Windows.Forms.DockStyle.Fill; + this.Location = new System.Drawing.Point(0, 0); + this.Name = "ConSessionHistory"; + this.Size = new System.Drawing.Size(584, 459); + this.TabIndex = 0; + // + // labelSessionHistory + // + this.labelSessionHistory.Anchor = System.Windows.Forms.AnchorStyles.Top | AnchorStyles.Left; + this.labelSessionHistory.AutoSize = true; + this.labelSessionHistory.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelSessionHistory.Location = new System.Drawing.Point(20, 20); + this.labelSessionHistory.Name = "labelSessionHistory"; + this.labelSessionHistory.Size = new System.Drawing.Size(103, 21); + this.labelSessionHistory.TabIndex = 3; + this.labelSessionHistory.Text = "Er zijn geen oude sessies."; + + flowlayout = new FlowLayoutPanel(); + + flowlayout.Dock = DockStyle.Fill; + flowlayout.BackColor = System.Drawing.Color.WhiteSmoke; + flowlayout.Location = new System.Drawing.Point(0, 0); + flowlayout.Name = "flowlayout"; + flowlayout.Padding = new Padding(15); + flowlayout.AutoScroll = true; + + this.Controls.Add(labelSessionHistory); + this.Controls.Add(flowlayout); + + //this.Controls.Add(data); + + updateHistory(MainClient.oldSessionsData); + + } + + public System.Windows.Forms.Label labelSessionHistory; + + public void updateHistory(List> historys) + { + flowlayout.Controls.Clear(); + + foreach (Tuple sessiondata in historys) + { + flowlayout.Controls.Add(new SessionPanel(sessiondata.Item3, sessiondata.Item1, false,sessiondata.Item2)); + } + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/ErgometerDoctorApplication.csproj b/ErgometerIPR/ErgometerDoctorApplication/ErgometerDoctorApplication.csproj new file mode 100644 index 0000000..7ce7163 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/ErgometerDoctorApplication.csproj @@ -0,0 +1,144 @@ + + + + + Debug + AnyCPU + {E543FF58-161A-4A41-AAE7-651327834362} + WinExe + Properties + ErgometerDoctorApplication + ErgometerDoctorApplication + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\..\..\ErgometerLibrary\ErgometerLibrary\ErgometerLibrary\bin\Debug\ErgometerLibrary.dll + + + + + + + + + + + + + + + + + + Component + + + + Component + + + Component + + + Component + + + Component + + + Form + + + SessionWindow.cs + + + Component + + + Component + + + Component + + + Component + + + Component + + + + Form + + + MainWindow.cs + + + + + Component + + + MainWindow.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SessionWindow.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerDoctorApplication/MainClient.cs b/ErgometerIPR/ErgometerDoctorApplication/MainClient.cs new file mode 100644 index 0000000..74c08e7 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/MainClient.cs @@ -0,0 +1,276 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ErgometerDoctorApplication +{ + class MainClient + { + + public static TcpClient Server { get; set; } + + public static bool loggedin; + + private static Thread t; + private static bool running; + + public static int Session; + + public static string HOST = "127.0.0.1"; + public static int PORT = 8888; + + + //Server information + public static List clients; + public static Dictionary users; + public static List> oldSessionsData; + + public static void RemoveActiveClient(ClientThread clientThread) + { + clients.Remove(clientThread); + } + + public static Dictionary activesessions; + + static MainClient() + { + Server = new TcpClient(); + + loggedin = false; + + clients = new List(); + users = new Dictionary(); + activesessions = new Dictionary(); + oldSessionsData = new List>(); + } + + public static bool Connect(string password, out string error) + { + error = "Succes"; + + if (Server == null || !Server.Connected) + { + if (Server == null) + Server = new TcpClient(); + + try + { + Server.Connect(HOST, PORT); + } + catch (Exception e) + { + error = "Server is niet online."; + return false; + } + + NetCommand net = NetHelper.ReadNetCommand(Server); + if (net.Type == NetCommand.CommandType.SESSION) + Session = net.Session; + else + throw new Exception("Session not assigned"); + + running = true; + t = new Thread(run); + t.IsBackground = true; + t.Start(); + } + + if (!loggedin) + { + NetCommand command = new NetCommand("Doctor0tVfW", true, password, Session); + NetHelper.SendNetCommand(Server, command); + + NetCommand response = NetHelper.ReadNetCommand(Server); + if (response.Type == NetCommand.CommandType.RESPONSE && response.Response == NetCommand.ResponseType.LOGINWRONG) + { + loggedin = false; + error = "Het wachtwoord is onjuist."; + return false; + } + + loggedin = true; + } + + + SendNetCommand(new NetCommand(NetCommand.RequestType.SESSIONDATA, Session)); + Thread.Sleep(15); + SendNetCommand(new NetCommand(NetCommand.RequestType.USERS, Session)); + + Thread.Sleep(200); + + return true; + } + + public static void Disconnect() + { + + if (Server != null && Server.Connected) + { + NetHelper.SendNetCommand(Server, new NetCommand(NetCommand.CommandType.LOGOUT, Session)); + loggedin = false; + running = false; + Server.Close(); + Server = null; + } + } + + public static void run() + { + while (running) + { + if (loggedin && Server.Connected && Server.Available > 0) + { + NetCommand command = NetHelper.ReadNetCommand(Server); + HandleNetCommand(command); + + } + } + + if(Server != null) + Server.Close(); + } + + private static bool UsersBeingSent = false; + private static int UsersSent = 0; + private static int UsersLength = 0; + + private static bool SessionsBeingSent = false; + private static int SessionsSent = 0; + private static int SessionsLength = 0; + + private static bool ActiveSessionsBeingSent = false; + private static int ActiveSessionsSent = 0; + private static int ActiveSessionsLength = 0; + + private static void HandleNetCommand(NetCommand command) + { + switch (command.Type) + { + case NetCommand.CommandType.LENGTH: + switch (command.Length) + { + case NetCommand.LengthType.USERS: + users.Clear(); + UsersBeingSent = true; + UsersSent = 0; + UsersLength = command.LengthValue; + break; + case NetCommand.LengthType.SESSIONS: + oldSessionsData.Clear(); + SessionsBeingSent = true; + SessionsSent = 0; + SessionsLength = command.LengthValue; + break; + case NetCommand.LengthType.SESSIONDATA: + activesessions.Clear(); + ActiveSessionsBeingSent = true; + ActiveSessionsSent = 0; + ActiveSessionsLength = command.LengthValue; + break; + default: + throw new FormatException("Error in NetCommand: Length type is not recognised"); + } + break; + case NetCommand.CommandType.ERROR: + Console.WriteLine("An error occured, ignoring"); + break; + case NetCommand.CommandType.USER: + if(UsersBeingSent) + { + users.Add(command.DisplayName, command.Password); + UsersSent++; + if (UsersSent >= UsersLength) + UsersBeingSent = false; + } + break; + case NetCommand.CommandType.SESSIONDATA: + if (ActiveSessionsBeingSent) + { + activesessions.Add(command.Session, command.DisplayName); + ActiveSessionsSent++; + if (ActiveSessionsSent >= ActiveSessionsLength) + ActiveSessionsBeingSent = false; + } + if (SessionsBeingSent) + { + oldSessionsData.Add(new Tuple(command.DisplayName, command.Timestamp, command.Session)); + SessionsSent++; + if (SessionsSent >= SessionsLength) + SessionsBeingSent = false; + } + break; + default: + HandToClient(command); + break; + } + } + + private static void HandToClient(NetCommand command) + { + foreach (ClientThread cl in clients) + { + if (cl.Session == command.Session) + { + cl.HandleCommand(command); + } + } + } + + public static void SendNetCommand(NetCommand command) + { + if(! NetHelper.SendNetCommand(Server, command)) + { + Disconnect(); + } + } + + private static bool IsSessionRunning(int session) + { + foreach (ClientThread cl in clients) + { + if (cl.Session == session) + return true; + } + + return false; + } + + public static void StartNewClient(string name, int session) + { + if (IsSessionRunning(session)) + return; + + //Start new client + ClientThread cl = new ClientThread(name, session, false); + clients.Add(cl); + + //Run client on new thread + Thread thread = new Thread(new ThreadStart(cl.run)); + thread.IsBackground = true; + thread.Start(); + } + + public static void StartOldClient(string name, int session) + { + if (IsSessionRunning(session)) + return; + + SendNetCommand(new NetCommand(NetCommand.RequestType.OLDDATA, session)); + SendNetCommand(new NetCommand(NetCommand.RequestType.CHAT, session)); + + //Start new client + ClientThread cl = new ClientThread(name, session, true); + clients.Add(cl); + + //Run client on new thread + Thread thread = new Thread(new ThreadStart(cl.run)); + thread.IsBackground = true; + thread.Start(); + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/MainWindow.Designer.cs b/ErgometerIPR/ErgometerDoctorApplication/MainWindow.Designer.cs new file mode 100644 index 0000000..d8a7c95 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/MainWindow.Designer.cs @@ -0,0 +1,320 @@ +using System; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + partial class MainWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.MenuPanel = new System.Windows.Forms.Panel(); + this.BtnLogout = new System.Windows.Forms.Button(); + this.BtnSessionHistory = new System.Windows.Forms.Button(); + this.BtnBroadcast = new System.Windows.Forms.Button(); + this.label2 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.BtnClientData = new System.Windows.Forms.Button(); + this.BtnActiveSessions = new System.Windows.Forms.Button(); + this.LblHoofdvenster = new System.Windows.Forms.Label(); + this.menuStrip1 = new System.Windows.Forms.MenuStrip(); + this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.viewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.panel1 = new System.Windows.Forms.Panel(); + this.HeaderLabel = new System.Windows.Forms.Label(); + this.panel3 = new System.Windows.Forms.Panel(); + this.MainContainer = new System.Windows.Forms.Panel(); + this.conActiveSessions = new ErgometerDoctorApplication.ConActiveSessions(); + this.conClientData = new ErgometerDoctorApplication.ConClientData(); + this.conSessionHistory = new ErgometerDoctorApplication.ConSessionHistory(); + this.conClientBroadcast = new ErgometerDoctorApplication.ConClientBroadcast(); + this.conPanelLogin = new ErgometerDoctorApplication.ConPanelLogin(this); + this.updateTimer = new Timer(); + MainContainer.Visible = false; + panel1.Visible = false; + MenuPanel.Visible = false; + menuStrip1.Visible = false; + // + // MenuPanel + // + this.MenuPanel.BackColor = System.Drawing.SystemColors.ControlLight; + this.MenuPanel.Controls.Add(this.BtnLogout); + this.MenuPanel.Controls.Add(this.BtnSessionHistory); + this.MenuPanel.Controls.Add(this.BtnBroadcast); + this.MenuPanel.Controls.Add(this.label2); + this.MenuPanel.Controls.Add(this.label1); + this.MenuPanel.Controls.Add(this.BtnClientData); + this.MenuPanel.Controls.Add(this.BtnActiveSessions); + this.MenuPanel.Controls.Add(this.LblHoofdvenster); + this.MenuPanel.Dock = System.Windows.Forms.DockStyle.Left; + this.MenuPanel.Location = new System.Drawing.Point(0, 24); + this.MenuPanel.Margin = new System.Windows.Forms.Padding(0); + this.MenuPanel.Name = "MenuPanel"; + this.MenuPanel.Size = new System.Drawing.Size(200, 537); + this.MenuPanel.TabIndex = 0; + + // + // BtnLogout + // + this.BtnLogout.BackColor = System.Drawing.Color.DarkGray; + this.BtnLogout.Dock = System.Windows.Forms.DockStyle.Bottom; + this.BtnLogout.FlatAppearance.BorderSize = 0; + this.BtnLogout.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.BtnLogout.ForeColor = System.Drawing.Color.White; + this.BtnLogout.Location = new System.Drawing.Point(0, 205); + this.BtnLogout.Name = "BtnLogout"; + this.BtnLogout.Size = new System.Drawing.Size(200, 35); + this.BtnLogout.Text = "Uitloggen"; + this.BtnLogout.UseVisualStyleBackColor = false; + this.BtnLogout.Click += new System.EventHandler(this.buttonLogout_Click); + // + // BtnSessionHistory + // + this.BtnSessionHistory.BackColor = System.Drawing.Color.DarkGray; + this.BtnSessionHistory.Dock = System.Windows.Forms.DockStyle.Top; + this.BtnSessionHistory.FlatAppearance.BorderSize = 0; + this.BtnSessionHistory.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.BtnSessionHistory.ForeColor = System.Drawing.Color.White; + this.BtnSessionHistory.Location = new System.Drawing.Point(0, 205); + this.BtnSessionHistory.Name = "BtnSessionHistory"; + this.BtnSessionHistory.Size = new System.Drawing.Size(200, 35); + this.BtnSessionHistory.TabIndex = 7; + this.BtnSessionHistory.Text = "Sessie Geschiedenis"; + this.BtnSessionHistory.UseVisualStyleBackColor = false; + this.BtnSessionHistory.Click += new System.EventHandler(this.BtnSessionHistory_Click); + // + // label2 + // + this.label2.Dock = System.Windows.Forms.DockStyle.Top; + this.label2.Location = new System.Drawing.Point(0, 180); + this.label2.Margin = new System.Windows.Forms.Padding(6, 3, 6, 3); + this.label2.Name = "label2"; + this.label2.Padding = new System.Windows.Forms.Padding(5, 4, 0, 0); + this.label2.Size = new System.Drawing.Size(200, 25); + this.label2.TabIndex = 6; + this.label2.Text = "SESSIE BEHEER"; + this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // updateTimer + // + this.updateTimer.Interval = 3000; + this.updateTimer.Tick += new EventHandler(this.updateTimer_tick); + // + // label1 + // + this.label1.Dock = System.Windows.Forms.DockStyle.Top; + this.label1.Location = new System.Drawing.Point(0, 95); + this.label1.Margin = new System.Windows.Forms.Padding(6, 3, 6, 3); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(200, 50); + this.label1.TabIndex = 5; + // + // BtnBroadcast + // + this.BtnBroadcast.BackColor = System.Drawing.Color.DarkGray; + this.BtnBroadcast.Dock = System.Windows.Forms.DockStyle.Top; + this.BtnBroadcast.FlatAppearance.BorderSize = 0; + this.BtnBroadcast.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.BtnBroadcast.ForeColor = System.Drawing.Color.White; + this.BtnBroadcast.Location = new System.Drawing.Point(0, 95); + this.BtnBroadcast.Name = "BtnBroadcast"; + this.BtnBroadcast.Size = new System.Drawing.Size(200, 35); + this.BtnBroadcast.TabIndex = 7; + this.BtnBroadcast.Text = "Broadcast"; + this.BtnBroadcast.UseVisualStyleBackColor = false; + this.BtnBroadcast.Click += new System.EventHandler(this.BtnBroadcast_Click); + // + // BtnClientData + // + this.BtnClientData.BackColor = System.Drawing.Color.DarkGray; + this.BtnClientData.Dock = System.Windows.Forms.DockStyle.Top; + this.BtnClientData.FlatAppearance.BorderSize = 0; + this.BtnClientData.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.BtnClientData.ForeColor = System.Drawing.Color.White; + this.BtnClientData.Location = new System.Drawing.Point(0, 60); + this.BtnClientData.Name = "BtnClientData"; + this.BtnClientData.Size = new System.Drawing.Size(200, 35); + this.BtnClientData.TabIndex = 4; + this.BtnClientData.Text = "Clientenbestand"; + this.BtnClientData.UseVisualStyleBackColor = false; + this.BtnClientData.Click += new System.EventHandler(this.BtnClientData_Click); + // + // BtnActiveSessions + // + this.BtnActiveSessions.BackColor = System.Drawing.Color.DarkGray; + this.BtnActiveSessions.Dock = System.Windows.Forms.DockStyle.Top; + this.BtnActiveSessions.FlatAppearance.BorderSize = 0; + this.BtnActiveSessions.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.BtnActiveSessions.ForeColor = System.Drawing.Color.White; + this.BtnActiveSessions.Location = new System.Drawing.Point(0, 25); + this.BtnActiveSessions.Name = "BtnActiveSessions"; + this.BtnActiveSessions.Size = new System.Drawing.Size(200, 35); + this.BtnActiveSessions.TabIndex = 3; + this.BtnActiveSessions.Text = "Actieve Sessies"; + this.BtnActiveSessions.UseVisualStyleBackColor = false; + this.BtnActiveSessions.Click += new System.EventHandler(this.BtnActiveSessions_Click); + // + // LblHoofdvenster + // + this.LblHoofdvenster.Dock = System.Windows.Forms.DockStyle.Top; + this.LblHoofdvenster.Location = new System.Drawing.Point(0, 0); + this.LblHoofdvenster.Margin = new System.Windows.Forms.Padding(6, 3, 6, 3); + this.LblHoofdvenster.Name = "LblHoofdvenster"; + this.LblHoofdvenster.Padding = new System.Windows.Forms.Padding(5, 4, 0, 0); + this.LblHoofdvenster.Size = new System.Drawing.Size(200, 25); + this.LblHoofdvenster.TabIndex = 0; + this.LblHoofdvenster.Text = "HOOFDVENSTER"; + this.LblHoofdvenster.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // menuStrip1 + // + this.menuStrip1.BackColor = System.Drawing.Color.DimGray; + this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileToolStripMenuItem, + this.editToolStripMenuItem, + this.viewToolStripMenuItem}); + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Size = new System.Drawing.Size(784, 24); + this.menuStrip1.TabIndex = 1; + this.menuStrip1.Text = "menuStrip1"; + // + // fileToolStripMenuItem + // + this.fileToolStripMenuItem.ForeColor = System.Drawing.Color.White; + this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); + this.fileToolStripMenuItem.Text = "File"; + // + // editToolStripMenuItem + // + this.editToolStripMenuItem.ForeColor = System.Drawing.Color.White; + this.editToolStripMenuItem.Name = "editToolStripMenuItem"; + this.editToolStripMenuItem.Size = new System.Drawing.Size(39, 20); + this.editToolStripMenuItem.Text = "Edit"; + // + // viewToolStripMenuItem + // + this.viewToolStripMenuItem.ForeColor = System.Drawing.Color.White; + this.viewToolStripMenuItem.Name = "viewToolStripMenuItem"; + this.viewToolStripMenuItem.Size = new System.Drawing.Size(44, 20); + this.viewToolStripMenuItem.Text = "View"; + // + // panel1 + // + this.panel1.BackColor = System.Drawing.Color.White; + this.panel1.Controls.Add(this.HeaderLabel); + this.panel1.Controls.Add(this.panel3); + this.panel1.Dock = System.Windows.Forms.DockStyle.Top; + this.panel1.Location = new System.Drawing.Point(200, 24); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(584, 78); + this.panel1.TabIndex = 2; + // + // HeaderLabel + // + this.HeaderLabel.Dock = System.Windows.Forms.DockStyle.Top; + this.HeaderLabel.Font = new System.Drawing.Font("Segoe UI Semibold", 14F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.HeaderLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.HeaderLabel.Location = new System.Drawing.Point(0, 0); + this.HeaderLabel.Name = "HeaderLabel"; + this.HeaderLabel.Padding = new System.Windows.Forms.Padding(8, 12, 0, 0); + this.HeaderLabel.Size = new System.Drawing.Size(584, 50); + this.HeaderLabel.TabIndex = 1; + this.HeaderLabel.Text = "Actieve Sessies"; + // + // panel3 + // + this.panel3.BackColor = System.Drawing.Color.Silver; + this.panel3.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel3.ForeColor = System.Drawing.Color.Black; + this.panel3.Location = new System.Drawing.Point(0, 74); + this.panel3.Name = "panel3"; + this.panel3.Size = new System.Drawing.Size(584, 4); + this.panel3.TabIndex = 0; + // + // MainContainer + // + this.MainContainer.Controls.Add(this.conActiveSessions); + this.MainContainer.Controls.Add(this.conClientData); + this.MainContainer.Controls.Add(this.conSessionHistory); + this.MainContainer.Controls.Add(this.conClientBroadcast); + this.MainContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.MainContainer.Location = new System.Drawing.Point(200, 102); + this.MainContainer.Name = "MainContainer"; + this.MainContainer.Size = new System.Drawing.Size(584, 459); + this.MainContainer.TabIndex = 3; + // + // MainWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(700, 500); + this.MinimumSize = new System.Drawing.Size(650, 550); + this.Controls.Add(this.MainContainer); + this.Controls.Add(this.panel1); + this.Controls.Add(this.MenuPanel); + this.Controls.Add(this.menuStrip1); + this.Controls.Add(this.conPanelLogin); + + this.MainMenuStrip = this.menuStrip1; + this.Name = "MainWindow"; + this.Text = "Dokter applicatie"; + this.WindowState = System.Windows.Forms.FormWindowState.Maximized; + + } + + + #endregion + + private Panel MenuPanel; + private Label LblHoofdvenster; + private MenuStrip menuStrip1; + private ToolStripMenuItem fileToolStripMenuItem; + private ToolStripMenuItem editToolStripMenuItem; + private ToolStripMenuItem viewToolStripMenuItem; + private Button BtnActiveSessions; + private Button BtnBroadcast; + private Button BtnLogout; + private Button BtnSessionHistory; + private Button BtnClientData; + private Label label2; + private Label label1; + private Panel panel1; + private Panel panel3; + public Label HeaderLabel; + public Timer updateTimer; + public Panel MainContainer; + public ConActiveSessions conActiveSessions; + public ConSessionHistory conSessionHistory; + public ConClientData conClientData; + public ConPanelLogin conPanelLogin; + public ConClientBroadcast conClientBroadcast; + + } +} + diff --git a/ErgometerIPR/ErgometerDoctorApplication/MainWindow.cs b/ErgometerIPR/ErgometerDoctorApplication/MainWindow.cs new file mode 100644 index 0000000..550854a --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/MainWindow.cs @@ -0,0 +1,147 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + public partial class MainWindow : Form + { + private int request; + + public MainWindow() + { + InitializeComponent(); + conPanelLogin.BringToFront(); + request = 2; + updateTimer.Start(); + + } + + private void BtnActiveSessions_Click(object sender, EventArgs e) + { + this.HeaderLabel.Text = "Actieve Sessies"; + + if(MainClient.activesessions.Count > 0) + { + conActiveSessions.labelActiveSessions.Text = ""; + conActiveSessions.updateActiveSessions(MainClient.activesessions); + } + else + { + conActiveSessions.updateActiveSessions(MainClient.activesessions); + conActiveSessions.labelActiveSessions.Text = "Er zijn geen actieve sessies."; + } + + conActiveSessions.BringToFront(); + } + + private void BtnClientData_Click(object sender, EventArgs e) + { + this.HeaderLabel.Text = "Clientenbestand"; + + + if (MainClient.users.Count > 0) + { + conClientData.updateUsers(MainClient.users); + } + + conClientData.BringToFront(); + } + + private void BtnBroadcast_Click(object sender, EventArgs e) + { + this.HeaderLabel.Text = "Broadcast"; + conClientBroadcast.BringToFront(); + } + + private void BtnSessionHistory_Click(object sender, EventArgs e) + { + this.HeaderLabel.Text = "Sessie geschiedenis"; + + if (MainClient.oldSessionsData.Count > 0) + { + conSessionHistory.labelSessionHistory.Text = ""; + conSessionHistory.updateHistory(MainClient.oldSessionsData); + } + else + { + conSessionHistory.updateHistory(MainClient.oldSessionsData); + conSessionHistory.labelSessionHistory.Text = "Er zijn geen oude sessies."; + } + + conSessionHistory.BringToFront(); + } + public void validateLogin() + { + string error = ""; + bool connect = MainClient.Connect(conPanelLogin.textBoxPassword.Text, out error); + + if (connect) + { + conPanelLogin.textBoxPassword.Text = ""; + conPanelLogin.labelLoginInfo.Text = ""; + + if (MainClient.activesessions.Count > 0) + { + conActiveSessions.labelActiveSessions.Text = ""; + conActiveSessions.updateActiveSessions(MainClient.activesessions); + } + + showDashboard(); + } + else + { + conPanelLogin.labelLoginInfo.Text = error; + showLoginScreen(); + } + } + + private void buttonLogout_Click(object sender, EventArgs e) + { + MainClient.Disconnect(); + showLoginScreen(); + } + + private void showDashboard() + { + conPanelLogin.Visible = false; + MainContainer.Visible = true; + panel1.Visible = true; + MenuPanel.Visible = true; + menuStrip1.Visible = true; + } + + private void showLoginScreen() + { + MainContainer.Visible = false; + panel1.Visible = false; + MenuPanel.Visible = false; + menuStrip1.Visible = false; + conPanelLogin.Visible = true; + conPanelLogin.BringToFront(); + } + + private void updateTimer_tick(object sender, EventArgs e) + { + if (request == 0) + MainClient.SendNetCommand(new NetCommand(NetCommand.RequestType.USERS, MainClient.Session)); + else if (request == 1) + MainClient.SendNetCommand(new NetCommand(NetCommand.RequestType.SESSIONDATA, MainClient.Session)); + else if (request == 2) + MainClient.SendNetCommand(new NetCommand(NetCommand.RequestType.ALLSESSIONS, MainClient.Session)); + + request ++; + if (request > 2) + { + request = 0; + } + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/MainWindow.resx b/ErgometerIPR/ErgometerDoctorApplication/MainWindow.resx new file mode 100644 index 0000000..32fcbfb --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/MainWindow.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 25 + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerDoctorApplication/Program.cs b/ErgometerIPR/ErgometerDoctorApplication/Program.cs new file mode 100644 index 0000000..c469659 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainWindow()); + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/Properties/AssemblyInfo.cs b/ErgometerIPR/ErgometerDoctorApplication/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f9eac09 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ErgometerDoctorApplication")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ErgometerDoctorApplication")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e543ff58-161a-4a41-aae7-651327834362")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ErgometerIPR/ErgometerDoctorApplication/Properties/Resources.Designer.cs b/ErgometerIPR/ErgometerDoctorApplication/Properties/Resources.Designer.cs new file mode 100644 index 0000000..c961241 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ErgometerDoctorApplication.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ErgometerDoctorApplication.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap imageDoctor { + get { + object obj = ResourceManager.GetObject("imageDoctor", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/Properties/Resources.resx b/ErgometerIPR/ErgometerDoctorApplication/Properties/Resources.resx new file mode 100644 index 0000000..2034261 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\doctorImage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerDoctorApplication/Properties/Settings.Designer.cs b/ErgometerIPR/ErgometerDoctorApplication/Properties/Settings.Designer.cs new file mode 100644 index 0000000..5284d3d --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ErgometerDoctorApplication.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/ErgometerIPR/ErgometerDoctorApplication/Properties/Settings.settings b/ErgometerIPR/ErgometerDoctorApplication/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ErgometerIPR/ErgometerDoctorApplication/Resources/doctorImage.png b/ErgometerIPR/ErgometerDoctorApplication/Resources/doctorImage.png new file mode 100644 index 0000000000000000000000000000000000000000..e138ccdf32cbf113ff530bb722b9cf73b07a63d3 GIT binary patch literal 12999 zcmXwfcRbbq_xS6&_Ac4;CPYTK$iA-P+F9A8viDx$3Q5*2B>Sox*_D-@i0o@`%D#lM zx8KYA`}_Ry<|%2I{T>06-is7da{TCV0tq1pG(g zi8Av604j#d1pzX$SO9=h&q-DF{(VOeUk@Kg4^JKqRaG8OZx4GXS33X*oXj?KFft@C zDIU!3BDG$iQ?xt`7|D4Ika6g1ulR*v6!e-eV0jaa`Yl)P-6LlIiG2Z0Ohm^s>I+l9 zq#P$(guP3QeE;HQ*U9gY_b#Ii2g|+ZRbxskB|F&_{bb+Bso$U^^`+3%g-EU|zal%@ z+J7&KA|fbncmnj~6;EJ3XS@*LFib(=*7a{>O#mcth>9Herjz;YHb=|_-5N685`vC` zeDi&EM~fVN4L}4ZBJ%-+8U&sFmfsM-5(5vrA3s_E?u!5q1;Uqy0Ce_YdI$utNP#gz zu&)3f2D=w`0Y`bDr2oOodw}U};F`Twn=CLc0^HUxu}1-)YJhKj^i*E}a%$kV{)=cq zfH(wr_>-465Qs_#uBmOCDEzxsM!zBiN-F(}LhTLNdl44oB3@)BCZb$7`!twuU6ZyY zvdutyI8SoG)QPA2|A7Q$#(QUQSX!t)aR& zZ(xgAP0cE z*a)FdD(t&0EW+PNIa)}jTc}PSN=G4Yw0%d?BPpMtIlLbVmOMk=eBS$sQ|O`CFoF~Q zP2cf(vL~(RcjK?Lik@`Gw&Xc2Hya`-h*f@)GuXl=UqBwcH04bsXHJhkgtefEug2Zr z>3nfb|GL&IDU`UuOCug*6z8_0H<4_NrCQx9`JaHqv(x7d>f$L8U-T7+sjF0LKgzI0 zslB7ZKk*gMNMgcf;=L=`6yv4yR`A7+bdyO+svWn#t+J3wwz=LGZEr<>z9USL{J8t` z^%CmhcD7FsN}%}Q*WhISsR$E+2B!Y~(_ z;^%%5Qf>U7dncay3)R0pCRh86~G^sGyNwHTZTX^ernB}WWv2wE@Z71yI z?6obyc|rDkxOT0$ouy0Bo3uBk#i(NaV)gY#>9UQ2?EJ=i^=2m#v??8%0+s^m4oQ6^ z*(-(MJ^xM^Xl&gG`kmUzRhW}M(1dI6wC%St{iiOik%sqxg8hm zsZ*PhR#4v{ouckzQTQR{<)OeS)-m-@U)8D=q58o#)i&*S)T`P7h4&+B32JolPVJTp zHx}I5-P+&tlk&s(wRF~WHZu%#YV!s2<+bItJ9>M1-}R2BOJypGk4da#u4Im7&ec3L z4>T99{#gChPUFj>dA`|Zb%T4fxvbd-Q)6?h3boSgQohmp()xlc1+}`#y2Ux84y~X5 zRd-ew*06qB8Yi79t9GjnwRL#J>`?QgtaUTFF*&p@G~rBb|Vh*PL^!?+K3)KwWf^AOOhM7a^4$Ka`9Gm^HKMk@;5hv!+|G z)g#9ydQLT*_u|P#@WlX7faZfZKw~3RA}okb4uhY6K(QbInnclDcgW}npD8R zw5reC-Q&A&?#^a9i0jFgi)cy-DHVx2D@@AN!RwUUMeWUcMohC!HC(kgWJQI_q}^Sc z=1wg8os_>S^gH)k1C~dc_+iX=P!J{J}ad5!NkT&swS=F6&grluOZ}>(gT! zR(hslW^+}x$Ir9bl^A1RmR*}-PGbxozd0Sa#2(|()to+^QdwBsR{Nyie$bw|M-xS-(*AF{+te+V$R5n?F_X{!;BM)M;3K@JxT+kYqP

    QmfwS#SljAk7`4 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 F{|6va>68Ef literal 0 HcmV?d00001 diff --git a/ErgometerIPR/ErgometerDoctorApplication/SessionPanel.cs b/ErgometerIPR/ErgometerDoctorApplication/SessionPanel.cs new file mode 100644 index 0000000..2ec251a --- /dev/null +++ b/ErgometerIPR/ErgometerDoctorApplication/SessionPanel.cs @@ -0,0 +1,91 @@ +using System.Windows.Forms; + +namespace ErgometerDoctorApplication +{ + class SessionPanel : Panel + { + private int Session; + private string name; + private bool IsNew; + private double timestamp; + + public SessionPanel(int session, string name, bool isNew,double timestamp) : base() + { + this.timestamp = timestamp; + Session = session; + this.name = name; + IsNew = isNew; + + this.Location = new System.Drawing.Point(0, 0); + this.Size = new System.Drawing.Size(180, 100); + this.BackColor = System.Drawing.Color.DarkGray; + + this.Click += SessionPanel_Click; + this.MouseEnter += SessionPanel_MouseEnter; + this.MouseLeave += SessionPanel_MouseLeave; + this.Cursor = Cursors.Hand; + + this.labelTimestamp = new Label(); + this.labelName = new System.Windows.Forms.Label(); + + // + // labelActiveSessions + // + this.labelName.Anchor = System.Windows.Forms.AnchorStyles.None; + this.labelName.AutoSize = true; + this.labelName.Font = new System.Drawing.Font("Segoe UI Semibold", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelName.Location = new System.Drawing.Point(10, 10); + this.labelName.ForeColor = System.Drawing.Color.White; + this.labelName.Name = "labelActiveSessions"; + this.labelName.Size = new System.Drawing.Size(103, 21); + this.labelName.TabIndex = 3; + this.labelName.Text = name; + this.labelName.Click += SessionPanel_Click; + this.labelName.MouseEnter += SessionPanel_MouseEnter; + this.labelName.MouseLeave += SessionPanel_MouseLeave; + this.labelName.Cursor = Cursors.Hand; + + this.labelTimestamp.Name = "labelTimeStamp"; + this.labelTimestamp.Anchor = AnchorStyles.None; + this.labelTimestamp.Text = ErgometerLibrary.Helper.MillisecondsToTime(timestamp); + this.labelTimestamp.Location = new System.Drawing.Point(10, 40); + this.labelTimestamp.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelTimestamp.ForeColor = System.Drawing.Color.White; + this.labelTimestamp.Size = new System.Drawing.Size(103, 21); + this.labelTimestamp.Click += SessionPanel_Click; + this.labelTimestamp.MouseEnter += SessionPanel_MouseEnter; + this.labelTimestamp.MouseLeave += SessionPanel_MouseLeave; + + if (isNew) + labelTimestamp.Visible = false; + + this.Controls.Add(labelName); + this.Controls.Add(labelTimestamp); + } + + private void SessionPanel_MouseEnter(object sender, System.EventArgs e) + { + this.BackColor = System.Drawing.Color.Gray; + this.labelName.ForeColor = System.Drawing.Color.WhiteSmoke; + labelTimestamp.ForeColor = System.Drawing.Color.WhiteSmoke; + } + + private void SessionPanel_MouseLeave(object sender, System.EventArgs e) + { + this.BackColor = System.Drawing.Color.DarkGray; + this.labelName.ForeColor = System.Drawing.Color.White; + this.labelTimestamp.ForeColor = System.Drawing.Color.White; + } + + private void SessionPanel_Click(object sender, System.EventArgs e) + { + if(IsNew) + MainClient.StartNewClient(name, Session); + else + MainClient.StartOldClient(name, Session); + } + + public System.Windows.Forms.Label labelName; + public Label labelTimestamp; + } +} \ No newline at end of file diff --git a/ErgometerIPR/ErgometerIPR.sln b/ErgometerIPR/ErgometerIPR.sln new file mode 100644 index 0000000..a8b9883 --- /dev/null +++ b/ErgometerIPR/ErgometerIPR.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErgometerIPR", "ErgometerIPR\ErgometerIPR.csproj", "{2E76E1E6-CBA7-46EA-858A-223B13F193A8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2E76E1E6-CBA7-46EA-858A-223B13F193A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E76E1E6-CBA7-46EA-858A-223B13F193A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E76E1E6-CBA7-46EA-858A-223B13F193A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E76E1E6-CBA7-46EA-858A-223B13F193A8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ErgometerIPR/ErgometerLibrary/AESEncrypt.cs b/ErgometerIPR/ErgometerLibrary/AESEncrypt.cs new file mode 100644 index 0000000..e17ba0d --- /dev/null +++ b/ErgometerIPR/ErgometerLibrary/AESEncrypt.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace ErgometerLibrary +{ + static class AESEncrypt + { + // Change these keys + private static byte[] Key = { 47, 20, 192, 222, 214, 221, 193, 151, 182, 182, 20, 246, 80, 235, 182, 216, 116, 141, 188, 191, 13, 159, 1, 137, 151, 150, 29, 171, 240, 21, 251, 252 }; + private static byte[] Vector = { 147, 16, 78, 223, 97, 189, 43, 197, 244, 102, 242, 123, 184, 101, 75, 203 }; + + + private static ICryptoTransform EncryptorTransform, DecryptorTransform; + private static System.Text.UTF8Encoding UTFEncoder; + + static AESEncrypt() + { + //This is our encryption method + RijndaelManaged rm = new RijndaelManaged(); + + //Create an encryptor and a decryptor using our encryption method, key, and vector. + EncryptorTransform = rm.CreateEncryptor(Key, Vector); + DecryptorTransform = rm.CreateDecryptor(Key, Vector); + + //Used to translate bytes to text and vice versa + UTFEncoder = new System.Text.UTF8Encoding(); + } + + /// -------------- Two Utility Methods (not used but may be useful) ----------- + /// Generates an encryption key. + static public byte[] GenerateEncryptionKey() + { + //Generate a Key. + RijndaelManaged rm = new RijndaelManaged(); + rm.GenerateKey(); + return rm.Key; + } + + /// Generates a unique encryption vector + static public byte[] GenerateEncryptionVector() + { + //Generate a Vector + RijndaelManaged rm = new RijndaelManaged(); + rm.GenerateIV(); + return rm.IV; + } + + + /// ----------- The commonly used methods ------------------------------ + /// Encrypt some text and return a string suitable for passing in a URL. + public static string EncryptToString(string TextValue) + { + return ByteArrToString(Encrypt(TextValue)); + } + + /// Encrypt some text and return an encrypted byte array. + public static byte[] Encrypt(string TextValue) + { + //Translates our text value into a byte array. + Byte[] bytes = UTFEncoder.GetBytes(TextValue); + + //Used to stream the data in and out of the CryptoStream. + MemoryStream memoryStream = new MemoryStream(); + + /* + * We will have to write the unencrypted bytes to the stream, + * then read the encrypted result back from the stream. + */ + #region Write the decrypted value to the encryption stream + CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write); + cs.Write(bytes, 0, bytes.Length); + cs.FlushFinalBlock(); + #endregion + + #region Read encrypted value back out of the stream + memoryStream.Position = 0; + byte[] encrypted = new byte[memoryStream.Length]; + memoryStream.Read(encrypted, 0, encrypted.Length); + #endregion + + //Clean up. + cs.Close(); + memoryStream.Close(); + + return encrypted; + } + + /// The other side: Decryption methods + public static string DecryptString(string EncryptedString) + { + return Decrypt(StrToByteArray(EncryptedString)); + } + + /// Decryption when working with byte arrays. + public static string Decrypt(byte[] EncryptedValue) + { + #region Write the encrypted value to the decryption stream + MemoryStream encryptedStream = new MemoryStream(); + CryptoStream decryptStream = new CryptoStream(encryptedStream, DecryptorTransform, CryptoStreamMode.Write); + decryptStream.Write(EncryptedValue, 0, EncryptedValue.Length); + decryptStream.FlushFinalBlock(); + #endregion + + #region Read the decrypted value from the stream. + encryptedStream.Position = 0; + Byte[] decryptedBytes = new Byte[encryptedStream.Length]; + encryptedStream.Read(decryptedBytes, 0, decryptedBytes.Length); + encryptedStream.Close(); + #endregion + return UTFEncoder.GetString(decryptedBytes); + } + + /// Convert a string to a byte array. NOTE: Normally we'd create a Byte Array from a string using an ASCII encoding (like so). + // System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding(); + // return encoding.GetBytes(str); + // However, this results in character values that cannot be passed in a URL. So, instead, I just + // lay out all of the byte values in a long string of numbers (three per - must pad numbers less than 100). + public static byte[] StrToByteArray(string str) + { + if (str.Length == 0) + throw new Exception("Invalid string value in StrToByteArray"); + + byte val; + byte[] byteArr = new byte[str.Length / 3]; + int i = 0; + int j = 0; + do + { + val = byte.Parse(str.Substring(i, 3)); + byteArr[j++] = val; + i += 3; + } + while (i < str.Length); + return byteArr; + } + + // Same comment as above. Normally the conversion would use an ASCII encoding in the other direction: + // System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding(); + // return enc.GetString(byteArr); + public static string ByteArrToString(byte[] byteArr) + { + byte val; + string tempStr = ""; + for (int i = 0; i <= byteArr.GetUpperBound(0); i++) + { + val = byteArr[i]; + if (val < (byte)10) + tempStr += "00" + val.ToString(); + else if (val < (byte)100) + tempStr += "0" + val.ToString(); + else + tempStr += val.ToString(); + } + return tempStr; + } + } +} diff --git a/ErgometerIPR/ErgometerLibrary/Chat/ChatItem.cs b/ErgometerIPR/ErgometerLibrary/Chat/ChatItem.cs new file mode 100644 index 0000000..55da46f --- /dev/null +++ b/ErgometerIPR/ErgometerLibrary/Chat/ChatItem.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ErgometerLibrary.Chat +{ + public class ChatItem : Panel + { + private Label messageLabel, timeLabel; + + public ChatItem(ChatMessage chat) : base() + { + this.messageLabel = new Label(); + this.timeLabel = new Label(); + + this.AutoSize = true; + this.setAppearance(chat.IsDoctor); + this.Controls.Add(this.messageLabel); + this.Controls.Add(this.timeLabel); + this.MinimumSize = new System.Drawing.Size(150, 60); + this.Name = "messageContainer"; + this.Padding = new System.Windows.Forms.Padding(6); + + // + // Message Label + // + this.messageLabel.AutoSize = true; + this.messageLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.messageLabel.Font = new System.Drawing.Font("Segoe UI Semibold", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.messageLabel.Location = new System.Drawing.Point(6, 6); + this.messageLabel.MaximumSize = new System.Drawing.Size(250, 0); + this.messageLabel.Size = new System.Drawing.Size(121, 20); + this.messageLabel.Text = chat.Message.Replace("\n", ""); + // + // Time Label + // + this.timeLabel.AutoSize = true; + this.timeLabel.Dock = System.Windows.Forms.DockStyle.Bottom; + this.timeLabel.Font = new System.Drawing.Font("Segoe UI Semilight", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.timeLabel.Location = new System.Drawing.Point(6, 26); + this.timeLabel.Margin = new System.Windows.Forms.Padding(6); + this.timeLabel.Size = new System.Drawing.Size(32, 13); + this.timeLabel.Text = Helper.MillisecondsToTime(chat.TimeStamp); + } + + private void setAppearance(bool isDoctor) + { + if (isDoctor) + { + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(255)))), ((int)(((byte)(255))))); + this.Dock = DockStyle.Right; + } + else + { + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240))))); + this.Dock = DockStyle.Left; + } + } + } +} diff --git a/ErgometerIPR/ErgometerLibrary/ChatMessage.cs b/ErgometerIPR/ErgometerLibrary/ChatMessage.cs new file mode 100644 index 0000000..6605674 --- /dev/null +++ b/ErgometerIPR/ErgometerLibrary/ChatMessage.cs @@ -0,0 +1,23 @@ +namespace ErgometerLibrary +{ + public class ChatMessage + { + public string Message { get; } + public string Name { get; } + public double TimeStamp { get; set;} + public bool IsDoctor { get; set; } + + public ChatMessage(string name, string message, bool isDoctor) + { + Name = name; + Message = message; + TimeStamp = Helper.Now; + IsDoctor = isDoctor; + } + + public override string ToString() + { + return $"[{Name}] <{TimeStamp}> - {Message}"; + } + } +} \ No newline at end of file diff --git a/ErgometerIPR/ErgometerLibrary/ComPort.cs b/ErgometerIPR/ErgometerLibrary/ComPort.cs new file mode 100644 index 0000000..126fc39 --- /dev/null +++ b/ErgometerIPR/ErgometerLibrary/ComPort.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ErgometerLibrary +{ + public class ComPort + { + private SerialPort comPort; + + public ComPort() + { + comPort = null; + } + + public Boolean Connect(string port) + { + comPort = new SerialPort(); + comPort.PortName = port; + comPort.DataBits = 8; + comPort.Parity = Parity.None; + comPort.StopBits = StopBits.One; + comPort.BaudRate = 9600; + comPort.ReadTimeout = 1500; + try { + comPort.Open(); + } + catch(Exception e) + { + return false; + } + + return comPort.IsOpen; + } + + public Boolean Disconnect() + { + comPort.Close(); + return !comPort.IsOpen; + } + + public Boolean IsOpen() + { + if (comPort != null) + return comPort.IsOpen; + else + return false; + } + + public void Write(string input) + { + if (IsOpen()) + { + comPort.WriteLine(input); + } + } + + public string Read() + { + if (IsOpen()) + { + try { + return comPort.ReadLine(); + } + catch(TimeoutException e) + { + return "err"; + } + } + + return ""; + } + } +} diff --git a/ErgometerIPR/ErgometerLibrary/ErgometerLibrary.csproj b/ErgometerIPR/ErgometerLibrary/ErgometerLibrary.csproj new file mode 100644 index 0000000..b0e2aeb --- /dev/null +++ b/ErgometerIPR/ErgometerLibrary/ErgometerLibrary.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {CC20FF4E-8751-42A1-A32B-0E5B22452CAC} + Library + Properties + ErgometerLibrary + ErgometerLibrary + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + Component + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerLibrary/Helper.cs b/ErgometerIPR/ErgometerLibrary/Helper.cs new file mode 100644 index 0000000..51c7a9e --- /dev/null +++ b/ErgometerIPR/ErgometerLibrary/Helper.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ErgometerLibrary +{ + public class Helper + { + public static double Now { get { return (DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; } } + + public static string MillisecondsToTime(double millis) + { + DateTime time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + string timestr = time.AddMilliseconds(millis) + ""; + return timestr; + } + + public static string Base64Encode(string plainText) + { + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); + return System.Convert.ToBase64String(plainTextBytes); + } + + public static string Base64Decode(string base64EncodedData) + { + var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData); + return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); + } + } +} diff --git a/ErgometerIPR/ErgometerLibrary/Meting.cs b/ErgometerIPR/ErgometerLibrary/Meting.cs new file mode 100644 index 0000000..0f2fba6 --- /dev/null +++ b/ErgometerIPR/ErgometerLibrary/Meting.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ErgometerLibrary +{ + public class Meting + { + public int HeartBeat { get; set; } + public int RPM { get; set; } + public double Speed { get; set; } + public double Distance { get; set; } + public int Power { get; set; } + public int Energy { get; set; } + public int Seconds { get; set; } + public int ActualPower { get; set; } + public double TimeStamp { get; } + + public Meting(int heartbeat, int rpm, double speed, double distance, int power, int energy, int seconds, int actualpower, double timestamp) + { + HeartBeat = heartbeat; + RPM = rpm; + Speed = speed; + Distance = distance; + Power = power; + Energy = energy; + Seconds = seconds; + ActualPower = actualpower; + TimeStamp = timestamp; + } + + public override string ToString() + { + string temp = ""; + temp += "Heartbeat: " + HeartBeat + "\n"; + temp += "RPM: " + RPM + "\n"; + temp += "Speed: " + Speed + "\n"; + temp += "Distance: " + Distance + "\n"; + temp += "Power: " + Power + "\n"; + temp += "Energy: " + Energy + "\n"; + temp += "Seconds: " + Seconds + "\n"; + temp += "ActualPower: " + ActualPower + "\n"; + return temp; + } + + public string ToCommand() + { + string temp = ""; + temp += HeartBeat + "»"; + temp += RPM + "»"; + temp += Speed + "»"; + temp += Distance + "»"; + temp += Power + "»"; + temp += Energy + "»"; + temp += Seconds + "»"; + temp += ActualPower + "»"; + temp += TimeStamp; + return temp; + } + + public static Meting Parse(string input) + { + return Parse(input, '\t'); + } + + public static Meting Parse(string input, char delimiter) + { + string[] status = input.Split(delimiter); + if (status.Length != 8 && status.Length != 9) + { + throw new FormatException("Error in Meting: Arguments do not match"); + } + int heartbeat = int.Parse(status[0]); + int rpm = int.Parse(status[1]); + double speed = double.Parse(status[2]) / 10; + double distance = double.Parse(status[3]) / 10; + int power = int.Parse(status[4]); + int energy = int.Parse(status[5]); + int actualpower = int.Parse(status[7]); + + double timestamp = 0; + if (status.Length == 9) + timestamp = double.Parse(status[8]); + else + timestamp = Helper.Now; + + string[] temp = status[6].Split(':'); + int seconds = 0; + if (temp.Length == 2) + { + seconds = (int.Parse(temp[0]) * 60) + (int.Parse(temp[1])); + } + else + { + seconds = int.Parse(status[6]); + } + + return new Meting(heartbeat, rpm, speed, distance, power, energy, seconds, actualpower,timestamp); + } + } +} diff --git a/ErgometerIPR/ErgometerLibrary/NetCommand.cs b/ErgometerIPR/ErgometerLibrary/NetCommand.cs new file mode 100644 index 0000000..fa1a753 --- /dev/null +++ b/ErgometerIPR/ErgometerLibrary/NetCommand.cs @@ -0,0 +1,424 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Win32; + +namespace ErgometerLibrary +{ + public class NetCommand + { + public enum CommandType { LOGIN, DATA, CHAT, LOGOUT, SESSION, VALUESET, USER, RESPONSE, REQUEST, LENGTH, SESSIONDATA, ERROR, BROADCAST } + public enum RequestType { USERS, ALLSESSIONS, OLDDATA, SESSIONDATA, CHAT } + public enum ResponseType { LOGINOK, LOGINWRONG, ERROR, NOTLOGGEDIN } + public enum ValueType { TIME, POWER, ENERGY, DISTANCE } + public enum LengthType { USERS, SESSIONS, SESSIONDATA, DATA } + + public double Timestamp { get; set; } + public int Session { get; set; } + public CommandType Type { get; set; } + public ResponseType Response { get; set; } + public ValueType Value { get; set; } + public RequestType Request { get; set; } + public LengthType Length { get; set; } + public int SetValue { get; set; } + public string DisplayName { get; set; } + public bool IsDoctor { get; set; } + public string Password { get; set; } + public string ChatMessage { get; set; } + public Meting Meting { get; set; } + public int LengthValue { get; set; } + + //SESSION + public NetCommand(int session) + { + Type = CommandType.SESSION; + Session = session; + Timestamp = Helper.Now; + } + + //SESSIONDATA + public NetCommand(string name, double timestamp, int session) + { + Type = CommandType.SESSIONDATA; + Session = session; + Timestamp = timestamp; + DisplayName = name; + } + + //RESPONSE + public NetCommand(ResponseType response, int session) + { + Type = CommandType.RESPONSE; + Session = session; + Timestamp = Helper.Now; + Response = response; + } + + //Length + public NetCommand(LengthType lengthtype, int length, int session) + { + Type = CommandType.LENGTH; + Length = lengthtype; + Session = session; + Timestamp = Helper.Now; + LengthValue = length; + } + + //REQUEST + public NetCommand(RequestType request, int session) + { + Type = CommandType.REQUEST; + Session = session; + Timestamp = Helper.Now; + Request = request; + } + + //STANDARD + public NetCommand(CommandType commandtype, int session) + { + Type = commandtype; + Session = session; + Timestamp = Helper.Now; + } + + //METING + public NetCommand(Meting m, int session) + { + Type = CommandType.DATA; + Session = session; + Timestamp = Helper.Now; + + Meting = m; + } + + //CHAT + public NetCommand(string chat, bool isDoctor, int session) + { + Type = CommandType.CHAT; + Session = session; + IsDoctor = isDoctor; + Timestamp = Helper.Now; + + ChatMessage = chat.Replace("\n", ""); + } + + //BROADCAST + public NetCommand(string broadcast, int session) + { + Type = CommandType.BROADCAST; + Session = session; + Timestamp = Helper.Now; + + ChatMessage = broadcast.Replace("\n", ""); + } + + //SETVALUE + public NetCommand(ValueType value, int val, int session) + { + Type = CommandType.VALUESET; + Session = session; + Value = value; + SetValue = val; + } + + //USER + public NetCommand(string username, string password, int session) + { + Type = CommandType.USER; + Session = session; + DisplayName = username; + Password = password; + } + + //LOGIN + public NetCommand(string name, bool doctor, string password, int session) + { + Type = CommandType.LOGIN; + Session = session; + Timestamp = Helper.Now; + + DisplayName = name; + IsDoctor = doctor; + Password = password; + } + + public static NetCommand Parse(string command) + { + string[] com = command.Split('»'); + + int comType = 0; + try + { + comType = int.Parse(com[0]); + } + catch(Exception e) + { + return new NetCommand(CommandType.ERROR, 0); + } + + int session = 0; + if (com[1].StartsWith("ses")) + session = int.Parse(com[1].Substring(3)); + else + throw new FormatException("Error in NetCommand: " + com[1] + " is not a valid session."); + + string[] args = new string[com.Length - 2]; + for (int i = 2; i < com.Length; i++) + { + args[i - 2] = com[i]; + } + + switch (comType) + { + case 1: + return ParseLoginRequest(session, args); + case 2: + return ParseData(session, args); + case 3: + return ParseChatMessage(session, args); + case 4: + return ParseLogoutRequest(session, args); + case 5: + return ParseSession(session); + case 6: + return ParseResponse(session, args); + case 7: + return ParseValue(session, args); + case 8: + return ParseUser(session, args); + case 9: + return ParseRequest(session, args); + case 10: + return ParseLength(session, args); + case 11: + return ParseSessionData(session, args); + case 12: + return ParseBroadcast(session, args); + default: + throw new FormatException("Error in NetCommand: " + comType + " is not a valid command type."); + } + } + + private static NetCommand ParseBroadcast(int session, string[] args) + { + if (args.Length != 1) + throw new MissingFieldException("Error in NetCommand: Broadcast Message is missing arguments"); + + NetCommand temp = new NetCommand(args[0], session); + + return temp; + } + + private static NetCommand ParseSessionData(int session, string[] args) + { + if (args.Length != 2) + throw new MissingFieldException("Error in NetCommand: Session Data is missing arguments"); + + NetCommand temp = new NetCommand(args[0], double.Parse(args[1]), session); + + return temp; + } + + private static NetCommand ParseLength(int session, string[] args) + { + if (args.Length != 2) + throw new MissingFieldException("Error in NetCommand: Length is missing arguments"); + + switch (args[0]) + { + case "users": + return new NetCommand(LengthType.USERS, int.Parse(args[1]), session); + case "sessiondata": + return new NetCommand(LengthType.SESSIONDATA, int.Parse(args[1]), session); + case "sessions": + return new NetCommand(LengthType.SESSIONS, int.Parse(args[1]), session); + case "data": + return new NetCommand(LengthType.DATA, int.Parse(args[1]), session); + default: + throw new FormatException("Error in NetCommand: Length type not recognised"); + } + } + + private static NetCommand ParseRequest(int session, string[] args) + { + if (args.Length != 1) + throw new MissingFieldException("Error in NetCommand: Request is missing arguments"); + + switch (args[0]) + { + case "users": + return new NetCommand(RequestType.USERS, session); + case "allsessions": + return new NetCommand(RequestType.ALLSESSIONS, session); + case "olddata": + return new NetCommand(RequestType.OLDDATA, session); + case "sessiondata": + return new NetCommand(RequestType.SESSIONDATA, session); + case "chat": + return new NetCommand(RequestType.CHAT, session); + default: + throw new FormatException("Error in NetCommand: Request type not recognised"); + } + } + + private static NetCommand ParseUser(int session, string[] args) + { + if (args.Length != 2) + throw new MissingFieldException("Error in NetCommand: User is missing arguments"); + + NetCommand temp = new NetCommand(args[0], Helper.Base64Decode(args[1]), session); + + return temp; + } + + private static NetCommand ParseValue(int session, string[] args) + { + if (args.Length != 2) + throw new MissingFieldException("Error in NetCommand: SetValue is missing arguments"); + + switch (args[0]) + { + case "time": + return new NetCommand(ValueType.TIME, int.Parse(args[1]), session); + case "power": + return new NetCommand(ValueType.POWER, int.Parse(args[1]), session); + case "energy": + return new NetCommand(ValueType.ENERGY, int.Parse(args[1]), session); + case "distance": + return new NetCommand(ValueType.DISTANCE, int.Parse(args[1]), session); + default: + throw new FormatException("Error in NetCommand: SetValue type not recognised"); + } + } + + private static NetCommand ParseResponse(int session, string[] args) + { + if (args.Length != 1) + throw new MissingFieldException("Error in NetCommand: Response is missing arguments"); + + switch (args[0]) + { + case "loginok": + return new NetCommand(ResponseType.LOGINOK, session); + case "loginwrong": + return new NetCommand(ResponseType.LOGINWRONG, session); + case "notloggedin": + return new NetCommand(ResponseType.NOTLOGGEDIN, session); + case "error": + return new NetCommand(ResponseType.ERROR, session); + default: + throw new FormatException("Error in NetCommand: Response type not recognised"); + } + } + + private static NetCommand ParseSession(int session) + { + NetCommand temp = new NetCommand(CommandType.SESSION, session); + return temp; + } + + private static NetCommand ParseLogoutRequest(int session, string[] args) + { + if (args.Length != 1) + throw new MissingFieldException("Error in NetCommand: Logout Request is missing arguments"); + + NetCommand temp = new NetCommand(CommandType.LOGOUT, session); + if (args[0] != "logout") + throw new FormatException("Error in NetCommand: " + args[0] + " is not a valid logout request"); + + return temp; + } + + private static NetCommand ParseChatMessage(int session, string[] args) + { + if (args.Length != 2) + throw new MissingFieldException("Error in NetCommand: Chat Message is missing arguments"); + + NetCommand temp = new NetCommand(CommandType.CHAT, session); + temp.ChatMessage = args[0]; + temp.IsDoctor = bool.Parse(args[1]); + + return temp; + } + + private static NetCommand ParseData(int session, string[] args) + { + if (args.Length != 9) + throw new MissingFieldException("Error in NetCommand: Data is missing arguments"); + + NetCommand temp = new NetCommand(CommandType.DATA, session); + temp.Meting = Meting.Parse(string.Join("\t", args)); + + return temp; + } + + private static NetCommand ParseLoginRequest(int session, string[] args) + { + bool doctor = bool.Parse(args[2]); + if (args.Length != 3) + throw new MissingFieldException("Error in NetCommand: Doctor login is missing arguments"); + + NetCommand temp = new NetCommand(CommandType.LOGIN, session); + temp.IsDoctor = doctor; + temp.DisplayName = args[0]; + temp.Password = Helper.Base64Decode(args[1]); + + return temp; + } + + public override string ToString() + { + string command = ""; + + switch (Type) + { + case CommandType.LOGIN: + command += "1»ses" + Session + "»" + DisplayName + "»" + Helper.Base64Encode(Password) + "»" + IsDoctor; + break; + case CommandType.DATA: + command += "2»ses" + Session + "»" + Meting.ToCommand(); + break; + case CommandType.CHAT: + command += "3»ses" + Session + "»" + ChatMessage + "»" + IsDoctor; + break; + case CommandType.LOGOUT: + command += "4»ses" + Session + "»logout"; + break; + case CommandType.SESSION: + command += "5»ses" + Session; + break; + case CommandType.RESPONSE: + command += "6»ses" + Session + "»" + Response.ToString().ToLower(); + break; + case CommandType.VALUESET: + command += "7»ses" + Session + "»" + Value.ToString().ToLower() + "»" + SetValue; + break; + case CommandType.USER: + command += "8»ses" + Session + "»" + DisplayName + "»" + Helper.Base64Encode(Password); + break; + case CommandType.REQUEST: + command += "9»ses" + Session + "»" + Request.ToString().ToLower(); + break; + case CommandType.LENGTH: + command += "10»ses" + Session + "»" + Length.ToString().ToLower() + "»" + LengthValue; + break; + case CommandType.SESSIONDATA: + command += "11»ses" + Session + "»" + DisplayName + "»" + Timestamp; + break; + case CommandType.BROADCAST: + command += "12»ses" + Session + "»" + ChatMessage; + break; + case CommandType.ERROR: + command += "ERROR IN NETCOMMAND"; + break; + + default: + throw new FormatException("Error in NetCommand: Cannot find type of command"); + } + + return command; + } + } +} diff --git a/ErgometerIPR/ErgometerLibrary/NetHelper.cs b/ErgometerIPR/ErgometerLibrary/NetHelper.cs new file mode 100644 index 0000000..d344b6b --- /dev/null +++ b/ErgometerIPR/ErgometerLibrary/NetHelper.cs @@ -0,0 +1,135 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Text; + +namespace ErgometerLibrary +{ + public class NetHelper + { + public static bool SendNetCommand(TcpClient client, NetCommand command) + { + /* + byte[] b = Encoding.Unicode.GetBytes(command.ToString()); + client.GetStream().Write(b, 0, b.Length); + client.GetStream().Flush(); + */ + return SendString(client, command.ToString()); + } + + public static bool SendString(TcpClient client, string command) + { + /* + byte[] b = Encoding.Unicode.GetBytes(command); + client.GetStream().Write(b, 0, b.Length); + client.GetStream().Flush(); + */ + + if (client != null && client.Connected) + { + try + { + StreamWriter wr = new StreamWriter(client.GetStream(), Encoding.Unicode); + wr.WriteLine(AESEncrypt.EncryptToString(command)); + wr.Flush(); + return true; + } + catch (Exception e) + { + return false; + } + } + else + return false; + } + + public static NetCommand ReadNetCommand(TcpClient client) + { + /* + byte[] bytesFrom = new byte[(int) client.ReceiveBufferSize]; + client.GetStream().Read(bytesFrom, 0, (int)client.ReceiveBufferSize); + string response = Encoding.Unicode.GetString(bytesFrom); + NetCommand net = NetCommand.Parse(response); + return net; + */ + + string str; + NetCommand net; + str = ReadString(client); + + if (str != "") + { + try + { + str = AESEncrypt.DecryptString(str); + } + catch (Exception e) + { + str = ""; + net = new NetCommand(NetCommand.CommandType.ERROR, 0); + } + try { + net = NetCommand.Parse(str); + } + catch (Exception e) + { + net = new NetCommand(NetCommand.CommandType.ERROR, 0); + } + } + else + net = new NetCommand(NetCommand.CommandType.ERROR, 0); + return net; + } + + public static string ReadString(TcpClient client) + { + /* + byte[] bytesFrom = new byte[(int)client.ReceiveBufferSize]; + client.GetStream().Read(bytesFrom, 0, (int)client.ReceiveBufferSize); + string response = Encoding.Unicode.GetString(bytesFrom); + return response; + */ + if (client != null && client.Connected) + { + try { + StreamReader rd = new StreamReader(client.GetStream(), Encoding.Unicode); + string str = rd.ReadLine(); + return str; + } + catch(Exception e) + { + return ""; + } + } + else + return ""; + } + + public static IPAddress GetIP(string ipstring) + { + IPAddress ip; + + bool ipIsOk = IPAddress.TryParse(ipstring, out ip); + if (!ipIsOk) { return null; } + + return ip; + } + + public static IPAddress GetIP() + { + IPHostEntry host; + string localIP = "?"; + host = Dns.GetHostEntry(Dns.GetHostName()); + foreach (IPAddress ip in host.AddressList) + { + if (ip.AddressFamily == AddressFamily.InterNetwork) + { + localIP = ip.ToString(); + } + } + return GetIP(localIP); + } + } +} diff --git a/ErgometerIPR/ErgometerLibrary/Properties/AssemblyInfo.cs b/ErgometerIPR/ErgometerLibrary/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f721bc3 --- /dev/null +++ b/ErgometerIPR/ErgometerLibrary/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ErgometerLibrary")] +[assembly: AssemblyDescription("Library with the most common methods and classes for the Ergometer")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Projectgroep A5")] +[assembly: AssemblyProduct("ErgometerLibrary")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("cc20ff4e-8751-42a1-a32b-0e5b22452cac")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.2.0.0")] +[assembly: AssemblyFileVersion("1.0.3.7")] diff --git a/ErgometerIPR/ErgometerLibrary/packages.config b/ErgometerIPR/ErgometerLibrary/packages.config new file mode 100644 index 0000000..64b5d8e --- /dev/null +++ b/ErgometerIPR/ErgometerLibrary/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerServer/App.config b/ErgometerIPR/ErgometerServer/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/ErgometerIPR/ErgometerServer/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerServer/ClientThread.cs b/ErgometerIPR/ErgometerServer/ClientThread.cs new file mode 100644 index 0000000..3194a9b --- /dev/null +++ b/ErgometerIPR/ErgometerServer/ClientThread.cs @@ -0,0 +1,137 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ErgometerServer +{ + class ClientThread + { + TcpClient client; + Server server; + + public string name; + public int session { get; } + + bool running; + bool loggedin; + + List metingen; + List chat; + + public ClientThread(TcpClient client, Server server) + { + this.client = client; + this.server = server; + this.name = "Unknown"; + this.session = 0; + this.running = false; + this.loggedin = false; + + metingen = new List(); + chat = new List(); + session = FileHandler.GenerateSession(); + Console.WriteLine("Generated new session: " + session); + } + + public void run() + { + running = true; + + NetHelper.SendNetCommand(client, new NetCommand(session)); + + while (running) + { + NetCommand input = NetHelper.ReadNetCommand(client); + + switch(input.Type) + { + case NetCommand.CommandType.SESSION: + + break; + case NetCommand.CommandType.LOGIN: + if(! server.CheckPassword(input.DisplayName, input.Password)) + { + NetHelper.SendNetCommand(client, new NetCommand(NetCommand.ResponseType.LOGINWRONG, session)); + loggedin = false; + } + else + { + NetHelper.SendNetCommand(client, new NetCommand(NetCommand.ResponseType.LOGINOK, session)); + loggedin = true; + + if (input.IsDoctor) + { + server.ChangeClientToDoctor(client, this); + Console.WriteLine("Doctor connected"); + running = false; + } + else + { + name = input.DisplayName; + loggedin = true; + FileHandler.CreateSession(session, name); + } + } + break; + case NetCommand.CommandType.DATA: + if (loggedin) + { + metingen.Add(input.Meting); + server.SendToDoctor(input); + } + else + NetHelper.SendNetCommand(client, new NetCommand(NetCommand.ResponseType.NOTLOGGEDIN, session)); + + break; + case NetCommand.CommandType.CHAT: + if (loggedin) + { + chat.Add(new ChatMessage(name, input.ChatMessage, false)); + server.SendToDoctor(input); + } + else + NetHelper.SendNetCommand(client, new NetCommand(NetCommand.ResponseType.NOTLOGGEDIN, session)); + break; + case NetCommand.CommandType.LOGOUT: + loggedin = false; + running = false; + Console.WriteLine(name + " logged out"); + FileHandler.WriteMetingen(session, metingen); + FileHandler.WriteChat(session, chat); + client.Close(); + break; + case NetCommand.CommandType.ERROR: + Console.WriteLine("An error occured, assuming client disconnected"); + loggedin = false; + running = false; + Console.WriteLine(name + " logged out due to an error"); + FileHandler.WriteMetingen(session, metingen); + FileHandler.WriteChat(session, chat); + client.Close(); + break; + default: + if(loggedin) + throw new FormatException("Unknown command"); + break; + } + } + + server.RemoveActiveSession(this); + } + + public void SendToClient(NetCommand command) + { + NetHelper.SendNetCommand(client, command); + if (command.Type == NetCommand.CommandType.CHAT) + { + chat.Add(new ChatMessage("Doctor", command.ChatMessage, true)); + } + } + } +} diff --git a/ErgometerIPR/ErgometerServer/DoctorThread.cs b/ErgometerIPR/ErgometerServer/DoctorThread.cs new file mode 100644 index 0000000..d4b062e --- /dev/null +++ b/ErgometerIPR/ErgometerServer/DoctorThread.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections; +using System.Net.Sockets; +using System.Runtime.Remoting.Lifetime; +using System.Threading; +using ErgometerLibrary; +using System.Collections.Generic; + +namespace ErgometerServer +{ + class DoctorThread + { + TcpClient client; + Server server; + + bool running; + + public DoctorThread(TcpClient client, Server server) + { + this.client = client; + this.server = server; + + this.running = false; + } + + public void run() + { + running = true; + while (running) + { + NetCommand input = NetHelper.ReadNetCommand(client); + + switch (input.Type) + { + case NetCommand.CommandType.LOGOUT: + running = false; + client.Close(); + Console.WriteLine("Doctor logged out"); + break; + case NetCommand.CommandType.CHAT: + server.SendToClient(input); + break; + case NetCommand.CommandType.BROADCAST: + server.BroadcastToClients(input.ChatMessage); + break; + case NetCommand.CommandType.VALUESET: + server.SendToClient(input); + break; + case NetCommand.CommandType.USER: + server.AddUser(input.DisplayName, input.Password); + break; + case NetCommand.CommandType.REQUEST: + switch (input.Request) + { + case NetCommand.RequestType.USERS: + sendToDoctor(new NetCommand(NetCommand.LengthType.USERS, server.users.Count-1, input.Session)); + foreach (KeyValuePair user in server.users) + { + Thread.Sleep(10); + if(user.Key != "Doctor0tVfW") + sendToDoctor(new NetCommand(user.Key, user.Value, input.Session)); + } + break; + case NetCommand.RequestType.CHAT: + List chat = FileHandler.ReadChat(input.Session); + foreach (ChatMessage msg in chat) + { + Thread.Sleep(10); + sendToDoctor(new NetCommand(msg.Message, msg.IsDoctor, input.Session)); + } + break; + case NetCommand.RequestType.OLDDATA: + List metingen = FileHandler.ReadMetingen(input.Session); + foreach (Meting meting in metingen) + { + Thread.Sleep(10); + sendToDoctor(new NetCommand(meting, input.Session)); + } + break; + case NetCommand.RequestType.ALLSESSIONS: + List> sessions = FileHandler.GetAllSessions(); + sendToDoctor(new NetCommand(NetCommand.LengthType.SESSIONS, sessions.Count, input.Session)); + foreach(Tuple session in sessions) + { + sendToDoctor(new NetCommand(session.Item2, session.Item3, session.Item1)); + + Thread.Sleep(10); + } + break; + case NetCommand.RequestType.SESSIONDATA: + List> currentsessionsdata = server.GetRunningSessionsData(); + sendToDoctor(new NetCommand(NetCommand.LengthType.SESSIONDATA, currentsessionsdata.Count, input.Session)); + foreach (Tuple ses in currentsessionsdata) + { + sendToDoctor(new NetCommand(ses.Item2, Helper.Now, ses.Item1)); + Thread.Sleep(10); + } + break; + default: + throw new FormatException("Unknown Command"); + } + + break; + case NetCommand.CommandType.ERROR: + Console.WriteLine("An error occured, assuming docter disconnected"); + running = false; + Console.WriteLine("Doctor logged out due to an error"); + client.Close(); + break; + default: + throw new FormatException("Unknown Command"); + } + } + } + + public void sendToDoctor(NetCommand command) + { + NetHelper.SendNetCommand(client, command); + } + } +} \ No newline at end of file diff --git a/ErgometerIPR/ErgometerServer/ErgometerServer.csproj b/ErgometerIPR/ErgometerServer/ErgometerServer.csproj new file mode 100644 index 0000000..ebff0f3 --- /dev/null +++ b/ErgometerIPR/ErgometerServer/ErgometerServer.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {398293F9-9DF4-4F7A-AC85-6CF488B08B29} + Exe + Properties + ErgometerServer + ErgometerServer + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\..\..\ErgometerLibrary\ErgometerLibrary\ErgometerLibrary\bin\Debug\ErgometerLibrary.dll + + + ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ErgometerIPR/ErgometerServer/FileHandler.cs b/ErgometerIPR/ErgometerServer/FileHandler.cs new file mode 100644 index 0000000..06839e6 --- /dev/null +++ b/ErgometerIPR/ErgometerServer/FileHandler.cs @@ -0,0 +1,204 @@ +using ErgometerLibrary; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace ErgometerServer +{ + public class FileHandler + { + public static string DataFolder { get; } = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments), "Ergometer"); + public static string UsersFile { get; } = Path.Combine(DataFolder, "users.ergo"); + + public static void CheckStorage() + { + if (!Directory.Exists(DataFolder)) + { + Directory.CreateDirectory(DataFolder); + } + + if (!File.Exists(UsersFile)) + { + using (Stream stream = File.Open(UsersFile, FileMode.Create)) + { + BinaryWriter writer = new BinaryWriter(stream); + writer.Write(1); + writer.Write("Doctor0tVfW"); + writer.Write(Helper.Base64Encode("password")); + } + } + } + + //SESSION + public static int GenerateSession() + { + string[] existingSessions = Directory.GetDirectories(DataFolder); + + Random rand = new Random(); + int sessionID = rand.Next(0, int.MaxValue); + + + while (existingSessions.Contains(sessionID.ToString())) + { + sessionID = rand.Next(int.MinValue, int.MaxValue); + } + + return sessionID; + } + + public static void CreateSession(int session, string naam) + { + Directory.CreateDirectory(GetSessionFolder(session)); + + using (File.Create(Path.Combine(GetSessionFile(session)))) ; + using (File.Create(Path.Combine(GetSessionMetingen(session)))) ; + using (File.Create(Path.Combine(GetSessionChat(session)))) ; + + File.WriteAllText(GetSessionFile(session), naam + Environment.NewLine + Helper.Now); + Console.WriteLine("Created session at " + Helper.MillisecondsToTime(Helper.Now)); + } + + public static void WriteMetingen(int session, List metingen) + { + if (metingen.Count <= 20 && Directory.Exists(GetSessionFolder(session))) + { + Directory.Delete(GetSessionFolder(session), true); + } + else + { + string json = Newtonsoft.Json.JsonConvert.SerializeObject(metingen.ToArray()); + File.WriteAllText(GetSessionMetingen(session), json); + Console.WriteLine("Writing metingen: " + GetSessionMetingen(session)); + File.WriteAllText(GetSessionFile(session), File.ReadAllText(GetSessionFile(session)) + Environment.NewLine + Helper.Now); + } + + } + + public static List ReadMetingen(int session) + { + string json = File.ReadAllText(GetSessionMetingen(session)); + + List metingen = Newtonsoft.Json.JsonConvert.DeserializeObject>(json); + Console.WriteLine("Reading metingen: " + GetSessionMetingen(session)); + return metingen; + } + + public static List> GetAllSessions() + { + string[] directories = Directory.GetDirectories(DataFolder); + List> sessiondata = new List>(); + + for (int i = 0; i < directories.Length; i++) + { + string directoryname = Path.GetFileName(directories[i]); + + string props = File.ReadAllText(GetSessionFile(int.Parse(directoryname))); + + string[] stringSeparators = new string[] { "\r\n" }; + string[] properties = props.Split(stringSeparators, StringSplitOptions.None); + + int session = int.Parse(directoryname); + + bool active = false; + + foreach(ClientThread t in Server.clients) + { + if (t.session == session) + active = true; + } + + string name = properties[0]; + double date = double.Parse(properties[1]); + + if (!active) + { + Tuple tup = new Tuple(session, name, date); + sessiondata.Add(tup); + } + } + + return sessiondata; + } + + public static void WriteChat(int session, List chat) + { + if (Directory.Exists(GetSessionFolder(session))) + { + /* + string write = ""; + foreach (ChatMessage c in chat) + { + write += c.ToString() + "\n"; + } + + File.WriteAllText(GetSessionChat(session), write); + Console.WriteLine("Writing chat: " + GetSessionChat(session)); + */ + string json = Newtonsoft.Json.JsonConvert.SerializeObject(chat.ToArray()); + File.WriteAllText(GetSessionChat(session), json); + Console.WriteLine("Writing chat: " + GetSessionChat(session)); + } + } + + public static List ReadChat(int session) + { + string json = File.ReadAllText(GetSessionChat(session)); + + List chat = Newtonsoft.Json.JsonConvert.DeserializeObject>(json); + return chat; + } + + private static string GetSessionFolder(int session) + { + return Path.Combine(DataFolder, session.ToString()); + } + private static string GetSessionFile(int session) + { + return Path.Combine(DataFolder, session.ToString(), "session.prop"); + } + private static string GetSessionMetingen(int session) + { + return Path.Combine(DataFolder, session.ToString(), "metingen.ergo"); + } + private static string GetSessionChat(int session) + { + return Path.Combine(DataFolder, session.ToString(), "chat.ergo"); + } + + //USER MANAGEMENT + public static Dictionary LoadUsers() + { + Dictionary users = new Dictionary(); + + using (Stream stream = File.Open(UsersFile, FileMode.Open)) + { + BinaryReader reader = new BinaryReader(stream); + int count = reader.ReadInt32(); + for (int n = 0; n < count; n++) + { + var key = reader.ReadString(); + var value = Helper.Base64Decode(reader.ReadString()); + users.Add(key, value); + } + } + + return users; + } + + public static void SaveUsers(Dictionary users) + { + using (Stream stream = File.Open(UsersFile, FileMode.Open)) + { + BinaryWriter writer = new BinaryWriter(stream); + writer.Write(users.Count); + foreach (var kvp in users) + { + writer.Write(kvp.Key); + writer.Write(Helper.Base64Encode(kvp.Value)); + } + writer.Flush(); + } + } + } +} diff --git a/ErgometerIPR/ErgometerServer/Properties/AssemblyInfo.cs b/ErgometerIPR/ErgometerServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c6e0abe --- /dev/null +++ b/ErgometerIPR/ErgometerServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ErgometerServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ErgometerServer")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("398293f9-9df4-4f7a-ac85-6cf488b08b29")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ErgometerIPR/ErgometerServer/Server.cs b/ErgometerIPR/ErgometerServer/Server.cs new file mode 100644 index 0000000..22f3bdb --- /dev/null +++ b/ErgometerIPR/ErgometerServer/Server.cs @@ -0,0 +1,167 @@ +using ErgometerLibrary; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace ErgometerServer +{ + class Server + { + static void Main(string[] args) + { + new Server(); + } + + public static List clients = new List(); + private DoctorThread doctor; + public Dictionary users; + + public Server() + { + AppDomain.CurrentDomain.ProcessExit += new EventHandler(OnProcessExit); + + FileHandler.CheckStorage(); + + users = FileHandler.LoadUsers(); + + TcpListener listener = new TcpListener(NetHelper.GetIP("127.0.0.1"), 8888); + //TcpListener listener = new TcpListener(NetHelper.GetIP(GetIp()), 8888); + listener.Start(); + + Console.WriteLine("Server started successfully..."); + + while (true) + { + Console.WriteLine("Waiting for connection with client..."); + + //AcceptTcpClient waits for a connection from the client + TcpClient client = listener.AcceptTcpClient(); + + Console.WriteLine("Client connected"); + + //Start new client + ClientThread cl = new ClientThread(client, this); + clients.Add(cl); + + //Run client on new thread + Thread thread = new Thread(new ThreadStart(cl.run)); + thread.IsBackground = true; + thread.Start(); + } + } + + public void ChangeClientToDoctor(TcpClient client, ClientThread clth) + { + clients.Remove(clth); + doctor = new DoctorThread(client, this); + Thread thread = new Thread(new ThreadStart(doctor.run)); + thread.IsBackground = true; + thread.Start(); + } + + public void SendToDoctor(NetCommand command) + { + if (doctor != null) + { + doctor.sendToDoctor(command); + } + } + + public void BroadcastToClients(string message) + { + foreach (ClientThread clientThread in clients) + { + clientThread.SendToClient(new NetCommand(message, true, clientThread.session)); + } + } + + public void SendToClient(NetCommand command) + { + foreach (ClientThread clientThread in clients) + { + if (clientThread.session == command.Session) + { + clientThread.SendToClient(command); + } + } + } + + public void AddUser(string name, string password) + { + users.Add(name, password); + FileHandler.SaveUsers(users); + } + + public void AddUser(Dictionary users) + { + foreach(KeyValuePair user in users) + { + users.Add(user.Key, user.Value); + } + + FileHandler.SaveUsers(users); + } + + public bool CheckPassword(string name, string password) + { + string pass; + bool isOk = users.TryGetValue(name, out pass); + + if (!isOk) return false; + + return pass == password; + } + + public List GetRunningSessions() + { + List sessions = new List(); + foreach (ClientThread thread in clients) + { + sessions.Add(thread.session); + } + return sessions; + } + + public List> GetRunningSessionsData() + { + List> sessions = new List>(); + foreach (ClientThread thread in clients) + { + sessions.Add(new Tuple(thread.session, thread.name)); + } + return sessions; + } + + internal void RemoveActiveSession(ClientThread clientThread) + { + clients.Remove(clientThread); + } + + private string GeneratePassword(int len = 8) + { + string pass = ""; + + char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray(); + + Random rand = new Random(); + + for (int i=0; i<=len; i++) + { + pass += chars[rand.Next(0, chars.Length - 1)]; + } + + return pass; + } + + private static void OnProcessExit(object sender, EventArgs e) + { + Console.WriteLine("Closing server"); + } + } + +} diff --git a/ErgometerIPR/ErgometerServer/packages.config b/ErgometerIPR/ErgometerServer/packages.config new file mode 100644 index 0000000..64b5d8e --- /dev/null +++ b/ErgometerIPR/ErgometerServer/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file