Signaling types
IOSignal supports three routing patterns. All use signal tags, but sending and subscribing interpret the CID separator differently.
1. Channel publish/subscribe
A sender publishes to a tag such as channel#topic; every subscriber to that exact tag receives it. channel omits the topic, while #topic uses HOME CHANNEL scope. There are no hierarchical topic wildcards.
// Register the handler once, and resubscribe whenever ready.
io.on('channel#topic', (tag, ...args) => console.log(tag, args));
io.on('ready', () => {
io.subscribe('channel#topic');
io.signal('channel#topic', 'hello');
});
As an alternative, declare io.listen('channel#topic', handler) before the first ready processing. Automatic subscriptions run after ready handlers; use manual subscribe-before-send for an immediate first publication.
publish() is an alias for signal(). With no payload arguments, JS handlers receive only tag; args is empty. See payload types.
Arduino subscribes inside its ready callback, receives (tag, payloadType, payload, payloadSize) in onMessage, and must keep calling io.loop(). Check type and length before reading bytes. A discovery device can subscribe to #search and answer with io.signal("#notify", io.cid).
2. Direct CID messages
Send to recipientCID@topic. The server removes the recipient CID; the recipient sees @topic.
// Sender: use the receiver's actual CID.
sender.signal('recipientCID@topic', 'hello');
// Receiver: no subscribe() call is needed.
receiver.on('@topic', (tag, message) => console.log(tag, message));
Use on('@', handler) or listen('@', handler) to handle every direct message, and on('@topic', handler) or listen('@topic', handler) for an exact topic. JS 7.2.0 emits general @ first, then exact @topic, synchronously. A topicless @ fires once. Direct messages do not emit message.
Do not process the same operation in both handlers. subscribe('@topic') and unsubscribe('@topic') throw TypeError; a comma list containing a direct-receive tag is rejected as a whole. Arduino handles a direct message once in onMessage and compares its tag with @topic; it has no JS-style on()/listen() API.
The stable @topic handler avoids hard-coding your own CID, which is assigned during connection and can change for anonymous clients. It does not authenticate the sender.
3. Subscribe to a CID's publications
A publishes with @topic; B subscribes to A@topic. Subscribe before A publishes.
// Publisher A, after ready:
io.signal('@topic', 'hello from A');
// Subscriber B: replace A with the actual publisher CID.
io.on('A@topic', (tag, message) => console.log(tag, message));
io.on('ready', () => io.subscribe('A@topic'));
The matching Arduino receiver checks the full tag and a terminated TEXT payload:
void onReady() {
io.subscribe("A@topic");
}
void onMessage(char *tag, uint8_t type, uint8_t *payload, size_t size) {
if (strcmp(tag, "A@topic") == 0 &&
type == IOSignal::TEXT && size > 0 && payload[size - 1] == 0) {
Serial.println(tag);
Serial.println((char *)payload);
}
}
Register the callbacks in setup and call io.loop() continuously. The publisher uses io.signal("@topic", "hello from A") after ready.
| Communication | Received tag |
|---|---|
A directly sends B@topic to B | @topic |
A publishes @topic and B subscribes | A@topic |
A subscribes to its own A@topic | A@topic |
CID publications retain the publisher CID, including self-subscriptions. They are useful for a device's status shared with many observers. The server associates publications with the sending connection's CID and can report connection state for reserved state topics. Authentication, authorization filters, quotas, and retention still depend on server configuration. See HOME CHANNEL applications for discovery and state conventions.