Thread Save Serial Connection in Telepot (Python)

376 Views Asked by At

I have a serial device (Arduino) regularly outputting log data, which shold be written in a Log file. Also the device takes spontaneous commands over serial. I send the commands to a Raspberry over Telegram, which are handled and sent to the arduino by Telepot, which runs in a separate thread.

How can I make sure that the two processes get along with each other?

I am a complete Beginner in Multithreading. Here is a shortened version of my Code:

import time
import datetime
import telepot
import os
import serial
from time import sleep

ser = None
bot = None


def log(data):
    with open('logfile', 'w') as f:
        file.write("Timestamp" + data)

#The handle Function is called by the telepot thread, 
#whenever a message is received from Telegram
def handle(msg):
        chat_id = msg['chat']['id']
        command = msg['text']
        print( 'Command Received: %s' % command)
        if command = '/start':
            bot.sendMessage(chat_id, 'welcome')
        elif command == 'close_door':
            #This serial write could possibly happen while a 
            #ser.readline() is executed, which would crash my program. 
            ser.write("Close Door")
        elif command == 'LOG':
            #Here i should make sure that nothing 
            #is waiting from the Arduino
            #so that the next two Serial lines are the Arduinos 
            #respoonce to the "LOG" command.
            #and that hanlde is the only 
            #function talking to the Serial port now.
            ser.write("LOG")
            response = ser.readline()
            response += "\0000000A" + ser.readline()
            #The Arduinos response is now saved as one string 
            #and sent to the User.
            bot.sendMessage(chat_id, response)

        print("Command Processed.")


bot = telepot.Bot('BOT TOKEN')
bot.message_loop(handle)


ser = serial.Serial("Arduino Serial Port", 9600)
print( 'I am listening ...')

while True:
    #anything to make it not run at full speed (Recommendations welcome)
    #The log updates are only once an hour. 
    sleep(10)

    #here i need to make sure it does not collide with the other thread.
    while ser.in_waiting > 0:
        data = ser.readline()
        log(data)

This code is not my actual code, but it should represent exactly what I'm trying to do.

My last resort would be to put the serial code in the threads loop function, But this would require me to change the libary which would be ugly.

I looked up some stuff about Queues in Asincio, and locking functions. However i don't really understand how to apply that. Also I don't use the async telepot.

1

There are 1 best solutions below

0
On BEST ANSWER

After reading more on locking and threads, I found an answer with help of the links provided in this Question: Locking a method in Python? It was often recommended to use Queues, however I don't know how.

My solution (code may have errors, but the principle works)

import time
import random
import datetime
import telepot
import os
import serial
from time import sleep
#we need to import the Lock from threading
from threading import Lock

ser = None
bot = None



def log(data):
    with open('logfile', 'w') as f:
        file.write("Timestamp" + data)


#create a lock:
ser_lock = Lock()


#The handle Function is called by the telepot thread, 
#whenever a message is received from Telegram
def handle(msg):
    #let the handle function use the same lock:
    global ser_lock

    chat_id = msg['chat']['id']
    command = msg['text']
    print( 'Command Received: %s' % command)
    if command == '/start':
        bot.sendMessage(chat_id, 'welcome')
    elif command == 'close_door':
        #This serial write could possibly happen while a 
        #ser.readline() is executed, which would crash my program.
        with ser_lock:
            ser.write("Close Door")
    elif command == 'LOG':
        #Here i should make sure that nothing 
        #is waiting from the Arduino
        #so that the next two Serial lines are the Arduinos 
        #respoonce to the "LOG" command.
        #and that hanlde is the only 
        #function talking to the Serial port now.

        #the lock will only be open when no other thread is using the port.
        #This thread will wait untill it's open.
        with ser_lock:
            while ser.in_waiting > 0:
                data = ser.readline()
                log(data)
                #Should there be any old data, just write it to a file
            #now i can safely execute serial writes and reads.
            ser.write("LOG")
            response = ser.readline()
            response += "\0000000A" + ser.readline()
        #The Arduinos response is now saved as one string 
        #and sent to the User.
        bot.sendMessage(chat_id, response)

    print("Command Processed.")


bot = telepot.Bot('BOT TOKEN')
bot.message_loop(handle)


ser = serial.Serial("Arduino Serial Port", 9600)
print( 'I am listening ...')

while True:
    #anything to make it not run at full speed (Recommendations welcome)
    #The log updates are only once a 
    sleep(10)

    #here i need to make sure it does not collide with the other thread.
    with ser_lock:
        while ser.in_waiting > 0:
            data = ser.readline()
            log(data)