Module: MaxCube::Messages::TCP::Parser::MessageC

Defined in:
lib/maxcube/messages/tcp/type/c.rb

Overview

Configuration message.

Constant Summary

KEYS =

Mandatory hash keys.

%i[length address rf_address device_type
test_result serial_number].freeze
OPT_KEYS =

Optional hash keys.

%i[
  firmware_version _firmware_version room_id

  portal_enabled button_up_mode button_down_mode portal_url

  comfort_temperature eco_temperature
  max_setpoint_temperature min_setpoint_temperature
  temperature_offset window_open_temperature window_open_duration
  boost_duration valve_opening
  decalcification_day decalcification_hour
  max_valve_setting valve_offset

  unknown unknown1 unknown2 unknown3 unknown4 weekly_program
].freeze
LENGTHS =
[6].freeze

Instance Method Summary collapse

Instance Method Details

#parse_tcp_c(body) ⇒ Object (private)



31
32
33
34
35
36
37
38
39
40
# File 'lib/maxcube/messages/tcp/type/c.rb', line 31

def parse_tcp_c(body)
  addr, enc_data = parse_tcp_c_split(body)

  @io = StringIO.new(decode(enc_data), 'rb')

  hash = parse_tcp_c_head(addr)
  parse_tcp_c_device_type(hash)

  hash
end

#parse_tcp_c_cubeObject (private)



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/maxcube/messages/tcp/type/c.rb', line 117

def parse_tcp_c_cube
  hash = {
    portal_enabled: !read(1, true).zero?,
    unknown1: read(11),
  }

  pushbutton_up_config = read(1, true)
  hash[:unknown2] = read(32)
  pushbutton_down_config = read(1, true)
  parse_tcp_c_cube_button_mode(hash,
                               pushbutton_up_config,
                               pushbutton_down_config)

  # ! Exact decoding of time zones is not clear yet
  hash.merge!(
    unknown3: read(21),
    portal_url: read(128),
    # _timezone_winter: read(5),
    # timezone_winter_month: read(1, true),
    # timezone_winter_day: DAYS_OF_WEEK[read(1, true)],
    # timezone_winter_hour: read(1, true),
    # _timezone_winter_offset: read(4),
    # _timezone_daylight: read(5),
    # timezone_daylight_month: read(1, true),
    # timezone_daylight_day: DAYS_OF_WEEK[read(1, true)],
    # timezone_daylight_hour: read(1, true),
    # _timezone_daylight_offset: read(4),
    # unknown4: read(1),
    unknown4: read,
  )
end

#parse_tcp_c_cube_button_mode(hash, up, down) ⇒ Object (private)



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/maxcube/messages/tcp/type/c.rb', line 92

def parse_tcp_c_cube_button_mode(hash, up, down)
  { 'up' => up, 'down' => down }.each do |k, v|
    mode_key = "button_#{k}_mode".to_sym
    case v
    when 0x00
      hash[mode_key] = :auto
    when 0x41
      hash[mode_key] = :eco
    when 0x42
      hash[mode_key] = :comfort
    else
      temp_key = "button_#{k}_temperature".to_sym
      if v.between?(0x09, 0x3d)
        hash[mode_key] = :auto_temp
        hash[temp_key] = parse_tcp_c_cube_button_mode_temp(v, 0x09)
      elsif v.between?(0x49, 0x7d)
        hash[mode_key] = :manual
        hash[temp_key] = parse_tcp_c_cube_button_mode_temp(v, 0x49)
      else
        hash[mode_key] = :unknown
      end
    end
  end
end

#parse_tcp_c_cube_button_mode_temp(value, base) ⇒ Object (private)



88
89
90
# File 'lib/maxcube/messages/tcp/type/c.rb', line 88

def parse_tcp_c_cube_button_mode_temp(value, base)
  (value - base).to_f / 2 + 4.5
end

#parse_tcp_c_device_type(hash) ⇒ Object (private)



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/maxcube/messages/tcp/type/c.rb', line 212

def parse_tcp_c_device_type(hash)
  device_type = hash[:device_type]
  hash.merge!(
    case device_type
    when :cube
      parse_tcp_c_cube
    when :radiator_thermostat, :radiator_thermostat_plus
      parse_tcp_c_radiator
    when :wall_thermostat
      parse_tcp_c_wall
    else
      {}
    end
  )
