UNPKG

@linuxcnc-node/hal

Version:

Node.js bindings for LinuxCNC HAL library

325 lines (324 loc) 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Param = exports.Pin = exports.HalComponent = exports.DEFAULT_POLL_INTERVAL = void 0; exports.DEFAULT_POLL_INTERVAL = 10; // Default polling interval in milliseconds class HalComponent { constructor(nativeInstance, componentName, componentPrefix) { this.pins = {}; this.params = {}; // Monitoring system this.watchedObjects = new Map(); this.monitoringTimer = null; this.monitoringOptions = { pollInterval: exports.DEFAULT_POLL_INTERVAL, }; this.nativeInstance = nativeInstance; this.name = componentName; this.prefix = componentPrefix; // The Proxy wraps `this` instance of HalComponent this.proxyInstance = new Proxy(this, { get: (target, propKey, receiver) => { if (typeof propKey === "string") { // Prioritize existing properties/methods of HalComponent class // Reflect.has checks own and prototype chain properties if (Reflect.has(target, propKey)) { return Reflect.get(target, propKey, receiver); } // Fallback to HAL item access via native getProperty try { return target.nativeInstance.getProperty(propKey); // Returns number | boolean } catch (e) { // Native getProperty should throw if item not found // console.warn(`HAL item '${propKey}' not found or error accessing:`, e); throw e; // Re-throw error to be more explicit } } return Reflect.get(target, propKey, receiver); }, set: (target, propKey, value, receiver) => { if (typeof propKey === "string") { // Check if the property is an own property or method of HalComponent if (Reflect.has(target, propKey)) { return Reflect.set(target, propKey, value, receiver); } // Fallback to HAL item access via native setProperty try { if (typeof value === "number" || typeof value === "boolean") { target.nativeInstance.setProperty(propKey, value); return true; } else { console.error(`HAL item '${propKey}' can only be set to a number or boolean. Received type ${typeof value}.`); return false; } } catch (e) { throw e; } } return Reflect.set(target, propKey, value, receiver); }, }); return this.proxyInstance; } /** * Creates a new HAL pin for this component. * @param nameSuffix The suffix for the pin name (e.g., "in1"). Full name will be "prefix.suffix". * @param type The data type of the pin. * @param direction The direction of the pin (IN, OUT, IO). * @returns A new Pin instance. */ newPin(nameSuffix, type, direction) { const success = this.nativeInstance.newPin(nameSuffix, type, direction); if (!success) { // The native layer should throw an error on failure, so this might not be strictly needed console.error(`Failed to create pin '${nameSuffix}'`); } const pin = new Pin(this.proxyInstance, nameSuffix, type, direction); this.pins[nameSuffix] = pin; return pin; } /** * Creates a new HAL parameter for this component. * @param nameSuffix The suffix for the parameter name. * @param type The data type of the parameter. * @param direction The writability of the parameter (RO, RW). * @returns A new Param instance. */ newParam(nameSuffix, type, direction) { const success = this.nativeInstance.newParam(nameSuffix, type, direction); if (!success) { console.error(`Failed to create param '${nameSuffix}'`); } const param = new Param(this.proxyInstance, nameSuffix, type, direction); this.params[nameSuffix] = param; return param; } /** * Marks this component as ready and locks out adding new pins/params. */ ready() { this.nativeInstance.ready(); } /** * Allows a component to add pins/params after ready() has been called. * ready() must be called again afterwards. */ unready() { this.nativeInstance.unready(); } /** * Retrieves all pins in this component. * @returns Map of all pins in this component */ getPins() { return this.pins; } /** * Retrieves all parameters in this component. * @returns Map of all parameters in this component */ getParams() { return this.params; } /** * Sets monitoring options for the component. * @param options Options to configure monitoring behavior */ setMonitoringOptions(options) { this.monitoringOptions = { ...this.monitoringOptions, ...options }; // Restart monitoring with new options if it's currently running if (this.monitoringTimer && this.watchedObjects.size > 0) { this.stopMonitoring(); this.startMonitoring(); } } /** * Gets current monitoring options. * @returns Current monitoring options */ getMonitoringOptions() { return { ...this.monitoringOptions }; } /** * Adds a watch callback for a pin or parameter. * @param name Name of the pin or parameter to watch * @param callback Function to call when value changes */ addWatch(name, callback) { // Validate name and callback const object = this.pins[name] || this.params[name]; if (!object) { throw new Error(`No pin or parameter found with name '${name}'`); } let watchedObj = this.watchedObjects.get(name); if (!watchedObj) { // Get initial value const initialValue = this.proxyInstance[name]; watchedObj = { object, lastValue: initialValue, callbacks: new Set(), }; this.watchedObjects.set(name, watchedObj); } watchedObj.callbacks.add(callback); // Start monitoring if this is the first watched object if (this.watchedObjects.size === 1 && !this.monitoringTimer) { this.startMonitoring(); } } /** * Removes a watch callback for a pin or parameter. * @param name Name of the pin or parameter to unwatch * @param callback Function to remove from callbacks */ removeWatch(name, callback) { const watchedObj = this.watchedObjects.get(name); if (watchedObj) { watchedObj.callbacks.delete(callback); // Remove the watched object if no callbacks remain if (watchedObj.callbacks.size === 0) { this.watchedObjects.delete(name); // Stop monitoring if no objects are being watched if (this.watchedObjects.size === 0) { this.stopMonitoring(); } } } } /** * Gets the list of currently watched objects. * @returns Array of watched object names and types */ getWatchedObjects() { return Array.from(this.watchedObjects.entries()).map(([name, obj]) => ({ object: obj.object, callbackCount: obj.callbacks.size, })); } /** * Starts the monitoring timer. * @private */ startMonitoring() { if (this.monitoringTimer) { return; // Already monitoring } this.monitoringTimer = setInterval(() => { this.checkForChanges(); }, this.monitoringOptions.pollInterval || exports.DEFAULT_POLL_INTERVAL); } /** * Stops the monitoring timer. * @private */ stopMonitoring() { if (this.monitoringTimer) { clearInterval(this.monitoringTimer); this.monitoringTimer = null; } } /** * Checks all watched objects for value changes and triggers callbacks. * @private */ checkForChanges() { for (const [name, watchedObj] of this.watchedObjects.entries()) { try { const currentValue = this.proxyInstance[name]; if (currentValue !== watchedObj.lastValue) { const oldValue = watchedObj.lastValue; watchedObj.lastValue = currentValue; // Trigger all callbacks for this object for (const callback of watchedObj.callbacks) { try { callback(currentValue, oldValue, watchedObj.object); } catch (error) { console.error(`Error in watch callback for ${name}:`, error); } } } } catch (error) { console.error(`Error checking value for ${name}:`, error); } } } /** * Cleanup method to stop monitoring when component is destroyed. */ destroy() { this.stopMonitoring(); this.watchedObjects.clear(); } } exports.HalComponent = HalComponent; class Pin { constructor(componentInstance, nameSuffix, type, direction) { this.componentInstance = componentInstance; this.name = nameSuffix; this.type = type; this.direction = direction; } getValue() { return this.componentInstance[this.name]; } setValue(value) { return (this.componentInstance[this.name] = value); } /** * Starts watching this pin for value changes. * @param callback Function to call when the pin value changes */ watch(callback) { // Get the HalComponent instance from the proxy const component = this.componentInstance; component.addWatch(this.name, callback); } /** * Stops watching this pin for value changes. * @param callback The specific callback function to remove */ removeWatch(callback) { // Get the HalComponent instance from the proxy const component = this.componentInstance; component.removeWatch(this.name, callback); } } exports.Pin = Pin; class Param { constructor(componentInstance, nameSuffix, type, direction) { this.componentInstance = componentInstance; this.name = nameSuffix; this.type = type; this.direction = direction; } getValue() { return this.componentInstance[this.name]; } setValue(value) { return (this.componentInstance[this.name] = value); } /** * Starts watching this parameter for value changes. * @param callback Function to call when the parameter value changes */ watch(callback) { // Get the HalComponent instance from the proxy const component = this.componentInstance; component.addWatch(this.name, callback); } /** * Stops watching this parameter for value changes. * @param callback The specific callback function to remove */ removeWatch(callback) { // Get the HalComponent instance from the proxy const component = this.componentInstance; component.removeWatch(this.name, callback); } } exports.Param = Param;