Home Automation - Chapter 3

Internet Checks

Coming up next in this section is the Internet checks in the ‘modules/internet_check.rb’ module.

#!/usr/bin/env ruby

# Libraries

require 'pry'
require 'resolv'
require 'logger'
require 'net/ping'

# Logger settings

logger = Logger.new(STDOUT)
logger.level = Logger::INFO
logger.datetime_format = '%Y-%m-%d %H:%M:%S'

logger.info("Internet --> Running Internet checks...")

def check_resolv
  begin
    resolved = Resolv.getaddress('google.com')
    resolved
  rescue
    return false
  end
  resolved
end

def up?(host)
    check = Net::Ping::External.new(host)
    check.ping?
end

if resolved_host = check_resolv
  logger.info("Internet --> Resolving OK")
else
  logger.fatal("Internet --> unable to resolve URLs - check DNS!")
end

if resolved_host
  up?(resolved_host) && logger.info("Internet --> Ping OK")
else
  logger.fatal("Internet --> cannot ping!")
  load "modules/internet_restore.rb"

I’ll walk you through the interesting parts of this code. The first thing that we can see is that I have extra libraries.

require 'resolv'
require 'net/ping'

Those are used for DNS resolution and ping. In old Ruby versions there used to be a ‘Ping’ namespace which was part of the standard library but it was deprecated in favor of ‘net/ping’ which is what we’re going to use.

Next we’ll look into the ‘check_resolv’ method. In Ruby most of the things you’ll do will be methods. I break this rule occasionally but sticking to it will make your code cleaner and more easy to test.

def check_resolv
  begin
    resolved = Resolv.getaddress('google.com')
    resolved
  rescue
    return false
  end
  resolved
end

The idea of this section is simple. I want to resolve ‘google.com’ and return its IP address. There are two main purposes of this method. The first one is to return false if it cannot resolve the host. Let’s test it in the Interactive Ruby REPL (irb).

$ irb
2.4.1 :001 > require 'resolv'
 => true 
2.4.1 :002 > def check_resolv
2.4.1 :003?>     begin
2.4.1 :004 >           resolved = Resolv.getaddress('google.com')
2.4.1 :005?>         resolved
2.4.1 :006?>       rescue
2.4.1 :007?>         return false
2.4.1 :008?>       end
2.4.1 :009?>     resolved
2.4.1 :010?> end
 => :check_resolv 
2.4.1 :011 > check_resolv
 => "172.217.17.206" 
2.4.1 :012 >

Notice that before I closed the method I said ‘resolved’. This is the same like ‘return resolved’ and it will return the variable. Now notice what happens if I do the some thing but with bad URL.

$ irb
2.4.1 :001 > require 'resolv'
 => true 
2.4.1 :002 > def check_resolv
2.4.1 :003?>     begin
2.4.1 :004 >           resolved = Resolv.getaddress('googleblabla.com')
2.4.1 :005?>         resolved
2.4.1 :006?>       rescue
2.4.1 :007?>         return false
2.4.1 :008?>       end
2.4.1 :009?>     resolved
2.4.1 :010?>   end
 => :check_resolv 
2.4.1 :011 > check_resolv
 => false 
2.4.1 :012 >

Having this ‘false’ being returned if URL is not resolved is important. That’s how in future calls I’ll know if the DNS is working or not. The second purpose of this method is to return IP. Every method in Ruby returns something. Having this something hold meaning is a good practice.

Now that I took care of the DNS check I’ll have to take care also about ping to the IP I got. That’s what this method is about:

def up?(host)
    check = Net::Ping::External.new(host)
    check.ping?
end

Notice that this time I’m also taking a variable called ‘host’ and passing it to the ‘Net::Ping::External’ method from the ‘Net::Ping’ namespace. Let’s play a bit in the irb so we see what’s happening.

$ irb
2.4.1 :001 > require 'net/ping'
 => true 
2.4.1 :002 > def up?(host)
2.4.1 :003?>       check = Net::Ping::External.new(host)
2.4.1 :004?>       check.ping?
2.4.1 :005?> end
 => :up? 
2.4.1 :006 > up?('google.com')
 => true 
2.4.1 :007 > up?('googleblabla.com')
 => false 
2.4.1 :008 >

OK but what’s this ‘ping?’ and where did it come from? Well… let’s see.

$ irb
2.4.1 :001 > require 'net/ping'
 => true 
2.4.1 :002 > check = Net::Ping::External.new(:host)
 => #<Net::Ping::External:0x007f84a891bbb0 @host=:host, @port=7, @timeout=5, @exception=nil, @warning=nil, @duration=nil> 
2.4.1 :003 > check.class
 => Net::Ping::External 
2.4.1 :004 >

Take a closer look at what I just did. This will help you a lot on the long run. I assigned the method call to a variable called ‘check’ just like in my original code. But then I called the ‘.class’ method on this variable. I did so because I want to understand what kind of object it is. This will help me see what behavior I can expect from it. I’ll go even further and call the ‘.ancestors’ method on the namespace that has the behavior of my new variable.

2.4.1 :005 > Net::Ping::External.ancestors
 => [Net::Ping::External, Net::Ping, Object, Kernel, BasicObject] 
2.4.1 :006 >

So, now I can see a lot. I see that it’s part of (inherits) Object. In other words all methods available to Object will also be available to Net::Ping and therefore to Net::Ping::External. Further down we’ll have also all methods from Kernel and BasicObject which are not that many but they are critical to having a working namespace.

I’m not going to explain it all in details but I’ll give you one last hint. You can call the ‘methods’ method on both namespace and our new variable to see what’s available to you.

2.4.1 :006 > BasicObject.methods
 => [:new, :allocate, :superclass, :<=>, :include, :<=, :>=, :==, :===, :included_modules, :include?, :name, :ancestors, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :public_constant, :private_constant, :deprecate_constant, :singleton_class?, :module_exec, :class_exec, :freeze, :inspect, :const_missing, :class_eval, :method_defined?, :public_method_defined?, :prepend, :<, :>, :private_method_defined?, :protected_method_defined?, :public_class_method, :module_eval, :to_s, :private_class_method, :autoload, :autoload?, :instance_method, :public_instance_method, :instance_of?, :kind_of?, :is_a?, :tap, :public_send, :public_method, :singleton_method, :remove_instance_variable, :define_singleton_method, :method, :instance_variable_set, :extend, :to_enum, :enum_for, :=~, :!~, :eql?, :respond_to?, :object_id, :send, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variable_get, :instance_variables, :instance_variable_defined?, :!, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__] 
2.4.1 :007 >
2.4.1 :007 > check.methods
 => [:ping, :ping6, :ping?, :pingecho, :exception, :host, :host=, :warning, :timeout, :port, :duration, :port=, :timeout=, :instance_of?, :kind_of?, :is_a?, :tap, :public_send, :public_method, :singleton_method, :remove_instance_variable, :define_singleton_method, :method, :instance_variable_set, :extend, :to_enum, :enum_for, :<=>, :===, :=~, :!~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :send, :display, :to_s, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variable_get, :instance_variables, :instance_variable_defined?, :!, :==, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__] 
2.4.1 :008 >

This will answer also where ‘ping?’ comes from. It comes from the inheritance which is core concept of Object Oriented Programming.

And now last but not least we call the Internet restore module if we we cannot ping hosts.

load "modules/internet_restore.rb"

Coming up next is my favorite module so far - the one that will reboot my router in case of connection issues.

The whole code of my Home Automation project is available on Github.

Home Automation - Chapter 1

Home Automation - Chapter 2

Home Automation - Chapter 3

Home Automation - Chapter 4

Categories:

Updated: