fins_ltt 0.0.1 copy "fins_ltt: ^0.0.1" to clipboard
fins_ltt: ^0.0.1 copied to clipboard

A lightweight Dart library that implements the Omron FINS UDP protocol for communicating with Omron PLCs.

fins_ltt #

Pub
Package License

fins_ltt is a lightweight Dart library that implements the Omron FINS UDP protocol.

It allows Dart or Flutter applications to communicate with Omron PLCs (CJ, CS, CP series) for reading and writing memory areas such as DM, WR, HR.

The library is designed for:

  • Industrial supervision software (SCADA)
  • Flutter desktop tools
  • PLC testing utilities
  • Lightweight automation integrations

The implementation is written in pure Dart with no native dependencies.


Features #

  • FINS UDP communication
  • Read and write DM / WR / HR memory areas
  • Support for word and bit access
  • Typed reads and writes:
    • Words
    • Int16
    • Int32
    • Float32
  • Automatic polling
  • Retry mechanism (fully configurable)
  • Timeout setting (fully configurable)
  • Jitter support to avoid PLC saturation (fully configurable)
  • Debug utilities for PLC value inspection
  • Verbose Mode

Installation #

Add the package to your pubspec.yaml:

dependencies:
  fins_ltt: ^0.0.1

Then run:

dart pub get

Import the library:

import 'package:fins_ltt/fins_ltt.dart';

Quick Start #

PLC and PC configuration #

Both the PLC and the PC must have a static IP. In the PLC you must set also the node and the unit (follow the instructions for your PLC type). A typical configuration can be:

PC IP   : 192.168.0.191
PLC IP  : 192.168.0.5

Network : 0
PcNode  : 191
PlcNode : 5
PcUnit  : 0
PlcUnit : 0

Then you simply need to define a PLC object that the client can connect to:

import 'package:fins_ltt/fins_ltt.dart';

Future<void> main() async {

  final plc = await FinsUdpClient.bind(
    plcHost: '192.168.0.5',
    network: 0,
    pcNode: 191,
    plcNode: 5,
  );

  final values = await plc.readWords(
    area: FinsMemoryArea.dmWord,
    start: 100,
    count: 4,
  );

  print(values);

  await plc.close();
}

combine Retry + Timeout + MaxWordsPerRequest #

Pay attention to how Retry, Timeout, and maxWordsPerRequest work together. (se below for detailied info)..

default setting: retryCount = 2 retryDelayMs = 50ms timeout = 300ms maxWordsPerRequest = 100

Example: reading 180 words

total_word_requested → 180
split_read →
  first_chunk → count 0-99
        first_attempt 0:
          → send request
          → waiting timeout = 300ms
          → timeout ❌

          → waiting retryDelayMs = 50ms

        Retry 1:
          → send request
          → waiting timeout = 300ms
          → timeout ❌

          → waiting retryDelayMs = 50ms

        Retry 2:
          → send request
  →  Request succeful

  second_chunk → count 100-80
        first_attempt 0:
          → send request
          → waiting timeout = 300ms
          → timeout ❌

          → waiting retryDelayMs = 50ms

        Retry 1:
          → send request
          → waiting timeout = 300ms
          → timeout ❌

          → waiting retryDelayMs = 50ms

        Retry 2:
          → send request
  →  Request succeful
toal_reads → 180 word
total_time → 1400ms

Important notes Each chunk is read independently. The timeout applies to each single attempt, not the whole operation. The total duration depends on: number of chunks retry count timeout and retry delay If one chunk exceeds the retry limit, the entire operation fails:


Retry Mechanism and Time out settings #

Default Settings #

Retry active is set to 2 automatic attempts, 50 millis between retries and Timeout betwen retry is set to 300 millis

result:

first_attempt 0:
  → send request
  → waiting timeout = 2s
  → timeout ❌

  → waiting retryDelayMs = 100ms

Retry 1:
  → send request
  → waiting timeout = 2 s
  → timeout ❌

  → waiting retryDelayMs = 100ms

Retry 2:
  → send request
  → waiting timeout = 2 s
  → timeout ❌

→  Trhow timeout Error

Use default settings #

  final plc = await FinsUdpClient.bind(
    plcHost: '192.168.0.5',
    network: 0,
    pcNode: 191,
    plcNode: 5,
    pcUnit: 0,
    plcUnit: 0,
  );

      final words = await plc.readWords(
      area: FinsMemoryArea.wrWord,
      start: 46,
      count: 10,
    );

Retry Disabled #

  final plc = await FinsUdpClient.bind(
    plcHost: '192.168.0.5',
    network: 0,
    pcNode: 191,
    plcNode: 5,
    pcUnit: 0,
    plcUnit: 0,
    retryEnabled: false,
  );

