Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
beastoin committed Nov 24, 2024
1 parent d75facc commit 14319a0
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 29 deletions.
74 changes: 46 additions & 28 deletions Friend/firmware/firmware_v1.0/src/button.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,21 +214,23 @@ void check_button_level(struct k_work *work_item)
{
//If button is pressed for a long time.......
notify_long_tap();
play_haptic_milli(10);
//Fire the long mode notify and enter a grace period
//turn off herre
if(!from_wakeup)
{
is_off = !is_off;
}
else
{
from_wakeup = false;
}
if (is_off)
{
bt_off();
turnoff_all();
}
// TODO: FIXME
//if(!from_wakeup)
//{
// is_off = !is_off;
//}
//else
//{
// from_wakeup = false;
//}
//if (is_off)
//{
// bt_off();
// turnoff_all();
//}
current_button_state = GRACE;
reset_count();
}
Expand All @@ -254,6 +256,20 @@ void check_button_level(struct k_work *work_item)
else if (inc_count_0 > 10)
{
notify_tap(); //Fire the notify and enter a grace period
if(!from_wakeup)
{
is_off = !is_off;
}
else
{
from_wakeup = false;
}
//Fire the notify and enter a grace period
if (is_off)
{
bt_off();
turnoff_all();
}
current_button_state = GRACE;
reset_count();
}
Expand All @@ -272,20 +288,22 @@ void check_button_level(struct k_work *work_item)
if (inc_count_1 > threshold)
{
notify_long_tap();
if(!from_wakeup)
{
is_off = !is_off;
}
else
{
from_wakeup = false;
}
//Fire the notify and enter a grace period
if (is_off)
{
bt_off();
turnoff_all();
}
play_haptic_milli(10);
// TODO: FIXME
//if(!from_wakeup)
//{
// is_off = !is_off;
//}
//else
//{
// from_wakeup = false;
//}
////Fire the notify and enter a grace period
//if (is_off)
//{
// bt_off();
// turnoff_all();
//}
current_button_state = GRACE;
reset_count();
}
Expand Down Expand Up @@ -422,4 +440,4 @@ void turnoff_all()
void force_button_state(FSM_STATE_T state)
{
current_button_state = state;
}
}
29 changes: 29 additions & 0 deletions app/lib/backend/http/api/messages.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:friend_private/backend/http/shared.dart';
import 'package:friend_private/backend/schema/message.dart';
import 'package:friend_private/env/env.dart';
import 'package:friend_private/utils/logger.dart';
import 'package:instabug_flutter/instabug_flutter.dart';
import 'package:path/path.dart';

Future<List<ServerMessage>> getMessagesServer() async {
// TODO: Add pagination
Expand Down Expand Up @@ -72,3 +75,29 @@ Future<ServerMessage> getInitialAppMessage(String? appId) {
}
});
}

Future<List<ServerMessage>> sendVoiceMessageServer(List<File> files) async {
var request = http.MultipartRequest(
'POST',
Uri.parse('${Env.apiBaseUrl}v1/voice-messages'),
);
for (var file in files) {
request.files.add(await http.MultipartFile.fromPath('files', file.path, filename: basename(file.path)));
}
request.headers.addAll({'Authorization': await getAuthHeader()});

try {
var streamedResponse = await request.send();
var response = await http.Response.fromStream(streamedResponse);
if (response.statusCode == 200) {
debugPrint('sendVoiceMessageServer response body: ${jsonDecode(response.body)}');
return ((jsonDecode(response.body) ?? []) as List<dynamic>).map((m) => ServerMessage.fromJson(m)).toList();
} else {
debugPrint('Failed to upload sample. Status code: ${response.statusCode} ${response.body}');
throw Exception('Failed to upload sample. Status code: ${response.statusCode}');
}
} catch (e) {
debugPrint('An error occurred uploadSample: $e');
throw Exception('An error occurred uploadSample: $e');
}
}
79 changes: 79 additions & 0 deletions app/lib/providers/capture_provider.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:flutter_provider_utilities/flutter_provider_utilities.dart';
import 'package:friend_private/backend/http/api/memories.dart';
import 'package:friend_private/backend/http/api/messages.dart';
import 'package:friend_private/backend/preferences.dart';
import 'package:friend_private/backend/schema/bt_device/bt_device.dart';
import 'package:friend_private/backend/schema/memory.dart';
Expand All @@ -25,6 +28,7 @@ import 'package:friend_private/utils/analytics/mixpanel.dart';
import 'package:friend_private/utils/enums.dart';
import 'package:friend_private/utils/logger.dart';
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';

