问题
I have working code that sends a struct from a Raspberry Pi to an Arduino using pySerialTransfer when the devices are connected via a USB cable. I'd like to do that over bluetooth instead of with a USB cable, however.
Independently, using separate python code and a separate Arduino sketch, I've been able to get the Raspberry Pi & Arduino to communicate over bluetooth via a continuous stream of text or binary data.
My issue is that I don't see how to combine the two approaches - i.e.: if I send a binary-encoded structure over bluetooth, I don't see how I get the benefits of the pySerialTransfer / SerialTransfer libraries to parse it on the receiving end. I see how I can do the parsing "manually", looking for special terminating characters, etc., but I was hoping to avoid the need for that with pySerialTransfer.
Thanks for any pointers / suggestions / examples. All the working code I've been able to construct so far is here.
pySerialTransfer
Working Arduino C serial code
#include "SerialTransfer.h"
SerialTransfer myTransfer;
int const ONBOARD_LED_PIN = 13;
struct POSITION {
int id;
float azimuth;
float altitude;
} position;
void Blink(int n) {
for (int i=0; i<n; i++) {
digitalWrite(ONBOARD_LED_PIN, HIGH);
delay(75);
digitalWrite(ONBOARD_LED_PIN, LOW);
delay(75);
}
delay(150);
}
void setup()
{
Serial.begin(115200);
myTransfer.begin(Serial);
pinMode(ONBOARD_LED_PIN, OUTPUT);
digitalWrite(ONBOARD_LED_PIN, LOW);
}
void loop()
{
if(myTransfer.available())
{
//////////////////////////////////////////////
// handle call from Python
myTransfer.rxObj(position, sizeof(position));
//////////////////////////////////////////////
//////////////////////////////////////////////
// send response
myTransfer.txObj(position, sizeof(position));
myTransfer.sendData(sizeof(position));
//////////////////////////////////////////////
}
else if(myTransfer.status < 0)
{
Serial.print("ERROR: ");
if(myTransfer.status == -1)
Serial.println(F("CRC_ERROR"));
else if(myTransfer.status == -2)
Serial.println(F("PAYLOAD_ERROR"));
else if(myTransfer.status == -3)
Serial.println(F("STOP_BYTE_ERROR"));
}
}
Working Raspberry Pi serial code
import time
import struct
from pySerialTransfer import pySerialTransfer
def StuffObject(txfer_obj, val, format_string, object_byte_size, start_pos=0):
"""Insert an object into pySerialtxfer TX buffer starting at the specified index.
Args:
txfer_obj: txfer - Transfer class instance to communicate over serial
val: value to be inserted into TX buffer
format_string: string used with struct.pack to pack the val
object_byte_size: integer number of bytes of the object to pack
start_pos: index of the last byte of the float in the TX buffer + 1
Returns:
start_pos for next object
"""
val_bytes = struct.pack(format_string, *val)
for index in range(object_byte_size):
txfer_obj.txBuff[index + start_pos] = val_bytes[index]
return object_byte_size + start_pos
if __name__ == '__main__':
try:
link = pySerialTransfer.SerialTransfer('/dev/cu.usbmodem14201', baud=115200)
link.open()
time.sleep(2) # allow some time for the Arduino to completely reset
base = time.time()
while True:
sent = (4, 1.2, 2.5)
format_string = '<lff'
format_size = 4+4+4
StuffObject(link, sent, format_string, format_size, start_pos=0)
link.send(format_size)
start_time = time.time()
elapsed_time = 0
while not link.available() and elapsed_time < 2:
if link.status < 0:
print('ERROR: {}'.format(link.status))
else:
print('.', end='')
elapsed_time = time.time()-start_time
print()
response = bytearray(link.rxBuff[:link.bytesRead])
response = struct.unpack(format_string, response)
print('SENT: %s' % str(sent))
print('RCVD: %s' % str(response))
print(' ')
except KeyboardInterrupt:
link.close()
Bluetooth Communication
Working Arduino bluetooth communication
#include "SerialTransfer.h"
// Connect the HC-05 TX to Arduino pin 2 RX.
// Connect the HC-05 RX to Arduino pin 3 TX through a voltage divider.
//
long n = 0;
struct POSITION {
float azimuth=5;
float altitude=10;
};
SerialTransfer myTransfer;
void setup()
{
Serial.begin(9600);
// HC-06 default serial speed for communcation mode is 9600
Serial1.begin(9600);
myTransfer.begin(Serial1);
}
void loop()
{
n++;
POSITION x;
x.azimuth=n;
x.altitude=n+1;
myTransfer.txObj(x, sizeof(x));
myTransfer.sendData(sizeof(x));
if(Serial1.available() > 0){ // Checks whether data is comming from the serial port
Serial.println(Serial1.read());} // Reads the data from the serial port
delay(1000);
}
Working python bluetooth communication
import bluetooth
sock=bluetooth.BluetoothSocket( bluetooth.RFCOMM )
bd_addr = '98:D3:11:FC:42:16'
port = 1
sock.connect((bd_addr, port))
d = sock.recv(10240000)
print(d)
sock.send("hello")
回答1:
pySerialTransfer / SerialTransfer will arrange data in packet format with CRC so it fast and safe way to communicate over serial.
In fact, there are no special character to terminate packet because binary data can be any character from 0x00 to 0xFF cover all ASCII code.
回答2:
I could not get the socket connection to last more than a few hours (see Detecting and recovering from “hanging” bluetooth python connection ); perhaps my socket attempt was an incorrect or at least unnecessary combination of technologies given ultimate goal of using pySerialTransfer. Falling back to just serial, I ultimately was able to get this code working for bidirectional RFCOMM serial communication between my Raspberry Pi and Arduino over bluetooth.
Python Code
#!/usr/bin/python3
import datetime
import sys
import time
import subprocess
from pySerialTransfer import pySerialTransfer
COMMAND_START_CHAR = '<'
COMMAND_END_CHAR = '>'
LOGFILE = 'bt.log'
def Log(s):
"""Appends message s to the logfile."""
with open(LOGFILE, 'a') as f:
f.write('%s\n' % s)
def Time(epoch):
"""Converts epoch to easy-to-read string."""
return datetime.datetime.fromtimestamp(epoch).strftime('%Y-%m-%d %H:%M:%S')
def ConnectBluetoothRetry(attempts=1):
"""Attempts to make connections for a number of attempts, exiting program on fail."""
attempt = 1
while attempt <= attempts:
try:
ser = pySerialTransfer.SerialTransfer('/dev/rfcomm1', 9600)
except pySerialTransfer.InvalidSerialPort as e:
Log('ERROR: Unbound; will attempt to bind: %s' % e)
cmd = 'sudo rfcomm bind 1 98:D3:11:FC:42:16 1'
conn = subprocess.Popen(cmd, shell=True)
if conn.returncode is None:
Log('ERROR: Unable to bind')
sys.exit()
else:
Log('Connection bound with return code %s' % conn.returncode)
if attempt == attempts:
attempts += 1
Log('Allowing one more attempt to connect')
if ser:
Log('Connected after %d attempts' % attempt)
return ser
attempt += 1
if attempt < attempts:
time.sleep(1)
Log('ERROR: Failed to connect after %d attempts' % attempt)
sys.exit()
def ReconnectOnError(ser, error=None):
"""Close and reopen the serial."""
if error:
Log('ERROR: %s' % error)
ser.close()
ser = ConnectBluetoothRetry(10)
return ser
def Parse(s):
"""Returns the string encapsulated between COMMAND_START_CHAR and COMMAND_END_CHAR."""
if COMMAND_START_CHAR in s:
start_char = s.find(COMMAND_START_CHAR)
else:
return None
if COMMAND_END_CHAR in s:
end_char = s.rfind(COMMAND_END_CHAR)
else:
return None
return s[start_char + len(COMMAND_START_CHAR): end_char - len(COMMAND_END_CHAR) + 1]
def Read(ser):
"""Non-blocking read on an open Bluetooth pySerialTransfer."""
recv = ''
if ser.available():
if ser.status < 0:
if ser.status == -1:
Log('ERROR: CRC_ERROR')
elif ser.status == -2:
Log('ERROR: PAYLOAD_ERROR')
elif ser.status == -3:
Log('ERROR: STOP_BYTE_ERROR')
else:
for index in range(ser.bytesRead):
recv += chr(ser.rxBuff[index])
if recv:
command = Parse(recv)
if command is not None:
command = recv[len(COMMAND_START_CHAR):-len(COMMAND_END_CHAR)]
return command
Log('ERROR: malformed %s' % recv)
return None
def Write(ser, command):
"""Sends the encapsulated string command on an open Bluetooth pySerialTransfer."""
send = COMMAND_START_CHAR+str(command)+COMMAND_END_CHAR
byte_count = ser.tx_obj(send)
ser.send(byte_count)
Log('Sent %s' % send)
def main():
"""Sends sequential numbers over bluetooth, and receives & parses anything sent."""
sys.stderr = open(LOGFILE, 'a')
start = time.time()
last_write = start
Log('Started at %s' % Time(start))
ser = ConnectBluetoothRetry(10)
x = 0
while True:
try:
command = Read(ser)
except Exception as e:
ser = ReconnectOnError(ser, 'Failed to receive: %s' % e)
if command is not None:
Log('Recd: %s; runtime: %.3f hours' % (command, (time.time() - start) / 60**2))
if time.time() - last_write > 1:
last_write = time.time()
try:
Write(ser, x)
except Exception as e:
ser = ReconnectOnError(ser, 'Failed to send: %s' % e)
x += 1
time.sleep(.1)
if __name__ == "__main__":
main()
Arduino Code
#include "SerialTransfer.h"
// Connect the HC-05 TX to Arduino pin 2 RX.
// Connect the HC-05 RX to Arduino pin 3 TX through a voltage divider.
//
SerialTransfer myTransfer;
char number[12];
unsigned long n = 1;
long last_mesg_received = 0;
long last_mesg_sent = 0;
void(* resetFunc) (void) = 0; //declare reset function @ address 0
void setup()
{
Serial.begin(9600);
// HC-05 default serial speed for communcation mode is 9600
Serial1.begin(9600);
myTransfer.begin(Serial1);
last_mesg_received = millis();
}
void Read(){
if(myTransfer.available())
{
Serial.print("Recd: ");
for(byte i = 0; i < myTransfer.bytesRead; i++)
Serial.write(myTransfer.rxBuff[i]);
Serial.println();
}
else if(myTransfer.status < 0)
{
Serial.print("ERROR: ");
if(myTransfer.status == -1)
Serial.println(F("CRC_ERROR"));
else if(myTransfer.status == -2)
Serial.println(F("PAYLOAD_ERROR"));
else if(myTransfer.status == -3)
Serial.println(F("STOP_BYTE_ERROR"));
}
}
void Write(){
sprintf(number, "<%d>", n);
int len = strlen(number) + 1;
for(int x; x<len; x++){
myTransfer.txBuff[x+1] = number[x];
}
Serial.print("Sent: ");
Serial.println(number);
myTransfer.sendData(len);
}
void loop()
{
Read();
if (millis() - last_mesg_sent > 1000){
Write();
last_mesg_sent = millis();
n++;
}
delay(100);
}
This produces output like the following:
Python bt.log
Started at 2020-05-05 09:04:47
Connected after 1 attempts
Sent <0>
Sent <1>
Sent <2>
Recd: <1; runtime: 0.001 hours
Recd: <1; runtime: 0.001 hours
Recd: <2; runtime: 0.001 hours
Recd: <3; runtime: 0.001 hours
Sent <3>
Recd: <4; runtime: 0.001 hours
Sent <4>
Recd: <5; runtime: 0.002 hours
Sent <5>
Recd: <6; runtime: 0.002 hours
Sent <6>
Sent <7>
Recd: <7; runtime: 0.002 hours
Sent <8>
Recd: <8; runtime: 0.003 hours
Sent <9>
Recd: <9; runtime: 0.003 hours
Sent <10>
Recd: <10; runtime: 0.003 hours
Sent <11>
Recd: <11; runtime: 0.003 hours
Sent <12>
Recd: <12; runtime: 0.004 hours
Sent <13>
Recd: <13; runtime: 0.004 hours
Sent <14>
Recd: <14; runtime: 0.004 hours
Sent <15>
Recd: <15; runtime: 0.005 hours
Sent <16>
Recd: <16; runtime: 0.005 hours
Sent <17>
Arduino Serial Monitor
09:04:49.591 -> Sent: <1>
09:04:50.564 -> Sent: <2>
09:04:51.368 -> Recd: <0>
09:04:51.477 -> Recd: <1>
09:04:51.583 -> Recd: <2>
09:04:51.687 -> Sent: <3>
09:04:52.080 -> Recd: <3>
09:04:52.777 -> Sent: <4>
09:04:52.997 -> Recd: <4>
09:04:53.804 -> Sent: <5>
09:04:54.096 -> Recd: <5>
09:04:54.896 -> Sent: <6>
09:04:55.083 -> Recd: <6>
09:04:55.995 -> Sent: <7>
09:04:56.104 -> Recd: <7>
09:04:56.970 -> Sent: <8>
09:04:57.073 -> Recd: <8>
09:04:58.084 -> Recd: <9>
09:04:58.084 -> Sent: <9>
09:04:59.106 -> Recd: <10>
09:04:59.174 -> Sent: <10>
09:05:00.087 -> Recd: <11>
09:05:00.197 -> Sent: <11>
09:05:01.111 -> Recd: <12>
09:05:01.291 -> Sent: <12>
09:05:02.091 -> Recd: <13>
09:05:02.380 -> Sent: <13>
09:05:03.103 -> Recd: <14>
09:05:03.390 -> Sent: <14>
09:05:04.087 -> Recd: <15>
09:05:04.518 -> Sent: <15>
09:05:05.091 -> Recd: <16>
09:05:05.607 -> Sent: <16>
来源:https://stackoverflow.com/questions/61549081/using-pyserialtransfer-over-bluetooth-sockets