modbus-connect
Version:
Modbus RTU over Web Serial and Node.js SerialPort
234 lines (233 loc) • 8.53 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var DeviceConnectionTracker_exports = {};
__export(DeviceConnectionTracker_exports, {
DeviceConnectionTracker: () => DeviceConnectionTracker
});
module.exports = __toCommonJS(DeviceConnectionTracker_exports);
var import_async_mutex = require("async-mutex");
var import_modbus_types = require("../../types/modbus-types.js");
class DeviceConnectionTracker {
_handler;
_states = /* @__PURE__ */ new Map();
_debounceMs;
_validateSlaveId;
_mutex = new import_async_mutex.Mutex();
_debounceTimeouts = /* @__PURE__ */ new Map();
/**
* Создаёт экземпляр трекера состояний.
*
* @param options Настройки трекера
* @param options.debounceMs Интервал дебонса уведомлений об отключении (мс), по умолчанию: `500`
* @param options.validateSlaveId Валидировать `slaveId` (1–255), по умолчанию: `true`
*/
constructor(options = {}) {
this._debounceMs = options.debounceMs ?? 500;
this._validateSlaveId = options.validateSlaveId ?? true;
}
/**
* Устанавливает обработчик изменения состояния устройства.
* При установке — вызывает обработчик для всех текущих состояний.
*
* @param handler Функция: `(slaveId: number, connected: boolean, error?) => void`
*/
async setHandler(handler) {
const release = await this._mutex.acquire();
try {
this._handler = handler;
for (const state of this._states.values()) {
handler(
state.slaveId,
state.hasConnectionDevice,
state.hasConnectionDevice ? void 0 : { type: state.errorType, message: state.errorMessage }
);
}
} finally {
release();
}
}
/**
* Удаляет обработчик изменения состояния.
* После вызова — уведомления прекращаются.
*/
async removeHandler() {
const release = await this._mutex.acquire();
try {
this._handler = void 0;
} finally {
release();
}
}
/**
* Уведомляет о подключении устройства.
* Игнорируется, если устройство уже подключено.
*
* @param slaveId Идентификатор устройства (1–255)
*/
async notifyConnected(slaveId) {
if (this._validateSlaveId && (slaveId < 1 || slaveId > 255)) return;
const release = await this._mutex.acquire();
try {
const existingTimeout = this._debounceTimeouts.get(slaveId);
if (existingTimeout) {
clearTimeout(existingTimeout);
this._debounceTimeouts.delete(slaveId);
}
const existing = this._states.get(slaveId);
if (existing?.hasConnectionDevice) {
return;
}
const state = {
slaveId,
hasConnectionDevice: true
};
this._states.set(slaveId, state);
this._handler?.(slaveId, true);
} finally {
release();
}
}
/**
* Уведомляет об отключении устройства с **trailing debounce**.
* Последний вызов в серии будет выполнен через `debounceMs`.
*
* @param slaveId Идентификатор устройства (1–255)
* @param errorType Тип ошибки, по умолчанию: `UnknownError`
* @param errorMessage Подробное сообщение, по умолчанию: `'Device disconnected'`
*/
notifyDisconnected(slaveId, errorType = import_modbus_types.ConnectionErrorType.UnknownError, errorMessage = "Device disconnected") {
if (this._validateSlaveId && (slaveId < 1 || slaveId > 255)) return;
const existingTimeout = this._debounceTimeouts.get(slaveId);
if (existingTimeout) clearTimeout(existingTimeout);
const timeout = setTimeout(() => {
this._debounceTimeouts.delete(slaveId);
this._doNotifyDisconnected(slaveId, errorType, errorMessage);
}, this._debounceMs);
this._debounceTimeouts.set(slaveId, timeout);
}
/**
* Полностью удаляет состояние устройства из памяти трекера.
* СИНХРОННЫЙ метод. Используется при принудительном удалении устройства из конфигурации.
* Гарантирует, что при следующем notifyConnected событие будет отправлено.
*
* @param slaveId Идентификатор устройства (1–255)
*/
removeState(slaveId) {
const existingTimeout = this._debounceTimeouts.get(slaveId);
if (existingTimeout) {
clearTimeout(existingTimeout);
this._debounceTimeouts.delete(slaveId);
}
this._states.delete(slaveId);
}
/**
* Выполняет фактическое уведомление об отключении (внутренний метод).
*/
async _doNotifyDisconnected(slaveId, errorType, errorMessage) {
const release = await this._mutex.acquire();
try {
const existing = this._states.get(slaveId);
if (existing && !existing.hasConnectionDevice && existing.errorType === errorType) {
return;
}
const newState = {
slaveId,
hasConnectionDevice: false,
errorType,
errorMessage
};
this._states.set(slaveId, newState);
this._handler?.(slaveId, false, { type: errorType, message: errorMessage });
} finally {
release();
}
}
/**
* Возвращает копию состояния конкретного устройства.
*/
async getState(slaveId) {
const release = await this._mutex.acquire();
try {
const state = this._states.get(slaveId);
return state ? { ...state } : void 0;
} finally {
release();
}
}
/**
* Возвращает копии всех текущих состояний устройств.
*/
async getAllStates() {
const release = await this._mutex.acquire();
try {
return Array.from(
this._states.values(),
({ slaveId, hasConnectionDevice, errorType, errorMessage }) => ({
slaveId,
hasConnectionDevice,
errorType,
errorMessage
})
);
} finally {
release();
}
}
/**
* Очищает все состояния и отменяет все таймеры дебонса.
*/
async clear() {
const release = await this._mutex.acquire();
try {
this._states.clear();
this._debounceTimeouts.forEach(clearTimeout);
this._debounceTimeouts.clear();
this._handler = void 0;
} finally {
release();
}
}
/**
* Проверяет, отслеживается ли устройство.
*/
hasState(slaveId) {
return this._states.has(slaveId);
}
/**
* Возвращает список `slaveId` всех подключённых устройств.
*/
getConnectedSlaveIds() {
return Array.from(this._states.entries()).filter(([, s]) => s.hasConnectionDevice).map(([id]) => id);
}
/**
* Сбрасывает таймер дебонса для указанного устройства.
* **Только для тестов.**
*
* @internal
*/
__resetDebounce(slaveId) {
const timeout = this._debounceTimeouts.get(slaveId);
if (timeout) clearTimeout(timeout);
this._debounceTimeouts.delete(slaveId);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
DeviceConnectionTracker
});