Skip to content

Commit

Permalink
Merge pull request #77 from jonyboi396825/develop
Browse files Browse the repository at this point in the history
Release 0.1
  • Loading branch information
jonathanhliu21 authored Dec 23, 2021
2 parents eff7080 + 944f9a3 commit f8ea59b
Show file tree
Hide file tree
Showing 17 changed files with 167 additions and 20 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# 0.1

Previous version: 0.1b1

## Changes from previous version:

- Added some more examples in the examples directory, addressing [#74](https://github.com/jonyboi396825/COM-Server/issues/74).
- Fixed [#78](https://github.com/jonyboi396825/COM-Server/issues/78) by adding thread lock to binary search method for checking availability, and also a lock to the `Connection.get_all_rcv()` and `Connection.get_all_rcv_str()` methods to **deep copy** the receive queue instead of directly returning the receive queue.
- Added more verbose output to the disconnect handler, including that port that it reconnected to.

# 0.1 Beta Release 1

Previous version: 0.1b0
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/jonyboi396825/COM-Server/Run%20Pytest%20(Push%20to%20master))
[![Documentation Status](https://readthedocs.org/projects/com-server/badge/?version=latest)](https://com-server.readthedocs.io/en/latest/?badge=latest)
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/jonyboi396825/COM-Server/Upload%20Python%20Package?label=PyPI%20upload)
![PyPI](https://img.shields.io/pypi/v/com_server?label=Version)

COM-Server is a Python library and a local web server that hosts an API locally and interacts with serial or COM ports. The Python library provides a different way of sending and receiving data from the serial port using a thread, and it also gives a set of tools that simplifies the task of manipulating data to and from the port. Additionally, the server makes it easier for other processes to communicate with the serial port.

Expand Down
2 changes: 2 additions & 0 deletions docs/guide/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ print(conn.connected)

On the event of a disconnect, you can call the `reconnect()` method to try to reconnect to a port provided in `__init__()`. However, it will raise a `ConnectException` if the port is already connected and this is called.

Note that disconnecting the serial device will **reset** the receive and send queues.

```py
with Connection(...) as conn:
while True:
Expand Down
9 changes: 6 additions & 3 deletions docs/guide/library-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,8 @@ continuously try to reconnect indefinitely.
Will raise `ConnectException` if already connected, regardless
of if `exception` is True or not.

Note that disconnecting the serial device will **reset** the receive and send queues.

Parameters:

- `timeout` (float, None) (optional): Will try to reconnect for
Expand Down Expand Up @@ -719,9 +721,10 @@ will be used to ensure that there is only one connection at a time. Note that un
resource classes have to extend the custom `ConnectionResource` class
from this library, not the `Resource` from `flask_restful`.

`500 Internal Server Error`s may occur with endpoints dealing with the connection
if the serial port is disconnected. Disconnections while the server is running
require restarts of the server and may change the port of the device that was previously connected.
`500 Internal Server Error`s will occur with endpoints dealing with the connection
if the serial port is disconnected. The server will spawn another thread that will
immediately try to reconnect the serial port if it is disconnected. However, note
that the receive and send queues will **reset** when the serial port is disconnected.

More information on [Flask](https://flask.palletsprojects.com/en/2.0.x/) and
[flask-restful](https://flask-restful.readthedocs.io/en/latest/)
Expand Down
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# COM-Server Documentation

![PyPI](https://img.shields.io/pypi/v/com_server?label=Latest%20Version)

Welcome to the COM-Server documentation.

COM-Server is a Python library and a local web server that hosts an API locally and interacts with serial or COM ports. The Python library provides a different way of sending and receiving data from the serial port using a thread, and it also gives a set of tools that simplifies the task of manipulating data to and from the port. Additionally, the server makes it easier for other processes to communicate with the serial port.
Expand Down
1 change: 1 addition & 0 deletions docs/server/server-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ Notes:

- When it reconnects, it calls the `reconnect()` method in the `Connection` object. It will try to reconnect to the ports given in `__init__()`, which means that if the port was changed somehow between disconnecting and reconnecting, it will not reconnect and will require restarting the server.
- When running a development server, it will print out the disconnect and reconnect events to stdout. It will not when running a production server.
- Disconnecting the serial device will **reset** the receive and send queues.

## Escape characters

Expand Down
9 changes: 9 additions & 0 deletions examples/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Examples of COM-Server:

NOTE: You need an Arduino, and you have to upload the Arduino sketches provided under each directory before running the scripts in that directory.

blink - An simple example of sending data to the serial port. It allows you to manipulate the LED on the Arduino from the command line.
Upload the blink.ino sketch, then run the python script.

send_back - The sketch tells the Arduino to read the data from the serial port until a newline character, then it will send that data back through the serial port.
Upload the send_back.ino sketch, then run each python script. Press ^C (Control-C) to exit the program. Read the files to see what output is expected.
47 changes: 47 additions & 0 deletions examples/blink/blink.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
blink.ino
This sketch reads data from the serial port, then changes the
state of the built-in LED whenever a command comes in from the
serial port. The baud rate for this is 115200.
When it reads "s", then it leaves it on solid.
When it reads "b", then it blinks the LED.
When it reads "o", then it turns the LED off.
It shows how you can write data to the Arduino using
com_server.
Copyright 2021 (c) Jonathan Liu
Code is licensed under MIT license.
*/

int curstate = 0; // 0 = off, 1 = blink, 2 = solid on

void setup(){
Serial.begin(115200);

pinMode(LED_BUILTIN, OUTPUT);
}

void loop(){
if (Serial.available()){
String s = Serial.readStringUntil('\n');

if (s == "s")
curstate = 2;
else if (s == "b")
curstate = 1;
else
curstate = 0;
}

if (curstate == 0) {
digitalWrite(LED_BUILTIN, LOW);
} else if (curstate == 2) {
digitalWrite(LED_BUILTIN, HIGH);
} else {
// blink every second so cycle = 2 seconds
digitalWrite(LED_BUILTIN, ((millis() / 1000) % 2 == 0));
}
}
47 changes: 47 additions & 0 deletions examples/blink/blink.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
An example of writing data to an Arduino.
This sketch will ask for an input, and the
built-in LED on the Arduino should turn on,
blink, or turn off based on different inputs.
NOTE: The sketch located in examples/blink must
be uploaded to an Arduino before running this script.
"""

import time

from com_server import Connection

# make the Connection object; make send_interval 0.1 seconds because not sending large data
conn = Connection(baud=115200, port="/dev/ttyUSB0", send_interval=0.1) # if Linux
# conn = Connection(baud=115200, port="/dev/ttyUSB...", send_interval=0.1) # if Linux; can be "/dev/ttyACM..."
# conn = Connection(baud=115200, port="/dev/cu.usbserial...", send_interval=0.1) # if Mac
# conn = Connection(baud=115200, port="COM...", send_interval=0.1) # if Windows

with conn:
# use a context manager, which will connect and disconnect
# automatically when entering and exiting.

while conn.connected:
# continue running as long as there is a connection established

cmd = input("Enter LED state: (s)olid, (b)link, (o)ff, or (e)xit: ")

if cmd == "e" or cmd == "exit":
break
elif cmd == "s" or cmd == "solid":
conn.send("s", ending='\n')
elif cmd == "b" or cmd == "blink":
conn.send("b", ending='\n')
elif cmd == "o" or cmd == "off":
conn.send("o", ending='\n')
else:
print("Command not recognized; please try again.")

# Recommended to include a delay when using connection
# object in a loop
time.sleep(0.01)
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
An example of how to use the Connection object.
NOTE: the sketch located in examples/send_back needs
to be uploaded to the Arduino before running this script.
to be uploaded to an Arduino before running this script.
"""

import time

from com_server import Connection

# make the Connection object
conn = Connection(baud=115200, port="/dev/ttyUSB0")
conn = Connection(baud=115200, port="/dev/ttyUSB0") # if Linux
# conn = Connection(baud=115200, port="/dev/ttyUSB...") # if Linux; can be "/dev/ttyACM..."
# conn = Connection(baud=115200, port="/dev/cu.usbserial...")
# conn = Connection(baud=115200, port="/dev/cu.usbserial...") # if Mac
# conn = Connection(baud=115200, port="COM...") # if Windows

conn.connect() # connect to serial port
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
from com_server import Builtins, Connection, ConnectionResource, RestApiHandler

# make the Connection object
conn = Connection(baud=115200, port="/dev/ttyUSB0")
conn = Connection(baud=115200, port="/dev/ttyUSB0") # if Linux
# conn = Connection(baud=115200, port="/dev/ttyUSB...") # if Linux; can be "/dev/ttyACM..."
# conn = Connection(baud=115200, port="/dev/cu.usbserial...")
# conn = Connection(baud=115200, port="/dev/cu.usbserial...") # if Mac
# conn = Connection(baud=115200, port="COM...") # if Windows

# make the API Handler object; initialize it with the connection object
Expand Down Expand Up @@ -48,6 +48,21 @@ class Hello_World_Endpoint(ConnectionResource):
# for more information on Flask, see https://flask.palletsprojects.com/en/2.0.x/

def get(self):
"""
When there is a GET request, this endpoint will respond with
{
"Hello": "World!",
"Received": [timestamp, data]
}
The "Received" key is mapped to a value that is a list: [timestamp, data]
where timestamp is the time that the data was received from the serial port and
data is the data that came from the serial port.
However, if there was no data received, then "Received" should be null.
"""

return {
"Hello": "World!",
"Received": self.conn.receive_str()
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = com-server
version = 0.1b1
version = 0.1
author = Jonathan Liu
author_email = [email protected]
description = A simple Python library and a REST API server that interacts with COM ports
Expand Down
2 changes: 1 addition & 1 deletion src/com_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@

from .api_builtins import Builtins

__version__ = "0.1b1"
__version__ = "0.1"
8 changes: 4 additions & 4 deletions src/com_server/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ class RestApiHandler:
Finally, resource classes have to extend the custom `ConnectionResource` class
from this library, not the `Resource` from `flask_restful`.
`500 Internal Server Error`s may occur with endpoints dealing with the connection
if the serial port is disconnected. Disconnections while the server is running
require restarts of the server and may change the port of the device that was
previously connected.
`500 Internal Server Error`s will occur with endpoints dealing with the connection
if the serial port is disconnected. The server will spawn another thread that will
immediately try to reconnect the serial port if it is disconnected. However, note
that the receive and send queues will **reset** when the serial port is disconnected.
More information on [Flask](https://flask.palletsprojects.com/en/2.0.x/) and [flask-restful](https://flask-restful.readthedocs.io/en/latest/).
Expand Down
9 changes: 6 additions & 3 deletions src/com_server/base_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,18 +498,21 @@ def _binary_search_rcv(self, target: float) -> int:
When comparing, rounds to 4 digits.
"""

if len(self._rcv_queue) <= 0:
with self._lock:
_tmp_q = self._rcv_queue.copy()

if len(_tmp_q) <= 0:
# not found if no size
return -1

low = 0
high = len(self._rcv_queue)
high = len(_tmp_q)

while low <= high:
mid = (low + high) // 2 # integer division

# comparing rounding to two digits
cmp1 = round(self._rcv_queue[mid][0], 4)
cmp1 = round(_tmp_q[mid][0], 4)
cmp2 = round(target, 4)

if cmp1 == cmp2:
Expand Down
10 changes: 8 additions & 2 deletions src/com_server/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Contains implementation of connection object.
"""

import copy
import os
import sys
import time
Expand Down Expand Up @@ -144,7 +145,10 @@ def get_all_rcv(self) -> t.List[t.Tuple[float, bytes]]:
if not self.connected:
raise base_connection.ConnectException("No connection established")

return self._rcv_queue
with self._lock:
_rq = copy.deepcopy(self._rcv_queue)

return _rq

def get_all_rcv_str(
self, read_until: t.Optional[str] = None, strip: bool = True
Expand Down Expand Up @@ -172,7 +176,7 @@ def get_all_rcv_str(

return [
(ts, self.conv_bytes_to_str(rcv, read_until=read_until, strip=strip))
for ts, rcv in self._rcv_queue
for ts, rcv in self.get_all_rcv()
]

def receive_str(
Expand Down Expand Up @@ -446,6 +450,8 @@ def reconnect(self, timeout: t.Optional[float] = None) -> bool:
Will raise `ConnectException` if already connected, regardless
of if `exception` is True or not.
Note that disconnecting the serial device will **reset** the receive and send queues.
Parameters:
- `timeout` (float, None) (optional): Will try to reconnect for
`timeout` seconds before returning. If None, then will try to reconnect
Expand Down
3 changes: 2 additions & 1 deletion src/com_server/disconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ def run(self) -> None:
if not self._conn.connected:
if self._v:
print("Device disconnected")
print("Attempting to reconnect...")

self._conn.reconnect()

if self._v:
print("Device reconnected")
print(f"Device reconnected at {self._conn.port}")

time.sleep(0.01)

0 comments on commit f8ea59b

Please sign in to comment.