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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +4 -0
- data/README.md +91 -0
- data/Rakefile +1 -0
- data/bin/read +39 -0
- data/lib/simatic.rb +7 -0
- data/lib/simatic/memory_mapper.rb +191 -0
- data/lib/simatic/plc.rb +100 -0
- data/lib/simatic/sessions.rb +9 -0
- data/lib/simatic/sessions/exchange_session.rb +50 -0
- data/lib/simatic/sessions/open_session.rb +27 -0
- data/lib/simatic/sessions/read_session.rb +83 -0
- data/lib/simatic/sessions/session.rb +44 -0
- data/lib/simatic/sessions/setup_session.rb +32 -0
- data/lib/simatic/sessions/write_session.rb +80 -0
- data/lib/simatic/types.rb +134 -0
- data/lib/simatic/types/bool.rb +18 -0
- data/lib/simatic/types/byte.rb +10 -0
- data/lib/simatic/types/char.rb +15 -0
- data/lib/simatic/types/date_and_time.rb +43 -0
- data/lib/simatic/types/dint.rb +10 -0
- data/lib/simatic/types/dword.rb +10 -0
- data/lib/simatic/types/iec_date.rb +20 -0
- data/lib/simatic/types/iec_time.rb +16 -0
- data/lib/simatic/types/int.rb +10 -0
- data/lib/simatic/types/real.rb +10 -0
- data/lib/simatic/types/s5_time.rb +47 -0
- data/lib/simatic/types/s7_string.rb +38 -0
- data/lib/simatic/types/simatic_simple_type.rb +20 -0
- data/lib/simatic/types/simatic_type.rb +34 -0
- data/lib/simatic/types/time_of_day.rb +8 -0
- data/lib/simatic/types/word.rb +10 -0
- data/lib/simatic/version.rb +3 -0
- data/simatic.gemspec +27 -0
- metadata +94 -0
|
@@ -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
|