Skip to content

Commit f4182cb

Browse files
author
Ezra Boley
committed
Added the ability to switch between different board configs
1 parent e46b412 commit f4182cb

7 files changed

Lines changed: 75 additions & 34 deletions

File tree

src/devlprd/DaemonState.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import threading
44
import collections as coll
55

6+
from devlprd.config import Board
7+
68
from .filtering import ButterworthFilter, BUTTER8_45_55_NOTCH, BUTTER8_55_65_NOTCH
79
from pydevlpr_protocol import wrap_packet, DataTopic, DaemonSocket
810
from typing import Deque, Dict, List
@@ -11,8 +13,8 @@ class DaemonState:
1113
"""Thread protected shared state for the Daemon. It manages all of the connections and data topics."""
1214

1315
BUFFER_SIZE = 32 # Somewhat arbitrary, can be fine tuned to provide different results with data processing (e.g. changes response time vs smoothing).
14-
def __init__(self, event_loop):
15-
16+
def __init__(self, event_loop, board: Board):
17+
self.PIN_NUMS = list(range(0, board['NUM PINS']))
1618
self.SUBS: Dict[str, List[DaemonSocket]] = dict()
1719
self.SERIAL_DATA: Dict[int, Deque[int]] = dict()
1820
self.SERIAL_DATA_RUNNING_SUMS: Dict[int, float] = dict() # for window avg
@@ -28,13 +30,13 @@ def init_butterworth_filters(self):
2830
self.BUTTER60_FILTS = dict()
2931
self.BUTTER50_FILTS = dict()
3032
# go through all possible pins and create a new one
31-
for pin in [0, 1, 2, 3, 4, 5]:
33+
for pin in self.PIN_NUMS:
3234
self.BUTTER60_FILTS[pin] = ButterworthFilter(BUTTER8_55_65_NOTCH)
3335
self.BUTTER50_FILTS[pin] = ButterworthFilter(BUTTER8_45_55_NOTCH)
3436

3537
def init_data_buffers(self):
3638
# no need to save space, just make buffer objects ahead
37-
for pin in [0, 1, 2, 3, 4, 5]:
39+
for pin in self.PIN_NUMS:
3840
# just create the deque and throw an initial 0 in there for now
3941
self.SERIAL_DATA[pin] = coll.deque(maxlen=self.BUFFER_SIZE)
4042
self.SERIAL_DATA[pin].appendleft(0)

src/devlprd/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
from .daemon import (
22
DaemonController
3+
)
4+
5+
from .config import (
6+
get_board_ids
37
)

src/devlprd/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .daemon import DaemonController
22

33
if __name__ == "__main__":
4-
controller = DaemonController()
4+
controller = DaemonController('DEVLPR')
55
controller.start(True)
66

src/devlprd/config.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,23 @@
1+
from typing import Dict, List, Union
2+
3+
Board = Dict[str, Union[str, int]]
4+
15
CONFIG = {
2-
'BAUD': 2000000,
36
'ADDRESS': ("localhost", 8765) # (Address/IP, Port)
4-
}
7+
}
8+
9+
BOARDS: Dict[str, Board] = {
10+
'DEVLPR': {
11+
'BAUD': 2_000_000,
12+
'NAME': 'arduino',
13+
'NUM PINS': 6
14+
},
15+
'Neuron': {
16+
'BAUD': 115_200,
17+
'NAME': 'neuron',
18+
'NUM PINS': 8
19+
}
20+
}
21+
22+
def get_board_ids() -> List[str]:
23+
return list(BOARDS.keys())

src/devlprd/daemon.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
11
import asyncio
22
import multiprocessing as mp
33
import logging
4+
from typing import Optional
45
from pydevlpr_protocol import DataFormatException, unwrap_packet, PacketType, DaemonSocket
56

