modbus

Modbus library with TCP Master & Slave.

For Serial RTU see baud.

Based on:

Installation and Usage

  1. Add modbus to your list of dependencies in mix.exs:
  def deps do
    [{:modbus, "~> MAJOR.MINOR"}]
  end
  1. Connect the TCP master to the testing TCP slave:
  # run with: mix slave
  alias Modbus.Slave
  alias Modbus.Master

  # start your slave with a shared model
  model = %{
    0x50 => %{
      {:c, 0x5152} => 0,
      {:i, 0x5354} => 0,
      {:i, 0x5355} => 1,
      {:hr, 0x5657} => 0x6162,
      {:ir, 0x5859} => 0x6364,
      {:ir, 0x585A} => 0x6566
    }
  }

  {:ok, slave} = Slave.start_link(model: model)
  # get the assigned tcp port
  port = Slave.port(slave)

  # interact with it
  {:ok, master} = Master.start_link(ip: {127, 0, 0, 1}, port: port)

  # read input
  {:ok, [0, 1]} = Master.exec(master, {:ri, 0x50, 0x5354, 2})
  # read input registers
  {:ok, [0x6364, 0x6566]} = Master.exec(master, {:rir, 0x50, 0x5859, 2})

  # toggle coil and read it back
  :ok = Master.exec(master, {:fc, 0x50, 0x5152, 0})
  {:ok, [0]} = Master.exec(master, {:rc, 0x50, 0x5152, 1})
  :ok = Master.exec(master, {:fc, 0x50, 0x5152, 1})
  {:ok, [1]} = Master.exec(master, {:rc, 0x50, 0x5152, 1})

  # increment holding register and read it back
  {:ok, [0x6162]} = Master.exec(master, {:rhr, 0x50, 0x5657, 1})
  :ok = Master.exec(master, {:phr, 0x50, 0x5657, 0x6163})
  {:ok, [0x6163]} = Master.exec(master, {:rhr, 0x50, 0x5657, 1})

  :ok = Master.stop(master)
  :ok = Slave.stop(slave)
  1. Connect the TCP master to a real industrial Opto22 device:
  # run with: mix opto22
  alias Modbus.Master
  alias Modbus.Float

  # opto22 learning center configured with script/opto22.otg
  # the otg is for an R2 but seems to work for R1, EB1, and EB2
  # digital points increment address by 4 per module and by 1 per point
  # analog points increment address by 8 per module and by 2 per point

  {:ok, master} = Master.start_link(ip: {10, 77, 0, 10}, port: 502)

  # turn on 'alarm'
  :ok = Master.exec(master, {:fc, 1, 4, 1})
  # turn on 'outside light'
  :ok = Master.exec(master, {:fc, 1, 5, 1})
  # turn on 'inside light'
  :ok = Master.exec(master, {:fc, 1, 6, 1})
  # turn on 'freezer door status'
  :ok = Master.exec(master, {:fc, 1, 7, 1})

  :timer.sleep(400)

  # turn off all digital outputs
  :ok = Master.exec(master, {:fc, 1, 4, [0, 0, 0, 0]})

  # read the 'emergency' switch
  {:ok, [0]} = Master.exec(master, {:rc, 1, 8, 1})

  # read the 'fuel level' knob (0 to 10,000)
  {:ok, data} = Master.exec(master, {:rir, 1, 32, 2})
  [_] = Float.from_be(data)

  # write to the 'fuel display' (0 to 10,000)
  data = Float.to_be([+5000.0])
  :ok = Master.exec(master, {:phr, 1, 16, data})

  :ok = Master.stop(master)

Endianess

Roadmap

Future

Version 0.4.0

Version 0.3.9

Version 0.3.8

Version 0.3.7

Version 0.3.6

Version 0.3.5

Version 0.3.4

Version 0.3.3

Version 0.3.2

Version 0.3.1

Version 0.3.0

Version 0.2.0

Version 0.1.0