public Task ListenAsync(CancellationToken ct) { return NetworkConnection.ListenAsync(_port, async connection => { var lineNeedsToBeReset = false; var cts = new CancellationTokenSource(); using (var timer = new Timer(state => { Util.Log("Idle Timeout"); cts.Cancel(); }, null, _idleTimeout, Timeout.InfiniteTimeSpan)) { while (connection.Connected && !cts.Token.IsCancellationRequested) { var receiver = new Receiver(connection.Stream); var message = await receiver.ReceiveMessageAsync(cts.Token); if (message == null) return; // Stop the idle timeout timer, since we have data timer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); // Ignore any backlog of messages if (!message.LastInBuffer) { lineNeedsToBeReset = true; continue; } // Make sure a good message came through if (message.Response == null) continue; using (var client = new SynelClient(connection, message.Response.TerminalId)) { // Reset the line if we detected a backlog earlier if (lineNeedsToBeReset) { client.Terminal.ResetLine(); lineNeedsToBeReset = false; } var notification = new PushNotification(client, message.Response); // Only push valid notifications if (Enum.IsDefined(typeof (NotificationType), notification.Type)) { Util.Log(string.Format("Listener Received: {0}", message.RawResponse), connection.RemoteEndPoint.Address); if (AsyncMessageReceivedHandler != null) { await AsyncMessageReceivedHandler(notification); } } } } } }, ct); }
/// <summary> /// Returns an awaitable task that communicates with the terminal by sending a request and receiving a response. /// </summary> /// <param name="requestCommand">The request command to send.</param> /// <param name="dataToSend">Any data that should be sent along with the command.</param> /// <param name="attempts">Number of times to attempt the command until receiving a response.</param> /// <param name="timeoutms">The timeout, in milliseconds, to wait for a response.</param> /// <param name="validResponses">If specified, the response must start with one of the valid responses (omit the terminal id).</param> /// <returns>A task that yields a validated <see cref="Response"/> object.</returns> internal async Task<Response> SendAndReceiveAsync(RequestCommand requestCommand, string dataToSend = null, int attempts = 1, int timeoutms = 5000, params string[] validResponses) { if (!Connected) throw new InvalidOperationException("Not connected!"); // augment valid responses with the terminal id if (validResponses.Length > 0) { var tid = Util.TerminalIdToChar(_terminalId).ToString(CultureInfo.InvariantCulture); validResponses = validResponses.Select(x => x.Insert(1, tid)).ToArray(); } var receiver = new Receiver(_connection.Stream); // retry loop for (int i = 1; i <= MaxRetries; i++) { try { var cts = new CancellationTokenSource(); using (var timer = new Timer(state => cts.Cancel(), null, timeoutms, Timeout.Infinite)) { // Send the request var rawRequest = CreateCommand(requestCommand, dataToSend); await SendAsync(rawRequest); // Wait for the response or timeout var message = await receiver.ReceiveMessageAsync(cts.Token); if (message == null) continue; // Stop the idle timeout timer, since we have data timer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); // Don't ever handle host query responses here. if (message.RawResponse[0] == 'q') continue; // If the valid list is populated, don't handle responses that aren't in it. if (validResponses.Length > 0 && !validResponses.Any(x => message.RawResponse.StartsWith(x))) continue; // Ignore responses intended for other terminals if (message.Response != null && message.Response.TerminalId != _terminalId) continue; if (message.RawResponse != null) Util.Log("Received: " + message.RawResponse, RemoteAddress); if (message.Exception != null) throw message.Exception; if (message.Response == null) { if (i < attempts && i < MaxRetries) { Util.Log("No response. Retrying...", RemoteAddress); continue; } throw new TimeoutException("No response received from the terminal."); } return message.Response; } } catch (InvalidCrcException) { // swallow these until the retry limit is reached if (i < MaxRetries) Util.Log("Bad CRC. Retrying...", RemoteAddress); } } // We've hit the retry limit, throw a CRC exception. throw new InvalidCrcException(string.Format("Retried the operation {0} times, but still got CRC errors.", MaxRetries)); }