Dual Brushless Motor ESC, 10-62V, up to 50A per motor. Motors ganged or independent, multiple control input methods, cycle-by-cycle current limit, speed mode and torque mode control. Motors tiny to kW. Speed limit and other parameters easily set in firmware. As used in 'The Brushless Brutalist' locomotive - www.jons-workshop.com. See also Model Engineer magazine June-October 2019.

Dependencies:   mbed BufferedSerial Servo PCT2075 FastPWM

Update 17th August 2020 Radio control inputs completed

cli_BLS_nortos.cpp

Committer:
JonFreeman
Date:
2020-06-09
Revision:
16:d1e4b9ad3b8b
Parent:
14:acaa1add097b
Child:
17:cc9b854295d6

File content as of revision 16:d1e4b9ad3b8b:

/**
*   Code in this file : -
*
*   STM3_ESC board uses two serial ports. 
*       One (use pc.printf etc) provides 9600 baud comms to a pc or other terminal. Essential for test and setup, not used in everyday use.
*
*       com2 provides 19200 baud comms via opto-isolators to Touch Screen Controller (see "Brute_TS_Controller_2018_11"). Opto isolation allows
*       for several boards to parallel up on this port, each STM3_ESC board having a unique single byte ID char in range '0' to '9'.
*       This enables Touch Screen controller to address ESC boards individually e.g. for requesting speed, RPM etc
*       while also allowing broadcast of commands not requiring responses
*
*   Code implements a Command Line Interpreter (see class   cli_2019)
*   Two instantiations of class   cli_2019 are used, 'pcc' for pc comms and 'tsc' for touch screen comms
*   These read incoming commands and execute code functions accordingly. These functions are all of type
*       void    func    (struct parameters &)  ;
*   This allows any command line parameters to pass to 'func'
*/
//  Brushless_STM3_Ctrl_2018_11
#include "mbed.h"
#include "BufferedSerial.h"
#include "STM3_ESC.h"
#include "brushless_motor.h"

#include <cctype>
using namespace std;

extern  eeprom_settings     user_settings     ;
extern  error_handling_Jan_2019     ESC_Error    ;         //  Provides array usable to store error codes.
extern  int     WatchDog;           //  from main
extern  bool    WatchDogEnable;     //  from main
extern  bool    read_temperature    (float & t) ;   //  from main March 2020

extern  brushless_motor MotorA, MotorB;     //  Controlling two motors together or individually
extern  const char *    get_version    ()  ;    //  Need this as extern const char can not be made to work. This returns & const_version_string

extern  BufferedSerial com2, pc;            //  The two com ports used. There is also an unused com port, com3 setup @ 1200 baud
extern  void    setVI_both   (double v, double i)  ;     //  Set motor voltage limit and current limit
extern  double  Read_DriverPot  ();
extern  double  Read_BatteryVolts   ();
extern  void    mode_set_motors_both   (int mode)  ;   //  called from cli to set fw, re, rb, hb

//  All void    func    (struct parameters &)  ; functions addressed by command line interpreter are together below here

/**
void    ver_cmd (struct parameters & a)
    Responds YES, causes action NO
    PC or TS able to read software / firmware / hardware version string
*/
void    ver_cmd (struct parameters & a)
{
    if  (a.source == SOURCE_PC) {
        pc.printf   ("Version [%s]\r\n", get_version());
    }
    else    {
        if  (a.source == SOURCE_TS)
            if (a.respond)  {     //  Only respond if this board addressed
                a.com->printf   ("%s\r", get_version());
            }
        else
            pc.printf   ("Crap source %d in ver_cmd\r\n", a.source);
    }
}

/**
void    pot_cmd (struct parameters & a)
    Responds YES, causes action NO
    pc reads DriverPot. No sense in TS reading as STM3_ESC uses either/or TS, DriverPot
*/
void    pot_cmd (struct parameters & a)
{   if  (a.source == SOURCE_PC)
        pc.printf   ("Driver's pot %.3f\r\n", Read_DriverPot  ());
    else
        pc.printf   ("Wrong use of pot_cmd\r\n");
}

/**
*   Do nothing command, but does report board ID code '0' to '9'
*/
void    null_cmd (struct parameters & a)
{
    if  (a.respond) 
        a.com->printf   ("Board ID %c\r\n", user_settings.rd(BOARD_ID));
}


