ruby-sml 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +24 -0
- data/README.md +0 -0
- data/lib/ruby-sml.rb +30 -0
- data/lib/ruby-sml/crc16.rb +47 -0
- data/lib/ruby-sml/encoding-binary.rb +262 -0
- data/lib/ruby-sml/helpers.rb +58 -0
- data/lib/ruby-sml/nilclass-mixin.rb +5 -0
- data/lib/ruby-sml/obis.rb +139 -0
- data/lib/ruby-sml/sml-attention.rb +60 -0
- data/lib/ruby-sml/sml-file.rb +37 -0
- data/lib/ruby-sml/sml-getlist.rb +88 -0
- data/lib/ruby-sml/sml-getprocparameter.rb +61 -0
- data/lib/ruby-sml/sml-getprofilelist.rb +103 -0
- data/lib/ruby-sml/sml-getprofilepack.rb +107 -0
- data/lib/ruby-sml/sml-listentry.rb +98 -0
- data/lib/ruby-sml/sml-message.rb +87 -0
- data/lib/ruby-sml/sml-messagebody.rb +95 -0
- data/lib/ruby-sml/sml-periodentry.rb +38 -0
- data/lib/ruby-sml/sml-procparametervalue.rb +46 -0
- data/lib/ruby-sml/sml-profileobjectheaderentry.rb +31 -0
- data/lib/ruby-sml/sml-profileobjectperiodentry.rb +46 -0
- data/lib/ruby-sml/sml-publicclose.rb +51 -0
- data/lib/ruby-sml/sml-publicopen.rb +84 -0
- data/lib/ruby-sml/sml-setprocparameter.rb +37 -0
- data/lib/ruby-sml/sml-time.rb +46 -0
- data/lib/ruby-sml/sml-tree.rb +42 -0
- data/lib/ruby-sml/sml-treepath.rb +24 -0
- data/lib/ruby-sml/sml-tupelentry.rb +105 -0
- data/lib/ruby-sml/sml-valueentry.rb +29 -0
- data/lib/ruby-sml/transport-binary.rb +158 -0
- data/lib/ruby-sml/units.rb +84 -0
- data/sample.rb +66 -0
- metadata +85 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
require 'ruby-sml/nilclass-mixin'
|
5
|
+
require 'ruby-sml/sml-time'
|
6
|
+
|
7
|
+
module SML
|
8
|
+
|
9
|
+
class ListEntry
|
10
|
+
attr_accessor :name, :status, :status_type, :value_time, :unit, :scaler, :value, :value_type, :signature
|
11
|
+
|
12
|
+
def initialize(name, status, status_type, value_time, unit, scaler, value, value_type, signature)
|
13
|
+
@name = name
|
14
|
+
@status = status
|
15
|
+
@status_type = status_type
|
16
|
+
@value_time = value_time
|
17
|
+
@unit = unit
|
18
|
+
@scaler = scaler
|
19
|
+
@value = value
|
20
|
+
@value_type = value_type
|
21
|
+
@signature = signature
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.construct(array_rep)
|
25
|
+
return nil if array_rep.nil?
|
26
|
+
name = array_rep.shift
|
27
|
+
status = array_rep.shift
|
28
|
+
status_type = array_rep.shift unless status.nil?
|
29
|
+
value_time = SML::Time.construct(array_rep.shift)
|
30
|
+
unit = array_rep.shift
|
31
|
+
array_rep.shift unless unit.nil?
|
32
|
+
scaler = array_rep.shift
|
33
|
+
array_rep.shift unless scaler.nil?
|
34
|
+
value = array_rep.shift
|
35
|
+
case value
|
36
|
+
when Fixnum
|
37
|
+
value_type = array_rep.shift
|
38
|
+
when String
|
39
|
+
value_type = :string
|
40
|
+
end
|
41
|
+
signature = array_rep.shift
|
42
|
+
|
43
|
+
return nil if (value.nil? or name.nil?)
|
44
|
+
return SML::ListEntry.new(name, status, status_type, value_time, unit, scaler, value, value_type, signature)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.pconstruct(o={})
|
48
|
+
return SML::ListEntry.new(o[:name], o[:status], o[:status_type], o[:value_time], o[:unit], o[:scaler], o[:value], o[:value_type], o[:signature])
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_a
|
52
|
+
result = [] << name << status << status_type << value_time.to_a << unit << :uint8 << scaler << :int8 << value
|
53
|
+
result << value_type if value.class == Fixnum
|
54
|
+
return result << signature
|
55
|
+
end
|
56
|
+
|
57
|
+
def calculate_hash(server_id)
|
58
|
+
return nil unless [:int8, :int16, :int32, :int64, :uint8, :uint16, :uint32, :uint64].include?(@value_type)
|
59
|
+
return nil unless @value_time.type == :timestamp
|
60
|
+
return nil unless @name.length == 6
|
61
|
+
|
62
|
+
bytes = String.new
|
63
|
+
bytes += server_id
|
64
|
+
(10-server_id.length).times do
|
65
|
+
bytes += [0x00].pack('c')
|
66
|
+
end
|
67
|
+
bytes += [@value_time.value].pack('N')
|
68
|
+
bytes += [@status].pack('c')
|
69
|
+
bytes += @name
|
70
|
+
bytes += [@unit].pack('c')
|
71
|
+
bytes += [@scaler].pack('c')
|
72
|
+
bytes += [(@value & 0xffff0000) >> 32,(@value & 0x0000ffff)].pack('NN')
|
73
|
+
17.times do
|
74
|
+
bytes += [0x00].pack('c')
|
75
|
+
end
|
76
|
+
|
77
|
+
hash = Digest::SHA2.new(256).digest(bytes)[0,24]
|
78
|
+
|
79
|
+
return hash
|
80
|
+
end
|
81
|
+
def sign(server_id, private_key)
|
82
|
+
hash = calculate_hash(server_id)
|
83
|
+
return nil if hash.nil?
|
84
|
+
|
85
|
+
signature = OpenSSL::PKey::EC.new(private_key).dsa_sign_asn1(hash)
|
86
|
+
|
87
|
+
@signature = signature
|
88
|
+
end
|
89
|
+
def verify(server_id, public_key)
|
90
|
+
hash = calculate_hash(server_id)
|
91
|
+
return nil if hash.nil?
|
92
|
+
|
93
|
+
return OpenSSL::PKey::EC.new(public_key).dsa_verify_asn1(hash,@signature)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'ruby-sml/nilclass-mixin'
|
2
|
+
require 'ruby-sml/sml-messagebody'
|
3
|
+
|
4
|
+
require 'ruby-sml/encoding-binary'
|
5
|
+
|
6
|
+
module SML
|
7
|
+
|
8
|
+
class Message
|
9
|
+
attr_accessor :transaction_id, :group_no, :abort_on_error, :body, :checksum
|
10
|
+
|
11
|
+
def initialize(transaction_id, group_no, abort_on_error, body, checksum)
|
12
|
+
@transaction_id = transaction_id
|
13
|
+
@group_no = group_no
|
14
|
+
@abort_on_error = abort_on_error
|
15
|
+
@body = body
|
16
|
+
@checksum = checksum
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.construct(array_rep)
|
20
|
+
return nil if array_rep.nil?
|
21
|
+
transaction_id = array_rep.shift
|
22
|
+
group_no = array_rep.shift
|
23
|
+
array_rep.shift unless group_no.nil?
|
24
|
+
abort_on_error_code = array_rep.shift
|
25
|
+
array_rep.shift unless abort_on_error_code.nil?
|
26
|
+
abort_on_error = nil
|
27
|
+
abort_on_error = case abort_on_error_code
|
28
|
+
when 0x00
|
29
|
+
:continue
|
30
|
+
when 0x01
|
31
|
+
:continue_with_next_group
|
32
|
+
when 0x02
|
33
|
+
:finish_group
|
34
|
+
when 0xff
|
35
|
+
:abort
|
36
|
+
end
|
37
|
+
body = SML::MessageBody.construct(array_rep.shift)
|
38
|
+
return nil if body.nil?
|
39
|
+
checksum = array_rep.shift
|
40
|
+
array_rep.shift unless checksum.nil?
|
41
|
+
|
42
|
+
return nil unless array_rep.shift == :end_of_message
|
43
|
+
return SML::Message.new(transaction_id, group_no, abort_on_error, body, checksum)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.pconstruct(o={})
|
47
|
+
return SML::Message.new(o[:transaction_id], o[:group_no], o[:abord_on_error], o[:body], o[:checksum])
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_a
|
51
|
+
calculate_checksum
|
52
|
+
return to_a_internal
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_a_internal
|
56
|
+
abort_on_error_code = case abort_on_error
|
57
|
+
when :continue
|
58
|
+
0x00
|
59
|
+
when :continue_with_next_group
|
60
|
+
0x01
|
61
|
+
when :finish_group
|
62
|
+
0x02
|
63
|
+
when :abort
|
64
|
+
0xff
|
65
|
+
end
|
66
|
+
|
67
|
+
result = [] << transaction_id << group_no
|
68
|
+
result << :uint8 unless group_no.nil?
|
69
|
+
result << abort_on_error_code
|
70
|
+
result << :uint8 unless abort_on_error_code.nil?
|
71
|
+
result << SML::MessageBody.to_a(body) << checksum
|
72
|
+
result << :uint16 unless checksum.nil?
|
73
|
+
return result << :end_of_message
|
74
|
+
end
|
75
|
+
|
76
|
+
def calculate_checksum
|
77
|
+
encoded = SML::BinaryEncoding.encode_value(self.to_a_internal, :array)
|
78
|
+
encoded.slice!(-3,3)
|
79
|
+
calculated_checksum = CRC16.crc16(encoded)
|
80
|
+
calculated_checksum = (calculated_checksum ^ 0xffff)
|
81
|
+
calculated_checksum = ((calculated_checksum & 0xff00) >> 8) | ((calculated_checksum & 0x00ff) << 8)
|
82
|
+
@checksum = calculated_checksum
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'ruby-sml/nilclass-mixin'
|
2
|
+
require 'ruby-sml/sml-publicopen'
|
3
|
+
require 'ruby-sml/sml-publicclose'
|
4
|
+
require 'ruby-sml/sml-getprofilepack'
|
5
|
+
require 'ruby-sml/sml-getprofilelist'
|
6
|
+
require 'ruby-sml/sml-getprocparameter'
|
7
|
+
require 'ruby-sml/sml-setprocparameter'
|
8
|
+
require 'ruby-sml/sml-getlist'
|
9
|
+
require 'ruby-sml/sml-attention'
|
10
|
+
|
11
|
+
module SML
|
12
|
+
|
13
|
+
class MessageBody
|
14
|
+
|
15
|
+
def self.construct(array_rep)
|
16
|
+
return nil if array_rep.nil?
|
17
|
+
choice = array_rep.shift
|
18
|
+
array_rep.shift unless choice.nil?
|
19
|
+
body_array = array_rep.shift
|
20
|
+
return case choice
|
21
|
+
when 0x0100
|
22
|
+
SML::PublicOpen::Request.construct(body_array)
|
23
|
+
when 0x0101
|
24
|
+
SML::PublicOpen::Response.construct(body_array)
|
25
|
+
when 0x0200
|
26
|
+
SML::PublicClose::Request.construct(body_array)
|
27
|
+
when 0x0201
|
28
|
+
SML::PublicClose::Response.construct(body_array)
|
29
|
+
when 0x0300
|
30
|
+
SML::GetProfilePack::Request.construct(body_array)
|
31
|
+
when 0x0301
|
32
|
+
SML::GetProfilePack::Response.construct(body_array)
|
33
|
+
when 0x0400
|
34
|
+
SML::GetProfileList::Request.construct(body_array)
|
35
|
+
when 0x0401
|
36
|
+
SML::GetProfileList::Response.construct(body_array)
|
37
|
+
when 0x0500
|
38
|
+
SML::GetProcParameter::Request.construct(body_array)
|
39
|
+
when 0x0501
|
40
|
+
SML::GetProcParameter::Response.construct(body_array)
|
41
|
+
when 0x0600
|
42
|
+
SML::SetProcParameter::Request.construct(body_array)
|
43
|
+
when 0x0601
|
44
|
+
nil
|
45
|
+
# SML::SetProcParameter::Response.construct(body_array)
|
46
|
+
# SML_SetProcParameter.Res is supposed to exist, but the standard doesn't define it
|
47
|
+
# respones to SetProcParameter.Req are supposed to be SML_Attention.Res'
|
48
|
+
when 0x0700
|
49
|
+
SML::GetList::Request.construct(body_array)
|
50
|
+
when 0x0701
|
51
|
+
SML::GetList::Response.construct(body_array)
|
52
|
+
when 0xff01
|
53
|
+
SML::Attention::Response.construct(body_array)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
def self.to_a(object)
|
57
|
+
choice = case object
|
58
|
+
when SML::PublicOpen::Request
|
59
|
+
0x0100
|
60
|
+
when SML::PublicOpen::Response
|
61
|
+
0x0101
|
62
|
+
when SML::PublicClose::Request
|
63
|
+
0x0200
|
64
|
+
when SML::PublicClose::Response
|
65
|
+
0x0201
|
66
|
+
when SML::GetProfilePack::Request
|
67
|
+
0x0300
|
68
|
+
when SML::GetProfilePack::Response
|
69
|
+
0x0301
|
70
|
+
when SML::GetProfileList::Request
|
71
|
+
0x0400
|
72
|
+
when SML::GetProfileList::Response
|
73
|
+
0x0401
|
74
|
+
when SML::GetProcParameter::Request
|
75
|
+
0x0500
|
76
|
+
when SML::GetProcParameter::Response
|
77
|
+
0x0501
|
78
|
+
when SML::SetProcParameter::Request
|
79
|
+
0x0600
|
80
|
+
when SML::GetList::Request
|
81
|
+
0x0700
|
82
|
+
when SML::GetList::Response
|
83
|
+
0x0701
|
84
|
+
when SML::Attention::Response
|
85
|
+
0xff01
|
86
|
+
end
|
87
|
+
|
88
|
+
result = [] << choice
|
89
|
+
result << :uint32 unless choice.nil?
|
90
|
+
return result << object.to_a
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'ruby-sml/nilclass-mixin'
|
2
|
+
|
3
|
+
module SML
|
4
|
+
|
5
|
+
class PeriodEntry
|
6
|
+
attr_accessor :name, :unit, :scaler, :value, :value_type, :signature
|
7
|
+
|
8
|
+
def initialize(name, unit, scaler, value, value_type, signature)
|
9
|
+
@name = name
|
10
|
+
@unit = unit
|
11
|
+
@scaler = scaler
|
12
|
+
@value = value
|
13
|
+
@value_type = value_type
|
14
|
+
@signature = signature
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.construct(array_rep)
|
18
|
+
return nil if array_rep.nil?
|
19
|
+
name = array_rep.shift
|
20
|
+
unit = array_rep.shift
|
21
|
+
scaler = array_rep.shift
|
22
|
+
array_rep.shift unless scaler.nil?
|
23
|
+
value = array_rep.shift
|
24
|
+
value_type = array_rep.shift
|
25
|
+
signature = array_rep.shift
|
26
|
+
|
27
|
+
return nil if value.nil?
|
28
|
+
return SML::PeriodEntry.new(name, unit, scaler, value, value_type, signature)
|
29
|
+
end
|
30
|
+
def to_a
|
31
|
+
result = [] << name << unit << scaler
|
32
|
+
result << :int8 unless scaler.nil?
|
33
|
+
return result << value << value_type << signature
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'ruby-sml/nilclass-mixin'
|
2
|
+
require 'ruby-sml/sml-periodentry'
|
3
|
+
require 'ruby-sml/sml-tupelentry'
|
4
|
+
require 'ruby-sml/sml-time'
|
5
|
+
|
6
|
+
module SML
|
7
|
+
|
8
|
+
class ProcParameterValue
|
9
|
+
|
10
|
+
def self.construct(array_rep)
|
11
|
+
return nil if array_rep.nil?
|
12
|
+
choice = array_rep.shift
|
13
|
+
array_rep.shift unless choice.nil?
|
14
|
+
body_rep = array_rep.shift
|
15
|
+
|
16
|
+
return case choice
|
17
|
+
when 0x01
|
18
|
+
body_rep
|
19
|
+
when 0x02
|
20
|
+
SML::PeriodEntry.construct(body_rep)
|
21
|
+
when 0x03
|
22
|
+
SML::TupelEntry.construct(body_rep)
|
23
|
+
when 0x04
|
24
|
+
SML::Time.construct(body_rep)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
def self.to_a(object)
|
28
|
+
choice = case object
|
29
|
+
when SML::PeriodEntry
|
30
|
+
0x02
|
31
|
+
when SML::TupelEntry
|
32
|
+
0x03
|
33
|
+
when SML::Time
|
34
|
+
0x04
|
35
|
+
else
|
36
|
+
0x01
|
37
|
+
end
|
38
|
+
|
39
|
+
result = [] << choice
|
40
|
+
result << :uint8 unless choice.nil?
|
41
|
+
return result << object
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'ruby-sml/nilclass-mixin'
|
2
|
+
|
3
|
+
module SML
|
4
|
+
|
5
|
+
class ProfileObjectHeaderEntry
|
6
|
+
attr_accessor :name, :unit, :scaler
|
7
|
+
|
8
|
+
def initialize(name, unit, scaler)
|
9
|
+
@name = name
|
10
|
+
@unit = unit
|
11
|
+
@scaler = scaler
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.construct(array_rep)
|
15
|
+
return nil if array_rep.nil?
|
16
|
+
name = array_rep.shift
|
17
|
+
unit = array_rep.shift
|
18
|
+
scaler = array_rep.shift
|
19
|
+
array_rep.shift unless scaler.nil?
|
20
|
+
|
21
|
+
return SML::ProfileObjectHeaderEntry.new(name, unit, scaler)
|
22
|
+
end
|
23
|
+
def to_a
|
24
|
+
result = [] << name << unit << scaler
|
25
|
+
result << :int8 unless scaler.nil?
|
26
|
+
return result
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'ruby-sml/nilclass-mixin'
|
2
|
+
require 'ruby-sml/sml-time'
|
3
|
+
require 'ruby-sml/sml-valueentry'
|
4
|
+
|
5
|
+
module SML
|
6
|
+
|
7
|
+
class ProfileObjectPeriodEntry
|
8
|
+
attr_accessor :val_time, :status, :value_list, :period_signature
|
9
|
+
|
10
|
+
def initialize(val_time, status, value_list, period_signature)
|
11
|
+
@val_time = val_time
|
12
|
+
@status = status
|
13
|
+
@value_list = value_list
|
14
|
+
@period_signature = period_signature
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.construct(array_rep)
|
18
|
+
return nil if array_rep.nil?
|
19
|
+
val_time = SML::Time.construct(array_rep.shift)
|
20
|
+
status = array_rep.shift
|
21
|
+
array_rep.shift unless status.nil?
|
22
|
+
value_list = []
|
23
|
+
array_rep.shift.each do |entry_array_rep|
|
24
|
+
entry = SML::ValueEntry.construct(entry_array_rep)
|
25
|
+
return nil if entry.nil?
|
26
|
+
value_list << entry
|
27
|
+
end
|
28
|
+
period_signature = array_rep.shift
|
29
|
+
|
30
|
+
return nil if val_time.nil?
|
31
|
+
return SML::ProfileObjectPeriodEntry.new(val_time, status, value_list, period_signature)
|
32
|
+
end
|
33
|
+
def to_a
|
34
|
+
value_list_array = []
|
35
|
+
value_list.each do |entry|
|
36
|
+
value_list_array << entry.to_a
|
37
|
+
end
|
38
|
+
|
39
|
+
result = [] << val_time.to_a << status
|
40
|
+
result << :uint64 unless status.nil?
|
41
|
+
return result << value_list_array << period_signature
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|