class Fancybox2::Module::Base

Attributes

configs[RW]
fbxfile[R]
fbxfile_path[R]
logger[R]
mqtt_client[R]
status[R]

Public Class Methods

new(fbxfile_path, options = {}) click to toggle source
# File lib/fancybox2/module/base.rb, line 16
def initialize(fbxfile_path, options = {})
  unless fbxfile_path || fbxfile_path.is_a?(String) || fbxfile_path.empty?
    raise FbxfileNotProvided
  end

  @fbxfile_path = fbxfile_path
  options.deep_symbolize_keys!
  @internal_mqtt_client = false

  @fbxfile = check_and_return_fbxfile options.fetch(:fbxfile, load_fbx_file)
  @mqtt_client_params = options[:mqtt_client_params] || {}
  check_or_build_mqtt_client options[:mqtt_client]
  @log_level = options.fetch :log_level, ::Logger::INFO
  @log_progname = options.fetch :log_progname, 'Fancybox2::Module::Base'
  @logger = options.fetch :logger, create_default_logger
  @status = :stopped
  @alive_task = nil
  @configs = {}
end

Public Instance Methods

alive_message_data(&block) click to toggle source
# File lib/fancybox2/module/base.rb, line 36
def alive_message_data(&block)
  if block_given?
    @alive_message_data = block
    return
  end
  @alive_message_data.call if @alive_message_data
end
alive_message_data=(callback) click to toggle source
# File lib/fancybox2/module/base.rb, line 44
def alive_message_data=(callback)
  @alive_message_data = callback if callback.is_a?(Proc)
end
message_to(dest, action = '', payload = '', retain = false, qos = 2) click to toggle source
# File lib/fancybox2/module/base.rb, line 48
def message_to(dest, action = '', payload = '', retain = false, qos = 2)
  if mqtt_client.connected?
    topic = topic_for dest: dest, action: action
    payload = case payload
              when Hash, Array
                payload.to_json
              else
                payload
              end
    logger.debug "#{self.class}#message_to '#{topic}' payload: #{payload}"
    mqtt_client.publish topic, payload, retain, qos
  else
    logger.error 'MQTT client not connected to broker'
  end
end
name() click to toggle source
# File lib/fancybox2/module/base.rb, line 64
def name
  fbxfile[:name]
end
on_action(action, callback = nil, &block) click to toggle source
# File lib/fancybox2/module/base.rb, line 68
def on_action(action, callback = nil, &block)
  topic = topic_for source: :core, action: action
  mqtt_client.add_topic_callback topic do |packet|
    # :nocov:
    payload = packet.payload
    # Try to parse payload as JSON. Rescue with original payload in case of error
    packet.payload = JSON.parse(payload) rescue payload
    if block_given?
      block.call packet
    elsif callback && callback.is_a?(Proc)
      callback.call packet
    end
    # :nocov:
  end
end
on_client_connack() click to toggle source

MQTT Client callbacks

# File lib/fancybox2/module/base.rb, line 281
def on_client_connack
  logger.debug 'Connected to the broker'
  # Setup default callbacks
  default_actions.each do |action_name, callback|
    action_name = action_name.to_s

    on_action action_name do |packet|
      # :nocov:
      if callback.is_a? Proc
        callback.call packet
      else
        logger.warn "No valid callback defined for '#{action_name}'"
      end
      # :nocov:
    end
  end

  if mqtt_client.subscribed_topics.size.zero?
    # Subscribe to all messages directed to me
    logger.debug 'Making broker subscriptions'
    mqtt_client.subscribe [topic_for(source: '+', action: '+'), 2]
  end
end
on_client_message(message) click to toggle source

@note Call super if you override this method

# File lib/fancybox2/module/base.rb, line 334
def on_client_message(message)
end
on_client_puback(message) click to toggle source

@note Call super if you override this method

# File lib/fancybox2/module/base.rb, line 318
def on_client_puback(message)
end
on_client_pubcomp(message) click to toggle source

@note Call super if you override this method

# File lib/fancybox2/module/base.rb, line 330
def on_client_pubcomp(message)
end
on_client_pubrec(message) click to toggle source

@note Call super if you override this method

# File lib/fancybox2/module/base.rb, line 326
def on_client_pubrec(message)
end
on_client_pubrel(message) click to toggle source

@note Call super if you override this method

# File lib/fancybox2/module/base.rb, line 322
def on_client_pubrel(message)
end
on_client_suback() click to toggle source

@note Call super if you override this method

# File lib/fancybox2/module/base.rb, line 306
def on_client_suback
  # Client subscribed, we're ready to rock -> Tell core
  logger.debug 'Subscriptions done'
  logger.debug "Sending 'ready' to core"
  message_to :core, :ready
end
on_client_unsuback() click to toggle source

@note Call super if you override this method

# File lib/fancybox2/module/base.rb, line 314
def on_client_unsuback
end
on_configs(packet = nil, &block) click to toggle source
# File lib/fancybox2/module/base.rb, line 84
def on_configs(packet = nil, &block)
  logger.debug 'on_configs'
  if block_given?
    @on_configs = block
    return
  end

  cfg = packet.payload
  if cfg && cfg.is_a?(Hash) && cfg['configs']
    self.configs.merge! cfg['configs']
  end

  @on_configs.call(packet) if @on_configs
