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
JavaScript
;
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