UNPKG

ntcore-ts-client

Version:

A TypeScript library for communication over [WPILib's NetworkTables 4.1 protocol](https://github.com/wpilibsuite/allwpilib/blob/main/ntcore/doc/networktables4.adoc).

299 lines 12.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Messenger = void 0; var tslib_1 = require("tslib"); var schemas_1 = require("../types/schemas"); var socket_1 = require("./socket"); /** NetworkTables client. */ var Messenger = /** @class */ (function () { /** * Creates a new NetworkTables client. * @param serverUrl - The URL of the server to connect to. * @param onTopicUpdate - Called when a topic is updated. * @param onAnnounce - Called when a topic is announced. * @param onUnannounce - Called when a topic is unannounced. * @param onTopicProperties - Called when a topic's properties are updated. */ function Messenger(serverUrl, onTopicUpdate, onAnnounce, onUnannounce, onTopicProperties) { var _this = this; this.publications = new Map(); this.subscriptions = new Map(); this._currentPubUID = 0; this._currentSubUID = 0; /** * Called when the socket opens. */ this.onSocketOpen = function () { // Send all subscriptions _this.subscriptions.forEach(function (params) { _this.subscribe(params, true); }); // Send all publications _this.publications.forEach(function (params) { _this.publish(params, true); }); }; /** * Called when the socket closes. */ // eslint-disable-next-line @typescript-eslint/no-empty-function this.onSocketClose = function () { }; this._socket = socket_1.NetworkTablesSocket.getInstance(serverUrl, this.onSocketOpen, this.onSocketClose, onTopicUpdate, onAnnounce, onUnannounce, onTopicProperties); } Object.defineProperty(Messenger.prototype, "socket", { /** * Gets the NetworkTablesSocket used by the Messenger. * @returns The NetworkTablesSocket used by the Messenger. */ get: function () { return this._socket; }, enumerable: false, configurable: true }); /** * Gets the instance of the NetworkTables client. * @param serverUrl - The URL of the server to connect to. This is not needed after the first call. * @param onTopicUpdate - Called when a topic is updated. * @param onAnnounce - Called when a topic is announced. * @param onUnannounce - Called when a topic is unannounced. * @param onTopicProperties - Called when a topic's properties are updated. * @returns The instance of the NetworkTables client. */ Messenger.getInstance = function (serverUrl, onTopicUpdate, onAnnounce, onUnannounce, onTopicProperties) { var instance = this._instances.get(serverUrl); if (!instance) { instance = new this(serverUrl, onTopicUpdate, onAnnounce, onUnannounce, onTopicProperties); this._instances.set(serverUrl, instance); } return instance; }; /** * Reinstantiates the messenger by resetting the socket with a new URL. * @param serverUrl - The URL of the server to connect to. */ Messenger.prototype.reinstantiate = function (serverUrl) { this._socket.stopAutoConnect(); this._socket.reinstantiate(serverUrl); this._socket.startAutoConnect(); }; /** * Gets all publications. * @returns An iterator of all publications in the form [id, params]. */ Messenger.prototype.getPublications = function () { return this.publications.entries(); }; /** * Gets all subscriptions. * @returns An iterator of all subscriptions in the form [id, params]. */ Messenger.prototype.getSubscriptions = function () { return this.subscriptions.entries(); }; Messenger.prototype.parseAndFilterMessage = function (event, method) { var e_1, _a; if (!(event.data instanceof ArrayBuffer) && !(event.data instanceof Uint8Array)) { var messageData = JSON.parse(event.data); var messages = schemas_1.messageSchema.parse(messageData); try { for (var messages_1 = tslib_1.__values(messages), messages_1_1 = messages_1.next(); !messages_1_1.done; messages_1_1 = messages_1.next()) { var message = messages_1_1.value; if (message.method === method) { return message; } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (messages_1_1 && !messages_1_1.done && (_a = messages_1.return)) _a.call(messages_1); } finally { if (e_1) throw e_1.error; } } } return null; }; /** * Publishes a topic to the server. * @param params - The publication parameters. * @param force - Whether to force the publication. * @returns The announcement parameters. */ Messenger.prototype.publish = function (params, force) { return tslib_1.__awaiter(this, void 0, void 0, function () { var message; var _this = this; return tslib_1.__generator(this, function (_a) { message = { method: 'publish', params: params, }; return [2 /*return*/, new Promise(function (resolve, reject) { // Check if the topic is already published if (_this.publications.has(params.pubuid) && !force) reject(new Error('Topic is already published')); // Listen for the announcement var resolver = function (msg) { _this.socket.websocket.removeEventListener('message', wsHandler); // Add the topic to the list of published topics _this.publications.set(params.pubuid, params); resolve(msg); }; var wsHandler = function (event) { var message = _this.parseAndFilterMessage(event, 'announce'); if (message && message.params.name === params.name) { resolver(message); } }; _this.socket.websocket.addEventListener('message', wsHandler); // Send the message to the server _this._socket.sendTextFrame(message); // HOTFIX: Subscribe to the topic to get the announcement. // This is a bug in 2025.2.1 WPILib var subMsg = { options: { topicsonly: true, }, topics: [params.name], subuid: _this.getNextSubUID(), }; _this.subscribe(subMsg); // Reject the promise if the topic is not announced within 3 seconds _this.socket.waitForConnection().then(function () { setTimeout(function () { reject(new Error("Topic ".concat(params.name, " was not announced within 3 seconds"))); }, 3000); }); })]; }); }); }; /** * Unpublishes a topic from the server. * @param pubuid - The publication ID to unpublish. */ Messenger.prototype.unpublish = function (pubuid) { // Check if the topic is not published if (!this.publications.delete(pubuid)) return; // Send the message to the server var message = { method: 'unpublish', params: { pubuid: pubuid, }, }; this._socket.sendTextFrame(message); }; /** * Subscribes to a topic. * @param params - The subscription parameters. * @param force - Whether to force the subscription. */ Messenger.prototype.subscribe = function (params, force) { if (this.subscriptions.has(params.subuid) && !force) return; this.subscriptions.set(params.subuid, params); // Create the message to send to the server var message = { method: 'subscribe', params: params, }; // Send the message to the server this._socket.sendTextFrame(message); }; /** * Unsubscribes from a topic. * @param subuid - The subscription ID to unsubscribe from. */ Messenger.prototype.unsubscribe = function (subuid) { // Check if the topic is not subscribed if (!this.subscriptions.has(subuid)) return; // Remove the topic from the list of subscribed topics this.subscriptions.delete(subuid); // Send the message to the server var message = { method: 'unsubscribe', params: { subuid: subuid, }, }; this._socket.sendTextFrame(message); }; /** * Sets the properties of a topic. * @param params - The parameters to set * @returns The new properties of the topic. */ Messenger.prototype.setProperties = function (params) { return tslib_1.__awaiter(this, void 0, void 0, function () { var message; var _this = this; return tslib_1.__generator(this, function (_a) { message = { method: 'setproperties', params: params, }; return [2 /*return*/, new Promise(function (resolve, reject) { var resolver = function (message) { _this._socket.websocket.removeEventListener('message', wsHandler); clearInterval(responseCheck); resolve(message); }; var wsHandler = function (event) { var message = _this.parseAndFilterMessage(event, 'announce'); if (message === null || message === void 0 ? void 0 : message.params.ack) { resolver(message); } }; _this._socket.websocket.addEventListener('message', wsHandler); // Send the message to the server _this._socket.sendTextFrame(message); // Reject the promise if the topic is not announced within 3 seconds var responseCheck = setInterval(function () { if (_this.socket.isConnected()) { reject(new Error("Topic ".concat(params.name, " was not announced within 3 seconds"))); } }, 3000); })]; }); }); }; /** * Send data to a topic. * This should only be called by the PubSubClient. * @param topic - The topic to update. * @param value - The value to update the topic to. * @returns The timestamp of the update, or -1 if the topic is not announced. */ Messenger.prototype.sendToTopic = function (topic, value) { var typeInfo = topic.typeInfo; if (!topic.publisher || topic.pubuid == null) { throw new Error("Topic ".concat(topic.name, " is not a publisher, so it cannot be updated")); } if (!topic.announced) { console.warn("Topic ".concat(topic.name, " is not announced, but the new value will be queued")); } return this._socket.sendValueToTopic(topic.pubuid, value, typeInfo); }; /** * Gets the next available publisher UID. * @returns The next available publisher UID. */ Messenger.prototype.getNextPubUID = function () { return this._currentPubUID++; }; /** * Gets the next available subscriber UID. * @returns The next available subscriber UID. */ Messenger.prototype.getNextSubUID = function () { return this._currentSubUID++; }; Messenger._instances = new Map(); return Messenger; }()); exports.Messenger = Messenger; //# sourceMappingURL=messenger.js.map