ModBoss (modboss v0.1.0)

Human-friendly modbus reading, writing, and translation.

Read and write modbus values by name, with automatic encoding and decoding.

Summary

Functions

Read from modbus using named mappings.

Write to modbus using named mappings.

Types

read_func()

@type read_func() :: (register_type(),
                starting_address :: integer(),
                count :: integer() ->
                  {:ok, any()} | {:error, any()})

register_type()

@type register_type() :: :holding_register | :input_register | :coil | :discrete_input

values_to_write()

@type values_to_write() :: [{atom(), any()}] | %{required(atom()) => any()}

write_func()

@type write_func() :: (register_type(),
                 starting_address :: integer(),
                 value_or_values :: any() ->
                   :ok | {:error, any()})

Functions

read(module, read_func, name_or_names, opts \\ [])

@spec read(module(), read_func(), atom() | [atom()], keyword()) ::
  {:ok, any()} | {:error, any()}

Read from modbus using named mappings.

This function takes either an atom or a list of atoms representing the mappings to read, batches the mappings into contiguous addresses per type, then reads and decodes the values before returning them.

For each batch, read_func will be called with the type of register (:holding_register, :input_register, :coil, or :discrete_input), the starting address for the batch to be read, and the count of addresses to read from. It must return either {:ok, result} or {:error, message}.

If a single name is requested, the result will be an :ok tuple including the singule result for that named mapping. If a list of names is requested, the result will be an :ok tuple including a map with mapping names as keys and mapping values as results.

Opts

  • :decode — if false, returns the "raw" result as provided by read_func; defaults to true

Examples

read_func = fn register_type, starting_address, count ->
  result = custom_read_logic()
  {:ok, result}
end

# Read one mapping
ModBoss.read(SchemaModule, read_func, :foo)
{:ok, 75}

# Read multiple mappings
ModBoss.read(SchemaModule, read_func, [:foo, :bar, :baz])
{:ok, %{foo: 75, bar: "ABC", baz: true}}

# Read *all* readable mappings
ModBoss.read(SchemaModule, read_func, :all)
{:ok, %{foo: 75, bar: "ABC", baz: true, qux: 1024}}

# Get "raw" Modbus values (as returned by `read_func`)
ModBoss.read(SchemaModule, read_func, :all, decode: false)
{:ok, %{foo: 75, bar: [16706, 17152], baz: 1, qux: 1024}}

write(module, write_func, values)

@spec write(module(), write_func(), values_to_write()) :: :ok | {:error, any()}

Write to modbus using named mappings.

ModBoss automatically encodes your values, then batches any encoded values destined for contiguous registers—creating separate batches per register type.

For each batch, write_func will be called with the type of register (:holding_register or :coil), the starting address for the batch to be written, and a list of values to write. It must return either :ok or {:error, message}.

Batch values

Each batch will contain either a list or an individual value based on the number of addresses to be written—so you should be prepared for both.

Non-atomic writes!

While ModBoss.write/3 has the feel of being atomic, it's important to recognize that it is not! It's fully possible that a write might fail after prior writes within the same call to ModBoss.write/3 have already succeeded.

Within ModBoss.write/3, if any call to write_func returns an error tuple, the function will immediately abort, and any subsequent writes will be skipped.

Example

write_func = fn register_type, starting_address, value_or_values ->
  result = custom_write_logic()
  {:ok, result}
end

iex> ModBoss.write(MyDevice.Schema, write_func, foo: 75, bar: "ABC")
:ok