MODBUS报文负数优化处理代码(补码,反码) java

前言

text
1 2 3
<a href="#_3">写此文档的原因</a>    <a href="#_6">参考资料</a>    <a href="#_12">简洁代码实现方式</a>    <a href="#_22">实现代码</a>    
 
  <a href="#_25">核心转码代码</a>      <a href="#_68">转换的工具类:</a>      <a href="#_169">结束</a>            

前言

写此文档的原因

今天弄MDOBUS报文处理,因为以前弄个,但处理数据时用了各种 If 和 for,代码极度不简洁也不高效,所以单独查询更简洁更高效的处理方法,但各种资料中,解决原理和解决方式都有,但具体代码描述和实际操作并没有说明,所以单独记录了此文档。

参考资料

以下文章需取重点同步观看,每个都只讲了部分内容
代码中所使用的运算符及原因: 点击查看原帖.
为什么转换会这么麻烦及原因: 点击查看原帖.
原码、反码、补码和符号介绍: 点击查看原帖.

简洁代码实现方式

代码想简洁高效,首先就要理清操作原因:

在MODBUS中,正数取值和负数取值是不一样的; 如果直接把负数当成正数转码进行数据转换,转换时会把符号位当数据进行了转换; MODBUS报文分为状态位(简单理解为false和true)和数据位数据; MODBUS数据位分为数值和字符(ACISS),数值又分为正数和小数,此处不讨论小数 MODBUS一个字节(byte)只能保存0~255(也可以说是-128到127)的数字 当数值超过一个byte时,需继续增加byte传递数据,此时需区分高位和低位(可以简单理解为高位值为2时代表两个满值低位) 有部分厂家,高位和低位具体是哪个不一定,有的前面是高位,有的后面是高位,需确认区分

实现代码

实现的代码分两部分,一个是简洁的,少的关键转码(主要是负数),还有一个是地城的byte工具类,但是我不需要使用它的jar包,所以把方法抽了出来成为工具类使用

核心转码代码

此代码为转码核心,先区分正负值在根据符号去处理

text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/**
    * MODBUS转码
    * (不支持小数转码)
    *
    * @param byte1 待转码数据(高位)
    * @param byte2 待转码数据(低位)
    */
   public Double byteToInt(byte byte1,byte byte2){
      //高位在前,低位在后
      //byte[] bs1 = new byte[]{(byte) 0xFF,(byte) 0x3D};      //测试数据:负数   -195
      //byte[] bs1 = new byte[]{(byte) 0x00,(byte) 0xC3};      //测试数据:正数   195
      int number = 0;
      //1、取出高位符号
      if(byte1<0){
         //负数先高位左移8位和低位拼接,再取反
         number =~(byte1<<8 | byte2);
         //补码并赋值符号位
         number =-(number+1);
      }else{
         //正数直接转成16位字符(主要为拼接字符串)
         String s = XmByteUtils.toHexAscii(new byte[]{byte1,byte2});
         //直接把16进制字符转int
         number = Integer.parseInt(s, 16);
      }
      //把已取出的数据做额外处理
      return (double) number / 10;      //具体数据处理
   }

代码解析:
原始数据转换例子(数值随便写的,仅是案例):
byte1:10000101
byte2:00011101

转换解释及案例
byte1<<8:把高位数据左移8位,给低位数据合并留出空间 (例:10000101 00000000)
byte1<<8|byte2:把高位数据和低位数据合并(例:10000101 00011101)
~(byte1<<8 | byte2):反码:(例:01111010 11100010)
-(number+1):补码就是加一,因为符号位丢失,再给他加上(例:11111010 11100011)

转换的工具类:

具体干什么就不细讲了,我也是把底层原码考出的,当做工具类使用

text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
package com.system.xiaoma.util;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;

public class XmByteUtils {

   /**
    * MODBUS报文字符串(不带空格)转 byte可发送的HEX格式
    * @param s MODBUS报文字符串
    */
   public static byte[] fromHexAscii(String s) throws NumberFormatException {
      try {
         int len = s.length();
         if ((len % 2) != 0)
            throw new NumberFormatException("Hex ascii must be exactly two digits per byte.");

         int out_len = len / 2;
         byte[] out = new byte[out_len];
         int i = 0;
         StringReader sr = new StringReader(s);
         while (i < out_len) {
            int val = (16 * fromHexDigit(sr.read())) + fromHexDigit(sr.read());
            out[i++] = (byte) val;
         }
         return out;
      } catch (IOException e) {
         throw new InternalError("IOException reading from StringReader?!?!");
      }
   }

   /**
    *
    * @param c 需发送命令的16位数值转换
    * @return
    * @throws NumberFormatException
    */
   private static int fromHexDigit(int c) throws NumberFormatException {
      if (c >= 0x30 && c < 0x3A)
         return c - 0x30;
      else if (c >= 0x41 && c < 0x47)
         return c - 0x37;
      else if (c >= 0x61 && c < 0x67)
         return c - 0x57;
      else
         throw new NumberFormatException('\'' + c + "' is not a valid hexadecimal digit.");
   }

   /**
    * byte数组转十六位字符串(不带空格)
    * @param bytes 待转换的byte数组
    */
   public static String toHexAscii(byte[] bytes) {
      int len = bytes.length;
      StringWriter sw = new StringWriter(len * 2);
      for (int i = 0; i < len; ++i)
         addHexAscii(bytes[i], sw);
      return sw.toString();
   }

   /**
    * byte转16位并拼接
    * @param b    数组
    * @param sw   字符串
    */
   static void addHexAscii(byte b, StringWriter sw) {
      int ub = unsignedPromote(b);
      int h1 = ub / 16;
      int h2 = ub % 16;
      sw.write(toHexDigit(h1));
      sw.write(toHexDigit(h2));
   }

   /**
    * byte取补位数据
    * @param b 数组
    * @return
    */
   public static int unsignedPromote(byte b) {
      return b & 0xff;
   }

   /* note: we do no arg. checking, because    */
   /* we only ever call this from addHexAscii() */
   /* above, and we are sure the args are okay  */
   private static char toHexDigit(int h) {
      char out;
      if (h <= 9) out = (char) (h + 0x30);
      else out = (char) (h + 0x37);
      //System.err.println(h + ": " + out);
      return out;
   }
}

结束

如果上面代码有BUG或更方便的写法请在评论告知,我会及时更新的,后续使用中我也会根据使用情况更新代码,谢谢!

晋级处理方式来啦:点击查看新内容

原文地址:https://blog.csdn.net/qq_35449424/article/details/119822313?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168904451816800186548539%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=168904451816800186548539&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-24-119822313-null-null.142^v88^control_2,239^v2^insert_chatgpt&utm_term=java%E4%BC%98%E5%8C%96