/// <summary> /// Constructor /// </summary> /// <param name="stateMachineInputCallback">delegate to call when there's an event to report</param> public DelcomLight(MainAppLogic.EnqueueStateMachineInput stateMachineInputCallback, TimeSpan holdTime) { hUSB = DelcomLightWrapper.TryOpeningDelcomDevice(); Delcom.DelcomEnableAutoConfirm(hUSB, 0); // Make sure we always start turned off DelcomLightWrapper.DelcomLEDAllAction(hUSB, DelcomLightWrapper.LightStates.Off); // remember the delegate so we can invoke when we get input this.stateMachineInputCallback = stateMachineInputCallback; //Initialize hold threshold from argument passed from Main this.holdThreshold = holdTime; // start a background thread to poll the device for input BackgroundWorker bgw1 = new BackgroundWorker(); bgw1.DoWork += delegate { this.BackgroundPollingWorker(); }; bgw1.RunWorkerAsync(); }
/// <summary> /// Initialize the device and start background threads. /// </summary> /// <returns>true on success, false on failure.</returns> public bool Start() { try { this.wrapper = new DelcomLightWrapper(); } catch (ApplicationException e) { Trace.TraceError("Failed to initialize DelcomLight wrapper. {0}", e); return(false); } // Start a background thread to process light control requests. this.processLightControlRequestsThread = new Thread(ProcessLightControlRequestsLoop); this.processLightControlRequestsThread.Start(); // Start a background thread to monitor and process the button state. this.handleButtonThread = new Thread(HandleButtonLoop); this.handleButtonThread.Start(); return(true); }
/// <summary> /// Loop that attempts to open a device connection until one is connected. Replaces old /// device id with new one. /// </summary> public static uint TryOpeningDelcomDevice() { // Initialize the light wrapper uint hUSB = 0; bool deviceOpened = false; //While no light has been found, wait for a connection while (deviceOpened == false) { hUSB = DelcomLightWrapper.OpenDelcomDevice(); if (hUSB == 0) { //If no light found, wait for a second and then try to open again. Thread.Sleep(1000); } else { deviceOpened = true; } } return(hUSB); }
/// <summary> /// Change the color of the light, including options to flash or show for only a particular duration /// </summary> /// <param name="inputColor"></param> /// <param name="steady"></param> /// <param name="duration"></param> public void ChangeColor(DelcomColor inputColor, bool steady, TimeSpan?duration) { lock (this) { changeColorRequestId++; if (inputColor == DelcomColor.Off) { if (!DelcomLightWrapper.DelcomLEDAllAction(this.hUSB, DelcomLightWrapper.LightStates.Off) && Program.RunFromConsole) { Trace.TraceInformation(DateTime.Now + ": LED failure: all off"); Trace.Flush(); } } else { // convert from the publicly exposed color to the internal value DelcomLightWrapper.LightColors color = ConvertColor(inputColor); DelcomLightWrapper.LightStates action = steady ? DelcomLightWrapper.LightStates.On : DelcomLightWrapper.LightStates.Flash; if (!DelcomLightWrapper.DelcomLEDAllAction(this.hUSB, DelcomLightWrapper.LightStates.Off) && Program.RunFromConsole) { Trace.TraceInformation(DateTime.Now + ": LED failure: all off"); Trace.Flush(); } if (!DelcomLightWrapper.DelcomLEDAction(this.hUSB, color, action) && Program.RunFromConsole) { Trace.TraceInformation(DateTime.Now + ": LED failure: " + Enum.GetName(typeof(DelcomLightWrapper.LightColors), color) + " " + Enum.GetName(typeof(DelcomLightWrapper.LightStates), action)); Trace.Flush(); } // We need to only have the light on for the requested duration if (duration != null) { // Create a callback that will safely turn the light off TimerCallback callback = new TimerCallback(delegate(object state) { // Get the current button action (as of the callback) int currentButtonAction = -1; lock (this) { currentButtonAction = this.changeColorRequestId; } int rememberedButtonAction = (int)state; // Compare the current buttonaction to the remembered action if (currentButtonAction == rememberedButtonAction) { // Only turn the light off if they still match, otherwise we've moved on to a new action if (!DelcomLightWrapper.DelcomLEDAllAction(this.hUSB, DelcomLightWrapper.LightStates.Off) && Program.RunFromConsole) { Trace.TraceInformation(DateTime.Now + ": LED failure: all off"); Trace.Flush(); } } else { // Another request has come in while waiting for the timer to fire, ignore the delayed request to turn the light off } }); // Make a timer Timer ledTimer = new Timer( callback, changeColorRequestId, duration.Value, TimeSpan.Zero); } } } }
/// <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); } }
// Stop the background thread public void Stop() { this.shouldStop = true; DelcomLightWrapper.CloseDelcomDevice(hUSB); }
/// <summary> /// Initialize the device and start background threads. /// </summary> /// <returns>true on success, false on failure.</returns> public bool Start() { try { this.wrapper = new DelcomLightWrapper(); } catch (ApplicationException e) { Trace.TraceError("Failed to initialize DelcomLight wrapper. {0}", e); return false; } // Start a background thread to process light control requests. this.processLightControlRequestsThread = new Thread(ProcessLightControlRequestsLoop); this.processLightControlRequestsThread.Start(); // Start a background thread to monitor and process the button state. this.handleButtonThread = new Thread(HandleButtonLoop); this.handleButtonThread.Start(); return true; }