Class: SDN::Message

Inherits:
Object
  • Object
show all
Includes:
Helpers
Defined in:
lib/sdn/message.rb,
lib/sdn/messages/get.rb,
lib/sdn/messages/set.rb,
lib/sdn/messages/post.rb,
lib/sdn/messages/control.rb,
lib/sdn/messages/helpers.rb

Defined Under Namespace

Modules: Helpers Classes: Ack, GetGroupAddr, GetMotorDirection, GetMotorIP, GetMotorLimits, GetMotorPosition, GetMotorRollingSpeed, GetMotorStatus, GetNodeAddr, GetNodeLabel, GetNodeSerialNumber, GetNodeStackVersion, Move, MoveOf, MoveTo, Nack, PostGroupAddr, PostMotorDirection, PostMotorIP, PostMotorLimits, PostMotorPosition, PostMotorRollingSpeed, PostMotorStatus, PostNodeAddr, PostNodeLabel, PostNodeSerialNumber, PostNodeStackVersion, SetFactoryDefault, SetGroupAddr, SetMotorDirection, SetMotorIP, SetMotorLimits, SetMotorRollingSpeed, SetNodeLabel, SimpleRequest, Stop, UnknownMessage, Wink

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers

#checksum, #from_number, #from_string, #is_group_address?, #parse_address, #print_address, #to_number, #to_string, #transform_param

Constructor Details

#initialize(reserved: nil, ack_requested: false, src: nil, dest: nil) ⇒ Message

Returns a new instance of Message.



89
90
91
92
93
94
95
96
97
98
# File 'lib/sdn/message.rb', line 89

def initialize(reserved: nil, ack_requested: false, src: nil, dest: nil)
  @reserved = reserved || 0x02 # message sent to Sonesse 30
  @ack_requested = ack_requested
  if src.nil? && is_group_address?(dest)
    src = dest
    dest = nil
  end
  @src = src || [0, 0, 1]
  @dest = dest || [0, 0, 0]
end

Instance Attribute Details

#ack_requestedObject (readonly)

Returns the value of attribute ack_requested.



87
88
89
# File 'lib/sdn/message.rb', line 87

def ack_requested
  @ack_requested
end

#destObject (readonly)

Returns the value of attribute dest.



87
88
89
# File 'lib/sdn/message.rb', line 87

def dest
  @dest
end

#reservedObject (readonly)

Returns the value of attribute reserved.



87
88
89
# File 'lib/sdn/message.rb', line 87

def reserved
  @reserved
end

#srcObject (readonly)

Returns the value of attribute src.



87
88
89
# File 'lib/sdn/message.rb', line 87

def src
  @src
end

Class Method Details

.parse(io) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/sdn/message.rb', line 23

def parse(io)
  io = StringIO.new(io) if io.is_a?(String)
  data = readpartial(io, 2, allow_empty: false)
  if data.length != 2
    # don't have enough data yet; buffer it
    io.ungetbyte(data.first) if data.length == 1
    raise MalformedMessage, "Could not get message type and length"
  end
  msg = to_number(data.first)
  length = to_number(data.last)
  ack_requested = length & 0x80 == 0x80
  length &= 0x7f
  if length < 11 || length > 32
    # only skip over one byte to try and re-sync
    io.ungetbyte(data.last)
    raise MalformedMessage, "Message has bogus length: #{length}"
  end
  data.concat(readpartial(io, length - 4))
  unless data.length == length - 2
    data.reverse.each { |byte| io.ungetbyte(byte) }
    raise MalformedMessage, "Missing data: got #{data.length} expected #{length}"
  end

  message_class = constants.find { |c| (const_get(c, false).const_get(:MSG, false) rescue nil) == msg }
  message_class = const_get(message_class, false) if message_class
  message_class ||= UnknownMessage

  bogus_checksums = [SetNodeLabel::MSG, PostNodeLabel::MSG].include?(msg)

  calculated_sum = checksum(data)
  read_sum = readpartial(io, 2)
  if read_sum.length == 0 || (!bogus_checksums && read_sum.length == 1)
    read_sum.each { |byte| io.ungetbyte(byte) }
    data.reverse.each { |byte| io.ungetbyte(byte) }
    raise MalformedMessage, "Missing data: got #{data.length} expected #{length}"
  end

  # check both the proper checksum, and a truncated checksum
  unless calculated_sum == read_sum || (bogus_checksums && calculated_sum.last == read_sum.first)
      raw_message = (data + read_sum).map { |b| '%02x' % b }.join(' ')
      # skip over single byte to try and re-sync
      data.shift
      read_sum.reverse.each { |byte| io.ungetbyte(byte) }
      data.reverse.each { |byte| io.ungetbyte(byte) }
      raise MalformedMessage, "Checksum mismatch for #{message_class.name}: #{raw_message}"
  end
  # the checksum was truncated; put back the unused byte
  io.ungetbyte(read_sum.last) if calculated_sum != read_sum && read_sum.length == 2

  puts "read #{(data + read_sum).map { |b| '%02x' % b }.join(' ')}"

  reserved = to_number(data[2])
  src = transform_param(data[3..5])
  dest = transform_param(data[6..8])
  result = message_class.new(reserved: reserved, ack_requested: ack_requested, src: src, dest: dest)
  result.parse(data[9..-1])
  result.msg = msg if message_class == UnknownMessage
  result
end

.readpartial(io, length, allow_empty: true) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/sdn/message.rb', line 8

def readpartial(io, length, allow_empty: true)
  data = []
  while data.length < length
    begin
      data.concat(io.read_nonblock(length - data.length).bytes)
    rescue EOFError
      break
    rescue IO::WaitReadable
      break if allow_empty
      IO.select([io])
    end
  end
  data
end

Instance Method Details

#class_inspectObject



118
119
120
121
122
# File 'lib/sdn/message.rb', line 118

def class_inspect
  ivars = instance_variables - [:@reserved, :@ack_requested, :@src, :@dest]
  return if ivars.empty?
  ivars.map { |iv| ", #{iv}=#{instance_variable_get(iv).inspect}" }.join
end

#inspectObject



114
115
116
# File 'lib/sdn/message.rb', line 114

def inspect
  "#<%s @reserved=%02xh, @ack_requested=%s, @src=%s, @dest=%s%s>" % [self.class.name, reserved, ack_requested, print_address(src), print_address(dest), class_inspect]
end

#parse(params) ⇒ Object

Raises:



100
101
102
# File 'lib/sdn/message.rb', line 100

def parse(params)
  raise MalformedMessage, "unrecognized params for #{self.class.name}: #{params.map { |b| '%02x' % b }}" if self.class.const_defined?(:PARAMS_LENGTH) && params.length != self.class.const_get(:PARAMS_LENGTH)
end

#serializeObject



104
105
106
107
108
109
110
111
112
# File 'lib/sdn/message.rb', line 104

def serialize
  result = transform_param(reserved) + transform_param(src) + transform_param(dest) + params
  length = result.length + 4
  length |= 0x80 if ack_requested
  result = transform_param(self.class.const_get(:MSG)) + transform_param(length) + result
  result.concat(checksum(result))
  puts "wrote #{result.map { |b| '%02x' % b }.join(' ')}"
  result.pack("C*")
end