Adding OpenHaystack Mobile app

Co-Authored-By: Lukas Burg <lukas.burg@hemalu.de>
This commit is contained in:
MaxGranzow
2022-05-11 13:02:07 +02:00
committed by Alexander Heinrich
parent b65a6e6be0
commit 3d593a006c
182 changed files with 10499 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CodeBlock extends StatelessWidget {
String text;
/// Displays a code block that can easily copied by the user.
CodeBlock({
Key? key,
required this.text,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Stack(
children: [
Container(
width: double.infinity,
constraints: const BoxConstraints(minHeight: 50),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(10)),
color: Theme.of(context).colorScheme.background,
),
padding: const EdgeInsets.all(5),
child: SelectableText(text),
),
Positioned(
top: 0,
right: 5,
child: OutlinedButton(
child: const Text('Copy'),
onPressed: () {
Clipboard.setData(ClipboardData(text: text));
},
),
),
],
),
);
}
}

View File

@@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
class DeploymentDetails extends StatefulWidget {
/// The steps required to deploy on this target.
List<Step> steps;
/// The name of the deployment target.
String title;
/// Describes a generic step-by-step deployment for a special hardware target.
///
/// The actual steps depend on the target platform and are provided in [steps].
DeploymentDetails({
Key? key,
required this.title,
required this.steps,
}) : super(key: key);
@override
_DeploymentDetailsState createState() => _DeploymentDetailsState();
}
class _DeploymentDetailsState extends State<DeploymentDetails> {
/// The index of the currently displayed step.
int _index = 0;
@override
Widget build(BuildContext context) {
var stepCount = widget.steps.length;
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: Stepper(
currentStep: _index,
controlsBuilder: (BuildContext context, ControlsDetails details) {
String continueText = _index < stepCount - 1 ? 'CONTINUE' : 'FINISH';
return Row(
children: <Widget>[
ElevatedButton(
style: ElevatedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(1))),
onPressed: details.onStepContinue,
child: Text(continueText),
),
if (_index > 0) TextButton(
onPressed: details.onStepCancel,
child: const Text('BACK'),
),
],
);
},
onStepCancel: () {
// Back button clicked
if (_index == 0) {
// Cancel deployment and return
Navigator.pop(context);
}
else if (_index > 0) {
setState(() {
_index -= 1;
});
}
},
onStepContinue: () {
// Continue button clicked
if (_index == stepCount - 1) {
// TODO: Mark accessory as deployed
// Deployment finished
Navigator.pop(context);
Navigator.pop(context);
} else {
setState(() {
_index += 1;
});
}
},
onStepTapped: (int index) {
setState(() {
_index = index;
});
},
steps: widget.steps,
),
),
);
}
}

View File

