Soup to Nuts: Cisco Codec – Part 4

In the final part of this series, we’ll finish by adding some basic functionality to the module we created in Part 3. Then we’ll look at how to drop this module into a real program and debug it when things go wrong. Lastly, we’ll consider how we could extend this module in the future.

Dialing and hanging up

Right now our module doesn’t do anything other than setup a connection to a Cisco codec and wait for further instructions. Let’s add the ability to take in a number to dial. We’ll start out by updating our Codec class:

public class Codec : Device
{
  public event EventHandler<DataEventArgs> OnResponse;

  public string VideoNumber { get; set; }

  public Codec()
  {

VideoNumber can be set to the number we wish to call. Once we’re ready to dial the number, we use the Dial method:

public void Dial()
{
   if (VideoNumber.Length > 0)
   {
      var cmd = String.Format("xcommand Dial Number: {0}",
         VideoNumber);
                
      SendCommand(cmd);
   }
}

There are many options we can pass to the Dial command but for now we’ll just use the number. We also need a way to hang up calls, so lets add a HangUp method:

public void HangUp()
{
   SendCommand("xcommand Call Disconnect");
}

We should also providea a way of tracking call status, i.e.: are we connected to anyone? Let’s define a new event named OnCallStatus:

public class Codec : Device
{
   public event EventHandler<DataEventArgs> OnResponse;
   public event EventHandler<DataEventArgs> OnCallStatus;

   public string VideoNumber { get; set; }
   public string CallStatus { get; private set; }
   public ushort CallConnected { get; private set; }

   public Codec()
   {

We also define a couple properties CallStatus and CallConnected so that we can check the current status at any time.

By default, the codec won’t give us status notifications, we need to request them. Add this line to RegisterEvents:

private void RegisterEvents(object sender, EventArgs args)
{
   SendCommand("");
   SendCommand("echo off");
   SendCommand("xfeedback deregisterall");
   SendCommand("xpreferences outputmode terminal");
   SendCommand("xfeedback register /Status/Call");
}

Now, any time something changes within /Status/Call, we’ll get a notification. Let’s add a check to HandleResponse for those notifications:

private void HandleResponse(object sender, DataEventArgs args)
{
    if (args.Message.StartsWith("*s Call "))
    {
        var status = args.Message.Remove(0, 8);
        HandleCallStatus(status);
    }
}

If our status message starts with "*s Call ", we process it. We ignore everything else for right now. We remove the "*s Call " from our string and pass it to HandleCallStatus:

private void HandleCallStatus(string msg)
{
   if (msg.Contains("Status"))
   {
      CallStatus = msg.Remove(0, msg.LastIndexOf(':') + 1).Trim();

      if (CallStatus == "Connected")
         CallConnected = 1;
      else
         CallConnected = 0;

      if (OnCallStatus != null)
      {
         OnCallStatus(this, new DataEventArgs { Message = CallStatus });
      }
   }
   else if (msg.Contains("(ghost=True)"))
   {
      CallStatus = "";
      CallConnected = 0;

      if (OnCallStatus != null)
      {
         OnCallStatus(this, new DataEventArgs { Message = CallStatus });
      }
   }
}

There are many messages the codec spits out when it’s reporting call status changes. If we find one that contains "Status", we can take the actual string and hold onto it in CallStatus. It might be “Idle”, “Connecting”, “Connected”, “Disconnecting”, etc. If it’s “Connected”, we also set CallConnected to 1 (true in SIMPL+ speak). If we find “(ghost=True)” that means the call ended unexpectedly.

Build the solution, then copy the CiscoRoomKit.clz library to your SIMPL project folder.

Cisco Codec Wrapper

In SIMPL+, let’s add the inputs and outputs we need to work with our library’s new functionality to the wrapper module:

// --- Compiler Directives ---

#DEFAULT_VOLATILE
#ENABLE_STACK_CHECKING
#ENABLE_TRACE

#DEFINE_CONSTANT MAX_NAME_LEN 50
#DEFINE_CONSTANT MAX_DIAL_LEN 100
#DEFINE_CONSTANT MAX_TEXT_LEN 255

#USER_SIMPLSHARP_LIBRARY "CiscoRoomKit"

// --- Inputs ---

DIGITAL_INPUT Connect;
DIGITAL_INPUT Debug;
DIGITAL_INPUT Dial;
DIGITAL_INPUT Hang_Up;
STRING_INPUT  Video_Number[MAX_DIAL_LEN];
STRING_INPUT  To_Device[MAX_TEXT_LEN];

// --- Outputs ---

DIGITAL_OUTPUT Connect_Fb;
DIGITAL_OUTPUT Error_Fb;
STRING_OUTPUT  Error_Message;
STRING_OUTPUT  Call_Status;
STRING_OUTPUT  From_Device;

// --- Parameters ---

STRING_PARAMETER Host[MAX_NAME_LEN];
STRING_PARAMETER User[MAX_NAME_LEN];
STRING_PARAMETER Password[MAX_NAME_LEN];

// --- Global Variables ---

Codec RoomKit;

// --- Events ---

PUSH Connect
{
	Try
	{
		RoomKit.Connect();

		Error_Fb = 0;
		Error_Message = "";
	}
	Catch
	{
		Error_Fb = 1;
		Error_Message = GetExceptionMessage();
	}
}

RELEASE Connect
{
	RoomKit.Disconnect();
}

PUSH Dial
{
	RoomKit.Dial();
}

PUSH Hang_Up
{
	RoomKit.HangUp();
}

CHANGE Video_Number
{
	RoomKit.VideoNumber = Video_Number;
}

CHANGE To_Device
{
	Try
	{
		RoomKit.SendCommand(To_Device);
	}
	Catch
	{
		Error_Fb = 1;
		Error_Message = GetExceptionMessage();
	}
}

EVENTHANDLER Codec_OnConnect (Codec sender, EventArgs args)
{
	Connect_Fb = 1;
}

EVENTHANDLER Codec_OnDisconnect (Codec sender, EventArgs args)
{
	Connect_Fb = 0;
}

EVENTHANDLER Codec_OnResponse (Codec sender, DataEventArgs args)
{
	If (Debug)
	{
		From_Device = args.Message;
	}
}

EVENTHANDLER Codec_OnCallStatus (Codec sender, DataEventArgs args)
{
	Call_Status = args.Message;
}

// --- Main ---

Function Main()
{
	RoomKit.Host = Host;
	RoomKit.User = User;
	RoomKit.Password = Password;

	RegisterEvent(RoomKit, OnConnect, Codec_OnConnect);
	RegisterEvent(RoomKit, OnDisconnect, Codec_OnDisconnect);
	RegisterEvent(RoomKit, OnResponse, Codec_OnResponse);
	RegisterEvent(RoomKit, OnCallStatus, Codec_OnCallStatus);

	WaitForInitializationComplete();
}

Save and compile this module. Not many changes here, SIMPL+ is really just the skin on top of SIMPL# so we can get access in SIMPL Windows.

SIMPL Windows

Now we can update our S2N Cisco Room Kit.umc module. Besides adding new joins for Dial, Hang_Up, and Video_Number, we also add Call_Connected_Fb that goes high when Call_Status equals “Connected”.

Save this then drop it into our Cisco Codec Demo.smw program:

Load it up, and if you have an actual Cisco codec to test with, give it a shot! In SIMPL Debugger, I see:

You can see when Call_Status$ = “Connected”, Call_Connect_Fb goes high.

When Things Go Wrong

If you put in the wrong IP address and hit connect, after a long delay, you’ll see a weird error message:

What’s going on here? We have a suspicion about where to break execution: somewhere in the Connect method in our Device class. How can we peek underneath the SIMPL Windows layer?

We can debug SIMPL# modules just like we do SIMPL# Pro programs! Attach to the process (it will be a splusmanagerapp.exe process) and then you can insert breakpoints:

Stepping through this method, we can see that when we call _ssh.Connect(), execution stops:

There’s an unhandled exception getting thrown back to SIMPL+. We really should have wrapped the connection sequence in a try block and handled exceptions locally if possible. Let’s stop debugging and clean up the Connect method:

public void Connect()
{
   try
   {
      // Handle interactive authentication (typing in password)
      var auth = new KeyboardInteractiveAuthenticationMethod(User);
      auth.AuthenticationPrompt += HandleAuthentication;

      // Create connection info
      var conn = new ConnectionInfo(Host, User, auth);

      // Connect SSH session
      _ssh = new SshClient(conn);
      _ssh.ErrorOccurred += HandleError;
      _ssh.Connect();

      // Create stream
      _stream = _ssh.CreateShellStream("Terminal", 80, 24,
         800, 600, 1024);
      _stream.DataReceived += HandleDataReceived;
      _stream.ErrorOccurred += HandleStreamError;

      if (OnConnect != null)
      {
         OnConnect(this, new EventArgs());
      }
   }
   catch (SshConnectionException e)
   {
      if (OnError != null)
      {
         OnError(this, new DataEventArgs() { Message = e.Message });
      }

      Disconnect();
   }
}

Note that we’ve added an OnError event to our Device class. We need to add this to the class as well:

public event EventHandler OnConnect;
public event EventHandler OnDisconnect;
public event EventHandler<DataEventArgs> OnDataReceived;
public event EventHandler<DataEventArgs> OnError;

Then in the SIMPL+ module, we can register an event handler for it:

EVENTHANDLER Codec_OnError (Codec sender, DataEventArgs args)
{
	Error_Fb = 1;
	Error_Message = args.Message;
}

// --- Main ---

Function Main()
{
	RoomKit.Host = Host;
	RoomKit.User = User;
	RoomKit.Password = Password;

	RegisterEvent(RoomKit, OnConnect, Codec_OnConnect);
	RegisterEvent(RoomKit, OnDisconnect, Codec_OnDisconnect);
	RegisterEvent(RoomKit, OnResponse, Codec_OnResponse);
	RegisterEvent(RoomKit, OnCallStatus, Codec_OnCallStatus);
	RegisterEvent(RoomKit, OnError, Codec_OnError);

	WaitForInitializationComplete();
}

Recompile the demo program and try running it. Now, we should get a better error message when we try connecting:

OK, maybe we could improve the error messages a bit, but at least it’s in the right direction…

The Future

So what does the future hold for our little module? There are many features still missing:

  • Microphone mute
  • Content sharing
  • Touch 10 room controls
  • Bookings / One Touch to Join
  • Address books

I do plan to continue updating this module, but probably won’t dedicate many future posts to it. If you want to grab the files as they are now and continue working on it, they’re available in my GitHub.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s