pyserial read variable byte array - python

I am writing a code that can flexibly read a mutable array (4 byte array and 7 byte array).
I don't think it's a good idea to read 7 bytes continuously and judge it by the number of bytes sent in the 2nd byte.
Reading an array that is sent in 4 bytes as 7 bytes results in slow reception.
serial_port = serial.Serial(
port="/dev/ttyUSB0",
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
)
if __name__ == '__main__':
while True :
time.sleep(1)
if serial_port.readable():
serial_port.flushInput()
serial_port.timeout = None
data =serial_port.read(7)
l_data=list(data)
print("Header : " , str.join("", ("0x%02X " % i for i in l_data)))
Results received from connected equipment
ouput:
Header : 0x2B 0x04 0x53 0x7C 0x2B 0x04 0x53 #0x04 is the actual total number of byte
Header : 0x2b 0x07 0x51 0x06 0x07 0x46 0x3a #0x07 is the actual total number of byte
If I replace read() with readline(), nothing comes up.
So did read_until().
(Actually, it is because of my lack of skills.)
I tried to find a solution to find the end of the array (\n or \r) and truncate it, but it didn't work.
Is there a way to flexibly receive 4 bytes and 7 bytes?

Related

Python 3 garbage data into the struct.unpack

I am trying to send and receive messages via socket using Python 3.
BUFFER_SIZE = 1024
# Create message
MLI = struct.pack("!I", len(MESSAGE))
MLI_MESSAGE = MLI + str.encode(MESSAGE)
When the message receive:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
s.send(MLI_MESSAGE)
print ("Sent data: ‘", MESSAGE, "’")
# Receive MLI from response (you might want to add some timeout handling as well
resp = s.recv(BUFFER_SIZE)
resp = struct.unpack("!I", resp)[0]
print(resp)
resp:
b'\x00\t\xeb\x07\xdf\x01\x00\xdf\x02\x010'
I am getting that error:
struct.error: unpack requires a buffer of 4 bytes
I think it is related with \t char into the resp but I am not sure. How can I remove that \t char and how to solve that issue?
You are basically trying to do the following (sockets removed):
1 import struct
2
3 msg = "foobar"
4 mli = struct.pack("!I", len(msg))
5 mli_msg = mli + str.encode(msg)
6
7 len = struct.unpack("!I", mli_msg)[0]
8 print(len)
The extraction of the length in line 7 will fail since you put the whole mli_msg as argument to unpack, not only the expected 4 bytes for the len. Instead you should do:
7 len = struct.unpack("!I", mli_msg[:4])[0]
Apart from that it is wrong to first take the length of the message and then convert the message to bytes. The first takes the number of characters while the latter takes the number of bytes, which will differ when non-ASCII characters are involved - check len("ü") vs. len(str.encode("ü")). You need to first convert the message to bytes thus and then take the length to provide the correct byte length for what you send.
4 encoded_msg = str.encode(msg)
5 mli_msg = struct.pack("!I", len(encoded_msg)) + encoded_msg
Explanation of !I:
! indicates big endian alignment
I indicates unsigned integer type, occupying 4 bytes.
The variable resp value is b'\x00\t\xeb\x07\xdf\x01\x00\xdf\x02\x010', and the length exceeds 4.
You can intercept 4 bytes for parsing, like below.
import struct
resp = b'\x00\t\xeb\x07\xdf\x01\x00\xdf\x02\x010'
print(struct.unpack("!I", resp[:4])[0])
# 649991

on-the-fly parsing of binary serial data in python

I'm new to using Python3 for data acquisition. I'm trying to find a way to parse binary data from a serial port on Linux.
import serial
ser = serial.Serial(
port='/dev/ttyS0',
baudrate = 9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1)
counter = 0
while 1:
x = ser.read(31)
print (x)
This gives me a string which I'm not sure about the format of:
x='\x00\x00\x91\x00\x02\x88BM\x00\x1c\x00\x00\x00\x01\x00\x01\x00\x00\x00\x01\x00\x01\x00\xe1\x00K\x00\x1a\x00\x02\x00\x00'
using
x.encode('hex')
gives a string of hex values
x='000091000288**424d**001c00000001000100000001000100e1004b001a00020000'
where 0x42 is the end of message and 0x4d is start of message.
I can convert it into a base 10 list using
y = map(ord,x)
print(y)
Then I have a way to re-order the message using the indexes but surely there is a neater way? How do I create a list which starts at 0x4d to parse with?
If you are using python3, this is likely already bytes:
x='\x00\x00\x91\x00\x02\x88BM\x00\x1c\x00\x00\x00\x01\x00\x01\x00\x00\x00\x01\x00\x01\x00\xe1\x00K\x00\x1a\x00\x02\x00\x00'
It likely looks this way because Python printed it for you, and all of the non-ascii characters are shown in hex. Your start of message is in 0x42, 0x4d which is BM in ascii and can be seen in the data above between 0x88 and 0x00 as \x88BM\x00.
I would suggest just iterating over the byte array in x to do your parsing. The encoding and mapping should not be needed.
for b in x:
if b == 0x4d:
found_byte1 = True
... # etc

how to send an hexadecimal from python to the UART?

