-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Gave each camera its own Isolate (#6)
* bug in progress first commit * Isolate test * Fixed .value --> .address * made CameraIsolate Class, need to change VideoServer next * sdaasd sdasd wq Revert "made CameraIsolate Class, need to change VideoServer next" This reverts commit 5199e9f. * working partially * isolate cameras running somewhat buggy * bug fixes * enhancements * Better logging, docs * Moved pubspec overrides to gitignore * Updated pubspec links * Migrated to newer logging version --------- Co-authored-by: aidanahram <[email protected]>
- Loading branch information
1 parent
aa0ac56
commit ccbdc2f
Showing
14 changed files
with
357 additions
and
210 deletions.
There are no files selected for viewing
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 |
---|---|---|
@@ -1,3 +1,5 @@ | ||
# https://dart.dev/guides/libraries/private-files | ||
# Created by `dart pub` | ||
.dart_tool/ | ||
|
||
pubspec_overrides.yaml |
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,58 @@ | ||
// ignore_for_file: avoid_print | ||
|
||
import "package:typed_isolate/typed_isolate.dart"; | ||
|
||
class NumberSender extends IsolateParent<int, int> { | ||
@override | ||
Future<void> run() async { | ||
print("Opening parent..."); | ||
print("Sending: 1"); | ||
//send(1, "braces"); | ||
send(1, "brackets"); | ||
await Future<void>.delayed(const Duration(seconds: 1)); | ||
|
||
print("Sending: 2"); | ||
send(2, "brackets"); | ||
//send(2, "braces"); | ||
await Future<void>.delayed(const Duration(seconds: 1)); | ||
|
||
print("Sending: 3"); | ||
//send(3, "braces"); | ||
send(3, "brackets"); | ||
await Future<void>.delayed(const Duration(seconds: 1)); | ||
} | ||
|
||
@override | ||
void onData(int data) => print("Got: $data"); | ||
} | ||
|
||
class NumberConverter extends IsolateChild<int, int> { | ||
NumberConverter() : super(id: "brackets"); | ||
|
||
@override | ||
void run() => print("Opening child..."); | ||
|
||
@override | ||
void onData(int data) => send(data + 5); | ||
} | ||
|
||
class NumberConverter2 extends IsolateChild<String, int> { | ||
NumberConverter2() : super(id: "braces"); | ||
|
||
@override | ||
void run() => print("Opening child..."); | ||
|
||
@override | ||
void onData(int data) => send("{$data}"); | ||
} | ||
|
||
void main() async { | ||
final parent = NumberSender(); | ||
final isolate1 = await parent.spawn(NumberConverter()); | ||
//final isolate2 = await parent.spawn(NumberConverter2()); | ||
await Future<void>.delayed(const Duration(seconds: 1)); | ||
await parent.run(); | ||
isolate1.kill(); | ||
//isolate2.kill(); | ||
parent.close(); | ||
} |
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,66 @@ | ||
// // ignore_for_file: avoid_print | ||
|
||
// import "dart:ffi"; | ||
// import "package:typed_isolate/typed_isolate.dart"; | ||
// import "package:opencv_ffi/opencv_ffi.dart"; | ||
|
||
// class ImageData { | ||
// final int address; | ||
// final int length; | ||
// const ImageData({required this.address, required this.length}); | ||
// } | ||
|
||
// class Parent extends IsolateParent<ImageData, void> { | ||
// @override | ||
// void onData(void data) { } | ||
|
||
// @override | ||
// Future<void> run() async { | ||
// print("Opening camera..."); | ||
// final camera = Camera.fromIndex(0); | ||
// if (!camera.isOpened) { | ||
// print("Could not open camera"); | ||
// return; | ||
// } | ||
// print("Camera opened"); | ||
// for (int i = 0; i < 10; i++) { | ||
// final image = camera.getJpg(); | ||
// if (image == null) { | ||
// print("Could not read frame"); | ||
// continue; | ||
// } | ||
// final address = image.pointer.address; | ||
// final length = image.data.length; | ||
// print("Got an image at ($address) with $length bytes"); | ||
// final data = ImageData(address: image.pointer.address, length: length); | ||
// send(data, 0); | ||
// } | ||
// } | ||
// } | ||
|
||
// class Child extends IsolateChild<void, ImageData> { | ||
// Child({required super.id}); | ||
|
||
// @override | ||
// void onData(ImageData data) { | ||
// final pointer = Pointer.fromAddress(data.address).cast<Uint8>(); | ||
// final image = OpenCVImage(pointer: pointer, length: data.length); | ||
// print(" Child received pointer ${image.pointer.address} w/ ${image.data.length} bytes"); | ||
// } | ||
|
||
// @override | ||
// void run() { } | ||
// } | ||
|
||
// void main() async { | ||
// final parent = Parent(); | ||
// print("Creating parent isolate... "); | ||
// final child = Child(id: 0); | ||
// final isolate = await parent.spawn(child); | ||
// print("Waiting for child to spawn..."); | ||
// await Future<void>.delayed(const Duration(seconds: 1)); | ||
// print("Running main isolate..."); | ||
// await parent.run(); | ||
// parent.close(); | ||
// isolate.kill(); | ||
// } |
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,115 @@ | ||
import "dart:async"; | ||
|
||
import "package:opencv_ffi/opencv_ffi.dart"; | ||
import "package:typed_isolate/typed_isolate.dart"; | ||
import "package:burt_network/burt_network.dart"; | ||
import "collection.dart"; | ||
import "frame.dart"; | ||
import "periodic_timer.dart"; | ||
|
||
/// An isolate that is spawned to manage one camera. | ||
/// | ||
/// This class accepts [VideoCommand]s and calls [updateDetails] with the newly-received details. | ||
/// When a frame is read, instead of sending the [VideoData], this class sends only the pointer | ||
/// to the [OpenCVImage] via the [FrameData] class, and the image is read by the parent isolate. | ||
class CameraIsolate extends IsolateChild<FrameData, VideoCommand>{ | ||
/// The native camera object from OpenCV. | ||
late final Camera camera; | ||
/// Holds the current details of the camera. | ||
final CameraDetails details; | ||
|
||
/// A timer to periodically send the camera status to the dashboard. | ||
Timer? statusTimer; | ||
/// A timer to read from the camera at an FPS given by [details]. | ||
PeriodicTimer? frameTimer; | ||
/// A timer to log out the [fpsCount] every 5 seconds using [LoggerUtils.debug]. | ||
Timer? fpsTimer; | ||
/// Records how many FPS this camera is actually running at. | ||
int fpsCount = 0; | ||
|
||
/// The log level at which this isolate should be reporting. | ||
LogLevel logLevel; | ||
|
||
/// Creates a new manager for the given camera and default details. | ||
CameraIsolate({required this.details, required this.logLevel}) : super(id: details.name); | ||
|
||
/// The name of this camera (where it is on the rover). | ||
CameraName get name => details.name; | ||
|
||
/// Sends the current status to the dashboard (with an empty frame). | ||
void sendStatus([_]) => send(FrameData(details: details, address: 0, length: 0)); | ||
|
||
@override | ||
Future<void> run() async { | ||
Logger.level = logLevel; | ||
logger.debug("Initializing camera: $name"); | ||
camera = getCamera(name); | ||
statusTimer = Timer.periodic(const Duration(seconds: 5), sendStatus); | ||
if (!camera.isOpened) { | ||
logger.warning("Camera $name is not connected"); | ||
updateDetails(CameraDetails(status: CameraStatus.CAMERA_DISCONNECTED)); | ||
} | ||
start(); | ||
} | ||
|
||
@override | ||
void onData(VideoCommand data) => updateDetails(data.details); | ||
|
||
/// Updates the camera's [details], which will take effect on the next [sendFrame] call. | ||
void updateDetails(CameraDetails newDetails) { | ||
details.mergeFromMessage(newDetails); | ||
stop(); | ||
start(); | ||
} | ||
|
||
/// Disposes of the camera and the timers. | ||
void dispose() { | ||
camera.dispose(); | ||
frameTimer?.cancel(); | ||
fpsTimer?.cancel(); | ||
statusTimer?.cancel(); | ||
logger.info("Disposed camera $name"); | ||
} | ||
|
||
/// Starts the camera and timers. | ||
void start() { | ||
if (details.status != CameraStatus.CAMERA_ENABLED) return; | ||
logger.debug("Starting camera $name. Status=${details.status}"); | ||
final interval = details.fps == 0 ? Duration.zero : Duration(milliseconds: 1000 ~/ details.fps); | ||
frameTimer = PeriodicTimer(interval, sendFrame); | ||
fpsTimer = Timer.periodic(const Duration(seconds: 5), (_) { | ||
logger.trace("Camera $name sent ${fpsCount ~/ 5} frames"); | ||
fpsCount = 0; | ||
}); | ||
} | ||
|
||
/// Cancels all timers and stops reading the camera. | ||
void stop() { | ||
logger.debug("Stopping camera $name"); | ||
frameTimer?.cancel(); | ||
fpsTimer?.cancel(); | ||
} | ||
|
||
/// Reads a frame from the camera and sends it to the dashboard. | ||
/// | ||
/// Checks for multiple errors along the way: | ||
/// - If the camera does not respond, alerts the dashboard | ||
/// - If the frame is too large, reduces the quality (increases JPG compression) | ||
/// - If the quality is already low, alerts the dashboard | ||
Future<void> sendFrame() async { | ||
final frame = camera.getJpg(quality: details.quality); | ||
if (frame == null) { // Error getting the frame | ||
logger.warning("Camera $name didn't respond"); | ||
updateDetails(CameraDetails(status: CameraStatus.CAMERA_NOT_RESPONDING)); | ||
} else if (frame.data.length < 60000) { // Frame can be sent | ||
send(FrameData(address: frame.pointer.address, length: frame.data.length, details: details)); | ||
fpsCount++; | ||
} else if (details.quality > 25) { // Frame too large, try lowering quality | ||
logger.debug("Lowering quality for $name from ${details.quality}"); | ||
updateDetails(CameraDetails(quality: details.quality - 1)); | ||
} else { // Frame too large, cannot lower quality anymore | ||
logger.warning("$name's frames are too large (${frame.data.length} bytes, quality=${details.quality})"); | ||
updateDetails(CameraDetails(status: CameraStatus.FRAME_TOO_LARGE)); | ||
} | ||
} | ||
} |
Oops, something went wrong.