Terminal Menü

Die folgende Application wird mit dem myDatalogMUCmini oder dem myDatalogMUC realisiert.

Vorgabe: Es soll ein Menü mit der Skriptfunktionalität des myDatalogMUC(mini) realisiert werden. Dafür wird die serielle Schnittstelle für die lokale Eingabe von Parametern verwendet. Dies kann unter anderem genutzt werden, um bei den Universaleingängen (UI1 – Ui8 bzw. Ui1 – Ui3) ein Offset einzustellen, wie auch in untenstehendem Beispiel demonstriert.

Dazu wird folgendes benötigt:

  • myDatalogMUC(mini)
  • Terminalprogramm (z.B.: TeraTerm)
  • das Script „TerminalMenu“
Nicht benötigt wird:

  • Firmwareänderung
  • eigens programmierte PC Software
  • Display vor Ort

Um über die serielle Schnittstelle kommunizieren zu können, muss für diese „script parsing“ aktiviert sein. Dazu müssen am Server unter „Schnittstellen“ folgende Einstellungen gemacht werden:

terminalMenu_cfg

Die Baudrate muss zwischen 300 Baud und 115200 Baud betragen und mit dem Wert, der im Terminalprogramm eingestellt ist, übereinstimmen. Das Stopbit muss auf 1 gesetzt sein.

Hier geht’s zum Demo-Video!

#include <string>

const
{
  COM_RX_IDLE         = 0x00,   // standard
  COM_RX_WAIT_FOR_STX = 0x01,   // atomatically wait for new STX
  COM_RX_ACTIVE       = 0x02,   // next input is part of Frame (no STX)
  COM_RX_FINISHED     = 0x03,   // \r or \n found
  COM_RX_PROCESSING   = 0x04,
}

const
{
  MENU_CHANNEL       = 0x00,
  MENU_OFFSET        = 0x01,
  MENU_SHOW_VALUES   = 0x02,
  MENU_MENU          = 0xFF,
}

const
{
  MAX_FRAME_LENGTH = 256, // On RS485 no more than 256 characters allowed
  MAX_TOKEN_LENGTH = 20,
}

const
{
  // 1 ... COM Port 1 (RS485)
  // 2 ... COM Port 2 (RS232)
  // 3 ... COM Port 3 (RS485)
  TERMINAL_COM_PORT = 2,
}

static const csValueStatus[]{} = [ "OK", "NAN", "OVERFLOW", "UNDERFLOW", "OPENLOOP", "SHORTCUT" ];

//static const csMenuStart{} = "\r\n###############################################\r\n";
static const csMenuStart{} = "\r\n";
static const csMenuEntry{}     = "# %d: %s\r\n";
static const csMenuCurrentCh{} = "# Current Channel: %d\r\n";
static const csMenuSelect{}    = "# Select Menu:";
static const csMenuEnter{}     = "\r\n# Enter %s (%s):";
static const csSaveOK{}        = "\r\n# %s (%s) was saved.\r\n";
static const csActualValueOK{} = "\r\n# Show current values activated, type menu to go back to menu\r\n";
static const csChannelData{}   = "%s (%d)=%s:%f\r\n";

#define NAME_MAXLENGTH   30
#define MENU_ENTRY       [.number, .name{NAME_MAXLENGTH}]

static const caMenuData[][MENU_ENTRY] =
[
  [MENU_CHANNEL,      "Set Current Channel [1-9]"],
  [MENU_OFFSET,       "Offset [mm]"],
  [MENU_SHOW_VALUES,  "Show actual values"]
];

#define CHANNEL_ENTRY   [.NumberInput, .Name{NAME_MAXLENGTH}, Float:.Offset, .BackupOffset]
#define CHANNEL_COUNT   9

static caChannels[CHANNEL_COUNT][CHANNEL_ENTRY] =
[
  [MDN_CH_UI1,      "UI1",         0.0, MDN_CH_MUC_IN1],
  [MDN_CH_UI2,      "UI2",         0.0, MDN_CH_MUC_IN2],
  [MDN_CH_UI3,      "UI3",         0.0, MDN_CH_MUC_IN3],
  [MDN_CH_UI4,      "UI4",         0.0, MDN_CH_MUC_IN4],
  [MDN_CH_UI5,      "UI5",         0.0, MDN_CH_MUC_IN5],
  [MDN_CH_UI6,      "UI6",         0.0, MDN_CH_MUC_IN6],
  [MDN_CH_UI7,      "UI7",         0.0, MDN_CH_MUC_IN7],
  [MDN_CH_UI8,      "UI8",         0.0, MDN_CH_MUC_IN8],
  [MDN_CH_EXTTEMP,  "Temperature", 0.0, MDN_CH_MUC_IN9]
];

//Default Startup Values
static iRXState = COM_RX_ACTIVE;
static iMenuState = MENU_MENU;
static iCurrentChannel = 1;

//Received Data Values
static sDataReceived{MAX_FRAME_LENGTH};
static iFrameIndex = 0;