/**
*   void    rdi_cmd (struct parameters & a)  //  read motor currents (uint32_t) and BatteryVolts (double)

void    rdi_cmd (struct parameters & a)  //  read motor currents (uint32_t) and BatteryVolts (double)
{   //  Voltage reading true volts, currents only useful as relative values
    if  (a.respond) 
//        a.com->printf ("rdi%d %d %.1f\r%s", MotorA.I.ave, MotorB.I.ave, Read_BatteryVolts  (), a.source == SOURCE_PC ? "\n" : "");
        a.com->printf ("rdi%.1f %.1f %.1f\r%s", MotorA.Idbl, MotorB.Idbl, Read_BatteryVolts  (), a.source == SOURCE_PC ? "\n" : "");
          //  Format good to be unpicked by cli in touch screen controller
}
*/
/**
*   void    rvi_cmd (struct parameters & a)  //  read last normalised motor voltage and current values sent to pwms
*
*/
//void    rvi_cmd (struct parameters & a)  //  read last normalised values sent to pwms
//{
//    if  (a.respond) 
//        a.com->printf ("rvi%.2f %.2f %.2f %.2f\r%s", MotorA.last_V, MotorA.last_I, MotorB.last_V, MotorB.last_I, a.source == SOURCE_PC ? "\n" : "");
//}

/**
*   void    fw_cmd (struct parameters & a)  //  Forward command
*   Broadcast to all STM3_ESC boards, required to ACT but NOT respond
*/
void    fw_cmd (struct parameters & a)  //  Forward command
{
//    MotorA.set_mode (MOTOR_FORWARD);
//    MotorB.set_mode (MOTOR_FORWARD);
    mode_set_motors_both   (MOTOR_FORWARD);
    if  (a.source == SOURCE_PC)
        pc.printf   ("fw\r\n");     //  Show response to action if command from pc terminal
}

/**
*   void    re_cmd (struct parameters & a)  //  Reverse command
*   Broadcast to all STM3_ESC boards, required to ACT but NOT respond
*/
void    re_cmd (struct parameters & a)  //  Reverse command
{
    mode_set_motors_both   (MOTOR_REVERSE);
    if  (a.source == SOURCE_PC)
        pc.printf   ("re\r\n");
}

/**
*   void    rb_cmd (struct parameters & a)      //  Regen brake command
*   Broadcast to all STM3_ESC boards, required to ACT but NOT respond
*/
void    rb_cmd (struct parameters & a)      //  Regen brake command
{
    double  tmp = a.dbl[0] / 100.0;
    MotorA.brake    (tmp);
    MotorB.brake    (tmp);  //  Corrected May 2020 - previously MotorA twice
    if  (a.source == SOURCE_PC)
        pc.printf   ("rb %.2f\r\n", tmp);
}

/**
*   void    hb_cmd (struct parameters & a)      //  Hand brake command
*   Broadcast to all STM3_ESC boards, required to ACT but NOT respond
*
*   NOTE    Jan 2019 Hand brake not implemented
*   May 2020 - Implemented, but not very good
*/
void    hb_cmd (struct parameters & a)      //  Hand brake command
{
    double  tmp;
    if  (a.numof_dbls != 0)  tmp = a.dbl[0] / 100.0;    //  a.numof_dbls is int32_t
    else                    tmp = 0.33;
    if  (a.respond) {
//        a.com->printf   ("numof params = %d\r\n", a.numof_dbls);
        a.com->printf   ("Hand Brake : Force 0 to 99 %.0f\r\n", tmp * 100.0);
    }
    mode_set_motors_both   (MOTOR_HANDBRAKE);
    if  (tmp < 0.0)    tmp = 0.0;
    if  (tmp > 1.0)    tmp = 1.0;
    setVI_both (tmp / 5.0, 0.99);
}




/**void    user_settings_cmd (struct parameters & a)       //  With no params, reads eeprom contents. With params sets eeprom contents
*   user_settings_cmd called only from pc comms. No sense calling from Touch Screen Controller
*
*   Called without parameters - Lists to pc terminal current settings
*
*/
void    user_settings_cmd (struct parameters & a)       //  With no params, reads eeprom contents. With params sets eeprom contents
{
    user_settings.edit   (a.dbl, a.numof_dbls);
}

extern  struct  optpar option_list[] ;  //= {

