Appearance
Live data processing
The resol-vbus-core
provides classes and functions to encoded and decode VBus data primitives from and to their "on-the-wire" representation as specified by the VBus Protocol Specification.
Encoding VBus data primitives
The three VBus data primitives Packet
, Datagram
and Telegram
can be encoded by using their respective liveEncode___
functions:
The functions take their respective primitive as input and produce the "on-the-wire" representation as a Uint8Array
buffer of bytes:
typescript
import { Packet, liveEncodePacket } from 'resol-vbus-core';
const frameData = new Uint8Array(27 * 4);
// TODO: fill `frameData` here
const packet = new Packet({
destinationAddress: 0x0010,
sourceAddress: 0x1911,
command: 0x0100,
frameCount: 27,
frameData,
});
const packetAsBytes = liveEncodePacket(packet);
// TODO: send `packetAsBytes` on the wire
The library also exports a helper function liveEncode
which take any of the three VBus data primitives.
Using the LiveEncoder
class
The LiveEncoder
class implements some best-practises for sending VBus data primitives over the wire:
- Transmission time tracking
- Power management
- Suspends with optional timeouts
- Idle management
Transmission time tracking
The VBus is a serial connection with 9.600 bits per second by default. Sending a byte of data takes roughly 1.042 milliseconds to transmit. The queue()
instance function of the LiveEncoder
class tracks the amount of data sent and estimates when the transmission should have completed. This prevents overwhelming the VBus with too much data while is it still working through a backlog of unsent data.
See queue()
instance function API and baudrate
constructor options API for details.
Power management
As lined out in the VBus specification the VBus is not only used to transport data, but also electrical power. When no data is transmitted, the VBus supply voltage can be used to charge up internal circuitry that provides enough energy for the phases when data is transmitted and VBus power supply fluctuates.
The LiveEncoder
class has mechanism to split larger chunks of data into smaller ones and send each of them followed by a small break to allow attached devices to recharge if necessary.
See the following constructor option APIs for details:
Suspends with optional timeouts
The LiveEncoder
class supports being suspended in two ways:
- indefinetly using
suspend()
- for a certain time using
suspendWithTimeout()
Both can be canceled using resume()
which is necessary to send data.
The indefinite variant is useful for implementing a VBus slave that passively listens for a request from another device to answer that and go back to its passive state. This implementation would call suspend()
right after initializing the LiveEncoder
. Once the request to send an answer was received, resume()
the encoder, queue()
the answer and immediately suspend()
again, which will automatically suspend after the data was transmitted.
The timeout-based variant is useful for implementing a VBus master that want to suspend for some time to give a requested device enough time to send an answer. In order to do that the master uses queue()
to send the request and immediately suspendWithTimeout()
which will automatically suspend after the data was transmitted. If the answer is received, the timeout can be prematurely canceled using resume()
.
See the following instance function APIs for details:
Idle management
The LiveEncoder
is considered idle, if:
- no data has been queued for transmission
- the optional energy management subsystem is not gaining energy currently
- the
LiveEncoder
is not suspended
If the LiveEncoder
is idle, it calls the onIdle
callback which can be used on a VBus master implementation to drive forward the communication statemachie, for example sending the next piece of information.
See the onIdle()
constructor option API and the isIdle()
instance function API for details.
Decoding VBus data primitives
The library provides two ways of decoding VBus data primitives that are encoded in their live "on-the-wire" representation:
- using the
liveDecodePacket
,liveDecodeDatagram
andliveDecodeTelegram
functions - using the
LiveDecoder
class
The liveDecode___
functions for trusted data
The liveDecode___
functions can be used to decode a Uint8Array
which is known to contain the respective live representation. Passing it an buffer containing incompatible data is undefined behaviour. If you do not trust the Uint8Array
data it is safer to use the LiveDecoder
class instead.
typescript
const { liveDecodeDatagram } from 'resol-vbus-core';
const bytes = new Uint8Array([
0xAA, 0x20, 0x00, 0x11, 0x19, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
]);
const dgram = liveDecodeDatagram(bytes);
// TODO: do something with the datagram here
See the liveDecodePacket()
function API, the liveDecodeDatagram()
function API and liveDecodeTelegram()
function API for details.
The LiveDecoder
for everything else
The LiveDecoder
class uses an internal buffer to buffer incoming data until a VBus data primitive can be decoded from it. It will then call the respective callbacks provided to the constructor of the LiveDecoder
to inform its consumer about the decoded data.
typescript
const { LiveDecoder } from 'resol-vbus-core';
const bytes = new Uint8Array([
0xAA, 0x20, 0x00, 0x11, 0x19, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
]);
const decoder = new LiveDecoder({
onDatagram(dgram) {
// TODO: do something with the datagram here
},
});
decoder.decode(bytes);
See the LiveDecoder
class API for details.
Performing two-way communication
The LiveTransceiver
class combines both a LiveEncoder
and a LiveDecoder
to make two-way communication easier.
The encoder part of the LiveTransceiver
starts suspended making it a passively listening VBus slave by default.
See LiveTransceiver
class API for details.
Handling I/O
The LiveTransceiver
class itself does not handle the raw I/O over something like serial port or network socket. It is up to the consumer to make sure data is flowing in and out of the LiveTransceiver
.
Incoming data must be passed to the decode()
instance function. It will try to decode VBus data primitives from it and call the respective callbacks if successful. See the LiveDecoder
documentation for details.
Outgoind data is provided by the onTransmit
callback passed in as a constructor option. Whenever the integrated LiveEncoder
has assembled a block of data to transmit, the onTransmit
callback is called. The consumer must take the appropriate steps to actually send the data using the underlying I/O subsystem.
The library resol-vbus-core-nodejs
provides a specialized NetLiveTransceiver
class that opens and maintains a net.Socket
TCP connection and takes care of the necessary I/O handling specified above.
typescript
import { LiveTransceiver } from 'resol-vbus-core';
const tx = new LiveTransceiver({
onTransmit(buffer) {
// TODO: send this buffer using the I/O of your choice (serial port, network socket, ...)
},
});
// TODO: whenever the underlying I/O has received some data, pass it on to `decode()`
const incomingData = new Uint8Array(/* ... */);
tx.decode(incomingData);
See the onTransmit
constructor option API and the decode()
instance function API for details.
Responding to a request
Since the LiveEncoder
used inside the LiveTransceiver
is automatically suspended on start, consumers must explicitly call resume()
for using queue()
to queue new data for transmission.
Assume your VBus slave simulates an extension module and waits for a Packet
request from the main controller providing you with fresh output values and requesting you in turn to send your measurement values:
typescript
import { LiveTransceiver, Packet } from 'resol-vbus-core';
const tx = new LiveTransceiver({
onPacket(packet) {
if (packet.destinationAddress === 0x6651 && packet.command === 0x0300) {
// TODO: evaluate the payload in `packet.frameData` containing the output values to apply
// TODO: construct answer
const frameData = new Uint8Array(6 * 4);
// ...
const answer = new Packet({
destinationAddress: packet.sourceAddress,
sourceAddress: packet.destinationAddress,
command: 0x0100,
frameCount: 6,
frameData,
});
// `resume()` to be able to use `queue()`
tx.getEncoder().resume();
// `queue()` your answer for transmission
tx.getEncoder().queue(answer);
// `suspend()` will automatically suspend after the transmission is done
tx.getEncoder().suspend();
}
},
onTransmit(buffer) {
// TODO: see above for notes about handling outgoing I/O
},
});
// TODO: see above for notes about handling incoming I/O
Assume the VBus master role
The VBus master drives the communication forward. It is in control of who sends what data when.
Using the LiveTransceiver
this can be achieved by calling resume()
right after initialization and providing an onIdle()
callback that gets called the the VBus master is allowed to send the next information.
typescript
import { LiveTransceiver, Packet } from 'resol-vbus-core';
let phase = 0;
const tx = new LiveTransceiver({
onIdle() {
let newPhase = phase + 1;
if (phase === 0) {
const packet = new Packet({
destinationAddress: 0x0010,
sourceAddress: myAddress,
command: 0x0100,
// TODO: set `frameCount` and `frameData` with your actual data to transmit
});
// queue the packet for transmission
tx.getEncoder().queue(packet);
// automatically suspend for 100 milliseconds after the transmission completed, will generate next `onIdle()` afterwards
tx.getEncoder().suspendWithTimeout(100 /* milliseconds */);
} else if (phase === 1) {
const packet = new Packet({
destinationAddress: 0x6651,
sourceAddress: myAddress,
command: 0x0300,
// TODO: set `frameCount` and `frameData` with your actual data to transmit
});
tx.getEncoder().queue(packet);
tx.getEncoder().suspendWithTimeout(100);
} else if (phase === 2) {
// ...
} else {
newPhase = 0;
}
phase = newPhase;
},
onTransmit() {
// TODO: see above for notes about handling outgoing I/O
}
});
// resume directly to start emitting `onIdle()` callbacks whenever it is "allowed" to send
tx.getEncoder().resume();
// TODO: see above for notes about handling incoming I/O