ModBoss.Schema (modboss v0.2.0)

Copy Markdown View Source

Macros for establishing Modbus schema.

The schema allows names to be assigned to individual modbus objects or groups of contiguous modbus objects along with encoder/decoder functions. It also allows objects to be flagged as readable and/or writable.

Naming a mapping

You create a ModBoss mapping with this format:

holding_register 17, :outdoor_temp, as: {ModBoss.Encoding, :signed_int}

This establishes address 17 as a holding register with the name :outdoor_temp. The raw value from the register will be passed to ModBoss.Encoding.decode_signed_int/1 when decoding.

Similarly, to set aside a group of registers to hold a single logical value, it would look like:

holding_register 20..23, :model_name, as: {ModBoss.Encoding, :ascii}

This establishes addresses 20–23 as holding registers with the name :model_name. The raw values from these registers will be passed (as a list) to ModBoss.Encoding.decode_ascii/1 when decoding.

Mode

All ModBoss mappings are read-only by default. Use mode: :rw to allow both reads & writes. Or use mode: :w to configure a mapping as write-only.

Automatic encoding/decoding

Depending on whether a mapping is flagged as readable/writable, it is expected that you will provide functions with encode_ or decode_ prepended to the value provided by the :as option.

For example, if you specify as: :on_off for a writable mapping, ModBoss will expect that the schema module defines either:

  • encode_on_off/1 — accepts the value to encode
  • encode_on_off/2 — accepts the value to encode and a ModBoss.Encoding.Metadata struct

Use the 2-arity version when you need access to metadata (e.g. address count, mapping name, or user-provided context). Both must return either {:ok, encoded_value} or {:error, message}.

The same applies to decode functions: define either decode_on_off/1 (accepts the encoded value) or decode_on_off/2 (accepts the encoded value and metadata).

If the function to be used lives outside of the current module, a tuple including the module name can be passed. For example, you can use built-in encoders from ModBoss.Encoding.

output of encode_*

Your encode function may need to encode for one or multiple objects, depending on the mapping. You are free to return either a single value or a list of values—the important thing is that the number of values returned needs to match the number of objects from your mapping. If it doesn't, ModBoss will return an error when encoding.

For example, if encoding "ABC!" as ascii into a mapping with 3 registers, these characters would technically only require 2 registers (one 16-bit register for every 2 characters). However, your encoding should return a list of length equaling what you've assigned to the mapping in your schema— i.e. in this example, a list of length 3.

input to decode_*

When decoding a mapping involving a single address, the decode function will be passed the single value from that address/object as provided by your read function.

When decoding a mapping involving multiple addresses (e.g. in ModBoss.Encoding.decode_ascii/1), the decode function will be passed a List of values.

Example

defmodule MyDevice.Schema do
  use ModBoss.Schema

  schema do
    holding_register 1..5, :model, as: {ModBoss.Encoding, :ascii}
    holding_register 6, :outdoor_temp, as: {ModBoss.Encoding, :signed_int}
    holding_register 7, :indoor_temp, as: {ModBoss.Encoding, :unsigned_int}

    input_register 200, :foo, as: {ModBoss.Encoding, :unsigned_int}
    coil 300, :bar, as: :on_off, mode: :rw
    holding_register 301, :setpoint, as: :scaled, mode: :w
    discrete_input 400, :baz, as: {ModBoss.Encoding, :boolean}
  end

  # 1-arity: no metadata needed
  def encode_on_off(:on), do: {:ok, 1}
  def encode_on_off(:off), do: {:ok, 0}

  def decode_on_off(1), do: {:ok, :on}
  def decode_on_off(0), do: {:ok, :off}

  # 2-arity: uses metadata.context for runtime behavior
  def encode_scaled(value, metadata) do
    case metadata.context do
      %{unit: :fahrenheit} -> {:ok, round((value - 32) * 5 / 9)}
      _ -> {:ok, value}
    end
  end
end

Summary

Functions

Adds a coil to a schema.

Adds a read-only discrete input to a schema.

Adds a holding register to a schema.

Adds a read-only input register to a schema.

Establishes a Modbus schema in the current module.

Functions

coil(addresses, name, opts \\ [])

(macro)

Adds a coil to a schema.

Opts

  • :mode — Makes the mapping readable/writable — can be one of [:r, :rw, :w] (default: :r)
  • :as — Determines which encoding/decoding functions to use when writing/reading values. See explanation of automatic encoding/decoding.
  • :gap_safe — Whether this mapping's addresses are safe to read incidentally when bridging a gap between other requested mappings (default: true for readable mappings, false for write-only). You should set this to false for any coil that triggers side effects when read (e.g. clear-on-read coils). See the :max_gap option in ModBoss.read/4 for details on gap tolerance.

discrete_input(addresses, name, opts \\ [])

(macro)

Adds a read-only discrete input to a schema.

Opts

  • :as — Determines which decoding functions to use when reading values. See explanation of automatic encoding/decoding.
  • :gap_safe — Whether this mapping's addresses are safe to read incidentally when bridging a gap between other requested mappings (default: true). You should set this to false for any input that triggers side effects when read (e.g. clear-on-read inputs). See the :max_gap option in ModBoss.read/4 for details on gap tolerance.

holding_register(addresses, name, opts \\ [])

(macro)

Adds a holding register to a schema.

Opts

  • :mode — Makes the mapping readable/writable — can be one of [:r, :rw, :w] (default: :r)
  • :as — Determines which encoding/decoding functions to use when writing/reading values. See explanation of automatic encoding/decoding.
  • :gap_safe — Whether this mapping's addresses are safe to read incidentally when bridging a gap between other requested mappings (default: true for readable mappings, false for write-only). You should set this to false for any register that triggers side effects when read (e.g. clear-on-read registers). See the :max_gap option in ModBoss.read/4 for details on gap tolerance.

input_register(addresses, name, opts \\ [])

(macro)

Adds a read-only input register to a schema.

Opts

  • :as — Determines which decoding functions to use when reading values. See explanation of automatic encoding/decoding.
  • :gap_safe — Whether this mapping's addresses are safe to read incidentally when bridging a gap between other requested mappings (default: true). You should set this to false for any register that triggers side effects when read (e.g. clear-on-read registers). See the :max_gap option in ModBoss.read/4 for details on gap tolerance.

schema(list)

(macro)

Establishes a Modbus schema in the current module.