2

Summary

I have a MATLAB script sending a serial message to a Python script using a pair of telemetry radios. The MATLAB data are all singles (32 bit float, IEEE Standard 754). When Python receives the serial message, however, it interprets each number as a single byte instead of 4.

Background

I'm running Windows 10, MATLAB R2018a, Python 3.7 and using a pair of Holybro Telemetry Radios to communicate. These radios work just fine for serial communication between two MATLAB scripts, which makes me believe the issue has to do with how Python interprets the MATLAB single data type.

My code

MATLAB Sender

This is the code that is sending the message:

comport = 'COM6'
s = serial(comport);
s.BaudRate = 57600;

try
    fopen(s);
    headerToSend = single([112 112]);
    packetToSend = single([3 4 5]);
    dataToSend = [headerToSend packetToSend];
    fwrite(s,dataToSend);
    disp('Data sent is: ')
    disp(dataToSend)
    fclose(s);
    delete(s);
    whos dataToSend
catch ME
    disp(ME.message)
end

Note that the message can contains integers that are converted to singles or decimal numbers (like 0.1) that can't be simplified to ints.

Python Receiver

This is the code that is receiving the message:

import serial
import time
import sys

dat = []

# Open serial port
ser = serial.Serial(
        port='COM3',
        baudrate=57600,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_ONE,
        timeout=0.1)

try:
    # Main loop
    while (True):
        buffer = ser.read(4)
        print("Buffer: ")
        print(buffer)
        time.sleep(1)
except KeyboardInterrupt:
      ser.close()
      sys.exit()

MATLAB Receiver (for comparison)

For comparison, I copied a receiver in MATLAB to verify that the sender was functioning properly:

clc;clear;
comport = 'COM3';
s = serial(comport);
s.BaudRate = 57600;
try
    fopen(s);
    data = [];    
    % Receive response data
    pause(0.5);
    expectedDataLength = 20; % 2 headers and 3 msg, each 4 bytes
    data = fread(s,expectedDataLength)
    fclose(s);
    delete(s);
catch ME
    disp(ME.message)
end
if isempty(data)
    disp('Unable to read any data.');
else
    %Logic to find the packet after stripping the header
    data = data';
    header = single([112 112]);

    %Find the starting indices of 'header' in the 'data' received. It is
    %expected to be 112 in this case
    k = strfind(data,header);

    if ~isempty(k) && any(ismember(k,1))
        %get the packet data by stripping the header
        packet = data(length(header)+1:expectedDataLength);
    end

    % The msg values are in the order x, y, and z.
    msg1 = typecast(single(packet(1)),'single');
    msg2 = typecast(single(packet(2)),'single');
    msg3 = typecast(single(packet(3)),'single');

    %display the msg data on screen
        disp(['Message Data ', num2str(msg1),' | ', num2str(msg2), ' | ', num2str(msg3), '.'])
end

Expected result

I would expect the buffer to read:

b'x00\x00\x00\x70'
b'x00\x00\x00\x70'
b'x00\x00\x00\x03'
b'x00\x00\x00\x04'
b'x00\x00\x00\x05'

For the message containing [112 112 3 4 5]

When using the MATLAB reader shown above, it can interpret the message properly. See the update at the bottom.

Actual result

Instead the buffer reads:

b'pp\x03\x04'
b'\x05'

It seems that, on the python end, it has crammed each element of the message into a single byte (p being 112, x03 being 3 and so forth). Using whois dataToSend confirms that it is a 1x5 array of singles, composed of 20 bytes (or at least it is, before being received).

The question

Any idea why this is happening and how to make python recognize MATLAB singles as 32 bits/4 bytes?

Update

So it turns out not even MATLAB can interpret its singles properly; I've realized that MATLAB receiver script waits for me to call the MATLAB sender 4 times, as it only interprets each single as byte (meaning the message size is only 5 bytes instead of 20). Furthermore, when an element of the message is changed to a decimal (say, 5.7), then at the output, it is rounded to 6.

Joshua O'Reilly
  • 109
  • 1
  • 9

1 Answers1

1

Your bytes are there, you just don't see them.

Replace your:

print(buffer)

With:

print(ord(buffer))

And they should magically appear out of thin air.

You might want to use struct.unpack to get the singles you're after.

EDIT: As discussed below the problem was you need to explicitly state the precision you want to use to write to the port from Matlab. If you don't, it will use the unsigned 8 bit default.

So doing:

fwrite(s, dataToSend, 'single')

fixed the problem.

Marcos G.
  • 3,371
  • 2
  • 8
  • 16
  • Using `ord` works if the single is representing an [integer](https://docs.python.org/3.5/library/functions.html#ord), but in my case the single could also a be a decimal number; in which was, it doesn't work. I've updated my question to include that the numbers sent can be decimal. I wrote these scripts as an attempt to debug why struct.unpack was not working in my primary program, so I'm afraid that doesn't work either (at least, not without some more detailed guidance). – Joshua O'Reilly Aug 09 '19 at 18:52
  • I see... I will update my answer tomorrow if nobody else jumps in. – Marcos G. Aug 09 '19 at 19:19
  • And why do they "magically appear out of thin air"? Why aren't they detected when I read byte by byte, but appear using `ord`? Thanks for answering! I wish I could rely on just using integers, but unfortunately I need the precision – Joshua O'Reilly Aug 09 '19 at 19:26
  • 1
    Looking at this again there is something I don't quite get: are you sure Matlab is sending those leading zeros? I think they are dropped if you don't explicitly indicate the precision you want. Maybe you can try with `fwrite(s,dataToSend,'single')` – Marcos G. Aug 10 '19 at 08:20
  • It seems a bit overkill in this case, but if you want to be sure you can try [this trick](https://stackoverflow.com/a/57062146/11476836) to make sure those zeros are there. One thing you need to be aware of is that `serial.read(4)` would read up to 4 bytes, if you get fewer within the timeout you specified you will get as many as you have in the buffer. – Marcos G. Aug 10 '19 at 08:24
  • I'll try specifying single as an argument to `fwrite` today and let you know how it goes (and resort to the modbus server solution if that doesn't resolve it) – Joshua O'Reilly Aug 12 '19 at 11:29
  • OK. The sniffing should take no more than 10 minutes to set up so it might be worth your while just to be sure what your port is doing – Marcos G. Aug 12 '19 at 11:36
  • 1
    specifying `single` in `fwrite(s,dataToSend,'single')` did the trick! The MATLAB [documentation](https://www.mathworks.com/help/matlab/ref/serial.fwrite.html) actually specifies: `"If precision is not specified, uchar (an 8-bit unsigned character) is used."` So adding `single` fixed the output for the python script: `b'\x00\x00\xe0B' b'\x00\x00\xe0B' b'\x00\x00@@' b'\x00\x00\x80@' b'ff\xb6@'` (the correct values). Thank you so much! Could you modify your original answer to include adding `single` to the `fwrite` command? I'll mark it as the accepted answer – Joshua O'Reilly Aug 12 '19 at 15:52