Intesive Retry #

  final plc = await FinsUdpClient.bind(
    plcHost: '192.168.0.5',
    network: 0,
    pcNode: 191,
    plcNode: 5,
    pcUnit: 0,
    plcUnit: 0,
    retryCount: 4,
    retryDelayMs: 100,
  );

Binding PLC with Intesive Retry and 2 seconds Timeout #

  final plc = await FinsUdpClient.bind(
    plcHost: '192.168.0.5',
    network: 0,
    pcNode: 191,
    plcNode: 5,
    pcUnit: 0,
    plcUnit: 0,
    retryCount: 2,
    retryDelayMs: 100,
  );

    final words = await plc.readWords(
      area: FinsMemoryArea.wrWord,
      start: 46,
      count: 10,
      timeout: Duration(seconds: 2)
    );

suggested settings #

Caso PLC stabile (LAN) timeout: 100–300 ms retryDelay: 20–50 ms retryCount: 1–2

Caso PLC più lento / rete incerta timeout: 500–1000 ms retryDelay: 100–200 ms retryCount: 2–3


Jitter #

It's a simple technique used in polling systems to prevent all requests from arriving at the PLC at exactly the same intervals. It helps prevent overly regular traffic spikes that can stress the PLC, the network, or multiple clients simultaneously. Imagine a simple poll every 500 ms. All devices can synchronize and send requests at the same time. The PLC then receives a small burst of traffic every 500 ms. This isn't always a problem, but in large systems it can become one. With jitter, instead of always using exactly 500 ms, a small random variation is added, for example ±30 ms:

t = 0 ms      request
t = 472 ms    request
t = 1008 ms   request
t = 1479 ms   request
t = 1995 ms   request

Supported Memory Areas #

Each area supports: Word acces, Bit access

Area	Description
-------------------
DM	    Data Memory
WR	    Work Relay
HR	    Holding Relay

Reading Example #

This package comes with several examples

Simple words reading #

Read from WR40 to WR45

    final words = await plc.readWords(
      area: FinsMemoryArea.wrWord,
      start: 40,
      count: 6,
    );

Simple words reading #

you can set the amount of word read with a single request by setting the optional param maxWordsPerRequest.

  • default → is 100 if you do not insert the param
  • maxWordsPerRequest: XX → set to XX
  • maxWordsPerRequest: 0 → disabled
    final words = await plc.readWords(
      area: FinsMemoryArea.wrWord,
      start: 40,
      count: 6,
      maxWordsPerRequest:50,
    );

AutoPolling words reading #

polling Read DM10 to DM15,

  final sub = plc
      .autoPollingWords(
    area: FinsMemoryArea.dmWord,
    start: 10,
    count: 6,
    intervalMs: 1000,
    jitterMs: 20, // jitter enabled
  )
      .listen(
    (values) {
      print("${DateTime.now()}  = $values");
    },
    onError: (err) {
      print("Error: $err");
    },
  );

AutoPolling Int16 reading #

polling Read DM100 to DM109,

  final sub = plc
      .autoPollingInt16(
    area: FinsMemoryArea.dmWord,
    start: 100,
    count: 10,
    intervalMs: 500,
  )
      .listen(
    (values) {
      print("${DateTime.now()} int16 = $values");
    },
    onError: (err) {
      print("Error: $err");
    },
  );

AutoPolling Int32 reading #

Each Int32 value occupies two PLC words. For example, starting at DM200:

DM200 DM201 → first Int32 value
DM202 DM203 → second Int32 value

You can use the order parameter to control the word order when reading 32-bit values.

 final sub = plc
      .autoPollingInt32(
    area: FinsMemoryArea.dmWord,
    start: 200,
    count: 2,
    intervalMs: 1000,
    order: WordOrder.normal,
  )
      .listen(
    (values) {
      print("${DateTime.now()} int32 = $values");
    },
    onError: (err) {
      print("Error: $err");
    },
  );

AutoPolling Float32 reading #

Each Float32 value occupies two PLC words. For example, starting at DM300:

DM300 DM301 → first Float32 value
DM302 DM303 → second Float32 value
DM304 DM305 → third Float32 value

You can use the order parameter to control the word order when reading 32-bit values.

  final sub = plc
      .autoPollingFloat32(
    area: FinsMemoryArea.dmWord,
    start: 300,
    count: 3,
    intervalMs: 1000,
    order: WordOrder.swapped,
  )
      .listen(
    (values) {
      print("${DateTime.now()} Float32 = $values");
    },
    onError: (err) {
      print("Error: $err");
    },
  );