void    brake_eff_set_cmd  (struct parameters & a)  {     //  set brake effectiveness from TS May 2020
    char    be = (char) a.dbl[0];
    pc.printf   ("BRAKE_EFFECTIVENESS min = %d, max = %d\r\n", option_list[BRAKE_EFFECTIVENESS].min, option_list[BRAKE_EFFECTIVENESS].max);
    if  (be > option_list[BRAKE_EFFECTIVENESS].max)   be = option_list[BRAKE_EFFECTIVENESS].max;
    if  (be < option_list[BRAKE_EFFECTIVENESS].min)   be = option_list[BRAKE_EFFECTIVENESS].min;
    user_settings.wr(be, BRAKE_EFFECTIVENESS);
    user_settings.save   ();
    pc.printf   ("Set brake effectiveness to %d pct\r\n", be);
}

void    ssl_cmd  (struct parameters & a)  {     //  set speed limit NEW and untested July 2019. Stored as speed * 10 to get 1dec place
    char    sp = (char) (a.dbl[0] * 10.0);
    if  (sp > option_list[TOP_SPEED].max)   sp = option_list[TOP_SPEED].max;
    if  (sp < option_list[TOP_SPEED].min)   sp = option_list[TOP_SPEED].min;
    user_settings.wr(sp, TOP_SPEED);
    user_settings.save   ();
    pc.printf   ("Set speed limit to %.1f mph\r\n", a.dbl[0]);
}

/**
*   void    temperature_cmd  (struct parameters & a)  {
*   Few boards have temperature sensor fitted. Now only supports LM75B i2c sensor
*/
void    read_temperature_cmd  (struct parameters & a)  {
    float   t = -99.25;
    if  (a.respond) {
        a.com->printf ("tem%c ", user_settings.rd(BOARD_ID));
        if  (read_temperature(t))
            a.com->printf   ("Temperature = %7.3f\r\n", t);
        else
            a.com->printf   ("Temp sensor not fitted\r\n");
    }
}

/**
*void    rpm_cmd (struct parameters & a) //  to report e.g. RPM 1000 1000 ; speed for both motors
*/
void    rpm_cmd (struct parameters & a) //  to report e.g. RPM 1000 1000 ; speed for both motors
{
    if  (a.respond) 
//        a.com->printf  ("rpm%c %d %d\r%s", user_settings.rd(BOARD_ID), MotorA.RPM, MotorB.RPM, a.source == SOURCE_PC ? "\n" : "");
        a.com->printf  ("rpm%c %.0f %.0f\r%s", user_settings.rd(BOARD_ID), MotorA.dRPM, MotorB.dRPM, a.source == SOURCE_PC ? "\n" : "");
}

/**
*void    mph_cmd (struct parameters & a) //  to report miles per hour
*/
void    mph_cmd (struct parameters & a) //  to report miles per hour to 1 decimal place - now 2dp May 2020
{
    int j = 0;
    double  speedmph = 0.0;
    if  (MotorA.exists  ()) {
        j |= 1;
        speedmph = MotorA.dMPH;
    }
    if  (MotorB.exists  ()) {
        j |= 2;
        speedmph += MotorB.dMPH;
    }
    if  (j == 3)
        speedmph /= 2.0;
    if  (a.respond) 
//  May 17th 2020 modified line below - removed superfluous cast, upped to 2 decimal places
//        a.com->printf ("mph%c %.2f\r%s", user_settings.rd(BOARD_ID), speedmph, a.source == SOURCE_PC ? "\n" : "");
        a.com->printf ("?s%c %.2f\r%s", user_settings.rd(BOARD_ID), speedmph, a.source == SOURCE_PC ? "\n" : "");
}

/**
*void    sysV_report (struct parameters & a) //  to report system voltage
*   Reports system link voltage to one decimal place
*/
void    sysV_report (struct parameters & a) //  
{
    if  (a.respond) 
        a.com->printf ("?v%c %.1f\r%s", user_settings.rd(BOARD_ID), Read_BatteryVolts(), a.source == SOURCE_PC ? "\n" : "");
}

/**
*void    sysI_report (struct parameters & a) //  to report motor currents
*   Reports doubles for each motor current amps to 2 decimal places
*/
void    sysI_report (struct parameters & a) //  
{
    if  (a.respond)
        a.com->printf ("?i%c %.2f %.2f\r%s", user_settings.rd(BOARD_ID), MotorA.Idbl, MotorB.Idbl, a.source == SOURCE_PC ? "\n" : "");
}

/*

New December 2019 - possible for implementation
    Having implemented radio control inputs and driver pot, there is some sense in moving slider handler from touch screen controller
    into STM3_ESC code.
        New instructions to be accepted from touch screen : -
    {"w", "touch screen new slider touch", slider_touch_cmd},
    {"x", "updated slider RANGE 0 to 99", slider_touch_position_cmd},   //  max two digits
    {"y", "touch_screen slider finger lifted clear", slider_untouch_cmd},
*/
bool    finger_on_slider = false;
/**void    slider_touch_cmd (struct parameters & a)
*
*   Message from touch screen controller, slider touched
*/
void    slider_touch_cmd (struct parameters & a)    {
    finger_on_slider = true;
}

/**void    slider_untouch_cmd (struct parameters & a)
*
*   Message from touch screen controller, finger taken off slider
*/
void    slider_untouch_cmd (struct parameters & a)    {
    finger_on_slider = false;
}

/**void    slider_touch_position_cmd (struct parameters & a)
*
*   Message from touch screen controller, latest slider touch position
*/
void    slider_touch_position_cmd (struct parameters & a)   {
}

//  End of  New December 2019 


/**void    vi_cmd (struct parameters & a)
*
*   For setting motor voltage and current limits from pc terminal for test   
*/
void    vi_cmd (struct parameters & a)
{
    setVI_both   (a.dbl[0] / 100.0, a.dbl[1] / 100.0);
//    pc.printf   ("setVI_both from %s\r\n", a.source == SOURCE_PC ? "PC" : "Touch Screen");
}

/**
*void    v_cmd (struct parameters & a)
*    Set motor voltage limit from either source without checking for addressed board
*/
void    v_cmd (struct parameters & a)
{
    MotorA.set_V_limit  (a.dbl[0] / 100.0);
    MotorB.set_V_limit  (a.dbl[0] / 100.0);
}

/**
*void    i_cmd (struct parameters & a)
*    Set motor current limit from either source without checking for addressed board
*/
void    i_cmd (struct parameters & a)
{
    MotorA.set_I_limit  (a.dbl[0] / 100.0);
    MotorB.set_I_limit  (a.dbl[0] / 100.0);
}

/**
*void    kd_cmd (struct parameters & a)  //  kick and enable the watch dog
*
*   Brute_TS_Controller or other external controller to issue regular 'kd\r' to come here.
*   WatchDog disabled by default, enabled on first call to here
*   This is where WatchDog timer is reset and reloaded.
*   Timeout may be detected and handled in 8Hz loop in main programme loop
*/
void    kd_cmd (struct parameters & a)  //  kick the watchdog. Reached from TS or pc.
{
    WatchDog = WATCHDOG_RELOAD + (user_settings.rd(BOARD_ID) & 0x0f);   //  Reload watchdog timeout. Counted down @ 8Hz
    WatchDogEnable = true;                          //  Receipt of this command sufficient to enable watchdog
}

void    wd_report (struct parameters & a)     //  Reachable always from pc. Only addressed board responds to TS
{
    pc.printf   ("WatchDog %d\r\n", WatchDog);
}
/**
*void    who_cmd (struct parameters & a)     //  Reachable always from pc. Only addressed board responds to TS
*
*   When using STM3_ESC boards together with 'Brute Touch Screen Controller', controller needs to identify number and identity
*   of all connected STM3_ESC boards. To do this the Touch Screen issues '0who', '1who' ... '9who' allowing time between each
*   for an identified STM3_ESC to respond with 'who7' (if it was number 7) without causing contention of paralleled serial opto isolated bus
*/
void    who_cmd (struct parameters & a)     //  Reachable always from pc. Only addressed board responds to TS
{
    if  (a.source == SOURCE_PC || user_settings.rd(BOARD_ID) == a.target_unit)
        a.com->printf ("who%c\r%s", user_settings.rd(BOARD_ID), a.source == SOURCE_PC ? "\n" : "");
}