@@ -0,0 +1,95 @@
class DeploymentEmail {
static const _mailtoLink =
'mailto:?subject=Open%20Haystack%20Deplyoment%20Instructions&body=';
static const _welcomeMessage = 'OpenHaystack Deployment Guide\n\n'
'This is the deployment guide for your recently created OpenHaystack accessory. '
'The next step is to deploy the generated cryptographic key to a compatible '
'Bluetooth device.\n\n';
static const _finishedMessage =
'\n\nThe device now sends out Bluetooth advertisements. '
'It can take up to an hour for the location updates to appear in the app.\n';
static String getMicrobitDeploymentEmail(String advertisementKey) {
String mailContent = 'nRF51822 Deployment:\n\n'
'Requirements\n'
'To build the firmware the GNU Arm Embedded Toolchain is required.\n\n'
'Download\n'
'Download the firmware source code from GitHub and navigate to the '
'given folder.\n'
'https://github.com/seemoo-lab/openhaystack\n'
'git clone https://github.com/seemoo-lab/openhaystack.git && '
'cd openhaystack/Firmware/Microbit_v1\n\n'
'Build\n'
'Replace the public_key in main.c (initially '
'OFFLINEFINEINGPUBLICKEYHERE!) with the actual advertisement key. '
'Then execute make to create the firmware. You can export your '
'advertisement key directly from the OpenHaystack app.\n'
'static char public_key[28] = $advertisementKey;\n'
'make\n\n'
'Firmware Deployment\n'
'If the firmware is built successfully it can be deployed to the '
'microcontroller with the following command. (Please fill in the '
'volume of your microcontroller) \n'
'make install DEPLOY_PATH=/Volumes/MICROBIT';
return _mailtoLink +
Uri.encodeComponent(_welcomeMessage) +
Uri.encodeComponent(mailContent) +
Uri.encodeComponent(_finishedMessage);
}
static String getESP32DeploymentEmail(String advertisementKey) {
String mailContent = 'Espressif ESP32 Deployment: \n\n'
'Requirements\n'
'To build the firmware for the ESP32 Espressif\'s IoT Development '
'Framework (ESP-IDF) is required. Additionally Python 3 and the venv '
'module need to be installed.\n\n'
'Download\n'
'Download the firmware source code from GitHub and navigate to the '
'given folder.\n'
'https://github.com/seemoo-lab/openhaystack\n'
'git clone https://github.com/seemoo-lab/openhaystack.git '
'&& cd openhaystack/Firmware/ESP32\n\n'
'Build\n'
'Execute the ESP-IDF build command to create the ESP32 firmware.\n'
'idf.py build\n\n'
'Firmware Deployment\n'
'If the firmware is built successfully it can be flashed onto the '
'ESP32. This action is performed by the flash_esp32.sh script that '
'is provided with the advertisement key of the newly created accessory.\n'
'Please fill in the serial port of your microcontroller.\n'
'You can export your advertisement key directly from the '
'OpenHaystack app.\n'
'./flash_esp32.sh -p /dev/yourSerialPort $advertisementKey';
return _mailtoLink +
Uri.encodeComponent(_welcomeMessage) +
Uri.encodeComponent(mailContent) +
Uri.encodeComponent(_finishedMessage);
}
static String getLinuxHCIDeploymentEmail(String advertisementKey) {
String mailContent = 'Linux HCI Deployment:\n\n'
'Requirements\n'
'Install the hcitool software on a Bluetooth Low Energy Linux device, '
'for example a Raspberry Pi. Additionally Pyhton 3 needs to be '
'installed.\n\n'
'Download\n'
'Next download the python script that configures the HCI tool to '
'send out BLE advertisements.\n'
'https://raw.githubusercontent.com/seemoo-lab/openhaystack/main/Firmware/Linux_HCI/HCI.py\n'
'curl -o HCI.py https://raw.githubusercontent.com/seemoo-lab/openhaystack/main/Firmware/Linux_HCI/HCI.py\n\n'
'Usage\n'
'To start the BLE advertisements run the script.\n'
'You can export your advertisement key directly from the '
'OpenHaystack app.\n'
'sudo python3 HCI.py --key $advertisementKey';
return _mailtoLink +
Uri.encodeComponent(_welcomeMessage) +
Uri.encodeComponent(mailContent) +
Uri.encodeComponent(_finishedMessage);
}
}

View File

