API Ruby class

From MikroTik Wiki
Jump to: navigation, search

Ruby GEM

The API Ruby class(es) are now packaged together as a Ruby GEM. The latest GEM is available for download from the author's web site. The current version is 4.0.0 available here:

mtik-4.0.0.gem

Or you can simply do:

gem install mtik

RDoc Documentation

The author's site also hosts Ruby RDoc documents for the classes implementing this API. The link is:

http://www.aarongifford.com/computers/mtik/latest/doc/

Examples

Several example scripts are included with the GEM or available for direct download from the author.

  • tikcli - A command-line-like interactive ruby script. You can type MikroTik API commands and arguments directly and have them executed.
  • tikcommand - A non-interactive command-line script to execute a single MikroTik API command and return the results to STDOUT.
  • tikfetch - This non-interactive command-line script lets one instruct a MikroTik device to download one or more files from the provided URL(s).
  • tikjson.rb - Another non-interactive command-line script that executes a single MikroTik API command and returns the results to STDOUT, however the results are encoded in JSON format. One could easily add CGI handling to this script, install it on a web server, and use it via a web browser. (The author in fact has done something like this for a JavaScript-based management web application that interacts with MikroTik devices via the API.)

Interactive examples using gem-supplied utility scripts

Here is are several example runs of the tikcli utility script included in the gem:

user@bsdhost:~$ tikcli 10.20.30.1 admin wrongpassword
<<< '/login' (6)
<<< END-OF-SENTENCE

>>> '!done' (5)
>>> 'ret=bf41fd4286417870c5eb86674a3b8fe4' (36)
>>> '.tag=0' (6)
>>> END-OF SENTENCE

<<< '/login' (6)
<<< '=name=admin' (11)
<<< '=response=0003a042937d84ca4bc4cf7da50aadd507' (44)
<<< END-OF-SENTENCE

>>> '!trap' (5)
>>> 'message=cannot log in' (21)
>>> '.tag=1' (6)
>>> END-OF SENTENCE

>>> '!done' (5)
>>> '.tag=1' (6)
>>> END-OF SENTENCE

=== LOGIN ERROR: Login failed: cannot log in
user@bsdhost:~$ 

That run was deliberately with the wrong password. Here's the login with the correct password:

user@bsdhost:~$ tikcli 10.20.30.1 admin correctpassword
<<< '/login' (6)
<<< END-OF-SENTENCE

>>> '!done' (5)
>>> 'ret=857e91c460620a02c3ca72ea7cf6c696' (36)
>>> '.tag=0' (6)
>>> END-OF SENTENCE

<<< '/login' (6)
<<< '=name=admin' (11)
<<< '=response=001a77aec14077ec267c5297969ba1fa24' (44)
<<< END-OF-SENTENCE

>>> '!done' (5)
>>> '.tag=1' (6)
>>> END-OF SENTENCE


Command (/quit to end):

At this point, the interactive client will accept MikroTik API commands in the format /command/name arg1 arg2 arg3 or also 12:/command/name arg1 arg2 arg3 where the 12: is a custom numeric prefix that tells the Ruby interactive client to auto-cancel the command in question after exactly 12 reply sentences are received, since otherwise a command with continuous output would hang the single-threaded interactive client in an endless reply-handling loop (until someone aborted it).

Arguments to API commands in this interactive client must ALREADY be in the API argument form. For example:

Command (/quit to end): /interface/getall ?name=ether1
=== COMMAND: /interface/getall ?name=ether1
<<< '/interface/getall' (17)
<<< '?name=ether1' (12)
<<< END-OF-SENTENCE

>>> '!re' (3)
>>> '.id=*5' (6)
>>> 'name=ether1' (11)
>>> 'type=ether' (10)
>>> 'mtu=1500' (8)
>>> 'l2mtu=1500' (10)
>>> 'bytes=26908361008/15001379552' (29)
>>> 'packets=34880279/26382227' (25)
>>> 'drops=0/0' (9)
>>> 'errors=5/0' (10)
>>> 'dynamic=false' (13)
>>> 'running=true' (12)
>>> 'disabled=false' (14)
>>> 'comment=' (8)
>>> '.tag=4' (6)
>>> END-OF SENTENCE

