/// Version 1.0.0 /// Last modified 24.5.2014 /// /// Copyright (C) 2014 Veli-Mikko Puupponen, Ilkka Rautiainen, Atte Söderlund and Niko Mononen. /// /// Halyri-system is a prototype emergency call system. Its purpose is to /// demonstrate the use of the advanced capabilities available in the current /// generation smartphones in facilitating the emergency service dispatcher's /// capability to determine the nature of the emergency and to dispatch help. /// /// For more information, see the README file of this package. /// /// The MIT License (MIT) /// /// Permission is hereby granted, free of charge, to any person obtaining a copy /// of this software and associated documentation files (the "Software"), to /// deal in the Software without restriction, including without limitation the /// rights to use, copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit persons to whom the /// Software is furnished to do so, subject to the following conditions: /// /// The above copyright notice and this permission notice shall be included in /// all copies or substantial portions of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING /// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS /// IN THE SOFTWARE. /// using Hake_WPF.CallCenterReference; using Hake_WPF.Conversion; using System; using System.Diagnostics; using System.Linq; using System.ServiceModel; using System.Threading.Tasks; using System.Collections.ObjectModel; using Microsoft.Maps.MapControl.WPF; using System.Threading; using SimpleUdpMediaClient; using SimpleUdpMediaClient.Packets; using System.Windows.Media; namespace Hake_WPF { /// /// The class delegate is invoked when the connection receives an update. /// /// List of the connections received from the server. public delegate void ConnectionsUpdated(ConnectionDto[] newConnections); /// /// The class delegate is invoked when TcpMedia is received. /// /// Source object that invokes this delegate. /// Information about recived data. /// Recived data packet. public delegate void TcpMediaDataReceived(object sender, MediaInformationDto info, byte[] data); /// /// The class delegate is invoked when udp media is received. /// /// Source object that invokes this delegate. /// Recived media packet. public delegate void UdpMediaDataReceived(object sender, MediaPacket media); /// /// The class delegate is invoked when measurement data media is received. /// /// Source object that invokes this delegate. /// Delegate event arguments. /// Sending instrument. /// Recived measurement data. /// Samples per second for recived measurement data. public delegate void MeasurementDataReceivedDelegate(object sender, EventArgs e, MeasurementInstrumentDto instrument, byte[] measurementData, int dataSamplesPerSecond); /// /// The class receives data from the server which invokes an event. /// class AsyncReceiver : IWcfCallCenterServiceCallback { /// /// It is a delegate that is triggered when new media is received. /// public TcpMediaDataReceived TcpMediaDataReceivedEvent; /// /// It is a delegate that is triggered when meausrement data is received. /// public MeasurementDataReceivedDelegate MeasurementDataReceivedEvent; /// /// It is a event that is triggered when the server tells the client that the connections have changed. /// public event ConnectionsUpdated ConnectionsUpdatedEvent; /// Veli-Mikko Puupponen /// /// The class delegate is invoked everytime the client receives an update for assignment from the server. /// This method only invokes ConnectionsUpdatedEvent so Connection class can handle it. /// /// List of changed connections. public void ActiveConnectionsUpdated(ConnectionDto[] updatedConnections) { if (ConnectionsUpdatedEvent != null) ConnectionsUpdatedEvent(updatedConnections); foreach (ConnectionDto connection in updatedConnections) { Debug.WriteLine("Connection updated:" + connection.ArrivalTime.ToShortTimeString() + " " + connection.ConnectionGuid + " " + connection.ConnectionState.ToString()); } } /// /// The function invokes tcpMediaDataReceivedEvent. /// public void AudioVideoReceived(string sourceGuid, MediaInformationDto mediaInfo, byte[] mediaData) { if (TcpMediaDataReceivedEvent != null) TcpMediaDataReceivedEvent(this, mediaInfo, mediaData); } /// /// The function invokes MeasurementDateReceivedEvent. /// public void MeasurementDataReceived(string sourceGuid, MeasurementInstrumentDto instrument, byte[] measurementData) { if(MeasurementDataReceivedEvent != null) MeasurementDataReceivedEvent(this, EventArgs.Empty, instrument, measurementData, instrument.DataSamplesPerSecond); Debug.WriteLine("MeasurementData received: " + instrument.DeviceType.ToString() + " " + measurementData); } } /// Atte Söderlund /// /// The class handles the communication between the server and this client. A user must use connect method to connect the server. /// After that the user can use rest of the methods to manage connection. Also the client should handle most of the events, but at least /// connectionupdatedevent. /// class Connection { private const int udpServerPort = 15103; private const string udpServerIp = "130.234.9.165"; private WcfCallCenterServiceClient client; private string guid; private InstanceContext context; private CallCenterConnectionDto userConnection; private ObservableCollection connectionDtos = new ObservableCollection(); private ConnectionDto currentConnection; private UserCredentialsDto userCredentialsDto; private UdpMediaClientSocket udpSocket; private bool isConnected = false; /// /// The function gets and sets AsyncReceiver. It handles the connection between the server and the client. /// public AsyncReceiver receiver { get; set; } /// /// The class delegate is invoked when the connection is updated. /// /// The connections as Assignments. public delegate void AssigmentUpdated(ObservableCollection newConnections); /// /// The event is triggered when the assinment is updated. /// public event AssigmentUpdated AssigmentUpdatedEvent; /// /// The class delegate is invoked when the current connection is setted. /// public delegate void AssignmentTakenForHandling(); /// /// The event is triggered when the assinment is taken to handlin. /// public event AssignmentTakenForHandling AssignmentTakenForHandlingEvent; /// /// The class delegate is invoked when udp media is received. /// public UdpMediaDataReceived UdpMediaDataReceivedEvent; /// /// The function creates a connection with the credentials provided. /// /// The username that connection uses. /// The password that connection uses. public Connection(String username, String password) { userCredentialsDto = new UserCredentialsDto() {UserName=username, Password=password }; receiver = new AsyncReceiver(); InitializeUdpClient(); } /// /// The function initializes a new UdpMediaClientSocket instance for media transfer from /// and to the server. /// private void InitializeUdpClient() { udpSocket = new UdpMediaClientSocket(udpServerIp, udpServerPort, guid = Guid.NewGuid().ToString()); udpSocket.MediaPacketReceivedEvent += UdpMediaPacketReceivedHandler; udpSocket.ConnectionFailedEvent += UdpSocketFailedEventHandler; } /// /// Handles incoming media packet events from the active /// UdpMediaClientSocket instance. Republishes them as /// UdpMediaDataReceivedEvent. /// /// UdpMediaDataReceivedEvent event punlishing this event. /// The received MediaPacket instance. private void UdpMediaPacketReceivedHandler(object sender, MediaPacket mediaPacket) { if (UdpMediaDataReceivedEvent != null) UdpMediaDataReceivedEvent(sender, mediaPacket); } /// /// The function creates a receiver and connects to the server with wcf and udp. /// /// ObservableCollection of connections as assignments. public ObservableCollection Connect(String endpointAddress) { receiver.ConnectionsUpdatedEvent += receiver_ConnectionsUpdatedEvent; context = new InstanceContext(receiver); client = new WcfCallCenterServiceClient(new InstanceContext(receiver), new NetTcpBinding() { Security = new NetTcpSecurity() {Mode=SecurityMode.None } }, new EndpointAddress(new Uri(endpointAddress))); try { userConnection = client.Connect(userCredentialsDto); } catch (EndpointNotFoundException endpointExeption) { Debug.WriteLine("Not connected: " + endpointExeption.Message); return new ObservableCollection { }; } catch(Exception ex) { Debug.WriteLine("Error in connecting: " + ex.Message); return new ObservableCollection { }; } Debug.WriteLine("Connected, using guid " + userConnection.ConnectionGuid); udpSocket.Connect(); isConnected = true; return GetAllConnections(); } /// /// The function sends media to the server using the active UdpMediaClientSocket instance. /// It returns true, if the data is successfully queued for sending. Otherwise returns false. /// /// The description of the media. /// The bytes constituting the media data. /// The length of the media prior to the encoding for such encodings /// that require the length for decompression. /// True, if the data is queued for sending, otherwise false. public bool UdpSendMedia(MediaInformation mediaInfo, byte[] data, int originalLength) { if (udpSocket != null) return udpSocket.SendMedia(mediaInfo, data, originalLength); return false; } /// /// Handles failure events of the active UdpMediaClientSocket. /// Currently only writes this information to the debug. /// /// UdpMediaClientSocket instance sending the event. private void UdpSocketFailedEventHandler(object sender) { Debug.WriteLine("Udp socket has failed"); } /// /// The function chances the priority to current connection and then updates that to the connections list and finaly to the server. /// It makes the connection in a new thread so it won't block the ui thread. /// /// The assignment that needs updating. /// The priority for the assignment to be changed. public void ChangePriority(Assignment assignment,Assignment.priorities priority) { Thread thread = new Thread(ChangePriorityThread); thread.Start(priority); } /// /// If there is no current connection this will not do anything. If there is it changes /// current connections priorit and send it to server. /// /// New priority. private void ChangePriorityThread(object priority) { if (currentConnection == null) return; currentConnection.ConnectionPriority = (ConnectionPriorityDto)(int)priority; UpdateConnectionsList(new ConnectionDto[] { currentConnection }); client.SetConnectionPriority(userConnection, currentConnection); } /// /// Handles when connectionupdates are received. Updates connectionslist and invokes event for update with /// connectiondto's converted to assignments. /// /// New connections. private void receiver_ConnectionsUpdatedEvent(ConnectionDto[] newConnections) { UpdateConnectionsList(newConnections); Debug.WriteLine("ReceivedUpdate"); if (AssigmentUpdatedEvent != null) AssigmentUpdatedEvent(ConvertConnectionsToAssigments(newConnections)); } /// /// Replaces connections with new connections by comparing their guid. If there is no /// connection with that guid, adds new connection. /// /// New connetions. private void UpdateConnectionsList(ConnectionDto[] newConnections) { for (int i = 0; i < newConnections.Length;i++ ) { ConnectionDto connection = connectionDtos.FirstOrDefault((connect => connect.ConnectionGuid.Contains(newConnections[i].ConnectionGuid))); if (connection != null) { int index = connectionDtos.IndexOf(connection); connectionDtos.Insert(index, connection); connectionDtos.RemoveAt(index + 1); } else connectionDtos.Add(newConnections[i]); } } /// /// The function converts ConnectionDto to Assignment. If the location is not set, it uses null and do not /// assign the accuracymeters or the time from the location. /// If device info is empty, uses empty DeviceInfoDto. /// /// ConnectionDto that needs converting to Assigment. /// Converted Assigment. public ObservableCollection ConvertConnectionsToAssigments(ConnectionDto[] Connections) { ObservableCollection assignments = new ObservableCollection(); foreach (ConnectionDto connection in Connections) { MobileDeviceInformationDto deviceInfo; LocationInformationDto latestNotUserSpecificLocation = null; LocationInformationDto latestUserSpecificLocation = null; try { latestNotUserSpecificLocation = connection.LocationInformation.OrderByDescending(x => x.AcquisitionTime). First((x =>x.LocationType != LocationTypeDto.UserSpecified)); } catch(InvalidOperationException e) { Debug.WriteLine("There was no location on connectiondto: " + e.Message); } catch(Exception e) { Debug.WriteLine("Error: " + e.Message); } try { latestUserSpecificLocation = connection.LocationInformation.OrderByDescending(x => x.AcquisitionTime) .First((x => x.LocationType == LocationTypeDto.UserSpecified)); } catch (Exception) { } try { deviceInfo = connection.MobileDeviceInformation[0]; } catch (IndexOutOfRangeException) { deviceInfo = new MobileDeviceInformationDto(); } assignments.Add(new Assignment(connection.ConnectionGuid, connection.ArrivalTime, (Assignment.priorities)(int)connection.ConnectionPriority, (Assignment.States)(int)connection.ConnectionState, (latestNotUserSpecificLocation == null)?latestUserSpecificLocation : latestNotUserSpecificLocation) { DeviceInfo = deviceInfo, NoSound = connection.RequestedNoSound, TextMessages = new ObservableCollection(connection.TextMessages.OrderBy(con => con.TimeStamp)), PersonalInfo = connection.PersonalInformation, LocationAccuracyMeters = (latestNotUserSpecificLocation != null) ? latestNotUserSpecificLocation.AccuracyMeters : 0, LocationAcquisitionTime = (latestNotUserSpecificLocation != null) ? latestNotUserSpecificLocation.AcquisitionTime : DateTimeOffset.MinValue, ClientConnected = !connection.clientDisconnected, Pushpins = new ObservableCollection() { (latestNotUserSpecificLocation != null)?new Pushpin() { Location = new Location(latestNotUserSpecificLocation.Latitude,latestNotUserSpecificLocation.Longitude), ToolTip = latestNotUserSpecificLocation.AcquisitionTime + "\nTarkkuus: " + latestNotUserSpecificLocation.AccuracyMeters + " m" } : null, (latestUserSpecificLocation != null)?new Pushpin() { Location = new Location(latestUserSpecificLocation.Latitude,latestUserSpecificLocation.Longitude), Background = Brushes.LightGreen, ToolTip = latestUserSpecificLocation.AcquisitionTime + "\nTarkkuus: " + latestUserSpecificLocation.AccuracyMeters + " m" } : null }, EmergencyType = (connection.EmergencyType != null) ? connection.EmergencyType.TypeName : "" }); } return assignments; } /// /// The function requests an action for the current user connection from caller with the given action. CurrentConnection must be specified /// or this function does nothing. /// /// What action should be carried out. public void RequestAction(RemoteActionDto action) { if (currentConnection == null) return; try { client.RequestRemoteAction(userConnection, currentConnection, action); } catch(Exception e) { Debug.WriteLine("Error in requesting action: " + e.Message); } } /// Niko Mononen /// /// If there is current connection specified, this function requests the measurements data to start from the server with the given instrument. /// /// The instrument that provides data. public void RequestMeasurementData(MeasurementInstrumentDto instrument) { if (currentConnection == null) return; try { client.RequestStartMeasurementAsync(userConnection, currentConnection, instrument); } catch (Exception e) { Debug.WriteLine("Error in requesting measurement start: " + e.Message); } } /// Niko Mononen /// /// The function cancels the request to the server to stop measurement data sending. The Current connection is needed for this. /// /// The instrument that is sending the data. public void CancelMeasurementData(MeasurementInstrumentDto instrument) { if (currentConnection == null) return; try { client.RequestStopMeasurementAsync(userConnection, currentConnection, instrument); } catch (Exception e) { Debug.WriteLine("Error in requesting action: " + e.Message); } } /// /// The function returns boolean whether the user is connected to server or not. /// /// Boolean whether connection established or not. public bool IsConnected() { return isConnected; } /// /// The function requests all connections from the server and converts the server response to the assignment list. /// This function should be used only one time to save localy and then use ActiveConnectionsUpdated to update those. /// /// ObservableCollection of all connections as Assignment. public ObservableCollection GetAllConnections() { try { ConnectionDto[] connections = client.GetActiveConnections(userConnection); connectionDtos = new ObservableCollection(connections); return ConvertConnectionsToAssigments(connections); } catch(Exception e) { Debug.WriteLine("Error in geting alla connections: " + e.Message); } return new ObservableCollection(); } /// /// The function reconnects to the server. /// /// True when reconnection goes without an error and false if there is any error. public bool Reconnect() { try { client.Reconnect(userConnection); isConnected = true; } catch(Exception e) { Debug.WriteLine("Error in reconnect: " + e.Message); return false; } return true; } /// /// The function disconnects from the server. /// /// True when disconnection is carried out without an error and false if there is any error. public bool Disconnect() { try { client.Disconnect(userConnection); udpSocket.Disconnect(); isConnected = false; } catch(Exception e) { Debug.WriteLine("Error in disconnect: " + e.Message); isConnected = false; return false; } return true; } /// /// The function changes the assignments state to in progress to the server. First it search the right connection using guid and then makes /// the connection as current connection. /// /// The assignment that is to be set as in progress. public void ProcessAssignment(object assignment) { ConnectionDto connection = connectionDtos.First((connect => connect.ConnectionGuid.Contains((assignment as Assignment).Guid))); if (connection == null) return; try { client.OpenConnectionForProcessing(userConnection, connection); udpSocket.SendRoutingRequest(guid, connection.ConnectionGuid, true); udpSocket.SendRoutingRequest(connection.ConnectionGuid, guid, true); currentConnection = connection; if (AssignmentTakenForHandlingEvent != null) AssignmentTakenForHandlingEvent(); } catch(TimeoutException e) { Debug.WriteLine("Timed out in processasiggment: " + e.Message); } catch(Exception e) { Debug.WriteLine("Error in processing assignment: " + e.Message); } } /// /// The function makes a new thread to send a message to server so it won't block ui thread. /// /// Message to be send. public void SendTextMessage(String message) { Thread thread = new Thread(SendMessage); thread.Start(message); } /// /// The function sends a message to the server. /// /// The message to be send. private void SendMessage(object message) { client.SendTextMessage(userConnection, currentConnection, new TextMessageDto() { Content = (String)message }); } /// /// Requests unprocessing a assignment from server. CurrentConnection to null. /// /// . internal void UnProcessAssignment(object assignment) { ConnectionDto connection = connectionDtos.First((connect => connect.ConnectionGuid.Contains((assignment as Assignment).Guid))); if (connection == null) return; try { client.MarkProcessedCloseConnection(userConnection, connection); udpSocket.SendRoutingRequest(guid, connection.ConnectionGuid, false); udpSocket.SendRoutingRequest(connection.ConnectionGuid, guid, false); currentConnection = null; } catch(TimeoutException e) { Debug.WriteLine("Timed out in unprocess assigment: " + e.Message); } catch(System.ServiceModel.FaultException e) { Debug.WriteLine(e.Message); } } /// /// The function makes a new thread to request audio video so it won't block the ui thread. /// public void RequestAudioVideoMedia() { Thread thread = new Thread(RequestAudioVideoMediaThread); thread.Start(false); } /// /// The function makes a new thread to request audio so it won't block the ui thread. /// public void RequestAudioMedia() { Thread thread = new Thread(RequestAudioVideoMediaThread); thread.Start(true); } /// /// Makes request to server for audio and video with hardcoded specs in exampleMediaConfiguration. /// private void RequestAudioVideoMediaThread(object audioOnly) { MediaConfigurationDto exampleMediaConfiguration = new MediaConfigurationDto(); exampleMediaConfiguration.AudioCompressionQuality = 4; exampleMediaConfiguration.EnableAudio = true; if (exampleMediaConfiguration.EnablePicture = !(bool)audioOnly) { exampleMediaConfiguration.PictureCompressionQuality = 30; exampleMediaConfiguration.PictureFps = 2F; exampleMediaConfiguration.PictureResolution = 3; } try { client.RequestMediaUpstreaming(userConnection, currentConnection, exampleMediaConfiguration); } catch(Exception e) { Debug.WriteLine("Error in requesting mediaupstream: " + e.Message); } } /// /// The function makes a new thread to request cancel of the audio and video so it won't block the ui thread. /// public void CancelAudioVideoMedia() { Thread thread = new Thread(CancelAudioVideoMediaThread); thread.Start(false); } /// /// The function makes a new thread to request cancel of the picture so it won't block the ui thread. /// public void CancelOnlyPicture() { Thread thread = new Thread(CancelAudioVideoMediaThread); thread.Start(true); } /// /// Request cancel to audio and video from server. It is done by mediaConfigurationdto and setting enableaudio and /// enablepicture to false. /// /// Bool if its only picture that need ending. private void CancelAudioVideoMediaThread(object onlyPicture) { MediaConfigurationDto exampleMediaConfiguration = new MediaConfigurationDto(); exampleMediaConfiguration.EnableAudio = ((bool)onlyPicture)?true:false; exampleMediaConfiguration.EnablePicture = false; try { client.RequestMediaUpstreaming(userConnection, currentConnection, exampleMediaConfiguration); } catch (Exception) { } } /// Ilkka Rautiainen /// /// The function sends a keepalive message to the server /// /// The ping interval in seconds. /// task that sends keepalive messages to the server. public async Task PingAsync(int interval) { await client.PingAsync(interval); } } }