Expand Down Expand Up @@ -75,6 +79,10 @@ class CaptureProvider extends ChangeNotifier

get bleBytesStream => _bleBytesStream;

StreamSubscription? _bleButtonStream;
bool _isDeviceButtonLongPressStart = false;
List<List<int>> _commandBytes = [];

StreamSubscription? _storageStream;

get storageStream => _storageStream;
Expand Down Expand Up @@ -162,12 +170,72 @@ class CaptureProvider extends ChangeNotifier
notifyListeners();
}

Future<File> _flushBytesToTempFile(List<List<int>> chunk, int timerStart) async {
final directory = await getTemporaryDirectory();
String filePath = '${directory.path}/audio_${timerStart}.bin';
List<int> data = [];
for (int i = 0; i < chunk.length; i++) {
var frame = chunk[i];

// Format: <length>|<data> ; bytes: 4 | n
final byteFrame = ByteData(frame.length);
for (int i = 0; i < frame.length; i++) {
byteFrame.setUint8(i, frame[i]);
}
data.addAll(Uint32List.fromList([frame.length]).buffer.asUint8List());
data.addAll(byteFrame.buffer.asUint8List());
}
final file = File(filePath);
await file.writeAsBytes(data);

return file;
}

void _processVoiceCommandBytes() async {
debugPrint("Send ${_commandBytes.length} voice frames to backend");
var file = await _flushBytesToTempFile(_commandBytes, DateTime.now().millisecondsSinceEpoch ~/ 1000);
try {
var messages = await sendVoiceMessageServer([file]);
debugPrint("Command respond: ${messages.map((m) => m.text).join(" | ")}");
} catch (e) {
debugPrint(e.toString());
}
}

Future streamButton(String id) async {
debugPrint('streamButton in capture_provider');
_bleButtonStream?.cancel();
_bleButtonStream = await _getBleButtonListener(id, onButtonReceived: (List<int> value) {
if (value.isEmpty) return;
var buttonState = ByteData.view(Uint8List.fromList(value.sublist(0, 4).reversed.toList()).buffer).getUint32(0);
debugPrint("button ${buttonState}");

// start long press
if (buttonState == 3) {
_isDeviceButtonLongPressStart = true;
_commandBytes = [];
}

// release
if (buttonState == 5 && _isDeviceButtonLongPressStart) {
_isDeviceButtonLongPressStart = false;
_processVoiceCommandBytes();
}
});
}