>>> '!done' (5)
>>> '.tag=4' (6)
>>> END-OF SENTENCE


Command (/quit to end):

Did you see how the user properly prefixed the query parameter name with the query character (question mark ?) and also paired it via = with the query value? With this example CLI, you must manually format all arguments as specified by the MikroTik API.

You may have noticed that this Ruby API implementation automatically adds a unique .tag to every command. That means if you specify a tag value, the Ruby code will ignore it and use its own. It adds a tag so that replies can be correctly matched to the appropriate request.

Now here's the same query again, only add another parameter, =interval=1 so that the command will repeatedly send output each second. To avoid the command continuing forever, it will be prefixed with 6: (this CLI script strips the digit(s) and colon before sending the command to the device) to limit the number of response sentences to exactly six before the interactive client will automagically issue an appropriate /cancel =tag=XYZ command to cancel it.

Command (/quit to end): 6:/interface/getall ?name=ether1 =interval=1
=== COMMAND: /interface/getall ?name=ether1 =interval=1
<<< '/interface/getall' (17)
<<< '?name=ether1' (12)
<<< '=interval=1' (11)
<<< END-OF-SENTENCE

>>> '!re' (3)
>>> '.id=*5' (6)
>>> 'name=ether1' (11)
>>> 'type=ether' (10)
>>> 'mtu=1524' (8)
>>> 'l2mtu=1524' (10)
>>> 'bytes=26909135851/15002882324' (29)
>>> 'packets=34886461/26387909' (25)
>>> 'drops=0/0' (9)
>>> 'errors=5/0' (10)
>>> 'dynamic=false' (13)
>>> 'running=true' (12)
>>> 'disabled=false' (14)
>>> 'comment=' (8)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> '.id=*5' (6)
>>> 'name=ether1' (11)
>>> 'type=ether' (10)
>>> 'mtu=1524' (8)
>>> 'l2mtu=1524' (10)
>>> 'bytes=26909140098/15002892177' (29)
>>> 'packets=34886498/26387943' (25)
>>> 'drops=0/0' (9)
>>> 'errors=5/0' (10)
>>> 'dynamic=false' (13)
>>> 'running=true' (12)
>>> 'disabled=false' (14)
>>> 'comment=' (8)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> '.id=*5' (6)
>>> 'name=ether1' (11)
>>> 'type=ether' (10)
>>> 'mtu=1524' (8)
>>> 'l2mtu=1524' (10)
>>> 'bytes=26909141508/15002893670' (29)
>>> 'packets=34886508/26387951' (25)
>>> 'drops=0/0' (9)
>>> 'errors=5/0' (10)
>>> 'dynamic=false' (13)
>>> 'running=true' (12)
>>> 'disabled=false' (14)
>>> 'comment=' (8)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> '.id=*5' (6)
>>> 'name=ether1' (11)
>>> 'type=ether' (10)
>>> 'mtu=1524' (8)
>>> 'l2mtu=1524' (10)
>>> 'bytes=26909143624/15002895110' (29)
>>> 'packets=34886524/26387963' (25)
>>> 'drops=0/0' (9)
>>> 'errors=5/0' (10)
>>> 'dynamic=false' (13)
>>> 'running=true' (12)
>>> 'disabled=false' (14)
>>> 'comment=' (8)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> '.id=*5' (6)
>>> 'name=ether1' (11)
>>> 'type=ether' (10)
>>> 'mtu=1524' (8)
>>> 'l2mtu=1524' (10)
>>> 'bytes=26909144116/15002895406' (29)
>>> 'packets=34886530/26387967' (25)
>>> 'drops=0/0' (9)
>>> 'errors=5/0' (10)
>>> 'dynamic=false' (13)
>>> 'running=true' (12)
>>> 'disabled=false' (14)
>>> 'comment=' (8)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> '.id=*5' (6)
>>> 'name=ether1' (11)
>>> 'type=ether' (10)
>>> 'mtu=1524' (8)
>>> 'l2mtu=1524' (10)
>>> 'bytes=26909144824/15002896659' (29)
>>> 'packets=34886535/26387973' (25)
>>> 'drops=0/0' (9)
>>> 'errors=5/0' (10)
>>> 'dynamic=false' (13)
>>> 'running=true' (12)
>>> 'disabled=false' (14)
>>> 'comment=' (8)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

