Problem reading RS485 communication packets with raspberry pi using python - python

I'm having trouble reading packets over a serial line, in the modbus protocol. I want to read the temperature from my thermometer that uses RS485 communication, so I used raspberry pi in combination with waveshare rs485 can hat.
Here is a code sample.
#!/usr/bin/python3
import RPi.GPIO as GPIO
import serial
import time
from prectime import processor_sleep
rsp = 4
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(rsp, GPIO.OUT)
GPIO.output(rsp, 0)
a = float(7.5)
command = [0x02,0x04,0x00,0x05,0x00,0x01,0x21,0xF8]
r = str(0)
ser = serial.Serial('/dev/ttyS0', 9600, timeout = 0.1)
while len(str(r)) < 31:
GPIO.output(rsp, 1)
ser.write(command)
processor_sleep(a)
GPIO.output(rsp, 0)
r = ser.readline()
print(r,' --- ',a)
a = a + 0.1
processor_sleep(1000)
ser.close()
My prectime library with the processorsleep () function is here.
import time
def processor_sleep(ms):
_ = time.perf_counter() + ms/1000
while time.perf_counter() < _:
pass
As soon as I connect the sniffer in parallel to the line I can see that the initiating packet is received and answered by the sensor correctly, however, when I try to read the same answer via python the result is this:
b'' --- 7.5
b'' --- 7.6
b'' --- 7.699999999999999
b'\x02\x04\x02\x01S\xbc\x9d' --- 7.799999999999999
b'\x02\x04\x02\x01S\xbc\x9d' --- 7.899999999999999
b'\x02\x04\x02\x01S\xbc\x9d' --- 7.999999999999998
b'\x02\x04\x02\x01S\xbc\x9d' --- 8.099999999999998
and it should look like (from sniffer)
b'\x02\x04\x02\x01\x52\xbc\x9d'
I don't know what's going on, sometimes it happens that I catch the packet well, but it's rare.
Thanks in advance.

This topic has been already discussed at length.
Just go here and follow the rabbit hole of links in my answer. You should be able to solve your problem.
I would go for the libmodbus solution (there is a Python wrapper too) but you can also stay with pymodbus if you want.
Anyway, what sawdust says in the comments is also very good advice. The easiest would be to invest 10 bucks and buy a transceiver that is able to toggle itself. Yours is not, unfortunately.

Related

Arduino pyfirmata analog reading problems

I am having some problems reading analog values from my Arduino Mega using pyfirmata.
I use Arduino Mega with a Mega Sensor Shield.
I would like to read analog values from a HW-201 IR sensor (pin A5).
I have uploaded the Standard firmata sketch on Arduino IDE, and I am running the following code using Anaconda Spyder:
import serial
import serial.tools.list_ports
from pyfirmata import ArduinoMega, util
from time import sleep
def readArduinoPort():
COM = []
ports = list(serial.tools.list_ports.comports())
for p in ports:
if "Arduino" in p.description:
COM.append(str(p.device))
return COM
COM = readArduinoPort()
board = ArduinoMega(COM[0])
pin = board.get_pin('a:5:i')
it = util.Iterator(board)
it.start()
try:
while True:
print(pin.read())
sleep(0.1)
except KeyboardInterrupt:
pass
Now, this code runs just fine the first time I use it, generating numbers close to 1 when the sensor is detecting proximity and numbers close to 0 when not detecting anything.
However, whenever I try to run the Iterator a second time (without restarting the kernel), the code generates seemingly random numbers (almost like the pin was floating, like nothing was connected to it).
Any idea why this is happening? Is this a normal behavior?
Thank you!
I already checked these questions that do not entirely address my issue:
arduino pyfirmata analog reading
Analog readings on Arduino returns wrong values
I have the same result by not having anything pinned to pin A5 of the Arduino Mega.
I wasn't sure, but I tried adding:
board.analog[5].enable_reporting()
before the iterator and then:
board.analog[5].disable_reporting()
but nothing changed.
Board Differences
I ran into the same problem. I own both a Mega and an Uno and needed to select the appropriate Board and Processor in the tools menu:
Tools > Board > _board_
Tools > Processor > _processor_

How to send an API frame to a remote XBee using Digi XBee Python module?

