initial commit
Some checks failed
xiao_pet_tracker / semantic-pull-request (push) Has been cancelled
xiao_pet_tracker / build (push) Has been cancelled
xiao_pet_tracker / spell-check (push) Has been cancelled

This commit is contained in:
2025-02-23 20:50:34 +01:00
commit 2fe59fee0d
360 changed files with 14384 additions and 0 deletions

View File

@@ -0,0 +1,280 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart';
import 'package:uuid/uuid.dart';
import 'package:xiao_pet_tracker/bootstrap.dart';
import 'package:xiao_pet_tracker/objectbox.dart';
import 'package:xiao_pet_tracker/objectbox.g.dart';
import 'package:xiao_pet_tracker/xiao_connector/models/capture_box.dart';
import 'package:xiao_pet_tracker/xiao_connector/models/capture_point.dart';
import 'package:xiao_pet_tracker/xiao_connector/utils/utils.dart';
part 'xiao_connector_state.dart';
class XiaoConnectorCubit extends Cubit<XiaoConnectorState> {
XiaoConnectorCubit() : super(const XiaoConnectorState());
late BluetoothDevice device;
List<BluetoothService> _services = [];
late BluetoothCharacteristic _senseService;
late StreamSubscription<List<int>> dataCapturingSubscription;
final ObjectBox _objectBox = getIt<ObjectBox>();
late final Box<CapturePoint> _capturePointsBox;
late final Box<CaptureBox> _captureBoxes;
bool gotRotation = false;
bool gotAcceleration = false;
bool gotTimeStamp = false;
int _rotX = 0;
int _rotY = 0;
int _rotZ = 0;
int _accX = 0;
int _accY = 0;
int _accZ = 0;
int _millisecondsSinceEpochSend = 0;
String _captureType = '';
String _uuid = '';
bool isRecording = false;
Future<void> init() async {
_capturePointsBox = _objectBox.store.box<CapturePoint>();
_captureBoxes = _objectBox.store.box<CaptureBox>();
}
Future<void> connect() async {
emit(state.copyWith(status: XiaoConnectorStatus.loading));
try {
// scan for devices for 15 seconds
await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
} catch (e) {
emit(state.copyWith(status: XiaoConnectorStatus.failure));
throw Exception('Start Scan Error: $e');
}
FlutterBluePlus.scanResults.listen(
(results) async {
for (final r in results) {
if (kDebugMode) {
print(r);
}
// set the xiao sense name
if (r.device.advName == 'Go Bluetooth 2') {
await FlutterBluePlus.stopScan();
device = r.device;
await device.connect();
try {
_services = await device.discoverServices();
} catch (e) {
emit(state.copyWith(status: XiaoConnectorStatus.failure));
throw Exception('Discover Services Error: $e');
}
device.connectionState
.listen((BluetoothConnectionState blueState) async {
if (blueState == BluetoothConnectionState.disconnected) {
debugPrint('DISCONNECTED!');
emit(state.copyWith(status: XiaoConnectorStatus.initial));
}
});
// device.cancelWhenDisconnected(deviceConnectionStream);
emit(state.copyWith(status: XiaoConnectorStatus.connected));
}
}
},
onDone: () {
debugPrint('DONE');
emit(state.copyWith(status: XiaoConnectorStatus.initial));
},
);
}
Future<void> setCapturingOn() async {
final senseService = _services
.where(
(s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
)
.first
.characteristics
.where(
(c) =>
c.characteristicUuid ==
Guid('63617074-7572-696E-6753-657276696365'),
)
.first;
await senseService.write([1]);
}
Uint8List int64BigEndianBytes(int value) =>
Uint8List(8)..buffer.asByteData().setInt64(0, value);
Future<void> setTime(int millisSinceEpoch) async {
final senseService = _services
.where(
(s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
)
.first
.characteristics
.where(
(c) =>
c.characteristicUuid ==
Guid('756E6978-5469-6D65-5374-616D70527374'),
)
.first;
final List<int> bytes = int64BigEndianBytes(millisSinceEpoch);
await senseService.write(bytes);
}
Future<void> startCapturing() async {
_senseService = _services
.where(
(s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
)
.first
.characteristics
.where(
(c) =>
c.characteristicUuid ==
Guid('61636365-6c65-7261-7469-6f6e44617461'),
)
.first;
dataCapturingSubscription = _senseService.onValueReceived.listen((value) {
// rotation
if (value.last == 1) {
_rotX = fromBytesToInt32(value[0], value[1], value[2], value[3]);
_rotY = fromBytesToInt32(value[4], value[5], value[6], value[7]);
_rotZ = fromBytesToInt32(value[8], value[9], value[10], value[11]);
gotRotation = true;
}
// acceleration
if (value.last == 2) {
_accX = fromBytesToInt32(value[0], value[1], value[2], value[3]);
_accY = fromBytesToInt32(value[4], value[5], value[6], value[7]);
_accZ = fromBytesToInt32(value[8], value[9], value[10], value[11]);
gotAcceleration = true;
}
if (value.last == 3) {
final timeStamp = fromBytesToInt64(
value[0],
value[1],
value[2],
value[3],
value[4],
value[5],
value[6],
value[7],
);
_millisecondsSinceEpochSend = timeStamp;
gotTimeStamp = true;
}
if (gotAcceleration && gotRotation && gotTimeStamp) {
final capturePoint = CapturePoint(
type: _captureType,
uuid: _uuid,
rotationX: _rotX,
rotationY: _rotY,
rotationZ: _rotZ,
accelerationX: _accX,
accelerationY: _accY,
accelerationZ: _accZ,
millisecondsSinceEpochReceived:
DateTime.now().toUtc().millisecondsSinceEpoch,
millisecondsSinceEpochSend: _millisecondsSinceEpochSend,
);
emit(state.copyWith(lastCapturedPoint: capturePoint));
if (isRecording) {
_writeToObjectBox(capturePoint: capturePoint);
}
gotAcceleration = false;
gotRotation = false;
gotTimeStamp = false;
}
});
await setCapturingOn();
await setTime(DateTime.now().millisecondsSinceEpoch);
device.cancelWhenDisconnected(dataCapturingSubscription);
await _senseService.setNotifyValue(true);
emit(state.copyWith(status: XiaoConnectorStatus.capturing));
}
Future<void> stopCapturing() async {
await dataCapturingSubscription.cancel();
await _senseService.setNotifyValue(false);
emit(state.copyWith(status: XiaoConnectorStatus.connected));
}
Future<void> disconnectDevice() async {
await device.disconnect();
_services = [];
emit(state.copyWith(status: XiaoConnectorStatus.initial));
}
void toggleRecording({required String captureType}) {
isRecording = !isRecording;
if (isRecording) {
_captureType = captureType;
_uuid = const Uuid().v4();
_captureBoxes.put(
CaptureBox(
type: _captureType,
uuid: _uuid,
startTime: DateTime.now(),
),
);
}
}
void _writeToObjectBox({
required CapturePoint capturePoint,
}) {
_capturePointsBox.put(capturePoint);
}
List<CapturePoint> getCapturePointsOfUuid(String uuid) {
final query =
_capturePointsBox.query(CapturePoint_.uuid.equals(uuid)).build();
final points = query.find();
return points;
}
void deleteRecording(String uuid) {
_capturePointsBox.query(CapturePoint_.uuid.equals(uuid)).build().remove();
_captureBoxes.query(CaptureBox_.uuid.equals(uuid)).build().remove();
}
List<CaptureBox> getAllCaptureBoxes() {
final query = (_captureBoxes.query()
..order(CaptureBox_.startTime, flags: Order.descending))
.build();
final boxes = query.find();
return boxes;
}
List<CapturePoint> getAllCapturePoints() => _capturePointsBox.getAll();
}

View File

@@ -0,0 +1,40 @@
part of 'xiao_connector_cubit.dart';
enum XiaoConnectorStatus {
initial,
loading,
connected,
capturing,
failure,
}
extension XiaoConnectorStatusX on XiaoConnectorStatus {
bool get isInitial => this == XiaoConnectorStatus.initial;
bool get isLoading => this == XiaoConnectorStatus.loading;
bool get isConnected => this == XiaoConnectorStatus.connected;
bool get isCapturing => this == XiaoConnectorStatus.capturing;
bool get isFailure => this == XiaoConnectorStatus.failure;
}
final class XiaoConnectorState extends Equatable {
const XiaoConnectorState({
this.status = XiaoConnectorStatus.initial,
this.lastCapturedPoint,
});
final XiaoConnectorStatus status;
final CapturePoint? lastCapturedPoint;
XiaoConnectorState copyWith({
XiaoConnectorStatus? status,
CapturePoint? lastCapturedPoint,
}) {
return XiaoConnectorState(
status: status ?? this.status,
lastCapturedPoint: lastCapturedPoint ?? this.lastCapturedPoint,
);
}
@override
List<Object?> get props => [status, lastCapturedPoint];
}