lib/maxcube/messages.rb in maxcube-client-0.4.1 vs lib/maxcube/messages.rb in maxcube-client-0.5.0
- old
+ new
@@ -1,76 +1,125 @@
require 'maxcube'
module MaxCube
+ # Encapsulates methods related to Cube messages,
+ # i.e. parsing and serializing of TCP/UDP messages.
+ # It does not provide any network features
+ # (this is responsibility of {Network}.
module Messages
+ # Device modes that determines geating scheduling.
DEVICE_MODE = %i[auto manual vacation boost].freeze
+ # Device types identified in Cube protocol.
DEVICE_TYPE = %i[cube
radiator_thermostat radiator_thermostat_plus
wall_thermostat
shutter_contact eco_switch].freeze
+ # Names of days of week in order Cube protocol uses.
DAYS_OF_WEEK = %w[Saturday Sunday Monday
Tuesday Wednesday Thursday Friday].freeze
- PACK_FORMAT = %w[x C n N N].freeze
-
+ # Base exception class
+ # that denotes an error during message parsing/serializing.
class InvalidMessage < RuntimeError; end
+ # Exception class that denotes that message is too short/long.
class InvalidMessageLength < InvalidMessage
+ # @param info contains context information to occured error.
def initialize(info = 'invalid message length')
super
end
end
+ # Exception class that denotes unrecognized message type.
class InvalidMessageType < InvalidMessage
+ # @param msg_type type of message that is being parsed/serialized.
+ # @param info contains context information to occured error.
def initialize(msg_type, info = 'invalid message type')
super("#{info}: #{msg_type}")
end
end
+ # Exception class that denotes invalid syntax format of message.
class InvalidMessageFormat < InvalidMessage
+ # @param info contains context information to occured error.
def initialize(info = 'invalid format')
super
end
end
+ # Exception class that denotes that an error occured
+ # while parsing/serializing message body,
+ # which is specific to message type.
class InvalidMessageBody < InvalidMessage
+ # @param msg_type type of message that is being parsed/serialized.
+ # @param info contains context information to occured error.
def initialize(msg_type, info = 'invalid format')
super("message type #{msg_type}: #{info}")
end
end
private
+ # Applies a block to given arguments
+ # in order to perform conversion to certain type.
+ # If conversion fails, {InvalidMessageBody} is raised.
+ # Thus, this method can be used also for type checking purposes only.
+ # @param type [#to_s] name of the type to convert to.
+ # @param info [#to_s] context information to pass to raised error.
+ # @param args [Array] arguments to be converted into the same type.
+ # @yield a rule to provide
+ # certain type check and conversion of arguments.
+ # @return [Array] converted elements.
+ # @raise [InvalidMessageBody] if conversion fails.
def conv_args(type, info, *args, &block)
info = info.to_s.tr('_', ' ')
args.map(&block)
rescue ArgumentError, TypeError
raise InvalidMessageBody
.new(@msg_type,
"invalid #{type} format of arguments #{args} (#{info})")
end
- # Convert string of characters (not binary data!) to hex number
- # For binary data use #String.unpack
+ # Uses {#conv_args} to convert numbers
+ # or string of characters (not binary data!)
+ # to integers in given base (radix).
+ # For binary data use {Parser#read}.
+ # @param base [Integer] integers base (radix), 0 means auto-recognition.
+ # @param args [Array<#Integer>] arguments to convert to integers.
+ # @return [Array<Integer>] converted elements.
def to_ints(base, info, *args)
base_str = base.zero? ? '' : "(#{base})"
conv_args("integer#{base_str}", info, *args) { |x| Integer(x, base) }
end
+ # Uses {#to_ints}, but operates with single argument.
+ # @param arg [#Integer] argument to convert to integer.
+ # @return [Integer] converted element.
def to_int(base, info, arg)
to_ints(base, info, arg).first
end
+ # Uses {#conv_args} to convert numbers
+ # or string of characters (not binary data!) to floats.
+ # @param args [Array<#Float>] arguments to convert to floats.
+ # @return [Array<Float>] converted elements.
def to_floats(info, *args)
conv_args('float', info, *args) { |x| Float(x) }
end
+ # Uses {#to_floats}, but operates with single argument.
+ # @param arg [#Float] argument to convert to float.
+ # @return [Float] converted element.
def to_float(info, arg)
to_floats(info, arg).first
end
+ # Uses {#conv_args} to convert objects to bools.
+ # @param args [Array] arguments to convert to bools.
+ # @return [Array<Boolean>] converted elements
+ # to +TrueClass+ or +FalseClass+.
def to_bools(info, *args)
conv_args('boolean', info, *args) do |arg|
if arg == !!arg
arg
elsif arg.nil?
@@ -81,63 +130,91 @@
!Integer(arg).zero?
end
end
end
+ # Uses {#to_bools}, but operates with single argument.
+ # @param arg argument to convert to bool.
+ # @return [Boolean] converted element
+ # to +TrueClass+ or +FalseClass+.
def to_bool(info, arg)
to_bools(info, arg).first
end
+ # Uses {#conv_args} to convert objects to +Time+.
+ # @param args [Array] arguments to convert to +Time+.
+ # @return [Array<Time>] converted elements.
def to_datetimes(info, *args)
conv_args('datetime', info, *args) do |arg|
- if arg.is_a?(DateTime)
+ if arg.is_a?(Time)
arg
- elsif arg.respond_to?('to_datetime')
- arg.to_datetime
+ elsif arg.is_a?(String)
+ Time.parse(arg)
+ elsif arg.respond_to?('to_time')
+ arg.to_time
+ elsif arg.respond_to?('to_date')
+ arg.to_date.to_time
else
- DateTime.parse(arg)
+ raise ArgumentError
end
end
end
+ # Uses {#to_datetime}, but operates with single argument.
+ # @param arg argument to convert to +Time+.
+ # @return [Time] converted element.
def to_datetime(info, arg)
to_datetimes(info, arg).first
end
+ # Helper method that checks presence of index in array
+ # (if not, exception is raised).
+ # @param ary [#[]] input container (usually constant).
+ # @param id index of element in container.
+ # @param info [#to_s] context information to pass to raised error.
+ # @return element of container if found.
+ # @raise [InvalidMessageBody] if element not found.
def ary_elem(ary, id, info)
elem = ary[id]
return elem if elem
raise InvalidMessageBody
.new(@msg_type, "unrecognized #{info} id: #{id}")
end
+ # Reverse method to {#ary_elem}.
def ary_elem_id(ary, elem, info)
id = ary.index(elem)
return id if id
raise InvalidMessageBody
.new(@msg_type, "unrecognized #{info}: #{elem}")
end
+ # Uses {#ary_elem} with {DEVICE_TYPE}
def device_type(device_type_id)
ary_elem(DEVICE_TYPE, device_type_id, 'device type')
end
+ # Uses {#ary_elem_id} with {DEVICE_TYPE}
def device_type_id(device_type)
ary_elem_id(DEVICE_TYPE, device_type.to_sym, 'device type')
end
+ # Uses {#ary_elem} with {DEVICE_MODE}
def device_mode(device_mode_id)
ary_elem(DEVICE_MODE, device_mode_id, 'device mode')
end
+ # Uses {#ary_elem_id} with {DEVICE_MODE}
def device_mode_id(device_mode)
ary_elem_id(DEVICE_MODE, device_mode.to_sym, 'device mode')
end
+ # Uses {#ary_elem} with {DAYS_OF_WEEK}
def day_of_week(day_id)
ary_elem(DAYS_OF_WEEK, day_id, 'day of week')
end
+ # Uses {#ary_elem_id} with {DAYS_OF_WEEK}
def day_of_week_id(day)
if day.respond_to?('to_i') && day.to_i.between?(1, 7)
return (day.to_i + 1) % 7
end
ary_elem_id(DAYS_OF_WEEK, day.capitalize, 'day of week')