Asyncio Server with Timer in python - python

I'm using asyncio to run a straightforward server and a client. The server is a simple echo server with two "special" commands sent by the client, "quit" and "timer". The quit command closes the connection, and the timer command starts a timer that will print a message in the console (server and client) every second. The timer will stop when the client sends the "quit" command.
Unfortunately, I'm having some problems with the timer. It blocks the server and the client.
How can I solve this problem?
Server
import asyncio
import time
HOST = '127.0.0.1'
PORT = 9999
async def timer():
while True:
print('tick')
await asyncio.sleep(1)
async def handle_echo(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
'''Handle the echo protocol.'''
data = None
while True:
if(data == b'quit'):
writer.close() # Close the connection
await writer.wait_closed() # Wait for the connection to close
if(data == b'timer'):
timertask = asyncio.create_task(timer())
await timertask #<-- This line freezes the server and the client
else:
data = await reader.read(1024) # Read 256 bytes from the reader. Size of the message
msg = data.decode() # Decode the message
addr, port = writer.get_extra_info('peername') # Get the address of the client
print(f"Received {msg!r} from {addr}:{port!r}")
send_message = 'Message received: ' + msg
writer.write(send_message.encode()) # Echo the data back to the client
await writer.drain() # This will wait until everything is clear to move to the next thing.
async def run_server() -> None:
# Our awaitable callable.
# This callable is ran when the server recieves some data
# Question: Does it run when a client connects?
server = await asyncio.start_server(handle_echo, HOST, PORT)
async with server:
await server.serve_forever()
if __name__ == '__main__':
loop = asyncio.new_event_loop() # new_event_loop() is for python 3.10. For older versions, use get_event_loop()
loop.run_until_complete(run_server())
Client
import asyncio
import time
HOST = '127.0.0.1'
PORT = 9999
async def run_client() -> None:
# It's a coroutine. It will wait until the connection is established
reader, writer = await asyncio.open_connection(HOST, PORT)
while True:
message = input('Enter a message: ')
writer.write(message.encode())
await writer.drain()
data = await reader.read(1024)
if not data:
raise Exception('Socket not communicating with the client')
print(f"Received {data.decode()!r}")
if(message == 'quit'):
writer.write(b"quit")
writer.close()
await writer.wait_closed()
exit(2)
#break # Don't know if this is necessary
if __name__ == '__main__':
loop = asyncio.new_event_loop()
loop.run_until_complete(run_client())

I slightly updated your server code: removed the await timertask line (this task never ends, can be only canceled):
Server.py
import asyncio
import time
HOST = "127.0.0.1"
PORT = 9999
async def timer():
while True:
print("tick")
await asyncio.sleep(1)
async def handle_echo(
reader: asyncio.StreamReader, writer: asyncio.StreamWriter
) -> None:
# timer task that the client can start:
timer_task = None
while True:
data = await reader.read(1024)
msg = data.decode()
addr, port = writer.get_extra_info("peername")
print(f"Received {msg!r} from {addr}:{port!r}")
send_message = "Message received: " + msg
writer.write(send_message.encode())
await writer.drain()
if data == b"quit":
# cancel the timer_task (if any)
if timer_task:
timer_task.cancel()
await timer_task
writer.close()
await writer.wait_closed()
elif data == b"timer" and timer_task is None:
timer_task = asyncio.create_task(timer())
async def run_server() -> None:
server = await asyncio.start_server(handle_echo, HOST, PORT)
async with server:
await server.serve_forever()
if __name__ == "__main__":
loop = asyncio.new_event_loop()
loop.run_until_complete(run_server())
Client.py
import asyncio
import time
HOST = "127.0.0.1"
PORT = 9999
async def run_client() -> None:
# It's a coroutine. It will wait until the connection is established
reader, writer = await asyncio.open_connection(HOST, PORT)
while True:
message = input("Enter a message: ")
writer.write(message.encode())
await writer.drain()
data = await reader.read(1024)
if not data:
raise Exception("Socket not communicating with the client")
print(f"Received {data.decode()!r}")
if message == "quit":
writer.write(b"quit")
await writer.drain()
writer.close()
await writer.wait_closed()
exit(2)
if __name__ == "__main__":
loop = asyncio.new_event_loop()
loop.run_until_complete(run_client())
The server waits for a client's messages. If message timer arrives, starts a new timer task and continues to read other messages.
When quit arrives, server cancels the timer_task (if exists) and exits.

Related

python asyncio server freezes after receiving the initial packet (Only on production mode!!!)

I'm new into python asyncio library and I'm using it to create a tcp sockets, I've created a very simple messaging client/server script that works perfectly on my computer, but for some reason it doesn't works on the production.
By production i mean when i'm trying to make the connection from the client script on my computer, to the server port on my online VPS.
The problem occurs only when the first packet have been sent, the server received it and responded to it. It just stuck there, the client cannot read anything, nor the server. both returning timeouts.
client.py:
import asyncio, datetime, time
async def main():
reader, writer = await asyncio.open_connection('mydomain.com', 4444)
while True:
msg = 'New message: ' + datetime.datetime.now().strftime('%H:%M:%S')
writer.write( msg.encode('utf8') )
print('sent: ', msg)
try:
response = await asyncio.wait_for( reader.read(1024), timeout=5 )
except asyncio.exceptions.TimeoutError:
print('timeout error ..')
break
print('received:', response)
await writer.drain()
time.sleep(2)
if __name__ == '__main__':
try:
asyncio.run(main())
except KeyboardInterrupt:
pass
server.py:
import asyncio, datetime
from asyncio import StreamReader, StreamWriter
async def echo_server(reader: StreamReader, writer: StreamWriter):
while True:
try:
data = await asyncio.wait_for( reader.read(1024), timeout=5 )
except asyncio.exceptions.TimeoutError:
print('timeout error ..')
break
print('Received: {}'.format( data ))
current_dtime = datetime.datetime.now().strftime('%H:%M:%S')
writer.write( current_dtime.encode('utf8') )
await writer.drain()
async def main(host: str, port: int):
server = await asyncio.start_server(echo_server, host, port)
await server.serve_forever()
if __name__ == '__main__':
try:
asyncio.run(main('0.0.0.0', 4444))
except KeyboardInterrupt:
pass
I'm new to asyncio but i'm not new to python and debugging, i am sure that the problem is not about misconfiguration on the server.
So what's up? what is the reason and how to solve it? anything to consider?

Cannot connect to Arduino over BLE Python: [org.bluez.Error.Failed] Software caused connection abort

I'm trying to send data from my Arduino to my Raspberry Pi over BLE. However, when I run the following script, I sometimes get one of the two errors:
[org.bluez.Error.Failed] Software caused connection abort or This service is already present in this BleakGATTServiceCollection!
When I exit the program, I get the following error as the last line: bleak.exc.BleakError: Characteristic 00001143-0000-1000-8000-00805f9b34fb not found! or bleak.exc.BleakError: Not connected
I have tried rebooting the Raspberry Pi and Arduino as well as restarting the bluetooth service with sudo systemctl restart bluetooth and sudo systemctl daemon-reload without avail.
The weird thing is, if I run the script on a different Pi that I set up in a similar way, the script runs as expected.
What could be causing this problem?
Here's the script (some irrelevant bits removed):
# Adapted from: https://github.com/Ladvien/arduino_ble_sense
import os
import sys
import asyncio
import pyrebase
import platform
import bluetooth
from datetime import datetime
from typing import Callable, Any, List
from time import sleep
from aioconsole import ainput
from bleak import BleakClient, discover
class Connection:
client: BleakClient = None
def __init__(
self,
loop: asyncio.AbstractEventLoop,
read_characteristic: str,
write_characteristic: str,
data_dump_handler: Callable[[str, str], None]
):
self.loop = loop
self.read_characteristic = read_characteristic
self.write_characteristic = write_characteristic
self.data_dump_handler = data_dump_handler
self.connected = False
self.connected_device = None
def on_disconnect(self, client: BleakClient):
self.connected = False
# Put code here to handle what happens on disconnect.
print(f"Disconnected from {self.connected_device.name}!")
async def cleanup(self):
if self.client:
await self.client.stop_notify(read_characteristic)
await self.client.disconnect()
async def manager(self):
print("Starting connection manager.")
while True:
if self.client:
await self.connect()
else:
await self.select_device()
await asyncio.sleep(15.0)
async def connect(self):
if self.connected:
return
try:
await self.client.connect()
self.connected = self.client.is_connected
if self.connected:
print(F"Connected to {self.connected_device.name}")
self.client.set_disconnected_callback(self.on_disconnect)
await self.client.start_notify(
self.read_characteristic, self.notification_handler,
)
while True:
if not self.connected:
break
await asyncio.sleep(3.0)
else:
print(f"Failed to connect to {self.connected_device.name}")
except Exception as e:
print(e)
async def select_device(self):
print("Bluetooh LE hardware warming up...")
await asyncio.sleep(2.0) # Wait for BLE to initialize.
devices = await discover()
print("Please select device: ")
for i, device in enumerate(devices):
print(f"{i}: {device.name}")
response = -1
while True:
response = await ainput("Select device: ")
try:
response = int(response.strip())
except:
print("Please make valid selection.")
if response > -1 and response < len(devices):
break
else:
print("Please make valid selection.")
print(f"Connecting to {devices[response].name}")
self.connected_device = devices[response]
self.client = BleakClient(devices[response].address, loop=self.loop)
#############
# Loops
#############
async def user_console_manager(connection: Connection):
if connection.client and connection.connected:
input_str = await ainput("Enter command: ")
bytes_to_send = bytearray(map(ord, input_str))
await connection.client.write_gatt_char(write_characteristic, bytes_to_send)
else:
await asyncio.sleep(2.0)
async def main():
while True:
await asyncio.sleep(5)
#############
# App Main
#############
read_characteristic = "00001143-0000-1000-8000-00805f9b34fb"
write_characteristic = "00001142-0000-1000-8000-00805f9b34fb"
if __name__ == "__main__":
# Create the event loop.
loop = asyncio.get_event_loop()
db = Database()
connection = Connection(
loop, read_characteristic, write_characteristic, db.writeToDB
)
try:
asyncio.ensure_future(connection.manager())
asyncio.ensure_future(user_console_manager(connection))
asyncio.ensure_future(main())
loop.run_forever()
except KeyboardInterrupt:
print()
print("User stopped program.")
finally:
print("Disconnecting...")
loop.run_until_complete(connection.cleanup())
exit()
Edit:
After #ukBaz's suggestion, I distilled the script to the following:
import asyncio
from bleak import discover
from bleak import BleakClient
address = "ARDUINO_ADDRESS"
async def connect(address, loop):
async with BleakClient(address, loop=loop) as client:
services = await client.get_services()
for ser in services:
print(ser.uuid)
loop = asyncio.get_event_loop()
loop.run_until_complete(connect(address, loop))
However, I still ran into the error:
Traceback (most recent call last):
File "/home/pi/smart-home-pi/test2.py", line 15, in <module>
loop.run_until_complete(connect(address, loop))
File "/home/pi/.pyenv/versions/3.9.7/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/home/pi/smart-home-pi/test2.py", line 9, in connect
async with BleakClient(address, loop=loop) as client:
File "/home/pi/.pyenv/versions/3.9.7/lib/python3.9/site-packages/bleak/backends/client.py", line 61, in __aenter__
await self.connect()
File "/home/pi/.pyenv/versions/3.9.7/lib/python3.9/site-packages/bleak/backends/bluezdbus/client.py", line 298, in connect
assert_reply(reply)
File "/home/pi/.pyenv/versions/3.9.7/lib/python3.9/site-packages/bleak/backends/bluezdbus/utils.py", line 23, in assert_reply
raise BleakDBusError(reply.error_name, reply.body)
bleak.exc.BleakDBusError: [org.bluez.Error.Failed] Software caused connection abort
Could it be the Python version I'm using?
I have run your second script and it worked for me although I'm not using a RPi or an Arduino. I'm also using Python 3.8.10 on Linux.
To get Bluetooth debug information on Linux:
Are you able to connect to the device using bluetoothctl?
Does service bluetooth status show errors?
When running your script have separate terminals open with the following running to get more debug information:
bluetootctl
journalctl -f -u bluetooth
sudo busctl monitor org.bluez
sudo btmon
I also took a look at your original script to simplify it. I came up with the following:
import asyncio
from aioconsole import ainput
from bleak import BleakClient, discover
async def data_client(device):
def handle_rx(_: int, data: bytearray):
print("received:", data)
async with BleakClient(device) as client:
await client.start_notify(read_characteristic, handle_rx)
while client.is_connected:
await asyncio.sleep(1)
input_str = await ainput("Enter command: ")
bytes_to_send = input_str.encode()
if input_str == 'exit':
await client.stop_notify(read_characteristic)
await client.disconnect()
else:
await client.write_gatt_char(write_characteristic, bytes_to_send)
async def select_device():
print("Scanning for Bluetooh LE hardware...")
await asyncio.sleep(2.0) # Wait for BLE to initialize.
devices = await discover()
print("Please select device: ")
for i, device in enumerate(devices):
print(f"{i}: {device.name}")
print("99: Exit program")
print("-1: Re-scan for BLE devices")
response = await ainput("Select device: ")
try:
response = int(response.strip())
except ValueError:
print("Please make valid selection.")
response = -1
print('response', type(response), response)
if -1 < response < len(devices):
return devices[response].address
elif response == 99:
return 99
print("Please make valid selection.")
async def main():
device = None
keep_alive = True
while keep_alive:
print('Device:', device)
if device is None:
device = await select_device()
if device == 99:
keep_alive = False
elif device:
await data_client(device)
device = None
print('Device disconnected.\n')
# read_characteristic = "00001143-0000-1000-8000-00805f9b34fb"
read_characteristic = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
# write_characteristic = "00001142-0000-1000-8000-00805f9b34fb"
write_characteristic = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
if __name__ == "__main__":
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
finally:
loop.close()

Update Server every n seconds by looping function every n seconds? Python Sockets

I'm running this server which receives data. However I want it to update every second. This Asyncio loop says it's running forever but it only receives data once.
What loops can I execute to update message retrieval every n seconds and where should I place these loops? I've tried Threading, For/While Loops etc but I may have been placing them in the wrong places.
What should I do?
import asyncio
import websockets
import socket
UDP_IP = socket.gethostname()
UDP_PORT = 5225
sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP, UDP_PORT))
while True:
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
#print(str(data))
x = 1
async def echo(websocket, path):
async for message in websocket:
await asyncio.sleep(1)
await websocket.send(str(data)) #FontWeight Value
print(bytes(data))
start_server = websockets.serve(echo, "localhost", 9090)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
#loop.run_forever(start_server)
You can't use ordinary sockets in asyncio because their blocking recv stalls the event loop. You need to use something like this:
data = None
class ServerProtocol(asyncio.Protocol):
def data_received(self, newdata):
global data
data = newdata
async def serve_udp():
loop = asyncio.get_running_loop()
server = await loop.create_server(ServerProtocol, UDP_IP, UDP_PORT)
async with server:
await server.serve_forever()
Then you to integrate it with the websocket serving code. For example:
async def ws_echo(websocket, path):
async for message in websocket:
await asyncio.sleep(1)
await websocket.send(str(data))
async def main():
asyncio.create_task(serve_udp())
await websockets.serve(ws_echo, "localhost", 9090)
await asyncio.Event().wait() # prevent main() from returning
asyncio.run(main())

