cabbi / modbus_client_serial

BSD 3-Clause "New" or "Revised" License
9 stars 6 forks source link

Using async #2

Open bruno-embtech opened 9 months ago

bruno-embtech commented 9 months ago

Hello, I'm attempting to create an abstraction of this package for use in my application, but I'm encountering issues with asynchronous execution. The UI freezes while the function is running. I'm still learning Flutter and Dart, and I've successfully implemented simple async tasks in the past.

Here's a snippet of the function:

Future<ModbusRegisterModel> sendFrameWriteSingleRegister(int addr, int register, int data) async {
  // Check if already connected
  if (!_modbusClientSerialRtu.isConnected) {
    // Connect only if not already connected
    await _modbusClientSerialRtu.connect();
  }

  // Create ModbusRegisterModel
  ModbusRegisterModel r = ModbusRegisterModel();

  // Create ModbusInt16Register
  var _reg = ModbusInt16Register(
    name: "Registrador $register",
    address: register,
    type: ModbusElementType.holdingRegister,
    onUpdate: (self) {
      r.registerId = self.address;
      r.registerValue = self.value as double;
    },
    offset: 0,
  );

  // Send Modbus frame
  try {
    await _modbusClientSerialRtu.send(_reg.getWriteRequest(data));
  } catch (err) {
    // Handle the error appropriately
    print("Error sending Modbus frame: $err");
  }

  // Return ModbusRegisterModel
  return r;
}

I'd appreciate any suggestions or examples to address the UI freezing issue.

cabbi commented 9 months ago

Hi,

Dart wise I don't see mistakes in your code. Flutter wise, I need to understand where and when are you calling your sendFrameWriteSingleRegister method.

You can use Provider to notify your widget on any change of the modbus register. Here an example:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:modbus_client/modbus_client.dart';
import 'package:modbus_client_serial/modbus_client_serial.dart';

var _modbusClientSerialRtu = ModbusClientSerialRtu(portName: "COM2", unitId: 1);

class ModbusRegisterModel extends ChangeNotifier {
  int? registerId;
  dynamic registerValue;

  void update(int registerId, dynamic registerValue) {
    this.registerId = registerId;
    this.registerValue = registerValue;
    notifyListeners();
  }
}

void sendFrameWriteSingleRegister(
    ModbusRegisterModel model, int addr, int register, int data) async {
  // Create ModbusInt16Register
  var _reg = ModbusInt16Register(
    name: "Registrador $register",
    address: register,
    type: ModbusElementType.holdingRegister,
    onUpdate: (self) {
      print("Register updated!");
      model.update(self.address, self.value);
    },
    offset: 0,
  );

  // Send Modbus frame
  try {
    await _modbusClientSerialRtu.send(_reg.getWriteRequest(data));
  } catch (err) {
    // Handle the error appropriately
    print("Error sending Modbus frame: $err");
  }
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Modbus Demo',
      theme: ThemeData.dark(),
      home: Scaffold(
          appBar: AppBar(title: const Text('Modbus Demo')),
          body: const Center(child: ModbusElementWidget())),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    // Capture model changes
    return ChangeNotifierProvider<ModbusRegisterModel>(
        // Create the model
        create: (_) => ModbusRegisterModel(),
        builder: (context, child) {
          // Here we are watching at this model (getting notified on its changes)
          var model = context.watch<ModbusRegisterModel>();
          return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text("Register ${model.registerId} : ${model.registerValue}"),
              const Divider(),
              ElevatedButton.icon(
                onPressed: () {
                  // NOTE: you might get the register attributes from somewhere!
                  sendFrameWriteSingleRegister(model, 400001, 400001, 123);
                },
                icon: const Icon(Icons.send),
                label: const Text("Send"),
              )
            ],
          );
        });
  }
}
bruno-embtech commented 9 months ago

Hi, thank you for the response.

I was already doing something similar and tested your code, but I encountered the same result. I apologize for the poor description in my previous message, as I forgot to mention a few things:

Here's where I call the function:

