/// <summary> /// Runs on a background thread to monitor the remoterecorder state and will dispatch events back to the /// main thread when the state changes. /// </summary> private void BackgroundPollingWorker() { StateMachine.StateMachineInput previousState = MapRRStateToSMInput(RemoteRecorderStatus.Disconnected); Exception exceptionInRR = null; while (!this.shouldStop) { StateMachine.StateMachineInput?state = null; try { // Get the current state from the RR process state = MapRRStateToSMInput(controller.GetCurrentState().Status); } catch (Exception e) { // If there's a problem with the RR, consider it disconnected and update SM state = MapRRStateToSMInput(RemoteRecorderStatus.Disconnected); exceptionInRR = e; } //If state changed, input new state to state machine if (state != previousState) { StateMachine.StateMachineInput stateMachineInput = (StateMachine.StateMachineInput)state; if (stateMachineInput != StateMachine.StateMachineInput.NoInput) { StateMachine.StateMachineInputArgs args = new StateMachine.StateMachineInputArgs(stateMachineInput); if (stateMachineInputCallback != null) { stateMachineInputCallback(args); } } previousState = stateMachineInput; } // Handle exception after SM has been updated if (exceptionInRR != null) { // Blocks while RR service is not running HandleRRException(exceptionInRR, true); exceptionInRR = null; } // Sleep for a moment before polling again to avoid spinlock Thread.Sleep(RRLightProgram.Properties.Settings.Default.RecorderPollingIntervalMS); } }
private void AddInputToStateMachineQueue(StateMachine.StateMachineInputArgs input) { if (Program.RunFromConsole) { Trace.TraceInformation(DateTime.Now + ": Detected input: "); Trace.TraceInformation(DateTime.Now + ": " + input.Input.ToString() + " " + DateTime.UtcNow.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture)); Trace.Flush(); } lock (stateMachineInputQueue) { stateMachineInputQueue.Enqueue(input); } }
/// <summary> /// Program main loop /// </summary> public void Main() { //Create new DelcomLight object and start it's thread to listen for input from the button DelcomLight dLight = new DelcomLight(new EnqueueStateMachineInput(this.AddInputToStateMachineQueue), RRLightProgram.Properties.Settings.Default.HoldDuration); //Create new remote recorder sync object to poll recorder state and input changes into state machine RemoteRecorderSync rSync = new RemoteRecorderSync(new EnqueueStateMachineInput(this.AddInputToStateMachineQueue)); //Initialize state machine. Pass in Light and RemoteRecorder StateMachine sm = new StateMachine(dLight, rSync); // Main thread loop // Loop endlessly until we're asked to stop while (!this.shouldStop) { StateMachine.StateMachineInputArgs argsToProcess = null; // lock only while we're inspecting and changing the queue lock (stateMachineInputQueue) { // if the queue has anything, then work on it if (stateMachineInputQueue.Any()) { // dequeue argsToProcess = stateMachineInputQueue.Dequeue(); } } if (argsToProcess != null) { if (Program.RunFromConsole) { Trace.TraceInformation(DateTime.Now + ": Processing input: "); Trace.TraceInformation(DateTime.Now + ": " + argsToProcess.Input.ToString() + " " + DateTime.UtcNow.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture)); Trace.Flush(); } // send the input to the state machine sm.ProcessStateMachineInput(argsToProcess); } else { // else sleep Thread.Sleep(50); } } }
/// <summary> /// Runs on a background thread to monitor the button state and will dispatch events back to the main thread /// </summary> private void BackgroundPollingWorker() { ButtonState currentState = ButtonState.NotPressed; int iterationsSinceLastButtonRelease = 0; int iterationsSinceLastButtonDown = 0; const int buttonReleaseTolerance = 2; // magic number that works well in practice //Timer to determine how long the button has been held down for TimeSpan holdDuration = TimeSpan.Zero; //The time when the button was last in a down state. Used for hardware error correction. DateTime lastButtonDownTime = DateTime.MinValue; //The time when the button was last in an up state. Used for hardware error correction. DateTime lastButtonUpTime = DateTime.MinValue; //Boolean switch for determining if a button was held for longer than the hold threshold while it is still being held down. Boolean buttonHeld = false; // Loop endlessly until we're asked to stop while (!this.shouldStop) { //Check if light is still connected bool isStillConnected = DelcomLightWrapper.isButtonConnected(hUSB); //If not still connected, start loop to poll for connection until it is connected. if (!isStillConnected) { DelcomLightWrapper.CloseDelcomDevice(hUSB); hUSB = DelcomLightWrapper.TryOpeningDelcomDevice(); } if ((DateTime.UtcNow - lastButtonUpTime) > minTimeBetweenClicks) { // Get the current state of the button ButtonState newState = DelcomLightWrapper.DelcomGetButtonStatus(hUSB); if (newState == ButtonState.Unknown) { throw new NotImplementedException(); } else if (newState != currentState) { // The state has changed, so we need to handle it if (newState == ButtonState.NotPressed) { // We were previously in a pressed state, but the button on the light is flaky, so we need // to make sure the button has really been released, so we'll wait a few iterations. iterationsSinceLastButtonRelease++; if (iterationsSinceLastButtonRelease > buttonReleaseTolerance) { // Only remember the currentstate as changed if we're outside of our tolerance currentState = newState; //Button just released so reset timer iterationsSinceLastButtonRelease = 0; //Fire a button up event. This will only cause the state machine to act if in a previewing state with nothing queued. //It will turn off the red light that is on when the button is being held down in that case if (stateMachineInputCallback != null) { StateMachine.StateMachineInputArgs buttonUpArgs = new StateMachine.StateMachineInputArgs(StateMachine.StateMachineInput.ButtonUp); stateMachineInputCallback(buttonUpArgs); } //If a button held event was already fired, we don't want to fire a button pressed event in this case if (!buttonHeld) { // Notify that we've had a button press if (stateMachineInputCallback != null) { StateMachine.StateMachineInputArgs buttonArgs = new StateMachine.StateMachineInputArgs(StateMachine.StateMachineInput.ButtonPressed); stateMachineInputCallback(buttonArgs); } } lastButtonUpTime = DateTime.UtcNow; //reset button held, as it is no longer being held if it was before buttonHeld = false; } } else if (newState == ButtonState.Pressed) { // We were previously in a pressed state, but the button on the light is flaky, so we need // to make sure the button has really been released, so we'll wait a few iterations. iterationsSinceLastButtonDown++; if (iterationsSinceLastButtonDown > buttonReleaseTolerance) { // The button has just been pressed // Only remember the current state as changed if we're outside of our tolerance currentState = newState; //Button just pressed so reset timer iterationsSinceLastButtonRelease = 0; //Fire a button down event. This will only cause the state machine to act if in a previewing state with nothing queued. //It will turn on the red light that is on when the button is being held down in that case if (stateMachineInputCallback != null) { StateMachine.StateMachineInputArgs buttonArgs = new StateMachine.StateMachineInputArgs(StateMachine.StateMachineInput.ButtonDown); stateMachineInputCallback(buttonArgs); } //Set last button down time to current time, as button was just pressed down lastButtonDownTime = DateTime.UtcNow; } } } else if (newState == ButtonState.Pressed) { //Button has been held, check if hold is greater than threshold holdDuration = DateTime.UtcNow - lastButtonDownTime; //Reset iterations since last release iterationsSinceLastButtonRelease = 0; //If button held event has already been fired, we don't want to fire again until the button has been released if (!buttonHeld) { //If hold duration is greater than threshold we should fire a button held event if (holdDuration > holdThreshold) { // Notify that we've had a button held if (stateMachineInputCallback != null) { StateMachine.StateMachineInputArgs buttonArgs = new StateMachine.StateMachineInputArgs(StateMachine.StateMachineInput.ButtonHeld, holdDuration); stateMachineInputCallback(buttonArgs); } //We fired a button held event, so set this to true to prevent the event from being fired again while the button is still being held down. buttonHeld = true; } } } else if (newState == ButtonState.NotPressed) { //Reset iterations since last press iterationsSinceLastButtonDown = 0; } } System.Threading.Thread.Sleep(RRLightProgram.Properties.Settings.Default.LightPollingIntervalMS); } }