internal IObservable <Command> ReceiveNodeEvents(byte nodeID, Command command) { if (nodeID == 0) { throw new ArgumentOutOfRangeException(nameof(nodeID), nodeID, "nodeID must be greater than 0"); } if (command == null) { throw new ArgumentNullException(nameof(command)); } var commandPipeline = Messages // decode the response .Select(message => Decode(message, hasCallbackID: false)) // we only want events (no responses) .OfType <ControllerEvent>() // after SendData controler will respond with ApplicationCommandHandler .Where(@event => Equals(@event.Function, Function.ApplicationCommandHandler)) // deserialize the received payload to a NodeResponse .Select(@event => @event.Payload.Deserialize <NodeResponse>()) // verify if the responding node is the correct one .Where(response => response.NodeID == nodeID) // and select the command .Select(response => response.Command); // check if the command contains an ecapsulated MultiChannelCommand if (Encapsulation.Flatten(command).OfType <MultiChannelCommand>().Any()) { // extract MultiChannelCommand var multiChannelRequestCommand = Encapsulation.Flatten(command).OfType <MultiChannelCommand>().Single(); commandPipeline = commandPipeline // extract the MultiChannelCommand from the response .Select(reply => Encapsulation.Flatten(reply).OfType <MultiChannelCommand>().SingleOrDefault()) // verify if present .Where(reply => reply != null) // verify if the MultiChannelCommand is the correct one .Where(reply => reply.CommandClass == multiChannelRequestCommand.CommandClass && reply.CommandID == multiChannelRequestCommand.CommandID) // verify if the endpoint is the correct one .Where(reply => reply.SourceEndpointID == multiChannelRequestCommand.SourceEndpointID); } // get the most inner command var requestCommand = Encapsulation.Flatten(command).Last(); return(commandPipeline // select the inner most command .Select(reply => Encapsulation.Flatten(reply).Last()) // verify if the response conmmand is the correct one .Where(reply => reply.CommandClass == requestCommand.CommandClass && reply.CommandID == requestCommand.CommandID)); }
// NodeCommand, with return value. Request followed by: // 1) a response from the controller // 2) a event from the controller: command deliverd at node) // 3) a event from the node: return value internal async Task <Command> Send(byte nodeID, Command command, byte responseCommandID, CancellationToken cancellationToken = default) { if (nodeID == 0) { throw new ArgumentOutOfRangeException(nameof(nodeID), nodeID, "nodeID must be greater than 0"); } if (command == null) { throw new ArgumentNullException(nameof(command)); } if (responseCommandID == 0) { throw new ArgumentOutOfRangeException(nameof(responseCommandID), responseCommandID, "responseCommandID must be greater than 0"); } // generate new callback var callbackID = GetNextCallbackID(); // create the request var nodeRequest = new NodeRequest(nodeID, command); var controllerRequest = new ControllerRequest(Function.SendData, nodeRequest.Serialize()); var responsePipeline = Messages // decode the response .Select(message => Decode(message, hasCallbackID: false)) // we only want responses (no events) .OfType <ControllerResponse>() // verify matching function .Where(response => Equals(response.Function, controllerRequest.Function)) // unknown what the 0x01 byte means, probably: ready, finished, OK .Where(response => response.Payload[0] == 0x01); var eventPipeline = Messages // decode the response .Select(message => Decode(message, hasCallbackID: true)) // we only want events (no responses) .OfType <ControllerEvent>() // verify matching function .Where(@event => Equals(@event.Function, controllerRequest.Function)) // verify matching callback .Where(@event => Equals(@event.CallbackID, callbackID)) // deserialize the received payload to a NodeCommandCompleted .Select(@event => @event.Payload.Deserialize <NodeCommandCompleted>()) // verify the state .Verify(completed => completed.TransmissionState == TransmissionState.CompleteOK, completed => new TransmissionException(completed.TransmissionState)); var commandPipeline = Messages // wait until the response pipeline has finished .SkipUntil(responsePipeline) // wait until the event pipeline has finished .SkipUntil(eventPipeline) // decode the response .Select(message => Decode(message, hasCallbackID: false)) // we only want events (no responses) .OfType <ControllerEvent>() // after SendData controler will respond with ApplicationCommandHandler .Where(@event => Equals(@event.Function, Function.ApplicationCommandHandler)) // deserialize the received payload to a NodeResponse .Select(@event => @event.Payload.Deserialize <NodeResponse>()) // verify if the responding node is the correct one .Where(response => response.NodeID == nodeID) // and select the command .Select(response => response.Command); // check if the command contains an ecapsulated MultiChannelCommand if (Encapsulation.Flatten(command).OfType <MultiChannelCommand>().Any()) { // extract MultiChannelCommand var multiChannelRequestCommand = Encapsulation.Flatten(command).OfType <MultiChannelCommand>().Single(); // insert MultiChannel filtering commandPipeline = commandPipeline // extract the MultiChannelCommand from the response .Select(reply => Encapsulation.Flatten(reply).OfType <MultiChannelCommand>().Single()) // verify if the MultiChannelCommand is the correct one .Where(reply => reply.CommandClass == multiChannelRequestCommand.CommandClass && reply.CommandID == multiChannelRequestCommand.CommandID) // verify if the endpoint the correct one .Where(reply => reply.SourceEndpointID == multiChannelRequestCommand.TargetEndpointID); } // get the most inner command var requestCommand = Encapsulation.Flatten(command).Last(); commandPipeline = commandPipeline // select the inner most command .Select(reply => Encapsulation.Flatten(reply).Last()) // verify if the response conmmand is the correct one .Where(reply => reply.CommandClass == requestCommand.CommandClass && reply.CommandID == responseCommandID); return(await Send(Encode(controllerRequest, callbackID), commandPipeline, cancellationToken)); }