end
on_configs=(callback) click to toggle source
# File lib/fancybox2/module/base.rb, line 99
def on_configs=(callback)
  @on_configs = callback if callback.is_a?(Proc)
end
on_logger(packet = nil, &block) click to toggle source
# File lib/fancybox2/module/base.rb, line 103
def on_logger(packet = nil, &block)
  if block_given?
    @on_logger = block
    return
  end
  @on_logger.call(packet) if @on_logger
  logger_configs = packet.payload
  logger.level = logger_configs['level'] if logger_configs['level']
end
on_logger=(callback) click to toggle source
# File lib/fancybox2/module/base.rb, line 113
def on_logger=(callback)
  @on_logger = callback if callback.is_a?(Proc)
end
on_restart(packet = nil, &block) click to toggle source
# File lib/fancybox2/module/base.rb, line 117
def on_restart(packet = nil, &block)
  if block_given?
    @on_restart = block
    return
  end
  @on_restart.call(packet) if @on_restart
  # Stop + start
  on_stop
  on_start packet
end
on_restart=(callback) click to toggle source
# File lib/fancybox2/module/base.rb, line 128
def on_restart=(callback)
  @on_restart = callback if callback.is_a?(Proc)
end
on_shutdown(do_exit = true, &block) click to toggle source
# File lib/fancybox2/module/base.rb, line 132
def on_shutdown(do_exit = true, &block)
  if block_given?
    @on_shutdown = block
    return
  end

  @status = :on_shutdown

  shutdown_ok = true
  logger.debug "Received 'shutdown' command"
  # Stop sending alive messages
  @alive_task.shutdown if @alive_task

  begin
    # Call user code if any
    @on_shutdown.call if @on_shutdown
  rescue StandardError => e
    logger.error "Error during shutdown: #{e.message}"
    shutdown_ok = false
  end

  # Signal core that we've executed shutdown operations.
  # This message is not mandatory, so keep it simple
  shutdown_message = shutdown_ok ? 'ok' : 'nok'
  logger.debug "Sending shutdown message to core with status '#{shutdown_message}'"
  message_to :core, :shutdown, { status: shutdown_message }
  sleep 0.05 # Wait some time in order to be sure that the message has been published (message is not mandatory)

  Thread.new do
    if mqtt_client && mqtt_client.connected?
      # Gracefully disconnect from broker and exit
      logger.debug 'Disconnecting from broker, bye'
      mqtt_client.disconnect
      @mqtt_client = nil
    end

    if do_exit
      # Exit from process
      status_code = shutdown_ok ? 0 : 1
      logger.debug "Exiting with status code #{status_code}"
      exit status_code
    end
  end
end
on_shutdown=(callback) click to toggle source
# File lib/fancybox2/module/base.rb, line 177
def on_shutdown=(callback)
  @on_shutdown = callback if callback.is_a?(Proc)
end
on_start(packet = nil, &block) click to toggle source
# File lib/fancybox2/module/base.rb, line 181
def on_start(packet = nil, &block)
  if block_given?
    @on_start = block
    return
  end
  # Call user code
  @on_start.call(packet) if @on_start

  cfg = packet ? packet.payload : {}
  interval = cfg['aliveTimeout'] || 1000
  # Start code execution from scratch
  logger.debug "Received 'start'"
  @status = :running
  start_sending_alive interval: interval
end
on_start=(callback) click to toggle source
# File lib/fancybox2/module/base.rb, line 197
def on_start=(callback)
  @on_start = callback if callback.is_a?(Proc)
end
on_stop(&block) click to toggle source
# File lib/fancybox2/module/base.rb, line 201
def on_stop(&block)
  if block_given?
    @on_stop = block
    return
  end
  @on_stop.call if @on_stop
  @status = :stopped
  # Stop code execution, but keep broker connection and continue to send alive
end
on_stop=(callback) click to toggle source
# File lib/fancybox2/module/base.rb, line 211
def on_stop=(callback)
  @on_stop = callback if callback.is_a?(Proc)
end
remove_action(action) click to toggle source
# File lib/fancybox2/module/base.rb, line 215
def remove_action(action)
  topic = topic_for source: :core, action: action
  mqtt_client.remove_topic_callback topic
end
running?() click to toggle source
# File lib/fancybox2/module/base.rb, line 246
def running?
  @status.eql? :running
end
setup(retry_connection = true) click to toggle source
# File lib/fancybox2/module/base.rb, line 254
def setup(retry_connection = true)
  unless @setted_up
    begin
      logger.debug 'Connecting to the broker...'
      mqtt_client.connect
    rescue PahoMqtt::Exception => e
      # :nocov:
      logger.error "Error while connecting to the broker: #{e.message}"
      retry if retry_connection
      # :nocov:
    end

    @setted_up = true
  end
end
shutdown(do_exit = true) click to toggle source
# File lib/fancybox2/module/base.rb, line 220
def shutdown(do_exit = true)
  on_shutdown do_exit