@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:openhaystack_mobile/deployment/code_block.dart';
import 'package:openhaystack_mobile/deployment/deployment_details.dart';
import 'package:openhaystack_mobile/deployment/hyperlink.dart';
class DeploymentInstructionsESP32 extends StatelessWidget {
String advertisementKey;
/// Displays a deployment guide for the ESP32 platform.
DeploymentInstructionsESP32({
Key? key,
this.advertisementKey = '<ADVERTISEMENT_KEY>',
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DeploymentDetails(
title: 'ESP32 Deployment',
steps: [
const Step(
title: Text('Requirements'),
content: Text('To build the firmware for the ESP32 Espressif\'s '
'IoT Development Framework (ESP-IDF) is required. Additionally '
'Python 3 and the venv module need to be installed.'),
),
Step(
title: const Text('Download'),
content: Column(
children: [
const Text('Download the firmware source code from GitHub '
'and navigate to the given folder.'),
Hyperlink(target: 'https://github.com/seemoo-lab/openhaystack'),
CodeBlock(text: 'git clone https://github.com/seemoo-lab/openhaystack.git && cd openhaystack/Firmware/ESP32'),
],
),
),
Step(
title: const Text('Build'),
content: Column(
children: [
const Text('Execute the ESP-IDF build command to create the ESP32 firmware.'),
CodeBlock(text: 'idf.py build'),
],
),
),
Step(
title: const Text('Firmware Deployment'),
content: Column(
children: [
const Text('If the firmware is built successfully it can '
'be flashed onto the ESP32. This action is performed by '
'the flash_esp32.sh script that is provided with the '
'advertisement key of the newly created accessory.'),
const Text(
'Please fill in the serial port of your microcontroller.',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
CodeBlock(text: './flash_esp32.sh -p /dev/yourSerialPort "$advertisementKey"'),
],
),
),
],
);
}
}

View File

@@ -0,0 +1,253 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:openhaystack_mobile/deployment/deployment_email.dart';
import 'package:openhaystack_mobile/deployment/deployment_esp32.dart';
import 'package:openhaystack_mobile/deployment/deployment_linux_hci.dart';
import 'package:openhaystack_mobile/deployment/deployment_nrf51.dart';
import 'package:openhaystack_mobile/deployment/hyperlink.dart';
import 'package:url_launcher/url_launcher.dart';
class DeploymentInstructions extends StatefulWidget {
String advertisementKey;
/// Displays deployment instructions for an already created accessory.
///
/// Provides general information about the created accessory and deployment.
/// Deployment guides for special hardware can be accessed separately.
///
/// The deployment instructions are customized with the [advertisementKey].
DeploymentInstructions({
Key? key,
this.advertisementKey = '<ADVERTISEMENT_KEY>',
}) : super(key: key);
@override
_DeploymentInstructionsState createState() => _DeploymentInstructionsState();
}
class _DeploymentInstructionsState extends State<DeploymentInstructions> {
final List<bool> _expanded = [false, false, false];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('How to Deploy'),
),
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
ListTile(
title: RichText(
text: TextSpan(
children: [
TextSpan(
text: 'Congratulations, you successfully created '
'your accessory!\nThe next step is to deploy the generated '
'key to a Bluetooth device. OpenHaystack currently '
'supports three different deployment targets:\n'
'Nordic nRF51, Espressif ESP32 and the generic Linux HCI '
'platform.\nAdditional information about the deployment '
'can be found on ',
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontSize: 18,
),
),
TextSpan(
text: 'GitHub',
style: const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
fontSize: 18,
),
recognizer: TapGestureRecognizer()
..onTap = () {
launch(
'https://github.com/seemoo-lab/openhaystack/');
},
),
const TextSpan(
text: '.',
style: TextStyle(color: Colors.black, fontSize: 18),
),
],
),
),
),
ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_expanded[index] = !isExpanded;
});
},
children: [
ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return const ListTile(
title: Text('Nordic vRF51'),
);
},
body: Column(
children: <Widget>[
const ListTile(
title: Text(
'For this firmware you need a nFR51822 platform '
'microcontroller. The provided firmware will send out '
'the created key so it can be found by Apple\'s Find My '
'network.'),
),
ListTile(
title: Hyperlink(
text: 'See deployment guide on GitHub',
target:
'https://github.com/seemoo-lab/openhaystack/tree/main/Firmware/Microbit_v1',
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
OutlinedButton(
child: const Text('Send per mail'),
onPressed: () async {
await launch(
DeploymentEmail.getMicrobitDeploymentEmail(
widget.advertisementKey));
},
),
ElevatedButton(
child: const Text('Continue'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
DeploymentInstructionsNRF51(
advertisementKey:
widget.advertisementKey,
)),
);
},
),
],
),
],
),
isExpanded: _expanded[0],
),
ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return const ListTile(
title: Text('Espressif ESP32'),
);
},
body: Column(
children: <Widget>[
const ListTile(
title: Text(
'For this firmware you need an ESP32 platform '
'microcontroller. The provided firmware will send out '
'the created key so it can be found by Apple\'s Find My '
'network.'),
),
ListTile(
title: Hyperlink(
text: 'See deployment guide on GitHub',
target:
'https://github.com/seemoo-lab/openhaystack/tree/main/Firmware/ESP32',
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
OutlinedButton(
child: const Text('Send per mail'),
onPressed: () async {
await launch(
DeploymentEmail.getESP32DeploymentEmail(
widget.advertisementKey));
},
),
ElevatedButton(
child: const Text('Continue'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
DeploymentInstructionsESP32(
advertisementKey:
widget.advertisementKey,
)),
);
},
),
],
),
],
),
isExpanded: _expanded[1],
),
ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return const ListTile(
title: Text('Linux HCI'),
);
},
body: Column(
children: <Widget>[
const ListTile(
title: Text(
'This method only requires a Bluetooth enabled '
'Linux device. Using the hcitool and a provided script '
'the devices advertises the created key so it can be '
'found by Apple\'s Find My network.'),
),
ListTile(
title: Hyperlink(
text: 'See deployment guide on GitHub',
target:
'https://github.com/seemoo-lab/openhaystack/tree/main/Firmware/Linux_HCI',
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
OutlinedButton(
child: const Text('Send per mail'),
onPressed: () async {
await launch(
DeploymentEmail.getLinuxHCIDeploymentEmail(
widget.advertisementKey));
},
),
ElevatedButton(
child: const Text('Continue'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
DeploymentInstructionsLinux(
advertisementKey:
widget.advertisementKey,
)),
);
},
),
],
),
],
),
isExpanded: _expanded[2],
),
],
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:openhaystack_mobile/deployment/code_block.dart';
import 'package:openhaystack_mobile/deployment/deployment_details.dart';
import 'package:openhaystack_mobile/deployment/hyperlink.dart';
class DeploymentInstructionsLinux extends StatelessWidget {
String advertisementKey;
/// Displays a deployment guide for the generic Linux HCI platform.
DeploymentInstructionsLinux({
Key? key,
this.advertisementKey = '<ADVERTISEMENT_KEY>',
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DeploymentDetails(
title: 'Linux HCI Deployment',
steps: [
const Step(
title: Text('Requirements'),
content: Text('Install the hcitool software on a Bluetooth '
'Low Energy Linux device, for example a Raspberry Pi. '
'Additionally Pyhton 3 needs to be installed.'),
),
Step(
title: const Text('Download'),
content: Column(
children: [
const Text('Next download the python script that '
'configures the HCI tool to send out BLE advertisements.'),
Hyperlink(target: 'https://raw.githubusercontent.com/seemoo-lab/openhaystack/main/Firmware/Linux_HCI/HCI.py'),
CodeBlock(text: 'curl -o HCI.py https://raw.githubusercontent.com/seemoo-lab/openhaystack/main/Firmware/Linux_HCI/HCI.py'),
],
),
),
Step(
title: const Text('Usage'),
content: Column(
children: [
const Text('To start the BLE advertisements run the script.'),
CodeBlock(text: 'sudo python3 HCI.py --key $advertisementKey'),
],
),
),
],
);
}
}

View File

@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:openhaystack_mobile/deployment/code_block.dart';
import 'package:openhaystack_mobile/deployment/deployment_details.dart';
import 'package:openhaystack_mobile/deployment/hyperlink.dart';
class DeploymentInstructionsNRF51 extends StatelessWidget {
String advertisementKey;
/// Displays a deployment guide for the NRF51 platform.
DeploymentInstructionsNRF51({
Key? key,
this.advertisementKey = '<ADVERTISEMENT_KEY>',
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DeploymentDetails(
title: 'nRF51822 Deployment',
steps: [
const Step(
title: Text('Requirements'),
content: Text('To build the firmware the GNU Arm Embedded '
'Toolchain is required.'),
),
Step(
title: const Text('Download'),
content: Column(
children: [
const Text('Download the firmware source code from GitHub '
'and navigate to the given folder.'),
Hyperlink(target: 'https://github.com/seemoo-lab/openhaystack'),
CodeBlock(text: 'git clone https://github.com/seemoo-lab/openhaystack.git && cd openhaystack/Firmware/Microbit_v1'),
],
),
),
Step(
title: const Text('Build'),
content: Column(
children: [
const Text('Replace the public_key in main.c (initially '
'OFFLINEFINEINGPUBLICKEYHERE!) with the actual '
'advertisement key. Then execute make to create the '
'firmware.'),
CodeBlock(text: 'static char public_key[28] = "$advertisementKey";'),
CodeBlock(text: 'make'),
],
),
),
Step(
title: const Text('Firmware Deployment'),
content: Column(
children: [
const Text('If the firmware is built successfully it can '
'be deployed to the microcontroller with the following '
'command.'),
const Text(
'Please fill in the volume of your microcontroller.',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
CodeBlock(text: 'make install DEPLOY_PATH=/Volumes/MICROBIT'),
],
),
),
],
);
}
}

View File

@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class Hyperlink extends StatelessWidget {
/// The target url to open.
String target;
/// The display text of the hyperlink. Default is [target].
String _text;
/// Displays a hyperlink that can be opened by a tap.
Hyperlink({
Key? key,
required this.target,
text,
}) : _text = text ?? target, super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
child: Text(_text,
style: const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
),
onTap: () {
launch(target);
},
);
}
}