/**
*void    rcin_pccmd (struct parameters & a)
*
*   For test, reports to pc terminal info about radio control input channels
*/
//void    rcin_pccmd (struct parameters & a)
//{
//    rcin_report ();
//}
/*
void    scmd (struct parameters & a)    //  filter coefficient fiddler
{
    switch  ((int)a.dbl[0]) {
        case    1:
            MotorA.sdbl[1] = MotorB.sdbl[1] = a.dbl[1];
            break;
        case    2:
            MotorA.sdbl[2] = MotorB.sdbl[2] = a.dbl[1];
            break;
        case    3:
            MotorA.sdbl[3] = MotorB.sdbl[3] = a.dbl[1];
            break;
        case    4:
            MotorA.sdbl[4] = MotorB.sdbl[4] = a.dbl[1];
            break;
        default:
            pc.printf   ("Wrong use of scmd %f\r\n", a.dbl[0]);
    }
    pc.printf   ("Filter Coefficient Fiddler - used in brushless_motor::speed_monitor_and_control   ()");
    pc.printf   ("Filter Coeffs 1 to 4\r\n");
    pc.printf   ("1 %.3f\tPscale 0.01-0.5\r\n",     MotorA.sdbl[1]);
    pc.printf   ("2 %.3f\tP_gain 1.0-1000.0\r\n",   MotorA.sdbl[2]);
    pc.printf   ("3 %.3f\tDscale 0.01-0.5\r\n",     MotorA.sdbl[3]);
    pc.printf   ("4 %.3f\tD_gain 1.0-1000.0\r\n",   MotorA.sdbl[4]);
//    pc.printf   ("5 Set target_speed\r\n");
}
*/
    struct kb_command  {    //  Commands tabulated as list of these structures as seen below
    const char * cmd_word;              //  points to text e.g. "menu"
    const char * explan;                //  very brief explanation or clue as to purpose of function
    void (*f)(struct parameters &);     //  points to function
}  ;                                    //  Positioned in code here as knowledge needed by following menucmd

/**
*   void    menucmd (struct parameters & a)
*
*   List available terminal commands to pc terminal. No sense in touch screen using this
*/
void    menucmd (struct parameters & a)
{
    if  (a.respond) {
        a.com->printf("\r\n\nDual BLDC ESC type STM3 2018-20, Ver %s\r\nAt menucmd function - listing commands, source %s:-\r\n", get_version(), a.source == SOURCE_PC ? "PC" : "TS");
        for(int i = 0; i < a.numof_menu_items; i++)
            a.com->printf("[%s]\t\t%s\r\n", a.command_list[i].cmd_word, a.command_list[i].explan);
        a.com->printf("End of List of Commands\r\n");
    }
}

/********************** END OF COMMAND LINE INTERPRETER COMMANDS *************************************/


/**
*   struct  kb_command const loco_command_list[] = {
*   List of commands accepted from external controller through opto isolated com port 19200, 8,n,1
*/
struct  kb_command const loco_command_list[] = {    //  For comms between STM3_ESC and 'Brute Touch Screen Controller'
    //  ***** Broadcast commands for all STM3_ESC boards to execute. Boards NOT to send serial response *****
    {"fw", "forward", fw_cmd},
    {"re", "reverse", re_cmd},
    {"rb", "regen brake 0 to 99 %", rb_cmd},    //  max two digits
    {"hb", "hand brake", hb_cmd},
    {"v", "set motors V percent RANGE 0 to 99", v_cmd},
    {"i", "set motors I percent RANGE 0 to 99", i_cmd},
    {"vi", "set motors V and I percent RANGE 0 to 99", vi_cmd},
    {"w", "touch screen new slider touch", slider_touch_cmd},
    {"x", "updated slider RANGE 0 to 99", slider_touch_position_cmd},
    {"y", "touch_screen slider finger lifted clear", slider_untouch_cmd},
    {"kd", "kick the dog, reloads WatchDog", kd_cmd},
    {"ssl", "set speed limit e.g. 10.7", ssl_cmd},              //  NEW July 2019
    {"sbe", "set brake effectiveness 5 to 90 percent", brake_eff_set_cmd},              //  NEW May 2020
    //  ***** Endof Broadcast commands for all STM3_ESC boards to execute. Boards NOT to send serial response *****

    //  ***** Following are rx'd requests for serial response from addressed STM3_ESC only
    {"?v", "Report system bus voltage", sysV_report},
    {"?i", "Report motor both currents", sysI_report},
    {"who", "search for connected units, e.g. 3who returs 'who3' if found", who_cmd},
    {"tem", "report temperature", read_temperature_cmd},
    {"mph", "read loco speed miles per hour", mph_cmd},
    {"?s", "read loco speed miles per hour", mph_cmd},       //  Shorter-hand added 17th May 2020
//    {"ssl", "set speed limit e.g. 10.7", ssl_cmd},              //  NEW July 2019
//    {"sbe", "set brake effectiveness 5 to 90 percent", brake_eff_set_cmd},              //  NEW May 2020
//    {"rvi", "read most recent values sent to pwms", rvi_cmd},
//    {"rdi", "read motor currents and power voltage", rdi_cmd},
    //  ***** Endof
}   ;


