Getting started with COM-Server
This page will show you the basics of getting started with COM-Server.
Creating a Connection class
import com_server
conn = com_server.Connection(port="<port>", baud=<baud>)
Each Serial port has a port and a baud rate. The port varies from operating system to operating system. For example:
- On Linux, the port is in the
/devdirectory and begins withttyfollowed by some letters and numbers (e.g./dev/ttyUSB0,/dev/ttyUSB1,/dev/ttyACM0). - On Mac OS, the port is also in the
/devdirectory and begins withcu.then a sequence of letters and numbers. If it is a USB port, then it would becu.usbserialfollowed by a string of letters/numbers (/dev/cu.usbserial-A1252381) - On Windows, the port usually begins with
COM(e.g.COM3).
A good way of determining the port is by disconnecting the serial port, typing in the command below, connecting the port, then entering the command again. The one that shows up is the one that you would use.
> pyserial-ports
If /dev/ttyUSB0 is the one that shows up after the connection, then we would write it like this:
import com_server
conn = com_server.Connection(port="/dev/ttyUSB0", baud=<baud>)
The baud rate is necessary for serial ports because it determines the speed that data should be transferred at, in bits per second. The standard baud rates can be seen here, with the ones that are most commonly used bolded.
Example:
Opening a port at /dev/ttyUSB0 with baud rate 115200:
import com_server
conn = com_server.Connection(port="/dev/ttyUSB0", baud=115200)
115200 and 9600 are the ones most commonly used with Arduino using Serial.begin():
void setup(){
Serial.begin(9600);
// setup code
}
void loop(){
// loop code
}
It is always recommended to use a timeout. The default timeout is 1 second. Below is an example of a port opened at /dev/ttyUSB0 with baud rate 115200 and a timeout of 2 seconds:
import com_server
conn = com_server.Connection(port="/dev/ttyUSB0", baud=115200, timeout=2)
Multiple ports
If you want to try multiple ports, then you can put them as arguments like this:
import com_server
conn = com_server.Connection(115200, "/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyACM0", "/dev/ttyACM1", timeout=1, send_interval=1)
In this example, the program will try to connect to /dev/ttyUSB0 with baud rate 115200. If that fails, then it will try to connect to /dev/ttyUSB1, and so on. It will establish a connection with the first port that succeeds.
Connecting and disconnecting
This is when the object actually connects to the serial port. When this happens, it spawns a thread called the IO thread which handles sending and receiving data to and from the serial port.
When disconnecting the object, it will delete the IO thread and reset its IO variables, including the send queue and the list of received data.
There are two ways of doing this:
- Using the
connectanddisconnectmethods
conn.connect()
# do stuff with the connection object
conn.disconnect()
Important: Remember to always call the disconnect() method if the object will not be used anymore, or there may be stray threads in your program.
- Using a context manager:
with com_server.Connection(port="/dev/ttyUSB0", baud=115200) as conn:
# do stuff with connection object
The context manager will connect the object automatically as it enters and disconnect it automatically when it exits, so you do not have to worry about calling disconnect() before deleting the object.
To check if the connection object is currently connected or not, use the connected property:
print(conn.connected)
Reconnecting
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.
with Connection(...) as conn:
while True:
# It is recommeded to put operations in try-except because
# methods may raise ConnectException if the device disconnects
# while in the middle of the loop.
try:
while conn.connected:
# -------------------------------
# do things with connection here
# -------------------------------
time.sleep(0.01) # recommeded to delay in main thread if in loop
# in case it exits without errors
conn.reconnect(timeout=None)
except ConnectException:
# ConnectException could be thrown if it is disconnected in the middle
# of the loop, right before a send() or receive() method call.
# If timeout is given, then it will try to reconnect within that timeout
# and if it is not reconnected, then it will exit and return False.
#
# If no timeout is given, then it will try to reconnect indefinitely.
conn.reconnect(timeout=None)
Sending
To send data to the serial port, use the send() method. Put the data you want to send as arguments inside the send method.
# will send "a b c 1 2 3\r\n" without the quotations
conn.send("a", "b", "c", 1, 2, 3)
To control what your arguments should be concatenated by, use the concatenate option:
# will send "a;b;c;1;2;3\r\n" without the quotations
conn.send("a", "b", "c", 1, 2, 3, concatenate=';')
By default the concatenate option is a whitespace character.
To control what should be appended at the end before sending, use the ending option:
# will send "a;b;c;1;2;3\n" without the quotations
conn.send("a", "b", "c", 1, 2, 3, ending='\n')
By default the ending option is a carraige return: \r\n.
Note that sending data too quickly will cause this function to return immediately without sending. This is because it takes time to send things through the serial port and sending data too rapidly may lead to data loss. To customize the interval between sending, use the send_interval option in the constructor of this class. The default is 1 second between sending.
If the timeout is reached, the method will return.
Receiving
To get the data that came in from the serial port most recently, use the receive() method:
rcv = conn.receive() # gets bytes object
rcv = conn.receive_str() # gets string object
To get the next most data that came in from the serial port:
rcv = conn.receive(num_before=1) # gets bytes object
rcv = conn.receive_str(num_before=1) # gets string object
Increase the value of num_before to get the next most recent, next next more recent, etc. received data.
If there has not been any received data, then returns None.
To wait for data to be received, use the get() method:
rcv = conn.get(bytes) # gets first bytes after the method is called
rcv = conn.get(str) # gets first string after method is called
If no data gets received before the timeout is reached, then the method will return None.
Full example
This example sends something to the serial port and then prints the first data that comes from the serial port after sending.
import time
from com_server import Connection
conn = Connection(port="/dev/ttyUSB0", baud=115200)
conn.connect()
while conn.connected:
conn.send("Sending something", ending="\n")
received = conn.get(str)
print(received)
time.sleep(1) # wait one second between sending
conn.disconnect()
Creating a RestApiHandler class
This is the class that handles setting up the server that hosts an API which interacts with the serial ports.
This class uses Flask and flask_restful's API class as its back end. More about them here and here.
The example below shows how to create a RestApiHandler object and run it as a Flask development server on https://0.0.0.0:8080:
import flask
import flask_restful
from com_server import Connection, RestApiHandler
conn = Connection(port="/dev/ttyUSB0", baud=115200)
handler = RestApiHandler(conn)
# automatically connects conn
handler.run_dev(host="0.0.0.0", port=8080)
conn.disconnect()
Adding built-in endpoints
See the server API docs.
Note that when using builtin endpoints, you cannot use those endpoints or the program will raise a EndpointExistsException.
Adding custom endpoints
To add custom endpoints, use the add_endpoint decorator above a class. The class has to extend ConnectionResource and should have functions similar to the classes of flask_restful. See here for more info. To use the connection object, you can use the self.conn attribute of the class.
The example below adds a route at /hello_world, sends "Hello world\n" to the serial port, then returns {"Hello": "world"} when there is a GET request.
from com_server import Connection, RestApiHandler, ConnectionResource
from com_server.api import Builtins
conn = Connection(port="/dev/ttyUSB0", baud=115200)
handler = RestApiHandler(conn)
Builtins(handler)
@handler.add_endpoint("/hello_world")
class HelloWorldEndpoint(ConnectionResource):
def get(self):
self.conn.send("Hello", "world", ending='\n')
return {"Hello": "world"}
handler.run_dev(host="0.0.0.0", port=8080)
conn.disconnect()
Using the endpoints
Assuming that you're using the com_server command to run or has_register_recall is False when initializing your RestApiHandler, then you need to access the /register and /recall to ensure to the program that there is only one IP/device connecting (see Recommended Use), unless has_register_recall is False.
However, if you visit an endpoint added using add_endpoint() while that endpoint or another endpoint also added using add_endpoint() is being used, it will respond with 503 Service Unavailable.
When you are beginning to use the serial port, send a GET request to /register, and when you are finished, send a GET request to /recall. There are no arguments needed.
This library comes with many built-in routes to interact with the serial port. See the Server API for more information on how to use them.