how to use and debug micropython-aioble library for esp32? - python

I am using micropython for esp32 to make BLE app using aioble library.i am using as per sample code + add from library but i am facing this problem and don't understand why. Is it because the library is having problems?. I have followed the instructions on github but the error still occurs, I can't handle it
import sys
sys.path.append("")
from micropython import const
import uasyncio as asyncio
import aioble
import bluetooth
SERVICE_UUID = bluetooth.UUID('00001800-0000-1000-8000-00805F9B34FB')
mtu_connect = 0
async def find_temp_sensor():
# Scan for 5 seconds, in active mode, with very low interval/window (to
# maximise detection rate).
async with aioble.scan(10000, interval_us=12000, window_us=10000, active=True) as scanner:
async for result in scanner:
# See if it matches our name and the environmental sensing service.
print(result, result.name(), result.rssi, result.services())
if result.name() == "70001697":
return result.device
return None
async def main():
device = await find_temp_sensor()
while not device:
print("Temperature sensor not found")
device = await find_temp_sensor()
return
print(device)
mtu_connect = 0
while mtu_connect < 3:
try:
print("Connecting to", device)
connection = await device.connect()
service = await connection.service(SERVICE_UUID)
print("service", service.uuid)
# Discover characteristics.
uuids = []
async for char in service.characteristics():
uuids.append(char.uuid)
print("found", sorted(uuids))
print("Connecting done!")
break
except asyncio.TimeoutError:
print("Timeout during connection")
mtu_connect = mtu_connect + 1
asyncio.run(main())

You need to specify the actual error message to get some help. Note that you need to put on your microcontroller the aioble library files (typically under lib/aioble) as aioble is not part of the standard micropython firmware.

You need to import aioble using mip. See the user instructions on installation with mpremote. It cannot be done through thonny.

Related

EV Driver Authorization using RFID in OCPP protocol

I am new to OCPP protocol and I am building a Python OCPP server that can communicate with an EV charger using OCPP protocol. This server has the feature "Authenticate user via RFID". I have created 2 Python files which are Charge_Stattion.py:
# Charge_Stattion.py
import asyncio
import logging
import websockets
from ocpp.v201 import call
from ocpp.v201 import ChargePoint as cp
logging.basicConfig(level=logging.INFO)
class ChargePoint(cp):
async def authentication(self):
request = call.AuthorizePayload(
id_token={'id_token':'AA12345',
'type': 'ISO14443'})
response = await self.call(request)
print(response)
async def main():
async with websockets.connect(
'ws://localhost:9000/CP_1',
subprotocols=['ocpp2.0.1']
) as ws:
cp = ChargePoint('CP_1', ws)
await asyncio.gather(cp.start(), cp.authentication())
if __name__ == '__main__':
asyncio.run(main())
and Central_System.py:
#Central_System.py
import asyncio
import logging
import websockets
from datetime import datetime
from ocpp.routing import on
from ocpp.v201 import ChargePoint as cp
from ocpp.v201 import call_result
from ocpp.v201.enums import AuthorizationStatusType, Action
logging.basicConfig(level=logging.INFO)
class ChargePoint(cp):
#on('BootNotification')
async def on_boot_notification(self, charging_station, reason, **kwargs):
return call_result.BootNotificationPayload(
current_time=datetime.utcnow().isoformat(),
interval=10,
status='Accepted'
)
#on(Action.Authorize)
async def on_authorize(self, id_token):
return call_result.AuthorizePayload(id_token_info={"status": AuthorizationStatusType.accepted})
async def on_connect(websocket, path):
""" For every new charge point that connects, create a ChargePoint
instance and start listening for messages.
"""
try:
requested_protocols = websocket.request_headers[
'Sec-WebSocket-Protocol']
except KeyError:
logging.info("Client hasn't requested any Subprotocol. "
"Closing Connection")
if websocket.subprotocol:
logging.info("Protocols Matched: %s", websocket.subprotocol)
else:
# In the websockets lib if no subprotocols are supported by the
# client and the server, it proceeds without a subprotocol,
# so we have to manually close the connection.
logging.warning('Protocols Mismatched | Expected Subprotocols: %s,'
' but client supports %s | Closing connection',
websocket.available_subprotocols,
requested_protocols)
return await websocket.close()
charge_point_id = path.strip('/')
cp = ChargePoint(charge_point_id, websocket)
logging.info("abcxyz: %s", charge_point_id)
await cp.start()
async def main():
server = await websockets.serve(
on_connect,
'0.0.0.0',
9000,
subprotocols=['ocpp2.0.1']
)
logging.info("WebSocket Server Started")
await server.wait_closed()
if __name__ == '__main__':
asyncio.run(main())
Following the document here, I understand that the user must present an RFID card first, then the Charge Station will send an AuthorizeRequest containing idToken from this RFID card to Central System, then Central System will send and AuthorizeResponse to Charge Station. In the 2 python files above, I have implemented the process Charge Station sends andAuthorizeRequest to Central System and Central System sends back AuthorizeResponse to Charge Station. This picture demonstrates these processes
My questions are:
How can I implement the process EV driver present an RFID card to Charge Station. Should I create 2 other python files which represent EV driver and RFID card?
How can I know whether Center System accept this authentication and how to implement this ?
Any help will be appreciated.
This is a simple flow
EV owner registers himself as a EV client on some server where the server provides an unique id, like "unique-client-id" and stores this value as idTag on a database.
When this client go to charge to some charging station, he inputs that unique id to charging device which sends the id in the following form via websocket connection:
[3, "unique-id-representing-the-current-msg", "Authorize", {"idTag": "unique-client-id"}]
OCPP server receives that message, and looks for received idTag on the database, if it exists it will send back response like below:
[4, "unique-id-representing-the-current-msg", {"idTagInfo": {"status": "Accepted"}}]
I recommend using sanic framework since it has both websocket and http support by default.

