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,9 @@
1
+ require 'simatic/sessions/open_session'
2
+ require 'simatic/sessions/setup_session'
3
+ require 'simatic/sessions/read_session'
4
+ require 'simatic/sessions/write_session'
5
+
6
+ module Simatic
7
+ module Sessions
8
+ end
9
+ end
@@ -0,0 +1,50 @@
1
+ require 'simatic/sessions/session'
2
+
3
+ module Simatic
4
+ module Sessions
5
+ # Parent of all exchange sessions classes
6
+ class ExchangeSession < Session
7
+ def make_request param, data = '', udata = ''
8
+ super [0x02, 0xf0, 0x80, # 24bit pdu_start
9
+ 0x32, # 8bit header_start
10
+ 0x01, # 8bit header_type
11
+ 0x00, # 16bit
12
+ packet_number, # 16bit pdu_number
13
+ param.length, # 16bit param_length
14
+ data.length, # 16bit data_length
15
+ ].pack('CCCCCnnnn') + param + data
16
+ end
17
+
18
+ def parse_response raw_data
19
+ super
20
+
21
+ pdu_start = raw_data[4,3].unpack('C*')
22
+ unless [0x02, 0xF0, 0x80] == pdu_start
23
+ raise "unknown response recived on #{self.class} with pdu start by #{pdu_start}"
24
+ end
25
+
26
+ header_type = raw_data[8, 1].unpack('C').first
27
+ @pdu_number = raw_data[11, 2].unpack('n').first
28
+ param_length = raw_data[13, 2].unpack('n').first
29
+ data_length = raw_data[15, 2].unpack('n').first
30
+
31
+ if (2..3).member? header_type
32
+ udata_length = raw_data[17,2].unpack('n').first
33
+ data_start = 19
34
+ else
35
+ data_start = 17
36
+ end
37
+
38
+ @params = raw_data[data_start, param_length]
39
+ @data = raw_data[data_start + param_length, data_length]
40
+ @udata = raw_data[data_start + param_length + data_length, udata_length] if (2..3).member? header_type
41
+
42
+ @function = @params[0,1].unpack('C').first
43
+ @block_count = @params[1,1].unpack('C').first unless @function == FuncOpenS7Connection
44
+
45
+ # print "params "; @params.bytes.each{|byte| printf "%02X ", byte}; puts '' if DEBUG
46
+ # print "data "; @data.bytes.each{|byte| printf "%02X ", byte}; puts '' if DEBUG
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,27 @@
1
+ require 'simatic/sessions/exchange_session'
2
+
3
+ module Simatic
4
+ module Sessions
5
+ # Open Communication Request->Response session class going second
6
+ class OpenSession < ExchangeSession
7
+ def initialize max_pdu_length = nil
8
+ @@max_pdu_length = max_pdu_length if max_pdu_length
9
+ end
10
+
11
+ def make_request
12
+ param = [FuncOpenS7Connection, 0x00, 0x00, 0x01, 0x00, 0x01, @@max_pdu_length].pack('C6n')
13
+ super param
14
+ end
15
+
16
+ def parse_response raw_data
17
+ super
18
+
19
+ unless FuncOpenS7Connection == @function
20
+ raise "unknown function 0x#{@function.to_s(16)} in #{self.class} response"
21
+ end
22
+
23
+ @@max_pdu_length = @params[6,2].unpack('n').first
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,83 @@
1
+ require 'simatic/sessions/exchange_session'
2
+
3
+ module Simatic
4
+ module Sessions
5
+ # Read communication session
6
+ class ReadSession < ExchangeSession
7
+ def initialize
8
+ @read_memory = []
9
+ end
10
+
11
+ # def add memory
12
+ # @read_memory << memory if memory.instance_of? MemoryMapper
13
+ # end
14
+
15
+ def make_request memory_mappers
16
+ @memory_mappers = memory_mappers
17
+ param = [ FuncRead, # 8bit function
18
+ @memory_mappers.count, # count of read-requests
19
+ ].pack('C2')
20
+
21
+ @memory_mappers.each do |memory_mapper|
22
+ param += read_request memory_mapper
23
+ end
24
+
25
+ super param
26
+ end
27
+
28
+ def parse_response raw_data
29
+ super raw_data
30
+
31
+ unless FuncRead == @function
32
+ raise "unknown function 0x#{@function.to_s(16)} in #{self.class} response"
33
+ end
34
+
35
+ start_byte = 0
36
+ data_block_number = 0
37
+ while start_byte < @data.length
38
+
39
+ unless @data[start_byte, 1].unpack('C').first == 0xff || @data[start_byte+=1, 1].unpack('C').first == 0xff
40
+ raise "one of data block is broken in #{self.class} response"
41
+ end
42
+
43
+ data_lenght_type = @data[start_byte+1,1].unpack('C').first # 4 - bits,
44
+ # 9 - bytes,
45
+ # 3 - bits + byte
46
+ data_lenght_raw = @data[start_byte+2,2].unpack('n').first
47
+
48
+ data_lenght = data_lenght_raw
49
+ data_lenght = data_lenght_raw / 8 if data_lenght_type == 4
50
+
51
+ @memory_mappers[data_block_number].raw_data = @data[start_byte + 4, data_lenght]
52
+
53
+ start_byte += 4 + data_lenght
54
+ data_block_number += 1
55
+ end
56
+
57
+ @memory_mappers = @memory_mappers.first if @memory_mappers.count == 1
58
+ @memory_mappers
59
+ end
60
+
61
+ private
62
+
63
+ def read_request memory_mapper
64
+ # puts "@area #{@area}, @length #{@length}, @db #{@db}, @address #{@address}"
65
+
66
+ read_size = memory_mapper.bit ? 0x01 : 0x02 # 8bit read size:
67
+ # 1 = single bit,
68
+ # 2 = byte,
69
+ # 4 = word.
70
+
71
+ read_size = memory_mapper.area if [AreaT, AreaC].include? memory_mapper.area
72
+
73
+ [0x12, 0x0a, 0x10, #read-request start
74
+ read_size,
75
+ memory_mapper.length * memory_mapper.count, # 16bit lenght in bits, bytes, words
76
+ memory_mapper.db || 0, # 16bit db number
77
+ memory_mapper.area, # 8bit # area
78
+ ].pack('C4nnC') + [ memory_mapper.address * 8 + (memory_mapper.bit || 0) # 24bit start adress in bits
79
+ ].pack('N')[1,3]
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,44 @@
1
+ module Simatic
2
+ AreaP = 0x80
3
+ AreaI = 0x81
4
+ AreaQ = 0x82
5
+ AreaM = 0x83
6
+ AreaDB = 0x84
7
+ AreaC = 28
8
+ AreaT = 29
9
+
10
+ module Sessions
11
+ FuncOpenS7Connection = 0xF0
12
+ FuncRead = 0x04
13
+ FuncWrite = 0x05
14
+
15
+ # Parent of all communication sessions
16
+ class Session
17
+ @@pdu_num = 0
18
+ @@max_pdu_length = 0x3c0
19
+
20
+ def packet_number
21
+ @@pdu_num = 0 if @@pdu_num >= 0xffff
22
+ @@pdu_num += 1
23
+ end
24
+
25
+ def make_request payload
26
+ [0x03, 0x00, payload.length + 4].pack('CCn') + payload
27
+ end
28
+
29
+ def parse_response raw_data
30
+ # print "raw "; raw_data.bytes.each{|byte| printf "%02X ", byte}; puts ''
31
+
32
+ @real_length = raw_data.length
33
+ raise "empty response" if @real_length < 2
34
+
35
+ @protocol_version = raw_data[0,2].unpack('n').first
36
+ raise "unknown response 0x#{@protocol_version.to_s(16)}" if @protocol_version != 0x0300
37
+
38
+ @lenght = raw_data[2,2].unpack('n').first
39
+ raise "too short response" if @real_length < 4
40
+ raise "broken response length #{@real_length}, must be #{@lenght}" if @real_length != @lenght
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ module Simatic
2
+ module Sessions
3
+ # Setup communication sessions class going to plc first of all
4
+ class SetupSession < Session
5
+ def initialize rack, slot, communication_type = 1
6
+ @rack = rack.to_i
7
+ @slot = slot.to_i
8
+ @communication_type = communication_type
9
+ end
10
+
11
+ def make_request
12
+ super [0x11, 0xe0, 0x00, 0x00,
13
+ 0x00, 0x01, 0x00, 0xc1,
14
+ 0x02, 0x01, 0x00, 0xc2,
15
+ 0x02,
16
+ @communication_type, # 1 = PG Communication,
17
+ # 2 = OP Communication,
18
+ # 3 = Step7Basic Communication.
19
+ @rack<<4 | @slot,
20
+ 0xc0, 0x01, 0x09].pack('C*')
21
+ end
22
+
23
+ def parse_response raw_data
24
+ super
25
+ pdu_start = raw_data[4,3].unpack('C*')
26
+ unless [[0x11, 0xE0, 0x00], [0x11, 0xD0, 0x00]].include? pdu_start
27
+ raise "unknown response recived on setup session with pdu start by #{pdu_start}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,80 @@
1
+ require 'simatic/sessions/exchange_session'
2
+
3
+ module Simatic
4
+ module Sessions
5
+ # Write communication session
6
+ class WriteSession < ExchangeSession
7
+
8
+ def make_request memory_mappers
9
+ @memory_mappers = memory_mappers
10
+
11
+ param = [ FuncWrite, # 8bit function
12
+ @memory_mappers.count, # count of read-requests
13
+ ].pack('C2')
14
+
15
+ data = ''
16
+
17
+ @memory_mappers.each do |memory_mapper|
18
+ param += write_request_param memory_mapper
19
+ data += write_request_data memory_mapper
20
+ end
21
+
22
+
23
+ super param, data
24
+ end
25
+
26
+ def parse_response raw_data
27
+ super raw_data
28
+
29
+ unless FuncWrite == @function
30
+ raise "unknown function 0x#{@function.to_s(16)} in #{self.class} response"
31
+ end
32
+
33
+ result_code = @data[0,1].unpack('C').first
34
+
35
+ case result_code
36
+ when 0xff
37
+ result_code
38
+ when 0x0A
39
+ raise "Item not available, code #{result_code}" # for s7 300
40
+ when 0x03
41
+ raise "Item not available, code #{result_code}" # for s7 200
42
+ when 0x05
43
+ raise "Address out of range, code #{result_code}"
44
+ when 0x07
45
+ raise "Write data size mismatch, code #{result_code}"
46
+ else
47
+ raise "Unknown error, code #{result_code}"
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def write_request_param memory_mapper
54
+ read_size = memory_mapper.bit ? 0x01 : 0x02 #memory_mapper.bit ? 0x01 : 0x02 # 8bit read size:
55
+ # 1 = single bit,
56
+ # 2 = byte,
57
+ # 4 = word.
58
+ read_size = memory_mapper.area if [AreaT, AreaC].include? memory_mapper.area
59
+
60
+ [0x12, 0x0a, 0x10, #read-request start
61
+ read_size,
62
+ memory_mapper.length * memory_mapper.count, # 16bit lenght in bits, bytes, words
63
+ memory_mapper.db, # 16bit db number
64
+ memory_mapper.area, # 8bit # area
65
+ ].pack('C4nnC') + [ memory_mapper.address * 8 + (memory_mapper.bit || 0) # 24bit start adress in bits
66
+ ].pack('N')[1,3]
67
+ end
68
+
69
+ def write_request_data memory_mapper
70
+ [ 0x09,
71
+ memory_mapper.bit ? 0x03 : 0x04, # 4 - bits,
72
+ # 9 - bytes,
73
+ # 3 - bits + byte
74
+ memory_mapper.raw_data.length*(memory_mapper.bit ? memory_mapper.count : 8),
75
+
76
+ ].pack('CCn') + memory_mapper.raw_data
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,134 @@
1
+ require 'simatic/types/bool'
2
+ require 'simatic/types/byte'
3
+ require 'simatic/types/char'
4
+ require 'simatic/types/date_and_time'
5
+ require 'simatic/types/dint'
6
+ require 'simatic/types/dword'
7
+ require 'simatic/types/iec_date'
8
+ require 'simatic/types/iec_time'
9
+ require 'simatic/types/int'
10
+ require 'simatic/types/real'
11
+ require 'simatic/types/s5_time'
12
+ require 'simatic/types/s7_string'
13
+ require 'simatic/types/time_of_day'
14
+ require 'simatic/types/word'
15
+
16
+ module Simatic
17
+ module Types
18
+ def self.avaliable
19
+ [:bool,
20
+ :byte,
21
+ :char,
22
+ :date_and_time,
23
+ :dint,
24
+ :dword,
25
+ :iec_date,
26
+ :iec_time,
27
+ :int,
28
+ :real,
29
+ :s5_time,
30
+ :s7_string,
31
+ :time_of_day,
32
+ :word]
33
+ end
34
+
35
+ def self.parse raw, type
36
+ parser = case type.to_sym
37
+ when :bool
38
+ Bool
39
+ when :byte
40
+ Byte
41
+ when :char
42
+ Char
43
+ when :date_and_time
44
+ DateAndTime
45
+ when :dint
46
+ Dint
47
+ when :dword
48
+ Dword
49
+ when :iec_date
50
+ IECDate
51
+ when :iec_time
52
+ IECTime
53
+ when :int
54
+ Int
55
+ when :real
56
+ Real
57
+ when :s5_time
58
+ S5Time
59
+ when :s7_string
60
+ S7String
61
+ when :time_of_day
62
+ TimeOfDay
63
+ when :word
64
+ Word
65
+ else
66
+ nil
67
+ end
68
+
69
+ raise "Unknown type #{type}" if parser.nil?
70
+ parser.parse raw if parser
71
+ end
72
+
73
+ def self.get value, type
74
+ raw = nil
75
+ parser = case type.to_sym
76
+ when :bool
77
+ if value.kind_of? String
78
+ if value == '0'
79
+ raw = false
80
+ elsif value.downcase == 'false'
81
+ raw = false
82
+ else
83
+ raw = value.to_i rescue value
84
+ end
85
+ end
86
+ Bool
87
+ when :byte
88
+ raw = value.to_i if value.kind_of? String
89
+ Byte
90
+ when :char
91
+ Char
92
+ when :date_and_time
93
+ raw = Time.parse value if value.kind_of? String
94
+ DateAndTime
95
+ when :dint
96
+ raw = value.to_i if value.kind_of? String
97
+ Dint
98
+ when :dword
99
+ raw = value.to_i if value.kind_of? String
100
+ Dword
101
+ when :iec_date
102
+ raw = Date.parse value if value.kind_of? String
103
+ IECDate
104
+ when :iec_time
105
+ raw = value.to_f if value.kind_of? String
106
+ IECTime
107
+ when :int
108
+ raw = value.to_i if value.kind_of? String
109
+ Int
110
+ when :real
111
+ raw = value.to_f if value.kind_of? String
112
+ Real
113
+ when :s5_time
114
+ raw = value.to_f if value.kind_of? String
115
+ S5Time
116
+ when :s7_string
117
+ S7String
118
+ when :time_of_day
119
+ raw = value.to_f if value.kind_of? String
120
+ TimeOfDay
121
+ when :word
122
+ raw = value.to_i if value.kind_of? String
123
+ Word
124
+ when :auto
125
+ return value
126
+ else
127
+ nil
128
+ end
129
+
130
+ raise "Unknown type #{type}" if parser.nil?
131
+ parser.new(raw) if parser
132
+ end
133
+ end
134
+ end