TextButton(
  child: Text('Click me'),
  onPressed: () async {
    try {
      await _modbus.modbusclient.sendFrameWriteSingleRegister(
        0x0001,
        0x0041,
        (!ModbusController.instance.reles[0]).toInt(),
      );
      // Update UI based on success
    } catch (err) {
      // Handle errors gracefully
    }
  },
),

The problem arises when the result code is requestTimeout, specifically when the slave board is disconnected.

Here's a visual representation of the issue (using your code as an example, but I'm facing the same problem in my own code):

problem

Aside from this particular situation, everything works flawlessly. The package is indeed easy to use, great work!

cabbi commented 9 months ago

I'm trying with the example app I wrote here and I do not get any freezing even if request is timing out because server is not connected. I'm using the app under Windows. On which device are you running your test?

bruno-embtech commented 9 months ago

I'm currently running this code on an embedded Linux board, but I also tested it on an x86 Linux system and encountered the same results. This leads me to suspect a potential issue with Linux systems in general. Here's what happens:

Error.webm

This the log :

flutter: FINE: 2024-01-17 08:29:54.371865 - Opening serial port /dev/ttyUSB0...
flutter: FINE: 2024-01-17 08:29:57.383015 - Request completed with code: requestTimeout
flutter: FINE: 2024-01-17 08:30:02.242950 - Request completed with code: requestTimeout
flutter: FINE: 2024-01-17 08:30:21.850988 - Request completed with code: requestTimeout
flutter: FINE: 2024-01-17 08:30:25.983892 - Request completed with code: requestTimeout
cabbi commented 9 months ago

I tried also with serial port not responding and I don't have UI getting stuck

bruno-embtech commented 9 months ago

I see. Have you tried running your code on a Linux device?

Perhaps the issue is with mine installation.

cabbi commented 9 months ago

no, I didn't

bruno-embtech commented 8 months ago

Hi there,

I've been investigating the reported issue further and identified the root cause. It seems to be specific to Linux devices, based on both my limited testing with other platforms and your confirmation that it works on your Windows machine.

The problem lies in the libserialport package, which is wrapped by flutter_libserialport. Within the read() function, there are two conditional branches based on the timeout parameter:

@override
Uint8List read(int bytes, {int timeout = -1}) {
  return Util.read(bytes, (ffi.Pointer<ffi.Uint8> ptr) {
    if (timeout < 0) {
      return dylib.sp_nonblocking_read(_port, ptr.cast(), bytes);
    } else {
      return dylib.sp_blocking_read(_port, ptr.cast(), bytes, timeout);
    }
  });
}

The problem arises because of the timeout of your package doesn't support a timeout of -1, intended for non-blocking behavior. Consequently, the read() function always falls back to blocking mode, leading to the observed issue.

Here's what I suggest:

  1. Enable -1 timeout for Linux: Modify your package to allow setting the timeout to -1 . This would enable the desired non-blocking behavior when needed.

  2. Document the limitation: Clearly explain this platform-specific limitation on the package's pub.dev page. This would raise awareness among developers and suggest potential workarounds.

Additional thoughts:

I hope this information helps diagnose and resolve the issue!

Edit:

Actually you can set to -1 using Duration(miliseconds:-1)

cabbi commented 8 months ago

Nice catch!

I did a quick check and current reading implementation uses that timeout. Strange is that the high level modbus reading is marked as async.

I will try a different approach by using serial read with -1 as timeout

cabbi commented 8 months ago

While implementing the fix I've discovered this issue in libserialport: https://github.com/jpnurmi/libserialport.dart/issues/83 I was simply moving all the serialPort!.read(....) to the below method, but it throws exception

  Uint8List _serialRead(int byteCount, int timeout_ms) {
    List<int> rxData = [];
    var stopWatch = Stopwatch()..start();
    while (stopWatch.elapsedMilliseconds < timeout_ms) {
      var data = _serialPort!.read(byteCount - rxData.length);
      rxData.addAll(data);
      if (byteCount <= rxData.length) {
        break;
      }
    }
    return Uint8List.fromList(rxData);
  }