Telephony H/W firmare with Elixir Nerves

Picked my my first BeagleBone Black on Friday. By Sunday evening, I was running 1000 line state machines running on the BBB, each registered with our Asterisk based PBX switch. Between Friday night I had to learn the BBB and the Elixir Nerves project as well as programming / porting a couple KLOC of elixir code to the BBB.

This is only the first stage of the project since I still have the drive the FPGA on our hardware card though an SPI interface.

What I have running is a:

  • supervisor capable of dynamically starting a LineSm GenFSM server
  • LineSm state machine
    • Opens a Reliable UDP socket with the PBX
    • Identifies itself (id, device type, device capabilities)
    • Updates a cache on each event with its current state
    • On a crash, recovers its last state from the cache
  • Remote console
  • Firmware update through http

I was pretty impressed with how easy it was to get up and running with Nerves. With the nerves_firmware_http package the workflow is pretty slick. I created a simple script that runs the mix firmware and updates the firmware with a curl command.

#!/bin/bash
mix firmware && curl -T _images/bbb/dms.fw "http://10.10.1.40:8988/firmware" -H "Content-Type: application/x-firmware" -H "X-Reboot: true"

Setting up remove access took a little research, but was pretty simple after getting the syntax right. Just add the following two line to rel/vm.args

-setcookie bbb
-name bbb@10.10.1.40

With that in place, connecting a shell running on the BBB is as simple as:

iex --name steve@10.10.1.100 --cookie bbb --remsh bbb@10.10.1.40

As I was developing the state machine code, I wrote tests which I ran on my mac host. After developing and testing the software on the host, I built and pushed the firmware to the BBB and it just ran!

By using Elixir for the firmware on this new card we are developing, I was able to use code that we had previously written for a server based product with minimal effort.

Next step is to get the SPI interface up and running once we have the BBB hooked up to our FPGA on our eval board.

Well done Nerves Team!

Working with JQuery Portlets

I had a difficult time finding good examples working with the JQuery Portlets, so I created by own. Based on the Portlet Example, here is source code that demonstrates the following features:

  • Draggable & Sortable
  • Different Sized Columns
  • Close Button
  • Settings Menu
  • Change Visibility Of Each Portlet
  • Change Column Widths
  • Scrollable Portlet Contents

Visit the Live Demo. Download the source code at GitHub.

dashboard.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">

<html>
  <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title>Sample Dashboard</title>
    <link rel='stylesheet' 
        href='http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/cupertino/jquery-ui.css'/>
    <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js'></script>
    <script src='http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.js'></script>
    <link rel='stylesheet' href='dashboard.css'/>
    <script src="dashboard.js" type="text/javascript" charset="utf-8"></script>
  </head>
  <body id="dashboard">
    <div id='header' class="ui-widget-header ui-corner-all ui-widget">
      <div>Dashboard test</div>
      <div id="menu2" class="ui-icon ui-icon-wrench"></div>
      <br class='clear'/>
    </div>
    <div class="demo">
      <div id="window_dialog" class="hidden">
        <fieldset>
          <legend>Column Widths</legend>
          <label>Left:</label><input type='text'  id='c1-width'>
          <label>Middle:</label><input type='text'  id='c2-width'>
          <label>Right:</label><input type='text'  id='c3-width'>
        </fieldset>
        <fieldset>
            <legend>Window Visibility</legend>
        <input type="checkbox"  id="feeds-visible"  /><label>Feeds</label><br/>
        <input type="checkbox" id="shopping-visible"  /><label>Shopping</label><br/>
        <input type="checkbox" id="news-visible"  /><label>News</label><br/>
        <input type="checkbox" id="links-visible"  /><label>Links</label><br/>
        <input type="checkbox"  id="images-visible"  /><label>Images</label><br/>
        </fieldset>
      </div>
    <div class="column1">
      <div id='feeds-portlet' class="portlet">
        <div class="portlet-header">Feeds</div>
        <div class="portlet-content">Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
      </div>

      <div id="news-portlet" class="portlet">
        <div class="portlet-header">News</div>
        <div class="portlet-content">Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
      </div>
    </div>
    <div class="column2">
      <div class="portlet" id='shopping-portlet'>
        <div class="portlet-header">Shopping</div>
        <div class="portlet-content">Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
      </div>
    </div>
    <div class="column3">
      <div id="links-portlet" class="portlet">
        <div class="portlet-header">Links</div>
        <div class="portlet-content">Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
      </div>
      <div id="images-portlet" class="portlet">
        <div class="portlet-header">Images</div>
        <div class="portlet-content">Lorem ipsum dolor sit amet, consectetuer adipiscing elit 
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud 
exercitation ullamco laboris nisi 
        </div>
      </div>
    </div>
    </div><!-- End demo -->
  </body>