/**
struct  kb_command const loco_command_list[] = {
List of commands accepted from external pc through non-opto isolated com port 9600, 8,n,1
*/
struct  kb_command const pc_command_list[] = {
    {"ls", "Lists available commands", menucmd},
    {"?", "Lists available commands, same as ls", menucmd},
    {"pot", "read drivers control pot", pot_cmd},
    {"fw", "forward", fw_cmd},
    {"re", "reverse", re_cmd},
    {"rb", "regen brake 0 to 99 %", rb_cmd},
    {"hb", "hand brake", hb_cmd},
    {"v", "set motors V percent RANGE 0 to 100", v_cmd},
    {"i", "set motors I percent RANGE 0 to 100", i_cmd},
    {"vi", "set motors V and I percent RANGE 0 to 100", vi_cmd},
    {"?v", "Report system bus voltage", sysV_report},
    {"?i", "Report motor both currents", sysI_report},
    {"?w", "show WatchDog timer contents", wd_report},
    {"who", "search for connected units, e.g. 3who returs 'who3' if found", who_cmd},
    {"us", "read or set user settings in eeprom", user_settings_cmd},                                 //  Big change Jan 2019
    {"ssl", "set speed limit e.g. 10.7", ssl_cmd},              //  NEW July 2019 ONLY HERE FOR TEST, normal use is from Touch Screen only.
    {"sbe", "set brake effectiveness 5 to 90 percent", brake_eff_set_cmd},              //  NEW May 2020
//    {"erase", "set eeprom contents to all 0xff", erase_cmd},
    {"tem", "report temperature", read_temperature_cmd},                                     //  Reports -50 when sensor not fitted
    {"kd", "kick the dog, reloads WatchDog", kd_cmd},
    {"ver", "Version", ver_cmd},
    {"rpm", "read motor pair speeds", rpm_cmd},
    {"mph", "read loco speed miles per hour", mph_cmd},
    {"?s", "read loco speed miles per hour", mph_cmd},
//    {"rvi", "read most recent values sent to pwms", rvi_cmd},
//    {"rdi", "read motor currents and power voltage", rdi_cmd},
//    {"bc", "bogie constants - wheel dia, motor pinion, wheel gear", bogie_constants_report_cmd},
//    {"gbb", "get bogie bytes from eeprom and report", gbb_cmd},    //  OBSOLETE, replaced by 'gbb'
    {"nu", "do nothing", null_cmd},
}   ;

//    cli_2019    (BufferedSerial * comport, kb_command const * list, int list_len, int source)  {
/**
*   cli_2019    pcc (&pc,   pc_command_list,    sizeof(pc_command_list) / sizeof(kb_command), SOURCE_PC)  ;
*   cli_2019    tsc (&com2, loco_command_list,  sizeof(loco_command_list) / sizeof(kb_command), SOURCE_TS)  ;
*
*   Instantiate two Command Line Interpreters, one for pc terminal and one for touch screen controller
*/
cli_2019    pcc (&pc,   pc_command_list,    sizeof(pc_command_list) / sizeof(kb_command), SOURCE_PC)  ;
cli_2019    tsc (&com2, loco_command_list,  sizeof(loco_command_list) / sizeof(kb_command), SOURCE_TS)  ;

/*
New - March 2018
Using opto isolated serial port, paralleled up using same pair to multiple STM3_ESC boards running this code.
New feature - commands have optional prefix digit 0-9 indicating which unit message is addressed to.
Commands without prefix digit - broadcast to all units, all to obey but none to respond.
Only units recognising its address from prefix digit may respond. This avoids bus contention.
But for BROADCAST commands, '0' may respond on behalf of the group
*/

/**
*   void    cli_2019::test ()  {
*
*   Daft check that class instantiation worked
*/
void    cli_2019::flush ()  {
//    char ch;
    while   (a.com->readable())
        a.com->getc();
    //pc.printf   ("At cli2019::test, source %d len %d,\r\n", a.source, a.numof_menu_items);
}