asyncio server client terminates early without output

I'm learning to play around with the asyncio library in py3.5 syntax with async def and await, and trying to write a simple server/client architecture.
For some reason, the client never receives the message and terminates early:
Client
IP = ''
PORT = 8880
import asyncio
import multiprocessing
import ssl
async def start_client(loop):
reader, writer = await asyncio.open_connection(IP, PORT, loop=loop)
writer.write("Gimme gimme gimme".encode())
writer.close()
data = await reader.read()
print(data.decode())
loop = asyncio.get_event_loop()
loop.run_until_complete(start_client(loop))
loop.close()
Server
IP = ''
PORT = 8880
import asyncio
import requests
import json
async def handle_echo(reader, writer):
data = await reader.read()
response = await whatsup()
print(response)
writer.write(response.encode())
writer.write_eof()
await writer.drain()
writer.close()
async def whatsup():
return "Hello there!"
loop = asyncio.get_event_loop()
server = asyncio.start_server(handle_echo,
IP,
PORT,
loop=loop)
server = loop.run_until_complete(server)
try:
loop.run_forever()
except:
pass
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
What I observe is that the server was able to print out the "Hello world!", and return successfully, but from what I can gather, the client reads a total of 0 bytes and just exits.
What I tried already
I tried doing:
while not reader.at_eof():
data = await reader.read(100)
print(data.decode())
But it evaluates at_eof() to be true and exits early as well.
OK I found the solution:
read() will read until eof marker. We need to do writer.write_eof() on both sides for the read() to be read.
Here's the solution code:
async def handle_echo(reader, writer):
data = await reader.read()
response = await whatsup()
print(response)
writer.write(response.encode())
writer.write_eof()
await writer.drain()
writer.close()
async def start_client(loop):
reader, writer = await asyncio.open_connection(IP, PORT, loop=loop)
writer.write("Gimme gimme gimme".encode())
writer.write_eof() # crucial here
writer.close()
data = await reader.read()
print(data.decode())