rescue IOError
  device_type_str = device_type.to_s.split('_')
                               .map(&:capitalize).join(' ')
  raise InvalidMessageBody
    .new(@msg_type,
         'unexpected EOF reached in decoded message data of ' \
         "'#{device_type_str}' device type")
end

#parse_tcp_c_head(addr) ⇒ Object (private)



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
82
83
84
85
86
# File 'lib/maxcube/messages/tcp/type/c.rb', line 51

def parse_tcp_c_head(addr)
  @length = read(1, true)
  # 'rf_address' should correspond with 'addr',
  # but it is not checked (yet)
  rf_address = read(3, true)
  device_type = device_type(read(1, true))
  hash = {
    address: addr,
    length: @length,
    rf_address: rf_address,
    device_type: device_type,
  }

  if device_type == :cube
    # For 'cube' type, both fiels seem to be combined
    # into 'firmware_version' string
    room_id__fw_v = read(2, 'H*')
    hash[:firmware_version] = room_id__fw_v[2..3] +
                              room_id__fw_v[0..1]
  else
    # For other types, both 'room_id' and 'firmware_version'
    # are unpacked as numbers
    # How should be 'firmware_version' interpreted ?
    hash[:room_id] = read(1, true)
    hash[:_firmware_version] = read(1, true)
  end

  hash.merge!(
    test_result: read(1, true),
    serial_number: read(10),
  )
rescue IOError
  raise InvalidMessageBody
    .new(@msg_type,
         'unexpected EOF reached at head of decoded message data')
end

#parse_tcp_c_program(subhash) ⇒ Object (private)



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/maxcube/messages/tcp/type/c.rb', line 158

def parse_tcp_c_program(subhash)
  program = DAYS_OF_WEEK.zip([]).to_h
  program.each_key do |day|
    setpoints = []
    13.times do
      setpoint = read(2, true)
      temperature = ((setpoint & 0xfe00) >> 9).to_f / 2
      time_until = (setpoint & 0x01ff) * 5
      setpoints << {
        temperature: temperature,
        hours_until: time_until / 60,
        minutes_until: time_until % 60,
      }
    end
    program[day] = setpoints
  end
  subhash[:weekly_program] = program
end

#parse_tcp_c_radiatorObject (private)



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/maxcube/messages/tcp/type/c.rb', line 177

def parse_tcp_c_radiator
  subhash = parse_tcp_c_thermostat_1.merge!(
    temperature_offset: read(1, true).to_f / 2 - 3.5,
    window_open_temperature: read(1, true).to_f / 2,
    window_open_duration: read(1, true) * 5,
  )

  boost = read(1, true)
  boost_duration = ((boost & 0xe0) >> 5) * 5
  boost_duration = 60 if boost_duration > 30

  decalcification = read(1, true)

  subhash.merge!(
    boost_duration: boost_duration,
    valve_opening: (boost & 0x1f) * 5,
    decalcification_day: day_of_week((decalcification & 0xe0) >> 5),
    decalcification_hour: decalcification & 0x1f,
    max_valve_setting: read(1, true) * (100.0 / 255),
    valve_offset: read(1, true) * (100.0 / 255),
  )

  parse_tcp_c_program(subhash)

  subhash
end

#parse_tcp_c_split(body) ⇒ Object (private)



44
45
46
47
48
49
# File 'lib/maxcube/messages/tcp/type/c.rb', line 44

def parse_tcp_c_split(body)
  addr, enc_data = body.split(',')
  check_msg_part_lengths(LENGTHS, addr)
  to_ints(16, 'device address', addr)
  [addr, enc_data]
end

#parse_tcp_c_thermostat_1Object (private)



149
150
151
152
153
154
155
156
# File 'lib/maxcube/messages/tcp/type/c.rb', line 149

def parse_tcp_c_thermostat_1
  {
    comfort_temperature: read(1, true).to_f / 2,
    eco_temperature: read(1, true).to_f / 2,
    max_setpoint_temperature: read(1, true).to_f / 2,
    min_setpoint_temperature: read(1, true).to_f / 2,
  }
end

#parse_tcp_c_wallObject (private)



204
205
206
207
208
209
210
# File 'lib/maxcube/messages/tcp/type/c.rb', line 204

def parse_tcp_c_wall
  subhash = parse_tcp_c_thermostat_1
  parse_tcp_c_program(subhash)
  subhash[:unknown] = read(3)

  subhash
end