NTShell for MBED OS

Summary

Sometimes when you are building an IoT system it is very convenient to have a command line interface.  This enables you to use the keyboard to run functions inside of your program.  I often implement a really simple command processor that looks like this:

while(1)
{
   c=getchar();
   switch(c)
   {
      case 'a':
         doSomeCommand();
      break;

      case 'b':
         doSomeOtherCommand();
      break;
   }
}

I generally do this because it is really simple and doesn’t require much work and is easily typed and doesn’t require a parser.  However, this implementation is not very rich as it doesn’t deal with more interesting commands, like ones with arguments.  This weekend while I was looking around I found the Natural Tiny Shell an open source project by Shinichiro Nakamura.  I thought that it was pretty cool, so I ported it to PSoC and MBED OS.  And, after using it a bit I decided to turn it into an MBED OS library so that it was easy to import into new projects.

This article has the following parts

  1. A Survey of the NT Shell Code
  2. Creating a MBED OS Library
  3. Using the Template Project to make an MBED OS Program
  4. Adding new commands to the shell

A Survey of the NT Shell Code

On the NTShell website there is a picture of the architecture of the code.   This picture is trying to show several things

  1. Your application code talks to:
    1. ntshell = the actual guts of the shell
    2. ntopt = a command line argument parser used by your program to figure out what commands arguments have been given to the shell
    3. ntstdio = a set of wrapper functions around stdio that allows for “multiple” standard I/Os
  2. vtsend and vtrecv a library of functions to control vt100 terminals
  3. ntlibc – a set of ntshell specific implementation of libc functions like strlen, strcmp, etc
  4. ntconf – #defines that setup the parameters for the system

He also provides a call graph of the functions.  You can see from this graph that you are responsible only for providing

  1. func_read – read 1 character from the input device, in my case the uart
  2. func_write – write 1 character to the output device
  3. func_callback – a function that will be called by the shell when a command is matched by the shell and you need to do something with it.

You call

  1. ntshell_init – to setup the shell
  2. ntshell_set_promopt – to setup the prompt (imagine that)
  3. ntshell_execute – to run the shell (it never returns from this function)

 

Creating a MBED OS Library

I wanted to create a github library that I could mbed add.  So, I started by downloading the source code from the CuBeatSystems ntshell download site. I ended up with a tar file which needed to be expanded. Once done you will have 3 basic directories:

  1. sample = two example implementations
  2. lib = the actual c-source code for the shell
  3. util = contains c-source files for managing stdio and parsing argument strings

To make this more usable for MBED OS I decided to do several things

  1. Create a github library that can put into your project with  “mbed add”
  2. Create the porting layer functions func_read and func_write and put them in an “mbed” directory in the library
  3. Create a template for the user command functions (usrcmd.h/c)
  4. Create a template with the ntshell in a thread
  5. Create a template main.cpp

Porting Layer

As I described earlier you need the porting function func_read, func_write and the nutshell callback  to make the shell work.  These function reside in mbed-os-ntshell/mbed/util/mbed_port.h/c  The read function just uses the mbed stdio command “getchar” to read the correct number of characters.  And the ntshell_write uses the “putchar” to go the other way.

#include "ntshell.h"
#include "ntlibc.h"
#include "usrcmd.h"
#include <stdio.h>

int ntshell_read(char *buf, int cnt, void *extobj)
{
  int i;
  (void)extobj;
  for (i = 0; i < cnt; i++) {
    buf[i] = getchar();
  }
  return cnt;
}

int ntshell_write(const char *buf, int cnt, void *extobj)
{
  int i;
  (void)extobj;
  for (i = 0; i < cnt; i++) {
    putchar(buf[i]);
  }
  return cnt;
}

The callback function simply called into the user command module (which I provided a template for) with the command that was run by the user.

int ntshell_callback(const char *text, void *extobj)
{
  ntshell_t *ntshell = (ntshell_t *)extobj;
  (void)ntshell;
  (void)extobj;
  if (ntlibc_strlen(text) > 0) {

      usrcmd_execute(text);
  }
  return 0;
}

After I took these step I created a github site to hold it all.  You can use:

or

Using the NT Shell Library and Template in your Project:

The steps to use the NT Shell Library are:

  1. Create a new MBED OS Project
  2. Add the library to your project
  3. Copy the template files into your project
  4. Modify your main.cpp to start the ntShellThread
  5. Add new commands to the shell

1. Create a new MBED OS project

  • mbed new testNTShell

2. Add the NT Shell Library to your project:

  • mbed add git@github.com:iotexpert/mbed-os-ntshell.git

or

  • mbed add https://github.com/iotexpert/mbed-os-ntshell.git

3. Copy the template files into your project

Inside of the library there is a directory called template.  This directory has the following files:

