healthrian_ble_support_for_cardio_em_cdc_http 0.0.5 copy "healthrian_ble_support_for_cardio_em_cdc_http: ^0.0.5" to clipboard
healthrian_ble_support_for_cardio_em_cdc_http: ^0.0.5 copied to clipboard

BLE support for Cardio EM CDC over HTTP

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:healthrian_ble_interface/healthrian_ble_interface.dart';
import 'package:healthrian_ble_support_for_cardio_em_cdc_http/healthrian_ble_support_for_cardio_em_cdc_http.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await filter.waitForCompletion();
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Cardio EM CDC HTTP Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final TextEditingController _topicController = TextEditingController(text: 'CardioEMCDC/5019F820');
  final TextEditingController _hostController = TextEditingController(text: '127.0.0.1');
  final TextEditingController _portController = TextEditingController(text: '8080');

  BLEManager? _manager;
  
  void _setupManager() {
    final topic = _topicController.text;
    final host = _hostController.text;
    final port = int.tryParse(_portController.text) ?? 8080;

    setState(() {
      _manager = BLEManager(host: host, port: port)..topic = topic;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Cardio EM CDC HTTP Example'),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Card(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  children: [
                    TextField(
                      controller: _hostController,
                      decoration: const InputDecoration(labelText: 'Host'),
                    ),
                    TextField(
                      controller: _portController,
                      decoration: const InputDecoration(labelText: 'Port'),
                      keyboardType: TextInputType.number,
                    ),
                    TextField(
                      controller: _topicController,
                      decoration: const InputDecoration(labelText: 'Topic'),
                    ),
                    const SizedBox(height: 10),
                    ElevatedButton(
                      onPressed: _setupManager,
                      child: const Text('Setup Manager'),
                    ),
                  ],
                ),
              ),
            ),
          ),
          if (_manager != null) ...[
            Expanded(
              child: ListView(
                children: [
                   ListTile(
                    title: const Text('Status'),
                    subtitle: Observer(
                      builder: (_) => StreamBuilder<bool?>(
                        stream: _manager!.ble.streams.ble.connection,
                        builder: (context, snapshot) {
                          return Text('Connected: ${snapshot.data ?? false}');
                        },
                      ),
                    ),
                  ),
                  ListTile(
                    title: const Text('Is Measuring'),
                    subtitle: Observer(
                      builder: (_) => Text(_manager!.ecg?.isMeasuring.toString() ?? 'false'),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: SizedBox(
                      height: 200,
                      child: Card(
                        child: Observer(
                          builder: (_) => StreamBuilder<ChannelData>(
                            stream: _manager!.ble.streams.ecg.filter.plot,
                            builder: (context, snapshot) {
                              if (!snapshot.hasData) {
                                return const Center(child: Text('No Data'));
                              }
                              return CustomPaint(
                                painter: EcgPainter(snapshot.data!),
                              );
                            },
                          ),
                        ),
                      ),
                    ),
                  ),
                  ListTile(
                    title: const Text('Raw Data (Hex)'),
                    subtitle: Observer(
                      builder: (_) => StreamBuilder<List<int>>(
                        stream: _manager!.ble.streams.ecg.meta.raw,
                        builder: (context, snapshot) {
                          if (!snapshot.hasData) return const Text('N/A');
                          final hex = snapshot.data!.take(16).map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ');
                          return Text(hex);
                        },
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ],
      ),
      floatingActionButton: _manager != null ? Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => _manager!.connectAndRegister(),
            heroTag: 'connect',
            child: const Icon(Icons.link),
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () => _manager!.ecg?.start(),
            heroTag: 'play',
            child: const Icon(Icons.play_arrow),
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () => _manager!.ecg?.stop(),
            heroTag: 'stop',
            child: const Icon(Icons.stop),
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () => _manager!.disconnectDevice(),
            heroTag: 'disconnect',
            child: const Icon(Icons.link_off),
          ),
        ],
      ) : null,
    );
  }
}

class EcgPainter extends CustomPainter {
  final ChannelData data;
  EcgPainter(this.data);

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.red
      ..strokeWidth = 1.0
      ..style = PaintingStyle.stroke;

    final l2 = data.l2;
    if (l2.isEmpty) return;

    final path = Path();
    final double stepX = size.width / data.limit;
    
    // We need to handle the circular buffer using the pivot
    for (int i = 0; i < l2.length; i++) {
       // Simple visualization for now: just draw what's in the buffer
       // The ChannelData.plot stream already provides the circular buffer data 
       // but we might need to handle the 'gap' if we want it to look exactly like a monitor.
       // For a simple example, we'll just draw the lines.
       
       double x = i * stepX;
       // Scale Y: assume values are around 0 with some range. 
       // This is a rough scaling for visualization.
       double y = size.height / 2 - (l2[i] / 1000); 
       
       if (i == 0) {
         path.moveTo(x, y);
       } else {
         // Handle the jump at the pivot if we are drawing the full limit
         if (i == data.pivot) {
            path.moveTo(x, y);
         } else {
            path.lineTo(x, y);
         }
       }
    }

    canvas.drawPath(path, paint);
    
    // Draw the cursor at the pivot
    final cursorX = data.pivot * stepX;
    canvas.drawLine(Offset(cursorX, 0), Offset(cursorX, size.height), Paint()..color = Colors.blue.withValues(alpha: 0.5)..strokeWidth = 2);
  }

  @override
  bool shouldRepaint(covariant EcgPainter oldDelegate) => true;
}