<<< '/cancel' (7)
<<< '=tag=2' (6)
<<< END-OF-SENTENCE

>>> '!trap' (5)
>>> 'category=2' (10)
>>> 'message=interrupted' (19)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

=== TRAP: 'interrupted'

>>> '!done' (5)
>>> '.tag=3' (6)
>>> END-OF SENTENCE

>>> '!done' (5)
>>> '.tag=2' (6)
>>> END-OF SENTENCE


Command (/quit to end):

Using the Gem in Code

Execute an API command that returns endless replies until canceled

Suppose I wish to monitor traffic on an interface using the /interface/monitor-traffic command for 10 seconds--or in other words, because devices usually send responses to this command once every second, I want to listen for and receive 10 replies, then cancel the command:

<nowiki>
#!/usr/bin/env ruby 

require 'rubygems'
require 'mtik'

# Be verbose in output
MTik::verbose = true

# Connect to the device:
connection = MTik::Connection.new :host => '10.0.0.1', :user => 'admin', :pass => 'password'

# We are going to send a "monitor-traffic" command that will keep sending
# output until we "cancel" the command.  We only want to receive 10 responses:
$reply_limit = 10

# Execute the command:
$reply_count = 0
connection.get_reply_each(
  "/interface/monitor-traffic",
  "=interface=ether1",
  "=.proplist=rx-bits-per-second,tx-bits-per-second"
) do |request_object, reply_sentence|
  if reply_sentence.key?('!re')  # We only pay attention to reply sentences
    # Print the reply sentence:
    p reply_sentence

    # Increment the reply counter:
    $reply_count += 1

    # If we've reached our reply goal, cancel:
    if $reply_count >= $reply_limit
      # Cancel this command request:
      request_object.cancel
    end
  end
end

connection.close
</noqiki>

Here's an example of output:

<<< '/login' (6)
<<< '.tag=0' (6)
<<< END-OF-SENTENCE

>>> '!done' (5)
>>> 'ret=cb21408d7123ebfc96bec24effe3409f' (36)
>>> '.tag=0' (6)
>>> END-OF SENTENCE

<<< '/login' (6)
<<< '=name=admin' (11)
<<< '=response=0ce20d1ed4bd3ef821dc203a1ff2698461' (44)
<<< '.tag=1' (6)
<<< END-OF-SENTENCE

>>> '!done' (5)
>>> '.tag=1' (6)
>>> END-OF SENTENCE

<<< '/interface/monitor-traffic' (26)
<<< '=interface=ether1' (17)
<<< '=.proplist=rx-bits-per-second,tx-bits-per-second' (48)
<<< '.tag=2' (6)
<<< END-OF-SENTENCE

>>> '!re' (3)
>>> 'rx-bits-per-second=5744844' (26)
>>> 'tx-bits-per-second=61787' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

{"!re"=>nil, "rx-bits-per-second"=>"5744844", "tx-bits-per-second"=>"61787", ".tag"=>"2"}
>>> '!re' (3)
>>> 'rx-bits-per-second=4310298' (26)
>>> 'tx-bits-per-second=44242' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

{"!re"=>nil, "rx-bits-per-second"=>"4310298", "tx-bits-per-second"=>"44242", ".tag"=>"2"}
>>> '!re' (3)
>>> 'rx-bits-per-second=5442059' (26)
>>> 'tx-bits-per-second=63398' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

{"!re"=>nil, "rx-bits-per-second"=>"5442059", "tx-bits-per-second"=>"63398", ".tag"=>"2"}
>>> '!re' (3)
>>> 'rx-bits-per-second=5711572' (26)
>>> 'tx-bits-per-second=63509' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

{"!re"=>nil, "rx-bits-per-second"=>"5711572", "tx-bits-per-second"=>"63509", ".tag"=>"2"}
>>> '!re' (3)
>>> 'rx-bits-per-second=5711572' (26)
>>> 'tx-bits-per-second=63509' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

{"!re"=>nil, "rx-bits-per-second"=>"5711572", "tx-bits-per-second"=>"63509", ".tag"=>"2"}
>>> '!re' (3)
>>> 'rx-bits-per-second=3712135' (26)
>>> 'tx-bits-per-second=28404' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

