/// 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);
}
}
}