nrf_mesh_flutter 6.9.7 copy "nrf_mesh_flutter: ^6.9.7" to clipboard
nrf_mesh_flutter: ^6.9.7 copied to clipboard

Flutter plugin for Bluetooth Mesh using Nordic nRF Mesh libraries

example/lib/main.dart

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:nrf_mesh_flutter/nrf_mesh_flutter.dart';
import 'group_messaging_page.dart';
import 'scenes_page.dart';
import 'real_p1_page.dart';
import 'provisioning_demo_page.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await PlatoJobsNrfMeshManager.instance.initialize();
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _meshManager = PlatoJobsNrfMeshManager.instance;
  MeshNetwork? _network;
  List<UnprovisionedDevice> _devices = [];
  List<ProvisionedNode> _nodes = [];
  List<MeshGroup> _groups = [];
  bool _isScanning = false;
  String _status = 'Ready';

  StreamSubscription<int>? _meshDbHintSub;

  @override
  void initState() {
    super.initState();
    _loadNetwork();
    _listenToMessages();
    _listenMeshDbHints();
  }

  @override
  void dispose() {
    _meshDbHintSub?.cancel();
    _scanSub?.cancel();
    super.dispose();
  }

  /// Debounced refresh when native mesh DB changes (Phase 2 parity demo).
  void _listenMeshDbHints() {
    _meshDbHintSub =
        debounceMeshNetworkUpdates(
          _meshManager.meshNetworkUpdatedStream,
          const Duration(milliseconds: 400),
        ).listen((_) async {
          if (!mounted) return;
          await _loadNodesAndGroups();
          setState(() => _status = 'Mesh DB updated — refreshed nodes/groups');
        });
  }

  Future<void> _loadNetwork() async {
    try {
      setState(() => _status = 'Loading network...');
      _network = await _meshManager.loadNetwork();
      if (_network == null) {
        _network = await _meshManager.createNetwork('My Mesh Network');
        setState(() => _status = 'Network created');
      } else {
        setState(() => _status = 'Network loaded');
      }
      _loadNodesAndGroups();
    } catch (e) {
      setState(() => _status = 'Error: $e');
    }
  }

  Future<void> _loadNodesAndGroups() async {
    _nodes = await _meshManager.getNodes();
    _groups = await _meshManager.getGroups();
    setState(() {});
  }

  void _listenToMessages() {
    _meshManager.messageStream.listen((message) {
      setState(() {
        _status = 'Received message: ${message.opcode}';
      });
    });
  }

  StreamSubscription<UnprovisionedDevice>? _scanSub;

  Future<void> _startScan() async {
    await _scanSub?.cancel();
    setState(() {
      _isScanning = true;
      _devices = [];
      _status = 'Scanning for devices...';
    });

    _scanSub = _meshManager.scanForDevices().listen((device) {
      setState(() {
        final id = device.deviceId;
        if (!_devices.any((d) => d.deviceId == id)) {
          _devices.add(device);
        }
      });
    });
  }

  Future<void> _stopScan() async {
    await _scanSub?.cancel();
    _scanSub = null;
    await _meshManager.stopScan();
    setState(() {
      _isScanning = false;
      _status = 'Scan stopped';
    });
  }

  Future<void> _provisionDevice(UnprovisionedDevice device) async {
    try {
      setState(() => _status = 'Provisioning device...');
      await _meshManager.provisionDevice(
        device,
        ProvisioningParameters(
          deviceName: device.name.isEmpty ? 'Mesh Device' : device.name,
        ),
      );
      setState(() => _status = 'Device provisioned');
      await _loadNodesAndGroups();
    } catch (e) {
      setState(() => _status = 'Provisioning error: $e');
    }
  }

  Future<void> _sendTestMessage() async {
    try {
      if (_nodes.isEmpty) {
        setState(() => _status = 'No nodes available');
        return;
      }

      setState(() => _status = 'Sending test message...');
      await _meshManager.sendMessage(
        GenericOnOffSet(
          state: true,
          transitionTime: 0,
          delay: 0,
          address: 0xC000,
          appKeyIndex: 0,
        ),
      );
      setState(() => _status = 'Message sent');
    } catch (e) {
      setState(() => _status = 'Send error: $e');
    }
  }

  Future<void> _configDemo() async {
    try {
      if (_nodes.isEmpty) {
        setState(() => _status = 'No nodes available');
        return;
      }
      final first = _nodes.first;
      if (first.elements.isEmpty || first.elements.first.models.isEmpty) {
        setState(() => _status = 'Node has no models');
        return;
      }
      final elementAddress =
          int.tryParse(
            first.elements.first.address.replaceAll('0x', ''),
            radix: 16,
          ) ??
          1;
      final modelId =
          int.tryParse(first.elements.first.models.first.modelId) ?? 0x1000;

      setState(() => _status = 'Configuring model...');
      await _meshManager.bindAppKey(elementAddress, modelId, 0);
      await _meshManager.addSubscription(elementAddress, modelId, 0xC000);
      await _meshManager.setPublication(
        elementAddress,
        modelId,
        0xC000,
        0,
        ttl: 5,
      );
      setState(() => _status = 'Config updated');
      await _loadNodesAndGroups();
    } catch (e) {
      setState(() => _status = 'Config error: $e');
    }
  }

  Future<void> _createGroup() async {
    try {
      setState(() => _status = 'Creating group...');
      await _meshManager.createGroup('Test Group');
      setState(() => _status = 'Group created');
      await _loadNodesAndGroups();
    } catch (e) {
      setState(() => _status = 'Create group error: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('PlatoJobs nRF Mesh Example')),
        body: SafeArea(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Expanded(
                child: SingleChildScrollView(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Status: $_status',
                        style: const TextStyle(fontSize: 16),
                      ),
                      const SizedBox(height: 16),
                      if (_network != null)
                        Card(
                          child: Padding(
                            padding: const EdgeInsets.all(12.0),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                const Text(
                                  'Network',
                                  style: TextStyle(
                                    fontWeight: FontWeight.bold,
                                  ),
                                ),
                                Text('Name: ${_network?.name}'),
                                Text('Nodes: ${_nodes.length}'),
                                Text('Groups: ${_groups.length}'),
                              ],
                            ),
                          ),
                        ),
                      const SizedBox(height: 16),
                      Wrap(
                        spacing: 16,
                        runSpacing: 12,
                        children: [
                          ElevatedButton(
                            onPressed: _isScanning ? _stopScan : _startScan,
                            child: Text(
                              _isScanning ? 'Stop Scan' : 'Start Scan',
                            ),
                          ),
                          ElevatedButton(
                            onPressed: _sendTestMessage,
                            child: const Text('Send Test Message'),
                          ),
                          ElevatedButton(
                            onPressed: _createGroup,
                            child: const Text('Create Group'),
                          ),
                          ElevatedButton(
                            onPressed: _configDemo,
                            child: const Text('Config Demo'),
                          ),
                          ElevatedButton(
                            onPressed: () {
                              Navigator.of(context).push(
                                MaterialPageRoute<void>(
                                  builder: (_) => const RealP1Page(),
                                ),
                              );
                            },
                            child: const Text('P1 Real Flow'),
                          ),
                          ElevatedButton(
                            onPressed: () {
                              Navigator.of(context).push(
                                MaterialPageRoute<void>(
                                  builder: (_) =>
                                      const ProvisioningDemoPage(),
                                ),
                              );
                            },
                            child: const Text('M1 Provisioning'),
                          ),
                          ElevatedButton(
                            onPressed: () {
                              Navigator.of(context).push(
                                MaterialPageRoute<void>(
                                  builder: (_) => const GroupMessagingPage(),
                                ),
                              );
                            },
                            child: const Text('M3 Groupcast'),
                          ),
                          ElevatedButton(
                            onPressed: () {
                              Navigator.of(context).push(
                                MaterialPageRoute<void>(
                                  builder: (_) => const ScenesPage(),
                                ),
                              );
                            },
                            child: const Text('M4 Scenes'),
                          ),
                        ],
                      ),
                      const SizedBox(height: 16),
                      const Text(
                        'Provisioned Nodes',
                        style: TextStyle(fontWeight: FontWeight.bold),
                      ),
                      ListView.builder(
                        shrinkWrap: true,
                        physics: const NeverScrollableScrollPhysics(),
                        itemCount: _nodes.length,
                        itemBuilder: (context, index) {
                          final node = _nodes[index];
                          final element = node.elements.isNotEmpty
                              ? node.elements.first
                              : null;
                          final model =
                              (element != null && element.models.isNotEmpty)
                              ? element.models.first
                              : null;
                          return ListTile(
                            dense: true,
                            title: Text(
                              'Node ${node.uuid} @ ${node.unicastAddress}',
                            ),
                            subtitle: model == null
                                ? const Text('No model info')
                                : Text(
                                    'Element: ${element!.address}\n'
                                    'Model: ${model.modelName} (${model.modelId})\n'
                                    'Bound AppKeys: ${model.boundAppKeyIndexes}\n'
                                    'Subs: ${model.subscriptions}\n'
                                    'Pub: ${model.publication == null ? 'null' : 'addr=${model.publication!.address} appKey=${model.publication!.appKeyIndex} ttl=${model.publication!.ttl}'}',
                                  ),
                          );
                        },
                      ),
                    ],
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      'Discovered Devices',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    SizedBox(
                      height: 160,
                      child: ListView.builder(
                        itemCount: _devices.length,
                        itemBuilder: (context, index) {
                          final device = _devices[index];
                          final uuidHint = device.serviceData.length == 16
                              ? device.serviceData
                                    .map(
                                      (b) => b.toRadixString(16).padLeft(2, '0'),
                                    )
                                    .join()
                              : 'no mesh UUID in adv';
                          return ListTile(
                            title: Text(device.name),
                            subtitle: Text(
                              'ID: ${device.deviceId}\n'
                              'RSSI: ${device.rssi}\n'
                              'UUID: $uuidHint',
                            ),
                            trailing: ElevatedButton(
                              onPressed: device.serviceData.length == 16
                                  ? () => _provisionDevice(device)
                                  : null,
                              child: const Text('Provision'),
                            ),
                          );
                        },
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
2
likes
160
points
77
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter plugin for Bluetooth Mesh using Nordic nRF Mesh libraries

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

convert, crypto, flutter, meta, plugin_platform_interface, pointycastle

More

Packages that depend on nrf_mesh_flutter

Packages that implement nrf_mesh_flutter