end
start() click to toggle source
# File lib/fancybox2/module/base.rb, line 224
def start
  on_start
end
start_sending_alive(interval: 5000) click to toggle source
# File lib/fancybox2/module/base.rb, line 228
def start_sending_alive(interval: 5000)
  # TODO: replace the alive interval task with Eventmachine?
  # Interval is expected to be msec, so convert it to secs
  interval /= 1000.0
  @alive_task.shutdown if @alive_task
  @alive_task = Concurrent::TimerTask.new(execution_interval: interval, timeout_interval: 2, run_now: true) do
    packet = { status: @status, lastSeen: Time.now.utc, data: nil }
    begin
      packet[:data] = alive_message_data
      message_to :core, :alive, packet
    rescue StandardError => e
      logger.error "Error in alive_message_data callback:  #{e.message}"
      logger.error e.backtrace.join "\n"
    end
  end
  @alive_task.execute
end
stopped?() click to toggle source
# File lib/fancybox2/module/base.rb, line 250
def stopped?
  @status.eql? :stopped
end
topic_for(source: self.name, dest: self.name, action: nil, packet_type: :msg) click to toggle source
# File lib/fancybox2/module/base.rb, line 270
def topic_for(source: self.name, dest: self.name, action: nil, packet_type: :msg)
  source = source.to_s
  packet_type = packet_type.to_s
  dest = dest.to_s
  action = action.to_s

  Config::DEFAULT_TOPIC_FORMAT % [source, packet_type, dest, action]
end

Private Instance Methods

check_and_return_fbxfile(hash_attributes) click to toggle source
# File lib/fancybox2/module/base.rb, line 352
def check_and_return_fbxfile(hash_attributes)
  raise ArgumentError, 'You must provide an Hash as argument' unless hash_attributes.is_a?(Hash)
  hash_attributes.deep_symbolize_keys
end
check_or_build_mqtt_client(mqtt_client = nil) click to toggle source
# File lib/fancybox2/module/base.rb, line 339
def check_or_build_mqtt_client(mqtt_client = nil)
  if mqtt_client
    unless mqtt_client.is_a? PahoMqtt::Client
      raise Exceptions::NotValidMQTTClient.new
    end
    @internal_mqtt_client = false
    @mqtt_client = mqtt_client
  else
    @internal_mqtt_client = true
    @mqtt_client = PahoMqtt::Client.new mqtt_params
  end
end
create_default_logger() click to toggle source
# File lib/fancybox2/module/base.rb, line 357
def create_default_logger
  stdout_logger = ::Logger.new STDOUT
  # broker_logger = ::Logger.new(Logger::MQTTLogDevice.new(topic_for(dest: :core, action: :logs),
  #                                                        client: mqtt_client),
  #                              formatter: Logger::JSONFormatter.new)
  logger = Logger::Multi.new stdout_logger,# broker_logger,
                             level: @log_level,
                             progname: @log_progname
  logger
end
default_actions() click to toggle source

:nocov:

# File lib/fancybox2/module/base.rb, line 369
def default_actions
  {
      start:    proc { |packet| on_start packet },
      stop:     proc { on_stop },
      restart:  proc { |packet| on_restart packet },
      shutdown: proc { on_shutdown },
      logger:   proc { |packet| on_logger packet },
      configs:  proc { |packet| on_configs packet }
  }
end
load_fbx_file() click to toggle source

:nocov:

# File lib/fancybox2/module/base.rb, line 381
def load_fbx_file
  if File.exists? @fbxfile_path
    @fbxfile = YAML.load(File.read(@fbxfile_path)).deep_symbolize_keys
  else
    raise Exceptions::FbxfileNotFound.new @fbxfile_path
  end
end
mqtt_default_params() click to toggle source

:nocov:

# File lib/fancybox2/module/base.rb, line 390
def mqtt_default_params
  {
      host:             'localhost',
      port:             1883,
      mqtt_version:     '3.1.1',
      clean_session:    true,
      persistent:       true,
      blocking:         false,
      reconnect_limit:  -1,
      reconnect_delay:  1,
      client_id:        nil,
      username:         nil,
      password:         nil,
      ssl:              false,
      will_topic:       nil,
      will_payload:     nil,
      will_qos:         0,
      will_retain:      false,
      keep_alive:       7,
      ack_timeout:      5,
      on_connack:       proc { on_client_connack },
      on_suback:        proc { on_client_suback },
      on_unsuback:      proc { on_client_unsuback },
      on_puback:        proc { |msg| on_client_puback msg },
      on_pubrel:        proc { |msg| on_client_pubrel msg },
      on_pubrec:        proc { |msg| on_client_pubrec msg },
      on_pubcomp:       proc { |msg| on_client_pubcomp msg },
      on_message:       proc { |msg| on_client_message msg }
  }
end
mqtt_params() click to toggle source

:nocov:

# File lib/fancybox2/module/base.rb, line 422
def mqtt_params
  return @mqtt_params if @mqtt_params
  @mqtt_params = mqtt_default_params.merge(@mqtt_client_params) { |key, old_val, new_val| new_val.nil? ? old_val : new_val }
  @mqtt_params
end