mirror of
https://github.com/gosticks/growme.git
synced 2025-10-16 11:45:38 +00:00
feat: update layout
This commit is contained in:
parent
636e6cdf17
commit
f97b57d9c9
BIN
app/assets/images/logo.png
Normal file
BIN
app/assets/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 380 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '15.0'
|
||||
platform :ios, '15.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:grow_me_app/main.dart';
|
||||
|
||||
class Button extends StatelessWidget {
|
||||
const Button(this.text, this.onTap, {super.key});
|
||||
@ -16,10 +17,14 @@ class Button extends StatelessWidget {
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
color: Colors.lightGreen[500],
|
||||
color: primarySwatch,
|
||||
shape: BoxShape.rectangle,
|
||||
),
|
||||
child: Center(child: Text(text)),
|
||||
child: Center(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(color: lightBackgroundColor),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:grow_me_app/main.dart';
|
||||
|
||||
class GrowMeCard extends StatelessWidget {
|
||||
const GrowMeCard({this.child, super.key});
|
||||
@ -13,13 +14,13 @@ class GrowMeCard extends StatelessWidget {
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(25.0),
|
||||
color: Colors.white,
|
||||
color: lightBackgroundColor,
|
||||
shape: BoxShape.rectangle,
|
||||
border: Border.all(
|
||||
color: Colors.grey.shade200, width: 2, style: BorderStyle.solid),
|
||||
color: lightBackgroundColor, width: 2, style: BorderStyle.solid),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
spreadRadius: 3,
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 10), // changes position of shadow
|
||||
|
||||
@ -35,19 +35,19 @@ class DeviceCarouselState extends State<DeviceCarousel> {
|
||||
backgroundColor: device.isConnected
|
||||
? Colors.lightGreen
|
||||
: Colors.grey[400],
|
||||
backgroundImage:
|
||||
AssetImage('assets/prototype-render.png'),
|
||||
backgroundImage: const AssetImage(
|
||||
'assets/images/prototype-render.png'),
|
||||
radius: 150.0,
|
||||
),
|
||||
...(!device.isConnected
|
||||
? [
|
||||
const SizedBox(height: 25),
|
||||
Text("Lost connection..."),
|
||||
const Text("Lost connection..."),
|
||||
]
|
||||
: [
|
||||
const SizedBox(height: 25),
|
||||
]),
|
||||
Spacer(),
|
||||
const Spacer(),
|
||||
DeviceActionSheet(device: device, model: model)
|
||||
].toList(),
|
||||
))));
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_blue/flutter_blue.dart';
|
||||
import 'package:grow_me_app/components/bottom_sheet.dart';
|
||||
import 'package:grow_me_app/components/button.dart';
|
||||
import 'package:grow_me_app/components/connection_sheet.dart';
|
||||
import 'package:grow_me_app/components/device_model.dart';
|
||||
import 'package:grow_me_app/message.pb.dart';
|
||||
@ -9,7 +10,6 @@ import 'dart:developer' as developer;
|
||||
|
||||
class DeviceDebugView extends StatefulWidget {
|
||||
const DeviceDebugView({super.key, required this.device});
|
||||
|
||||
final LinkedDevice device;
|
||||
|
||||
@override
|
||||
@ -19,52 +19,6 @@ class DeviceDebugView extends StatefulWidget {
|
||||
class DeviceDebugViewState extends State<DeviceDebugView> {
|
||||
final List<double> _motorStepValues = [20, 20, 40, 50, 60, 20];
|
||||
|
||||
Future<BluetoothCharacteristic?> _getCharacteristic() async {
|
||||
var device = widget.device.device;
|
||||
try {
|
||||
var services = await device!.discoverServices();
|
||||
|
||||
BluetoothService service = services.firstWhere(
|
||||
(element) => element.uuid.toString() == bleServiceName.toLowerCase());
|
||||
if (service == null) {
|
||||
developer.log("device does not offer motor control service",
|
||||
name: "device.debug");
|
||||
}
|
||||
return service.characteristics.first;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _resetMotorPosition(int index) async {
|
||||
var ch = await _getCharacteristic();
|
||||
if (ch == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cmd = Command(reset: ResetMotorPositionCommand());
|
||||
|
||||
await ch.write(cmd.writeToBuffer(), withoutResponse: false);
|
||||
await ch.setNotifyValue(true);
|
||||
}
|
||||
|
||||
void _sendMotorControl(int index, bool isDecriment) async {
|
||||
var ch = await _getCharacteristic();
|
||||
if (ch == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find motor control service
|
||||
int factor = isDecriment ? -1 : 1;
|
||||
|
||||
var cmd = Command(
|
||||
move:
|
||||
MoveMotorCommand(target: factor * _motorStepValues[index].toInt()));
|
||||
|
||||
await ch.write(cmd.writeToBuffer(), withoutResponse: false);
|
||||
await ch.setNotifyValue(true);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: [
|
||||
@ -74,9 +28,20 @@ class DeviceDebugViewState extends State<DeviceDebugView> {
|
||||
Text("Motor ${entry.key + 1}"),
|
||||
Row(
|
||||
children: [
|
||||
(widget.device.motorStatus == null
|
||||
? Container()
|
||||
: Row(
|
||||
children: widget.device.motorStatus!.status
|
||||
.asMap()
|
||||
.entries
|
||||
.map((e) => Column(
|
||||
children: [Text("M${e.key} pos: ${e.value}")],
|
||||
))
|
||||
.toList(),
|
||||
)),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_resetMotorPosition(entry.key);
|
||||
widget.device.resetMotorPosition(entry.key);
|
||||
},
|
||||
icon: const Icon(Icons.replay_outlined)),
|
||||
const Spacer(),
|
||||
@ -94,17 +59,24 @@ class DeviceDebugViewState extends State<DeviceDebugView> {
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_sendMotorControl(entry.key, true);
|
||||
widget.device.moveMotor(
|
||||
entry.key, -1 * _motorStepValues[entry.key].toInt());
|
||||
},
|
||||
icon: const Icon(Icons.remove_circle)),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_sendMotorControl(entry.key, false);
|
||||
widget.device.moveMotor(
|
||||
entry.key, _motorStepValues[entry.key].toInt());
|
||||
},
|
||||
icon: const Icon(Icons.add_circle))
|
||||
],
|
||||
)
|
||||
])),
|
||||
Button("Reload state", (() {
|
||||
widget.device.fetchStatusUpdate().then((value) {
|
||||
setState(() {});
|
||||
});
|
||||
}))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,15 +7,32 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_blue/flutter_blue.dart';
|
||||
import 'package:grow_me_app/app.pb.dart';
|
||||
import 'package:grow_me_app/components/connection_sheet.dart';
|
||||
import 'package:grow_me_app/message.pbserver.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
const storageKey = "known-devices";
|
||||
|
||||
const Map<String, String> deviceCharacteristics = {
|
||||
"M1": "68023a4e-e253-46e4-a439-f716b3702ae1",
|
||||
"M2": "a9102165-7b40-4cce-9008-f4af28b5ac5e",
|
||||
"M3": "4e9cad83-71d8-40c1-8876-53c1cd5fe27e",
|
||||
"M4": "e0b49f4b-d7b0-4336-8562-41a16e16e8e6",
|
||||
"M5": "5f01e609-182f-45fe-aa23-45c12b82e2df",
|
||||
"M6": "09da8768-b6ba-4b4e-b91f-65d624581d48",
|
||||
"info": "9c05490f-cc74-4fd2-8d16-fb228e3f2270"
|
||||
};
|
||||
|
||||
class LinkedDevice {
|
||||
BluetoothDevice? _device;
|
||||
MotorStatus? _motorStatus;
|
||||
KnownDevice description;
|
||||
BluetoothDeviceState _status = BluetoothDeviceState.disconnected;
|
||||
|
||||
MotorStatus? get motorStatus {
|
||||
return _motorStatus;
|
||||
}
|
||||
|
||||
BluetoothDeviceState get status {
|
||||
return _status;
|
||||
}
|
||||
@ -43,11 +60,80 @@ class LinkedDevice {
|
||||
LinkedDevice(this.description, BluetoothDevice? device) {
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
Future<BluetoothCharacteristic?> _getCharacteristic(String uuid) async {
|
||||
if (_device == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
var services = await _device!.discoverServices();
|
||||
|
||||
BluetoothService service = services.firstWhere(
|
||||
(element) => element.uuid.toString() == bleServiceName.toLowerCase());
|
||||
if (service == null) {
|
||||
log("device does not offer motor control service",
|
||||
name: "device.debug");
|
||||
}
|
||||
|
||||
// find selected characteristic
|
||||
return service.characteristics
|
||||
.firstWhereOrNull((ch) => ch.uuid.toString() == uuid.toLowerCase());
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> fetchStatusUpdate() async {
|
||||
var ch = await _getCharacteristic(deviceCharacteristics["info"]!);
|
||||
if (ch == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var resp = await ch.read();
|
||||
try {
|
||||
var status = MotorStatus.fromBuffer(resp);
|
||||
_motorStatus = status;
|
||||
return true;
|
||||
} catch (e) {
|
||||
log("failed to fetch new machine status $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> moveMotor(int motorIndex, int offset) async {
|
||||
var ch =
|
||||
await _getCharacteristic(deviceCharacteristics["M${motorIndex + 1}"]!);
|
||||
if (ch == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var cmd = Command(move: MoveMotorCommand(target: offset));
|
||||
|
||||
await ch.write(cmd.writeToBuffer(), withoutResponse: false);
|
||||
await ch.setNotifyValue(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> resetMotorPosition(int motorIndex) async {
|
||||
var ch =
|
||||
await _getCharacteristic(deviceCharacteristics["M${motorIndex + 1}"]!);
|
||||
if (ch == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var cmd = Command(reset: ResetMotorPositionCommand());
|
||||
|
||||
await ch.write(cmd.writeToBuffer(), withoutResponse: false);
|
||||
await ch.setNotifyValue(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceModel extends ChangeNotifier {
|
||||
List<LinkedDevice> _devices = [];
|
||||
Stream<List<ScanResult>>? _scanStream = null;
|
||||
|
||||
UnmodifiableListView<LinkedDevice> get devices =>
|
||||
UnmodifiableListView(_devices);
|
||||
|
||||
@ -46,13 +46,14 @@ class EditDeviceViewState extends State<EditDeviceView> {
|
||||
controller: _deviceNameController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'Enter a search term',
|
||||
hintText: 'Give your plant a name',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
const CircleAvatar(
|
||||
backgroundColor: Colors.lightGreen,
|
||||
backgroundImage: AssetImage('assets/prototype-render.png'),
|
||||
backgroundImage:
|
||||
AssetImage('assets/images/prototype-render.png'),
|
||||
radius: 150.0,
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
|
||||
@ -16,7 +16,7 @@ void main() {
|
||||
create: (context) => DeviceModel(), child: const GrowMeApp()));
|
||||
}
|
||||
|
||||
const primarySwatch = MaterialColor(0xFFE5DBCF, {
|
||||
const primarySwatch = MaterialColor(0xFF273825, {
|
||||
50: Color(0xFFE5E7E5),
|
||||
100: Color(0xFFBEC3BE),
|
||||
200: Color(0xFF939C92),
|
||||
@ -28,6 +28,9 @@ const primarySwatch = MaterialColor(0xFFE5DBCF, {
|
||||
800: Color(0xFF172416),
|
||||
900: Color(0xFF0E170D),
|
||||
});
|
||||
|
||||
const lightBackgroundColor = Color(0xFFE5DBCF);
|
||||
|
||||
const PrimaryAssentColor = Color(0x00263825);
|
||||
const PrimaryDarkColor = Color(0xFF808080);
|
||||
const ErroColor = Color(0xFF808080);
|
||||
@ -121,15 +124,17 @@ class _MyHomePageState extends State<GrowMeHomePage> {
|
||||
appBar: AppBar(
|
||||
// Here we take the value from the GrowMeHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
title: const Image(
|
||||
height: 50, image: AssetImage("assets/images/logo.png")),
|
||||
leadingWidth: 100,
|
||||
actions: [const Icon(Icons.settings)].toList(),
|
||||
// actions: [const Icon(Icons.settings)].toList(),
|
||||
automaticallyImplyLeading: true,
|
||||
elevation: 0,
|
||||
),
|
||||
backgroundColor: primarySwatch,
|
||||
body: Column(
|
||||
children: [
|
||||
const Spacer(),
|
||||
const SizedBox(height: 50),
|
||||
DeviceCarousel(leadingCarouselItems: [_connectionCard()]),
|
||||
const Spacer(),
|
||||
].toList()),
|
||||
|
||||
@ -248,3 +248,44 @@ class ResetMotorPositionCommand extends $pb.GeneratedMessage {
|
||||
void clearMotorIndex() => clearField(1);
|
||||
}
|
||||
|
||||
class MotorStatus extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'MotorStatus', createEmptyInstance: create)
|
||||
..p<$core.int>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'status', $pb.PbFieldType.K3)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
MotorStatus._() : super();
|
||||
factory MotorStatus({
|
||||
$core.Iterable<$core.int>? status,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (status != null) {
|
||||
_result.status.addAll(status);
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory MotorStatus.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory MotorStatus.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
MotorStatus clone() => MotorStatus()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
MotorStatus copyWith(void Function(MotorStatus) updates) => super.copyWith((message) => updates(message as MotorStatus)) as MotorStatus; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static MotorStatus create() => MotorStatus._();
|
||||
MotorStatus createEmptyInstance() => create();
|
||||
static $pb.PbList<MotorStatus> createRepeated() => $pb.PbList<MotorStatus>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static MotorStatus getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<MotorStatus>(create);
|
||||
static MotorStatus? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get status => $_getList(0);
|
||||
}
|
||||
|
||||
|
||||
@ -53,3 +53,13 @@ const ResetMotorPositionCommand$json = const {
|
||||
|
||||
/// Descriptor for `ResetMotorPositionCommand`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List resetMotorPositionCommandDescriptor = $convert.base64Decode('ChlSZXNldE1vdG9yUG9zaXRpb25Db21tYW5kEh4KCm1vdG9ySW5kZXgYASABKAVSCm1vdG9ySW5kZXg=');
|
||||
@$core.Deprecated('Use motorStatusDescriptor instead')
|
||||
const MotorStatus$json = const {
|
||||
'1': 'MotorStatus',
|
||||
'2': const [
|
||||
const {'1': 'status', '3': 1, '4': 3, '5': 5, '10': 'status'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `MotorStatus`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List motorStatusDescriptor = $convert.base64Decode('CgtNb3RvclN0YXR1cxIWCgZzdGF0dXMYASADKAVSBnN0YXR1cw==');
|
||||
|
||||
@ -66,7 +66,7 @@ flutter:
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/
|
||||
- assets/images/
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
@ -78,16 +78,16 @@ flutter:
|
||||
fonts:
|
||||
- family: Manrope
|
||||
fonts:
|
||||
- asset: fonts/Manrope-ExtraLight.ttf
|
||||
- asset: assets/fonts/Manrope-ExtraLight.ttf
|
||||
weight: 200
|
||||
- asset: fonts/Manrope-Light.ttf
|
||||
- asset: assets/fonts/Manrope-Light.ttf
|
||||
weight: 300
|
||||
- asset: fonts/Manrope-Regular.ttf
|
||||
- asset: fonts/Manrope-Mediumttf
|
||||
- asset: assets/fonts/Manrope-Regular.ttf
|
||||
- asset: assets/fonts/Manrope-Medium.ttf
|
||||
weight: 500
|
||||
- asset: fonts/Manrope-SemiBold.ttf
|
||||
- asset: assets/fonts/Manrope-SemiBold.ttf
|
||||
weight: 600
|
||||
- asset: fonts/Manrope-Bold.ttf
|
||||
- asset: assets/fonts/Manrope-Bold.ttf
|
||||
weight: 700
|
||||
- asset: fonts/Manrope-ExtraBold.ttf
|
||||
- asset: assets/fonts/Manrope-ExtraBold.ttf
|
||||
weight: 800
|
||||
|
||||
@ -19,12 +19,12 @@ struct MotorControl {
|
||||
long stepTarget = 0;
|
||||
long currentPosition = 0;
|
||||
BLECharacteristic *bleCharacteristic = NULL;
|
||||
BLECharacteristic *bleInfoCharacteristic = NULL;
|
||||
|
||||
MotorControl(uint8_t index, short dir, short step);
|
||||
|
||||
void addControlCharacteristic(BLECharacteristic *bleCharac);
|
||||
void addInfoCharacteristic(BLECharacteristic *bleCharac);
|
||||
|
||||
void updateCurrentPosition(long newPosition);
|
||||
};
|
||||
|
||||
class CustomBLEMotorCallback : public BLECharacteristicCallbacks {
|
||||
@ -44,8 +44,8 @@ class CustomBLEMotorCallback : public BLECharacteristicCallbacks {
|
||||
};
|
||||
|
||||
class CustomBLEMotorInfoCallback : public BLECharacteristicCallbacks {
|
||||
int motorIndex;
|
||||
MotorControl *controller;
|
||||
MotorControl **motors;
|
||||
size_t numMotors;
|
||||
MotorStatus msg;
|
||||
|
||||
// set internal position value
|
||||
@ -54,8 +54,9 @@ class CustomBLEMotorInfoCallback : public BLECharacteristicCallbacks {
|
||||
void onRead(BLECharacteristic *ch);
|
||||
|
||||
public:
|
||||
CustomBLEMotorInfoCallback(uint8_t motorIndex, MotorControl *controller) {
|
||||
this->motorIndex = motorIndex;
|
||||
this->controller = controller;
|
||||
CustomBLEMotorInfoCallback(MotorControl *motors[]) {
|
||||
this->motors = motors;
|
||||
// TODO: compute dynamically
|
||||
numMotors = 6;
|
||||
};
|
||||
};
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
/* Struct definitions */
|
||||
typedef struct _MotorStatus {
|
||||
int32_t totalSteps;
|
||||
pb_callback_t status;
|
||||
} MotorStatus;
|
||||
|
||||
typedef struct _MoveMotorCommand {
|
||||
@ -45,15 +45,15 @@ extern "C" {
|
||||
#define ProgressCommand_init_default {0}
|
||||
#define MoveMotorCommand_init_default {0}
|
||||
#define ResetMotorPositionCommand_init_default {0}
|
||||
#define MotorStatus_init_default {0}
|
||||
#define MotorStatus_init_default {{{NULL}, NULL}}
|
||||
#define Command_init_zero {0, {ProgressCommand_init_zero}}
|
||||
#define ProgressCommand_init_zero {0}
|
||||
#define MoveMotorCommand_init_zero {0}
|
||||
#define ResetMotorPositionCommand_init_zero {0}
|
||||
#define MotorStatus_init_zero {0}
|
||||
#define MotorStatus_init_zero {{{NULL}, NULL}}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define MotorStatus_totalSteps_tag 1
|
||||
#define MotorStatus_status_tag 1
|
||||
#define MoveMotorCommand_target_tag 1
|
||||
#define ProgressCommand_progress_tag 1
|
||||
#define ResetMotorPositionCommand_motorIndex_tag 1
|
||||
@ -88,8 +88,8 @@ X(a, STATIC, SINGULAR, INT32, motorIndex, 1)
|
||||
#define ResetMotorPositionCommand_DEFAULT NULL
|
||||
|
||||
#define MotorStatus_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, INT32, totalSteps, 1)
|
||||
#define MotorStatus_CALLBACK NULL
|
||||
X(a, CALLBACK, REPEATED, INT32, status, 1)
|
||||
#define MotorStatus_CALLBACK pb_default_field_callback
|
||||
#define MotorStatus_DEFAULT NULL
|
||||
|
||||
extern const pb_msgdesc_t Command_msg;
|
||||
@ -106,8 +106,8 @@ extern const pb_msgdesc_t MotorStatus_msg;
|
||||
#define MotorStatus_fields &MotorStatus_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
/* MotorStatus_size depends on runtime parameters */
|
||||
#define Command_size 13
|
||||
#define MotorStatus_size 11
|
||||
#define MoveMotorCommand_size 11
|
||||
#define ProgressCommand_size 5
|
||||
#define ResetMotorPositionCommand_size 11
|
||||
|
||||
@ -24,5 +24,5 @@ message ResetMotorPositionCommand {
|
||||
}
|
||||
|
||||
message MotorStatus {
|
||||
int32 totalSteps = 1;
|
||||
repeated int32 status = 1;
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
#include "MotorControl.hpp"
|
||||
|
||||
#include <EEPROM.h>
|
||||
|
||||
void CustomBLEMotorCallback::onWrite(BLECharacteristic *characteristic) {
|
||||
const char *tag = "BLEMotor";
|
||||
|
||||
@ -32,24 +34,74 @@ void CustomBLEMotorCallback::onRead(BLECharacteristic *ch) {
|
||||
ESP_LOGI("MotorCallback", "onRead");
|
||||
};
|
||||
|
||||
struct RepeatedStatus {
|
||||
int32_t *status;
|
||||
int index;
|
||||
size_t max_size;
|
||||
};
|
||||
|
||||
bool status_encode(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) {
|
||||
RepeatedStatus *def = (RepeatedStatus *)*arg;
|
||||
while (def->index < def->max_size) {
|
||||
int32_t status = def->status[def->index];
|
||||
++def->index;
|
||||
|
||||
if (!pb_encode_tag(stream, PB_WT_STRING, field->tag)) {
|
||||
ESP_LOGE(TAG, "failed to encode: %s", PB_GET_ERROR(stream));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pb_encode_varint(stream, status)) {
|
||||
ESP_LOGE(TAG, "failed to encode: %s", PB_GET_ERROR(stream));
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "written value %d", status);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CustomBLEMotorInfoCallback::onRead(BLECharacteristic *characteristic) {
|
||||
// encode message in pb format
|
||||
pb_ostream_t stream = pb_ostream_from_buffer(this->buffer, sizeof(this->buffer));
|
||||
|
||||
this->msg.totalSteps = this->controller->currentPosition;
|
||||
int32_t status[numMotors];
|
||||
|
||||
for (int i = 0; i < this->numMotors; i++) {
|
||||
status[i] = this->motors[i]->currentPosition;
|
||||
}
|
||||
struct RepeatedStatus args = {.status = status, .index = 0, .max_size = this->numMotors};
|
||||
|
||||
msg.status.arg = &args;
|
||||
msg.status.funcs.encode = status_encode;
|
||||
|
||||
if (!pb_encode(&stream, Command_fields, &this->msg)) {
|
||||
ESP_LOGE(TAG, "failed to encode: %s", PB_GET_ERROR(&stream));
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "encoded status msg %d", stream.bytes_written);
|
||||
|
||||
// encode latest value in characteristic
|
||||
characteristic->setValue(buffer, stream.bytes_written);
|
||||
characteristic->setNotifyProperty(true);
|
||||
};
|
||||
|
||||
MotorControl::MotorControl(uint8_t index, short dir, short step) {
|
||||
this->stepper = new A4988(MOTOR_STEPS, dir, step);
|
||||
this->index = index;
|
||||
int address = index * 8;
|
||||
|
||||
// reload position from EEPROM
|
||||
long four = EEPROM.read(address);
|
||||
long three = EEPROM.read(address + 1);
|
||||
long two = EEPROM.read(address + 2);
|
||||
long one = EEPROM.read(address + 3);
|
||||
|
||||
this->currentPosition = ((four << 0) & 0xFF) + ((three << 8) & 0xFFFF) +
|
||||
((two << 16) & 0xFFFFFF) + ((one << 24) & 0xFFFFFFFF);
|
||||
this->stepTarget = this->currentPosition;
|
||||
};
|
||||
|
||||
void MotorControl::addControlCharacteristic(BLECharacteristic *bleCharac) {
|
||||
@ -61,11 +113,16 @@ void MotorControl::addControlCharacteristic(BLECharacteristic *bleCharac) {
|
||||
this->bleCharacteristic->setCallbacks(new CustomBLEMotorCallback(index, this));
|
||||
};
|
||||
|
||||
void MotorControl::addInfoCharacteristic(BLECharacteristic *bleCharac) {
|
||||
this->bleInfoCharacteristic = bleCharac;
|
||||
void MotorControl::updateCurrentPosition(long newPosition) {
|
||||
currentPosition = newPosition;
|
||||
int address = index * 8;
|
||||
byte four = (newPosition & 0xFF);
|
||||
byte three = ((newPosition >> 8) & 0xFF);
|
||||
byte two = ((newPosition >> 16) & 0xFF);
|
||||
byte one = ((newPosition >> 24) & 0xFF);
|
||||
|
||||
ESP_LOGI(TAG, "adding info characteristic for motor %d", this->index);
|
||||
|
||||
// add callback
|
||||
this->bleInfoCharacteristic->setCallbacks(new CustomBLEMotorInfoCallback(index, this));
|
||||
};
|
||||
EEPROM.write(address, four);
|
||||
EEPROM.write(address + 1, three);
|
||||
EEPROM.write(address + 2, two);
|
||||
EEPROM.write(address + 3, one);
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
#include <EEPROM.h>
|
||||
#include <WiFi.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
@ -50,14 +51,7 @@ char *motorControlCharacteristicUUIDs[] = {
|
||||
};
|
||||
|
||||
// BLE characteristic IDs for each motor
|
||||
char *motorInfoCharacteristicUUIDs[] = {
|
||||
"9c05490f-cc74-4fd2-8d16-fb228e3f2270",
|
||||
"6b342a89-086b-4e6c-8c26-c46352e99709",
|
||||
"6729beea-61c3-4376-a8fb-9f0aab020ed0",
|
||||
"faf8581b-1085-4b45-980f-5039db39cfbe",
|
||||
"69999658-9997-4e57-8e07-e14e0faf9b32",
|
||||
"d02d9c65-df4d-42d5-abef-24f71b612aad",
|
||||
};
|
||||
char *motorInfoCharacteristicUUID = "9c05490f-cc74-4fd2-8d16-fb228e3f2270";
|
||||
|
||||
// motor movement direction
|
||||
// -1 = counter clockwise
|
||||
@ -135,8 +129,7 @@ void motorTask(void *pvParameter) {
|
||||
vTaskDelay(20);
|
||||
}
|
||||
}
|
||||
|
||||
m->currentPosition = ongoingStepTarget;
|
||||
m->updateCurrentPosition(ongoingStepTarget);
|
||||
}
|
||||
|
||||
vTaskDelay(noActionIdleTime);
|
||||
@ -203,14 +196,16 @@ extern "C" void app_main() {
|
||||
BLECharacteristic *ctrl = pService->createCharacteristic(
|
||||
motorControlCharacteristicUUIDs[i],
|
||||
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
|
||||
BLECharacteristic *info = pService->createCharacteristic(motorInfoCharacteristicUUIDs[i],
|
||||
BLECharacteristic::PROPERTY_READ);
|
||||
|
||||
// register characteristic and callback methods
|
||||
m->addControlCharacteristic(ctrl);
|
||||
m->addInfoCharacteristic(info);
|
||||
}
|
||||
|
||||
// create single state characteristic
|
||||
BLECharacteristic *info = pService->createCharacteristic(motorInfoCharacteristicUUID,
|
||||
BLECharacteristic::PROPERTY_READ);
|
||||
info->setCallbacks(new CustomBLEMotorInfoCallback(motors));
|
||||
|
||||
pService->start();
|
||||
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
|
||||
pAdvertising->addServiceUUID(SERVICE_UUID);
|
||||
@ -221,16 +216,17 @@ extern "C" void app_main() {
|
||||
|
||||
pServer->setCallbacks(new CustomBLEServerCallback());
|
||||
|
||||
// setup serial COMs
|
||||
// Serial.begin(115200);
|
||||
// Setup on device memory
|
||||
// one long 64bit state per motor
|
||||
EEPROM.begin(8 * 6);
|
||||
|
||||
// xTaskCreate(&arduinoTask, "arduino_task", 8192, NULL, 5, NULL);
|
||||
xTaskCreate(&controlTask, "control_task", 4096, NULL, 3, NULL);
|
||||
|
||||
xTaskCreate(&motorTask, "motor_1_task", 4096, (void *)motors[0], 2, NULL);
|
||||
xTaskCreate(&motorTask, "motor_1_task", 4096, (void *)motors[1], 2, NULL);
|
||||
xTaskCreate(&motorTask, "motor_1_task", 4096, (void *)motors[2], 2, NULL);
|
||||
xTaskCreate(&motorTask, "motor_1_task", 4096, (void *)motors[3], 2, NULL);
|
||||
xTaskCreate(&motorTask, "motor_1_task", 4096, (void *)motors[4], 2, NULL);
|
||||
xTaskCreate(&motorTask, "motor_1_task", 4096, (void *)motors[5], 2, NULL);
|
||||
xTaskCreate(&motorTask, "motor_2_task", 4096, (void *)motors[1], 2, NULL);
|
||||
xTaskCreate(&motorTask, "motor_3_task", 4096, (void *)motors[2], 2, NULL);
|
||||
xTaskCreate(&motorTask, "motor_4_task", 4096, (void *)motors[3], 2, NULL);
|
||||
xTaskCreate(&motorTask, "motor_5_task", 4096, (void *)motors[4], 2, NULL);
|
||||
xTaskCreate(&motorTask, "motor_6_task", 4096, (void *)motors[5], 2, NULL);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user