I want to use all of these files to kickstart my project.  So I run:

  • cp mbed-os-ntshell/template/* .

4. Use main.cpp to start the ntShellThread

One of the files in the template directory was a main.cpp.  This program does two things

  1. Starts up the NT Shell Thread
  2. Starts 1Hz blinking LED in the main thread
#include "mbed.h"
#include "ntShellThread.h"

Thread ntShellThreadHandle;

DigitalOut led1(LED1);

int main()
{
    printf("Started NTShellThread\n");
    ntShellThreadHandle.start(ntShellThread);
    while(1)
    {
        led1 = !led1;
        wait(0.5);
    }

}

Add new commands to the shell

I also provide you template files for the user commands in the directory mbed-os-ntshell/template/usrcmd.h&c.  To add your own commands you need to:

  1. A function prototype for your command
  2. Add your command to the command table
  3. Create the actual function for your command.

The function prototype must match and will look like this (notice there are four commands in my template)

typedef int (*USRCMDFUNC)(int argc, char **argv);

static int usrcmd_help(int argc, char **argv);
static int usrcmd_info(int argc, char **argv);
static int usrcmd_clear(int argc, char **argv);
static int usrcmd_printargs(int argc, char **argv);

And the command table looks like this:

typedef struct {
    char *cmd;
    char *desc;
    USRCMDFUNC func;
} cmd_table_t;

static const cmd_table_t cmdlist[] = {
    { "help", "This is a description text string for help command.", usrcmd_help },
    { "info", "This is a description text string for info command.", usrcmd_info },
    { "clear", "Clear the screen", usrcmd_clear },
    { "printargs","print the list of arguments", usrcmd_printargs},

};

Finally your command function will look like this command which just prints out the commands.

static int usrcmd_printargs(int argc, char **argv)
{
    printf("ARGC = %d\n",argc);

    for(int i =0;i<argc;i++)
    {
        printf("argv[%d] = %s\n",i,argv[i]);
    }
    return 0;

}

Live Like You Are Dying

Sunday is the memorial service for my friend, Jeff Wagner, who died last week from Pancreatic Cancer.  He is an electrical engineer who has a daughter that is the same age as mine.  We got to know each other as our kids “orbits” bounced together over the years.  I remember the first time that we met at the band concert at the high school a number of years ago.  He asked what I did … “engineer” I said… “Oh, so am I,” he said.  We then chatted a bit about our jobs… I said that I worked for chip company.  He told me he worked as a process engineer at a manufacturing company.  A few weeks later when I saw him again he said “Hey, you didn’t tell me that you were EVP of Software at Cypress!”… I then joked that I was embarrassed to spend more time writing powerpoint than doing any real engineering – like him.  Later I got into the habit of bringing him Cypress development kits to try.  He was very interested in embedded development and actively used PSoC Creator, PSoC, CapSense and Bluetooth.  Jeff was interested in machine learning and had bought a number of development kits to make ML systems to do different things both for his work and for his real life.  You see, Jeff was a real engineer.

Over the last several years, as our daughters went off to college, we didn’t see each other very much.  Occasionally we met for a meal to catch up… or I would send him a new development kit… or we would trade email about how things were going.  Earlier this year I sent him a note that I was teaching  a class for Mouser and invited him to join.  He responded that things had changed quite a lot since we had last talked and that he had pancreatic cancer.  What is crazy is that the focus of the email was more about a project that he was working on and hardly at all about the diagnosis.

From the beginning of May – the date of the email – until his death last week, he was a constant source of inspiration for me.  How you can be struck down in the prime of life and still keep an awesome attitude, I just don’t know.  He said “Fuck cancer.”  Over the last months, we saw each other quite a few times … unfortunately, mostly in the hospital.  Although he was weak and in pain I would bring a computer and we would write or talk about software together.  I showed him beta software, he tried MBED OS on PSoC, we talked about machine learning, we talked about bikes, our kids and all manner of other stuff.

Jeff WAS STILL ACTIVELY LEARNING DAYS BEFORE HIS FREAKING DEATH.  He still cared and it was awesome to behold for me.  He was a model for what an engineer should be and I will always be inspired by that.

One of the last times that I saw him, we talked about the choices we make in life.  He still thought that things were going to work out.  He talked about the things he would do after he got clear of the cancer.  I’m sure that he was talking to me as much to himself, because I always seem to make the most self destructive choices.  In fact, I was sleeping in a hotel the night he died.

I have to assume he was a good father and husband.  All the evidence suggests that to be true as he has great kids and a cool wife.  But, I know he was a great engineer; he loved to learn and build.

He was often one of the first people to read this website.  And I am sad for that to no longer be the case.

I am sad for Sandy, Anna and the rest of his family.  I can’t begin to imagine how hard that it will be.

And even though I know it is selfish, I am sad for myself.

Rest in peace, my friend.