67
from .serif import DevlprSerif
78
from .DaemonState import DaemonState
8-
from .config import CONFIG
9+
from .config import BOARDS, Board, get_board_ids, CONFIG
910

1011
server = None
11-
state: DaemonState = None # Make sure to pass this to any other threads that need access to shared state
12-
devlpr_serial: DevlprSerif = None
12+
state: Optional[DaemonState] = None # Make sure to pass this to any other threads that need access to shared state
13+
devlpr_serial: Optional[DevlprSerif] = None
1314

1415
logging.basicConfig(level=logging.INFO)
1516

1617
class DaemonController:
18+
def __init__(self, board_id: str) -> None:
19+
try:
20+
self.board = BOARDS[board_id]
21+
except KeyError:
22+
logging.warning("Invalid board ID, try get_board_ids() for options")
23+
logging.info('Assuming DEVLPR')
24+
self.board = BOARDS['DEVLPR']
1725

1826
def start(self, block=False):
19-
self.p = mp.Process(target=main)
27+
self.p = mp.Process(target=main, args=(self.board,))
2028
self.p.start()
2129
if block:
2230
self.p.join()
@@ -25,8 +33,8 @@ def stop(self):
2533
if self.p is not None and self.p.is_alive():
2634
self.p.terminate()
2735

28-
def main():
29-
asyncio.run(startup())
36+
def main(board: Board):
37+
asyncio.run(startup(board))
3038

3139
async def client_accept(sock: DaemonSocket) -> None:
3240
"""Delegate and process incoming messages from a websocket connection."""
@@ -40,8 +48,11 @@ async def client_accept(sock: DaemonSocket) -> None:
4048
except DataFormatException:
4149
continue # Handle an unexpected issue with the packet
4250
if command == PacketType.SUBSCRIBE:
43-
logging.info("Sub to {}".format(data))
44-
state.subscribe(sock, data)
51+
logging.info("Subscribing to {}".format(data))
52+
try:
53+
state.subscribe(sock, data)
54+
except AttributeError:
55+
logging.error("Failed to subscribe, Daemon State is None")
4556