// Limits iValue between iMin and iMax
Limit(&iValue, iMin=cellmin, iMax=cellmax)
{
  if      (iValue > iMax)
    iValue = iMax;
  else if (iValue < iMin)
    iValue = iMin;
}

// Converts given number(float or integer) to string
NumberToString( &{_, Float}:xNumber, iTag = tagof(xNumber) )
{
  new sBuffer{15};

  if(iTag == 0 ) // integer value
  {
    sprintf(sBuffer, sizeof(sBuffer), "%d", xNumber);
  }
  else if(iTag == tagof(Float:)) // float value
  {
    sprintf(sBuffer, sizeof(sBuffer), "%f", xNumber);
  }

  return sBuffer;
}

// Send menu to given com port
SendMenu(iComPort)
{
  new sBuffer{MAX_FRAME_LENGTH};
  new sMenu{MAX_FRAME_LENGTH};

  // Add menu start
  strcat(sMenu, csMenuStart);

  // Add list of menu entries
  for(new iIndex=0; iIndex < sizeof(caMenuData); iIndex++)
  {
    sprintf(sBuffer, sizeof(sBuffer), csMenuEntry, caMenuData[iIndex].number, caMenuData[iIndex].name);
    strcat(sMenu, sBuffer);
  }

  //Add current channel for configuration
  sprintf(sBuffer, sizeof(sBuffer), csMenuCurrentCh, iCurrentChannel);
  strcat(sMenu, sBuffer);

  // Add menu command promt
  strcat(sMenu, csMenuSelect);
  printf("strlen(sMenu):%d\r\n", strlen(sMenu));

  Mdn_SerialTx(iComPort, sMenu, strlen(sMenu));
}

// Send entered value to given com port
SendMenuEnter(iComPort, iMenuNumber, &{_, Float}:xCurrentValue, iTag = tagof(xCurrentValue))
{
  new sBuffer{MAX_FRAME_LENGTH};

  sprintf(sBuffer, sizeof(sBuffer), csMenuEnter, caMenuData[iMenuNumber].name, NumberToString(xCurrentValue, iTag));

  Mdn_SerialTx(iComPort, sBuffer, strlen(sBuffer));
}

// Send confirmation of entered value to given com port
SendSaveOK(iComPort, iMenuNumber, &{_, Float}:xValue, iTag = tagof(xValue))
{
  new sBuffer{MAX_FRAME_LENGTH};

  sprintf(sBuffer, sizeof(sBuffer), csSaveOK, caMenuData[iMenuNumber].name, NumberToString(xValue, iTag));
  strcat(sBuffer, csMenuSelect);

  Mdn_SerialTx(iComPort, sBuffer, strlen(sBuffer));
}

// Send confirmation, that "show actual values" is activated
SendCurrentValuesActivated(iComPort)
{
  Mdn_SerialTx(iComPort, csActualValueOK, strlen(csActualValueOK));
}

public main()
{
  //Load last offset values
  for(new iIndex = 0; iIndex < CHANNEL_COUNT; iIndex++)
  {
    Mdn_GetCh(caChannels[iIndex].BackupOffset, caChannels[iIndex].Offset);
  }

  SendMenu(TERMINAL_COM_PORT);
}

//This code is executed at the end of every measurement intervall
public Mdn_CtrlFinish()
{
  new sData{MAX_FRAME_LENGTH};
  new Float:fValue;
  new Mdn_ValueStatus:iStatus;

  print("Mdn_CtrlFinish\r\n");

  for(new iIndex = 0; iIndex < CHANNEL_COUNT; iIndex++)
  {
    //Change InputChannels
    Mdn_GetCh(caChannels[iIndex].NumberInput, fValue, iStatus);
    Mdn_SetCh(caChannels[iIndex].NumberInput, caChannels[iIndex].Offset + fValue, iStatus);

    //Save offset
    Mdn_SetCh(caChannels[iIndex].BackupOffset, caChannels[iIndex].Offset);
  }

  //Show value of current channel on every measurement intervall
  if(iMenuState == MENU_SHOW_VALUES)
  {
    Mdn_GetCh(caChannels[iCurrentChannel-1].NumberInput, fValue, iStatus);
    sprintf(sData, sizeof(sData), csChannelData, caChannels[iCurrentChannel-1].Name, iCurrentChannel, csValueStatus[iStatus], fValue);
    print(sData);
    Mdn_SerialTx(TERMINAL_COM_PORT, sData, strlen(sData));
  }
}

public Mdn_SerialEvent(com, event)
{
  // print received data
  printf("Mdn_SerialEvent(%d, %d)\r\n", com, event);

  if(event == MDN_SERIAL_EVENT_INIT)
  {
    print("Event MDN_SERIAL_EVENT_INIT\r\n");
  }
  else if(event == MDN_SERIAL_EVENT_MEASURE)
  {
    print("Event MDN_SERIAL_EVENT_MEASURE\r\n");
    Mdn_SerialFinish(com);
  }
  else if(event == MDN_SERIAL_EVENT_TIMEOUT)
  {
    // Timeout
    Mdn_SerialFinish(com);
  }
}

