Skip to content

Commit

Permalink
Merge pull request #102 from widesky/bugfix/issue101-getdata-slowness
Browse files Browse the repository at this point in the history
Add some flags to `getData` to avoid lengthy time-outs
  • Loading branch information
Apollon77 authored Aug 11, 2023
2 parents e641ad5 + 7eaa082 commit eb42394
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 26 deletions.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ In the options object you set the communication and other parameter for the libr
* *serialPort*/*serialBaudRate*: For Serial communication you set the *serialPort* (e.g. /dev/ttyUSB0) and optionally the *serialBaudRate* to connect. Default Baudrate is 2400baud if option is missing
* *autoConnect*: set to "true" if connection should be established automatically when needed - else you need to call "connect()" before you can communicate with the devices.

#### A note about TCP communcation `timeout`

On TCP networks, this sets the response time-out in milliseconds. The default is a quite conservative 4 seconds (4000ms). For serial networks, it is a function of the baud rate: between 11 and 330 "bit times" to which one should factor in some round-trip-time delay for network traversal.

`libmbus` uses [the following equation](https://github.com/rscada/libmbus/blob/master/mbus/mbus-serial.c#L67-L79) to estimate this on serial networks (factoring in about 100ms round-trip-time between the host and the M-Bus driver):

```
(330 + 11) / BAUD + 0.15
```

* 2400 baud network should respond within 300ms (~292ms), assuming 100ms host-driver round-trip time.
* 300 baud network should respond within 1300ms (~1287ms), assuming 100ms host-driver round-trip time.

### connect(callback)
Call this method to connect to TCP/Serial. Needs to be done before you can communicate with the devices.
The optional callback will be called with an *error* parameter that is *null* on success.
Expand All @@ -60,8 +73,13 @@ The optional callback will be called with an *error* parameter that is *null* on
The method will return true/false when no callback is provided.
When you have provided a callback and you try to close the connection while communication is in progress the method will wait till communication has finished (checked every 500ms), then close the connection and then call the callback. When not using a callback then you get false as result in this case. When you set *waitTillClosed* while using a callback the callback will be called with an error if communication is still ongoing.

### getData(address, callback)
### getData(address, [options,] callback)
This method is requesting "Class 2 Data" from the device with the given *address*.

The *options* parameter is optional, and if given, is in the form of an object with one or more of the following properties set:

* `pingFirst`: (Boolean; default `true`) Ping the target devices first before attempting communication. This is primarily a work-around to some devices (such as the Sontex Supercal531) that must be "reset" first. The default is to always perform these initial pings, however the feature can be disabled if the M-Bus slave devices are known to behave without it.

The callback is called with an *error* and *data* parameter. When data are received successfully the *data* parameter contains the data object.
When you try to read data while communication is in progress your callback is called with an error.

Expand Down
24 changes: 20 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,23 @@ class MbusMaster {
});
}

getData(address, callback) {
getData(address, options, callback) {
// default options
// pingFirst: Work-around buggy behaviour with some M-Bus devices,
// notably Sontex Supercal531
// https://github.com/rscada/libmbus/pull/95
let pingFirst = true;

if (typeof(options) === "function") {
callback = options;
options = null;
}

if (options) {
// de-structure
({pingFirst} = options);
}

if (!this.mbusMaster.connected && !this.options.autoConnect) {
if (callback) callback(new Error('Not connected and autoConnect is false'));
return;
Expand All @@ -137,7 +153,7 @@ class MbusMaster {
if (callback) callback(err);
return;
}
this.mbusMaster.get(address, (err, data) => {
this.mbusMaster.get(address, pingFirst, (err, data) => {
if (!err && data) {
//data = JSON.parse(data).MBusData;
const parserOpt = {
Expand Down Expand Up @@ -165,9 +181,9 @@ class MbusMaster {
});
}

getDataAsync(address) {
getDataAsync(address, options=null) {
return new Promise((resolve, reject) => {
this.getData(address, (err, data) => {
this.getData(address, options, (err, data) => {
if (err) {
reject(err);
}
Expand Down
51 changes: 30 additions & 21 deletions src/mbus-master.cc
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ static int init_slaves(mbus_handle *handle)

class RecieveWorker : public Nan::AsyncWorker {
public:
RecieveWorker(Nan::Callback *callback,char *addr_str,uv_rwlock_t *lock, mbus_handle *handle, bool *communicationInProgress)
: Nan::AsyncWorker(callback), addr_str(addr_str), lock(lock), handle(handle), communicationInProgress(communicationInProgress) {}
RecieveWorker(Nan::Callback *callback, char *addr_str, bool ping_first, uv_rwlock_t *lock, mbus_handle *handle, bool *communicationInProgress)
: Nan::AsyncWorker(callback), addr_str(addr_str), ping_first(ping_first), lock(lock), handle(handle), communicationInProgress(communicationInProgress) {}
~RecieveWorker() {
free(addr_str);
}
Expand All @@ -259,12 +259,15 @@ class RecieveWorker : public Nan::AsyncWorker {

memset((void *)&reply, 0, sizeof(mbus_frame));

if (init_slaves(handle) == 0)
if (ping_first)
{
sprintf(error, "Failed to init slaves.");
SetErrorMessage(error);
uv_rwlock_wrunlock(lock);
return;
if (init_slaves(handle) == 0)
{
sprintf(error, "Failed to init slaves.");
SetErrorMessage(error);
uv_rwlock_wrunlock(lock);
return;
}
}

if (mbus_is_secondary_address(addr_str))
Expand Down Expand Up @@ -308,20 +311,23 @@ class RecieveWorker : public Nan::AsyncWorker {
// primary addressing
address = atoi(addr_str);

// send a reset SND_NKE to the device before requesting data
// this does not make sense for devices that are accessed by secondary addressing
// as the reset de-selects the device
// taken from https://github.com/rscada/libmbus/pull/95
if (mbus_send_ping_frame(handle, address, 1) == -1)
if (ping_first)
{
sprintf(error, "Failed to initialize slave[%s].", addr_str);
SetErrorMessage(error);
// send a reset SND_NKE to the device before requesting data
// this does not make sense for devices that are accessed by secondary addressing
// as the reset de-selects the device
// taken from https://github.com/rscada/libmbus/pull/95
if (mbus_send_ping_frame(handle, address, 1) == -1)
{
sprintf(error, "Failed to initialize slave[%s].", addr_str);
SetErrorMessage(error);

// manual free
mbus_frame_free((mbus_frame*)reply.next);
// manual free
mbus_frame_free((mbus_frame*)reply.next);

uv_rwlock_wrunlock(lock);
return;
uv_rwlock_wrunlock(lock);
return;
}
}
}

Expand All @@ -348,7 +354,7 @@ class RecieveWorker : public Nan::AsyncWorker {
SetErrorMessage(error);

// manual free
mbus_frame_free((mbus_frame*)reply.next);
mbus_frame_free((mbus_frame*)reply.next);

uv_rwlock_wrunlock(lock);
return;
Expand Down Expand Up @@ -390,6 +396,7 @@ class RecieveWorker : public Nan::AsyncWorker {
private:
char *data;
char *addr_str;
bool ping_first;
uv_rwlock_t *lock;
mbus_handle *handle;
bool *communicationInProgress;
Expand All @@ -401,11 +408,13 @@ NAN_METHOD(MbusMaster::Get) {
MbusMaster* obj = node::ObjectWrap::Unwrap<MbusMaster>(info.This());

char *address = get(Nan::To<v8::String>(info[0]).ToLocalChecked(),"0");
Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());
bool ping_first = Nan::To<bool>(info[1]).FromJust();
Nan::Callback *callback = new Nan::Callback(info[2].As<Function>());

if(obj->connected) {
obj->communicationInProgress = true;

Nan::AsyncQueueWorker(new RecieveWorker(callback, address, &(obj->queueLock), obj->handle, &(obj->communicationInProgress)));
Nan::AsyncQueueWorker(new RecieveWorker(callback, address, ping_first, &(obj->queueLock), obj->handle, &(obj->communicationInProgress)));
} else {
Local<Value> argv[] = {
Nan::Error("Not connected to port")
Expand Down

0 comments on commit eb42394

Please sign in to comment.