Bleak client: problem with sending bytes to BLE device (E104-BT02)

I'm trying to send bytes using Python and Bleak (https://bleak.readthedocs.io/en/latest/index.html) module from PC (Windows) to Ebyte BLE module E104-BT02.
The code is following:
ADDRESS = "ED:F5:E8:F6:DE:D8"
MODEL_NBR_UUID = "00002a24-0000-1000-8000-00805f9b34fb"
UART_TX_CHAR_UID = "0000fff1-0000-1000-8000-00805f9b34fb"
UART_RX_CHAR_UUID = "0000fff2-0000-1000-8000-00805f9b34fb"
def handle_rx(_: int, data: bytearray):
print("received:", data)
async def main(ble_address):
device = await BleakScanner.find_device_by_address(ble_address, timeout=60.0)
if not device:
raise BleakError(f"A device with address {ble_address} could not be found.")
async with BleakClient(ble_address) as client:
await client.start_notify(UART_TX_CHAR_UID, handle_rx)
print("Connected, start typing and press ENTER...")
loop = asyncio.get_running_loop()
while True:
data = await loop.run_in_executor(None, sys.stdin.buffer.readline)
if data == b'\r\n':
break
await client.write_gatt_char(UART_RX_CHAR_UUID, data)
print("sent:", data)
print(res)
asyncio.run(main(ADDRESS))
await client.write_gatt_char(UART_RX_CHAR_UUID, data) should send every byte that I enter through the console. But I get nothing on my module.
However, if I use BLEConsole (https://sensboston.github.io/BLEConsole/) or a mobile App. LightBlue everything is OK, I get bytes without any problems. Characteristic UUID 0000fff2-0000-1000-8000-00805f9b34fb is correct. I feel that it is some stupid thing, but I've been trying to find something for 3 days. I would be very grateful for any ideas
P. S. Everything works perfectly in the opposite direction. When I send bytes from the E104-BT02 module, the PC gets and shows them in the console. (characteristic UUID 0000fff1-0000-1000-8000-00805f9b34fb)

How to implement logic like sock.recv(), based on python-asyncio's transport & protocol api?

I'm trying to build some simple applications based on asyncio tcp. In traditional socket programming, we use sock.recv() and sock.send() to manage the receiving and sending of sockets, but I noticed that using sockets directly is not recommended according to asyncio documentation, correspondingly, they suggest using the transport abstraction.
I want to know how to use transport to reproduce a logic similar to traditional socket programming. For example I'd like to implement the following logic:
async def main():
loop = asyncio.get_running_loop()
transport, protocal = await loop.create_connection(EchoClientProtocol(), '', 25000)
await transport.write("hello")
await transport.read(5) # Error
....
The above code does not work because transport does not provide a read method in the begining, the read event must be implemented in the corresponding protocol. This prevents me from clearly separating different tcp packages. What is the right way to do it? Thanks.
You can implement TCP server and client using asyncio streams
Edit based on #user4815162342 great suggests:
I increased for read maximum number of bytes on chuck from 1 byte to 8192 bytes, it was my bad idea to use use the smallest possible number in the example and it could be misleading for other people.
In addition BytesIO is much better suited for concatenation than just += bytes. I introduced BytesIO to this code example.
Server Script Example:
import asyncio
import socket
from io import BytesIO
async def handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
print(len(asyncio.all_tasks())) # let's show number of tasks
ip, port = writer.get_extra_info('peername') # get info about incoming connection
print(f"Incoming connection from {ip}: {port}")
# better use BytesIO than += if you gonna concat many times
all_data = BytesIO()
while True:
try:
# read chunk up to 8 kbytes
data = await asyncio.wait_for(reader.read(8192), timeout=2.0)
all_data.write(data)
if reader.at_eof():
print(f"Received data:\n{all_data.getvalue().decode('utf8')}")
break
except (asyncio.CancelledError, asyncio.TimeoutError):
print("Too slow connection aborted")
break
writer.write(b"FROM_SERVER:\n") # prepare data
writer.write(all_data.getvalue()) # prepare more data
# simulate slow server
# await asyncio.sleep(5)
await writer.drain() # send all prepared data
if writer.can_write_eof():
writer.write_eof()
writer.close() # do not forget to close stream
async def main_server():
server = await asyncio.start_server(
client_connected_cb=handler,
host="localhost",
port=8888,
family=socket.AF_INET, # ipv4
)
ip, port = server.sockets[0].getsockname()
print(f"Serving on: {ip}:{port}")
print("*" * 200)
async with server:
await server.serve_forever()
if __name__ == '__main__':
asyncio.run(main_server())
Client Script Example:
import asyncio
from io import BytesIO
async def main():
reader, writer = await asyncio.open_connection(host="localhost", port=8888)
# remove comment to test slow client
# await asyncio.sleep(20)
for i in range(10):
writer.write(f"hello-{i}\n".encode("utf8")) # prepare data
await writer.drain() # send data
if writer.can_write_eof():
writer.write_eof() # tell server that we sent all data
# better use BytesIO than += if you gonna concat many times
data_from_server = BytesIO() # now get server answer
try:
while True:
# read chunk up to 8 kbytes
data = await asyncio.wait_for(reader.read(8192), timeout=1.0)
data_from_server.write(data)
# if server told use that no more data
if reader.at_eof():
break
print(data_from_server.getvalue().decode('utf8'))
writer.close()
except ConnectionAbortedError:
# if our client was too slow
print("Server timed out connection")
writer.close()
except (asyncio.TimeoutError, asyncio.CancelledError):
# if server was too slow
print("Did not get answer from server due to timeout")
writer.close()
if __name__ == '__main__':
asyncio.run(main())

Tornado Asynchronous Writes

The code below is a simplified version of a Tornado based TCP server that is currently used to host a Videotex system. This code was derived from the Tornado documentation and the server has been running in a live environment for some time without issue, however, there is a feature I need to add.
The system currently blocks until a character is received from the client before returning the data via the stream.write. As the system typically runs at 1200 baud at the client end (via a telnet modem), this means that the user has to wait until all stream writes have completed before the next 'user entered' character is processed.
What I would like to do is find a way that would allow me to abandon writing data to stream.write if another character is received form the client.
I am new to Tornado and fairly new to Python, however, I have coded asynchronous functions and threaded solutions in the past using C#.
From the documentation the stream.write operation is asynchronous, I am assuming therefore that the call may return before the data is completely written, I am left thinking that I need a method to abandon/empty/advance the write buffer to stop the write operation if a new char is detected on the stream.read.
One option that would seem to give me what I need is to somehow perform the stream.writes on another thread , however, this approach seems inappropriate when using Tornado's IOLoop etc.
Is there a way to give me the facility I am after? I have full control of the code and am happy to restructure the app if needed.
import logging
import struct
import os
import traceback
from tornado import gen
from tornado.ioloop import IOLoop
from tornado.iostream import StreamClosedError
from tornado.tcpserver import TCPServer
# Configure logging.
logger = logging.getLogger(os.path.basename(__file__))
logger.setLevel(logging.INFO)
# Cache this struct definition; important optimization.
int_struct = struct.Struct("<i")
_UNPACK_INT = int_struct.unpack
_PACK_INT = int_struct.pack
class TornadoServer(TCPServer):
def start(self, port):
self.port = port
server.listen(port)
#gen.coroutine
def handle_stream(self, stream, address):
logging.info("[viewdata] Connection from client address {0}.".format(address))
try:
while True:
char = yield stream.read_bytes(1) # this call blocks
asc = ord(char)
logger.info('[viewdata] Byte Received {0} ({1})'.format(hex(asc), asc))
# Do some processing using the received char and return the appropriate page of data
stream.write('This is the data you asked for...'.encode())
except StreamClosedError as ex:
logger.info("[viewdata] {0} Disconnected: {1} Message: {2}".format(address, type(ex), str(ex)))
except Exception as ex:
logger.error("[viewdata] {0} Exception: {1} Message: {2}".format(address, type(ex), str(ex)))
logger.error(traceback.format_exc())
if __name__ == '__main__':
server = TornadoServer()
server.start(25232)
loop = IOLoop.current()
loop.start()
The main idea is that you move long processing into separate task.
When you receive some new data, you choose what to do (in case below I cancel current operation)
import logging
import os
import traceback
import threading
from tornado import gen
from tornado.ioloop import IOLoop
from tornado.iostream import StreamClosedError
from tornado.tcpserver import TCPServer
# Configure logging.
logger = logging.getLogger(os.path.basename(__file__))
logger.setLevel(logging.INFO)
class TornadoServer(TCPServer):
def start(self, port):
self.port = port
server.listen(port)
async def process_stream(self, stream, char, cancel_event):
asc = ord(char)
logger.info('[viewdata] Byte Received {0} ({1})'.format(hex(asc), asc))
N = 5
for i in range(N):
if cancel_event.is_set():
logger.info('[viewdata] Abort streaming')
break
# Do some processing using the received char and return the appropriate page of data
msg = 'This is the {0} data you asked for...'.format(i)
logger.info(msg)
await stream.write('This is the part {0} of {1} you asked for...'.format(i, N).encode())
await gen.sleep(1.0) # make this processing longer..
async def handle_stream(self, stream, address):
process_stream_future = None
cancel_event = None
logging.info("[viewdata] Connection from client address {0}.".format(address))
while True:
try:
char = await stream.read_bytes(1) # this call blocks
# when received client input, cancel running job
if process_stream_future:
process_stream_future.cancel()
if cancel_event:
cancel_event.set()
cancel_event = threading.Event()
process_stream_future = gen.convert_yielded(
self.process_stream(stream, char, cancel_event))
self.io_loop.add_future(process_stream_future, lambda f: f.result())
except StreamClosedError as ex:
logger.info("[viewdata] {0} Disconnected: {1} Message: {2}".format(address, type(ex), str(ex)))
except Exception as ex:
logger.error("[viewdata] {0} Exception: {1} Message: {2}".format(address, type(ex), str(ex)))
logger.error(traceback.format_exc())
if __name__ == '__main__':
server = TornadoServer()
server.listen(25232)
loop = IOLoop.current()
loop.start()

Python 3 websockets - send message before closing connection

I'm new to Stack Overflow (although have been a long-term "stalker"!) so please be gentle with me!
I'm trying to learn Python, in particular Asyncio using websockets.
Having scoured the web for examples/tutorials I've put together the following tiny chat application, and could use some advice before it gets bulkier (more commands etc) and becomes difficult to refactor.
My main question, is why (when sending the DISCONNECT command) does it need the asyncio.sleep(0) in order to send the disconnection verification message BEFORE closing the connection?
Other than that, am I on the right tracks with the structure here?
I feel that there's too much async/await but I can't quite wrap my head around why.
Staring at tutorials and S/O posts for hours on end doesn't seem to be helping at this point so I thought I'd get some expert advice directly!
Here we go, simple WS server that responds to "nick", "msg", "test" & "disconnect" commands. No prefix required, i.e "nick Rachel".
import asyncio
import websockets
import sys
class ChatServer:
def __init__(self):
print("Chat Server Starting..")
self.Clients = set()
if sys.platform == 'win32':
self.loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(self.loop)
else:
self.loop = asyncio.get_event_loop()
def run(self):
start_server = websockets.serve(self.listen, '0.0.0.0', 8080)
try:
self.loop.run_until_complete(start_server)
print("Chat Server Running!")
self.loop.run_forever()
except:
print("Chat Server Error!")
async def listen(self, websocket, path):
client = Client(websocket=websocket)
sender_task = asyncio.ensure_future(self.handle_outgoing_queue(client))
self.Clients.add(client)
print("+ connection: " + str(len(self.Clients)))
while True:
try:
msg = await websocket.recv()
if msg is None:
break
await self.handle_message(client, msg)
except websockets.exceptions.ConnectionClosed:
break
self.Clients.remove(client)
print("- connection: " + str(len(self.Clients)))
async def handle_outgoing_queue(self, client):
while client.websocket.open:
msg = await client.outbox.get()
await client.websocket.send(msg)
async def handle_message(self, client, data):
strdata = data.split(" ")
_cmd = strdata[0].lower()
try:
# Check to see if the command exists. Otherwise, AttributeError is thrown.
func = getattr(self, "cmd_" + _cmd)
try:
await func(client, param, strdata)
except IndexError:
await client.send("Not enough parameters!")
except AttributeError:
await client.send("Command '%s' does not exist!" % (_cmd))
# SERVER COMMANDS
async def cmd_nick(self, client, param, strdata):
# This command needs a parameter (with at least one character). If not supplied, IndexError is raised
# Is there a cleaner way of doing this? Otherwise it'll need to reside within all functions that require a param
test = param[1][0]
# If we've reached this point there's definitely a parameter supplied
client.Nick = param[1]
await client.send("Your nickname is now %s" % (client.Nick))
async def cmd_msg(self, client, param, strdata):
# This command needs a parameter (with at least one character). If not supplied, IndexError is raised
# Is there a cleaner way of doing this? Otherwise it'll need to reside within all functions that require a param
test = param[1][0]
# If we've reached this point there's definitely a parameter supplied
message = strdata.split(" ",1)[1]
# Before we proceed, do we have a nickname?
if client.Nick == None:
await client.send("You must choose a nickname before sending messages!")
return
for each in self.Clients:
await each.send("%s says: %s" % (client.Nick, message))
async def cmd_test(self, client, param, strdata):
# This command doesn't need a parameter, so simply let the client know they issued this command successfully.
await client.send("Test command reply!")
async def cmd_disconnect(self, client, param, strdata):
# This command doesn't need a parameter, so simply let the client know they issued this command successfully.
await client.send("DISCONNECTING")
await asyncio.sleep(0) # If this isn't here we don't receive the "disconnecting" message - just an exception in "handle_outgoing_queue" ?
await client.websocket.close()
class Client():
def __init__(self, websocket=None):
self.websocket = websocket
self.IPAddress = websocket.remote_address[0]
self.Port = websocket.remote_address[1]
self.Nick = None
self.outbox = asyncio.Queue()
async def send(self, data):
await self.outbox.put(data)
chat = ChatServer()
chat.run()
Your code uses infinite size Queues, which means .put() calls .put_nowait() and returns immediately. (If you do want to keep these queues in your code, consider using 'None' in the queue as a signal to close a connection and move client.websocket.close() to handle_outgoing_queue()).
Another issue: Consider replacing for x in seq: await co(x) with await asyncio.wait([co(x) for x in seq]). Try it with asyncio.sleep(1) to experience a dramatic difference.
I believe a better option will be dropping all outbox Queues and just relay on the built in asyncio queue and ensure_future. The websockets package already includes Queues in its implementation.
I want to point out that the author of websockets indicated in a post on July 17 of 2017 that websockets used to return None when the connection was closed but that was changed at some point. Instead he suggests that you use a try and deal with the exception. The OP's code shows both a check for None AND a try/except. The None check is needlessly verbose and apparently not even accurate since with the current version, websocket.recv() doesn't return anything when the client closes.
Addressing the "main" question, it looks like a race condition of sorts. Remember that asyncio does it's work by going around and touching all the awaited elements in order to nudge them along. If your 'close connection' command is processed at some point ahead of when your queue is cleared, the client will never get that last message in the queue. Adding the async.sleep adds an extra step to the round robin and probably puts your queue emptying task ahead of your 'close connection'.
Addressing the amount of awaits, it's all about how many asynchronous things you need to have happen to accomplish the goal. If you block at any point you'll stop all the other tasks that you want to keep going.

Categories