Bit reading #

    // read h150.0 bit
    final h150_0 = await plc.readBit(
      area: FinsMemoryArea.hrBit,
      start: 150,
      bit: 0,
    );

    // read h150.15 bit
    final h150_15 = await plc.readBit(
      area: FinsMemoryArea.hrBit,
      start: 150,
      bit: 15,
    );

    // read all 16 bits of h150
    final h150_bits = await plc.readBits(
      area: FinsMemoryArea.hrBit,
      start: 150,
      bit: 0,
      count: 16,
    );

AutoPolling Bit reading #

Polling only one bit: H150.3

final sub = plc.autoPollingBit(
  area: FinsMemoryArea.hrBit,
  start: 150,
  bit: 3,
  intervalMs: 500,
).listen(
  (value) {
    print('H150.3 = $value');
  },
  onError: (e) {
    print('Error polling single bit: $e');
  },
);

AutoPolling Bits reading #

Polling on H150.0 , H150.1 ... H150.15

final sub = plc
      .autoPollingBits(
    area: FinsMemoryArea.hrBit,
    start: 150,
    bit: 0,
    count: 16,
    intervalMs: 500,
    jitterMs: 20,
  )
      .listen(
    (bits) {
      print('H150.0..15 = $bits');
    },
    onError: (e) {
      print('Errora polling bits: $e');
    },
  );

Writing Example #

Simple words writing #

Write

WR46 = 10
WR47 = 20
WR48 = 30
    await plc.writeWords(
      area: FinsMemoryArea.wrWord,
      start: 46,
      words: [10, 20, 30],
    );

Int16 writing #

write valuse as int16

 DM46 = 65535
 DM47 = 10
 DM48 = 32767
    await plc.writeInt16(
      area: FinsMemoryArea.dmWord,
      start: 46,
      values: [-1, 10, 32767],
    );

Int16 writing #

write valuse as int16

 DM46 = 65535
 DM47 = 10
 DM48 = 32767
    await plc.writeInt16(
      area: FinsMemoryArea.dmWord,
      start: 46,
      values: [-1, 10, 32767],
    );

Int32 writing #

Write 85875 as int32 starting from DM16, so due to two words Int 32 space needed, DM16 and DM17 qill be occupied

    await plc.writeInt32(
      area: FinsMemoryArea.dmWord,
      start: 16,
      values: [85875],
    );

Float32 writing #

Write 12.5 as Float32 starting from DM16, so due to two words Int 32 space needed, DM16 and DM17 qill be occupied

    await plc.writeFloat32(
      area: FinsMemoryArea.dmWord,
      start: 16,
      values: [12.5],
    );

Works with bit #

Two function that does not talk with the PLC but are very usefull when handling bit.

  bool getBit(int word, int bit);
  int setBit(int word, int bit, bool value);

getBit #

If for example you already have H10 = 5 (int) -> H10 = 0000 0000 0000 0101 (bin), so with one fins request you obtain more bit

bit 0 = 1
bit 1 = 0
bit 2 = 1
bit 3 = 0
  getBit(5, 0) // true
  getBit(5, 1) // false
  getBit(5, 2) // true
  getBit(5, 3) // false

setBit #

setBit take one word and give you back another word with with the bit set

final h10 = 5; // 0000 0000 0000 0101
final newWord = setBit(h10, 1, true); // 0000 0000 0000 0111


Handlig Errors #

onError: (error, stackTrace) {
  if (error is FinsTimeoutException) {
    print('Timeout PLC');
  } else if (error is FinsTransportException) {
    print('Errore trasporto: $error');
  } else {
    print('Errore generico: $error');
  }
},

Debug Utilities #

The library includes debug helpers for PLC value inspection.

    print(FinsDebug.debugFloat32(12.5));

Output example:

FLOAT32 DEBUG
value       : 12.5
hex32       : 0x41480000
word[0] hex : 0x4148
word[1] hex : 0x0000
word[0] dec : 16712
word[1] dec : 0

Verbose Mode #

if enabled a litle bit of "print" actions helps to understand whats going on under the skin

  final plc = await FinsUdpClient.bind(
    plcHost: '192.168.0.5',
    network: 0,
    pcNode: 191,
    plcNode: 5,
    pcUnit: 0,
    plcUnit: 0,
    verboseMode: true
  );

you can also use the verbose message via the callback


Project Status #

The library is under active development.


License #

MIT License.

0
likes
140
points
--
downloads

Documentation

API reference

Publisher

verified publisherluigitarpani.it

A lightweight Dart library that implements the Omron FINS UDP protocol for communicating with Omron PLCs.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

More

Packages that depend on fins_ltt