用Java读取序列信息

时间:2013-08-06 17:10:15

标签: java android python

我正在开发一个已经用Python开发的Android应用程序。在Python程序中,有一行我想要完全理解:

self.comfd = Serial(...) # from the pySerial API
....
self.buffer  = list(struct.unpack('192H', self.comfd.read(384)))

根据我的理解,self.comfd.read(384)正在读取384个字节,而unpack('192H'正在从该数据中解包192个无符号短路。这是对的吗?

现在在Java中,我已经能够使用

读取缓冲区了
SerialPort device = SerialPort(file, baud, flags);
InputStream in = device.getInputStream(); 

我的问题是,现在我有输入流,如何创建像Python程序一样的无符号短路?

我尝试了什么(没有产生正确的值):

byte[] buffer = new byte[384];
in.read(buffer);
ByteBuffer bb = ByteBuffer.allocate(2);
for (int i = 0; i < buffer.length / 2; i++) {
    bb.put(buffer[i]);
    bb.put(buffer[i + 1]);
    short val = bb.getShort(0);
    System.out.println(val);
    bb.clear();
}

我做错了什么?谢谢你的帮助!

编辑:我收录了Jason C的答案,而且我的循环错误。通过将其更改为

for (int i = 0; i < buffer.length; i=i+2)解决了我的问题。

2 个答案:

答案 0 :(得分:3)

您可以使用char(它是Java中的16位无符号值),例如:

byte[] buffer = ...;
ByteBuffer bb = ByteBuffer.wrap(buffer); // don't need to put()
int val = (int)bb.getChar(0);

使用bb.order()设置big-little-endian。

你也可以在不使用ByteBuffer的情况下将2个字节打包成int(假设是little-endian)。字节是用Java签名的,所以你必须在移位之前将字节转换为无符号值,你可以通过将它临时存储在一个short(或int或任何大到足以容纳0-255的东西)来完成:

short b0 = (buffer[0] & 255); // trick converts to unsigned
short b1 = (buffer[1] & 255);
int val = b0 | (b1 << 8);

// or just put it all inline:
int val = (buffer[0]&255) | ((buffer[1]&255) << 8);

对于big-endian数据,只需交换b0和b1。

希望有所帮助。

答案 1 :(得分:1)

Java没有无符号数字(char是无符号的16位但它不是数字,char的数学将始终导致int的隐式强制转换

如果您将2个字节的无符号数据读入short并想要查看范围为0-65535(而不是-32768 - 32767)的值,则必须使用可以包含值的类型那范围。

如果是16位short,则下一个更大的是32位int。实现这一诀窍的转换是

short signed = ...;
int unsigned = signed & 0xFFFF;

假设signed的值为0xFFFF,则会发生以下情况:

short signed = -1; // FFFF on byte level

表达式signed & 0xFFFF包含shortint0xFFFF是一个文字整数类型号,在Java源代码中找到它时被视为int。您可以将其long更改为0xFFFFL(如果您想将未签名的int转换为long,则需要这样做。)

由于&运算符需要共同类型的Java,因此Java会默默转换较小的一行。

int stillSigned = (int) signed; // hidden step

它仍将具有完全相同的-1值,因为这是在无条件查看它之前的情况,但它在字节级别上更改为0xFFFFFFFF

现在应用位操作来删除所有添加的FF s

int unsigned = stillSigned & 0xFFFF;

并且您在字节级别最终得到0x0000FFFF,并且最终可以看到65535的值。

由于您碰巧拥有16位值,因此可以使用char并将其强制转换为int

char value = ...;
int unsigned = value;

但上述方法适用于任何未签名的转化:byteValue & 0xFFshortValue & 0xFFFFintValue & 0xFFFFFFFFL


接下来你应该做的就是不要使用简单的InputStream来做

SerialPort device = SerialPort(file, baud, flags);
InputStream in = device.getInputStream(); 
byte[] buffer = new byte[384];
in.read(buffer);

原因是InputStream#read(byte[])无法保证读取缓冲区中所需的所有字节。它返回它已读取的字节数,如果流已完成,则返回-1。手动编写确保填充缓冲区的代码是令人讨厌的,但有一个简单的解决方案:DataInputStream

SerialPort device = SerialPort(file, baud, flags);
DataInputStream in = new DataInputStream(device.getInputStream());
byte[] buffer = new byte[384];
in.readFully(buffer);

DataInputStream具有非常好的功能,您可以使用:

SerialPort device = SerialPort(file, baud, flags);
DataInputStream in = new DataInputStream(device.getInputStream());
int unsignedShort = in.readUnsignedShort();

byte[]数据中获取不同数字的另一种方法是使用ByteBuffer,因为它提供了.getShort()等方法

SerialPort device = SerialPort(file, baud, flags);
DataInputStream in = new DataInputStream(device.getInputStream());
byte[] buffer = new byte[384];
in.readFully(buffer);
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
while (byteBuffer.hasRemaining()) {
    int unsigned = byteBuffer.getChar();
    System.out.println(unsigned);
}