/**
*   void    cli_2019::core ()  {    
*
*   Command Line Interpreter.
*   This to be called every few milli secs from main programme loop.
*   Reads any rx'd chars into command line buffer, returns when serial buffer empty.
*   If last char rx'd war '\r' end of text delimiter, apt command_list is searched for a matched command in command line
*   If matched command found, apt function is executed.
*   Parameters available to functions from 'parameters' struct.
*/
void    cli_2019::core ()  {    
    int ch, IAm = user_settings.rd(BOARD_ID);
    char * pEnd;//, * cmd_line_ptr;
    while  (a.com->readable()) {
        ch = a.com->getc();
        if  (clindex > MAX_CMD_LEN)  {   //  trap out stupidly long command lines
            ESC_Error.set   (FAULT_COM_LINE_LEN, 1);                      //  Set FAULT_EEPROM bit 0 if 24LC64 problem
            a.com->printf   ("Error!! Stupidly long cmd line\r\n");
            clindex = 0;
        }
        if(ch != '\r')  {   //  was this the 'Enter' key?
            if  (ch != '\n')       //  Ignore line feeds
                cmdline[clindex++] = ch;  //  added char to command being assembled
        }
        else    {   //  key was CR, may or may not be command to lookup
            a.target_unit = BROADCAST;    //  Set to BROADCAST default once found command line '\r'
            cmdline_ptr = cmdline;
            cmdline[clindex] = 0; //  null terminate command string
            if(clindex)    {   //  If have got some chars to lookup
                int i, wrdlen;
                if  (isdigit(cmdline[0]))  {   //  Look for command with prefix digit
                    cmdline_ptr++;     //  point past identified digit prefix
                    a.target_unit = cmdline[0];  //  '0' to '9'
                    //com->printf ("Got prefix %c\r\n", cmd_line[0]);
                }
                for (i = 0; i < a.numof_menu_items; i++)   {   //  Look for input match in command list
                    wrdlen = strlen(commandlist[i].cmd_word);
                    if(strncmp(commandlist[i].cmd_word, cmdline_ptr, wrdlen) == 0 && !isalpha(cmdline_ptr[wrdlen]))  {   //  If match found
                        for (int k = 0; k < MAX_CLI_PARAMS; k++)    {
                            a.dbl[k] = 0.0;
                        }
                        a.position_in_list = i;
                        a.numof_dbls = 0;
//                                pEnd = cmdline + clindex; // does indeed point to null terminator
                        pEnd = cmdline_ptr + wrdlen;
//                        while   (*pEnd)  {          //  Assemble all numerics as doubles
                        while   (*pEnd && a.numof_dbls < MAX_CLI_PARAMS)  {          //  Assemble all numerics as doubles
                            a.dbl[a.numof_dbls++] = strtod    (pEnd, &pEnd);
                            while   (*pEnd && (pEnd < cmdline + clindex) && *pEnd && !isdigit(*pEnd) && ('.' != *pEnd) && ('-' != *pEnd) && ('+' != *pEnd))  {   //  Can crash cli here with e.g. 'ls -l'
                                pEnd++;
                            }   //  problem occurs with input such as "- ", or "-a", seemingly anything dodgy following a '-' or a '+'
                            if  (((*pEnd == '-') || (*pEnd == '+')) &&(!isdigit(*(pEnd+1))) && ('.' !=*(pEnd+1)))
                                pEnd = cmdline + clindex;   //  fixed by aborting remainder of line
                        }
                        a.respond = a.resp_always;
                        if  (((a.target_unit == BROADCAST) && (IAm == '0')) || (IAm == a.target_unit))
                            a.respond = true; //  sorted 26/4/18
                        //  All boards to obey BROADCAST command, only specific board to obey number prefixed command
                        if  ((a.target_unit == BROADCAST) || (IAm == a.target_unit))
                            commandlist[i].f(a);   //  execute command if addressed to this unit
                        i = a.numof_menu_items + 1;    //  to exit for loop
                    }   //  end of match found
                }       // End of for numof_menu_items
                if(i == a.numof_menu_items) {
//                    a.com->printf("No Match Found for CMD [%s]\r\n", cmdline);
                    pc.printf("No Match Found for CMD [%s]\r\n", cmdline);
                    ESC_Error.set   (FAULT_COM_LINE_NOMATCH, 1);                      //  Set FAULT_EEPROM bit 0 if 24LC64 problem
                }
            }           //  End of If have got some chars to lookup
            clindex = 0;
        }       // End of else key was CR, may or may not be command to lookup
    }           //  End of while (com->readable())
}               //  end of command line interpreter core function