-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add shared serial code to the library (#5)
- Loading branch information
1 parent
8499c7d
commit cb34671
Showing
27 changed files
with
568 additions
and
67 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Levi has to review everything | ||
|
||
* @Levi-Lesches |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import "dart:async"; | ||
import "dart:typed_data"; | ||
|
||
import "package:burt_network/burt_network.dart"; | ||
|
||
/// A wrapper around the `package:libserialport` library. | ||
/// | ||
/// - Check [DelegateSerialPort.allPorts] for a list of all available ports. | ||
/// - Call [init] to open the port | ||
/// - Use [write] to write bytes to the port. Strings are not supported | ||
/// - Listen to [stream] to get incoming data | ||
/// - Call [dispose] to close the port | ||
class SerialDevice extends Service { | ||
/// The port to connect to. | ||
final String portName; | ||
/// How often to read from the port. | ||
final Duration readInterval; | ||
/// The underlying connection to the serial port. | ||
final SerialPortInterface _port; | ||
/// The logger to use | ||
final BurtLogger logger; | ||
|
||
/// A timer to periodically read from the port (see [readBytes]). | ||
Timer? _timer; | ||
|
||
/// The controller for [stream]. | ||
final _controller = StreamController<Uint8List>.broadcast(); | ||
|
||
/// Manages a connection to a serial device. | ||
SerialDevice({ | ||
required this.portName, | ||
required this.readInterval, | ||
required this.logger, | ||
}) : _port = SerialPortInterface.factory(portName); | ||
|
||
/// Whether the port is open (ie, the device is connected). | ||
bool get isOpen => _port.isOpen; | ||
|
||
@override | ||
Future<bool> init() async { | ||
try { | ||
return _port.init(); | ||
} catch (error) { | ||
return false; | ||
} | ||
} | ||
|
||
/// Starts listening to data sent over the serial port via [stream]. | ||
void startListening() => _timer = Timer.periodic(readInterval, _listenForBytes); | ||
|
||
/// Stops listening to the serial port. | ||
void stopListening() => _timer?.cancel(); | ||
|
||
/// Reads bytes from the port. If [count] is provided, only reads that number of bytes. | ||
Uint8List readBytes([int? count]) { | ||
try { | ||
return _port.read(count ?? _port.bytesAvailable); | ||
} catch (error) { | ||
logger.error("Could not read from serial port $portName:\n $error"); | ||
return Uint8List(0); | ||
} | ||
} | ||
|
||
/// Reads any data from the port and adds it to the [stream]. | ||
void _listenForBytes(_) { | ||
try { | ||
final bytes = readBytes(); | ||
if (bytes.isEmpty) return; | ||
_controller.add(bytes); | ||
} catch (error) { | ||
logger.critical("Could not read $portName", body: error.toString()); | ||
dispose(); | ||
} | ||
} | ||
|
||
@override | ||
Future<void> dispose() async { | ||
_timer?.cancel(); | ||
await _port.dispose(); | ||
await _controller.close(); | ||
} | ||
|
||
/// Writes data to the port. | ||
void write(Uint8List data) { | ||
if (!_port.isOpen) return; | ||
try { | ||
_port.write(data); | ||
} catch (error) { | ||
logger.warning("Could not write data to port $portName"); | ||
} | ||
} | ||
|
||
/// All incoming bytes coming from the port. | ||
Stream<Uint8List> get stream => _controller.stream; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import "dart:typed_data"; | ||
|
||
import "package:protobuf/protobuf.dart"; | ||
|
||
import "package:burt_network/burt_network.dart"; | ||
|
||
/// Represents a firmware device connected over Serial. | ||
/// | ||
/// This device starts with an unknown [device]. Calling [init] starts a handshake with the device | ||
/// that identifies it. If the handshake fails, [isReady] will be false. Calling [dispose] will | ||
/// reset the device and close the connection. | ||
class BurtFirmwareSerial extends Service { | ||
/// The interval to read serial data at. | ||
static const readInterval = Duration(milliseconds: 100); | ||
/// How long it should take for a firmware device to respond to a handshake. | ||
static const handshakeDelay = Duration(milliseconds: 200); | ||
/// The reset code to send to a firmware device. | ||
static final resetCode = Uint8List.fromList([0, 0, 0, 0]); | ||
|
||
/// The name of this device. | ||
Device device = Device.FIRMWARE; | ||
|
||
SerialDevice? _serial; | ||
|
||
/// The port this device is attached to. | ||
final String port; | ||
/// The logger to use. | ||
final BurtLogger logger; | ||
/// Creates a firmware device at the given serial port. | ||
BurtFirmwareSerial({required this.port, required this.logger}); | ||
|
||
/// The stream of incoming data. | ||
Stream<Uint8List>? get stream => _serial?.stream; | ||
|
||
/// Whether this device has passed the handshake. | ||
bool get isReady => device != Device.FIRMWARE; | ||
|
||
@override | ||
Future<bool> init() async { | ||
// Open the port | ||
_serial = SerialDevice(portName: port, readInterval: readInterval, logger: logger); | ||
if (!await _serial!.init()) { | ||
logger.warning("Could not open firmware device on port $port"); | ||
return false; | ||
} | ||
|
||
// Execute the handshake | ||
if (!_reset()) logger.warning("The Teensy on port $port failed to reset"); | ||
if (!await _sendHandshake()) { | ||
logger.warning("Could not connect to Teensy", body: "Device on port $port failed the handshake"); | ||
return false; | ||
} | ||
|
||
logger.info("Connected to the ${device.name} Teensy on port $port"); | ||
_serial!.startListening(); | ||
return true; | ||
} | ||
|
||
/// Sends the handshake to the device and returns whether it was successful. | ||
Future<bool> _sendHandshake() async { | ||
logger.debug("Sending handshake to port $port..."); | ||
final handshake = Connect(sender: Device.SUBSYSTEMS, receiver: Device.FIRMWARE); | ||
_serial!.write(handshake.writeToBuffer()); | ||
await Future<void>.delayed(handshakeDelay); | ||
final response = _serial!.readBytes(4); | ||
if (response.isEmpty) { | ||
logger.trace("Device did not respond"); | ||
return false; | ||
} | ||
try { | ||
final message = Connect.fromBuffer(response); | ||
logger.trace("Device responded with: ${message.toProto3Json()}"); | ||
if (message.receiver != Device.SUBSYSTEMS) return false; | ||
device = message.sender; | ||
return true; | ||
} on InvalidProtocolBufferException { | ||
logger.trace("Device responded with malformed data: $response"); | ||
return false; | ||
} | ||
} | ||
|
||
/// Sends the reset code and returns whether the device confirmed its reset. | ||
bool _reset() { | ||
_serial?.write(resetCode); | ||
final response = _serial?.readBytes( 4); | ||
if (response == null) return false; | ||
if (response.length != 4 || response.any((x) => x != 1)) return false; | ||
logger.info("The ${device.name} Teensy has been reset"); | ||
return true; | ||
} | ||
|
||
/// Sends bytes to the device via Serial. | ||
void sendBytes(List<int> bytes) => _serial?.write(Uint8List.fromList(bytes)); | ||
|
||
/// Resets the device and closes the port. | ||
@override | ||
Future<void> dispose() async { | ||
if (!_reset()) logger.warning("The $device device on port $port did not reset"); | ||
await _serial?.dispose(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import "dart:typed_data"; | ||
|
||
import "package:libserialport/libserialport.dart"; | ||
|
||
import "port_interface.dart"; | ||
|
||
/// A serial port implementation that delegates to [`package:libserialport`](https://pub.dev/packages/libserialport) | ||
class DelegateSerialPort extends SerialPortInterface { | ||
/// A list of all available ports on the device. | ||
static List<String> allPorts = SerialPort.availablePorts; | ||
|
||
SerialPort? _delegate; | ||
|
||
/// Creates a serial port that delegates to the `libserialport` package. | ||
DelegateSerialPort(super.portName); | ||
|
||
@override | ||
bool get isOpen => _delegate?.isOpen ?? false; | ||
|
||
@override | ||
Future<bool> init() async { | ||
try { | ||
_delegate = SerialPort(portName); | ||
return _delegate!.openReadWrite(); | ||
} catch (error) { | ||
return false; | ||
} | ||
} | ||
|
||
@override | ||
int get bytesAvailable => _delegate?.bytesAvailable ?? 0; | ||
|
||
@override | ||
Uint8List read(int count) => _delegate?.read(count) ?? Uint8List.fromList([]); | ||
|
||
@override | ||
void write(Uint8List bytes) => _delegate?.write(bytes); | ||
|
||
@override | ||
Future<void> dispose() async { | ||
if (!isOpen) return; | ||
_delegate?.close(); | ||
_delegate?.dispose(); | ||
} | ||
} |
Oops, something went wrong.