</html>


dashboard.css

body{min-width: 900px;}
h1{color: red;}
.button{padding: 5px; border: 1px solid gray;}
.column1 { width: 170px; float: left; padding-bottom: 100px; }
.column2 {width: 500px; float: left; padding-bottom: 100px; }
.column3 { width: 170px; float: left; padding-bottom: 100px; }

.portlet { margin: 0 1em 1em 0; }
.portlet-header { margin: 0.3em; padding-bottom: 4px; padding-left: 0.2em; }
.portlet-header .ui-icon { float: right; }
.portlet-content { padding: 0.4em; }
.ui-sortable-placeholder { border: 1px dotted black; visibility: visible !important; height: 50px !important; }
.ui-sortable-placeholder * { visibility: hidden; }
.portlet-content {
    max-height:200px;
    _height:expression(this.scrollHeight>199?"200px":"auto");
    overflow:auto;
    overflow-x:hidden;
}
.hidden{display: none}
#header{margin-bottom: 5px; padding: 3px}
#header div{float: left;}
#menu2{float:left;}
br.clear{clear:both;}


dashboard.js

var portlets = ["feeds", "shopping", "news", "links", "images"];

$(document).ready(function () {
  $('#menu2').click(function(event) {
    $('#window_dialog').dialog({
        autoOpen: true,
        draggable: false,
        modal: true,
        title: 'Settings',
        buttons: {
            "Save": function () { 
              
              $(portlets).each(function() {
                set_window_visibility(this);
              });
              $(".column1").width(parseInt($('#c1-width').val()));
              $(".column2").width(parseInt($('#c2-width').val()));
              $(".column3").width(parseInt($('#c3-width').val()));
              $(this).dialog('destroy');
            },
            "Cancel": function () {
              $(this).dialog('destroy');
            }
        } 
      });
  });
  function set_window_visibility(name){
    if($('#'+name+'-visible').is(':checked'))
      $('#'+name+'-portlet').show();
    else
      $('#'+name+'-portlet').hide();
  }
  function set_visible_check(name){
    if($('#'+name+'-portlet').is(":visible"))
      $('#'+name+'-visible').each(function(){ this.checked = true; });
    else
      $('#'+name+'-visible').attr("checked", false);
  }
  $( "#settings_dialog" ).bind( "dialogopen", function(event, ui) {

  });
  $( "#window_dialog" ).bind( "dialogopen", function(event, ui) {
    $(portlets).each(function() {
      set_visible_check(this);
    });
    $('#c1-width').val($(".column1").width());
    $('#c2-width').val($(".column2").width());
    $('#c3-width').val($(".column3").width());
  });
});