{"!re"=>nil, "rx-bits-per-second"=>"3712135", "tx-bits-per-second"=>"28404", ".tag"=>"2"}
>>> '!re' (3)
>>> 'rx-bits-per-second=4822099' (26)
>>> 'tx-bits-per-second=39160' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

{"!re"=>nil, "rx-bits-per-second"=>"4822099", "tx-bits-per-second"=>"39160", ".tag"=>"2"}
>>> '!re' (3)
>>> 'rx-bits-per-second=4536317' (26)
>>> 'tx-bits-per-second=52562' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

{"!re"=>nil, "rx-bits-per-second"=>"4536317", "tx-bits-per-second"=>"52562", ".tag"=>"2"}
>>> '!re' (3)
>>> 'rx-bits-per-second=3976832' (26)
>>> 'tx-bits-per-second=45331' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

{"!re"=>nil, "rx-bits-per-second"=>"3976832", "tx-bits-per-second"=>"45331", ".tag"=>"2"}
>>> '!re' (3)
>>> 'rx-bits-per-second=4124776' (26)
>>> 'tx-bits-per-second=80547' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

{"!re"=>nil, "rx-bits-per-second"=>"4124776", "tx-bits-per-second"=>"80547", ".tag"=>"2"}
<<< '/cancel' (7)
<<< '=tag=2' (6)
<<< '.tag=3' (6)
<<< END-OF-SENTENCE

>>> '!trap' (5)
>>> 'category=2' (10)
>>> 'message=interrupted' (19)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!done' (5)
>>> '.tag=3' (6)
>>> END-OF SENTENCE

>>> '!done' (5)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

Updating DNS settings on multiple devices

Imagine I have a list of RouterOS devices that all need primary and secondary DNS settings updated. Here's an example Ruby script to do this:

#!/usr/bin/env ruby

require 'rubygems'
require 'mtik'

## List of devices (hostnames/IPs) to contact:
devlist = [
  '10.0.0.4',
  '10.0.0.5',
  '10.0.0.22',
  '10.1.44.22',
  '10.1.44.79'
]

## Example assumes all devices use the same API user/pass:
USERNAME = 'admin'
PASSWORD = 'password'

## Set DNS to these IPs (if these were the name servers in question):
PRIMARYDNS   = '10.20.30.2'
SECONDARYDNS = '192.168.44.2'

## Set to 1 to do each device serially, or greater to fork parallel processes
MAXFORK = 1

children = 0
devlist.each do |host|
  Kernel.fork do
    puts "#{host}: Connecting..."
    mt = nil
    begin
      mt = MTik::Connection.new(
        :host=>host,
        :user=>USERNAME,
        :pass=>PASSWORD
      )
    rescue Errno::ETIMEDOUT, Errno::ENETUNREACH, Errno::EHOSTUNREACH => e
      puts "#{host}: Error connecting: #{e}"
      exit
    end

    ## The MTik::Connection#get_reply() method executes a command, then waits
    ## for it to complete (either with a '!done' or '!trap' response) before
    ## executing the callback code block.  The call will block (execution of
    ## this script halts) and wait for the command to finish.  Don't use this
    ## method if you need to handle simultaneous commands to a single device
    ## over a single API connection.  Use an asynchronous calls send_request()
    ## and wait_for_reply().
    mt.get_reply(
      '/ip/dns/set',
      "=primary-dns=#{PRIMARYDNS}",
      "=secondary-dns=#{SECONDARYDNS}"
    ) do |request, sentence|
      trap = request.reply.find_sentence('!trap')
      if trap.nil?
        puts "#{host}: Update command was sent."
      else
        puts "#{host}: An error occurred while setting DNS servers: #{trap['message']}"
      end
    end

    ## Now let's double-check the settings:
    mt.get_reply('/ip/dns/getall') do |request, sentence|
      trap = request.reply.find_sentence('!trap')
      if trap.nil?
        re = request.reply.find_sentence('!re')
        unless re.nil?
          ## Check DNS settings:
          if re['primary-dns'] == PRIMARYDNS && re['secondary-dns'] == SECONDARYDNS
            puts "#{host}: Successfully updated DNS servers."
          else
            puts "#{host}: WARNING: DNS servers DO NOT MATCH: primary-dns=" +
                 "'#{re['primary-dns']}', secondary-dns='#{re['secondary-dns']}'"
          end
        else
          puts "#{host}: WARNING: '/ip/dns/getall' command did work to retrieve DNS settings!"
        end
      else
        puts "#{host}: An error occurred while setting DNS servers: #{trap['message']}"
      end
    end
    mt.close
  end
  children += 1
  while children >= MAXFORK
    Process.wait
    children -= 1
  end
