├── .gitignore ├── .metadata ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── License ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── meshtastic_app │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── json │ ├── command_groups.json │ ├── commands.json │ └── commands_old.json ├── docs ├── bluetooth_notes.md └── sequence_diagrams.md ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── application │ ├── connect_device │ │ ├── connect_device_bloc.dart │ │ ├── connect_device_bloc.freezed.dart │ │ ├── connect_device_event.dart │ │ └── connect_device_state.dart │ ├── find_device │ │ ├── find_device_bloc.dart │ │ ├── find_device_bloc.freezed.dart │ │ ├── find_device_event.dart │ │ └── find_device_state.dart │ ├── setup_device │ │ ├── setup_device_bloc.dart │ │ ├── setup_device_bloc.freezed.dart │ │ ├── setup_device_event.dart │ │ └── setup_device_state.dart │ └── simple_bloc_observer.dart ├── domain │ ├── commands │ │ ├── command.dart │ │ ├── command_failure.dart │ │ └── command_failure.freezed.dart │ ├── connect_failure.dart │ ├── connect_failure.freezed.dart │ ├── core │ │ ├── errors.dart │ │ ├── failures.dart │ │ ├── value_objects.dart │ │ ├── value_transformers.dart │ │ └── value_validators.dart │ ├── device_repo.dart │ └── value_objects.dart ├── main.dart ├── services │ ├── bluetooth │ │ ├── ble_api.dart │ │ ├── ble_common.dart │ │ ├── bluetooth.dart │ │ └── service_uuids_index.dart │ ├── mesh │ │ ├── mesh.dart │ │ ├── mesh_api.dart │ │ ├── mesh_device.dart │ │ ├── mesh_node.dart │ │ └── mesh_preferences.dart │ └── proto │ │ ├── admin.options │ │ ├── admin.pb.dart │ │ ├── admin.pbenum.dart │ │ ├── admin.pbjson.dart │ │ ├── admin.pbserver.dart │ │ ├── admin.proto │ │ ├── apponly.pb.dart │ │ ├── apponly.pbenum.dart │ │ ├── apponly.pbjson.dart │ │ ├── apponly.pbserver.dart │ │ ├── apponly.proto │ │ ├── channel.pb.dart │ │ ├── channel.pbenum.dart │ │ ├── channel.pbjson.dart │ │ ├── channel.pbserver.dart │ │ ├── channel.proto │ │ ├── deviceonly.pb.dart │ │ ├── deviceonly.pbenum.dart │ │ ├── deviceonly.pbjson.dart │ │ ├── deviceonly.pbserver.dart │ │ ├── deviceonly.proto │ │ ├── environmental_measurement.pb.dart │ │ ├── environmental_measurement.pbenum.dart │ │ ├── environmental_measurement.pbjson.dart │ │ ├── environmental_measurement.pbserver.dart │ │ ├── environmental_measurement.proto │ │ ├── mesh.pb.dart │ │ ├── mesh.pbenum.dart │ │ ├── mesh.pbjson.dart │ │ ├── mesh.pbserver.dart │ │ ├── mesh.proto │ │ ├── portnums.pb.dart │ │ ├── portnums.pbenum.dart │ │ ├── portnums.pbjson.dart │ │ ├── portnums.pbserver.dart │ │ ├── portnums.proto │ │ ├── proto.dart │ │ ├── protoc.exe │ │ ├── radioconfig.pb.dart │ │ ├── radioconfig.pbenum.dart │ │ ├── radioconfig.pbjson.dart │ │ ├── radioconfig.pbserver.dart │ │ ├── radioconfig.proto │ │ ├── remote_hardware.pb.dart │ │ ├── remote_hardware.pbenum.dart │ │ ├── remote_hardware.pbjson.dart │ │ ├── remote_hardware.pbserver.dart │ │ └── remote_hardware.proto └── ui │ ├── find_device │ ├── find_device.dart │ └── widgets │ │ └── scan_result.dart │ ├── menu │ └── drawer.dart │ ├── permissions.dart │ ├── router │ └── route_generator.dart │ ├── settings │ └── log_settings.dart │ └── setup_device │ ├── mesh_command.dart │ ├── mesh_command_list.dart │ └── widgets │ ├── command_tiles.dart │ └── gatt_service.dart ├── pubspec.yaml ├── test └── widget_test.dart ├── todo_notes.md └── where /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | pubspec.lock 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2783f8e2e14efec8b7e08f668dde61c40d128c24 8 | channel: dev 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "eriklynd.json-tools" 4 | ] 5 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "meshtastic_app", 9 | "request": "launch", 10 | "type": "dart" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "launch": { 3 | 4 | "configurations": [ { 5 | "name": "Dart", 6 | "program": "bin/main.dart", 7 | "request": "launch", 8 | "type": "dart", 9 | "vmAdditionalArgs": [ 10 | "--enable-experiment=non-nullable", 11 | ], 12 | }], 13 | "compounds": [] 14 | } 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Meshtastic-Flutter 2 | 3 | A application to configure and manage Meshtastic devices over BLE. 4 | 5 | ## Getting Started 6 | 7 | Tested on Android only 8 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # include: package:effective_dart/analysis_options.yaml 2 | include: package:pedantic/analysis_options.yaml 3 | # include: package:very_good_analysis/analysis_options.yaml 4 | 5 | analyzer: 6 | exclude: 7 | build/**: true 8 | lib/services/proto/**: true 9 | lib/**/*.freezed.dart: true 10 | errors: 11 | avoid_void_async: error 12 | camel_case_types: error 13 | await_only_futures: error 14 | unawaited_futures: error 15 | 16 | strong-mode: 17 | implicit-casts: false 18 | 19 | linter: 20 | rules: 21 | avoid_void_async: true 22 | camel_case_types: true 23 | await_only_futures: true 24 | unawaited_futures: true 25 | lines_longer_than_80_chars: false -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 29 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.meshtastic_app" 42 | minSdkVersion 19 43 | targetSdkVersion 29 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | minifyEnabled false 51 | shrinkResources false 52 | // TODO: Add your own signing config for the release build. 53 | // Signing with the debug keys for now, so `flutter run --release` works. 54 | signingConfig signingConfigs.debug 55 | } 56 | } 57 | } 58 | 59 | flutter { 60 | source '../..' 61 | } 62 | 63 | dependencies { 64 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 65 | } 66 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/meshtastic_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.meshtastic_app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/json/command_groups.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 201, 4 | "group": "Sleep times", 5 | "command": "long_sleep_mode1", 6 | "commandDescription": "If set, this node will try to join the specified Wifi network and acquire an address via DHCP.", 7 | "params": [ 8 | { 9 | "id": "screen_on_secs", 10 | "pValue": "2777", 11 | "pDescription": "Time (s)", 12 | "pDefault": "60", 13 | "pMax": 28800, 14 | "pMin": 60, 15 | "pEdit": "true", 16 | "pVisible": "true" 17 | }, 18 | { 19 | "id": "wait_bluetooth_secs", 20 | "pValue": "2888", 21 | "pDescription": "Time (s)", 22 | "pDefault": "60", 23 | "pMax": 28800, 24 | "pMin": 60 25 | }, 26 | { 27 | "id": "phone_timeout_secs", 28 | "pValue": "2999", 29 | "pDescription": "Time (s)", 30 | "pDefault": "60", 31 | "pMax": 28800, 32 | "pMin": 60 33 | } 34 | ] 35 | }, 36 | { 37 | "id": 201, 38 | "group": "WiFi Setup", 39 | "command": "configure_wifi_station", 40 | "commandDescription": "This configuration gets the node to try to join the specified local Wifi network and acquire an address via DHCP.", 41 | "params": [ 42 | { 43 | "id": "wifi_ap_mode", 44 | "pValue": "false", 45 | "pDescription": "If set true, the node will operate as an AP (and DHCP server), otherwise it will join a Wifi network as a station.", 46 | "pDefault": "false", 47 | "pType": "bool", 48 | "pEdit": "false" 49 | }, 50 | { 51 | "id": "wifi_ssid", 52 | "pValue": "MyWiFiSSID", 53 | "pDescription": "Wifi SSID string. Used to join named network, or is advertised as AP SSID.", 54 | "pDefault": "", 55 | "pType": "string", 56 | "pEdit": "true", 57 | "pMax": 32 58 | }, 59 | { 60 | "id": "wifi_password", 61 | "pValue": "MyWiFiPassword", 62 | "pDescription": "Wifi Password string. Used to join to the named wifi, or required by clients joining this AP.", 63 | "pDefault": "", 64 | "pType": "string", 65 | "pEdit": "true", 66 | "pMax": 63 67 | } 68 | ] 69 | }, 70 | { 71 | "id": 202, 72 | "group": "WiFi Setup", 73 | "command": "configure_wifi_access_point", 74 | "commandDescription": "If set, this node will try to join the specified Wifi network and acquire an address via DHCP.", 75 | "params": [ 76 | { 77 | "id": "wifi_ap_mode", 78 | "pValue": "true", 79 | "pDescription": "If set true, the node will operate as an AP (and DHCP server), otherwise it will join a Wifi network as a station.", 80 | "pDefault": "false", 81 | "pType": "bool", 82 | "pEdit": "false" 83 | }, 84 | { 85 | "id": "wifi_ssid", 86 | "pValue": "", 87 | "pDescription": "Wifi SSID string. Used to join named network, or is advertised as AP SSID.", 88 | "pDefault": "", 89 | "pType": "string", 90 | "pMax": 32 91 | }, 92 | { 93 | "id": "wifi_password", 94 | "pValue": "", 95 | "pDescription": "Wifi Password string. Used to join to the named wifi, or required by clients joining this AP.", 96 | "pDefault": "", 97 | "pType": "string", 98 | "pMax": 63 99 | } 100 | ] 101 | }, 102 | { 103 | "id": 203, 104 | "group": "WiFi Setup", 105 | "command": "wifi_password", 106 | "commandDescription": "If set, will be use to authenticate to the named wifi .", 107 | "params": [ 108 | { 109 | "id": "wifi_password", 110 | "pValue": "", 111 | "pDescription": "Wifi Password string", 112 | "pDefault": "", 113 | "pType": "string", 114 | "pMax": 63 115 | } 116 | ] 117 | }, 118 | { 119 | "id": 204, 120 | "group": "WiFi Setup", 121 | "command": "disable_wifi", 122 | "commandDescription": " If set true, the node will operate as an AP (and DHCP server), otherwise it will be a station.", 123 | "params": [ 124 | { 125 | "id": "wifi_ap_mode", 126 | "pValue": "false", 127 | "pDescription": "true/false", 128 | "pDefault": "false", 129 | "pType": "bool", 130 | "pEdit": "false" 131 | } 132 | ] 133 | } 134 | ] -------------------------------------------------------------------------------- /docs/bluetooth_notes.md: -------------------------------------------------------------------------------- 1 | Disable/enable bluetooth on phone 2 | 3 | Restart phone 4 | 5 | Check with basic flutter_blue example 6 | 7 | 8 | 9 | 10 | 11 | 12 | ## These lines repeating frequently, after device firmware upgrade 13 | https://github.com/NordicSemiconductor/Android-BLE-Library/issues/17 14 | 15 | D/BluetoothGatt(16240): onConnectionUpdated() - Device=C4:4F:33:6A:AC:47 interval=6 latency=0 timeout=500 status=0 16 | D/BluetoothGatt(16240): onConnectionUpdated() - Device=C4:4F:33:6A:AC:47 interval=39 latency=0 timeout=500 status=0 17 | D/BluetoothGatt(16240): onConnectionUpdated() - Device=C4:4F:33:6A:AC:47 interval=6 latency=0 timeout=500 status=0 18 | D/BluetoothGatt(16240): onConnectionUpdated() - Device=C4:4F:33:6A:AC:47 interval=39 latency=0 timeout=500 status=0 19 | 20 | 21 | onConnectionUpdated with interval 6 means that Android does service discovery. To make it as quick as possible, it switches the connection interval to lowest possible value. 6 means 7.5ms. 22 | When you don't get it, it means that services were obtained from the cache and no service discovery we performed. 23 | 24 | Tried 25 | - close Nordic, Meshtastic BLE apps, 26 | - unpair, re-pair device 27 | 28 | 29 | 30 | ## Build for release needs special setting: 31 | https://github.com/pauldemarco/flutter_blue/issues/662#issuecomment-741710564 32 | 33 | Fixed this issue on release build by disabling shrink resources and minifyEnabled at android/app/build.gradle 34 | But It's strange as It's disabling ProGuard. So what about android apk file source securities. 35 | So It must need a proactive solution for the same for It should work with ProGuard enable. 36 | 37 | flutter build apk --no-shrink 38 | worked for me. 39 | 40 | 41 | ## Sleep etc 42 | On wake we see 43 | D/FlutterBluePlugin(29790): [onConnectionStateChange] status: 0 newState: 2 44 | On device sleep (or BLE sleep) 45 | D/FlutterBluePlugin(19981): [onConnectionStateChange] status: 19 newState: 0 46 | 47 | https://stackoverflow.com/questions/45056566/android-ble-gatt-connection-change-statuses 48 | Programmatically disconnected - 0 49 | Device went out of range - 8 50 | Disconnected by device - 19 51 | Issue with bond - 22 52 | Device not found - 133(some phone it gives 62) 53 | 54 | -------------------------------------------------------------------------------- /docs/sequence_diagrams.md: -------------------------------------------------------------------------------- 1 | see https://mermaid-js.github.io/mermaid/diagrams-and-syntax-and-examples/sequenceDiagram.html 2 | ````mermaid 3 | sequenceDiagram 4 | participant P as Page 5 | participant B as BLoc 6 | participant R as Repository 7 | participant S as Service 8 | Note over P: Initial 9 | P->>B: InitialiseRequest 10 | B->>R: Intialising 11 | Note over B: Intialising 12 | B-xP: Intialising 13 | 14 | alt Known device? 15 | R->>S: Connect device 16 | S-xR: Service List? 17 | 18 | else 19 | R->>S: StartScan 20 | S-xR: DeviceList 21 | R-xB: DeviceList 22 | B-xP: DeviceList 23 | end 24 | 25 | Note over P: Requested 26 | Note over P: Connected 27 | Note over P: NoDevice 28 | Note over P: KnownDevice 29 | Note over P: Initial 30 | Note over B: List 31 | Note over B: NoDevices 32 | 33 | Note over B: Selected 34 | ```` -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | meshtastic_app 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/application/connect_device/connect_device_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:freezed_annotation/freezed_annotation.dart'; 6 | // import 'package:injectable/injectable.dart'; 7 | import 'package:meshtastic_app/services/bluetooth/ble_common.dart'; 8 | // import 'package:meshtastic_app/bloc/find_device/bloc/find_device_bloc.dart'; 9 | import 'package:meta/meta.dart'; 10 | import 'package:get_it/get_it.dart'; 11 | 12 | import '../../domain/connect_failure.dart'; 13 | import '../../domain/device_repo.dart'; 14 | import '../../services/bluetooth/ble_api.dart'; 15 | import '../../services/mesh/mesh.dart'; 16 | 17 | part 'connect_device_bloc.freezed.dart'; 18 | part 'connect_device_event.dart'; 19 | part 'connect_device_state.dart'; 20 | 21 | // run this at terminal after changes to generate freezed code: 22 | // flutter pub run build_runner watch --delete-conflicting-outputs 23 | // also try: 24 | // flutter pub get && flutter pub run build_runner build --delete-conflicting-outputs 25 | 26 | /// ConnectDevice Bloc 27 | /// Purpose of this bloc is to manage BLE connection to the selected device 28 | /// chosen in [FindDeviceBloc] and providing connection state globally. 29 | /// See [setup_device] bloc for already connected device 30 | /// Application logic, and deals with UI and domain layers 31 | /// incoming events are transformed, yielded as States 32 | /// use of timer: https://stackoverflow.com/questions/61891062/flutter-bloc-using-timer-to-refetch-data 33 | 34 | class ConnectDeviceBloc extends Bloc { 35 | ConnectDeviceBloc() : super(const ConnectDeviceState.initial()); 36 | 37 | /// BLE device repository 38 | DeviceConnect connectRepo = GetIt.I(); 39 | StreamSubscription? _deviceSubscription; 40 | // StreamSubscription _periodicSubscription; 41 | List connectedDevices = []; 42 | 43 | // ConnectDeviceBloc(this.connectRepo) 44 | // : assert(connectRepo != null), 45 | // super(ConnectDeviceState.initial()) 46 | 47 | // @override nothing to override here 48 | ConnectDeviceState get initialState => const ConnectDeviceState.initial(); 49 | 50 | @override 51 | Stream mapEventToState( 52 | ConnectDeviceEvent event, 53 | ) async* { 54 | if (event is ConnectPressedEvent) { 55 | yield const ConnectingState(); 56 | try { 57 | // final connectedDevices = await connectRepo.connectedDevices3; 58 | await event.device.connect(); 59 | yield ConnectedState(event.device); 60 | add(const ConnectCompletedEvent()); 61 | //then the UI can refresh to show the connected device(s) 62 | 63 | // on BLE device change state, raise a ConnectStateChanged event in this bloc 64 | // BLEDeviceState { disconnected, connecting, connected, disconnecting } 65 | _deviceSubscription = event.device.deviceState.listen((status) { 66 | //call back action: 67 | add(ConnectStateChangedEvent(status)); 68 | }); 69 | } catch (e) { 70 | rethrow; 71 | //handle Failures from the repo - i.e. BLE off, no location services etc? 72 | } 73 | } 74 | 75 | if (event is ConnectCompletedEvent) { 76 | try { 77 | //anything to do? } 78 | } catch (e) { 79 | //handle Failures from the repo - i.e. BLE off, no location services etc? 80 | rethrow; 81 | } 82 | } 83 | 84 | if (event is ConnectStateChangedEvent) { 85 | // yield Requested(); 86 | // / BLEDeviceState { disconnected, connecting, connected, disconnecting } 87 | try { 88 | switch (event.deviceState) { 89 | case BLEDeviceState.disconnecting: 90 | yield const DeviceOfflineState(); 91 | break; 92 | case BLEDeviceState.disconnected: 93 | yield const DeviceOfflineState(); 94 | break; 95 | case BLEDeviceState.connecting: 96 | //what to od here 97 | // yield DeviceOffline(); 98 | break; 99 | //resume after brief interruption, or... 100 | case BLEDeviceState.connected: 101 | // yield Connected(event.device); 102 | yield const DeviceOnlineState(); 103 | break; 104 | } 105 | } catch (e) { 106 | //handle Failures from the repo - i.e. BLE off, no location services etc? 107 | rethrow; 108 | } 109 | } 110 | } 111 | 112 | @override 113 | Future close() { 114 | _deviceSubscription?.cancel(); 115 | return super.close(); 116 | } 117 | } 118 | // TODO: implement mapEventToState 119 | /// use Task() from dartz to wrap around Future> 120 | // see pattern at https://resocoder.com/2019/12/14/functional-error-handling-in-flutter-dart-2-either-task-fp/ 121 | 122 | /// This below is the ResoCoder pattern - very abstract 123 | // yield* event.map( 124 | // // AF scan not important now, as can depend on already paired devices for Mesht 125 | // scan: (e) async* { 126 | // // converts the Future into a Task 127 | // final userOption = await Task(() => connectRepo.scan()) 128 | // // execute function above, catches exceptions into Left 129 | // .attempt() 130 | // // results in a Either 131 | // .map( 132 | // // AF: do we need all of this, as connect is Either: 133 | // // Grab only the *left* side of Either 134 | // (either) => either.leftMap((obj) { 135 | // try { 136 | // // Cast the Object into a Failure 137 | // return obj as ConnectFailure; 138 | // } catch (e) { 139 | // // 'rethrow' the original exception 140 | // throw obj; 141 | // } 142 | // }), 143 | // ) 144 | // .run(); 145 | // // AF here want to update the Either<, R>, but what with ? 146 | // // .then((value) => ()); 147 | // // .then((value) => ConnectDeviceState.deviceknown()); 148 | 149 | // // yield userOption.fold( 150 | // // () => const ConnectFailure.cancelledByUser(), 151 | // // (_) => const ConnectDeviceState.deviceknown(), 152 | // // ); 153 | // }, 154 | // connect: (e) async* {}, 155 | // // //final userOption = await connectRepo.connect(deviceAddress: null); 156 | // // DeviceAddress device; // = "ABCD"; //.cast(); 157 | // // final userOption = await Task(() => connectRepo.connect(device)) 158 | // // .attempt() 159 | // // .map( 160 | // // (either) => either.leftMap((obj) { 161 | // // try { 162 | // // ConnectDeviceState.nodevice(); 163 | // // // Cast the Object into a Failure 164 | // // return obj as ConnectFailure; 165 | // // } catch (e) { 166 | // // // 'rethrow' the original exception 167 | // // throw obj; 168 | // // } 169 | // // }), 170 | // // ) 171 | // // .run() 172 | // // .then((value) => const ConnectDeviceState.deviceknown()); 173 | // // // yield userOption.fold( 174 | // // // () => const ConnectDeviceState.nodevice(), 175 | // // // (_) => const ConnectDeviceState.deviceknown(), 176 | // // // ); 177 | // // }, 178 | 179 | // /// initial - try to get current connected devices list first 180 | // initialise: (e) async* { 181 | // try { 182 | // // final deviceList = await connectRepo.connectedDevices; 183 | // // yield DeviceKnown(deviceList); //not the right State? Reconnect? 184 | 185 | // //todo if one device in list, then auto-connect, get Services 186 | // //final deviceList = await connectRepo.connectedServices; 187 | // final deviceList = await connectRepo.scan; 188 | // yield DeviceKnown(deviceList); //not the right State? Reconnect? 189 | 190 | // } catch (_) { 191 | // //State is NoDevice, and the Failure is noDevice - may simplify later 192 | // yield NoDevice(ConnectFailure.noDevice()); 193 | // } 194 | // }, 195 | // ); 196 | -------------------------------------------------------------------------------- /lib/application/connect_device/connect_device_event.dart: -------------------------------------------------------------------------------- 1 | 2 | part of 'connect_device_bloc.dart'; 3 | 4 | /// see https://bloclibrary.dev/#/blocnamingconventions 5 | /// Events should be named in the past tense because events are things that have already occurred from the bloc's perspective. 6 | /// BlocSubject + Noun (optional) + Verb (event) 7 | /// Initial load events should follow the convention: BlocSubject + Started 8 | 9 | /// Events from UI are 10 | /// Initialise (results in a new Scan, or reconnec to last known device) 11 | /// Scan for devices (current connected, and other/new) 12 | /// ConnectPressed, Connect to the device specified (or reconnect to the last known device) 13 | /// Disconnect ? 14 | /// 15 | /// Events from BLE are: 16 | /// DeviceSleeping? 17 | /// DeviceAwake, DeviceOn? 18 | /// (plus Stream values for scan results, etc) 19 | //@immutable 20 | @freezed 21 | abstract class ConnectDeviceEvent with _$ConnectDeviceEvent { 22 | const factory ConnectDeviceEvent.connect(BLEDevice2 device) = 23 | ConnectPressedEvent; 24 | const factory ConnectDeviceEvent.connectComplete() = ConnectCompletedEvent; 25 | const factory ConnectDeviceEvent.initialise() = IntialiseRequestedEvent; 26 | const factory ConnectDeviceEvent.stateChange(BLEDeviceState deviceState) = 27 | ConnectStateChangedEvent; 28 | } 29 | -------------------------------------------------------------------------------- /lib/application/connect_device/connect_device_state.dart: -------------------------------------------------------------------------------- 1 | part of 'connect_device_bloc.dart'; 2 | 3 | /// Based on ddd-firebase pattern 4 | /// may need to add parameters Todo 5 | /// see https://bloclibrary.dev/#/blocnamingconventions 6 | /// States should be nouns because a state is just a snapshot at a particular point in time. 7 | /// BlocSubject + Verb (action) + State 8 | /// State should be one of the following: Initial | Success | Failure | InProgress and initial states should follow the convention: BlocSubject + Initial. 9 | 10 | /// Purpose of this bloc is to manage getting a list of Mesh devices and connecting to the selected one. 11 | @freezed 12 | abstract class ConnectDeviceState with _$ConnectDeviceState { 13 | const factory ConnectDeviceState.initial() = 14 | Initial; //not known - will move to other state shortly 15 | 16 | //need to distinguish no devices from found devices? 17 | const factory ConnectDeviceState.connected(BLEDevice2 device) = 18 | ConnectedState; 19 | const factory ConnectDeviceState.requested() = ConnectingState; 20 | const factory ConnectDeviceState.nodevice(ConnectFailure connectFailure) = 21 | NoDeviceState; 22 | // const factory ConnectDeviceState.deviceknown(List deviceList) = DeviceKnown; 23 | const factory ConnectDeviceState.deviceKnown(deviceList) = DeviceKnownState; 24 | // when the device has been connected and disconnects 25 | // could be range, power-off, unpair, etc 26 | const factory ConnectDeviceState.deviceOffline() = DeviceOfflineState; 27 | // when the device has been disconnected and reconnects 28 | // could be range, power-off, unpair, etc 29 | const factory ConnectDeviceState.deviceOnline() = DeviceOnlineState; 30 | } 31 | -------------------------------------------------------------------------------- /lib/application/find_device/find_device_event.dart: -------------------------------------------------------------------------------- 1 | 2 | part of 'find_device_bloc.dart'; 3 | 4 | @freezed 5 | abstract class FindDeviceEvent with _$FindDeviceEvent { 6 | const factory FindDeviceEvent.started() = FindStartedEvent; 7 | const factory FindDeviceEvent.scan() = FindScanRequestedEvent; 8 | const factory FindDeviceEvent.scanStop() = FindScanStopRequestedEvent; 9 | const factory FindDeviceEvent.scanComplete() = FindScanCompletedEvent; 10 | const factory FindDeviceEvent.refresh() = FindRefreshRequestedEvent; 11 | const factory FindDeviceEvent.refreshComplete() = FindRefreshCompletedEvent; 12 | // const factory FindDeviceEvent.initialise() = IntialiseRequested; 13 | } 14 | -------------------------------------------------------------------------------- /lib/application/find_device/find_device_state.dart: -------------------------------------------------------------------------------- 1 | 2 | part of 'find_device_bloc.dart'; 3 | 4 | @freezed 5 | abstract class FindDeviceState with _$FindDeviceState { 6 | const factory FindDeviceState.initial() = 7 | FindInitialState; //not known - will move to other state shortly 8 | const factory FindDeviceState.completedscan() = FindCompletedScanState; 9 | // Stream> allDevices 10 | // List connectedDevices 11 | // ) = FindCompletedScanState; 12 | //need to distinguish no devices from found devices? 13 | // refresh for Connected devices, not a full scan 14 | const factory FindDeviceState.completedRefresh( 15 | List connectedDevices) = FindCompletedRefreshState; 16 | //need to distinguish no devices from found devices? 17 | const factory FindDeviceState.findRequested() = FindRequestedState; 18 | 19 | /// may need to have own Failures, meanwhile use Connect 20 | const factory FindDeviceState.failed(ConnectFailure failure) = 21 | FindFailureState; 22 | } 23 | -------------------------------------------------------------------------------- /lib/application/setup_device/setup_device_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:get_it/get_it.dart'; 6 | import 'package:injectable/injectable.dart'; 7 | 8 | import '../../domain/commands/command.dart'; 9 | import '../../domain/commands/command_failure.dart'; 10 | import '../../domain/device_repo.dart'; 11 | import '../../services/bluetooth/ble_api.dart'; 12 | import '../../services/mesh/mesh.dart'; 13 | import '../connect_device/connect_device_bloc.dart'; 14 | 15 | part 'setup_device_bloc.freezed.dart'; 16 | part 'setup_device_event.dart'; 17 | part 'setup_device_state.dart'; 18 | 19 | /// Setup device bloc initialises & sets preferences/config of the mesh device.S 20 | /// 21 | /// and sends any commands that are requested. 22 | /// Used by mesh_command and "normal" startup 23 | /// Uses [ConnectDeviceBloc] to listen to the selected device, 24 | /// so that state changes can update Mesh interface 25 | /// 26 | /// AF 11/01/2021 Now is intitialised in the [RouteGenerator] 27 | /// This may not need a bloc, just a cubit 28 | @injectable 29 | class SetupDeviceBloc extends Bloc { 30 | // SetupDeviceBloc({this.connectDeviceBloc}) : super(DeviceInitial()) { 31 | SetupDeviceBloc() : super(const DeviceInitialState()); 32 | 33 | //cannot run the DeviceStarted event on bloc create, as _device not initialised.: 34 | // add(DeviceStarted()); 35 | 36 | final ConnectDeviceBloc connectDeviceBloc = GetIt.I(); 37 | final DeviceConnect _connectFacade = GetIt.I(); 38 | late MeshDevice _device; 39 | StreamSubscription? _meshSubscription; 40 | StreamSubscription? _connectDeviceSubscription; 41 | late BLEInterface _meshInterface; 42 | 43 | @override 44 | Stream mapEventToState( 45 | SetupDeviceEvent event, 46 | ) async* { 47 | // Device is conected, needs Mesh startup 48 | if (event is DeviceStartedEvent) { 49 | yield const DeviceInProgressState(); 50 | try { 51 | /// do this first so can check is connected 52 | _connectDeviceSubscription = connectDeviceBloc.stream.listen((state) { 53 | //call back for connect changes 54 | //TEMP 55 | if (state is ConnectedState) { 56 | _meshInterface.connected(); 57 | } 58 | if (state is DeviceOfflineState) { 59 | _meshInterface.disconnected(); 60 | } 61 | appLogger.d('connectDeviceBloc state: ${state.toString()} '); 62 | }); 63 | 64 | // creates the BLEinterface 65 | _device = event.device; 66 | _meshInterface = await (_connectFacade.meshServiceStart(_device) 67 | as FutureOr); 68 | _meshSubscription = _meshInterface.meshEvents.listen((event) { 69 | add(DeviceEventEvent(event)); 70 | }); 71 | 72 | yield DeviceSuccessState('Connected to ${_device.id}'); 73 | } catch (e) { 74 | yield const DeviceFailureState(CommandFailure.unexpected()); 75 | appLogger.d('DeviceStarted event unexpected falure: $e '); 76 | } 77 | } 78 | 79 | if (event is DeviceEventEvent) { 80 | appLogger.i('SetupDevice: Mesh Event ${event.meshEvent}'); 81 | } 82 | 83 | /// Command submitted by form/application, call setPreference 84 | if (event is DeviceCommandEvent) { 85 | yield const DeviceInProgressState(); 86 | try { 87 | // set Preference and write to device 88 | late String msg; 89 | // appLogger.i( 90 | // 'SetupDevice: setPreferenceList ${event.meshCommand.params.paramList}'); 91 | final prefMap = { 92 | for (var p in event.meshCommand.params!.paramList!) 93 | p.id as String: p.value as String 94 | }; 95 | appLogger.i('SetupDevice: setPreferenceList ${prefMap}'); 96 | final possibleFailure = 97 | _connectFacade.bleInterface!.setPreferenceList(prefMap); 98 | 99 | //TODO - handle writeConfig failures also 100 | if (possibleFailure.isRight()) { 101 | await _connectFacade.bleInterface!.localNode 102 | .writeConfig(); //Now on Node class 103 | // TODO temp disable 104 | // msg = await _connectFacade.bleInterface.getConfig(); 105 | msg = 'Config sent to devices'; 106 | } 107 | yield possibleFailure.fold( 108 | (failure) => DeviceFailureState(failure), 109 | (_) => DeviceSuccessState(msg), 110 | ); 111 | 112 | // do anything else,, like read info? 113 | // _connectFacade.bleInterface.read(); 114 | 115 | } catch (e) { 116 | yield const DeviceFailureState(CommandFailure.unexpected()); 117 | appLogger.e('SetupDevice: event unexpected falure: $e '); 118 | } 119 | } 120 | } 121 | 122 | @override 123 | Future close() { 124 | _meshSubscription?.cancel(); 125 | return super.close(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lib/application/setup_device/setup_device_event.dart: -------------------------------------------------------------------------------- 1 | 2 | part of 'setup_device_bloc.dart'; 3 | 4 | @freezed 5 | abstract class SetupDeviceEvent with _$SetupDeviceEvent { 6 | const factory SetupDeviceEvent.started(MeshDevice device) = 7 | DeviceStartedEvent; 8 | const factory SetupDeviceEvent.command(MeshCommand meshCommand) = 9 | DeviceCommandEvent; 10 | const factory SetupDeviceEvent.eventRaised(MeshtasticReceive meshEvent) = 11 | DeviceEventEvent; 12 | // list other commands, e.g.send message etc 13 | //const factory SetupDeviceEvent.started() = _Started; 14 | } 15 | -------------------------------------------------------------------------------- /lib/application/setup_device/setup_device_state.dart: -------------------------------------------------------------------------------- 1 | 2 | part of 'setup_device_bloc.dart'; 3 | 4 | /// see https://bloclibrary.dev/#/blocnamingconventions 5 | /// States should be nouns because a state is just a snapshot at a particular point in time. 6 | /// BlocSubject + Verb (action) + State 7 | /// State should be one of the following: Initial | Success | Failure | InProgress 8 | /// and initial states should follow the convention: BlocSubject + Initial. 9 | 10 | @freezed 11 | abstract class SetupDeviceState with _$SetupDeviceState { 12 | const factory SetupDeviceState.initial() = DeviceInitialState; 13 | const factory SetupDeviceState.requested() = DeviceInProgressState; 14 | const factory SetupDeviceState.completed(String message) = DeviceSuccessState; 15 | const factory SetupDeviceState.failed(CommandFailure failure) = 16 | DeviceFailureState; 17 | } 18 | -------------------------------------------------------------------------------- /lib/application/simple_bloc_observer.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:bloc/bloc.dart'; 3 | import 'package:logger/logger.dart'; 4 | 5 | /// utility to see state transitions. 6 | /// source https://bloclibrary.dev/#/flutterweathertutorial 7 | 8 | class SimpleBlocObserver extends BlocObserver { 9 | Logger _logger; 10 | 11 | SimpleBlocObserver(Logger parentLogger) 12 | : _logger = parentLogger, 13 | super(); 14 | 15 | @override 16 | void onEvent(Bloc bloc, Object? event) { 17 | // print('onEvent $event'); 18 | _logger.i('BlocObserver: onEvent $event'); 19 | super.onEvent(bloc, event); 20 | } 21 | 22 | @override 23 | void onTransition(Bloc bloc, Transition transition) { 24 | // print('onTransition $transition'); 25 | _logger.i('BlocObserver: onTransition $transition'); 26 | super.onTransition(bloc, transition); 27 | } 28 | 29 | @override 30 | void onError(BlocBase bloc, Object error, StackTrace stackTrace) { 31 | // print('onError $error'); 32 | _logger.e('BlocObserver: onError $error'); 33 | 34 | super.onError(bloc, error, stackTrace); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/domain/commands/command.dart: -------------------------------------------------------------------------------- 1 | 2 | /// List of [MeshCommand], Data object, used with [CommandTile] to build Command list UI 3 | /// should be in the domain layer? 4 | class MeshCommandList { 5 | final List? commands; 6 | 7 | MeshCommandList({ 8 | this.commands, 9 | }); 10 | 11 | factory MeshCommandList.fromJson(List parsedJson) { 12 | List commands = []; 13 | commands = parsedJson 14 | .map((i) => MeshCommand.fromJson(i as Map)) 15 | .toList(); 16 | return new MeshCommandList(commands: commands); 17 | } 18 | 19 | List fromJson2(List parsedJson) { 20 | List commands = []; 21 | commands = parsedJson 22 | .map((i) => MeshCommand.fromJson(i as Map)) 23 | .toList(); 24 | return commands; 25 | } 26 | } 27 | 28 | /// Data object, used with [CommandTile] to build Command list UI 29 | class MeshCommand { 30 | final int? id; 31 | final String? group; 32 | final String? command; 33 | final String? commandDescription; 34 | // final List params; 35 | final MeshCommandParameterList? params; 36 | 37 | MeshCommand( 38 | {this.id, 39 | this.group, 40 | this.command, 41 | this.commandDescription, 42 | this.params}); 43 | 44 | factory MeshCommand.fromJson(Map json) { 45 | return MeshCommand( 46 | id: json['id'] as int?, 47 | group: (json['group'] ?? 'Ungrouped') as String, 48 | command: json['command'] as String?, 49 | commandDescription: json['commandDescription'] as String?, 50 | params: new MeshCommandParameterList.fromJson( 51 | json['params'] as List), 52 | ); 53 | } 54 | } 55 | 56 | /// Data object, list of children of [MeshCommand], 57 | /// used with [ParameterTile] to build Command list UI 58 | /// extends Iterable 59 | class MeshCommandParameterList { 60 | final List? paramList; 61 | 62 | MeshCommandParameterList({ 63 | this.paramList, 64 | }); 65 | 66 | factory MeshCommandParameterList.fromJson(List parsedJson) { 67 | List paramList = []; 68 | paramList = parsedJson 69 | .map((i) => MeshCommandParameter.fromJson(i as Map)) 70 | .toList(); 71 | return new MeshCommandParameterList(paramList: paramList); 72 | } 73 | } 74 | 75 | /// Data object,child of [MeshCommand], 76 | /// used with [ParameterTile] to build Command list UI 77 | /// 78 | /// "id": "wait_bluetooth_secs", 79 | /// "pValue": "28800", 80 | /// "pDescription": "Time (s)", 81 | /// "pDefault": "60", 82 | /// "pType": "bool" optional, default 'int' 83 | /// "pMax": 28800, optional, default 32, max string length 84 | /// "pMin": 60, optional, default 0 85 | /// "pEdit": "false" optional, default true, can edit? 86 | /// "pHide": "true" optional, default false, can edit? 87 | class MeshCommandParameter { 88 | final String? id; 89 | String? value; //editable by user 90 | final String? description; 91 | final String? defaultValue; 92 | final String? type; 93 | final int? max; 94 | final int? min; 95 | final bool? editable; 96 | final bool? visible; 97 | // add callback function 98 | 99 | MeshCommandParameter( 100 | {this.id, 101 | this.value, 102 | this.description, 103 | this.defaultValue, 104 | this.type, 105 | this.max, 106 | this.min, 107 | this.editable, 108 | this.visible}); 109 | 110 | factory MeshCommandParameter.fromJson(Map json) { 111 | return MeshCommandParameter( 112 | id: json['id'] as String?, 113 | value: json['pValue'] as String?, 114 | description: json['pDescription'] as String?, 115 | defaultValue: json['pDefault'] as String?, 116 | type: (json['pType'] ?? 'int') as String, 117 | max: (json['pMax'] ?? 32) as int, 118 | min: (json['pMin'] ?? 0) as int, 119 | editable: isBool((json['pEdit'] ?? 'true') as String), 120 | visible: isBool((json['pVisible'] ?? 'true') as String), 121 | ); 122 | } 123 | } 124 | 125 | bool isNumericInteger(String? string) { 126 | // Null or empty string is not a number 127 | if (string == null || string.isEmpty) { 128 | return false; 129 | } 130 | // Try to parse input string to number. 131 | // Use int.tryParse if you want to check integer only. 132 | // Use double.tryParse if you want to check double only. 133 | final number = int.tryParse(string); 134 | 135 | if (number == null) { 136 | return false; 137 | } 138 | return true; 139 | } 140 | 141 | /// returns true or false based on any string 142 | bool isBool(String string) { 143 | // when strict then only [1, true] return true 144 | bool strict = true; 145 | // Null or empty string is not a number 146 | if (string == null || string.isEmpty) { 147 | return false; 148 | } 149 | // Try to parse input string to number. 150 | if (strict == true) { 151 | return string == '1' || string == 'true'; 152 | } 153 | return string != '0' && string != 'false' && string != ''; 154 | } 155 | -------------------------------------------------------------------------------- /lib/domain/commands/command_failure.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:flutter/foundation.dart'; 4 | 5 | part 'command_failure.freezed.dart'; 6 | 7 | @freezed 8 | abstract class CommandFailure with _$CommandFailure { 9 | /// from the UI - timeout, cancel etc? 10 | const factory CommandFailure.cancelledByUser() = CancelledByUser; 11 | //used in mesh_api, setPreference 12 | const factory CommandFailure.unknownCommand() = UnknownCommand; 13 | const factory CommandFailure.incorrectParameter() = IncorrectParameter; 14 | const factory CommandFailure.unexpected() = UnexpectedFailure; 15 | } 16 | -------------------------------------------------------------------------------- /lib/domain/connect_failure.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:flutter/foundation.dart'; 4 | 5 | part 'connect_failure.freezed.dart'; 6 | 7 | @freezed 8 | abstract class ConnectFailure with _$ConnectFailure { 9 | /// from the UI - timeout, cancel etc? 10 | const factory ConnectFailure.cancelledByUser() = CancelledByUser; 11 | 12 | // is this a Failure? 13 | const factory ConnectFailure.noDevice() = NoDevice; // is this a Failure? 14 | 15 | //cannot reconnect to know good device (after restart, startup) 16 | const factory ConnectFailure.noKnownDevice() = NoKnownDevice; 17 | 18 | /// from the device, unable to connect, sleep, turned off 19 | const factory ConnectFailure.deviceError() = InvalidDeviceAddress; 20 | 21 | /// from the device, Bluetooth is not on, or not present 22 | const factory ConnectFailure.nobluetooth() = NoBluetooth; 23 | } 24 | -------------------------------------------------------------------------------- /lib/domain/core/errors.dart: -------------------------------------------------------------------------------- 1 | import 'failures.dart'; 2 | 3 | /// AF 2021-05-08 temp remove while doing null safe conversion 4 | // class NotAuthenticatedError extends Error {} 5 | 6 | // class UnexpectedValueError extends Error { 7 | // final ValueFailure valueFailure; 8 | 9 | // UnexpectedValueError(this.valueFailure); 10 | 11 | // @override 12 | // String toString() { 13 | // const explanation = 14 | // 'Encountered a ValueFailure at an unrecoverable point. Terminating.'; 15 | // return Error.safeToString('$explanation Failure was: $valueFailure'); 16 | // } 17 | // } 18 | -------------------------------------------------------------------------------- /lib/domain/core/failures.dart: -------------------------------------------------------------------------------- 1 | 2 | /// AF 2021-05-08 temp remove while doing null safe conversion 3 | // import 'package:freezed_annotation/freezed_annotation.dart'; 4 | 5 | // part 'failures.freezed.dart'; 6 | 7 | // @freezed 8 | // abstract class ValueFailure with _$ValueFailure { 9 | // const factory ValueFailure.exceedingLength({ 10 | // @required T failedValue, 11 | // @required int max, 12 | // }) = ExceedingLength; 13 | // const factory ValueFailure.empty({ 14 | // @required T failedValue, 15 | // }) = Empty; 16 | // const factory ValueFailure.multiline({ 17 | // @required T failedValue, 18 | // }) = Multiline; 19 | // const factory ValueFailure.listTooLong({ 20 | // @required T failedValue, 21 | // @required int max, 22 | // }) = ListTooLong; 23 | 24 | // const factory ValueFailure.invalidDeviceAddress({ 25 | // @required T failedValue, 26 | // }) = InvalidDeviceAddress; 27 | 28 | // /// from the device, Bluetooth is not on, or not present 29 | // // const factory ConnectFailure.nobluetooth() = NoBluetooth; 30 | // const factory ValueFailure.invalidEmail({ 31 | // @required T failedValue, 32 | // }) = InvalidEmail; 33 | // const factory ValueFailure.shortPassword({ 34 | // @required T failedValue, 35 | // }) = ShortPassword; 36 | // } 37 | -------------------------------------------------------------------------------- /lib/domain/core/value_objects.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | import 'errors.dart'; 5 | import 'failures.dart'; 6 | 7 | /// AF 2021-05-08 temp remove while doing null safe conversion 8 | // /// 9 | // @immutable 10 | // abstract class ValueObject { 11 | // const ValueObject(); 12 | // Either, T> get value; 13 | 14 | // /// Throws [UnexpectedValueError] containing the [ValueFailure] 15 | // T getOrCrash() { 16 | // // id = identity - same as writing (right) => right 17 | // return value.fold((f) => throw UnexpectedValueError(f), id); 18 | // } 19 | 20 | // Either, Unit> get failureOrUnit { 21 | // return value.fold( 22 | // (l) => left(l), 23 | // (r) => right(unit), 24 | // ); 25 | // } 26 | 27 | // bool isValid() => value.isRight(); 28 | 29 | // @override 30 | // bool operator ==(Object o) { 31 | // if (identical(this, o)) return true; 32 | 33 | // return o is ValueObject && o.value == value; 34 | // } 35 | 36 | // @override 37 | // int get hashCode => value.hashCode; 38 | 39 | // @override 40 | // String toString() => 'Value($value)'; 41 | // } 42 | 43 | // class UniqueId extends ValueObject { 44 | // @override 45 | // final Either, String> value; 46 | 47 | // factory UniqueId() { 48 | // return UniqueId._( 49 | // right(Uuid().v1()), 50 | // ); 51 | // } 52 | 53 | // factory UniqueId.fromUniqueString(String uniqueId) { 54 | // assert(uniqueId != null); 55 | // return UniqueId._( 56 | // right(uniqueId), 57 | // ); 58 | // } 59 | 60 | // const UniqueId._(this.value); 61 | // } 62 | -------------------------------------------------------------------------------- /lib/domain/core/value_transformers.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | Color makeColorOpaque(Color color) { 4 | return color.withOpacity(1); 5 | } 6 | -------------------------------------------------------------------------------- /lib/domain/core/value_validators.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | // import 'package:kt_dart/kt.dart'; 3 | import '../value_objects.dart'; 4 | import 'failures.dart'; 5 | 6 | /// AF 2021-05-08 temp remove while doing null safe conversion 7 | 8 | // Either, String> validateMaxStringLength( 9 | // String input, 10 | // int maxLength, 11 | // ) { 12 | // if (input.length <= maxLength) { 13 | // return right(input); 14 | // } else { 15 | // return left( 16 | // ValueFailure.exceedingLength(failedValue: input, max: maxLength), 17 | // ); 18 | // } 19 | // } 20 | 21 | // Either, String> validateStringNotEmpty(String input) { 22 | // if (input.isNotEmpty) { 23 | // return right(input); 24 | // } else { 25 | // return left(ValueFailure.empty(failedValue: input)); 26 | // } 27 | // } 28 | 29 | // Either, String> validateSingleLine(String input) { 30 | // if (input.contains('\n')) { 31 | // return left(ValueFailure.multiline(failedValue: input)); 32 | // } else { 33 | // return right(input); 34 | // } 35 | // } 36 | 37 | // // todo define a regex. Do we need to validate, if device comes from the OS/Flutter_blue - maybe just a null check 38 | // Either, String> validateDeviceAddress(String input) { 39 | // if (input != null) { 40 | // return right(input); 41 | // } else { 42 | // return left(ValueFailure.invalidDeviceAddress(failedValue: input)); 43 | // } 44 | // } 45 | 46 | // Either, String> validateEmailAddress(String input) { 47 | // const emailRegex = 48 | // r"""^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"""; 49 | // if (RegExp(emailRegex).hasMatch(input)) { 50 | // return right(input); 51 | // } else { 52 | // return left(ValueFailure.invalidEmail(failedValue: input)); 53 | // } 54 | // } 55 | 56 | // Either, String> validatePassword(String input) { 57 | // if (input.length >= 6) { 58 | // return right(input); 59 | // } else { 60 | // return left(ValueFailure.shortPassword(failedValue: input)); 61 | // } 62 | // } 63 | -------------------------------------------------------------------------------- /lib/domain/value_objects.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:meshtastic_app/domain/core/failures.dart'; 3 | import 'package:meshtastic_app/domain/core/value_objects.dart'; 4 | import 'package:meshtastic_app/domain/core/value_validators.dart'; 5 | 6 | /// AF 2021-05-08 temp remove while doing null safe conversion 7 | 8 | // class DeviceAddress extends ValueObject { 9 | // @override 10 | // final Either, String> value; 11 | 12 | // factory DeviceAddress(String input) { 13 | // assert(input != null); 14 | // return DeviceAddress._( 15 | // validateDeviceAddress(input), 16 | // ); 17 | // } 18 | 19 | // const DeviceAddress._(this.value); 20 | // } 21 | 22 | // class Password extends ValueObject { 23 | // @override 24 | // final Either, String> value; 25 | 26 | // factory Password(String input) { 27 | // assert(input != null); 28 | // return Password._( 29 | // validatePassword(input), 30 | // ); 31 | // } 32 | 33 | // const Password._(this.value); 34 | // } 35 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Andrew Frigaard 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:flutter_bloc/flutter_bloc.dart'; 19 | import 'package:flex_color_scheme/flex_color_scheme.dart'; 20 | import 'package:logger/logger.dart'; 21 | // import 'package:logger_flutter/logger_flutter.dart'; 22 | import 'package:shared_preferences/shared_preferences.dart'; 23 | import 'package:get_it/get_it.dart'; 24 | import 'package:wakelock/wakelock.dart'; 25 | 26 | import 'application/connect_device/connect_device_bloc.dart'; 27 | import 'application/simple_bloc_observer.dart'; 28 | import 'domain/device_repo.dart'; 29 | import 'services/bluetooth/ble_api.dart'; 30 | 31 | // import 'widgets.dart'; 32 | import 'ui/menu/drawer.dart'; 33 | import 'ui/router/route_generator.dart'; 34 | import 'ui/find_device/find_device.dart'; 35 | import 'ui/settings/log_settings.dart' as log; 36 | 37 | //import 'ui/ble_connect.dart'; 38 | 39 | // layer model based on https://resocoder.com/2020/03/09/flutter-firebase-ddd-course-1-domain-driven-design-principles/ 40 | 41 | void main() { 42 | WidgetsFlutterBinding 43 | .ensureInitialized(); //required before running plugin code 44 | final sl = GetIt.instance; //sl = service locator 45 | //global logging instance - visible to user, do we need second instance for user? 46 | sl.registerSingleton(log.appLogger, instanceName: 'appLogger'); 47 | sl.registerSingleton(log.userLogger, instanceName: 'userLogger'); 48 | Bloc.observer = SimpleBlocObserver(sl.get(instanceName: 'appLogger')); 49 | 50 | final deviceConnectRepository = DeviceConnect(blueAPIClient: BlueAPIClient()); 51 | sl.registerSingleton(deviceConnectRepository); 52 | sl.registerSingleton(ConnectDeviceBloc()); 53 | 54 | Wakelock.enable(); //force screen unlocked, for dev only 55 | 56 | runApp(BLEApp(router: RouteGenerator(sl.get()))); 57 | } 58 | 59 | class BLEApp extends StatelessWidget { 60 | BLEApp({Key? key, /*required*/ required this.router}) : super(key: key); 61 | final RouteGenerator router; 62 | final sl = GetIt.instance; //sl = service locator 63 | @override 64 | Widget build(BuildContext context) { 65 | return MaterialApp( 66 | title: 'Meshtastic Configurator', 67 | // theme: ThemeData( 68 | // primarySwatch: Colors.green, 69 | // ), 70 | // Green color theme, based on Material green and cyan colors. light theme. 71 | theme: FlexColorScheme.light(scheme: FlexScheme.green).toTheme, 72 | // Green color theme, based on Material green and cyan colors. dark theme. 73 | darkTheme: FlexColorScheme.dark(scheme: FlexScheme.green).toTheme, 74 | // Use dark or light theme based on system setting. 75 | themeMode: ThemeMode.system, 76 | // home: MyHomePage(title: 'Flutter Demo Home Page'), 77 | // home: FindDevicesScreen(deviceConnect: deviceConnect), 78 | 79 | initialRoute: '/', 80 | onGenerateRoute: router.onGenerateRoute, 81 | onGenerateInitialRoutes: (String initialRouteName) { 82 | return [ 83 | router.onGenerateRoute(RouteSettings( 84 | name: '/', 85 | // args are not used in initial page route, dont want to pass a null 86 | // arguments: MeshRouteArguments( 87 | // device: null, 88 | // ) 89 | )) 90 | ]; 91 | }, 92 | ); 93 | } 94 | 95 | @override 96 | //required to ensure that bloc in Router gets dispose event 97 | void dispose() { 98 | router.dispose(); 99 | //super.dispose(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/services/bluetooth/ble_common.dart: -------------------------------------------------------------------------------- 1 | // Common for Ennums and other objects required in multiple layers 2 | // Import into domain and service layer 3 | 4 | import 'package:flutter_blue/flutter_blue.dart'; 5 | 6 | /// copies of Bluetooth Enums to avoid refs to flutter_blue 7 | /// these should keep the same order as fluter_blue, but can be renamed 8 | enum BLEDeviceType { unknown, classic, le, dual } 9 | enum BLEDeviceState { disconnected, connecting, connected, disconnecting } 10 | 11 | /// State of the bluetooth adapter. 12 | enum BLEState { 13 | unknown, 14 | unavailable, 15 | unauthorized, 16 | turningOn, 17 | on, 18 | turningOff, 19 | off 20 | } 21 | 22 | // 8ba2bcc2-ee02-4a55-a531-c525c5e454d5 23 | // read 24 | // fromradio - contains a newly received FromRadio packet destined towards the phone (up to MAXPACKET bytes per packet). 25 | // After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this mailbox. 26 | 27 | // f75c76d2-129e-4dad-a1dd-7866124401e7 28 | // write 29 | // toradio - write ToRadio protobufs to this characteristic to send them (up to MAXPACKET len) 30 | 31 | // ed9da18c-a800-4f66-a670-aa7547e34453 32 | // read,notify,write 33 | // fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages 34 | // until it catches up with this number. 35 | const fromRadioUuidStr = '8ba2bcc2-ee02-4a55-a531-c525c5e454d5'; 36 | final Guid fromRadioUuid = Guid(fromRadioUuidStr); 37 | const toRadioUuidStr = 'f75c76d2-129e-4dad-a1dd-7866124401e7'; 38 | final Guid toRadioUuid = Guid(toRadioUuidStr); 39 | const fromNumUuidStr = 'ed9da18c-a800-4f66-a670-aa7547e34453'; 40 | final Guid fromNumUuid = Guid(fromNumUuidStr); 41 | 42 | class BLEService { 43 | late Guid uuid; 44 | late String serviceName; //friendly name, enriched data from json list 45 | late String deviceId; 46 | late bool isPrimary; 47 | List characteristics = []; 48 | List includedServices = []; 49 | 50 | BLEService(this.uuid, this.deviceId, this.isPrimary, this.characteristics, 51 | this.includedServices); 52 | // 53 | BLEService.fromBluetoothService(BluetoothService service) { 54 | uuid = service.uuid; 55 | deviceId = service.deviceId.toString(); 56 | service.characteristics.forEach((c) { 57 | characteristics.add(BLECharacteristic.fromBluetoothCharacteristic(c)); 58 | }); 59 | 60 | // this.includedServices = []; 61 | service.includedServices.forEach((s) { 62 | includedServices.add(BLEService.fromBluetoothService(s)); 63 | }); 64 | switch (uuid.toString()) { 65 | case '6ba1b218-15a8-461f-9fa8-5dcae273eafd': 66 | serviceName = 'Meshtastic'; 67 | break; 68 | case '00001800-0000-1000-8000-00805f9b34fb': 69 | serviceName = 'Generic Access'; 70 | break; 71 | case '00001801-0000-1000-8000-00805f9b34fb': 72 | serviceName = 'Generic Attribute'; 73 | break; 74 | default: 75 | serviceName = 'Unknown'; 76 | } 77 | } 78 | 79 | // is this needed. or pass thru to flutter_blue? 80 | @override 81 | String toString() { 82 | return 'BluetoothService{uuid: $uuid, deviceId: $deviceId, isPrimary: $isPrimary, characteristics: $characteristics, includedServices: $includedServices}'; 83 | } 84 | } 85 | 86 | class BLECharacteristic { 87 | late Guid uuid; 88 | late String name; //friendly name, enriched data from json list 89 | late String deviceId; 90 | late Guid serviceUuid; 91 | Guid? secondaryServiceUuid; 92 | late CharacteristicProperties properties; 93 | List descriptors = []; 94 | bool isNotifying = 95 | false; //default as descriptor may be null, so fb cannot provide 96 | late BluetoothCharacteristic _characteristic; //todo - is this needed? 97 | 98 | BLECharacteristic(this.uuid, this.deviceId, this.serviceUuid, 99 | this.secondaryServiceUuid, this.properties, this.descriptors); 100 | // 101 | BLECharacteristic.fromBluetoothCharacteristic(BluetoothCharacteristic char) { 102 | _characteristic = char; 103 | uuid = char.uuid; 104 | deviceId = char.deviceId.toString(); 105 | serviceUuid = char.serviceUuid; 106 | secondaryServiceUuid = char.secondaryServiceUuid; 107 | properties = char.properties; 108 | // this.descriptors=[] //initialise to avoid .add on null 109 | // can descriptors be null 110 | char.descriptors.forEach((d) { 111 | descriptors.add(BLEDescriptor.fromBluetoothDescriptor(d)); 112 | }); 113 | //BUG here - can't check isNotifying this for 1800 - no descriptor? 114 | // this.isNotifying = char.isNotifying; 115 | //logger.w('BLECharacteristic ${char?.isNotifying}'); 116 | switch (uuid.toString()) { 117 | case fromRadioUuidStr: 118 | name = 'from Radio'; 119 | break; 120 | case toRadioUuidStr: 121 | name = 'to Radio'; 122 | break; 123 | case fromNumUuidStr: 124 | name = 'from Num'; 125 | break; 126 | default: 127 | name = 'Unknown'; 128 | } 129 | } 130 | 131 | Stream> get value => _characteristic.value; 132 | 133 | List get lastValue => _characteristic.lastValue; 134 | 135 | /// Retrieves the value of a specified characteristic. 136 | /// As a list of byte values. 137 | Future> read() async { 138 | return await _characteristic.read(); 139 | } 140 | 141 | /// Writes the value of a characteristic. 142 | /// [withoutResponse]: the write is not guaranteed and will return immediately with success. 143 | /// [withResponse] the method will return after the write operation has either passed or failed. 144 | Future write(List value, {required bool withoutResponse}) async { 145 | final type = withoutResponse ? true : false; 146 | return await _characteristic.write(value, withoutResponse: type); 147 | } 148 | 149 | /// Sets notifications or indications for the value of a specified characteristic 150 | Future setNotifyValue(bool notify) async { 151 | return await _characteristic.setNotifyValue(notify); 152 | } 153 | } 154 | 155 | /// Note use of cccd write to descriptor to enable Notifications 156 | /// see https://stackoverflow.com/questions/17910322/android-ble-api-gatt-notification-not-received 157 | class BLEDescriptor { 158 | static final Guid cccd = Guid('00002902-0000-1000-8000-00805f9b34fb'); 159 | 160 | late Guid uuid; 161 | late String deviceId; 162 | late Guid serviceUuid; 163 | late Guid characteristicUuid; 164 | late BluetoothDescriptor _descriptor; //handle on flutter_blue desc - needed? 165 | 166 | BLEDescriptor( 167 | this.uuid, this.deviceId, this.serviceUuid, this.characteristicUuid); 168 | // 169 | BLEDescriptor.fromBluetoothDescriptor(BluetoothDescriptor desc) { 170 | _descriptor = desc; 171 | uuid = desc.uuid; 172 | deviceId = desc.deviceId.toString(); 173 | serviceUuid = desc.serviceUuid; 174 | characteristicUuid = desc.characteristicUuid; 175 | } 176 | 177 | Stream> get value => _descriptor.value; 178 | 179 | List get lastValue => _descriptor.lastValue; 180 | 181 | /// Retrieves the value of a specified descriptor 182 | Future> read() async { 183 | return await _descriptor.read(); 184 | } 185 | 186 | /// Write the value of a specified descriptor 187 | Future write(List value) async { 188 | return await _descriptor.write(value); 189 | } 190 | } 191 | 192 | class BLEAdvertisementData { 193 | // these should be final, but prevents named constructor!? 194 | final String localName; 195 | final int? txPowerLevel; 196 | final bool connectable; 197 | final Map> manufacturerData; 198 | final Map> serviceData; 199 | List serviceUuids = []; 200 | 201 | BLEAdvertisementData.fromAD(AdvertisementData ad) 202 | : localName = ad.localName, 203 | txPowerLevel = ad.txPowerLevel ?? 0, //avoid null 204 | connectable = ad.connectable, 205 | manufacturerData = ad.manufacturerData, 206 | serviceData = ad.serviceData, 207 | serviceUuids = ad.serviceUuids; 208 | 209 | @override 210 | String toString() { 211 | return 'AdvertisementData{localName: $localName, txPowerLevel: $txPowerLevel, connectable: $connectable, manufacturerData: $manufacturerData, serviceData: $serviceData, serviceUuids: $serviceUuids}'; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /lib/services/bluetooth/bluetooth.dart: -------------------------------------------------------------------------------- 1 | 2 | export 'ble_api.dart'; 3 | export 'ble_common.dart'; 4 | export 'service_uuids_index.dart'; 5 | -------------------------------------------------------------------------------- /lib/services/bluetooth/service_uuids_index.dart: -------------------------------------------------------------------------------- 1 | String service_uuids = '''{ 2 | "uuid": "1800", 3 | "name": "Generic Access" 4 | }, 5 | { 6 | "uuid": "1811", 7 | "name": "Alert Notification Service" 8 | }, 9 | { 10 | "uuid": "1815", 11 | "name": "Automation IO" 12 | }, 13 | { 14 | "uuid": "180F", 15 | "name": "Battery Service" 16 | }, 17 | { 18 | "uuid": "1810", 19 | "name": "Blood Pressure" 20 | }, 21 | { 22 | "uuid": "181B", 23 | "name": "Body Composition" 24 | }, 25 | { 26 | "uuid": "181E", 27 | "name": "Bond Management Service" 28 | }, 29 | { 30 | "uuid": "181F", 31 | "name": "Continuous Glucose Monitoring" 32 | }, 33 | { 34 | "uuid": "1805", 35 | "name": "Current Time Service" 36 | }, 37 | { 38 | "uuid": "1818", 39 | "name": "Cycling Power" 40 | }, 41 | { 42 | "uuid": "1816", 43 | "name": "Cycling Speed and Cadence" 44 | }, 45 | { 46 | "uuid": "180A", 47 | "name": "Device Information" 48 | }, 49 | { 50 | "uuid": "181A", 51 | "name": "Environmental Sensing" 52 | }, 53 | { 54 | "uuid": "1826", 55 | "name": "Fitness Machine" 56 | }, 57 | { 58 | "uuid": "1801", 59 | "name": "Generic Attribute" 60 | }, 61 | { 62 | "uuid": "1808", 63 | "name": "Glucose" 64 | }, 65 | { 66 | "uuid": "1809", 67 | "name": "Health Thermometer" 68 | }, 69 | { 70 | "uuid": "180D", 71 | "name": "Heart Rate" 72 | }, 73 | { 74 | "uuid": "1823", 75 | "name": "HTTP Proxy" 76 | }, 77 | { 78 | "uuid": "1812", 79 | "name": "Human Interface Device" 80 | }, 81 | { 82 | "uuid": "1802", 83 | "name": "Immediate Alert" 84 | }, 85 | { 86 | "uuid": "1821", 87 | "name": "Indoor Positioning" 88 | }, 89 | { 90 | "uuid": "183A", 91 | "name": "Insulin Delivery" 92 | }, 93 | { 94 | "uuid": "1820", 95 | "name": "Internet Protocol Support Service" 96 | }, 97 | { 98 | "uuid": "1803", 99 | "name": "Link Loss" 100 | }, 101 | { 102 | "uuid": "1819", 103 | "name": "Location and Navigation" 104 | }, 105 | { 106 | "uuid": "1827", 107 | "name": "Mesh Provisioning Service" 108 | }, 109 | { 110 | "uuid": "1828", 111 | "name": "Mesh Proxy Service" 112 | }, 113 | { 114 | "uuid": "1807", 115 | "name": "Next DST Change Service" 116 | }, 117 | { 118 | "uuid": "1825", 119 | "name": "Object Transfer Service" 120 | }, 121 | { 122 | "uuid": "180E", 123 | "name": "Phone Alert Status Service" 124 | }, 125 | { 126 | "uuid": "1822", 127 | "name": "Pulse Oximeter Service" 128 | }, 129 | { 130 | "uuid": "1829", 131 | "name": "Reconnection Configuration" 132 | }, 133 | { 134 | "uuid": "1806", 135 | "name": "Reference Time Update Service" 136 | }, 137 | { 138 | "uuid": "1814", 139 | "name": "Running Speed and Cadence" 140 | }, 141 | { 142 | "uuid": "1813", 143 | "name": "Scan Parameters" 144 | }, 145 | { 146 | "uuid": "1824", 147 | "name": "Transport Discovery" 148 | }, 149 | { 150 | "uuid": "1804", 151 | "name": "Tx Power" 152 | }, 153 | { 154 | "uuid": "181C", 155 | "name": "User Data" 156 | }, 157 | { 158 | "uuid": "181D", 159 | "name": "Weight Scale" 160 | }, 161 | { 162 | "uuid": "7905F431-B5CE-4E99-A40F-4B1E122D00D0", 163 | "name": "Apple Notification Center Service" 164 | }, 165 | { 166 | "uuid": "89D3502B-0F36-433A-8EF4-C502AD55F8DC", 167 | "name": "Apple Media Service" 168 | }, 169 | { 170 | "uuid": "E95D0753-251D-470A-A062-FA1922DFA9A8", 171 | "name": "micro:bit Accelerometer Service" 172 | }, 173 | { 174 | "uuid": "E95DF2D8-251D-470A-A062-FA1922DFA9A8", 175 | "name": "micro:bit Magnetometer Service" 176 | }, 177 | { 178 | "uuid": "E95D9882-251D-470A-A062-FA1922DFA9A8", 179 | "name": "micro:bit Button Service" 180 | }, 181 | { 182 | "uuid": "E95D127B-251D-470A-A062-FA1922DFA9A8", 183 | "name": "micro:bit IO Pin Service" 184 | }, 185 | { 186 | "uuid": "E95DD91D-251D-470A-A062-FA1922DFA9A8", 187 | "name": "micro:bit LED Service" 188 | }, 189 | { 190 | "uuid": "E95D93AF-251D-470A-A062-FA1922DFA9A8", 191 | "name": "micro:bit Event Service" 192 | }, 193 | { 194 | "uuid": "E95D93B0-251D-470A-A062-FA1922DFA9A8", 195 | "name": "micro:bit DFU Control Service" 196 | }, 197 | { 198 | "uuid": "E95D6100-251D-470A-A062-FA1922DFA9A8", 199 | "name": "micro:bit Temperature Service" 200 | }, 201 | { 202 | "uuid": "EF680100-9B35-4933-9B10-52FFA9740042", 203 | "name": "Thingy Configuration Service" 204 | }, 205 | { 206 | "uuid": "EF680200-9B35-4933-9B10-52FFA9740042", 207 | "name": "Thingy Weather Station Service" 208 | }, 209 | { 210 | "uuid": "EF680300-9B35-4933-9B10-52FFA9740042", 211 | "name": "Thingy UI Service" 212 | }, 213 | { 214 | "uuid": "EF680400-9B35-4933-9B10-52FFA9740042", 215 | "name": "Thingy Motion Service" 216 | }, 217 | { 218 | "uuid": "EF680500-9B35-4933-9B10-52FFA9740042", 219 | "name": "Thingy Sound Service" 220 | }, 221 | { 222 | "uuid": "00001523-1212-EFDE-1523-785FEABCD123", 223 | "name": "Nordic LED and Button Service" 224 | }, 225 | { 226 | "uuid": "6E400001-B5A3-F393-E0A9-E50E24DCCA9E", 227 | "name": "Nordic UART Service" 228 | }, 229 | { 230 | "uuid": "FEAA", 231 | "name": "Eddystone" 232 | }, 233 | { 234 | "uuid": "A3C87500-8ED3-4BDF-8A39-A01BEBEDE295", 235 | "name": "Eddystone Configuration Service" 236 | }, 237 | { 238 | "uuid": "00001530-1212-EFDE-1523-785FEABCD123", 239 | "name": "Legacy DFU Service" 240 | }, 241 | { 242 | "uuid": "FE59", 243 | "name": "Secure DFU Service" 244 | }, 245 | { 246 | "uuid": "8E400001-F315-4F60-9FB8-838830DAEA50", 247 | "name": "Experimental Buttonless DFU Service" 248 | }, 249 | { 250 | "uuid": "FD6F", 251 | "name": "Exposure Notification Service" 252 | }, 253 | { 254 | "uuid": "8D53DC1D-1DB7-4CD3-868B-8A527460AA84", 255 | "name": "SMP Service" 256 | } 257 | '''; 258 | -------------------------------------------------------------------------------- /lib/services/mesh/mesh.dart: -------------------------------------------------------------------------------- 1 | 2 | export 'mesh_api.dart'; 3 | export 'mesh_device.dart'; 4 | -------------------------------------------------------------------------------- /lib/services/mesh/mesh_device.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter_blue/flutter_blue.dart'; 3 | // import 'package:logger/logger.dart'; 4 | 5 | import '../bluetooth/bluetooth.dart'; 6 | 7 | /// Class for addtional specific methods for a Mesh device. 8 | /// Extends the generic BLEDevice class 9 | /// has Meshtastic BLE/GATT methods, whereas MeshNode has Meshtastic methods 10 | class MeshDevice extends BLEDevice2 { 11 | // BLEDevice2 device; 12 | BluetoothDevice device; 13 | late BLEService _meshService; //GATT service for Meshtastic 14 | late List _serviceList; 15 | late BLECharacteristic fromRadio; 16 | late BLECharacteristic toRadio; 17 | late BLECharacteristic fromNum; 18 | bool? isMeshDevice; //flag, true only for Meshtastic devices 19 | 20 | //this will create another super object each time! 21 | //MeshDevice(this.device) : super(device.id, device.name, device.type); 22 | 23 | // create new instance of this and super object with the new device 24 | MeshDevice(BluetoothDevice connectedDevice) 25 | : device = connectedDevice, 26 | super.fromBluetoothDevice(connectedDevice) 27 | 28 | //todo call void method to intialise constructor body 29 | { 30 | appLogger.d('MeshDevice constructor body: ${device.id}'); 31 | // initialiseMeshService(); //see below moved due to async 32 | } 33 | 34 | /// Discover and setup Meshtastic GATT Characteristics. 35 | /// has async calls, so cannot be in class constructor 36 | /// called from mesh BLEInterface init in mesh_api 37 | Future initialiseMeshService() async { 38 | //List _serviceList; 39 | bool _intialised = false; 40 | 41 | // if (super._deviceState == BLEDeviceState.connected) { 42 | // appLogger.d('initialiseMeshService() Is connected '); 43 | _serviceList = await super.discoverServices(); 44 | //this goes to async wait here 45 | appLogger.d( 46 | 'initialiseMeshService() start ${super.id} Services found: ${_serviceList.length} '); 47 | if (_serviceList.length > 0) { 48 | // for (var service in _serviceList) { 49 | _meshService = _serviceList.firstWhere((s) => s.uuid == meshServiceUuid); 50 | fromRadio = _meshService.characteristics 51 | .firstWhere((char) => char.uuid == fromRadioUuid); 52 | toRadio = _meshService.characteristics 53 | .firstWhere((char) => char.uuid == toRadioUuid); 54 | fromNum = _meshService.characteristics 55 | .firstWhere((char) => char.uuid == fromNumUuid); 56 | appLogger.v( 57 | 'initialiseMeshService() Char fromRadio found: ${fromRadio.uuid.toString()} '); 58 | appLogger.v( 59 | 'initialiseMeshService() Char toRadio found: ${toRadio.uuid.toString()} '); 60 | appLogger.v( 61 | 'initialiseMeshService() Char fromNum found: ${fromNum.uuid.toString()} '); 62 | 63 | _intialised = true; 64 | // _readFromRadio(); // read the initial responses 65 | 66 | } else { 67 | _intialised = false; 68 | appLogger.e( 69 | 'initialiseMeshService() No services found: ${super.id.toString()} '); 70 | } 71 | return _intialised; 72 | } 73 | 74 | /// Note that a char.read is a List 75 | /// and a char.value is Stream> 76 | Stream> fromRadioValue(BLEService s) { 77 | fromRadio = 78 | s.characteristics.firstWhere((char) => char.uuid == fromRadioUuid); 79 | return fromRadio.value; 80 | } 81 | 82 | /// Filter the list put back into the stream 83 | /// TODO - this does not work - seems to return null lists etc 84 | /// temp filtered out - do in UI or higher layer. 85 | /// can ignore other services 86 | Stream> get meshService async* { 87 | Stream> _serviceList; 88 | // _serviceList = super.services; 89 | yield* super.services; 90 | 91 | /// TODO - this does not work - seems to return null lists etc 92 | //await for _serviceList { 93 | // yield* _serviceList.where((s) => s. .uuid == meshServiceUuid); 94 | // appLogger.v('meshService stream list: ${service.first.toString()}'); 95 | // } 96 | } 97 | 98 | // Future get meshService2 async { 99 | // List _serviceList; 100 | // _serviceList = await super.discoverServices(); 101 | // //yield* super.services; 102 | 103 | // for (var service in _serviceList) { 104 | // return _serviceList.firstWhere((s) => s.uuid == meshServiceUuid); 105 | // // appLogger.v('meshService2: ${service.toString()}'); 106 | // } 107 | // } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /lib/services/mesh/mesh_preferences.dart: -------------------------------------------------------------------------------- 1 | 2 | /// Interface classes for meshtastic devices, MeshInterface, BLEInterface 3 | import 'dart:async'; 4 | import 'dart:convert'; 5 | import 'dart:typed_data'; 6 | 7 | import 'package:dartz/dartz.dart'; 8 | import 'package:flutter/foundation.dart'; 9 | import 'package:meshtastic_app/services/mesh/mesh_node.dart'; 10 | 11 | // import 'package:recase/recase.dart'; 12 | 13 | import '../../domain/commands/command_failure.dart'; 14 | -------------------------------------------------------------------------------- /lib/services/proto/admin.options: -------------------------------------------------------------------------------- 1 | *AdminMessage.variant anonymous_oneof:true -------------------------------------------------------------------------------- /lib/services/proto/admin.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: admin.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | -------------------------------------------------------------------------------- /lib/services/proto/admin.pbjson.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: admin.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | import 'dart:core' as $core; 9 | import 'dart:convert' as $convert; 10 | import 'dart:typed_data' as $typed_data; 11 | @$core.Deprecated('Use adminMessageDescriptor instead') 12 | const AdminMessage$json = const { 13 | '1': 'AdminMessage', 14 | '2': const [ 15 | const {'1': 'set_radio', '3': 1, '4': 1, '5': 11, '6': '.RadioConfig', '9': 0, '10': 'setRadio'}, 16 | const {'1': 'set_owner', '3': 2, '4': 1, '5': 11, '6': '.User', '9': 0, '10': 'setOwner'}, 17 | const {'1': 'set_channel', '3': 3, '4': 1, '5': 11, '6': '.Channel', '9': 0, '10': 'setChannel'}, 18 | const {'1': 'get_radio_request', '3': 4, '4': 1, '5': 8, '9': 0, '10': 'getRadioRequest'}, 19 | const {'1': 'get_radio_response', '3': 5, '4': 1, '5': 11, '6': '.RadioConfig', '9': 0, '10': 'getRadioResponse'}, 20 | const {'1': 'get_channel_request', '3': 6, '4': 1, '5': 13, '9': 0, '10': 'getChannelRequest'}, 21 | const {'1': 'get_channel_response', '3': 7, '4': 1, '5': 11, '6': '.Channel', '9': 0, '10': 'getChannelResponse'}, 22 | const {'1': 'confirm_set_channel', '3': 32, '4': 1, '5': 8, '9': 0, '10': 'confirmSetChannel'}, 23 | const {'1': 'confirm_set_radio', '3': 33, '4': 1, '5': 8, '9': 0, '10': 'confirmSetRadio'}, 24 | ], 25 | '8': const [ 26 | const {'1': 'variant'}, 27 | ], 28 | }; 29 | 30 | /// Descriptor for `AdminMessage`. Decode as a `google.protobuf.DescriptorProto`. 31 | final $typed_data.Uint8List adminMessageDescriptor = $convert.base64Decode('CgxBZG1pbk1lc3NhZ2USKwoJc2V0X3JhZGlvGAEgASgLMgwuUmFkaW9Db25maWdIAFIIc2V0UmFkaW8SJAoJc2V0X293bmVyGAIgASgLMgUuVXNlckgAUghzZXRPd25lchIrCgtzZXRfY2hhbm5lbBgDIAEoCzIILkNoYW5uZWxIAFIKc2V0Q2hhbm5lbBIsChFnZXRfcmFkaW9fcmVxdWVzdBgEIAEoCEgAUg9nZXRSYWRpb1JlcXVlc3QSPAoSZ2V0X3JhZGlvX3Jlc3BvbnNlGAUgASgLMgwuUmFkaW9Db25maWdIAFIQZ2V0UmFkaW9SZXNwb25zZRIwChNnZXRfY2hhbm5lbF9yZXF1ZXN0GAYgASgNSABSEWdldENoYW5uZWxSZXF1ZXN0EjwKFGdldF9jaGFubmVsX3Jlc3BvbnNlGAcgASgLMgguQ2hhbm5lbEgAUhJnZXRDaGFubmVsUmVzcG9uc2USMAoTY29uZmlybV9zZXRfY2hhbm5lbBggIAEoCEgAUhFjb25maXJtU2V0Q2hhbm5lbBIsChFjb25maXJtX3NldF9yYWRpbxghIAEoCEgAUg9jb25maXJtU2V0UmFkaW9CCQoHdmFyaWFudA=='); 32 | -------------------------------------------------------------------------------- /lib/services/proto/admin.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: admin.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | export 'admin.pb.dart'; 9 | 10 | -------------------------------------------------------------------------------- /lib/services/proto/admin.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_package = "com.geeksville.mesh"; 4 | option optimize_for = LITE_RUNTIME; 5 | 6 | import "mesh.proto"; 7 | import "radioconfig.proto"; 8 | import "channel.proto"; 9 | 10 | option java_outer_classname = "AdminProtos"; 11 | 12 | /* 13 | * This message is handled by the Admin plugin and is responsible for all settings/channel read/write operations. This message 14 | * is used to do settings operations to both remote AND local nodes. 15 | * (Prior to 1.2 these operations were done via special ToRadio operations) 16 | */ 17 | message AdminMessage { 18 | 19 | oneof variant { 20 | 21 | /* 22 | * set the radio provisioning for this node 23 | */ 24 | RadioConfig set_radio = 1; 25 | 26 | /* 27 | * Set the owner for this node 28 | */ 29 | User set_owner = 2; 30 | 31 | /* 32 | * Set channels (using the new API). A special channel is the "primary channel". The other records are secondary channels. 33 | * Note: only one channel can be marked as primary. If the client sets a particular channel to be primary, the previous channel will be set to SECONDARY automatically */ 34 | Channel set_channel = 3; 35 | 36 | /* 37 | * Send the current RadioConfig in the response for this message 38 | */ 39 | bool get_radio_request = 4; 40 | RadioConfig get_radio_response = 5; 41 | 42 | /* 43 | * Send the specified channel in the response for this message 44 | * NOTE: This field is sent with the channel index + 1 (to ensure we never try to send 'zero' - which protobufs treats as not present) 45 | */ 46 | uint32 get_channel_request = 6; 47 | Channel get_channel_response = 7; 48 | 49 | /* Setting channels/radio config remotely carries the risk that you might 50 | * send an invalid config and the radio never talks to your mesh again. 51 | * Therefore if setting either of these properties remotely, you must send 52 | * a confirm_xxx message within 10 minutes. If you fail to do so, the radio 53 | * will assume loss of comms and revert your changes. 54 | * These messages are optional when changing the local node. 55 | */ 56 | bool confirm_set_channel = 32; 57 | bool confirm_set_radio = 33; 58 | } 59 | } -------------------------------------------------------------------------------- /lib/services/proto/apponly.pb.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: apponly.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | import 'dart:core' as $core; 9 | 10 | import 'package:protobuf/protobuf.dart' as $pb; 11 | 12 | import 'mesh.pb.dart' as $0; 13 | import 'channel.pb.dart' as $1; 14 | 15 | class ServiceEnvelope extends $pb.GeneratedMessage { 16 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ServiceEnvelope', createEmptyInstance: create) 17 | ..aOM<$0.MeshPacket>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'packet', subBuilder: $0.MeshPacket.create) 18 | ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'channelId') 19 | ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gatewayId') 20 | ..hasRequiredFields = false 21 | ; 22 | 23 | ServiceEnvelope._() : super(); 24 | factory ServiceEnvelope({ 25 | $0.MeshPacket? packet, 26 | $core.String? channelId, 27 | $core.String? gatewayId, 28 | }) { 29 | final _result = create(); 30 | if (packet != null) { 31 | _result.packet = packet; 32 | } 33 | if (channelId != null) { 34 | _result.channelId = channelId; 35 | } 36 | if (gatewayId != null) { 37 | _result.gatewayId = gatewayId; 38 | } 39 | return _result; 40 | } 41 | factory ServiceEnvelope.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 42 | factory ServiceEnvelope.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 43 | @$core.Deprecated( 44 | 'Using this can add significant overhead to your binary. ' 45 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 46 | 'Will be removed in next major version') 47 | ServiceEnvelope clone() => ServiceEnvelope()..mergeFromMessage(this); 48 | @$core.Deprecated( 49 | 'Using this can add significant overhead to your binary. ' 50 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 51 | 'Will be removed in next major version') 52 | ServiceEnvelope copyWith(void Function(ServiceEnvelope) updates) => super.copyWith((message) => updates(message as ServiceEnvelope)) as ServiceEnvelope; // ignore: deprecated_member_use 53 | $pb.BuilderInfo get info_ => _i; 54 | @$core.pragma('dart2js:noInline') 55 | static ServiceEnvelope create() => ServiceEnvelope._(); 56 | ServiceEnvelope createEmptyInstance() => create(); 57 | static $pb.PbList createRepeated() => $pb.PbList(); 58 | @$core.pragma('dart2js:noInline') 59 | static ServiceEnvelope getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 60 | static ServiceEnvelope? _defaultInstance; 61 | 62 | @$pb.TagNumber(1) 63 | $0.MeshPacket get packet => $_getN(0); 64 | @$pb.TagNumber(1) 65 | set packet($0.MeshPacket v) { setField(1, v); } 66 | @$pb.TagNumber(1) 67 | $core.bool hasPacket() => $_has(0); 68 | @$pb.TagNumber(1) 69 | void clearPacket() => clearField(1); 70 | @$pb.TagNumber(1) 71 | $0.MeshPacket ensurePacket() => $_ensure(0); 72 | 73 | @$pb.TagNumber(2) 74 | $core.String get channelId => $_getSZ(1); 75 | @$pb.TagNumber(2) 76 | set channelId($core.String v) { $_setString(1, v); } 77 | @$pb.TagNumber(2) 78 | $core.bool hasChannelId() => $_has(1); 79 | @$pb.TagNumber(2) 80 | void clearChannelId() => clearField(2); 81 | 82 | @$pb.TagNumber(3) 83 | $core.String get gatewayId => $_getSZ(2); 84 | @$pb.TagNumber(3) 85 | set gatewayId($core.String v) { $_setString(2, v); } 86 | @$pb.TagNumber(3) 87 | $core.bool hasGatewayId() => $_has(2); 88 | @$pb.TagNumber(3) 89 | void clearGatewayId() => clearField(3); 90 | } 91 | 92 | class ChannelSet extends $pb.GeneratedMessage { 93 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ChannelSet', createEmptyInstance: create) 94 | ..pc<$1.ChannelSettings>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'settings', $pb.PbFieldType.PM, subBuilder: $1.ChannelSettings.create) 95 | ..hasRequiredFields = false 96 | ; 97 | 98 | ChannelSet._() : super(); 99 | factory ChannelSet({ 100 | $core.Iterable<$1.ChannelSettings>? settings, 101 | }) { 102 | final _result = create(); 103 | if (settings != null) { 104 | _result.settings.addAll(settings); 105 | } 106 | return _result; 107 | } 108 | factory ChannelSet.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 109 | factory ChannelSet.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 110 | @$core.Deprecated( 111 | 'Using this can add significant overhead to your binary. ' 112 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 113 | 'Will be removed in next major version') 114 | ChannelSet clone() => ChannelSet()..mergeFromMessage(this); 115 | @$core.Deprecated( 116 | 'Using this can add significant overhead to your binary. ' 117 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 118 | 'Will be removed in next major version') 119 | ChannelSet copyWith(void Function(ChannelSet) updates) => super.copyWith((message) => updates(message as ChannelSet)) as ChannelSet; // ignore: deprecated_member_use 120 | $pb.BuilderInfo get info_ => _i; 121 | @$core.pragma('dart2js:noInline') 122 | static ChannelSet create() => ChannelSet._(); 123 | ChannelSet createEmptyInstance() => create(); 124 | static $pb.PbList createRepeated() => $pb.PbList(); 125 | @$core.pragma('dart2js:noInline') 126 | static ChannelSet getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 127 | static ChannelSet? _defaultInstance; 128 | 129 | @$pb.TagNumber(1) 130 | $core.List<$1.ChannelSettings> get settings => $_getList(0); 131 | } 132 | 133 | -------------------------------------------------------------------------------- /lib/services/proto/apponly.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: apponly.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | -------------------------------------------------------------------------------- /lib/services/proto/apponly.pbjson.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: apponly.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | import 'dart:core' as $core; 9 | import 'dart:convert' as $convert; 10 | import 'dart:typed_data' as $typed_data; 11 | @$core.Deprecated('Use serviceEnvelopeDescriptor instead') 12 | const ServiceEnvelope$json = const { 13 | '1': 'ServiceEnvelope', 14 | '2': const [ 15 | const {'1': 'packet', '3': 1, '4': 1, '5': 11, '6': '.MeshPacket', '10': 'packet'}, 16 | const {'1': 'channel_id', '3': 2, '4': 1, '5': 9, '10': 'channelId'}, 17 | const {'1': 'gateway_id', '3': 3, '4': 1, '5': 9, '10': 'gatewayId'}, 18 | ], 19 | }; 20 | 21 | /// Descriptor for `ServiceEnvelope`. Decode as a `google.protobuf.DescriptorProto`. 22 | final $typed_data.Uint8List serviceEnvelopeDescriptor = $convert.base64Decode('Cg9TZXJ2aWNlRW52ZWxvcGUSIwoGcGFja2V0GAEgASgLMgsuTWVzaFBhY2tldFIGcGFja2V0Eh0KCmNoYW5uZWxfaWQYAiABKAlSCWNoYW5uZWxJZBIdCgpnYXRld2F5X2lkGAMgASgJUglnYXRld2F5SWQ='); 23 | @$core.Deprecated('Use channelSetDescriptor instead') 24 | const ChannelSet$json = const { 25 | '1': 'ChannelSet', 26 | '2': const [ 27 | const {'1': 'settings', '3': 1, '4': 3, '5': 11, '6': '.ChannelSettings', '10': 'settings'}, 28 | ], 29 | }; 30 | 31 | /// Descriptor for `ChannelSet`. Decode as a `google.protobuf.DescriptorProto`. 32 | final $typed_data.Uint8List channelSetDescriptor = $convert.base64Decode('CgpDaGFubmVsU2V0EiwKCHNldHRpbmdzGAEgAygLMhAuQ2hhbm5lbFNldHRpbmdzUghzZXR0aW5ncw=='); 33 | -------------------------------------------------------------------------------- /lib/services/proto/apponly.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: apponly.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | export 'apponly.pb.dart'; 9 | 10 | -------------------------------------------------------------------------------- /lib/services/proto/apponly.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_package = "com.geeksville.mesh"; 4 | option optimize_for = LITE_RUNTIME; 5 | 6 | import "mesh.proto"; 7 | import "channel.proto"; 8 | 9 | option java_outer_classname = "AppOnlyProtos"; 10 | 11 | /* 12 | * This message wraps a MeshPacket with extra metadata about the sender and how it arrived. 13 | */ 14 | message ServiceEnvelope { 15 | /* 16 | * The (probably encrypted) packet 17 | */ 18 | MeshPacket packet = 1; 19 | 20 | /* 21 | * The global channel ID it was sent on 22 | */ 23 | string channel_id = 2; 24 | 25 | /* 26 | * The sending gateway. Can we use this to authenticate/prevent fake 27 | * nodeid impersonation for senders? - i.e. use gateway/mesh id (which is authenticated) + local node id as 28 | * the globally trusted nodenum 29 | */ 30 | string gateway_id = 3; 31 | } 32 | 33 | /* 34 | * This is the most compact possible representation for a set of channels. It includes only one PRIMARY channel (which must be first) and 35 | * any SECONDARY channels. No DISABLED channels are included. 36 | * This abstraction is used only on the the 'app side' of the world (ie python, javascript and android etc) to show a group of Channels as a (long) URL 37 | */ 38 | message ChannelSet { 39 | repeated ChannelSettings settings = 1; 40 | } -------------------------------------------------------------------------------- /lib/services/proto/channel.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: channel.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | // ignore_for_file: UNDEFINED_SHOWN_NAME 9 | import 'dart:core' as $core; 10 | import 'package:protobuf/protobuf.dart' as $pb; 11 | 12 | class ChannelSettings_ModemConfig extends $pb.ProtobufEnum { 13 | static const ChannelSettings_ModemConfig Bw125Cr45Sf128 = ChannelSettings_ModemConfig._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Bw125Cr45Sf128'); 14 | static const ChannelSettings_ModemConfig Bw500Cr45Sf128 = ChannelSettings_ModemConfig._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Bw500Cr45Sf128'); 15 | static const ChannelSettings_ModemConfig Bw31_25Cr48Sf512 = ChannelSettings_ModemConfig._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Bw31_25Cr48Sf512'); 16 | static const ChannelSettings_ModemConfig Bw125Cr48Sf4096 = ChannelSettings_ModemConfig._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Bw125Cr48Sf4096'); 17 | 18 | static const $core.List values = [ 19 | Bw125Cr45Sf128, 20 | Bw500Cr45Sf128, 21 | Bw31_25Cr48Sf512, 22 | Bw125Cr48Sf4096, 23 | ]; 24 | 25 | static final $core.Map<$core.int, ChannelSettings_ModemConfig> _byValue = $pb.ProtobufEnum.initByValue(values); 26 | static ChannelSettings_ModemConfig? valueOf($core.int value) => _byValue[value]; 27 | 28 | const ChannelSettings_ModemConfig._($core.int v, $core.String n) : super(v, n); 29 | } 30 | 31 | class Channel_Role extends $pb.ProtobufEnum { 32 | static const Channel_Role DISABLED = Channel_Role._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DISABLED'); 33 | static const Channel_Role PRIMARY = Channel_Role._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PRIMARY'); 34 | static const Channel_Role SECONDARY = Channel_Role._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SECONDARY'); 35 | 36 | static const $core.List values = [ 37 | DISABLED, 38 | PRIMARY, 39 | SECONDARY, 40 | ]; 41 | 42 | static final $core.Map<$core.int, Channel_Role> _byValue = $pb.ProtobufEnum.initByValue(values); 43 | static Channel_Role? valueOf($core.int value) => _byValue[value]; 44 | 45 | const Channel_Role._($core.int v, $core.String n) : super(v, n); 46 | } 47 | 48 | -------------------------------------------------------------------------------- /lib/services/proto/channel.pbjson.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: channel.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | import 'dart:core' as $core; 9 | import 'dart:convert' as $convert; 10 | import 'dart:typed_data' as $typed_data; 11 | @$core.Deprecated('Use channelSettingsDescriptor instead') 12 | const ChannelSettings$json = const { 13 | '1': 'ChannelSettings', 14 | '2': const [ 15 | const {'1': 'tx_power', '3': 1, '4': 1, '5': 5, '10': 'txPower'}, 16 | const {'1': 'modem_config', '3': 3, '4': 1, '5': 14, '6': '.ChannelSettings.ModemConfig', '10': 'modemConfig'}, 17 | const {'1': 'bandwidth', '3': 6, '4': 1, '5': 13, '10': 'bandwidth'}, 18 | const {'1': 'spread_factor', '3': 7, '4': 1, '5': 13, '10': 'spreadFactor'}, 19 | const {'1': 'coding_rate', '3': 8, '4': 1, '5': 13, '10': 'codingRate'}, 20 | const {'1': 'channel_num', '3': 9, '4': 1, '5': 13, '10': 'channelNum'}, 21 | const {'1': 'psk', '3': 4, '4': 1, '5': 12, '10': 'psk'}, 22 | const {'1': 'name', '3': 5, '4': 1, '5': 9, '10': 'name'}, 23 | const {'1': 'id', '3': 10, '4': 1, '5': 7, '10': 'id'}, 24 | const {'1': 'uplink_enabled', '3': 16, '4': 1, '5': 8, '10': 'uplinkEnabled'}, 25 | const {'1': 'downlink_enabled', '3': 17, '4': 1, '5': 8, '10': 'downlinkEnabled'}, 26 | ], 27 | '4': const [ChannelSettings_ModemConfig$json], 28 | }; 29 | 30 | @$core.Deprecated('Use channelSettingsDescriptor instead') 31 | const ChannelSettings_ModemConfig$json = const { 32 | '1': 'ModemConfig', 33 | '2': const [ 34 | const {'1': 'Bw125Cr45Sf128', '2': 0}, 35 | const {'1': 'Bw500Cr45Sf128', '2': 1}, 36 | const {'1': 'Bw31_25Cr48Sf512', '2': 2}, 37 | const {'1': 'Bw125Cr48Sf4096', '2': 3}, 38 | ], 39 | }; 40 | 41 | /// Descriptor for `ChannelSettings`. Decode as a `google.protobuf.DescriptorProto`. 42 | final $typed_data.Uint8List channelSettingsDescriptor = $convert.base64Decode('Cg9DaGFubmVsU2V0dGluZ3MSGQoIdHhfcG93ZXIYASABKAVSB3R4UG93ZXISPwoMbW9kZW1fY29uZmlnGAMgASgOMhwuQ2hhbm5lbFNldHRpbmdzLk1vZGVtQ29uZmlnUgttb2RlbUNvbmZpZxIcCgliYW5kd2lkdGgYBiABKA1SCWJhbmR3aWR0aBIjCg1zcHJlYWRfZmFjdG9yGAcgASgNUgxzcHJlYWRGYWN0b3ISHwoLY29kaW5nX3JhdGUYCCABKA1SCmNvZGluZ1JhdGUSHwoLY2hhbm5lbF9udW0YCSABKA1SCmNoYW5uZWxOdW0SEAoDcHNrGAQgASgMUgNwc2sSEgoEbmFtZRgFIAEoCVIEbmFtZRIOCgJpZBgKIAEoB1ICaWQSJQoOdXBsaW5rX2VuYWJsZWQYECABKAhSDXVwbGlua0VuYWJsZWQSKQoQZG93bmxpbmtfZW5hYmxlZBgRIAEoCFIPZG93bmxpbmtFbmFibGVkImAKC01vZGVtQ29uZmlnEhIKDkJ3MTI1Q3I0NVNmMTI4EAASEgoOQnc1MDBDcjQ1U2YxMjgQARIUChBCdzMxXzI1Q3I0OFNmNTEyEAISEwoPQncxMjVDcjQ4U2Y0MDk2EAM='); 43 | @$core.Deprecated('Use channelDescriptor instead') 44 | const Channel$json = const { 45 | '1': 'Channel', 46 | '2': const [ 47 | const {'1': 'index', '3': 1, '4': 1, '5': 5, '10': 'index'}, 48 | const {'1': 'settings', '3': 2, '4': 1, '5': 11, '6': '.ChannelSettings', '10': 'settings'}, 49 | const {'1': 'role', '3': 3, '4': 1, '5': 14, '6': '.Channel.Role', '10': 'role'}, 50 | ], 51 | '4': const [Channel_Role$json], 52 | }; 53 | 54 | @$core.Deprecated('Use channelDescriptor instead') 55 | const Channel_Role$json = const { 56 | '1': 'Role', 57 | '2': const [ 58 | const {'1': 'DISABLED', '2': 0}, 59 | const {'1': 'PRIMARY', '2': 1}, 60 | const {'1': 'SECONDARY', '2': 2}, 61 | ], 62 | }; 63 | 64 | /// Descriptor for `Channel`. Decode as a `google.protobuf.DescriptorProto`. 65 | final $typed_data.Uint8List channelDescriptor = $convert.base64Decode('CgdDaGFubmVsEhQKBWluZGV4GAEgASgFUgVpbmRleBIsCghzZXR0aW5ncxgCIAEoCzIQLkNoYW5uZWxTZXR0aW5nc1IIc2V0dGluZ3MSIQoEcm9sZRgDIAEoDjINLkNoYW5uZWwuUm9sZVIEcm9sZSIwCgRSb2xlEgwKCERJU0FCTEVEEAASCwoHUFJJTUFSWRABEg0KCVNFQ09OREFSWRAC'); 66 | -------------------------------------------------------------------------------- /lib/services/proto/channel.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: channel.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | export 'channel.pb.dart'; 9 | 10 | -------------------------------------------------------------------------------- /lib/services/proto/deviceonly.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: deviceonly.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | -------------------------------------------------------------------------------- /lib/services/proto/deviceonly.pbjson.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: deviceonly.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | import 'dart:core' as $core; 9 | import 'dart:convert' as $convert; 10 | import 'dart:typed_data' as $typed_data; 11 | @$core.Deprecated('Use legacyRadioConfigDescriptor instead') 12 | const LegacyRadioConfig$json = const { 13 | '1': 'LegacyRadioConfig', 14 | '2': const [ 15 | const {'1': 'preferences', '3': 1, '4': 1, '5': 11, '6': '.LegacyRadioConfig.LegacyPreferences', '10': 'preferences'}, 16 | ], 17 | '3': const [LegacyRadioConfig_LegacyPreferences$json], 18 | }; 19 | 20 | @$core.Deprecated('Use legacyRadioConfigDescriptor instead') 21 | const LegacyRadioConfig_LegacyPreferences$json = const { 22 | '1': 'LegacyPreferences', 23 | '2': const [ 24 | const {'1': 'region', '3': 15, '4': 1, '5': 14, '6': '.RegionCode', '10': 'region'}, 25 | ], 26 | }; 27 | 28 | /// Descriptor for `LegacyRadioConfig`. Decode as a `google.protobuf.DescriptorProto`. 29 | final $typed_data.Uint8List legacyRadioConfigDescriptor = $convert.base64Decode('ChFMZWdhY3lSYWRpb0NvbmZpZxJGCgtwcmVmZXJlbmNlcxgBIAEoCzIkLkxlZ2FjeVJhZGlvQ29uZmlnLkxlZ2FjeVByZWZlcmVuY2VzUgtwcmVmZXJlbmNlcxo4ChFMZWdhY3lQcmVmZXJlbmNlcxIjCgZyZWdpb24YDyABKA4yCy5SZWdpb25Db2RlUgZyZWdpb24='); 30 | @$core.Deprecated('Use deviceStateDescriptor instead') 31 | const DeviceState$json = const { 32 | '1': 'DeviceState', 33 | '2': const [ 34 | const {'1': 'legacyRadio', '3': 1, '4': 1, '5': 11, '6': '.LegacyRadioConfig', '10': 'legacyRadio'}, 35 | const {'1': 'my_node', '3': 2, '4': 1, '5': 11, '6': '.MyNodeInfo', '10': 'myNode'}, 36 | const {'1': 'owner', '3': 3, '4': 1, '5': 11, '6': '.User', '10': 'owner'}, 37 | const {'1': 'node_db', '3': 4, '4': 3, '5': 11, '6': '.NodeInfo', '10': 'nodeDb'}, 38 | const {'1': 'receive_queue', '3': 5, '4': 3, '5': 11, '6': '.MeshPacket', '10': 'receiveQueue'}, 39 | const {'1': 'version', '3': 8, '4': 1, '5': 13, '10': 'version'}, 40 | const {'1': 'rx_text_message', '3': 7, '4': 1, '5': 11, '6': '.MeshPacket', '10': 'rxTextMessage'}, 41 | const {'1': 'no_save', '3': 9, '4': 1, '5': 8, '10': 'noSave'}, 42 | const {'1': 'did_gps_reset', '3': 11, '4': 1, '5': 8, '10': 'didGpsReset'}, 43 | ], 44 | '9': const [ 45 | const {'1': 12, '2': 13}, 46 | ], 47 | }; 48 | 49 | /// Descriptor for `DeviceState`. Decode as a `google.protobuf.DescriptorProto`. 50 | final $typed_data.Uint8List deviceStateDescriptor = $convert.base64Decode('CgtEZXZpY2VTdGF0ZRI0CgtsZWdhY3lSYWRpbxgBIAEoCzISLkxlZ2FjeVJhZGlvQ29uZmlnUgtsZWdhY3lSYWRpbxIkCgdteV9ub2RlGAIgASgLMgsuTXlOb2RlSW5mb1IGbXlOb2RlEhsKBW93bmVyGAMgASgLMgUuVXNlclIFb3duZXISIgoHbm9kZV9kYhgEIAMoCzIJLk5vZGVJbmZvUgZub2RlRGISMAoNcmVjZWl2ZV9xdWV1ZRgFIAMoCzILLk1lc2hQYWNrZXRSDHJlY2VpdmVRdWV1ZRIYCgd2ZXJzaW9uGAggASgNUgd2ZXJzaW9uEjMKD3J4X3RleHRfbWVzc2FnZRgHIAEoCzILLk1lc2hQYWNrZXRSDXJ4VGV4dE1lc3NhZ2USFwoHbm9fc2F2ZRgJIAEoCFIGbm9TYXZlEiIKDWRpZF9ncHNfcmVzZXQYCyABKAhSC2RpZEdwc1Jlc2V0SgQIDBAN'); 51 | @$core.Deprecated('Use channelFileDescriptor instead') 52 | const ChannelFile$json = const { 53 | '1': 'ChannelFile', 54 | '2': const [ 55 | const {'1': 'channels', '3': 1, '4': 3, '5': 11, '6': '.Channel', '10': 'channels'}, 56 | ], 57 | }; 58 | 59 | /// Descriptor for `ChannelFile`. Decode as a `google.protobuf.DescriptorProto`. 60 | final $typed_data.Uint8List channelFileDescriptor = $convert.base64Decode('CgtDaGFubmVsRmlsZRIkCghjaGFubmVscxgBIAMoCzIILkNoYW5uZWxSCGNoYW5uZWxz'); 61 | -------------------------------------------------------------------------------- /lib/services/proto/deviceonly.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: deviceonly.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | export 'deviceonly.pb.dart'; 9 | 10 | -------------------------------------------------------------------------------- /lib/services/proto/deviceonly.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_package = "com.geeksville.mesh"; 4 | option optimize_for = LITE_RUNTIME; 5 | 6 | import "mesh.proto"; 7 | import "channel.proto"; 8 | import "radioconfig.proto"; 9 | 10 | option java_outer_classname = "DeviceOnly"; 11 | 12 | /* This is a stub version of the old 1.1 representation of RadioConfig. But only keeping the region info. The device firmware uses 13 | this stub while migrating old nodes to the new preferences system. 14 | */ 15 | message LegacyRadioConfig { 16 | message LegacyPreferences { 17 | /* 18 | * The region code for my radio (US, CN, EU433, etc...) 19 | */ 20 | RegionCode region = 15; 21 | } 22 | 23 | LegacyPreferences preferences = 1; 24 | } 25 | 26 | /* 27 | * This message is never sent over the wire, but it is used for serializing DB 28 | * state to flash in the device code 29 | * FIXME, since we write this each time we enter deep sleep (and have infinite 30 | * flash) it would be better to use some sort of append only data structure for 31 | * the receive queue and use the preferences store for the other stuff 32 | */ 33 | message DeviceState { 34 | 35 | /* 36 | * Was secondary_channels before 1.2 37 | */ 38 | reserved 12; 39 | 40 | // Moved to its own file, but we keep this here so we can automatically migrate old radio.region settings 41 | LegacyRadioConfig legacyRadio = 1; 42 | 43 | /* 44 | * Read only settings/info about this node 45 | */ 46 | MyNodeInfo my_node = 2; 47 | 48 | /* 49 | * My owner info 50 | */ 51 | User owner = 3; 52 | 53 | repeated NodeInfo node_db = 4; 54 | 55 | /* 56 | * Received packets saved for delivery to the phone 57 | */ 58 | repeated MeshPacket receive_queue = 5; 59 | 60 | /* 61 | * A version integer used to invalidate old save files when we make 62 | * incompatible changes This integer is set at build time and is private to 63 | * NodeDB.cpp in the device code. 64 | */ 65 | uint32 version = 8; 66 | 67 | /* 68 | * We keep the last received text message (only) stored in the device flash, 69 | * so we can show it on the screen. 70 | * Might be null 71 | */ 72 | MeshPacket rx_text_message = 7; 73 | 74 | /* 75 | * Used only during development. Indicates developer is testing and changes 76 | * should never be saved to flash. 77 | */ 78 | bool no_save = 9; 79 | 80 | /* 81 | * Some GPSes seem to have bogus settings from the factory, so we always do one factory reset. 82 | */ 83 | bool did_gps_reset = 11; 84 | } 85 | 86 | /** The on-disk saved channels */ 87 | message ChannelFile { 88 | /* 89 | * The channels our node knows about 90 | */ 91 | repeated Channel channels = 1; 92 | } -------------------------------------------------------------------------------- /lib/services/proto/environmental_measurement.pb.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: environmental_measurement.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | import 'dart:core' as $core; 9 | 10 | import 'package:protobuf/protobuf.dart' as $pb; 11 | 12 | class EnvironmentalMeasurement extends $pb.GeneratedMessage { 13 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'EnvironmentalMeasurement', createEmptyInstance: create) 14 | ..a<$core.double>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'temperature', $pb.PbFieldType.OF) 15 | ..a<$core.double>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'relativeHumidity', $pb.PbFieldType.OF) 16 | ..a<$core.double>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'barometricPressure', $pb.PbFieldType.OF) 17 | ..hasRequiredFields = false 18 | ; 19 | 20 | EnvironmentalMeasurement._() : super(); 21 | factory EnvironmentalMeasurement({ 22 | $core.double? temperature, 23 | $core.double? relativeHumidity, 24 | $core.double? barometricPressure, 25 | }) { 26 | final _result = create(); 27 | if (temperature != null) { 28 | _result.temperature = temperature; 29 | } 30 | if (relativeHumidity != null) { 31 | _result.relativeHumidity = relativeHumidity; 32 | } 33 | if (barometricPressure != null) { 34 | _result.barometricPressure = barometricPressure; 35 | } 36 | return _result; 37 | } 38 | factory EnvironmentalMeasurement.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 39 | factory EnvironmentalMeasurement.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 40 | @$core.Deprecated( 41 | 'Using this can add significant overhead to your binary. ' 42 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 43 | 'Will be removed in next major version') 44 | EnvironmentalMeasurement clone() => EnvironmentalMeasurement()..mergeFromMessage(this); 45 | @$core.Deprecated( 46 | 'Using this can add significant overhead to your binary. ' 47 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 48 | 'Will be removed in next major version') 49 | EnvironmentalMeasurement copyWith(void Function(EnvironmentalMeasurement) updates) => super.copyWith((message) => updates(message as EnvironmentalMeasurement)) as EnvironmentalMeasurement; // ignore: deprecated_member_use 50 | $pb.BuilderInfo get info_ => _i; 51 | @$core.pragma('dart2js:noInline') 52 | static EnvironmentalMeasurement create() => EnvironmentalMeasurement._(); 53 | EnvironmentalMeasurement createEmptyInstance() => create(); 54 | static $pb.PbList createRepeated() => $pb.PbList(); 55 | @$core.pragma('dart2js:noInline') 56 | static EnvironmentalMeasurement getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 57 | static EnvironmentalMeasurement? _defaultInstance; 58 | 59 | @$pb.TagNumber(1) 60 | $core.double get temperature => $_getN(0); 61 | @$pb.TagNumber(1) 62 | set temperature($core.double v) { $_setFloat(0, v); } 63 | @$pb.TagNumber(1) 64 | $core.bool hasTemperature() => $_has(0); 65 | @$pb.TagNumber(1) 66 | void clearTemperature() => clearField(1); 67 | 68 | @$pb.TagNumber(2) 69 | $core.double get relativeHumidity => $_getN(1); 70 | @$pb.TagNumber(2) 71 | set relativeHumidity($core.double v) { $_setFloat(1, v); } 72 | @$pb.TagNumber(2) 73 | $core.bool hasRelativeHumidity() => $_has(1); 74 | @$pb.TagNumber(2) 75 | void clearRelativeHumidity() => clearField(2); 76 | 77 | @$pb.TagNumber(3) 78 | $core.double get barometricPressure => $_getN(2); 79 | @$pb.TagNumber(3) 80 | set barometricPressure($core.double v) { $_setFloat(2, v); } 81 | @$pb.TagNumber(3) 82 | $core.bool hasBarometricPressure() => $_has(2); 83 | @$pb.TagNumber(3) 84 | void clearBarometricPressure() => clearField(3); 85 | } 86 | 87 | -------------------------------------------------------------------------------- /lib/services/proto/environmental_measurement.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: environmental_measurement.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | -------------------------------------------------------------------------------- /lib/services/proto/environmental_measurement.pbjson.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: environmental_measurement.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | import 'dart:core' as $core; 9 | import 'dart:convert' as $convert; 10 | import 'dart:typed_data' as $typed_data; 11 | @$core.Deprecated('Use environmentalMeasurementDescriptor instead') 12 | const EnvironmentalMeasurement$json = const { 13 | '1': 'EnvironmentalMeasurement', 14 | '2': const [ 15 | const {'1': 'temperature', '3': 1, '4': 1, '5': 2, '10': 'temperature'}, 16 | const {'1': 'relative_humidity', '3': 2, '4': 1, '5': 2, '10': 'relativeHumidity'}, 17 | const {'1': 'barometric_pressure', '3': 3, '4': 1, '5': 2, '10': 'barometricPressure'}, 18 | ], 19 | }; 20 | 21 | /// Descriptor for `EnvironmentalMeasurement`. Decode as a `google.protobuf.DescriptorProto`. 22 | final $typed_data.Uint8List environmentalMeasurementDescriptor = $convert.base64Decode('ChhFbnZpcm9ubWVudGFsTWVhc3VyZW1lbnQSIAoLdGVtcGVyYXR1cmUYASABKAJSC3RlbXBlcmF0dXJlEisKEXJlbGF0aXZlX2h1bWlkaXR5GAIgASgCUhByZWxhdGl2ZUh1bWlkaXR5Ei8KE2Jhcm9tZXRyaWNfcHJlc3N1cmUYAyABKAJSEmJhcm9tZXRyaWNQcmVzc3VyZQ=='); 23 | -------------------------------------------------------------------------------- /lib/services/proto/environmental_measurement.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: environmental_measurement.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | export 'environmental_measurement.pb.dart'; 9 | 10 | -------------------------------------------------------------------------------- /lib/services/proto/environmental_measurement.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message EnvironmentalMeasurement { 4 | 5 | float temperature = 1; 6 | float relative_humidity = 2; 7 | float barometric_pressure = 3; 8 | 9 | } -------------------------------------------------------------------------------- /lib/services/proto/mesh.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: mesh.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | export 'mesh.pb.dart'; 9 | 10 | -------------------------------------------------------------------------------- /lib/services/proto/portnums.pb.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: portnums.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | import 'dart:core' as $core; 9 | 10 | export 'portnums.pbenum.dart'; 11 | 12 | -------------------------------------------------------------------------------- /lib/services/proto/portnums.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: portnums.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | // ignore_for_file: UNDEFINED_SHOWN_NAME 9 | import 'dart:core' as $core; 10 | import 'package:protobuf/protobuf.dart' as $pb; 11 | 12 | class PortNum extends $pb.ProtobufEnum { 13 | static const PortNum UNKNOWN_APP = PortNum._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UNKNOWN_APP'); 14 | static const PortNum TEXT_MESSAGE_APP = PortNum._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TEXT_MESSAGE_APP'); 15 | static const PortNum REMOTE_HARDWARE_APP = PortNum._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'REMOTE_HARDWARE_APP'); 16 | static const PortNum POSITION_APP = PortNum._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'POSITION_APP'); 17 | static const PortNum NODEINFO_APP = PortNum._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NODEINFO_APP'); 18 | static const PortNum ROUTING_APP = PortNum._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ROUTING_APP'); 19 | static const PortNum ADMIN_APP = PortNum._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ADMIN_APP'); 20 | static const PortNum REPLY_APP = PortNum._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'REPLY_APP'); 21 | static const PortNum IP_TUNNEL_APP = PortNum._(33, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'IP_TUNNEL_APP'); 22 | static const PortNum SERIAL_APP = PortNum._(64, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SERIAL_APP'); 23 | static const PortNum STORE_FORWARD_APP = PortNum._(65, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'STORE_FORWARD_APP'); 24 | static const PortNum RANGE_TEST_APP = PortNum._(66, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RANGE_TEST_APP'); 25 | static const PortNum ENVIRONMENTAL_MEASUREMENT_APP = PortNum._(67, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ENVIRONMENTAL_MEASUREMENT_APP'); 26 | static const PortNum PRIVATE_APP = PortNum._(256, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PRIVATE_APP'); 27 | static const PortNum ATAK_FORWARDER = PortNum._(257, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ATAK_FORWARDER'); 28 | static const PortNum MAX = PortNum._(511, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MAX'); 29 | 30 | static const $core.List values = [ 31 | UNKNOWN_APP, 32 | TEXT_MESSAGE_APP, 33 | REMOTE_HARDWARE_APP, 34 | POSITION_APP, 35 | NODEINFO_APP, 36 | ROUTING_APP, 37 | ADMIN_APP, 38 | REPLY_APP, 39 | IP_TUNNEL_APP, 40 | SERIAL_APP, 41 | STORE_FORWARD_APP, 42 | RANGE_TEST_APP, 43 | ENVIRONMENTAL_MEASUREMENT_APP, 44 | PRIVATE_APP, 45 | ATAK_FORWARDER, 46 | MAX, 47 | ]; 48 | 49 | static final $core.Map<$core.int, PortNum> _byValue = $pb.ProtobufEnum.initByValue(values); 50 | static PortNum? valueOf($core.int value) => _byValue[value]; 51 | 52 | const PortNum._($core.int v, $core.String n) : super(v, n); 53 | } 54 | 55 | -------------------------------------------------------------------------------- /lib/services/proto/portnums.pbjson.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: portnums.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | import 'dart:core' as $core; 9 | import 'dart:convert' as $convert; 10 | import 'dart:typed_data' as $typed_data; 11 | @$core.Deprecated('Use portNumDescriptor instead') 12 | const PortNum$json = const { 13 | '1': 'PortNum', 14 | '2': const [ 15 | const {'1': 'UNKNOWN_APP', '2': 0}, 16 | const {'1': 'TEXT_MESSAGE_APP', '2': 1}, 17 | const {'1': 'REMOTE_HARDWARE_APP', '2': 2}, 18 | const {'1': 'POSITION_APP', '2': 3}, 19 | const {'1': 'NODEINFO_APP', '2': 4}, 20 | const {'1': 'ROUTING_APP', '2': 5}, 21 | const {'1': 'ADMIN_APP', '2': 6}, 22 | const {'1': 'REPLY_APP', '2': 32}, 23 | const {'1': 'IP_TUNNEL_APP', '2': 33}, 24 | const {'1': 'SERIAL_APP', '2': 64}, 25 | const {'1': 'STORE_FORWARD_APP', '2': 65}, 26 | const {'1': 'RANGE_TEST_APP', '2': 66}, 27 | const {'1': 'ENVIRONMENTAL_MEASUREMENT_APP', '2': 67}, 28 | const {'1': 'PRIVATE_APP', '2': 256}, 29 | const {'1': 'ATAK_FORWARDER', '2': 257}, 30 | const {'1': 'MAX', '2': 511}, 31 | ], 32 | }; 33 | 34 | /// Descriptor for `PortNum`. Decode as a `google.protobuf.EnumDescriptorProto`. 35 | final $typed_data.Uint8List portNumDescriptor = $convert.base64Decode('CgdQb3J0TnVtEg8KC1VOS05PV05fQVBQEAASFAoQVEVYVF9NRVNTQUdFX0FQUBABEhcKE1JFTU9URV9IQVJEV0FSRV9BUFAQAhIQCgxQT1NJVElPTl9BUFAQAxIQCgxOT0RFSU5GT19BUFAQBBIPCgtST1VUSU5HX0FQUBAFEg0KCUFETUlOX0FQUBAGEg0KCVJFUExZX0FQUBAgEhEKDUlQX1RVTk5FTF9BUFAQIRIOCgpTRVJJQUxfQVBQEEASFQoRU1RPUkVfRk9SV0FSRF9BUFAQQRISCg5SQU5HRV9URVNUX0FQUBBCEiEKHUVOVklST05NRU5UQUxfTUVBU1VSRU1FTlRfQVBQEEMSEAoLUFJJVkFURV9BUFAQgAISEwoOQVRBS19GT1JXQVJERVIQgQISCAoDTUFYEP8D'); 36 | -------------------------------------------------------------------------------- /lib/services/proto/portnums.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: portnums.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | export 'portnums.pb.dart'; 9 | 10 | -------------------------------------------------------------------------------- /lib/services/proto/portnums.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_package = "com.geeksville.mesh"; 4 | option java_outer_classname = "Portnums"; 5 | option optimize_for = LITE_RUNTIME; 6 | 7 | /* 8 | * For any new 'apps' that run on the device or via sister apps on phones/PCs they should pick and use a 9 | * unique 'portnum' for their application. 10 | * 11 | * If you are making a new app using meshtastic, please send in a pull request to add your 'portnum' to this 12 | * master table. PortNums should be assigned in the following range: 13 | * 14 | * 0-63 Core Meshtastic use, do not use for third party apps 15 | * 64-127 Registered 3rd party apps, send in a pull request that adds a new entry to portnums.proto to register your application 16 | * 256-511 Use one of these portnums for your private applications that you don't want to register publically 17 | * 18 | * All other values are reserved. 19 | * 20 | * Note: This was formerly a Type enum named 'typ' with the same id # 21 | * 22 | * We have change to this 'portnum' based scheme for specifying app handlers for particular payloads. 23 | * This change is backwards compatible by treating the legacy OPAQUE/CLEAR_TEXT values identically. 24 | */ 25 | enum PortNum { 26 | 27 | /* 28 | * Deprecated: do not use in new code (formerly called OPAQUE) 29 | * A message sent from a device outside of the mesh, in a form the mesh does not understand 30 | * NOTE: This must be 0, because it is documented in IMeshService.aidl to be so 31 | */ 32 | UNKNOWN_APP = 0; 33 | 34 | /* 35 | * A simple UTF-8 text message, which even the little micros in the mesh 36 | * can understand and show on their screen eventually in some circumstances 37 | * even signal might send messages in this form (see below) 38 | * Formerly called CLEAR_TEXT 39 | */ 40 | TEXT_MESSAGE_APP = 1; 41 | 42 | /* 43 | * A message receive acknowledgment, sent in cleartext - allows radio to 44 | * show user that a message has been read by the recipient, optional 45 | * Note: this concept has been removed for now. Once READACK is implemented, use the 46 | * new packet type/port number stuff? 47 | * @exclude 48 | * CLEAR_READACK = 2; 49 | * Reserved for built-in GPIO/example app. 50 | * See remote_hardware.proto/HardwareMessage for details on the message sent/received to this port number 51 | */ 52 | REMOTE_HARDWARE_APP = 2; 53 | 54 | /* 55 | * The built-in position messaging app. 56 | * See Position for details on the message sent to this port number. 57 | * payload is a Position protobuf 58 | */ 59 | POSITION_APP = 3; 60 | 61 | /* 62 | * The built-in user info app. 63 | * See User for details on the message sent to this port number. 64 | * payload is a User protobuf 65 | */ 66 | NODEINFO_APP = 4; 67 | 68 | /* 69 | * Protocol control packets for mesh protocol use, payload is a Routing protobuf 70 | */ 71 | ROUTING_APP = 5; 72 | 73 | /* 74 | * Admin control packets, payload is a AdminMessage protobuf 75 | */ 76 | ADMIN_APP = 6; 77 | 78 | /* 79 | * Provides a 'ping' service that replies to any packet it receives. 80 | * Also this serves as a small example plugin. 81 | */ 82 | REPLY_APP = 32; 83 | 84 | /* 85 | * Used for the python IP tunnel feature 86 | */ 87 | IP_TUNNEL_APP = 33; 88 | 89 | /* 90 | * Provides a hardware serial interface to send and receive from the Meshtastic network. 91 | * Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic 92 | * network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network. 93 | * Maximum packet size of 240 bytes. 94 | * Plugin is disabled by default can be turned on by setting SERIALPLUGIN_ENABLED = 1 in SerialPlugh.cpp. 95 | * Maintained by Jm Casler (MC Hamster) : jm@casler.org 96 | */ 97 | SERIAL_APP = 64; 98 | 99 | /* 100 | * STORE_FORWARD_APP (Work in Progress) 101 | * Maintained by Jm Casler (MC Hamster) : jm@casler.org 102 | */ 103 | STORE_FORWARD_APP = 65; 104 | 105 | /* 106 | * STORE_FORWARD_APP (Work in Progress) 107 | * Maintained by Jm Casler (MC Hamster) : jm@casler.org 108 | */ 109 | RANGE_TEST_APP = 66; 110 | 111 | /* 112 | * Provides a format to send and receive environmental data from the Meshtastic network. 113 | * Maintained by Charles Crossan (crossan007) : crossan007@gmail.com 114 | */ 115 | ENVIRONMENTAL_MEASUREMENT_APP = 67; 116 | 117 | /* 118 | * Private applications should use portnums >= 256. 119 | * To simplify initial development and testing you can use "PRIVATE_APP" 120 | * in your code without needing to rebuild protobuf files (via bin/regin_protos.sh) 121 | */ 122 | PRIVATE_APP = 256; 123 | 124 | /* 125 | * ATAK Forwarder Plugin https://github.com/paulmandal/atak-forwarder 126 | */ 127 | ATAK_FORWARDER = 257; 128 | 129 | /* 130 | * Currently we limit port nums to no higher than this value 131 | */ 132 | MAX = 511; 133 | } 134 | -------------------------------------------------------------------------------- /lib/services/proto/proto.dart: -------------------------------------------------------------------------------- 1 | 2 | export 'admin.pb.dart'; 3 | 4 | export 'apponly.pb.dart'; 5 | 6 | export 'channel.pb.dart'; 7 | 8 | export 'deviceonly.pb.dart'; 9 | 10 | export 'environmental_measurement.pb.dart'; 11 | 12 | export 'mesh.pb.dart'; 13 | 14 | export 'portnums.pb.dart'; 15 | 16 | export 'radioconfig.pb.dart'; 17 | 18 | export 'remote_hardware.pb.dart'; 19 | -------------------------------------------------------------------------------- /lib/services/proto/protoc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/lib/services/proto/protoc.exe -------------------------------------------------------------------------------- /lib/services/proto/radioconfig.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: radioconfig.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | // ignore_for_file: UNDEFINED_SHOWN_NAME 9 | import 'dart:core' as $core; 10 | import 'package:protobuf/protobuf.dart' as $pb; 11 | 12 | class RegionCode extends $pb.ProtobufEnum { 13 | static const RegionCode Unset = RegionCode._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unset'); 14 | static const RegionCode US = RegionCode._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'US'); 15 | static const RegionCode EU433 = RegionCode._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EU433'); 16 | static const RegionCode EU865 = RegionCode._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EU865'); 17 | static const RegionCode CN = RegionCode._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CN'); 18 | static const RegionCode JP = RegionCode._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'JP'); 19 | static const RegionCode ANZ = RegionCode._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ANZ'); 20 | static const RegionCode KR = RegionCode._(7, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'KR'); 21 | static const RegionCode TW = RegionCode._(8, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TW'); 22 | static const RegionCode RU = RegionCode._(9, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RU'); 23 | 24 | static const $core.List values = [ 25 | Unset, 26 | US, 27 | EU433, 28 | EU865, 29 | CN, 30 | JP, 31 | ANZ, 32 | KR, 33 | TW, 34 | RU, 35 | ]; 36 | 37 | static final $core.Map<$core.int, RegionCode> _byValue = $pb.ProtobufEnum.initByValue(values); 38 | static RegionCode? valueOf($core.int value) => _byValue[value]; 39 | 40 | const RegionCode._($core.int v, $core.String n) : super(v, n); 41 | } 42 | 43 | class ChargeCurrent extends $pb.ProtobufEnum { 44 | static const ChargeCurrent MAUnset = ChargeCurrent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MAUnset'); 45 | static const ChargeCurrent MA100 = ChargeCurrent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA100'); 46 | static const ChargeCurrent MA190 = ChargeCurrent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA190'); 47 | static const ChargeCurrent MA280 = ChargeCurrent._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA280'); 48 | static const ChargeCurrent MA360 = ChargeCurrent._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA360'); 49 | static const ChargeCurrent MA450 = ChargeCurrent._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA450'); 50 | static const ChargeCurrent MA550 = ChargeCurrent._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA550'); 51 | static const ChargeCurrent MA630 = ChargeCurrent._(7, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA630'); 52 | static const ChargeCurrent MA700 = ChargeCurrent._(8, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA700'); 53 | static const ChargeCurrent MA780 = ChargeCurrent._(9, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA780'); 54 | static const ChargeCurrent MA880 = ChargeCurrent._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA880'); 55 | static const ChargeCurrent MA960 = ChargeCurrent._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA960'); 56 | static const ChargeCurrent MA1000 = ChargeCurrent._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA1000'); 57 | static const ChargeCurrent MA1080 = ChargeCurrent._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA1080'); 58 | static const ChargeCurrent MA1160 = ChargeCurrent._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA1160'); 59 | static const ChargeCurrent MA1240 = ChargeCurrent._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA1240'); 60 | static const ChargeCurrent MA1320 = ChargeCurrent._(16, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MA1320'); 61 | 62 | static const $core.List values = [ 63 | MAUnset, 64 | MA100, 65 | MA190, 66 | MA280, 67 | MA360, 68 | MA450, 69 | MA550, 70 | MA630, 71 | MA700, 72 | MA780, 73 | MA880, 74 | MA960, 75 | MA1000, 76 | MA1080, 77 | MA1160, 78 | MA1240, 79 | MA1320, 80 | ]; 81 | 82 | static final $core.Map<$core.int, ChargeCurrent> _byValue = $pb.ProtobufEnum.initByValue(values); 83 | static ChargeCurrent? valueOf($core.int value) => _byValue[value]; 84 | 85 | const ChargeCurrent._($core.int v, $core.String n) : super(v, n); 86 | } 87 | 88 | class GpsOperation extends $pb.ProtobufEnum { 89 | static const GpsOperation GpsOpUnset = GpsOperation._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GpsOpUnset'); 90 | static const GpsOperation GpsOpStationary = GpsOperation._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GpsOpStationary'); 91 | static const GpsOperation GpsOpMobile = GpsOperation._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GpsOpMobile'); 92 | static const GpsOperation GpsOpTimeOnly = GpsOperation._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GpsOpTimeOnly'); 93 | static const GpsOperation GpsOpDisabled = GpsOperation._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GpsOpDisabled'); 94 | 95 | static const $core.List values = [ 96 | GpsOpUnset, 97 | GpsOpStationary, 98 | GpsOpMobile, 99 | GpsOpTimeOnly, 100 | GpsOpDisabled, 101 | ]; 102 | 103 | static final $core.Map<$core.int, GpsOperation> _byValue = $pb.ProtobufEnum.initByValue(values); 104 | static GpsOperation? valueOf($core.int value) => _byValue[value]; 105 | 106 | const GpsOperation._($core.int v, $core.String n) : super(v, n); 107 | } 108 | 109 | class LocationSharing extends $pb.ProtobufEnum { 110 | static const LocationSharing LocUnset = LocationSharing._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'LocUnset'); 111 | static const LocationSharing LocEnabled = LocationSharing._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'LocEnabled'); 112 | static const LocationSharing LocDisabled = LocationSharing._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'LocDisabled'); 113 | 114 | static const $core.List values = [ 115 | LocUnset, 116 | LocEnabled, 117 | LocDisabled, 118 | ]; 119 | 120 | static final $core.Map<$core.int, LocationSharing> _byValue = $pb.ProtobufEnum.initByValue(values); 121 | static LocationSharing? valueOf($core.int value) => _byValue[value]; 122 | 123 | const LocationSharing._($core.int v, $core.String n) : super(v, n); 124 | } 125 | 126 | class RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType extends $pb.ProtobufEnum { 127 | static const RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType DHT11 = RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DHT11'); 128 | 129 | static const $core.List values = [ 130 | DHT11, 131 | ]; 132 | 133 | static final $core.Map<$core.int, RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType> _byValue = $pb.ProtobufEnum.initByValue(values); 134 | static RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType? valueOf($core.int value) => _byValue[value]; 135 | 136 | const RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType._($core.int v, $core.String n) : super(v, n); 137 | } 138 | 139 | -------------------------------------------------------------------------------- /lib/services/proto/radioconfig.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: radioconfig.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | export 'radioconfig.pb.dart'; 9 | 10 | -------------------------------------------------------------------------------- /lib/services/proto/remote_hardware.pb.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: remote_hardware.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | import 'dart:core' as $core; 9 | 10 | import 'package:fixnum/fixnum.dart' as $fixnum; 11 | import 'package:protobuf/protobuf.dart' as $pb; 12 | 13 | import 'remote_hardware.pbenum.dart'; 14 | 15 | export 'remote_hardware.pbenum.dart'; 16 | 17 | class HardwareMessage extends $pb.GeneratedMessage { 18 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'HardwareMessage', createEmptyInstance: create) 19 | ..e(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typ', $pb.PbFieldType.OE, defaultOrMaker: HardwareMessage_Type.UNSET, valueOf: HardwareMessage_Type.valueOf, enumValues: HardwareMessage_Type.values) 20 | ..a<$fixnum.Int64>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gpioMask', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO) 21 | ..a<$fixnum.Int64>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gpioValue', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO) 22 | ..hasRequiredFields = false 23 | ; 24 | 25 | HardwareMessage._() : super(); 26 | factory HardwareMessage({ 27 | HardwareMessage_Type? typ, 28 | $fixnum.Int64? gpioMask, 29 | $fixnum.Int64? gpioValue, 30 | }) { 31 | final _result = create(); 32 | if (typ != null) { 33 | _result.typ = typ; 34 | } 35 | if (gpioMask != null) { 36 | _result.gpioMask = gpioMask; 37 | } 38 | if (gpioValue != null) { 39 | _result.gpioValue = gpioValue; 40 | } 41 | return _result; 42 | } 43 | factory HardwareMessage.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 44 | factory HardwareMessage.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 45 | @$core.Deprecated( 46 | 'Using this can add significant overhead to your binary. ' 47 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 48 | 'Will be removed in next major version') 49 | HardwareMessage clone() => HardwareMessage()..mergeFromMessage(this); 50 | @$core.Deprecated( 51 | 'Using this can add significant overhead to your binary. ' 52 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 53 | 'Will be removed in next major version') 54 | HardwareMessage copyWith(void Function(HardwareMessage) updates) => super.copyWith((message) => updates(message as HardwareMessage)) as HardwareMessage; // ignore: deprecated_member_use 55 | $pb.BuilderInfo get info_ => _i; 56 | @$core.pragma('dart2js:noInline') 57 | static HardwareMessage create() => HardwareMessage._(); 58 | HardwareMessage createEmptyInstance() => create(); 59 | static $pb.PbList createRepeated() => $pb.PbList(); 60 | @$core.pragma('dart2js:noInline') 61 | static HardwareMessage getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 62 | static HardwareMessage? _defaultInstance; 63 | 64 | @$pb.TagNumber(1) 65 | HardwareMessage_Type get typ => $_getN(0); 66 | @$pb.TagNumber(1) 67 | set typ(HardwareMessage_Type v) { setField(1, v); } 68 | @$pb.TagNumber(1) 69 | $core.bool hasTyp() => $_has(0); 70 | @$pb.TagNumber(1) 71 | void clearTyp() => clearField(1); 72 | 73 | @$pb.TagNumber(2) 74 | $fixnum.Int64 get gpioMask => $_getI64(1); 75 | @$pb.TagNumber(2) 76 | set gpioMask($fixnum.Int64 v) { $_setInt64(1, v); } 77 | @$pb.TagNumber(2) 78 | $core.bool hasGpioMask() => $_has(1); 79 | @$pb.TagNumber(2) 80 | void clearGpioMask() => clearField(2); 81 | 82 | @$pb.TagNumber(3) 83 | $fixnum.Int64 get gpioValue => $_getI64(2); 84 | @$pb.TagNumber(3) 85 | set gpioValue($fixnum.Int64 v) { $_setInt64(2, v); } 86 | @$pb.TagNumber(3) 87 | $core.bool hasGpioValue() => $_has(2); 88 | @$pb.TagNumber(3) 89 | void clearGpioValue() => clearField(3); 90 | } 91 | 92 | -------------------------------------------------------------------------------- /lib/services/proto/remote_hardware.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: remote_hardware.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | // ignore_for_file: UNDEFINED_SHOWN_NAME 9 | import 'dart:core' as $core; 10 | import 'package:protobuf/protobuf.dart' as $pb; 11 | 12 | class HardwareMessage_Type extends $pb.ProtobufEnum { 13 | static const HardwareMessage_Type UNSET = HardwareMessage_Type._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UNSET'); 14 | static const HardwareMessage_Type WRITE_GPIOS = HardwareMessage_Type._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WRITE_GPIOS'); 15 | static const HardwareMessage_Type WATCH_GPIOS = HardwareMessage_Type._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WATCH_GPIOS'); 16 | static const HardwareMessage_Type GPIOS_CHANGED = HardwareMessage_Type._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GPIOS_CHANGED'); 17 | static const HardwareMessage_Type READ_GPIOS = HardwareMessage_Type._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'READ_GPIOS'); 18 | static const HardwareMessage_Type READ_GPIOS_REPLY = HardwareMessage_Type._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'READ_GPIOS_REPLY'); 19 | 20 | static const $core.List values = [ 21 | UNSET, 22 | WRITE_GPIOS, 23 | WATCH_GPIOS, 24 | GPIOS_CHANGED, 25 | READ_GPIOS, 26 | READ_GPIOS_REPLY, 27 | ]; 28 | 29 | static final $core.Map<$core.int, HardwareMessage_Type> _byValue = $pb.ProtobufEnum.initByValue(values); 30 | static HardwareMessage_Type? valueOf($core.int value) => _byValue[value]; 31 | 32 | const HardwareMessage_Type._($core.int v, $core.String n) : super(v, n); 33 | } 34 | 35 | -------------------------------------------------------------------------------- /lib/services/proto/remote_hardware.pbjson.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: remote_hardware.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | import 'dart:core' as $core; 9 | import 'dart:convert' as $convert; 10 | import 'dart:typed_data' as $typed_data; 11 | @$core.Deprecated('Use hardwareMessageDescriptor instead') 12 | const HardwareMessage$json = const { 13 | '1': 'HardwareMessage', 14 | '2': const [ 15 | const {'1': 'typ', '3': 1, '4': 1, '5': 14, '6': '.HardwareMessage.Type', '10': 'typ'}, 16 | const {'1': 'gpio_mask', '3': 2, '4': 1, '5': 4, '10': 'gpioMask'}, 17 | const {'1': 'gpio_value', '3': 3, '4': 1, '5': 4, '10': 'gpioValue'}, 18 | ], 19 | '4': const [HardwareMessage_Type$json], 20 | }; 21 | 22 | @$core.Deprecated('Use hardwareMessageDescriptor instead') 23 | const HardwareMessage_Type$json = const { 24 | '1': 'Type', 25 | '2': const [ 26 | const {'1': 'UNSET', '2': 0}, 27 | const {'1': 'WRITE_GPIOS', '2': 1}, 28 | const {'1': 'WATCH_GPIOS', '2': 2}, 29 | const {'1': 'GPIOS_CHANGED', '2': 3}, 30 | const {'1': 'READ_GPIOS', '2': 4}, 31 | const {'1': 'READ_GPIOS_REPLY', '2': 5}, 32 | ], 33 | }; 34 | 35 | /// Descriptor for `HardwareMessage`. Decode as a `google.protobuf.DescriptorProto`. 36 | final $typed_data.Uint8List hardwareMessageDescriptor = $convert.base64Decode('Cg9IYXJkd2FyZU1lc3NhZ2USJwoDdHlwGAEgASgOMhUuSGFyZHdhcmVNZXNzYWdlLlR5cGVSA3R5cBIbCglncGlvX21hc2sYAiABKARSCGdwaW9NYXNrEh0KCmdwaW9fdmFsdWUYAyABKARSCWdwaW9WYWx1ZSJsCgRUeXBlEgkKBVVOU0VUEAASDwoLV1JJVEVfR1BJT1MQARIPCgtXQVRDSF9HUElPUxACEhEKDUdQSU9TX0NIQU5HRUQQAxIOCgpSRUFEX0dQSU9TEAQSFAoQUkVBRF9HUElPU19SRVBMWRAF'); 37 | -------------------------------------------------------------------------------- /lib/services/proto/remote_hardware.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: remote_hardware.proto 4 | // 5 | 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | export 'remote_hardware.pb.dart'; 9 | 10 | -------------------------------------------------------------------------------- /lib/services/proto/remote_hardware.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_package = "com.geeksville.mesh"; 4 | option java_outer_classname = "RemoteHardware"; 5 | option optimize_for = LITE_RUNTIME; 6 | 7 | /* 8 | * An example app to show off the plugin system. This message is used for 9 | * REMOTE_HARDWARE_APP PortNums. 10 | * 11 | * Also provides easy remote access to any GPIO. 12 | * 13 | * In the future other remote hardware operations can be added based on user interest 14 | * (i.e. serial output, spi/i2c input/output). 15 | * 16 | * FIXME - currently this feature is turned on by default which is dangerous 17 | * because no security yet (beyond the channel mechanism). 18 | * It should be off by default and then protected based on some TBD mechanism 19 | * (a special channel once multichannel support is included?) 20 | */ 21 | message HardwareMessage { 22 | enum Type { 23 | 24 | /* 25 | * Unset/unused 26 | */ 27 | UNSET = 0; 28 | 29 | /* 30 | * Set gpio gpios based on gpio_mask/gpio_value 31 | */ 32 | WRITE_GPIOS = 1; 33 | 34 | /* 35 | * We are now interested in watching the gpio_mask gpios. 36 | * If the selected gpios change, please broadcast GPIOS_CHANGED. 37 | * Will implicitly change the gpios requested to be INPUT gpios. 38 | */ 39 | WATCH_GPIOS = 2; 40 | 41 | /* 42 | * The gpios listed in gpio_mask have changed, the new values are listed in gpio_value 43 | */ 44 | GPIOS_CHANGED = 3; 45 | 46 | /* 47 | * Read the gpios specified in gpio_mask, send back a READ_GPIOS_REPLY reply with gpio_value populated 48 | */ 49 | READ_GPIOS = 4; 50 | 51 | /* 52 | * A reply to READ_GPIOS. gpio_mask and gpio_value will be populated 53 | */ 54 | READ_GPIOS_REPLY = 5; 55 | } 56 | 57 | /* 58 | * What type of HardwareMessage is this? 59 | */ 60 | Type typ = 1; 61 | 62 | /* 63 | * What gpios are we changing. Not used for all MessageTypes, see MessageType for details 64 | */ 65 | uint64 gpio_mask = 2; 66 | 67 | /* 68 | * For gpios that were listed in gpio_mask as valid, what are the signal levels for those gpios. 69 | * Not used for all MessageTypes, see MessageType for details 70 | */ 71 | uint64 gpio_value = 3; 72 | } -------------------------------------------------------------------------------- /lib/ui/find_device/widgets/scan_result.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Paul DeMarco. 2 | // All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | // import 'package:logger/logger.dart'; 7 | import '../../../services/bluetooth/ble_api.dart'; 8 | 9 | // var logger = Logger( 10 | // printer: PrettyPrinter(), 11 | // ); 12 | 13 | //ANDY TODO started to get NULL results here after merging BLEDevice, BLEDevice2 14 | 15 | class ScanResultTile extends StatelessWidget { 16 | final ScannedDevice result; 17 | final VoidCallback onTap; 18 | const ScanResultTile({Key? key, required this.result, required this.onTap}) 19 | : super(key: key); 20 | 21 | Widget _buildTitle(BuildContext context) { 22 | if (result != null) { 23 | if (result.device!.name.length > 0) { 24 | // logger.v( 25 | // 'ScanResult for BLEDevice ${result.device.id} ${result.hashCode}'); 26 | return Column( 27 | mainAxisAlignment: MainAxisAlignment.start, 28 | crossAxisAlignment: CrossAxisAlignment.start, 29 | children: [ 30 | Text( 31 | result.device!.name, 32 | overflow: TextOverflow.ellipsis, 33 | ), 34 | Text( 35 | result.device!.id.toString(), 36 | style: 37 | Theme.of(context).textTheme.caption!.apply(color: Colors.red), 38 | ), 39 | Icon(Icons.bluetooth, 40 | color: Theme.of(context).iconTheme.color!.withOpacity(0.9)), 41 | // RSSI also shown in title leading 42 | // Text( 43 | // 'RSSI: ${result.rssi.toString()}', 44 | // style: 45 | // Theme.of(context).textTheme.caption.apply(color: Colors.red), 46 | // ) 47 | ], 48 | ); 49 | } else { 50 | return Text(result.device!.id.toString()); 51 | } 52 | } else { 53 | return Text('No Device'); 54 | } 55 | } 56 | 57 | Widget _buildAdvRow(BuildContext context, String title, String value) { 58 | return Padding( 59 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), 60 | child: Row( 61 | crossAxisAlignment: CrossAxisAlignment.start, 62 | children: [ 63 | Text(title, style: Theme.of(context).textTheme.caption), 64 | const SizedBox( 65 | width: 12.0, 66 | ), 67 | Expanded( 68 | child: Text( 69 | value, 70 | style: 71 | Theme.of(context).textTheme.caption!.apply(color: Colors.red), 72 | softWrap: true, 73 | ), 74 | ), 75 | ], 76 | ), 77 | ); 78 | } 79 | 80 | String getNiceHexArray(List bytes) { 81 | return '[${bytes.map((i) => i.toRadixString(16).padLeft(2, '0')).join(', ')}]' 82 | .toUpperCase(); 83 | } 84 | 85 | String? getNiceManufacturerData(Map> data) { 86 | if (data.isEmpty) { 87 | return null; 88 | } 89 | List res = []; 90 | data.forEach((id, bytes) { 91 | res.add( 92 | '${id.toRadixString(16).toUpperCase()}: ${getNiceHexArray(bytes)}'); 93 | }); 94 | return res.join(', '); 95 | } 96 | 97 | String getNiceServiceData(Map> data) { 98 | if (data.isEmpty) { 99 | return 'N/A'; 100 | } 101 | List res = []; 102 | data.forEach((id, bytes) { 103 | res.add('${id.toUpperCase()}: ${getNiceHexArray(bytes)}'); 104 | }); 105 | return res.join(', '); 106 | } 107 | 108 | String getNiceServiceUUID(List data) { 109 | if (data.isEmpty) { 110 | return 'N/A'; 111 | } 112 | List res = []; 113 | data.forEach((v) { 114 | if (v.toLowerCase() == meshServiceStr) { 115 | // res.add('Meshtastic (${v.toUpperCase()})'); //shows name and UUID 116 | res.add('Meshtastic'); 117 | } else { 118 | res.add('${v.toUpperCase()}'); 119 | } 120 | }); 121 | return res.join(', '); 122 | } 123 | 124 | @override 125 | Widget build(BuildContext context) { 126 | return ExpansionTile( 127 | title: _buildTitle(context), 128 | leading: Text(result.rssi.toString()), 129 | initiallyExpanded: false, //toggle this for expand/collapse layout 130 | trailing: OutlinedButton( 131 | // color: Colors.black, 132 | // textColor: Colors.white, 133 | // onPressed: (result.advertisementData.connectable) ? onTap : null, 134 | onPressed: onTap, 135 | child: result.advertisementData!.connectable 136 | ? const Text('CONNECT') 137 | : const Text('Not Meshtastic'), 138 | ), 139 | children: [ 140 | _buildAdvRow(context, 'Complete Local Name', 141 | result.advertisementData!.localName), 142 | _buildAdvRow( 143 | context, 'Connectable', '${result.advertisementData!.connectable}'), 144 | _buildAdvRow(context, 'Tx Power Level', 145 | '${result.advertisementData!.txPowerLevel ?? 'N/A'}'), 146 | _buildAdvRow( 147 | context, 148 | 'Manufacturer Data', 149 | getNiceManufacturerData( 150 | result.advertisementData!.manufacturerData) ?? 151 | 'N/A'), 152 | _buildAdvRow( 153 | context, 154 | 'Service UUIDs', 155 | getNiceServiceUUID(result.advertisementData! 156 | .serviceUuids)), // (result.advertisementData.serviceUuids.isNotEmpty) 157 | // ? result.advertisementData.serviceUuids.join(', ').toUpperCase() 158 | // : 'N/A'), 159 | _buildAdvRow(context, 'Service Data', 160 | getNiceServiceData(result.advertisementData!.serviceData)), 161 | ], 162 | ); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /lib/ui/menu/drawer.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:get_it/get_it.dart'; 4 | 5 | import '../router/route_generator.dart'; 6 | import 'package:logger/logger.dart'; 7 | // import 'package:logger_flutter/logger_flutter.dart'; 8 | 9 | class AppDrawer extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | return Drawer( 13 | child: ListView( 14 | padding: EdgeInsets.zero, 15 | children: [ 16 | _createHeader(), 17 | _createDrawerItem( 18 | icon: Icons.home, 19 | text: 'Home', 20 | onTap: () => Navigator.pushReplacementNamed(context, '/')), 21 | _createDrawerItem( 22 | icon: Icons.contacts, 23 | text: 'Find Devices', 24 | onTap: () => Navigator.pushReplacementNamed(context, '')), 25 | _createDrawerItem( 26 | icon: Icons.event, 27 | text: 'Events', 28 | onTap: () => Navigator.pushReplacementNamed(context, '')), 29 | _createDrawerItem( 30 | icon: Icons.note, 31 | text: 'Device Setup', 32 | onTap: () => Navigator.pushReplacementNamed( 33 | context, '/meshCommandListScreen')), 34 | const Divider(), 35 | _createDrawerItem(icon: Icons.collections_bookmark, text: 'Setup'), 36 | // _createDrawerItem( 37 | // icon: Icons.collections_bookmark, 38 | // text: 'Log Console', 39 | // onTap: () => LogConsole.open(context)), 40 | _createDrawerItem(icon: Icons.face, text: 'Authors'), 41 | _createDrawerItem( 42 | icon: Icons.account_box, text: 'Flutter Documentation'), 43 | _createDrawerItem(icon: Icons.stars, text: 'Useful Links'), 44 | const Divider(), 45 | _createDrawerItem(icon: Icons.bug_report, text: 'Report an issue'), 46 | ListTile( 47 | title: const Text('0.0.1'), 48 | onTap: () {}, 49 | ), 50 | ], 51 | ), 52 | ); 53 | } 54 | 55 | Widget _createHeader() { 56 | return DrawerHeader( 57 | margin: EdgeInsets.zero, 58 | padding: EdgeInsets.zero, 59 | // decoration: BoxDecoration( 60 | // image: DecorationImage( 61 | // fit: BoxFit.fill, 62 | // image: AssetImage('res/images/drawer_header_background.png'))), 63 | child: Stack(children: [ 64 | const Positioned( 65 | bottom: 12.0, 66 | left: 16.0, 67 | child: Text('Meshtastic Flutter App', 68 | style: TextStyle( 69 | color: Colors.white, 70 | fontSize: 20.0, 71 | fontWeight: FontWeight.w500))), 72 | ])); 73 | } 74 | 75 | Widget _createDrawerItem( 76 | {IconData? icon, required String text, GestureTapCallback? onTap}) { 77 | return ListTile( 78 | title: Row( 79 | children: [ 80 | Icon(icon), 81 | Padding( 82 | padding: const EdgeInsets.only(left: 8.0), 83 | child: Text(text), 84 | ) 85 | ], 86 | ), 87 | onTap: onTap, 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/ui/permissions.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:flutter_blue/flutter_blue.dart'; 5 | import 'package:logger/logger.dart'; 6 | 7 | import '../domain/device_repo.dart'; 8 | import '../services/bluetooth/ble_api.dart'; 9 | import '../services/bluetooth/ble_common.dart'; 10 | //AF to expand this to report any permissions errors on startup 11 | 12 | class BluetoothOffScreen extends StatelessWidget { 13 | const BluetoothOffScreen({Key? key, this.state}) : super(key: key); 14 | 15 | final BLEState? state; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | backgroundColor: Colors.lightBlue, 21 | body: Center( 22 | child: Column( 23 | mainAxisSize: MainAxisSize.min, 24 | children: [ 25 | const Icon( 26 | Icons.bluetooth_disabled, 27 | size: 200.0, 28 | color: Colors.white54, 29 | ), 30 | Text( 31 | 'Bluetooth Adapter is ${state != null ? state.toString().substring(15) : 'not available'}.', 32 | style: Theme.of(context) 33 | .primaryTextTheme 34 | .subtitle1! 35 | .copyWith(color: Colors.white), 36 | ), 37 | ], 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/ui/router/route_generator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:get_it/get_it.dart'; 4 | 5 | import '../../application/connect_device/connect_device_bloc.dart'; 6 | import '../../application/find_device/find_device_bloc.dart'; 7 | import '../../application/setup_device/setup_device_bloc.dart'; 8 | import '../../domain/commands/command.dart'; 9 | import '../../domain/device_repo.dart'; 10 | 11 | import '../../services/mesh/mesh.dart'; 12 | import '../setup_device/mesh_command.dart'; 13 | import '../setup_device/mesh_command_list.dart'; 14 | import '../find_device/find_device.dart'; 15 | 16 | // see https://resocoder.com/2019/04/27/flutter-routes-navigation-parameters-named-routes-ongenerateroute/ 17 | 18 | /// 19 | ///bloc creation 20 | /// 1. DeviceConnect repository in main() (no device) 21 | /// 2. ConnectDeviceBloc in main - lazy 22 | /// 3. Routegenerator created (inject DeviceConnect Repository) 23 | /// 4. was SetupDeviceBloc - lazy 24 | /// 5. FindDeviceBloc in first Home page route (not lazy) 25 | /// 5. Try pattern SetupDeviceBloc in first Mesh command page route (not lazy) 26 | /// n. FindDeviceBloc calls a connectDeviceBloc.add(ConnectPressed(device)); 27 | 28 | class RouteGenerator { 29 | // pattern using Routes to set scope for blocs 30 | // see https://bloclibrary.dev/#/recipesflutterblocaccess?id=generated-route-access 31 | RouteGenerator(this._deviceConnect) { 32 | // _initbloc; 33 | sl.registerSingleton(SetupDeviceBloc()); 34 | } 35 | 36 | final DeviceConnect _deviceConnect; 37 | final sl = GetIt.instance; //sl = service locator 38 | 39 | // SetupDeviceBloc _setupDeviceBloc; 40 | 41 | //TODO - try to start the bloc events in the Command List svcreen. 42 | 43 | // final _setupDeviceBloc = SetupDeviceBloc(); 44 | 45 | // Future _initbloc() async { 46 | // await Future.delayed(Duration.zero); 47 | // _setupDeviceBloc = SetupDeviceBloc( 48 | // connectDeviceBloc: BlocProvider.of(context)); 49 | // } 50 | 51 | Route onGenerateRoute(RouteSettings settings) { 52 | // Getting arguments passed in while calling Navigator.pushNamed 53 | final args = settings.arguments; 54 | 55 | switch (settings.name) { 56 | case '/': 57 | // args are not used here 58 | // if (args is MeshRouteArguments) { 59 | return MaterialPageRoute( 60 | 61 | /// Start the Find Bloc immediately and raise FindStarted event 62 | builder: (_) => BlocProvider( 63 | lazy: false, 64 | create: (context) => 65 | FindDeviceBloc()..add(const FindStartedEvent()), 66 | child: FindDevicesScreen(deviceConnect: _deviceConnect))); 67 | // } 68 | // return _errorRoute(settings.name); 69 | 70 | // case '/userLogScreen': 71 | // // return MaterialPageRoute(builder: (_) => UserLogScreen()); 72 | // return MaterialPageRoute(builder: (_) => LogConsole.open(context)); 73 | 74 | // case '/meshScreen': 75 | // // Old Device screen from Flutterblue, see mesh_device.dart 76 | // // Validation of correct data type 77 | // if (args is MeshRouteArguments) { 78 | // return MaterialPageRoute( 79 | // builder: (_) => MeshScreen( 80 | // device: args.device, 81 | // deviceConnect: sl.get(), 82 | // ), 83 | // ); 84 | // } 85 | // // If args is not of the correct type, return an error page. 86 | // // You can also throw an exception while in development. 87 | // return _errorRoute(settings.name); 88 | case '/meshCommandListScreen': 89 | // Validation of correct data type 90 | if (args is MeshRouteArguments) { 91 | return MaterialPageRoute( 92 | //TODO creating it twice?? 93 | builder: (_) => BlocProvider.value( 94 | value: sl.get() 95 | // builder: (_) => BlocProvider( 96 | // lazy: false, 97 | // create: (context) => SetupDeviceBloc() 98 | ..add(DeviceStartedEvent(args.device)), 99 | child: MeshCommandListScreen( 100 | device: args.device, 101 | // deviceConnect: sl.get(), 102 | // deviceConnect: args.deviceConnect, 103 | ), 104 | )); 105 | } 106 | // If args is not of the correct type, return an error page. 107 | // You can also throw an exception while in development. 108 | return _errorRoute(settings.name); 109 | case '/meshCommandScreen': 110 | // Validation of correct data type 111 | if (args is MeshCommandRouteArguments) { 112 | return MaterialPageRoute( 113 | builder: (_) => BlocProvider.value( 114 | value: sl.get(), 115 | // BlocProvider.value( 116 | // value: _setupDeviceBloc, 117 | child: MeshCommandScreen( 118 | device: args.device, 119 | // deviceConnect: sl.get(), 120 | command: args.command, 121 | )), 122 | ); 123 | } 124 | // If args is not of the correct type, return an error page. 125 | // You can also throw an exception while in development. 126 | return _errorRoute(settings.name); 127 | default: 128 | // If there is no such named route in the switch statement, e.g. /third 129 | return _errorRoute(settings.name); 130 | } 131 | } 132 | 133 | static Route _errorRoute(String? route) { 134 | return MaterialPageRoute(builder: (_) { 135 | return Scaffold( 136 | appBar: AppBar( 137 | title: const Text('Navigation Error'), 138 | ), 139 | body: Center( 140 | child: Text('Error - Route not found $route'), 141 | ), 142 | ); 143 | }); 144 | } 145 | 146 | void dispose() { 147 | sl.get().close(); 148 | } 149 | } 150 | 151 | /// used for Mesh related screens, MeshCommand etc 152 | class MeshRouteArguments { 153 | final MeshDevice device; 154 | // final DeviceConnect deviceConnect; 155 | // final MeshCommand? command; 156 | MeshRouteArguments({required this.device}); 157 | } 158 | 159 | /// used for MeshCommand etc 160 | class MeshCommandRouteArguments { 161 | final MeshDevice device; 162 | // final DeviceConnect deviceConnect; 163 | final MeshCommand command; 164 | MeshCommandRouteArguments({required this.device, required this.command}); 165 | } 166 | -------------------------------------------------------------------------------- /lib/ui/settings/log_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:logger/logger.dart'; 2 | // import 'package:logger_flutter/logger_flutter.dart'; 3 | 4 | // logger.v("Verbose log"); 5 | // logger.d("Debug log"); 6 | // logger.i("Info log"); 7 | // logger.w("Warning log"); 8 | // logger.e("Error log"); 9 | // logger.wtf("What a terrible failure log"); 10 | 11 | Logger appLogger = Logger( 12 | // output: ExampleLogOutput(), 13 | level: Level.debug, 14 | printer: PrettyPrinter( 15 | methodCount: 4, // number of method calls to be displayed 16 | errorMethodCount: 8, 17 | printTime: true, 18 | ), 19 | ); 20 | 21 | Logger userLogger = 22 | // sl.registerSingleton( 23 | Logger( 24 | // output: ExampleLogOutput(), 25 | level: Level.debug, 26 | // printer: SimpleLogPrinter(''), 27 | printer: PrettyPrinter( 28 | methodCount: 0, // number of method calls to be displayed 29 | errorMethodCount: 4, 30 | printTime: true, 31 | ), 32 | ); 33 | // instanceName: 'userLogger'); 34 | 35 | //used in logger_flutter 36 | /* class ExampleLogOutput extends ConsoleOutput { 37 | @override 38 | void output(OutputEvent event) { 39 | super.output(event); 40 | LogConsole.add(event); 41 | } 42 | } */ 43 | 44 | // see https://github.com/leisim/logger/issues/31 45 | 46 | class SimpleLogPrinter extends LogPrinter { 47 | final String className; 48 | SimpleLogPrinter(this.className); 49 | 50 | @override 51 | List log(LogEvent event) { 52 | AnsiColor color = PrettyPrinter.levelColors[event.level]!; 53 | String emoji = PrettyPrinter.levelEmojis[event.level]!; 54 | return [color('$emoji $className - ${event.message}')]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/ui/setup_device/mesh_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:get_it/get_it.dart'; 5 | import 'package:logger/logger.dart'; 6 | 7 | import '../../application/setup_device/setup_device_bloc.dart'; 8 | import '../../domain/commands/command.dart'; 9 | import '../../domain/device_repo.dart'; 10 | import '../../services/bluetooth/bluetooth.dart'; 11 | import '../../services/mesh/mesh.dart'; 12 | import '../menu/drawer.dart'; 13 | import 'mesh_command_list.dart'; 14 | 15 | Logger appLogger = GetIt.I(instanceName: 'appLogger'); 16 | 17 | /// Display single command, called from selection on Command list 18 | /// as Card using [MeshCommandForm] with submit button 19 | class MeshCommandScreen extends StatelessWidget { 20 | final DeviceConnect deviceConnect = GetIt.I(); 21 | final MeshDevice device; 22 | final MeshCommand command; 23 | 24 | ///const for immutable class 25 | MeshCommandScreen({Key? key, required this.device, required this.command}) 26 | : assert(device != null), 27 | super(key: key); 28 | 29 | /// Generate the tiles from json file 30 | Widget _buildCommandCard(MeshCommand command, BuildContext context) { 31 | // bleInterface = BLEInterface(this.device); 32 | if (command == null) { 33 | appLogger.w('command is Null', 'app.DeviceScreen._buildCommandCard'); 34 | return Container( 35 | height: 50, 36 | color: Colors.amber[600], 37 | child: const Center(child: Text('Null command')), 38 | ); 39 | } else { 40 | appLogger.v('app.DeviceScreen._buildCommandTiles: Commands Found'); 41 | return Column(children: [ 42 | MeshCommandForm( 43 | command: command, 44 | onRunPressed: () { 45 | BlocProvider.of(context).add( 46 | DeviceCommandEvent(command), 47 | ); 48 | }) 49 | ]); 50 | } 51 | } 52 | 53 | /// Command top bar 54 | @override 55 | Widget build(BuildContext context) { 56 | return Scaffold( 57 | drawer: AppDrawer(), 58 | appBar: AppBar( 59 | title: Text(device.name), 60 | actions: [ 61 | TextButton( 62 | onPressed: null, //onPressed, 63 | child: Text('BACK BUTTON?', 64 | style: Theme.of(context).primaryTextTheme.button 65 | // .copyWith(color: Colors.white), 66 | )) 67 | ], 68 | ), 69 | body: BlocListener( 70 | listener: (context, state) { 71 | if (state is DeviceSuccessState) { 72 | showDialog( 73 | context: context, 74 | builder: (dialogContext) { 75 | return AlertDialog( 76 | title: const Text('Preferences from Device:'), 77 | content: Text(state.message), 78 | actions: [ 79 | // usually buttons at the bottom of the dialog 80 | TextButton( 81 | child: const Text('Close'), 82 | onPressed: () { 83 | Navigator.of(dialogContext).pop(); 84 | // Navigator.pop(context); 85 | }), 86 | TextButton( 87 | child: const Text('Copy'), 88 | onPressed: () { 89 | Clipboard.setData( 90 | ClipboardData(text: state.message)); 91 | ScaffoldMessenger.of(context) 92 | .showSnackBar(const SnackBar( 93 | // backgroundColor: Colors.green, 94 | content: Text('Copied to Clipboard}'), 95 | )); 96 | }) 97 | ]); 98 | }, 99 | ); 100 | } 101 | ScaffoldMessenger.of(context).showSnackBar(SnackBar( 102 | // backgroundColor: Colors.green[100], 103 | content: Text('SetupDeviceState ${state.toString()}'), 104 | )); 105 | }, 106 | // uses listview here to allow scrollable, avoids overflow on pop-up keyboard 107 | child: ListView( 108 | children: [ 109 | _buildCommandCard(command, context), 110 | ], 111 | ), 112 | ), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/ui/setup_device/widgets/gatt_service.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | // import 'package:flutter_blue/flutter_blue.dart'; 4 | import '../../../services/bluetooth/ble_api.dart'; 5 | import '../../../services/bluetooth/ble_common.dart'; 6 | 7 | class ServiceTile extends StatelessWidget { 8 | final BLEService service; 9 | final List characteristicTiles; 10 | 11 | const ServiceTile({Key? key, required this.service, required this.characteristicTiles}) 12 | : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | if (characteristicTiles.length > 0) { 17 | return ExpansionTile( 18 | title: Column( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | crossAxisAlignment: CrossAxisAlignment.start, 21 | children: [ 22 | const Text('Service'), 23 | Text( 24 | '0x ${service.uuid.toString().toUpperCase().substring(4, 8)} ${service.serviceName}', 25 | style: Theme.of(context) 26 | .textTheme 27 | .bodyText2! 28 | .copyWith(color: Theme.of(context).textTheme.caption!.color)) 29 | ], 30 | ), 31 | children: characteristicTiles, 32 | ); 33 | } else { 34 | return ListTile( 35 | title: const Text('Service'), 36 | subtitle: 37 | Text('0x ${service.uuid.toString().toUpperCase().substring(4, 8)}'), 38 | ); 39 | } 40 | } 41 | } 42 | 43 | class CharacteristicTile extends StatelessWidget { 44 | final BLECharacteristic characteristic; 45 | final List descriptorTiles; 46 | final VoidCallback? onReadPressed; 47 | final VoidCallback? onWritePressed; 48 | final VoidCallback? onNotificationPressed; 49 | // final VoidCallback onCharacteristicChanged; 50 | 51 | const CharacteristicTile( 52 | {Key? key, 53 | required this.characteristic, 54 | required this.descriptorTiles, 55 | this.onReadPressed, 56 | this.onWritePressed, 57 | this.onNotificationPressed}) 58 | : super(key: key); 59 | 60 | @override 61 | Widget build(BuildContext context) { 62 | return StreamBuilder>( 63 | stream: characteristic.value, 64 | initialData: characteristic.lastValue, 65 | builder: (c, snapshot) { 66 | final value = snapshot.data; 67 | return ExpansionTile( 68 | title: ListTile( 69 | title: Column( 70 | mainAxisAlignment: MainAxisAlignment.center, 71 | crossAxisAlignment: CrossAxisAlignment.start, 72 | children: [ 73 | Text('Characteristic ${characteristic.name}'), 74 | Text( 75 | 76 | // '0x${characteristic.uuid.toString().toUpperCase().substring(4, 8)}', 77 | '0x ${characteristic.uuid.toString().substring(0, 8)}', 78 | style: Theme.of(context).textTheme.bodyText2!.copyWith( 79 | color: Theme.of(context).textTheme.caption!.color)) 80 | ], 81 | ), 82 | subtitle: Text(value.toString()), 83 | contentPadding: const EdgeInsets.all(0.0), 84 | ), 85 | trailing: Row( 86 | mainAxisSize: MainAxisSize.min, 87 | children: [ 88 | IconButton( 89 | icon: Icon( 90 | Icons.file_download, 91 | color: Theme.of(context).iconTheme.color!.withOpacity(0.5), 92 | ), 93 | onPressed: onReadPressed, 94 | ), 95 | IconButton( 96 | icon: Icon(Icons.file_upload, 97 | color: Theme.of(context).iconTheme.color!.withOpacity(0.5)), 98 | onPressed: onWritePressed, 99 | ), 100 | IconButton( 101 | icon: Icon( 102 | 103 | ///to do button icon does not seem to change back to sync 104 | characteristic.isNotifying 105 | ? Icons.sync_disabled 106 | : Icons.sync, 107 | color: Theme.of(context).iconTheme.color!.withOpacity(0.5)), 108 | onPressed: onNotificationPressed, 109 | ) 110 | ], 111 | ), 112 | children: descriptorTiles, 113 | ); 114 | }, 115 | ); 116 | } 117 | } 118 | 119 | class DescriptorTile extends StatelessWidget { 120 | final BLEDescriptor descriptor; 121 | final VoidCallback? onReadPressed; 122 | final VoidCallback? onWritePressed; 123 | 124 | const DescriptorTile( 125 | {Key? key, required this.descriptor, this.onReadPressed, this.onWritePressed}) 126 | : super(key: key); 127 | 128 | @override 129 | Widget build(BuildContext context) { 130 | return ListTile( 131 | title: Column( 132 | mainAxisAlignment: MainAxisAlignment.center, 133 | crossAxisAlignment: CrossAxisAlignment.start, 134 | children: [ 135 | const Text('Descriptor'), 136 | Text('0x${descriptor.uuid.toString().toUpperCase().substring(4, 8)}', 137 | style: Theme.of(context) 138 | .textTheme 139 | .bodyText2! 140 | .copyWith(color: Theme.of(context).textTheme.caption!.color)) 141 | ], 142 | ), 143 | subtitle: StreamBuilder>( 144 | stream: descriptor.value, 145 | initialData: descriptor.lastValue, 146 | builder: (c, snapshot) => Text(snapshot.data.toString()), 147 | ), 148 | trailing: Row( 149 | mainAxisSize: MainAxisSize.min, 150 | children: [ 151 | IconButton( 152 | icon: Icon( 153 | Icons.file_download, 154 | color: Theme.of(context).iconTheme.color!.withOpacity(0.5), 155 | ), 156 | onPressed: onReadPressed, 157 | ), 158 | IconButton( 159 | icon: Icon( 160 | Icons.file_upload, 161 | color: Theme.of(context).iconTheme.color!.withOpacity(0.5), 162 | ), 163 | onPressed: onWritePressed, 164 | ) 165 | ], 166 | ), 167 | ); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: meshtastic_app 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | 19 | # use 20 | # flutter pub outdated 21 | # flutter pub upgrade 22 | version: 1.0.0+1 23 | 24 | environment: 25 | # sdk: ">=2.11.0-213.0.dev <2.12.0" # for null safety test 26 | sdk: ">=2.12.0 <3.0.0" 27 | flutter: ">=1.22.00 <2.0.0" 28 | 29 | dependencies: 30 | # analyzer: ^1.5.0 31 | # auto_route: ^0.6.9 # could not resolve package uri Dec 2020 - try again later? 32 | flutter: 33 | sdk: flutter 34 | flutter_blue: ^0.8.0 35 | bloc: ^7.0.0 36 | dartz: 0.10.0-nullsafety.1 37 | flutter_bloc: ^7.0.0 38 | flex_color_scheme: ^2.0.0 39 | # convex_bottom_bar: ^2.7.1+2 40 | freezed_annotation: ^0.14.1 41 | # freezed_annotation: ^0.12.0 42 | get_it: ^6.0.0 43 | grouped_list: ^4.0.0-nullsafety.1 44 | injectable: ^1.2.2 45 | # logging: '>=0.11.4 <2.0.0' 46 | # see https://github.com/leisim/logger_flutter/issues/6 47 | # and https://github.com/ewertonrp/logger_flutter 48 | # logger_flutter: '^0.7.1' # - any longer maintained 49 | # logger_flutter: 50 | # git: https://github.com/ewertonrp/logger_flutter.git 51 | logger: ^1.0.0 52 | # recase: ^3.0.1 53 | protobuf: ^2.0.0 # flutter blue also uses ^2.0.0 54 | rxdart: ^0.26.0 # flutter blue also uses ^0.26.0 55 | shared_preferences: ^2.0.5 56 | # preferences: '^5.2.1' // see https://pub.dev/packages/pref 57 | wakelock: ^0.5.0+2 58 | 59 | # The following adds the Cupertino Icons font to your application. 60 | # Use with the CupertinoIcons class for iOS style icons. 61 | cupertino_icons: ^1.0.0 62 | 63 | # see https://pub.dev/packages/freezed needed to make it build pre null safety 64 | # dependency_overrides: 65 | # freezed: ^0.12.7 66 | # freezed_annotation: ^0.12.0 67 | collection: ^1.15.0-nullsafety.4 68 | 69 | dev_dependencies: 70 | # auto_route_generator: ^0.6.10 71 | flutter_test: 72 | sdk: flutter 73 | # build_runner: ^1.12.0 # was ^1.12.2, conflict with freezed 74 | # build_runner: ^1.11.5 # was ^1.12.2, conflict with freezed 75 | build_runner: ^2.0.2 # was ^1.12.2, conflict with freezed 76 | # freezed: ^0.12.7 #build runner dependency 77 | freezed: ^0.14.1+3 #build runner dependency?? 78 | # json_serializable: ^3.3.0 79 | effective_dart: ^1.0.0 80 | pedantic: ^1.9.0 81 | very_good_analysis: ^2.0.0 82 | 83 | # For information on the generic Dart part of this file, see the 84 | # following page: https://dart.dev/tools/pub/pubspec 85 | 86 | # The following section is specific to Flutter. 87 | flutter: 88 | 89 | # The following line ensures that the Material Icons font is 90 | # included with your application, so that you can use the icons in 91 | # the material Icons class. 92 | uses-material-design: true 93 | 94 | # To add assets to your application, add an assets section, like this: 95 | assets: 96 | - assets/json/ 97 | # - images/a_dot_burr.jpeg 98 | # - images/a_dot_ham.jpeg 99 | 100 | # An image asset can refer to one or more resolution-specific "variants", see 101 | # https://flutter.dev/assets-and-images/#resolution-aware. 102 | 103 | # For details regarding adding assets from package dependencies, see 104 | # https://flutter.dev/assets-and-images/#from-packages 105 | 106 | # To add custom fonts to your application, add a fonts section here, 107 | # in this "flutter" section. Each entry in this list should have a 108 | # "family" key with the font family name, and a "fonts" key with a 109 | # list giving the asset and other descriptors for the font. For 110 | # example: 111 | # fonts: 112 | # - family: Schyler 113 | # fonts: 114 | # - asset: fonts/Schyler-Regular.ttf 115 | # - asset: fonts/Schyler-Italic.ttf 116 | # style: italic 117 | # - family: Trajan Pro 118 | # fonts: 119 | # - asset: fonts/TrajanPro.ttf 120 | # - asset: fonts/TrajanPro_Bold.ttf 121 | # weight: 700 122 | # 123 | # For details regarding fonts from package dependencies, 124 | # see https://flutter.dev/custom-fonts/#from-packages 125 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | import 'package:get_it/get_it.dart'; 11 | 12 | import 'package:meshtastic_app/main.dart'; 13 | import 'package:meshtastic_app/ui/router/route_generator.dart'; 14 | import 'package:meshtastic_app/domain/device_repo.dart'; 15 | 16 | void main() { 17 | final sl = GetIt.instance; //sl = service locator 18 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 19 | // Build our app and trigger a frame. 20 | await tester 21 | .pumpWidget(BLEApp(router: RouteGenerator(sl.get()))); 22 | 23 | // Verify that our counter starts at 0. 24 | expect(find.text('0'), findsOneWidget); 25 | expect(find.text('1'), findsNothing); 26 | 27 | // Tap the '+' icon and trigger a frame. 28 | await tester.tap(find.byIcon(Icons.add)); 29 | await tester.pump(); 30 | 31 | // Verify that our counter has incremented. 32 | expect(find.text('0'), findsNothing); 33 | expect(find.text('1'), findsOneWidget); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /where: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qurm/meshtastic-flutter/c294a86f6a6621721fd2ea80fb94c5e55c2ee478/where --------------------------------------------------------------------------------