Future streamAudioToWs(String id, BleAudioCodec codec) async {
debugPrint('streamAudioToWs in capture_provider');
await streamButton(id);
_bleBytesStream?.cancel();
_bleBytesStream = await _getBleAudioBytesListener(id, onAudioBytesReceived: (List<int> value) {
if (value.isEmpty) return;

// command button triggered
if (_isDeviceButtonLongPressStart) {
_commandBytes.add(value.sublist(3));
}

// support: opus codec, 1m from the first device connectes
var deviceFirstConnectedAt = _deviceService.getFirstConnectedAt();
var checkWalSupported = codec == BleAudioCodec.opus &&
Expand Down Expand Up @@ -241,6 +309,17 @@ class CaptureProvider extends ChangeNotifier
return connection.getBleAudioBytesListener(onAudioBytesReceived: onAudioBytesReceived);
}

Future<StreamSubscription?> _getBleButtonListener(
String deviceId, {
required void Function(List<int>) onButtonReceived,
}) async {
var connection = await ServiceManager.instance().device.ensureConnection(deviceId);
if (connection == null) {
return Future.value(null);
}
return connection.getBleButtonListener(onButtonReceived: onButtonReceived);
}

Future<bool> _recheckCodecChange() async {
if (_recordingDevice != null) {
BleAudioCodec newCodec = await _getAudioCodec(_recordingDevice!.id);
Expand Down
14 changes: 14 additions & 0 deletions app/lib/services/devices/device_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,24 @@ abstract class DeviceConnection {
return null;
}

Future<StreamSubscription?> getBleButtonListener({
required void Function(List<int>) onButtonReceived,
}) async {
if (await isConnected()) {
return await performGetBleButtonListener(onButtonReceived: onButtonReceived);
}
//_showDeviceDisconnectedNotification();
return null;
}

Future<StreamSubscription?> performGetBleAudioBytesListener({
required void Function(List<int>) onAudioBytesReceived,
});

Future<StreamSubscription?> performGetBleButtonListener({
required void Function(List<int>) onButtonReceived,
});

Future<BleAudioCodec> getAudioCodec() async {
if (await isConnected()) {
return await performGetAudioCodec();
Expand Down
2 changes: 2 additions & 0 deletions app/lib/services/devices/frame_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ class FrameDeviceConnection extends DeviceConnection {
return Future.value(BleAudioCodec.pcm8);
}

Future<StreamSubscription?> performGetBleButtonListener({required void Function(List<int>) onButtonReceived}) async {}

@override
Future<StreamSubscription?> performGetBleAudioBytesListener(
{required void Function(List<int>) onAudioBytesReceived}) async {
Expand Down
62 changes: 62 additions & 0 deletions app/lib/services/devices/friend_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class FriendDeviceConnection extends DeviceConnection {
BluetoothService? _friendService;
BluetoothService? _storageService;
BluetoothService? _accelService;
BluetoothService? _buttonService;

FriendDeviceConnection(super.device, super.bleDevice);

Expand Down Expand Up @@ -49,6 +50,11 @@ class FriendDeviceConnection extends DeviceConnection {
if (_accelService == null) {
logServiceNotFoundError('Accelerometer', deviceId);
}

_buttonService = await getService(buttonDataStreamServiceUuid);
if (_buttonService == null) {
logServiceNotFoundError('Button', deviceId);
}
}

// Mimic @app/lib/utils/ble/friend_communication.dart
Expand Down Expand Up @@ -116,6 +122,62 @@ class FriendDeviceConnection extends DeviceConnection {
return listener;
}

Future<StreamSubscription?> performGetBleButtonListener({
required void Function(List<int>) onButtonReceived,
}) async {
if (_buttonService == null) {
logServiceNotFoundError('Button', deviceId);
return null;
}

var buttonDataStreamCharacteristic = getCharacteristic(_buttonService!, buttonTriggerCharacteristicUuid);
if (buttonDataStreamCharacteristic == null) {
logCharacteristicNotFoundError('Button data stream', deviceId);
return null;
}

try {
// TODO: Unknown GATT error here (code 133) on Android. StackOverflow says that it has to do with smaller MTU size
// The creator of the plugin says not to use autoConnect
// https://github.com/chipweinberger/flutter_blue_plus/issues/612
final device = bleDevice;
if (device.isConnected) {
if (Platform.isAndroid && device.mtuNow < 512) {
await device.requestMtu(512); // This might fix the code 133 error
}
if (device.isConnected) {
try {
await buttonDataStreamCharacteristic.setNotifyValue(true); // device could be disconnected here.
} on PlatformException catch (e) {
Logger.error('Error setting notify value for audio data stream $e');
}
} else {
Logger.handle(Exception('Device disconnected before setting notify value'), StackTrace.current,
message: 'Device is disconnected. Please reconnect and try again');
}
}
} catch (e, stackTrace) {
logSubscribeError('Button data stream', deviceId, e, stackTrace);
return null;
}

debugPrint('Subscribed to button stream from Friend Device');
var listener = buttonDataStreamCharacteristic.lastValueStream.listen((value) {
debugPrint("new button value ${value}");
if (value.isNotEmpty) onButtonReceived(value);
});

final device = bleDevice;
device.cancelWhenDisconnected(listener);

// This will cause a crash in OpenGlass devices
// due to a race with discoverServices() that triggers
// a bug in the device firmware.
if (Platform.isAndroid && device.isConnected) await device.requestMtu(512);

return listener;
}

@override
Future<StreamSubscription?> performGetBleAudioBytesListener({
required void Function(List<int>) onAudioBytesReceived,
Expand Down
2 changes: 2 additions & 0 deletions app/lib/services/devices/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const String friendServiceUuid = '19b10000-e8f2-537e-4f6c-d104768a1214';
const String audioDataStreamCharacteristicUuid = '19b10001-e8f2-537e-4f6c-d104768a1214';
const String audioCodecCharacteristicUuid = '19b10002-e8f2-537e-4f6c-d104768a1214';

//static struct bt_uuid_128 button_uuid_x = BT_UUID_INIT_128(BT_UUID_128_ENCODE(23ba7925 ,0x0000,0x1000,0x7450,0x346EAC492E92));
const String buttonDataStreamServiceUuid = '23ba7924-0000-1000-7450-346eac492e92';
const String buttonDataStreamCharacteristicUuid = '23ba7924-0000-1000-7450-346eac492e92';
const String buttonTriggerCharacteristicUuid = '23ba7925-0000-1000-7450-346eac492e92';

Expand Down
Loading

0 comments on commit 14319a0

Please sign in to comment.