simatic 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cb39fba52aa7abd65f8a1f837b6a8bb1e5572ffa
4
+ data.tar.gz: 0e88119b704b2d9c95d6f4744163ac073e5cebc4
5
+ SHA512:
6
+ metadata.gz: b140bdef40f8ad4a3ffc43ddb4ed828703984fea3c5a5044f40e24dcaa728f63b78c774c55ed006dd7dd2e8ce0a2ebae54485bdd526666d152942f7fbed5efa3
7
+ data.tar.gz: cac43153f5aebb686b11ed143b9e0edf6d0e052b7138232ec2e70fd48f9dd8e56be2b1337a6cdf9f6834a709bc0a16132fa63cd78baf2ac80ec910ae0b85c290
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in simatic.gemspec
4
+ gemspec
@@ -0,0 +1,91 @@
1
+ # ruby_s7plc
2
+ Ruby library for Siemens Simatic S7-300 PLC data exchange.
3
+
4
+ ## Quick start
5
+ ```ruby
6
+ require 'simatic'
7
+ Simatic::Plc.exchange('192.168.0.1') do |plc|
8
+ plc.write('db1.dbw2'=> 0xffff)
9
+ res = plc.read('db1.dbw2')
10
+ puts "#{res.verbal} = #{Simatic::Types::Word.parse(res.value)}"
11
+ end
12
+ ```
13
+
14
+ ## Reference
15
+ ### Requires
16
+ ```ruby
17
+ require 'simatic'
18
+ ```
19
+
20
+ ### Client creation
21
+ ```ruby
22
+ plc = Simatic::Plc.new('192.168.0.1')
23
+ plc.connect
24
+ # Add you code here
25
+ plc.disconnect
26
+ ```
27
+
28
+ Or with block:
29
+ ```ruby
30
+ Simatic::Plc.exchange('192.168.0.1') do |plc|
31
+ # Add you code here
32
+ end
33
+ ```
34
+
35
+ You can specify rack and slot parametrs by adding a hash args to functions <new> or <exchange>, defaults rack: 0, slot: 2
36
+ ```ruby
37
+ Simatic::Plc.new('192.168.0.1', rack: 0, slot: 2) ...
38
+ Simatic::Plc.exchange('192.168.0.1', rack: 0, slot: 2) do |plc| ...
39
+ ```
40
+
41
+ ### Exchange functions
42
+ ```ruby
43
+ plc.read('db1.dbx0.0')
44
+ ```
45
+ This function can understand verbal adressing in Simatic Step 7 notation, like M0.0, DB1.DBB23, C0, T1 and so on.
46
+ Result of this funtion is a MemoryMapper object that can give you ```raw_data``` and ```value``` of plc response.
47
+ After this function you should use SymaticTypes module to convert data into ruby undestandable types.
48
+ ```ruby
49
+ plc.write('db1.dbw2' => 0xffff)
50
+ ```
51
+ This function take a hash with verbal addresses and values to write pairs. Values can be simple types like Integer, Float, one char String, Date, Time or it can be special types of SimaticTypes Module.
52
+ ```ruby
53
+ plc.write('db1.dbb18[2]' => Simatic::Types::S5time.new(711.3))
54
+ ```
55
+
56
+ ### Convertation functions
57
+ ```ruby
58
+ Simatic::Types::Bool # use TrueClass, FalseClass
59
+ Simatic::Types::Byte # use Numeric
60
+ Simatic::Types::Word # use Numeric
61
+ Simatic::Types::Dword # use Numeric
62
+ Simatic::Types::Int # use Numeric
63
+ Simatic::Types::Dint # use Numeric
64
+ Simatic::Types::Real # use Numeric
65
+ Simatic::Types::S5time # use Numeric as a time in seconds
66
+ Simatic::Types::IECTime # use Numeric as a time in seconds
67
+ Simatic::Types::IECDate # use Numeric as a days
68
+ Simatic::Types::TimeOfDay # use Numeric as a seconds from start of day 0:00 a.m.
69
+ Simatic::Types::DateAndTime # use Time
70
+ Simatic::Types::S7String # use String with length of buffer param
71
+ ```
72
+ Each of this objects take Ruby types with ```new``` method and gives you with ```parse``` method:
73
+ ```ruby
74
+ Simatic::Types::S5time.new(10.2)
75
+ Simatic::Types::S5time.parse(res.raw_data)
76
+ Simatic::Types::S7String.new("hello world", 15) # 15 is a length of buffer (address for this string to read or write must be [17])
77
+ ```
78
+ For special types like S5time, IECTime, IECDate, TimeOfDay, DateAndTime, S7String you should use ```raw_data``` method instead of `value`, because if you write an adress with [] notation of bytes ```value``` will return a array of chars.
79
+
80
+ ## Waring
81
+ This is beta code! Do not use it in danger manufactory!
82
+
83
+ ## See also
84
+ Father and oldest of all open Simatic communication libraries is Libnodave project:
85
+ * http://libnodave.sourceforge.net/
86
+
87
+ Another interesting projects:
88
+ * https://github.com/kprovost/libs7comm
89
+ * https://github.com/kirilk/simatic
90
+ * https://github.com/killnine/s7netplus
91
+ * https://github.com/plcpeople/nodeS7
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "simatic"
5
+
6
+ ip = ARGV.shift
7
+ address = ARGV.shift
8
+
9
+ type = nil
10
+ slot = 2
11
+ rack = 0
12
+
13
+ while param = ARGV.shift do
14
+ case param
15
+ when '-t', '--type'
16
+ type = ARGV.shift
17
+ when '--slot'
18
+ slot = ARGV.shift
19
+ when '--rack'
20
+ rack = ARGV.shift
21
+ when '-h', '--help'
22
+ puts "USAGE"
23
+ puts "read <ip> <address> (-t|--type <type>) [--rack <rack>] [--slot <slot>] [-h|--help]"
24
+ puts "avaliable types #{Simatic::Types.avaliable}"
25
+ puts "EXAMPLE"
26
+ puts "read 192.168.0.1 db0.dbw4 -t int"
27
+ exit
28
+ end
29
+ end
30
+
31
+ #puts "address #{address}"
32
+ #puts "type #{type}"
33
+ #puts "rack #{rack}"
34
+ #puts "slot #{slot}"
35
+
36
+ Simatic::Plc.exchange(ip, rack: rack, slot: slot) do |plc|
37
+ value = plc.read(address).value
38
+ puts "#{address}: #{Simatic::Types.parse value, type}"
39
+ end
@@ -0,0 +1,7 @@
1
+ require 'simatic/types'
2
+ require 'simatic/plc'
3
+ require 'simatic/version'
4
+
5
+ module Simatic
6
+ # Your code goes here...
7
+ end
@@ -0,0 +1,191 @@
1
+ module Simatic
2
+ class MemoryMapper
3
+ attr_reader :verbal
4
+ attr_reader :raw_values
5
+ attr_accessor :raw_data
6
+
7
+ attr_reader :length
8
+ attr_reader :count
9
+
10
+ attr_reader :area
11
+ attr_reader :db
12
+ attr_reader :address
13
+ attr_reader :bit
14
+
15
+ def initialize verbal, args={}
16
+ @verbal = verbal
17
+
18
+ parse_address @verbal
19
+ # puts args[:value]
20
+ self.value = args[:value] unless args[:value].nil?
21
+ end
22
+
23
+ def parse_address verbal = @verbal
24
+ /^(DB\s*(?<db>\d+)(\.|\s*))?(?<area>PI|PQ|I|Q|M|DB|T|C)(?<type>X|B|W|D)?\s*?(?<adr>\d+)(\.(?<bit>\d+))?\s*?(\[(?<count>\d+)\])?$/i =~ verbal
25
+
26
+ @db = db ? db.to_i : 0
27
+ @count = count ? count.to_i : 1
28
+ @address = adr ? adr.to_i : nil
29
+ @bit = bit ? bit.to_i : nil
30
+
31
+ unless @address
32
+ raise "Adressing by #{verbal} is impossible. Cant parse it."
33
+ end
34
+
35
+ case area.upcase
36
+ when 'PI','PQ'
37
+ @area = AreaP
38
+ when 'I'
39
+ @area = AreaI
40
+ when 'Q'
41
+ @area = AreaQ
42
+ when 'M'
43
+ @area = AreaM
44
+ when 'DB'
45
+ @area = AreaDB
46
+ when 'T'
47
+ @area = AreaT
48
+ when 'C'
49
+ @area = AreaC
50
+ end
51
+
52
+ case type ? type.upcase : 'X'
53
+ when 'B'
54
+ @length = 1
55
+ when 'W'
56
+ @length = 2
57
+ when 'D'
58
+ @length = 4
59
+ else
60
+ @length = 1
61
+ unless @bit
62
+ raise "Adressing by #{verbal} is impossible. Cant parse it. Is bit expected?"
63
+ end
64
+ end
65
+ end
66
+
67
+ def value= new_value
68
+ val_array = (new_value.kind_of? Array) ? new_value : [new_value]
69
+
70
+ @raw_values = []
71
+ val_array.each do |v|
72
+
73
+ val = case v
74
+ when Simatic::Types::SimaticType
75
+ v
76
+ when Integer
77
+ case @length
78
+ when 1
79
+ Simatic::Types::Byte.new(v)
80
+ when 2
81
+ Simatic::Types::Int.new(v)
82
+ when 4
83
+ Simatic::Types::Dint.new(v)
84
+ else
85
+ raise "Cannot convert #{v.class} to SimaticType class"
86
+ end
87
+ when Float
88
+ Simatic::Types::Real.new(v)
89
+ when TrueClass, FalseClass
90
+ Simatic::Types::Bool.new(v)
91
+ when String
92
+ raise "Cannot write string to plc use SimaticType::S7String class" if length > 1
93
+ Simatic::Types::Char.new(v) if v.length == 1
94
+ when Date
95
+ Simatic::Types::IECDate.new(v)
96
+ when Time
97
+ Simatic::Types::DateAndTime.new(v)
98
+ else
99
+ raise "Unknoun type of write value <#{v.class}> #{v}"
100
+ end
101
+
102
+ @raw_values << val.serialize
103
+ end
104
+
105
+ @raw_data = @raw_values.inject { |sum, v| sum+v }
106
+
107
+ if @raw_data.length != @length*@count
108
+ raise "This values #{val_array} can not be writed because adress #{verbal} mean #{@length*@count} byte(s) instead #{@raw_data.length}"
109
+ end
110
+ end
111
+
112
+ def value
113
+ @raw_values = []
114
+
115
+ byte = 0
116
+ while byte < @count*@length
117
+ @raw_values << @raw_data[byte, @length]
118
+ byte += @length
119
+ end
120
+
121
+ result = @raw_values
122
+
123
+ # .map do |raw|
124
+
125
+ # case type
126
+ # when :bool
127
+
128
+ # bit = @bit ? 0 : (args[:bit] || nil)
129
+
130
+ # binary_string = raw.unpack('b*').first
131
+
132
+ # if bit
133
+ # binary_string[bit] == '1' ? true : false
134
+ # else
135
+ # binary_string.index('1') ? true : false
136
+ # end
137
+
138
+ # when :int8
139
+ # raw.unpack('c').first
140
+ # when :uint8
141
+ # raw.unpack('C').first
142
+ # when :int16
143
+ # raw.unpack('s>').first
144
+ # when :uint16
145
+ # raw.unpack('S>').first # try n
146
+ # when :int32
147
+ # raw.unpack('l>').first
148
+ # when :uint32
149
+ # raw.unpack('L>').first # try N
150
+ # when :float32
151
+ # raw.unpack('g').first
152
+ # when :float64
153
+ # raw.unpack('G').first
154
+ # when :text8
155
+ # raw.unpack('A*').first
156
+ # when :text16
157
+ # raw.unpack('u*').first
158
+ # else
159
+ # raw
160
+ # end
161
+ # end
162
+
163
+ result = result.first if result.count == 1
164
+ result
165
+ end
166
+
167
+ def as_bool raw
168
+ end
169
+
170
+ def as_int raw
171
+ end
172
+
173
+ def as_uint raw
174
+ end
175
+
176
+ def as_real raw_data
177
+ end
178
+
179
+ def as_s5time raw_data
180
+ end
181
+
182
+ def as_time raw_data
183
+ end
184
+
185
+ def as_date raw_data
186
+ end
187
+
188
+ def as_string raw_data
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,100 @@
1
+ require 'socket'
2
+ require 'simatic/sessions'
3
+ require 'simatic/memory_mapper'
4
+
5
+ module Simatic
6
+ BUFFER_SIZE = 1000
7
+
8
+ class Plc
9
+ def initialize address, args = {}
10
+ @address = address
11
+ @rack = args[:rack] || 0
12
+ @slot = args[:slot] || 2
13
+ end
14
+
15
+ def self.exchange address, args = {}, timeout = 500
16
+ plc = self.new address, args
17
+
18
+ plc.connect timeout
19
+ yield plc
20
+ plc.disconnect
21
+ end
22
+
23
+ def read *args
24
+ if @socket.nil?
25
+ raise "Plc #{@address} is not connected"
26
+ end
27
+
28
+ read = Sessions::ReadSession.new
29
+ memory_mappers = []
30
+ args.each do |verbal|
31
+ memory_mappers << MemoryMapper.new(verbal)
32
+ end
33
+
34
+ request = read.make_request memory_mappers
35
+ @socket.send request, 0
36
+
37
+ # debug_print request
38
+
39
+ response = @socket.recv BUFFER_SIZE
40
+
41
+ # debug_print response
42
+
43
+ result = read.parse_response response
44
+
45
+ # Hash[result.map { |memory_mapper| [memory.verbal, memory.value] }]
46
+ end
47
+
48
+ def write args
49
+ if @socket.nil?
50
+ raise "Plc #{@address} is not connected"
51
+ end
52
+
53
+ memory_mappers = []
54
+ args.each do |verbal, value|
55
+ memory_mappers << MemoryMapper.new(verbal, value: value)
56
+ end
57
+
58
+ write = Sessions::WriteSession.new
59
+ request = write.make_request memory_mappers
60
+ @socket.send request, 0
61
+
62
+ # debug_print request
63
+
64
+ response = @socket.recv BUFFER_SIZE
65
+
66
+ # debug_print response
67
+
68
+ result = write.parse_response response
69
+ end
70
+
71
+ def connect timeout = 500
72
+ @socket = Socket.tcp @address, 102, connect_timeout: timeout / 1000.0
73
+
74
+ setup = Sessions::SetupSession.new @rack, @slot
75
+ @socket.send setup.make_request, 0
76
+ setup.parse_response @socket.recv BUFFER_SIZE
77
+
78
+ open = Sessions::OpenSession.new
79
+ @socket.send open.make_request, 0
80
+ open.parse_response @socket.recv BUFFER_SIZE
81
+
82
+ end
83
+
84
+ def disconnect
85
+ @socket.close
86
+ end
87
+
88
+ private
89
+
90
+ def debug_print raw
91
+ puts '-'*80
92
+ raw.bytes.each_with_index do |byte, i|
93
+ ends = (i+1)%16 == 0 ? "\n" : ' '
94
+ printf '%02X '+ ends, byte
95
+ end
96
+ puts ''
97
+ puts '-'*80
98
+ end
99
+ end
100
+ end