如何将十六进制字符串转换为可读 Number/Decimal 表示(来自 RS485 串行设备的压力读出响应)

How to Covert Hexadecimal String to readable Number/Decimal Representation (Pressure readout response from RS485 serial device)

所以我正在努力将此离子计(设备读取室内压力)从 ASCII 协议切换到二进制协议(尽管它实际上通过十六进制进行通信)。该设备通过 RS485 串行通信与我们的 PC 连接。我目前正在使用 javascript 来发送和接收我的 ASCII 协议命令。

发送的命令和从设备收到的响应都有一种格式。 Length of command send to device = length of response returned

正在向设备发送命令:

Byte 0: '!' (hex 0x21) <-- start character
Byte 1: '1' (hex 0x01) <-- address of unit, this is a constant
Byte 2: '2' (hex 0x02) <-- command byte value, this will change as you change commands
Byte 3: '0' (hex 0x00) <-- dont care UNITS
Byte 4: '0' (hex 0x00) <-- dont care data 1
Byte 5: '0' (hex 0x00) <-- dont care data 2
Byte 6: '0' (hex 0x00) <-- dont care data 3
Byte 7: '0' (hex 0x00) <-- dont care data 4
Byte 8: 183 (hex 0xB7) <-- CRC - Carriage return - Not actually typed by user

For testing, this command is physically sent through a serial terminal as: 
'0x21 0x01 0x02 0x00 0x00 0x00 0x00 0x00 0xB7'

正在接收单位的回复:

Byte 0: '*' (hex 0x2A) <-- start character
Byte 1: '1' (hex 0x01) <-- address of unit, this is a constant
Byte 2: '2' (hex 0x02) <-- command byte value, this will change as you change commands
Byte 3: '0' (hex 0x00) <-- UNITS: 0=Torr, 1=Pascals, 2=mBAR
Byte 4: '0' (hex 0x00) <-- floating point byte 1
Byte 5: '0' (hex 0x00) <-- floating point byte 2
Byte 6: '0' (hex 0x00) <-- floating point byte 3
Byte 7: '0' (hex 0x00) <-- floating point byte 4
Byte 8: 148 (hex 0x94) <-- CRC - Carriage return