how to handle tcp client socket auto reconnect in python asyncio?

I am using python asyncio streams to connect to several socket servers, but when the server is down, my code can't auto reconnect.
What I need is that, when the server is down, my script will try to reconnect every 5 seconds, until connected and start to parse the data again.
import asyncio
server1 = {'host': '192.168.1.51', 'port': 11110}
server2 = {'host': '192.168.1.52', 'port': 11110}
async def tcp_client(host, port, loop):
print('connect to server {} {}'.format(host, str(port)))
reader, writer = await asyncio.open_connection(host, port, loop=loop)
while True:
data = await reader.read(100)
print('raw data received: {}'.format(data))
await asyncio.sleep(0.1)
loop = asyncio.get_event_loop()
try:
for server in [server1, server2]:
loop.run_until_complete(tcp_client(server['host'], server['port'], loop))
print('task added: connect to server {} {}'.format(server['host'], server['port']))
finally:
loop.close()
print('loop closed')
You can handle reconnection by simply looping over a try/except statement.
Additionally, asyncio.wait_for can be used to set a timeout on the read operation.
Consider this working example:
import asyncio
async def tcp_client(host, port):
reader, writer = await asyncio.open_connection(host, port)
try:
while not reader.at_eof():
data = await asyncio.wait_for(reader.read(100), 3.0)
print('raw data received: {}'.format(data))
finally:
writer.close()
async def tcp_reconnect(host, port):
server = '{} {}'.format(host, port)
while True:
print('Connecting to server {} ...'.format(server))
try:
await tcp_client(host, port)
except ConnectionRefusedError:
print('Connection to server {} failed!'.format(server))
except asyncio.TimeoutError:
print('Connection to server {} timed out!'.format(server))
else:
print('Connection to server {} is closed.'.format(server))
await asyncio.sleep(2.0)
async def main():
servers = [('localhost', 8888), ('localhost', 9999)]
coros = [tcp_reconnect(host, port) for host, port in servers]
await asyncio.gather(*coros)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

Categories