$(function() {
  $( ".column1" ).sortable({
    connectWith: ".column1, .column2, .column3"
  });
  $( ".column2" ).sortable({
    connectWith: ".column1, .column2, .column3"
  });
  $( ".column3" ).sortable({
    connectWith: ".column1, .column2, .column3"
  });
  $( ".portlet" ).addClass( "ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" )
    .find( ".portlet-header" )
      .addClass( "ui-widget-header ui-corner-all" )
      .prepend( "<span  class='ui-icon ui-icon-closethick icon-close'></span><span class='ui-icon ui-icon-minusthick icon-vis'></span>")
      .end()
    .find( ".portlet-content" );

  $( ".icon-vis" ).click(function() {
    $( this ).toggleClass( "ui-icon-minusthick" ).toggleClass( "ui-icon-plusthick" );
    $( this ).parents( ".portlet:first" ).find( ".portlet-content" ).toggle();
  });
  $( ".icon-close" ).click(function() {
    //$( this ).toggleClass( "ui-icon-minusthick" ).toggleClass( "ui-icon-plusthick" );
    $( this ).parents( ".portlet:first" ).hide();
  });
  $( ".column" ).disableSelection();
});

Using dataforms with xmpp4r pubsub

I could not find an example anywhere for using data forms with xmpp4r pubsub feature. Here’s a working example. Note that I included only the publishing site of it since I’m using javascript for the subscribers.

#!/usr/bin/ruby

require 'rubygems'
require 'xmpp4r'
require "xmpp4r/pubsub"
require "xmpp4r/dataforms"
require "xmpp4r/pubsub/helper/servicehelper.rb"
require "xmpp4r/pubsub/helper/nodebrowser.rb"
require "xmpp4r/pubsub/helper/nodehelper.rb"
include Jabber

JID='smpallen99@xmpp'
PASSWD='test123'
SERVICE='pubsub.xmpp'
NODE='xmpp/ahearsion'

Jabber::debug = true

cl = Jabber::Client.new(Jabber::JID.new(JID))
puts "Connecting"
cl.connect
cl.auth(PASSWD)
cl.send(Jabber::Presence.new.set_priority(-1))

pubsub = PubSub::ServiceHelper.new(cl, SERVICE)

def create_node(pubsub)
  pubsub.create_node(NODE)
end

def publish(action, user, pubsub)
  item = Jabber::PubSub::Item.new
  
  fa = Jabber::Dataforms::XDataField.new("action")
  fa.value = action
  fu = Jabber::Dataforms::XDataField.new("user")
  fu.value = user
  
  df = Jabber::Dataforms::XData.new :result
  df.add(fa)
  df.add(fu)
  
  item.add(df)
  pubsub.publish_item_to(NODE, item)
end

cont = true
while cont
  puts "Enter your command (a |d |c|q) "
  input = gets
  tokens = input.split(" ")
  case tokens.first
  when "a"
    user = tokens[1]
    publish("activate", user, pubsub)
    puts "Activated #{user}\n"
  when "d"
    user = tokens[1]
    publish("deactivate", user, pubsub)
    puts "Deactivated #{user}\n"
  when "q"
    cont = false
    puts "quitting...\n"
  when "c"
    puts "Creating node...\n"
    create_node(pubsub)
  else
    puts "Don't understand command #{tokens.first}\n"
  end
  
end
cl.close

Ducks Quack, Eagles Soar

I received the following by email from a friend today. This is not the first time I’ve come across “Ducks Quack, Eagles Soar”.  This is such a great read, I want to share it.

No one can make you serve customers well….that’s because great service is a choice. Harvey Mackay, tells a wonderful story about a cab driver that proved this point.

He was waiting in line for a ride at the  airport. When a cab pulled up, the first thing Harvey noticed was that the  taxi was polished to a bright shine. Smartly dressed in a white shirt,  black tie, and freshly pressed black slacks, the cab driver jumped out and  rounded the car to open the back passenger door for Harvey .

He  handed my friend a laminated card and said: ‘I’m Wally, your driver. While  I’m loading your bags in the trunk I’d like you to read my mission  statement.’

Taken aback, Harvey read the card.. It said: Wally’s  Mission Statement: To get my customers to their destination in the  quickest, safest and cheapest way possible in a friendly  environment…

This blew Harvey away. Especially when he noticed  that the inside of the cab matched the outside. Spotlessly  clean!

Continue reading