4657
async def client_handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
4758
"""Main function for socket connections. Holds the connection until the other side disconnects."""
@@ -52,10 +63,12 @@ async def client_handler(reader: asyncio.StreamReader, writer: asyncio.StreamWri
5263
await client_accept(dsock)
5364
finally:
5465
logging.info("Disconnected from {0}".format(dsock.get_remote_address()))
55-
state.unsubscribe_all(dsock)
66+
try:
67+
state.unsubscribe_all(dsock)
68+
except AttributeError:
69+
logging.error("Failed to unsubscribe_all, Daemon State is None")
5670

57-
58-
async def startup() -> None:
71+
async def startup(board: Board) -> None:
5972
"""Initiallizes both the serial connection and the socket server. It then just hangs until everything is done internally before cleaning up."""
6073

6174
global server
@@ -64,17 +77,16 @@ async def startup() -> None:
6477
# we'll want the asyncio event loop for subscriptions, some processing, and publishing
6578
event_loop = asyncio.get_running_loop()
6679
# the DaemonState requests publishes upon state change, so needs to know the event loop
67-
state = DaemonState(event_loop)
80+
state = DaemonState(event_loop, board)
6881
# we initialize our serial connection, which is managed on a separate thread
69-
devlpr_serial = DevlprSerif()
82+
devlpr_serial = DevlprSerif(board)
7083
devlpr_serial.init_serial(state)
7184
# start a server, waiting for incoming subscribers to data (pydevlpr and other libraries)
7285
server = await asyncio.start_server(client_handler, CONFIG['ADDRESS'][0], CONFIG['ADDRESS'][1])
7386
try:
7487
await server.serve_forever()
7588
except asyncio.exceptions.CancelledError:
7689
await server.wait_closed()
77-
print("Finish up")
7890
devlpr_serial.deinit_serial()
7991

8092
def shutdown() -> None:
@@ -89,6 +101,8 @@ def shutdown() -> None:
89101
pass
90102
try:
91103
devlpr_serial.deinit_serial()
104+
except AttributeError:
105+
logging.warning("Serial couldn't close because devlpr_serial is already None")
92106
except:
93107
pass # not even sure this is necessary
94108

src/devlprd/serif.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import logging
2+
from typing import Optional
23
import serial
34
import serial.threaded as sthread
45
import serial.tools.list_ports as list_ports
56

67
from pydevlpr_protocol import unpack_serial, DataFormatException
7-
from .config import CONFIG
8+
from .config import CONFIG, BOARDS, Board
89

9-
def find_arduino_port() -> str:
10+
def find_port(board_name: str) -> str:
1011
"""Smart port searching for finding an Arduino.
1112
1213
Parses metadata to figure out where your Arduino is to avoid
@@ -15,20 +16,20 @@ def find_arduino_port() -> str:
1516

1617
port_list = list_ports.comports()
1718
for port in port_list:
18-
if "arduino" in port.description.lower():
19+
if board_name in port.description.lower():
1920
return port.device
2021
return ""
2122

2223

23-
def connect_to_arduino() -> serial.Serial:
24+
def connect_to_board(board: Board) -> serial.Serial:
2425
"""Creates a serial connection to an Arduino if possible."""
2526

26-
port = find_arduino_port()
27+
port = find_port(str(board['NAME']))
2728
if port == "":
28-
logging.warning("No Arduino Found")
29+
logging.warning("No Board Found")
2930
return None
3031
try:
31-
serif = serial.serial_for_url(port, baudrate=CONFIG["BAUD"])
32+
serif = serial.serial_for_url(port, baudrate=board['BAUD'])
3233
except serial.SerialException as e:
3334
logging.error("Failed to open serial port {}: {}".format(port, e))
3435
return None
@@ -37,6 +38,7 @@ def connect_to_arduino() -> serial.Serial:
3738

3839
class DevlprReader(sthread.Packetizer):
3940
"""Extends Packetizer from pyserial threading module, async serial support."""
41+
4042
TERMINATOR = bytes([1]) ## Assumed to be at the end of every message
4143

4244
def __init__(self, daemon_state):
@@ -56,7 +58,6 @@ def connection_made(self, transport: sthread.Protocol) -> None:
5658
# Called on each new packet (packet + TERMINATOR) from the serial port
5759
def handle_packet(self, packet: bytes) -> None:
5860
# Always buffer, custom callbacks are further up the stack
59-
6061
try:
6162
(pin, data) = unpack_serial(packet) # Split into payload and topic
6263
except DataFormatException: # If the packet is invalid
@@ -73,15 +74,16 @@ def connection_lost(self, exc: Exception) -> None:
7374
class DevlprSerif:
7475
"""Devlpr Ser(ial) i(nter)f(ace). Manages the threaded serial data connection with a DEVLPR board."""
7576

76-
def __init__(self) -> None:
77-
self.reader_thread: sthread.ReaderThread = None
78-
self.devlpr_reader: DevlprReader = None
77+
def __init__(self, board: Board) -> None:
78+
self.board = board
79+
self.reader_thread: Optional[sthread.ReaderThread] = None
80+
self.devlpr_reader: Optional[DevlprReader] = None
7981

8082
def init_serial(self, state) -> None:
8183
""" First opens the port, then spins off a watcher thread
8284
so it doesn't block the main path of execution."""
8385

84-
serif = connect_to_arduino()
86+
serif = connect_to_board(self.board)
8587
if serif is None:
8688
return
8789
self.devlpr_reader = DevlprReader(state)

src/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
import devlprd
33

44
if __name__ == "__main__":
5-
controller = devlprd.DaemonController()
5+
controller = devlprd.DaemonController('Neuron')
66
controller.start(True)
77

0 commit comments

Comments
 (0)