end

while children > 1
  Process.wait
  children -= 1
end

Output might look a bit like:

user@host:~/$ ./dnsupdate.rb 
10.0.0.4: Connecting...
10.0.0.4: Update command was sent.
10.0.0.4: Successfully updated DNS servers.
10.0.0.5: Connecting...
10.0.0.5: Update command was sent.
10.0.0.5: Successfully updated DNS servers.

... MORE OUTPUT ...

10.1.44.79: Successfully updated DNS servers.
user@host:~/$

The benefit of using Mikrotik's API is you can whip up a script to do something, then feed it a bunch of device IPs, login user IDs and passwords from a database, then execute desired commands on ALL of the devices. Check settings, change settings, monitor stats, etc.

Changing user group or deleting a user from a device

This shows how one can query a device for a list of configured users, then subsequently use API commands to remove or alter settings for users (if they exist) referencing them by API .id:

## Retrieve a list of all users on a RouterOS device (with associated IDs):
users = {}
mt.get_reply_each('/user/getall') do |r, s|
 if s.key?('!re') && s.key?('name')
   users[s['name']] = {
     :name     => s['name'],
     :group    => s['group'],
     :address  => s['address'],
     :comment  => s['comment'],
     :disabled => s['disabled'] == true,
     :id       => s['.id']
   }
 end
end

## Remove a specific named user (if found on the device):
mt.get_reply('/user/remove', "=.id=#{users['foo'][:id]}") if users.key?('foo')

## Make a named user (if found) an read-only user:
mt.get_reply('/user/set', "=.id=#{users['foo'][:id]}", "=group=read")

One may wonder why not use ?name=foo instead of using the ID parameter. The gem author has discovered that API commands that make changes often do not work even though the API does not respond with an error, unless the object to be changed is directly referenced by .id. In updating or removing users, this appears to be the case.

Execute a multiple-response command and automatically cancel it to limit the number of replies

require 'rubygems'
require 'mtik'

# Be verbose in output
MTik::verbose = true

# Connect to the device:
p connection = MTik::command(
  :host    => '10.0.0.1',
  :user    => 'username',
  :pass    => 'password',
  :command => [
    "/interface/monitor-traffic",
    "=interface=ether0",
    "=.proplist=rx-bits-per-second,tx-bits-per-second"
  ],
  :limit => 10  ## Auto-cancel after 10 replies
)

In the above code, the /interface/monitor-traffic command is executed using the blocking (non-event-style) MTik::command() library method. But because the monitor-traffic command normally will keep sending replies (one per second) forever, the :limit => 10 parameter was passed. That makes the library count replies and automatically issue a /cancel API command after the specified number of replies have been received. That way the blocking-style MTik::command() method can be safely used without the program hanging forever.

Below is what this example might output. REMEMBER that MTik::verbose = true so most of the output is due to that, and only the final line is the actual final data returned by the MTik::command() call:

<<< '/login' (6)
<<< '.tag=0' (6)
<<< END-OF-SENTENCE

>>> '!done' (5)
>>> 'ret=2830ce30c78f9123d31544654d28a6e0' (36)
>>> '.tag=0' (6)
>>> END-OF SENTENCE

<<< '/login' (6)
<<< '=name=username' (9)
<<< '=response=008a9eba141f9e0f8d2337ab84366298cb' (44)
<<< '.tag=1' (6)
<<< END-OF-SENTENCE

>>> '!done' (5)
>>> '.tag=1' (6)
>>> END-OF SENTENCE

<<< '/interface/monitor-traffic' (26)
<<< '=interface=ether0' (21)
<<< '=.proplist=rx-bits-per-second,tx-bits-per-second' (48)
<<< '.tag=2' (6)
<<< END-OF-SENTENCE

