Skip to content

samuelventura/modbus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

78 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

  • Transport behaviour for serial and socket
  • Protocol behaviour for TCP, RTU, and ASCII
  • Improve documentation and samples
  • Improve error handling
  • TCP<->RTU translator

Version 0.4.0

  • Transport and protocol behaviours
  • API refactored (breaking changes)
  • Basic crash and socket testing
  • Added echo server as testing helper

Version 0.3.9

  • Basic crash testing
  • Resilient master, slave, and shared model

Version 0.3.8

  • Shared and slave for testing purposes only
  • Removed client (no clear api)

Version 0.3.7

  • Changed little endian flag from :se to :le

Version 0.3.6

  • Added endianness flag to float helpers

Version 0.3.5

  • Added float helper

Version 0.3.4

  • Fixed RTU CRC endianess

Version 0.3.3

  • Shared model slave implementation

Version 0.3.2

  • Added request length prediction
  • Refactored namespaces to avoid baud clash
  • Tcp master api updated to match baud rtu master api

Version 0.3.1

  • Added master/slave test for each code
  • Added response length prediction
  • Added a couple of helpers to tcp and rtu api
  • A reference-only tcp slave added in test helper

Version 0.3.0

  • Modbus TCP slave: wont fix, to be implemented as forward plugin
  • API breaking changes

Version 0.2.0

  • Updated documentation
  • Renamed commands to match spec wording

Version 0.1.0

  • Modbus TCP master
  • Request/response packet builder and parser
  • Device model to emulate slave interaction

Helpers

#Packet encoding
iex(1)> Modbus.Rtu.Protocol.pack_req({:fc, 1, 4, 1}) |> Base.encode16
"01050004FF00CDFB"
iex(2)> Modbus.Tcp.Protocol.pack_req({:fc, 1, 4, 1}, 0) |> Base.encode16
"00000000000601050004FF00"
#Packet decoding
iex(3)> Base.decode16!("01050004FF00CDFB") |> Modbus.Rtu.Protocol.parse_req
{{:fc, 1, 4, 1}, nil}
iex(4)> Base.decode16!("00000000000601050004FF00") |> Modbus.Tcp.Protocol.parse_req
{{:fc, 1, 4, 1}, 0}