@opalkelly/frontpanel-platform-api
Version:
TypeScript definitions for Opal Kelly FrontPanel Platform API
190 lines (150 loc) • 6.04 kB
text/typescript
/**
* Copyright (c) 2024 Opal Kelly Incorporated
*
* This source code is licensed under the FrontPanel license.
* See the LICENSE file found in the root directory of this project.
*/
import IFrontPanelEventSource from "./IFrontPanelEventSource";
import IFrontPanel from "./IFrontPanel";
import { IFrontPanelEvent } from "./IFrontPanelEvent";
import FrontPanelEvent from "./FrontPanelEvent";
/**
* Class representing a timer that periodically updates WireOuts and TriggerOuts and dispatches
* events to notify subscribers of changes.
*/
class FrontPanelPeriodicUpdateTimer implements IFrontPanelEventSource {
/**
* Event that is dispatched when WireOut values change.
*/
private readonly _Device: IFrontPanel;
/**
* Event that notifies subscribers when WireOut values change.
*/
private readonly _WireOutValuesChangedEvent = new FrontPanelEvent();
/**
* Event that notifies subscribers when TriggerOut values change.
*/
private readonly _TriggerOutValuesChangedEvent = new FrontPanelEvent();
/**
* Reference to the update timer loop used to identify when the timer
* loop has started and when it has exited.
*/
private _UpdateTimer: Promise<void> | null = null;
/**
* The period in milliseconds between updates.
*/
private _UpdatePeriodMilliseconds: number;
/**
* Flag indicating whether the timer loop is currently running.
*/
private _IsRunning: boolean = false;
/**
* Flag indicating that the timer loop should exit on the next iteration.
*/
private _IsStopPending: boolean = false;
/**
* Function that cancels the timeout used to wait for the next update period.
*/
private _CancelTimeout?: () => void;
/**
* Event that notifies subscribers when WireOut values change.
*/
public get wireOutValuesChangedEvent(): IFrontPanelEvent {
return this._WireOutValuesChangedEvent;
}
/**
* Event that notifies subscribers when TriggerOut values change.
*/
public get triggerOutValuesChangedEvent(): IFrontPanelEvent {
return this._TriggerOutValuesChangedEvent;
}
/**
* Creates a new instance of the FrontPanelPeriodicUpdateTimer.
* @param device - The interface to the FrontPanel device that is the source of WireOuts and TriggerOuts.
* @param periodMilliseconds - The period in milliseconds between updates.
*/
constructor(device: IFrontPanel, periodMilliseconds: number) {
this._Device = device;
this._UpdatePeriodMilliseconds = periodMilliseconds;
}
/**
* Starts the update timer loop if it is not already running.
* @returns true if the timer loop was successfully started; false if the timer loop was already started.
*/
public async start(): Promise<boolean> {
let retval: boolean;
// Wait for the timer to stop if stop is pending
if (this._IsStopPending) {
await this._UpdateTimer;
}
// Start the timer loop if it is currently stopped
if (!this._IsRunning) {
this._UpdateTimer = this.updateTimerLoop();
retval = this._IsRunning;
} else {
retval = false; //ERROR: Timer already started
}
return retval;
}
/**
* Stops the update timer loop and returns a promise that resolves when the loop has exited.
* @returns A promise that resolves when the timer loop has exited.
*/
public async stop(): Promise<void> {
if (!this._IsStopPending) {
this._IsStopPending = true;
if (this._CancelTimeout) {
this._CancelTimeout();
}
if (this._UpdateTimer !== null) {
await this._UpdateTimer;
this._UpdateTimer = null;
}
this._IsStopPending = false;
}
}
/**
* The main loop for the update timer. It periodically updates WireOuts and TriggerOuts, and
* dispatches the events.
* @returns A promise that resolves when the timer loop has exited.
*/
private async updateTimerLoop(): Promise<void> {
this._IsRunning = true;
while (!this._IsStopPending) {
const start: number = performance.now();
// Update WireOuts and TriggerOuts and dispatch events
await this.updateWireOuts();
await this.updateTriggerOuts();
const elapsed: number = performance.now() - start;
// NOTE: Stop pending can change while waiting for the update promises to
// resolve so we check it again before waiting for the next update period.
if (!this._IsStopPending) {
// Wait until the update period has elapsed
const delay: number = this._UpdatePeriodMilliseconds - elapsed;
await new Promise<void>((resolve) => {
const timeoutId = setTimeout(resolve, delay);
this._CancelTimeout = () => {
clearTimeout(timeoutId);
resolve();
};
});
}
}
this._IsRunning = false;
}
/**
* Updates the WireOuts and dispatches the WireOutValuesChangedEvent.
*/
private async updateWireOuts(): Promise<void> {
await this._Device.updateWireOuts();
this._WireOutValuesChangedEvent.dispatch(this._Device);
}
/**
* Updates the TriggerOuts and dispatches the TriggerOutValuesChangedEvent.
*/
private async updateTriggerOuts(): Promise<void> {
await this._Device.updateTriggerOuts();
this._TriggerOutValuesChangedEvent.dispatch(this._Device);
}
}
export default FrontPanelPeriodicUpdateTimer;