// Uart receive callback
public Mdn_SerialRx(com, const data{}, len)
{
  if(com == TERMINAL_COM_PORT)
  {
    Terminal_SerialRx(com, data, len);
  }
}

// evaluate received data
Terminal_SerialRx(iComPort, const aData{}, iDataLength)
{
  new iIndex=0;
  new iCurrentChar=0;

  // print received data
  printf("Mdn_SerialRx(com:%d state:%d, \"%s\", %d)\r\n", iComPort, iRXState, aData, iDataLength);

  while (iIndex < iDataLength)
  {
    iCurrentChar = aData{iIndex++};
    printf("iCurrentChar: %c\r\n", iCurrentChar);

    // set on default, next input is part of Frame (no STX)
    if(iRXState == COM_RX_ACTIVE)
    {
      print("STATE COM_RX_ACTIVE\r\n");
      // check for ETX
      if(iCurrentChar != '\r' && iCurrentChar != '\n')
      {
        printf("check for ETX: iFrameIndex=%d\r\n", iFrameIndex);
        if(iFrameIndex < MAX_FRAME_LENGTH)
        {
          sDataReceived{iFrameIndex++} = iCurrentChar;
        }
      }
      else
      {
        // ETX received -> go to COM_RX_FINISHED state
        print("ETX received\r\n");
        iRXState = COM_RX_FINISHED;
        if(iFrameIndex < MAX_FRAME_LENGTH)
        {
          sDataReceived{iFrameIndex} = EOS
          break; // exit while
        }
      }
    }
  }

  if(iRXState == COM_RX_FINISHED)
  {
    printf("STATE COM_RX_FINISHED: %s\r\n", sDataReceived);

    Terminal_ProcessFrame(iComPort, sDataReceived, iFrameIndex);

    iRXState = COM_RX_ACTIVE;
    iFrameIndex = 0;
  }
}

Terminal_ProcessFrame(iComPort, const sFrame{}, iFrameLength)
{
  //new sToken{MAX_TOKEN_LENGTH};
  new Float:fValue = 0.0;

  printf("Terminal_ProcessFrame (%d): %s\r\n", iFrameLength, sFrame);

  if(iFrameLength == 0 || strcmp(sFrame, "menu") == 0)
  {
    print("MENU found.\r\n");
    SendMenu(iComPort);
    iMenuState = MENU_MENU;
  }
  else
  {
    if(iMenuState == MENU_MENU)
    {
      if(iFrameLength == 1)
      {
        new iMenuNumber = sFrame{0};

        if(iMenuNumber == '0' + MENU_CHANNEL)
        {
          print("MENU 0 (Channel) found.\r\n");
          SendMenuEnter(iComPort, MENU_CHANNEL, iCurrentChannel);
          iMenuState = MENU_CHANNEL;
        }
        else if(iMenuNumber == '0' + MENU_OFFSET)
        {
          print("MENU 1 (Offset) found.\r\n");
          SendMenuEnter(iComPort, MENU_OFFSET, caChannels[iCurrentChannel-1].Offset);
          iMenuState = MENU_OFFSET;
        }
        else if(iMenuNumber == '0' + MENU_SHOW_VALUES)
        {
          print("MENU 2 (show values) found.\r\n");
          SendCurrentValuesActivated(iComPort);
          iMenuState = MENU_SHOW_VALUES;
        }
      }
    }
    else if(iMenuState == MENU_CHANNEL)
    {
      printf("New Value Entered: %s.\r\n", sFrame);
      iCurrentChannel = atoi(sFrame);

      Limit(iCurrentChannel, 1, CHANNEL_COUNT);

      SendSaveOK(iComPort, MENU_CHANNEL, iCurrentChannel);
      iMenuState = MENU_MENU;
    }
    else if(iMenuState == MENU_OFFSET)
    {
      printf("New Value Entered: %s.\r\n", sFrame);
      fValue = atof(sFrame);
      SendSaveOK(iComPort, MENU_OFFSET, fValue);
      caChannels[iCurrentChannel-1].Offset = fValue;
      iMenuState = MENU_MENU;
    }
  }
}

Beitrag aktualisiert 31.03.2014: Erforderliche Einstellungen hinzugefügt

Juergen

Jürgen kennen Sie vielleicht schon vom Telefon. Als Mitglied des Microtronics Support Teams hat er ein offenes Ohr für Kundenfragen aller Art. Jürgen ist ein Allrounder und hat bei Microtronics Erfahrung als Firmware- und Softwaretester sowie Entwickler gesammelt. Sein wertvolles Know-How in Sachen Pawn Scripts und PLC teilt er im Microtronics Blog mit Ihnen.

Letzte Artikel von Juergen (Alle anzeigen)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.