Skip to content

Commit

Permalink
0.3.0 beta2
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelventura committed Feb 24, 2017
1 parent 4ac1afc commit 1959a2b
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 77 deletions.
57 changes: 34 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
Modbus library with TCP implementation.

- For Serial RTU see [baud](https://github.com/samuelventura/baud).
- For TCP-to-RTU translation see [baud](https://github.com/samuelventura/baud)
- For TCP-to-RTU translation see [forward](https://github.com/samuelventura/forward).
- For Modbus Slave see [forward](https://github.com/samuelventura/forward).

Based on:

Expand All @@ -26,11 +27,14 @@ Based on:
```elixir
alias Modbus.Request
alias Modbus.Response
alias Modbus.Tcp
#read 1 from input at slave 1 address 0
cmd = {:ri, 1, 0, 2}
req = Request.pack(cmd)
#send request thru a serial or socket channel
res = channel.send(req)
wreq = Tcp.wrap(req, transid)
#send wrapped request thru a serial or socket channel
wres = :gen_tcp.send(socket, wreq)
res = Tcp.unwrap(wres, transid)
[1, 0] = Response.parse(cmd, res)
```

Expand All @@ -39,7 +43,7 @@ Based on:
```elixir
alias Modbus.Master

#opto22 rack configured as follows
# opto22 rack configured as follows
# m0 - 4p digital input
# p0 - 24V
# p1 - 0V
Expand All @@ -50,34 +54,41 @@ Based on:
# p1 - NC
# p2 - m0.p2
# p3 - m0.p3
# m2 - 2p analog input (-10V to +10V)
# p0 - m3.p0
# p1 - m3.p1
# m3 - 2p analog output (-10V to +10V)
# p0 - m2.p0
# p1 - m2.p1

{:ok, pid} = Master.start_link([ip: {10,77,0,2}, port: 502])

#read 1 from input at slave 1 address 0 (m0.p0)
{:ok, [1]} = Master.tcp(pid, {:ri, 1, 0, 1})
#read 0 from input at slave 1 address 1 (m0.p1)
{:ok, [0]} = Master.tcp(pid, {:ri, 1, 1, 1})
#read both previous inputs at once
{:ok, [1, 0]} = Master.tcp(pid, {:ri, 1, 0, 2})

#turn off coil at slave 1 address 6 (m1.p2)
:ok = Master.tcp(pid, {:fc, 1, 6, 0})
:timer.sleep(50) #let output settle
#read 0 from input at slave 1 address 2 (m0.p2)
{:ok, [0]} = Master.tcp(pid, {:ri, 1, 2, 1})

#turn on coil at slave 1 address 7 (m1.p3)
:ok = Master.tcp(pid, {:fc, 1, 7, 1})
:timer.sleep(50) #let output settle
#read 1 from input at slave 1 address 3 (m0.p3)
{:ok, [1]} = Master.tcp(pid, {:ri, 1, 3, 1})
#turn off m1.p0
:ok = Master.tcp(pid, {:fc, 1, 4, 0})
#turn on m1.p1
:ok = Master.tcp(pid, {:fc, 1, 5, 1})
#alternate m1.p2 and m1.p3
:ok = Master.tcp(pid, {:fc, 1, 6, [1, 0]})

#write -5V (IEEE 754 float) to m3.p0
:ok = Master.tcp(pid, {:phr, 1, 24, [0xc0a0, 0x0000]})
#write +5V (IEEE 754 float) to m3.p1
:ok = Master.tcp(pid, {:phr, 1, 26, [0x40a0, 0x0000]})

:timer.sleep(20) #outputs settle delay

#read previous coils as inputs
{:ok, [0, 1, 1, 0]} = Master.tcp(pid, {:ri, 1, 4, 4})

#read previous analog channels as input registers
{:ok, [0xc0a0, 0x0000, 0x40a0, 0x0000]} = Master.tcp(pid, {:rir, 1, 24, 4})
```

## Roadmap

Version 0.3.0

- [ ] Modbus TCP slave
- [x] Modbus TCP slave: wont fix, to be implemented as forward plugin
- [x] API breaking changes

Version 0.2.0
Expand Down
47 changes: 27 additions & 20 deletions lib/master.ex
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
defmodule Modbus.Master do
@moduledoc """
TCP Master module.
TCP Master server.
## Example
```elixir
alias Modbus.Master
#opto22 rack configured as follows
# opto22 rack configured as follows
# m0 - 4p digital input
# p0 - 24V
# p1 - 0V
Expand All @@ -18,27 +18,34 @@ defmodule Modbus.Master do
# p1 - NC
# p2 - m0.p2
# p3 - m0.p3
# m2 - 2p analog input (-10V to +10V)
# p0 - m3.p0
# p1 - m3.p1
# m3 - 2p analog output (-10V to +10V)
# p0 - m2.p0
# p1 - m2.p1
{:ok, pid} = Master.start_link([ip: {10,77,0,2}, port: 502])
#read 1 from input at slave 1 address 0 (m0.p0)
{:ok, [1]} = Master.tcp(pid, {:ri, 1, 0, 1})
#read 0 from input at slave 1 address 1 (m0.p1)
{:ok, [0]} = Master.tcp(pid, {:ri, 1, 1, 1})
#read both previous inputs at once
{:ok, [1, 0]} = Master.tcp(pid, {:ri, 1, 0, 2})
#turn off coil at slave 1 address 6 (m1.p2)
:ok = Master.tcp(pid, {:fc, 1, 6, 0})
:timer.sleep(50) #let output settle
#read 0 from input at slave 1 address 2 (m0.p2)
{:ok, [0]} = Master.tcp(pid, {:ri, 1, 2, 1})
#turn on coil at slave 1 address 7 (m1.p3)
:ok = Master.tcp(pid, {:fc, 1, 7, 1})
:timer.sleep(50) #let output settle
#read 1 from input at slave 1 address 3 (m0.p3)
{:ok, [1]} = Master.tcp(pid, {:ri, 1, 3, 1})
#turn off m1.p0
:ok = Master.tcp(pid, {:fc, 1, 4, 0})
#turn on m1.p1
:ok = Master.tcp(pid, {:fc, 1, 5, 1})
#alternate m1.p2 and m1.p3
:ok = Master.tcp(pid, {:fc, 1, 6, [1, 0]})
#write -5V (IEEE 754 float) to m3.p0
:ok = Master.tcp(pid, {:phr, 1, 24, [0xc0a0, 0x0000]})
#write +5V (IEEE 754 float) to m3.p1
:ok = Master.tcp(pid, {:phr, 1, 26, [0x40a0, 0x0000]})
:timer.sleep(20) #outputs settle delay
#read previous coils as inputs
{:ok, [0, 1, 1, 0]} = Master.tcp(pid, {:ri, 1, 4, 4})
#read previous analog channels as input registers
{:ok, [0xc0a0, 0x0000, 0x40a0, 0x0000]} = Master.tcp(pid, {:rir, 1, 24, 4})
```
"""
alias Modbus.Request
Expand Down
41 changes: 24 additions & 17 deletions scripts/sample.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
alias Modbus.Master

#opto22 rack configured as follows
# opto22 rack configured as follows
# m0 - 4p digital input
# p0 - 24V
# p1 - 0V
Expand All @@ -11,24 +11,31 @@ alias Modbus.Master
# p1 - NC
# p2 - m0.p2
# p3 - m0.p3
# m2 - 2p analog input (-10V to +10V)
# p0 - m3.p0
# p1 - m3.p1
# m3 - 2p analog output (-10V to +10V)
# p0 - m2.p0
# p1 - m2.p1

{:ok, pid} = Master.start_link([ip: {10,77,0,2}, port: 502])

#read 1 from input at slave 1 address 0 (m0.p0)
{:ok, [1]} = Master.tcp(pid, {:ri, 1, 0, 1})
#read 0 from input at slave 1 address 1 (m0.p1)
{:ok, [0]} = Master.tcp(pid, {:ri, 1, 1, 1})
#read both previous inputs at once
{:ok, [1, 0]} = Master.tcp(pid, {:ri, 1, 0, 2})
#turn off m1.p0
:ok = Master.tcp(pid, {:fc, 1, 4, 0})
#turn on m1.p1
:ok = Master.tcp(pid, {:fc, 1, 5, 1})
#alternate m1.p2 and m1.p3
:ok = Master.tcp(pid, {:fc, 1, 6, [1, 0]})

#turn off coil at slave 1 address 6 (m1.p2)
:ok = Master.tcp(pid, {:fc, 1, 6, 0})
:timer.sleep(50) #let output settle
#read 0 from input at slave 1 address 2 (m0.p2)
{:ok, [0]} = Master.tcp(pid, {:ri, 1, 2, 1})
#write -5V (IEEE 754 float) to m3.p0
:ok = Master.tcp(pid, {:phr, 1, 24, [0xc0a0, 0x0000]})
#write +5V (IEEE 754 float) to m3.p1
:ok = Master.tcp(pid, {:phr, 1, 26, [0x40a0, 0x0000]})

#turn on coil at slave 1 address 7 (m1.p3)
:ok = Master.tcp(pid, {:fc, 1, 7, 1})
:timer.sleep(50) #let output settle
#read 1 from input at slave 1 address 3 (m0.p3)
{:ok, [1]} = Master.tcp(pid, {:ri, 1, 3, 1})
:timer.sleep(20) #outputs settle delay

#read previous coils as inputs
{:ok, [0, 1, 1, 0]} = Master.tcp(pid, {:ri, 1, 4, 4})

#read previous analog channels as input registers
{:ok, [0xc0a0, 0x0000, 0x40a0, 0x0000]} = Master.tcp(pid, {:rir, 1, 24, 4})
26 changes: 9 additions & 17 deletions test/request_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,15 @@ defmodule RequestTest do
pp <<0x22, 0x0F, 0x23, 0x24, 0x00, 0x01, 0x01, 0x01>>, {:fc, 0x22, 0x2324, [1]}
pp <<0x22, 0x10, 0x23, 0x24, 0x00, 0x01, 0x02, 0x25, 0x26>>, {:phr, 0x22, 0x2324, [0x2526]}
#corner cases
pp <<0x22, 0x0F, 0x23, 0x24, 0x00, 0x08, 0x01, 0x96>>,
{:fc, 0x22, 0x2324, [0, 1, 1, 0, 1, 0, 0, 1]}
pp <<0x22, 0x0F, 0x23, 0x24, 0x00, 0x09, 0x02, 0x96, 0x01>>,
{:fc, 0x22, 0x2324, [0, 1, 1, 0, 1, 0, 0, 1, 1]}
pp <<0x22, 0x0F, 0x23, 0x24, 0x00, 0x10, 0x02, 0x96, 0xC3>>,
{:fc, 0x22, 0x2324, [0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1]}
pp <<0x22, 0x0F, 0x23, 0x24, 0x00, 0x11, 0x03, 0x96, 0xC3, 0x01>>,
{:fc, 0x22, 0x2324, [0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1]}
pp <<0x22, 0x0F, 0x23, 0x24, 0x07, 0xF8, 0xFF>> <> l2b1(bls(2040)),
{:fc, 0x22, 0x2324, bls(2040)}
pp <<0x22, 0x10, 0x23, 0x24, 0x00, 0x7F, 0xFE>> <> l2b16(rls(127)),
{:phr, 0x22, 0x2324, rls(127)}
#invalid
assert <<0x22, 0x0F, 0x23, 0x24, 0x07, 0xF9, 0x00>> <> l2b1(bls(2041))
== Request.pack({:fc, 0x22, 0x2324, bls(2041)})
assert <<0x22, 0x10, 0x23, 0x24, 0x00, 0x80, 0x00>> <> l2b16(rls(128))
Request.pack({:phr, 0x22, 0x2324, rls(128)})
pp <<0x22, 0x0F, 0x23, 0x24, 0x00, 0x08, 0x01, 0x96>>, {:fc, 0x22, 0x2324, [0,1,1,0, 1,0,0,1]}
pp <<0x22, 0x0F, 0x23, 0x24, 0x00, 0x09, 0x02, 0x96, 0x01>>, {:fc, 0x22, 0x2324, [0,1,1,0, 1,0,0,1, 1]}
pp <<0x22, 0x0F, 0x23, 0x24, 0x00, 0x10, 0x02, 0x96, 0xC3>>, {:fc, 0x22, 0x2324, [0,1,1,0, 1,0,0,1, 1,1,0,0, 0,0,1,1]}
pp <<0x22, 0x0F, 0x23, 0x24, 0x00, 0x11, 0x03, 0x96, 0xC3, 0x01>>, {:fc, 0x22, 0x2324, [0,1,1,0, 1,0,0,1, 1,1,0,0, 0,0,1,1, 1]}
pp <<0x22, 0x0F, 0x23, 0x24, 0x07, 0xF8, 0xFF>> <> l2b1(bls(2040)), {:fc, 0x22, 0x2324, bls(2040)}
pp <<0x22, 0x10, 0x23, 0x24, 0x00, 0x7F, 0xFE>> <> l2b16(rls(127)), {:phr, 0x22, 0x2324, rls(127)}
#invalid cases
assert <<0x22, 0x0F, 0x23, 0x24, 0x07, 0xF9, 0x00>> <> l2b1(bls(2041)) == Request.pack({:fc, 0x22, 0x2324, bls(2041)})
assert <<0x22, 0x10, 0x23, 0x24, 0x00, 0x80, 0x00>> <> l2b16(rls(128)) == Request.pack({:phr, 0x22, 0x2324, rls(128)})
end

defp pp(packet, cmd) do
Expand Down
69 changes: 69 additions & 0 deletions test/response_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
defmodule ResponseTest do
use ExUnit.Case
alias Modbus.Response

test "Response pack and parse test" do
pp <<0x22, 0x01, 0x01, 0x00>>, {:rc, 0x22, 0x2324, 1}, [0]
pp <<0x22, 0x01, 0x01, 0x01>>, {:rc, 0x22, 0x2324, 1}, [1]
pp <<0x22, 0x02, 0x01, 0x00>>, {:ri, 0x22, 0x2324, 1}, [0]
pp <<0x22, 0x02, 0x01, 0x01>>, {:ri, 0x22, 0x2324, 1}, [1]
pp <<0x22, 0x03, 0x02, 0x25, 0x26>>, {:rhr, 0x22, 0x2324, 1}, [0x2526]
pp <<0x22, 0x04, 0x02, 0x25, 0x26>>, {:rir, 0x22, 0x2324, 1}, [0x2526]
pp <<0x22, 0x05, 0x23, 0x24, 0x00, 0x00>>, {:fc, 0x22, 0x2324, 0}, nil
pp <<0x22, 0x05, 0x23, 0x24, 0xFF, 0x00>>, {:fc, 0x22, 0x2324, 1}, nil
pp <<0x22, 0x06, 0x23, 0x24, 0x25, 0x26>>, {:phr, 0x22, 0x2324, 0x2526}, nil
pp <<0x22, 0x0F, 0x23, 0x24, 0x00, 0x01>>, {:fc, 0x22, 0x2324, [0]}, nil
pp <<0x22, 0x10, 0x23, 0x24, 0x00, 0x01>>, {:phr, 0x22, 0x2324, [0x2526]}, nil
#corner cases
pp <<0x22, 0x01, 0x01, 0x96>>, {:rc, 0x22, 0x2324, 8}, [0,1,1,0, 1,0,0,1]
pp <<0x22, 0x01, 0x02, 0x96, 0x01>>, {:rc, 0x22, 0x2324, 9}, [0,1,1,0, 1,0,0,1, 1]
pp <<0x22, 0x01, 0x02, 0x96, 0xC3>>, {:rc, 0x22, 0x2324, 16}, [0,1,1,0, 1,0,0,1, 1,1,0,0, 0,0,1,1]
pp <<0x22, 0x01, 0x03, 0x96, 0xC3, 0x01>>, {:rc, 0x22, 0x2324, 17}, [0,1,1,0, 1,0,0,1, 1,1,0,0, 0,0,1,1, 1]
pp <<0x22, 0x01, 0xFF>> <> l2b1(bls(2040)), {:rc, 0x22, 0x2324, 2040}, bls(2040)
pp <<0x22, 0x02, 0x01, 0x96>>, {:ri, 0x22, 0x2324, 8}, [0,1,1,0, 1,0,0,1]
pp <<0x22, 0x02, 0x02, 0x96, 0x01>>, {:ri, 0x22, 0x2324, 9}, [0,1,1,0, 1,0,0,1, 1]
pp <<0x22, 0x02, 0x02, 0x96, 0xC3>>, {:ri, 0x22, 0x2324, 16}, [0,1,1,0, 1,0,0,1, 1,1,0,0, 0,0,1,1]
pp <<0x22, 0x02, 0x03, 0x96, 0xC3, 0x01>>, {:ri, 0x22, 0x2324, 17}, [0,1,1,0, 1,0,0,1, 1,1,0,0, 0,0,1,1, 1]
pp <<0x22, 0x02, 0xFF>> <> l2b1(bls(2040)), {:ri, 0x22, 0x2324, 2040}, bls(2040)
pp <<0x22, 0x03, 0xFE>> <> l2b16(rls(127)), {:rhr, 0x22, 0x2324, 127}, rls(127)
pp <<0x22, 0x04, 0xFE>> <> l2b16(rls(127)), {:rir, 0x22, 0x2324, 127}, rls(127)
#invalid cases
assert <<0x22, 0x01, 0x00>> <> l2b1(bls(2041)) == Response.pack({:rc, 0x22, 0x2324, 2041}, bls(2041))
assert <<0x22, 0x02, 0x00>> <> l2b1(bls(2041)) == Response.pack({:ri, 0x22, 0x2324, 2041}, bls(2041))
assert <<0x22, 0x03, 0x00>> <> l2b16(rls(128)) == Response.pack({:rhr, 0x22, 0x2324, 128}, rls(128))
assert <<0x22, 0x04, 0x00>> <> l2b16(rls(128)) == Response.pack({:rir, 0x22, 0x2324, 128}, rls(128))
end

defp pp(packet, cmd, vals) do
assert packet == Response.pack(cmd, vals)
assert vals == Response.parse(cmd, packet)
end

defp bls(size) do
for i <- 1..size do
rem(i, 2)
end
end

defp rls(size) do
for i <- 1..size do
i
end
end

defp l2b1(list) do
lists = Enum.chunk(list, 8, 8, [0, 0, 0, 0, 0, 0, 0, 0])
list = for [v0, v1, v2, v3, v4, v5, v6, v7] <- lists do
<< v7::1, v6::1, v5::1, v4::1, v3::1, v2::1, v1::1, v0::1 >>
end
:erlang.iolist_to_binary(list)
end

defp l2b16(list) do
list2 = for i <- list do
<<i::16>>
end
:erlang.iolist_to_binary(list2)
end

end

0 comments on commit 1959a2b

Please sign in to comment.