This response is received as: '2A 01 02 00 00 00 00 00 B7' 
or '0x2A 0x01 0x02 0x00 0x00 0x00 0x00 0x00 0xB7'
(not sure if that's how it actually responds, or if my terminal program automatically formats for readability)

所以,我目前正在通过 ASCII 协议与该设备通信,如果我想使用 ASCII 命令实现上述目的,我会发送例如“#01RDS”来读取压力,我会收到一个类似的值“9.36e-8”作为我的回应。我正在切换到二进制 protocol/hex 命令,因为使用该方法可以使用更多有用的命令。我应该如何切换到此十六进制代码通信?我在 GUI 中使用此代码,我使用 node.js、vue.js、HTML 和 CSS 在 Electron 中编写。 我只是希望能够将设备的响应正确转换为可读格式,我可以轻松地使用这种格式根据这些响应中的小数点值执行 actions/trigger 函数。 我不确定如何处理数据,因为它以 2A 01 02 00 00 00 00 00 B7 格式出现。 我希望程序按照以下思路思考:

 - Check if the first byte (byte 0) contains the start character '!' --> Byte0 = 2A --> Proceed
  - Check Byte3 units, if 0 --> units = Torr, Proceed
  - Check and store values of Byte4-Byte7
  - Convert Hex values of floating point bytes4-7 to Decimal,   etc

如果有人对我如何处理此问题有任何提示,我将不胜感激。请参阅下面的手册以更深入地了解通信的工作原理。

Link 到我正在使用的特定设备的操作手册: https://www.lesker.com/newweb/gauges/pdf/manuals/392usermanual.pdf

*PDF pg. 59: RS485 Serial Communicatoins - Binary Protocol
*PDF pg. 61: Notes when using Binary Protocol
*PDF pg. 62: RS485 Commands Summary

在回答标题中的问题时:

  1. 如果hexString是一串十六进制数字对,

    let binArray = hexSring.match(/../g).map( hex=> parseInt(hex,16));
    

    将字符串转换为 [0-255] 范围内的无符号整数数组,使用反逻辑

  2. 如果binArray是[0-255]范围内的无符号整数数组,

    let hexString = binArray.map( byte=>byte.toString(16).padStart(2,'0')).join('');
    

    将数组转换为从第一个数组条目开始的十六进制字符对字符串。

需要吗?

这个答案最初描述了二进制协议消息被转换成十六进制数字对流,以便通过 RS-485 通信 link 进行传输。这是基于压力表的(某种)十六进制输出显示在终端上的问题报告,以及手册第 59 页上的附加注释说

  1. The messages sent to the unit and received from the unit are sent in hexadecimal,...

对此答案的评论指出,二进制协议 "messages" 在手册的其他地方进行了记录,就好像直接通过 RS485 link 作为二进制字节发送而不转换为十六进制。

我同意这很可能是 "sent in hexadecimal" 被理解为 "sent as binary bytes" 的情况,上面的 binary/hex 转换仅供参考。

二进制协议的更多信息

  1. 二进制消息是 8 个二进制位字节(或 "octets")的序列,以根据前面的消息字节计算的 8 位循环冗余校验字节结束。 (CRC值的属性是对整个报文进行CRC计算,包括末尾的CRC8字节,如果没有发生传输错误,结果为零。)

  2. Carriage returns 似乎是 ASCII 协议的一部分,我发现手册中没有提到它们在二进制消息中的使用(请在实践中重新检查)。

  3. 回复的预期长度与发送的长度相同。

  4. 如果一个二进制字节代表一个ASCII字符,它的数值就是该字符的ASCII字符码。例如

    '*'.charCodeAt(0)
    

    将星号转换为数字 42(十进制),并且

    String.fromCharCode( 42)
    

    将数字 42 转换为星号。

  5. 转换浮点数比较棘手。我在代码片段中使用的方法是将 4 个浮点字节值放入 Uint8 类型数组,然后使用数组缓冲区创建一个 Float32 类型数组,第一个元素可以是作为(浮点)数字检索。正确性取决于 fp 字节在消息中的顺序(小端或大端),使用零进行测试不会显示哪个 - 您可能必须反转 JavaScript 的字节顺序(希望不是)。使用 DataView 可能是另一种方法。我还假设仪器使用与 JavaScript 相同的标准提供 32 位浮点值(它们已经有一段时间了)。

此示例着眼于解码收到的消息示例,2A0102000000000094(十六进制)。 CRC8 函数已从手册中提供的 C 版本翻译而来,并进行了修改以接受字节值数组并计算调用中提供的所有字节的 CRC:

function calculate_CRC8( uint8Array) {
    var CRC_Value;
    var Counter;
    var BitCounter;
    var XOR_Byte;
    var TransmitByte;

    // Initialize the local variable.
    CRC_Value = 0xFF;
    
    // Calculate the CRC.
    for(Counter = 0; Counter < uint8Array.length; Counter++) {
        TransmitByte = uint8Array[ Counter];
        BitCounter = 8;
    
        while(BitCounter != 0) {
            BitCounter--;
            XOR_Byte = TransmitByte ^ CRC_Value;
            if((XOR_Byte & 0x80) != 0) {
                CRC_Value ^= 0x0E;
                CRC_Value <<= 1;
                CRC_Value |= 1;
            }
            else{
                CRC_Value <<= 1;
            }
    
            // Left shift the calculation byte.
            TransmitByte <<= 1;
            // keep CRC to 8 bits (not needed in C)
            CRC_Value &= 0xff;  
        }
    }
    // Return the calculated CRC value.
    return(CRC_Value);
}

/**********  Example to check received message   ********/

// Only if Encoded as Hexadecial digits:
// let hexReceived = "2A0102000000000094"; // without <CR>
// convert hexString to 8 bit integers in range [0, 255]
// let binReceived = hexReceived.match(/../g).map( hex=> parseInt(hex,16));

let binReceived = [0x2a, 1, 2, 0,0,0,0,0, 0x94];  // binary protocol byte values

console.log( "binReceived: ", binReceived)

// check CRC of binary bytes, including sent CRC, reduces to zero
let CRC = calculate_CRC8( binReceived);
if( CRC) {
    // handle Error
 throw new Error( "bad CRC at end of " + hexReceived);
}

// extract fp value

let bytes = binReceived.splice (4,4);  // 4 byte Array
// reverse byte order if in wrong order!!!!

let buffer = Uint8Array.of.apply(Uint8Array, bytes).buffer;
console.log( "buffer: ", buffer)

let fp = new Float32Array( buffer) [0];

console.log( "message start '%s', address %s, command %s, units %s, value %s",
   String.fromCharCode(binReceived[0]),
   binReceived[ 1],
   binReceived[ 2],
   ["TORR", "PASCALS", "mBAR"][ binReceived[3]],
   fp
);

/************* Example to encode a command to send *********/

// Example command to read gauge 1's IG pressure

let cmdBytes = [
   '!'.charCodeAt(0), // start character
   1,                 // instrument address
   2,                 // Read IG Pressure command
   0,                 // xx units
   0,0,0,0            // xxxxxxxx float32 value (zero)
];

// calculate and add CRC

cmdBytes.push( calculate_CRC8( cmdBytes));

console.log("/* Send command example*/");
console.log("Bytes to read IG pressure from gauge 1: ", cmdBytes);

// send cmdBytes to the gauge...

要使用上面的 calculateCRC8 JavaScript 函数发送命令,

  • assemble 数组中命令的二进制字节,不带 CRC,
  • 通过使用仅包含命令字节的数组调用 calculateCRC8 来计算命令字节的 CRC - return 值是它们计算出的 CRC8 校验值。
  • 将 CRC8 值附加到数组,
  • 将所有数组字节发送到仪器。