I need to understand how to send a message in hexadecimal format from UART for example:
msg='99' +'70b4c55ad8cdb7806a7b043069c4e0d8'
'99'is to distinct the start of message and the rest is the data.
As
import serial
import time
#serial port
ser = serial.Serial(
port='COM4',\
baudrate=230400,\
parity=serial.PARITY_NONE,\
stopbits=serial.STOPBITS_ONE,\
bytesize=serial.EIGHTBITS,\
timeout=0)
msg=b'\x99\x70\xb4\xc5\x5a\xd8\xcd\xb7\x80\x6a\x7b\x04\x30\x69\xc4\xe0\xd8'
ser.write(msg)
time.sleep(0.4)
while True:
print(ser.read(30))
ser.close() # close ports
But i don't unerstand th error:
msg= b'\x99\x70\xb4\xc5\x5a\xd8\xcd\xb7\x80\x6a\x7b\x04\x30\x69\xc4\xe0\xd8'
^
IndentationError: unexpected indent
I need then to compare by using c langage, to compare the first received byte 99 with 0x99.
int lenght = dev_uart_ptr->uart_read((void*)buffer,34 );
if (lenght<34)
{
if buffer [0]='0x99'
}
Thanks in advance.
It seems like you are loading to buf but using buffer in your c code.
You are reading 34 bytes to the buffer, then checking if the length is less than 34. Shouldn't work well. And judging by your example, it should be 17.
Encode will not convert msg to hex string. It will treat every character as a char, i.e. '0' is 48, '1' is 49 etc
Create bytes object using
msg = b'\x99\x70\xb4\xc5\x5a\xd8\xcd\xb7\x80\x6a\x7b\x04\x30\x69\xc4\xe0\xd8'
ser.write(msg)

How to find first byte of a serial stream with python?

I am trying to read a Datastream from RS232 with python 3.3.2 and pyserial. I start the stream with:
ser = serial.Serial('/dev/ttyUSB0', baudrate=19200, timeout=3, stopbits=serial.STOPBITS_TWO)
ser.write(bytes([0x05, 0x69, 0x02, 0x0A, 0x86]))
After this i get every second a 107 byte long dataset. First byte should be 107 (number of bytes) and second one should be 105 (Code).
What is the best way to seperate one 107 byte long dataset starting with 107 and 105? If I use print(ser.read(107)) a few times I get:
b'\x00\x00\x00\x00\x00P\xbf\x99\x10\xe0}\x86\xaaV\xd4\xeeg\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x87}i\xdf\n\x00\x00\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc1\xc3k\x00\x00\x00\xc0t\xb4\xbd\xf0\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Y\x00\x00\x00\x00\x00\x00\x80\x01\x00\x80\xc7\x10\xc0\xcd\xe6G\x0b\x99\xd4\xcb.'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc5\xeck\x00\x00\x00\xc0\xf4\xb3\xbd\xf0\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0V\x00\x00\x00\x00\x00\x00\x80\x01\x00\x00\xc8\x10#\xcd\xe67\x8b\x99\xd4\x0b/\x00\x00#\x00\xf6\xb7\xbf\xfd\xed\xaf:\x00\x00\x00\xe8\xf3J\xaf\x04\x00\x00\x00\x00\x00p\xbf\x99\x18\xe0}\x86\xaaV\xd4\xeeg\x12\x00\x00\x00\x00\x00\x00\x00'
The k (ascii for 107) is somewhere in the middle.
It might be helpful to flush your input before you put the request. I.e.,
ser.read(ser.inWaiting())
to read out all bytes which are waiting. Then, hoping that no further bytes are being sent, you can send your command:
ser.write(bytes([0x05, 0x69, 0x02, 0x0A, 0x86]))
This is supposed to make sure that all bytes which are coming next are an answer to this command.
Then read data until you get your 107:
found = False
buffer = '' # what is left from the previous run...
while not found:
rd = ser.read(50)
buffer += rd
sp = buffer.split(chr(107), 1)
if len(sp) == 2:
pkt = chr(107) + sp[1] # candidate for a valid packet
if pkt[1] == chr(105): #
while len(pkt) < 107: # TODO add a timeout condition here...
rd = ser.read(107 - len(pkt))
pkt += rd
found = True
else:
buffer = pkt[1:] # process this further...
else: # no 107 found; empty the buffer.
buffer = ''
# Now we have a pkt of 107 bytes and can do whatever we want with it.

Reading UDP Packets

I am having some trouble dissecting a UDP packet. I am receiving the packets and storing the data and sender-address in variables 'data' and 'addr' with:
data,addr = UDPSock.recvfrom(buf)
This parses the data as a string, that I am now unable to turn into bytes. I know the structure of the datagram packet which is a total of 28 bytes, and that the data I am trying to get out is in bytes 17:28.
I have tried doing this:
mybytes = data[16:19]
print struct.unpack('>I', mybytes)
--> struct.error: unpack str size does not match format
And this:
response = (0, 0, data[16], data[17], 6)
bytes = array('B', response[:-1])
print struct.unpack('>I', bytes)
--> TypeError: Type not compatible with array type
And this:
print "\nData byte 17:", str.encode(data[17])
--> UnicodeEncodeError: 'ascii' codec can't encode character u'\xff' in position 0: ordinal not in range(128)
More specifically I want to parse what I think is an unsigned int. And now I am not sure what to try next. I am completely new to sockets and byte-conversions in Python, so any advice would be helpful :)
Thanks,
Thomas
An unsigned int32 is 4 bytes long, so you have to feed 4 bytes into struct.unpack.
Replace
mybytes = data[16:19]
with
mybytes = data[16:20]
(right number is the first byte not included, i.e. range(16,19) = [16,17,18]) and you should be good to go.

Categories

Resources