>>> '!re' (3)
>>> 'rx-bits-per-second=326944' (25)
>>> 'tx-bits-per-second=1175812' (26)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> 'rx-bits-per-second=84251' (24)
>>> 'tx-bits-per-second=444610' (25)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> 'rx-bits-per-second=111604' (25)
>>> 'tx-bits-per-second=21782' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> 'rx-bits-per-second=116277' (25)
>>> 'tx-bits-per-second=681' (22)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> 'rx-bits-per-second=116277' (25)
>>> 'tx-bits-per-second=681' (22)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> 'rx-bits-per-second=339747' (25)
>>> 'tx-bits-per-second=14495' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> 'rx-bits-per-second=106012' (25)
>>> 'tx-bits-per-second=3952' (23)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> 'rx-bits-per-second=118867' (25)
>>> 'tx-bits-per-second=17370' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> 'rx-bits-per-second=130582' (25)
>>> 'tx-bits-per-second=29941' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!re' (3)
>>> 'rx-bits-per-second=130582' (25)
>>> 'tx-bits-per-second=29941' (24)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

<<< '/cancel' (7)
<<< '=tag=2' (6)
<<< '.tag=3' (6)
<<< END-OF-SENTENCE

>>> '!trap' (5)
>>> 'category=2' (10)
>>> 'message=interrupted' (19)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

>>> '!done' (5)
>>> '.tag=3' (6)
>>> END-OF SENTENCE

>>> '!done' (5)
>>> '.tag=2' (6)
>>> END-OF SENTENCE

<<< '/quit' (5)
<<< '.tag=4' (6)
<<< END-OF-SENTENCE

>>> '!fatal' (6)
>>> 'session terminated on request' (29)
>>> END-OF SENTENCE

[[{"!re"=>nil, "rx-bits-per-second"=>"326944", "tx-bits-per-second"=>"1175812", ".tag"=>"2"},
 {"!re"=>nil, "rx-bits-per-second"=>"84251", "tx-bits-per-second"=>"444610", ".tag"=>"2"}, 
 {"!re"=>nil, "rx-bits-per-second"=>"111604", "tx-bits-per-second"=>"21782", ".tag"=>"2"}, 
 {"!re"=>nil, "rx-bits-per-second"=>"116277", "tx-bits-per-second"=>"681", ".tag"=>"2"}, 
 {"!re"=>nil, "rx-bits-per-second"=>"116277", "tx-bits-per-second"=>"681", ".tag"=>"2"}, 
 {"!re"=>nil, "rx-bits-per-second"=>"339747", "tx-bits-per-second"=>"14495", ".tag"=>"2"}, 
 {"!re"=>nil, "rx-bits-per-second"=>"106012", "tx-bits-per-second"=>"3952", ".tag"=>"2"}, 
 {"!re"=>nil, "rx-bits-per-second"=>"118867", "tx-bits-per-second"=>"17370", ".tag"=>"2"}, 
 {"!re"=>nil, "rx-bits-per-second"=>"130582", "tx-bits-per-second"=>"29941", ".tag"=>"2"}, 
 {"!re"=>nil, "rx-bits-per-second"=>"130582", "tx-bits-per-second"=>"29941", ".tag"=>"2"}, 
 {"!trap"=>nil, "category"=>"2", "message"=>"interrupted", ".tag"=>"2"}, 
 {"!done"=>nil, ".tag"=>"2"}]]

Notes

  • This has only been testing using Ruby 1.9.2 and Ruby 1.8.7 on several FreeBSD hosts, though it should work identically on other Ruby installations.
  • Encoding/decoding longer words has NOT be thoroughly tested.
  • Connection timeouts and auto-reconnections are NOT implemented.
  • The above examples are single-threaded, but it is probably be safe to use within a multi-threaded Ruby application (untested).
  • The gem uses an event driven callback style to send commands and receive responses. Multiple simultaneous commands may be executing over a single TCP API connection. If one fully utilizes the event-loop style of programming, one could have a single-threaded single process simultaneously executing many commands over many separate TCP API connections to different devices. To do so, one has to be careful to implement a main event loop to avoid blocking.

See the author's web site in the external links below for a link to the CHANGELOG.

See also

External links