I am moving code from Python XBee library to Digi's Python XBee library and I am unable to locate syntax in the docs on how to send a API frame to a remote XBee. I am using S1 and S1 Pro devices where the "local" device is attached to a Beaglebone and the "remote" devices are standalone out in the wild.
I have the basic framework down:
from digi.xbee.devices import XBeeDevice, RemoteXBeeDevice, XBee64BitAddress
PORT = '/dev/ttyO1'
BAUD = 9600
API_FRAME = 'long-hex-string-here'
local_xbee = XBeeDevice(PORT, BAUD)
local_xbee.open()
# Instantiate a remote XBee device object.
remote_xbee = RemoteXBeeDevice(local_xbee, XBee64BitAddress.from_hex_string("0013A20040DD7DCD"))
# Transmit frame to remote_xbee - unsure of correct method and syntax
# perhaps XBeePacket.create_packet(hex string here)
# local_xbee.send_data(remote_xbee, "api frame here?") ??
local_xbee.close()
but I am unable to locate syntax for how to transmit my constructed API frame. Based upon the Introduction section in the docs I presume that is the correct approach. I am not interested in broadcasting to all devices on the network but rather unicast communication.
I have some older model of the source when I could get it to work.
…
I have some WiFi Xbee modules that I used w/ some converter boards (base boards). I used it to attach the communication of the Xbee from one computer to another, e.g. random desktop to BeagleBone Black via USB instead of UART. So, I would use the source, listed below, to attach my USB dongle for the Xbee communication from the BBB to the other field module.
Their I/O stuff can be found here: https://github.com/digidotcom/xbee-python/tree/master/examples/io.
Also...changing just some of their lines in their source w/ the USB dongle WiFi adapter boards proved valuable in signaling LEDs and other sensors.
Oh and you will need what they are now calling Carrier Boards. It is the adapter board I just typed out. So, if you have already got a Carrier Board, use lsusb as the command in Linux to find your USB "name."
So, for instance, if lsusb brings up /dev/ttyUSB0, then that is the port identification.
And you can use that section, from lsusb, to then change your xbee modules in the xtcu software from Digi.
…
from digi.xbee.devices import XBeeDevice
from digi.xbee.io import IOLine, IOMode
import time
import threading
# TODO: Replace with the serial port where your local module is connected to.
PORT = "/dev/ttyUSB0"
# TODO: Replace with the baud rate of your local module.
BAUD_RATE = 9600
REMOTE_NODE_ID = "Xbee_B"
IOLINE_IN = IOLine.DIO2_AD2
IOLINE_OUT = IOLine.DIO4_AD4
def main():
print(" +-----------------------------------------------+")
print(" | XBee Python Library Get/Set Remote DIO Sample |")
print(" +-----------------------------------------------+\n")
stop = False
th = None
local_device = XBeeDevice(PORT, BAUD_RATE)
try:
local_device.open()
print("local device: ", local_device.get_node_id())
# Obtain the remote XBee device from the XBee network.
xbee_network = local_device.get_network()
remote_device = xbee_network.discover_device(REMOTE_NODE_ID)
if local_device is None:
print("Could not find the remote device")
exit(2)
def io_detection_callback():
while not stop:
# Read the digital value from the input line.
io_value = remote_device.get_dio_value(IOLINE_IN)
print("%s: %s" % (IOLINE_IN, io_value))
# Set the previous value to the local output line.
local_device.set_dio_value(IOLINE_OUT, io_value)
time.sleep(2)
th = threading.Thread(target=io_detection_callback)
remote_device.set_io_configuration(IOLINE_IN, IOMode.DIGITAL_IN)
local_device.set_io_configuration(IOLINE_OUT, IOMode.DIGITAL_OUT_HIGH)
time.sleep(1)
th.start()
input()
finally:
stop = True
if th is not None and th.is_alive():
th.join()
if local_device is not None and local_device.is_open():
local_device.close()
if __name__ == '__main__':
main()
So, see the PORT = "/dev/ttyUSB0" section of the source?
This is where I attached my Xbee module to the Carrier Board and then attached the Carrier Board to the BBB by way of USB.
Um, this may not answer a question but give more insight as to how to handle Digi Devices/Modules.
I also think that if you want to venture in this direction of UART communication w/ Xbee and the BeagleBone Black, it may be more complicated. I will keep searching my text.
P.S. This book goes over some methods to connect, Experiment 10 and Experiment 16, your "BBB" to a UART, Xbee, and how to communicate. It is a bit too in depth to get all of the communication ideas from this book but this is it:
The Hands-on XBEE Lab Manual, Experiments that Teach you XBEE Wireless Communications – Jonathan A Titus

How to check simultaneously for serial input and input from keyboard (simultaneous use of readchar and serial library)

I am trying to code the following using python3 in a raspberry pi:
1) wait for a 14 digits bar code (barcode scanner connected through usb port and input received as keyboard data)
2) after a barcode is read, wait for serial communication (device connected to usb port and sends serial commands. could be one, or more....) the idea is that all commands received are going to be associated with the scanned barcode
3) the process of waiting for serial commands has to stop when a new barcode is read. THIS IS THE PART I HAVE NOT FIGURED OUT HOW TO DO IT
After some research, I decided to use the "readchar" library for the barcode scanner and the "serial" library for the serial communication received. Both of them work by themselves but the problem is when I try to detect both things at the same time.
In the following code, I managed to read a barcode and then wait for 5 lines of serial communication to finally repeat the process and read a barcode again. The program works as it is right now BUT the problem is that I don't know how many lines of serial communication I will receive so I need to somehow detect a new barcode while also waiting to receive the serial communication.
import readchar
import time
import serial
ser = serial.Serial(
port='/dev/ttyUSB0',
baudrate = 115200,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1
)
print("Waiting for barcode...")
while 1:
inputStr = ""
while len(inputStr) != 14: #detect only 14 digit barcodes
inputStr += str(readchar.readchar())
inputStr = ''.join(e for e in inputStr if e.isalnum()) #had to add this to strip non alphanumeric characters
currentCode = inputStr
inputStr = ""
print(currentCode)
ser.flushInput()
time.sleep(.1)
# Wait for 5 lines of serial communication
# BUT it should break the while loop when a new barcode is read!
count = 0
while count < 5:
dataRead=ser.readline()
if len(dataRead) > 0:
print(dataRead)
count+=1
print("Waiting for barcode...")
If I add a condition to the while loop that reading the serial communication using (ser.readline()) so that if a character is read from the scanner (readchar.readchar()) then it messes thing up. It is like if readline and reacher can not be in the same while loop.
Doing some research I think I need to use Asynchronous IO, or threads or something like that, but I have no clue. Also I don't know if I could keep using the same libraries (serial and readchar). Please help
I cannot be sure (I don't have your barcode reader and serial port device) but based on what you say I don't think you need threads, you just have to rely on the buffers to keep your data stored until you have time to read them.
Simply change the condition on your second while loop to:
while serial.inWaiting() != 0:
This way you will make sure the RX buffer on your serial port will empty. This approach might or might not work depending on the speed and timing of your devices.
You could also try to add a short delay after the buffer is emptied:
import serial
import time
ser=serial.Serial(port="/dev/ttyUSB0",baudrate=115200, timeout=1.0)
time.sleep(1)
data=b""
timeout = time.time() + 1.0
while ser.inWaiting() or time.time()-timeout < 0.0: #keep reading until the RX buffer is empty and wait for 1 seconds to make sure no more data is coming
if ser.inWaiting() > 0:
data+=ser.read(ser.inWaiting())
timeout = time.time() + 1.0
else:
print("waiting...")
This keeps trying to read from the port for 1 second after the last byte is received, to make sure nothing else is coming. You might want to play with the duration of the delay depending, again, on the speed and timing of your devices.
Again, I don't have your devices, so I'm in no position to judge, but the way you read characters from the barcode/keyboard looks far from optimum. I doubt readchar is the best approach. At the end of the day, your barcode reader is probably a serial port. You might want to dig into that and/or find a more efficient way to read several keyboard strokes in one go.
I found this answer in another question:
How to read keyboard-input?
I have tried it and it works! I´ll also give a try to the method proposed by Marcos G.

Issue with daisychaining MCP3008 SPI on Raspberry Pi with Python

At the moment I am trying to let two MCP3008's communicate through SPI with my raspberry pi and a Python script. A potentiometer should send a certain analog value to the MCP3008 input channel.
Here is my setup in Fritzing:
Breadboard Schematic
and here is the schematic overview:
Schematic Overview
The SPI wiring is based upon a standard daisychain schematic as shown in:
SPI Daisy Chain
The Python Code I am using is:
import spidev
import time
spi = spidev.SpiDev()
spi.open(0,0)
spi.max_speed_hz = 1000000
def read_spi(channel):
spidata = spi.xfer2([0,(8+channel)<<4,0])
return ((spidata[1] & 3) << 8) + spidata[2]
try:
while True:
channeldata = read_spi(0)
print (channeldata)
time.sleep(.1)
except KeyboardInterrupt:
spi.close()
I am getting values, but they fluctuate a lot with every value possible between 0 and 1023.
I've tried it with one MCP3008 without daisychaining of course and it worked fine, so my guess is that it has something to do with either the daisychain being incorrect, or the addressing of the MCP3008.
Can you guys help me out? Thanks alot!!
Cheers,
Devatu
Might be a late response, but according to the data sheet this chip can't be daisy-chained at all.
I am also looking for an ADC which can be daisy-chained.

PySerial non-blocking read loop

I am reading serial data like this:
connected = False
port = 'COM4'
baud = 9600
ser = serial.Serial(port, baud, timeout=0)
while not connected:
#serin = ser.read()
connected = True
while True:
print("test")
reading = ser.readline().decode()
The problem is that it prevents anything else from executing including bottle py web framework. Adding sleep() won't help.
Changing "while True"" to "while ser.readline():" doesn't print "test", which is strange since it worked in Python 2.7. Any ideas what could be wrong?
Ideally I should be able to read serial data only when it's available. Data is being sent every 1,000 ms.
Using a separate thread is totally unnecessary. Just follow the example below for your infinite while loop instead.
I use this technique in my eRCaGuy_PyTerm serial terminal program here (search the code for inWaiting() or in_waiting).
Notes:
To check your python3 version, run this:
python3 --version
My output when I first wrote and tested this answer was Python 3.2.3.
To check your pyserial library (serial module) version, run this--I first learned this here:
python3 -c 'import serial; \
print("serial.__version__ = {}".format(serial.__version__))'
This simply imports the serial module and prints its serial.__version__ attribute.
My output as of Oct. 2022 is: serial.__version__ = 3.5.
If your pyserial version is 3.0 or later, use property in_waiting in the code below. If your pyserial version is < 3.0, use function inWaiting() in the code below. See the official pyserial documentation here: https://pyserial.readthedocs.io/en/latest/pyserial_api.html#serial.Serial.in_waiting.
Non-blocking, single-threaded serial read example
import serial
import time # Optional (required if using time.sleep() below)
ser = serial.Serial(port='COM4', baudrate=9600)
while (True):
# Check if incoming bytes are waiting to be read from the serial input
# buffer.
# NB: for PySerial v3.0 or later, use property `in_waiting` instead of
# function `inWaiting()` below!
if (ser.inWaiting() > 0):
# read the bytes and convert from binary array to ASCII
data_str = ser.read(ser.inWaiting()).decode('ascii')
# print the incoming string without putting a new-line
# ('\n') automatically after every print()
print(data_str, end='')
# Put the rest of your code you want here
# Optional, but recommended: sleep 10 ms (0.01 sec) once per loop to let
# other threads on your PC run during this time.
time.sleep(0.01)
This way you only read and print if something is there. You said, "Ideally I should be able to read serial data only when it's available." This is exactly what the code above does. If nothing is available to read, it skips on to the rest of your code in the while loop. Totally non-blocking.
(This answer originally posted & debugged here: Python 3 non-blocking read with pySerial (Cannot get pySerial's "in_waiting" property to work))
pySerial documentation: http://pyserial.readthedocs.io/en/latest/pyserial_api.html
UPDATE:
27 Dec. 2018: added comment about in_waiting vs inWaiting(). Thanks to #FurkanTürkal for pointing that out in the comments below. See documentation here: https://pyserial.readthedocs.io/en/latest/pyserial_api.html#serial.Serial.in_waiting.
27 Oct. 2018: Add sleep to let other threads run.
Documentation: https://docs.python.org/3/library/time.html#time.sleep
Thanks to #RufusV2 for bringing this point up in the comments.
Note on multi-threading:
Even though reading serial data, as shown above, does not require using multiple threads, reading keyboard input in a non-blocking manner does. Therefore, to accomplish non-blocking keyboard input reading, I've written this answer: How to read keyboard input?.
References:
Official pySerial serial.Serial() class API - https://pyserial.readthedocs.io/en/latest/pyserial_api.html
Put it in a separate thread, for example:
import threading
import serial
connected = False
port = 'COM4'
baud = 9600
serial_port = serial.Serial(port, baud, timeout=0)
def handle_data(data):
print(data)
def read_from_port(ser):
while not connected:
#serin = ser.read()
connected = True
while True:
print("test")
reading = ser.readline().decode()
handle_data(reading)
thread = threading.Thread(target=read_from_port, args=(serial_port,))
thread.start()
http://docs.python.org/3/library/threading
I would warn against using blocking IO in a thread. Remember Python has a GIL and at one time only one thread can execute. Now please note that pyserial module is a wrapper over an OS implementation of accessing the serial port. That means it calls code external to the Python. If that code blocks, then the interpreter also get blocked and nothing will execute in the Python program, even the main thread.
This can even happen when using non-blocking IO or timeout based polling if the underlying device driver does not implement timeout well.
A more robust approach is to use multiprocessing module with a queue. Run serial read code in a separate process. This will make sure main and other threads don't block and the program can exit in clean way.
Use a timer driven event to test and read the serial port.
Untested example:
import threading
class serialreading():
def __init__(self):
self.active = True
self.test()
def test(self):
n_in =comport.in_waiting()
if n_in> 0:
self.data = self.data + comport.read(size=n_in)
if len(self.data) > 0:
print(self.data)
self.data=""
if self.active:
threading.Timer(1, test).start() # start new timer of 1 second
def stop(self):
self.active = False

Categories

Resources