6

I want to read Data via node red modbus node from a data source. The range is -20000 to 20000, but the node cannot handle negative numbers, so I had to convert them to binary numbers (DWORD), split them in the lower and higher word and convert these words back to integers.

var low

function dec2bin(dec){
    return (dec >>> 0).toString(2);
}

var a = msg.payload

if (a >= 0){

    a = dec2bin(a);
    a = parseInt(a,2);

} else {

    a = dec2bin(a);
    a = a.substr(16);
    a = parseInt(a,2);

} 

low = { payload: a };

return low;

For visualisation I want to use the dashboard nodes, but therefor I need to join the 2 binary strings together and convert them to an int.

Problem:

node red converts them as a qword, so the binary number 1111 1111 1111 1111 1111 1100 0001 1000 is seen as 4.294.966.296‬, not as -1000. But if i fill the next rest with 1 lime so: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1100 0001 1000 puts out 18446744073709552000

Thanks

c0nr3f
  • 87
  • 7

3 Answers3

4

parseInt works on variable-length strings as input, which means that it cannot recognize the most significant bit as a sign bit. In other words: It parses binary strings like decimal strings, using a - as sign; so you have to use a different method than parseInt.

What hinders you from taking that 16-bit value and just pass it as unsigned? You'd just have to check whether the value coming in to your javascript is bigger than 32767, then do the two-complement conversion manually:

Your incoming numbers range would then be 0..20000 for positive "originals" and 45536..65535 for the original range -20000..-1 (if I'm thinking correct right now, I didn't verify that). This would mean that conversion is easy:

if( number > 32767 ) {
  number = -65536 + number;
}

Or, for any binary number type (that is smaller than the number size of the language you're working in):

if( number > SIGNED_MAX_INT ) {
  number = -(UNSIGNED_MAX_INT+1) + number;
}

(Those constants don't exist as written, they're more like pseudo code here)

orithena
  • 1,455
  • 1
  • 10
  • 24
2

First, it is REALLY bad practice to create new msg objects in function nodes, you should be just updating the msg.payload value and passing on the original object.

Next the easiest way to do this will be to work with a Buffer

e.g.

var b = Buffer(4)
b.writeUInt32BE(msg.playload)
msg.payload = b.readInt32BE()

return msg;

A join node in manual mode can combine the 2 smaller buffers into one of 16bits in length.

hardillb
  • 54,545
  • 11
  • 67
  • 105
  • Ok that your for your answer, can you tell me why it is a bad practice to create a new object? The problem is, that I have 2 uint16 values. How can I merge the buffers to one, so that I can use your function? – c0nr3f May 13 '20 at 14:56
  • Here is an example for the high word: {"data":[65535],"buffer":[0xff,0xff]} and for the low byte: {"data":[64536],"buffer":[0xfc,0x18]} – c0nr3f May 13 '20 at 15:00
  • OK, as it's already buffers, just merge them into a single buffer and read from that with the supplied methods. You don't create new msg's because this throws away any extra meta data that might be travelling with the payload, e.g. the `msg.resp` object added by a http-in node needed by a http-response node to work. – hardillb May 13 '20 at 15:09
  • I cannot merge them with a join node, do I have to do it manually somehow and can you tell me how? Sorry, I'm quite new to javascript. – c0nr3f May 13 '20 at 15:20
  • Actually you can just merge them with the join node (assuming they always arrive in order) – hardillb May 13 '20 at 15:22
  • No, but i can set the msg.complete to arrange the order at least in the second telegram. If I join manually as a buffer, I get an error. But if I join as an array, I get 0 back. Example after the join funtion: [{"data":[64536],"buffer":[252,24]},{"data":[65535],"buffer":[255,255]}] – c0nr3f May 13 '20 at 15:31
  • You don't need to combine all of `msg.payload`, you can limit it to just `msg.payload.buffer` – hardillb May 13 '20 at 17:27
1

Well, JavaScript is lame at this point because it does not have strong types to do this. Probably not the best, but one of the ways to do this is creating custom Int class.

class Int {
  constructor(length, binaryStr) {
    const cleanBinary = "0".repeat(length - binaryStr.length) + binaryStr
    if (cleanBinary.startsWith("1")) {
      const invertedBinary = cleanBinary.split("")
        .map(char => char === "1" ? "0": "1")
        .join("")
      this.decValue = -parseInt(invertedBinary, 2) - 1
    } else {
      this.decValue = parseInt(cleanBinary, 2)
    }
  }
}

Something like that

new Int(8, "11111100")
> Int {decValue: -4}
strBin = "11111111111111111111110000011000";
new Int(strBin.length, strBin)
> Int {decValue: -1000}

This may be developed into something more advanced and can be used in browser as well.

Tim Nimets
  • 348
  • 2
  • 9
  • I have some problems to implement this class into node red. Furthermore, I'm quite new to javascript so it's not so easy to me converting this into a function. What I need here, is:strBin = msg.payload; new Int(strBin.length, strBin) msg.payload = Int; This does not work, because it's a class. Do you have any suggestion ? – c0nr3f May 13 '20 at 15:25
  • If you have a fixed size number for example 16 bits, you can get the answer in this way `const convertedNumber = new Int(16, msg.playload).decValue`. But note that that class accepts binary without spaces. You can add some formatting before. – Tim Nimets May 13 '20 at 18:03