/*
 * Copyright 2015 Mario Richter, Peter Stephan
 * This file is part of NeMo.
 *
 * NeMo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * NeMo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with NeMo.  If not, see <http://www.gnu.org/licenses/>.
 */

/*!
 * \file cmd.c
 * \ingroup	cmd
 * \brief	Kommandoverarbeitung
 */

/*!
 * \defgroup cmd Routinen zur Kommandoverarbeitung
 */

#include "main.h"

volatile uint8_t * avrport;
volatile uint8_t * avrddr;
volatile uint8_t * avrpin;

volatile uint8_t tcpCmdBuf[TCP_MAX_CONNECTIONS][CMD_BUFLEN];
volatile uint8_t tcpCmdBufInd[TCP_MAX_CONNECTIONS];

volatile uint8_t usartCmdBuf[CMD_BUFLEN];
volatile uint8_t usartCmdBufInd;
volatile uint8_t subMenu = SM_NONE;
volatile uint8_t connIndex;

uint8_t lightProgNr;
uint8_t lightCmdInd;
uint8_t lightBuf[64];
uint8_t lightBufInd;


/*!
 * \ingroup cmd
 * \brief Verarbeitung von TCP-Befehlen (TCP-Handlerroutine)
 *
 * Diese Funktion ist die Handlerfunktion für TCP-Verbindungen der Ports 1396 und 1397. Sie übernimmt
 * die Verbindungssteuerung und die Befehlsvorverarbeitung für eingetroffene Kommandos, bevor sie den
 * Befehlshandler aufruft.
 *
 * \param ind Index auf die Verbindung in der Verbindungsliste
 * \param flag Flags für die TCP-Verbindung
 *
 * \sa usartCmdHandler
 */

void tcpCmdHandler(uint8_t ind, uint8_t flag)
{
  uint8_t j, c;
  char buf[32];

  switch(flag)
  {
    case TCP_HNDL_NEWDATA:

      // Zeichen im Eingangsbuffer der Reihe nach abarbeiten
      while(tcpConnList[ind].rxread != tcpConnList[ind].rxwrit)
      {
        // Zeichen holen
        c = tcpConnList[ind].rxbuf[tcpConnList[ind].rxread];

        // Buffer weiterziehen
        tcpConnList[ind].rxread++;
        if(tcpConnList[ind].rxread >= TCP_RXTX_BUFLEN)
          tcpConnList[ind].rxread = 0;

        // Line Feed und Null-Character ignorieren, alles andere in den Buffer
        if((c != '\n') && (c != '\0'))
        {
          tcpCmdBuf[ind][tcpCmdBufInd[ind]] = c;
          if(tcpCmdBufInd[ind] < (CMD_BUFLEN - 1))
            tcpCmdBufInd[ind]++;
        }

        // Bei Carriage Return Befehlsauswertung starten
        if(c == '\r')
        {
          // Befehlshandler aufrufen
          cmd_init(ind);

          // Nach Befehlsausführung Kommandobuffer zurücksetzen
          tcpCmdBufInd[ind] = 0;
        }
      }
      break;

    case TCP_HNDL_ESTABLISHED:

      if(debugLevel & DEBUGLVL_STACK_TCP_INFO)
      {
        usart_send_string_P(PSTR("tcpCmdHandler: new conn to "));
        for(j = 0; j < 4; j++)
        {
          if(tcpConnList[ind].remoteIp[j] > 100)
            usart_send_char((tcpConnList[ind].remoteIp[j] / 100) + 0x30);
          if(tcpConnList[ind].remoteIp[j] > 10)
            usart_send_char(((tcpConnList[ind].remoteIp[j] % 100) / 10) + 0x30);
          usart_send_char((tcpConnList[ind].remoteIp[j] % 10) + 0x30);

          if(j != 3)
            usart_send_char('.');
        }
        usart_send_line("");
      }

      // Willkommensnachricht an Gegenstelle
      j = sprintf(buf, "Welcome to NeMo!\r\n");
      tcpSend(ind, (uint8_t *)buf, j);
      break;

    case TCP_HNDL_CLOSING:
      usart_send_line_P(PSTR("tcpCmdHandler: close conn"));
      tcpClose(ind);
      break;
  }
}


/*!
 * \ingroup cmd
 * \brief Verarbeitung von USART-Befehlen
 *
 * Diese Funktion liest Zeichen von der seriellen Schnittstelle und bereitet die Befehlsstrings vor,
 * bevor sie den Befehlshandler aufruft.
 *
 * \sa tcpCmdHandler
 */

void usartCmdHandler(void)
{
  uint8_t c;

  // Zeichen von serieller Schnittstelle einlesen
  c = (uint8_t)usart_get_char();

  // Ist ein neues Zeichen vorhanden?
  if(c != 0)
  {
    // Line Feed und Null-Character ignorieren, alles andere in den Buffer
    if((c != '\n') && (c != '\0'))
    {
      usartCmdBuf[usartCmdBufInd] = c;
      if(usartCmdBufInd < (CMD_BUFLEN - 1))
        usartCmdBufInd++;
    }

    // Bei Carriage Return Befehlsauswertung starten
    if(c == '\r')
    {
      // Befehlshandler aufrufen
      cmd_init(255);

      // Nach Befehlsausführung Kommandobuffer zurücksetzen
      usartCmdBufInd = 0;
    }
  }
}


/*!
 * \ingroup cmd
 * \brief wertet den über USART und TCP/IP empfangene Zeichenkette (Befehl) im Puffer aus.
 * Je nach Wert von Index ind kann eine Rückantwort an die Quelle USART / TCP/IP gesendet werden. 
 * Befehlssyntax: Gruppe Spezifikation1 (Spezifikation2)(;)(Parameter)(;)(Parameter)
 * Dabei werden zunächst Gruppe und Spezifikation 1 ausgetestet.
 * 
 * Da beim Empfang nicht klar ist ob der Befehl schon zu Ende ist, erfolgt solange ein Rücksprung, bis \r empfangen wird. 
 * Ist die Zeichekette zu lang erfolgt auch ein Rücksprung. 
 * 
 * Keine Leerzeichen zwischen Gruppeund Spezifikation1 und Spezifikation2
 * Gross- Kleinschreibung ist irrelevant.
 *
 * Zufügen von  Befehlen: 
 * Befehl einer Gruppe zugeordnen. Die Gruppen N,P,I,E,X müssten ausreichen.
 * Existiert keine passende Gruppe muss zunächst die switch/case Struktur der Gruppe zugefügt werden.
 * Spezifikation 1 in switch case Struktur zufügen.
 * Eventuel Abarbeitung für Spezifikation 2 zufügen. 
 * Parameter sollten (!) in cmc_XX abgearbeitet werden.
 * Zum Check des Aufbaus von Spezifikation2 existieren Funktionen wie is_S2_port oder is_S2_dez
 * Gegebenfalls diese verwenden oder ergänzen (Vorsicht!) oder Neuschreiben 
 * FEHLERMELDUNGEN NICHT VERGESSEN -> err()
 *  
 */

void cmd_init(uint8_t ind)
{  
  uint8_t   *chr;
  char      *ptr;
  uint8_t   i;
  uint16_t  k = 0;
  uint8_t   isOkay;
  char      buf[8];

  // Entsprechenden Buffer anhand übergebenem Index auswählen (TCP/USART)
  if(ind < TCP_MAX_CONNECTIONS)
  {
    chr = (uint8_t *)tcpCmdBuf[ind];
  }
  else
  {
    chr = (uint8_t *)usartCmdBuf;
  }

  if(subMenu == SM_NONE)
  {
    /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% */
    /* HAUPTMENUE */
    /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% */

    switch (toupper(chr[0]))
    {
      /***********************************************************************/
      /* Gruppe N: Netzwerk */
      case 'N':
      {
        switch (toupper(chr[1]))
        {
          case 'A':
            if (chr[2] == '\r')
            {
              arpTableShow(ind);
            }
            else errMsg(ind, ERRNO_TOO_MUCH_PARAM);
            break;

          case 'B':
            if (chr[2] == '\r')
            {
              errMsg(ind, ERRNO_OK);
              arpTableClear();
            }
            else errMsg(ind, ERRNO_TOO_MUCH_PARAM);
            break;

          case 'I':
            if(chr[2] == ';')
            {
              if(getNewIpAddr(&chr[3]) == 0)
              {
                for(i = 0; i < 4; i++)
                {
                  sprintf(buf, "%d", eeprom_read_byte(&eeIpAddr[i]));
                  sendString(ind, buf);
                  if(i < 3)
                    sendChar(ind, '.');
                }
                sendLine(ind, "");
              }
              else
              {
                errMsg(ind, ERRNO_BAD_PARAM);
              }
            }
            else
            {
              for(i = 0; i < 4; i++)
              {
                sprintf(buf, "%d", myIpAddr[i]);
                sendString(ind, buf);

                if(i < 3)
                  sendChar(ind, '.');
              }
              sendLine(ind, "");
            }
            break;

          case 'M':
            if(chr[2] == ';')
            {
              if(getNewMacAddr(&chr[3]) == 0)
              {
                for(i = 0; i < 6; i++)
                {
                  sprintf(buf, "%02x", eeprom_read_byte(&eeMacAddr[i]));
                  sendString(ind, buf);
                  if(i < 5)
                    sendChar(ind, ':');
                }
                sendLine(ind, "");
              }
              else
              {
                errMsg(ind, ERRNO_BAD_PARAM);
              }
            }
            else
            {
              for(i = 0; i < 6; i++)
              {
                sendHex(ind, myMacAddr[i], 0);

                if(i < 5)
                  sendChar(ind, ':');
              }
              sendLine(ind, "");
            }
            break;

          case 'P':
            if (chr[2] == '\r')
            {
              tcpShowPortList(ind);
            }
            else
              errMsg(ind, ERRNO_TOO_MUCH_PARAM);
            break;

          case 'T':
            if (chr[2] == '\r')
            {
              tcpShowConnList(ind);
            }
            else
              errMsg(ind, ERRNO_TOO_MUCH_PARAM);
            break;

          default:
            errMsg(ind, ERRNO_UNKNOWN_CMD);
        }
        break;
      }

      /***********************************************************************/
      /* Gruppe L: Lichtsteuerung */
      case 'L':
      {
        switch(toupper(chr[1]))
        {
          // -------------------------------------------
          // LA - Autostart konfigurieren
          case 'A':

            if(IS_NUMBER(chr[2]))
            {
              if(chr[2] == '0')
              {
                // Lichtprogrammnr. erhalten, nur oberes Nibble auf OFF setzen
                lightAutoStart &= ~(0xF0);
                lightAutoStart |= EEP_AUTOSTART_OFF;
                eeprom_write_byte(&eepLightAutoStart, lightAutoStart);
                errMsg(ind, ERRNO_OK);
              }
              else if(chr[2] == '1')
              {
                // Lichtprogrammnr. erhalten, nur oberes Nibble auf ON setzen
                lightAutoStart &= ~(0xF0);
                lightAutoStart |= EEP_AUTOSTART_ON;
                eeprom_write_byte(&eepLightAutoStart, lightAutoStart);
                errMsg(ind, ERRNO_OK);
              }
              else
              {
                errMsg(ind, ERRNO_BAD_PARAM);
              }
            }
            else
            {
              if((lightAutoStart & 0xF0) == EEP_AUTOSTART_ON)
                sendLine(ind, "1");
              else
                sendLine(ind, "0");
            }
            break;

          // -------------------------------------------
          // LE - Lichtprogramm stoppen (Ende)
          case 'E':
            lightMode = LIGHT_MODE_OFF;
            lightProgAct = 0;
            errMsg(ind, ERRNO_OK);
            break;

          // -------------------------------------------
          // LH - Lichtprogramm pausieren (Hold)
          case 'H':
            if(lightMode == LIGHT_MODE_ACTIVE)
            {
              lightMode = LIGHT_MODE_PAUSE;
              errMsg(ind, ERRNO_OK);
            }
            else
              errMsg(ind, ERRNO_NO_PROG_ACTIVE);
            break;

          // -------------------------------------------
          // LO - Einsatzort setzen / abfragen
          case 'O':
            if(chr[2] == '\r')        // Abfrage
            {
              sendLine(ind, lightLocation);
            }
            else if(IS_CHARACTER(chr[2]))
            {
              i = 0;

              // String kopieren
              while((IS_CHARACTER(chr[i+2])) && (i < 31))
              {
                lightLocation[i] = chr[i+2];
                i++;
              }

              // String abschließen, neuen Ort ins EEPROM schreiben
              lightLocation[i] = '\0';
              eeprom_write_block(lightLocation, eepLightLocation, 32);

              errMsg(ind, ERRNO_OK);
            }
            else
            {
              errMsg(ind, ERRNO_BAD_PARAM);
            }
            break;

          // -------------------------------------------
          // LP - Lichtprogramm abspielen (Play)
          case 'P':
            if(IS_NUMBER(chr[2]) && IS_NUMBER(chr[3]))
            {
              lightProgNr = ((chr[2] - 0x30) * 10) + (chr[3] - 0x30);

              if((lightProgNr == 0) || (lightProgNr > 16))
              {
                errMsg(ind, ERRNO_BAD_PARAM);
              }
              else
              {
                if(lightProgAct != lightProgNr)           // Neues Lichtprogramm? Dann dieses starten
                {
                  // Prüfen, ob das angeforderte Programm existiert (Name vorhanden)
                  if(IS_CHARACTER(lightPrograms[lightProgNr - 1][0]))
                  {
                    // Letztes aktives Programm merken
                    lightAutoStart = (lightAutoStart & 0xF0) | (lightProgNr - 1);
                    eeprom_write_byte(&eepLightAutoStart, lightAutoStart);

                    lightProgStart(lightProgNr);          // Lichtprogramm starten
                    errMsg(ind, ERRNO_OK);
                  }
                  else                                    // Kein Programm -> Fehler
                  {
                    errMsg(ind, ERRNO_NO_LIGHT_PROG);
                  }
                }
                else if(lightMode == LIGHT_MODE_PAUSE)    // Ist gerade pausiert? Dann jetzt weitermachen
                {
                  lightMode = LIGHT_MODE_ACTIVE;
                  errMsg(ind, ERRNO_OK);
                }
                else
                {
                  errMsg(ind, ERRNO_BAD_PARAM);
                }
              }
            }
            else if(lightMode == LIGHT_MODE_PAUSE)
            {
              lightMode = LIGHT_MODE_ACTIVE;
              errMsg(ind, ERRNO_OK);
            }
            else
            {
              errMsg(ind, ERRNO_BAD_PARAM);
            }
            break;

          // -------------------------------------------
          // LG - Lichtprogramm anzeigen (Get)
          case 'G':
            if(IS_NUMBER(chr[2]) && IS_NUMBER(chr[3]))
            {
              // Nr. des zu lesenden Lichtprogramms dekodieren
              lightProgNr = ((chr[2] - 0x30) * 10) + (chr[3] - 0x30);

              if((lightProgNr == 0) || (lightProgNr > 16))
              {
                errMsg(ind, ERRNO_BAD_PARAM);
              }
              else
              {
                // Prüfe, ob das angeforderte Programm existiert
                if(IS_CHARACTER(lightPrograms[lightProgNr - 1][0]))
                {
                  // Den ersten Befehlsblock aus dem EEPROM holen
                  if(i2cEepromReadSequential(((uint16_t)(lightProgNr - 1) << 11) + 0x40, lightBuf, 64) == 1)
                  {
                    lightMode = LIGHT_MODE_ERROR;
                    errMsg(ind, ERRNO_I2C_EEPROM_WRITE);
                  }
                  else
                  {
                    sendStringP(ind, PSTR("LG"));
                    sendChar(ind, chr[2]);
                    sendChar(ind, chr[3]);
                    sendChar(ind, ';');
                    sendLine(ind, (char *)lightPrograms[lightProgNr - 1]);

                    // Variablen für Ausgabe vorbereiten
                    connIndex = ind;
                    lightCmdInd = 0;
                    lightBufInd = 0;

                    // die Ausgabe übernimmt der lightScheduler() !
                    subMenu = SM_GETLIGHTPROG;
                  }
                }
                else
                {
                  errMsg(ind, ERRNO_NO_LIGHT_PROG);
                }
              }
            }
            break;

          // -------------------------------------------
          // LS - Lichtprogramm setzen (Set)
          case 'S':
            if(IS_NUMBER(chr[2]) && IS_NUMBER(chr[3]) && chr[4] == ';')
            {
              // Nr. des zu setzenden Lichtprogramms dekodieren
              lightProgNr = ((chr[2] - 0x30) * 10) + (chr[3] - 0x30);

              // Prüfe ob Programmnummer gültig (1-16)
              if((lightProgNr == 0) || (lightProgNr > 16))
              {
                errMsg(ind, ERRNO_BAD_PARAM);
              }
              else
              {
                // Prüfe ob Programmname gültig (mind. 1 Zeichen)
                if(IS_CHARACTER(chr[5]))
                {
                  lightBufInd = 0;
                  while(IS_CHARACTER(chr[5+lightBufInd]) && (lightBufInd < 63))
                  {
                    // Programmname in Zwischenspeicher übernehmen
                    lightBuf[lightBufInd] = chr[5+lightBufInd];
                    lightBufInd++;
                  }

                  // Rest des Namens mit Nullen füllen
                  while(lightBufInd < 64)
                  {
                    lightBuf[lightBufInd] = 0;
                    lightBufInd++;
                  }

                  // Programmname ins EEPROM schreiben
                  if(i2cEepromWritePage((uint16_t)(lightProgNr - 1) << 11, lightBuf, 64) == 0)
                  {
                    // Programmname ins Übersichtsarray kopieren
                    strncpy((char *)lightPrograms[lightProgNr - 1], (char *)lightBuf, 64);

                    subMenu = SM_SETLIGHTPROG;        // Verzweig ins Untermenü
                    lightBufInd = 0;                  // Es geht dann weiter im Bufferindex 0
                    lightCmdInd = 0;                  // Wir erwarten den 1. Befehl des Programms ...
                    sendStringP(ind, PSTR("001>"));   // ... und zeigen das auch an :-)
                  }
                  else
                  {
                    errMsg(ind, ERRNO_I2C_EEPROM_WRITE);
                  }
                }
                else
                {
                  errMsg(ind, ERRNO_BAD_PARAM);
                }
              }
            }
            else
              errMsg(ind, ERRNO_BAD_PARAM);
            break;

          // -------------------------------------------
          // LV - Aktuelle PWM-Werte anzeigen
          case 'V':
            sendStringP(ind, PSTR("Mode:"));
            if(lightMode == LIGHT_MODE_ACTIVE)
              sendStringP(ind, PSTR("ACT  "));
            else if(lightMode == LIGHT_MODE_PAUSE)
              sendStringP(ind, PSTR("PAUSE"));
            else if(lightMode == LIGHT_MODE_OFF)
              sendStringP(ind, PSTR("OFF  "));
            else
              sendStringP(ind, PSTR("XXX  "));

            sendStringP(ind, PSTR("; Step:"));
            sendChar(ind, (lightCmdStep / 100) + 0x30);
            sendChar(ind, ((lightCmdStep % 100) / 10) + 0x30);
            sendChar(ind, (lightCmdStep % 10) + 0x30);

            sendStringP(ind, PSTR("; Delay:"));
            snprintf(buf, 8, "%d", lightCmdAct.delay);
            sendString(ind, buf);

            sendStringP(ind, PSTR("; R:0x"));
            sendHex(ind, (uint8_t)((lightValRed & 0xFF00)>> 8), 0);
            sendStringP(ind, PSTR("; G:0x"));
            sendHex(ind, (uint8_t)((lightValGrn & 0xFF00) >> 8), 0);
            sendStringP(ind, PSTR("; B:0x"));
            sendHex(ind, (uint8_t)((lightValBlu & 0xFF00) >> 8), 0);
#if KANAL_WEISS == 1
            sendStringP(ind, PSTR("; W:0x"));
            sendHex(ind, (uint8_t)((lightValWht & 0xFF00) >> 8), 0);
#endif
            sendLine(ind, "");
            break;

          // -------------------------------------------
          // LX - Lichtprogramm löschen
          case 'X':
            if(IS_NUMBER(chr[2]) && IS_NUMBER(chr[3]))
            {
              // Nr. des zu setzenden Lichtprogramms dekodieren
              lightProgNr = ((chr[2] - 0x30) * 10) + (chr[3] - 0x30);

              // Prüfe ob Programmnummer gültig (1-16)
              if((lightProgNr == 0) || (lightProgNr > 16))
              {
                errMsg(ind, ERRNO_BAD_PARAM);
              }
              else
              {
                // Erstes Byte des Lichtprogramm-Namens auf 0 setzen --> Programm ungültig markieren
                i2cEepromWriteByte((uint16_t)(lightProgNr - 1) << 11, 0);

                // Name in der Übersichtsliste entfernen
                lightPrograms[lightProgNr - 1][0] = 0;

                errMsg(ind, ERRNO_OK);
              }
            }
            else
              errMsg(ind, ERRNO_BAD_PARAM);
            break;

          // -------------------------------------------
          // LU - Vorhandene Lichtprogramme ausgeben
          case 'U':

            connIndex = ind;
            lightCmdInd = 0;
            subMenu = SM_GETPROGRAMS;
            break;

          default:
            errMsg(ind, ERRNO_UNKNOWN_CMD);
        }
        break;
      }

      /***********************************************************************/
      /* Gruppe I: NeMo Intern */
      case 'I':
      {
        switch (toupper(chr[1]))
        {
          case 'A':
            sendStringP(ind, PSTR("5V:   "));
            sendString(ind, u5v);
            sendStringP(ind, PSTR("V (0x"));
            sendHex(ind, (uint8_t)((adcResults[0] & 0xFF00) >> 8), 0);
            sendHex(ind, (uint8_t)(adcResults[0] & 0x00FF), 0);
            sendLine(ind, ")");

            sendStringP(ind, PSTR("3.3V: "));
            sendString(ind, u3v3);
            sendStringP(ind, PSTR("V (0x"));
            sendHex(ind, (uint8_t)((adcResults[1] & 0xFF00) >> 8), 0);
            sendHex(ind, (uint8_t)(adcResults[1] & 0x00FF), 0);
            sendLine(ind, ")");

            sendStringP(ind, PSTR("VLED: "));
            sendString(ind, uLed);
            sendStringP(ind, PSTR("V (0x"));
            sendHex(ind, (uint8_t)((adcResults[2] & 0xFF00) >> 8), 0);
            sendHex(ind, (uint8_t)(adcResults[2] & 0x00FF), 0);
            sendLine(ind, ")");

            sendStringP(ind, PSTR("Temp: "));
            sendString(ind, NTCtemp);
            sendStringP(ind, PSTR("C (0x"));
            sendHex(ind, (uint8_t)((adcResults[3] & 0xFF00) >> 8), 0);
            sendHex(ind, (uint8_t)(adcResults[3] & 0x00FF), 0);
            sendLine(ind, ")");
            break;

          case 'D':
            if (chr[4] == '\r')
            {
              if (is_S2_hex(chr[2], chr[3]))
              {
                debugLevel = ((0x10 * asctohex(chr[2])) + asctohex(chr[3]));
                sendStringP(ind, PSTR("Debug Level changed to 0x"));
                sendHex(ind, debugLevel, 0);
                sendLine(ind, "");
              }
              else errMsg(ind, ERRNO_BAD_PARAM);
            }
            else errMsg(ind, ERRNO_BAD_PARAM);
            break;

          case 'V':
            sendString(ind, SW_VERSION);
            sendString(ind, " [");
            sendString(ind, __DATE__);
            sendChar(ind, ' ');
            sendString(ind, __TIME__);
            sendLine(ind, "]");
            break;

          default:
            errMsg(ind, ERRNO_UNKNOWN_CMD);
        }
        break;
      }

      default:
      {
        errMsg(ind, ERRNO_UNKNOWN_CMD);							// Falsche Gruppe
      }
    }
  }


  else if(subMenu == SM_SETLIGHTPROG)
  {
    /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% */
    /* LICHTPROGRAMM SETZEN */
    /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% */

    /* Aufbau des lightBuf (enthält jeweils eine EEPROM-Page zu 64 Byte):
     * ------------------------------------------------------------------
     *    0x00 ->     S - - - r g b w     - 0x07
     *    0x08 ->     P 1 2 3 - - - -     - 0x0F
     *    0x10 ->     F 1 2 3 r g b w     - 0x17
     *    0x18 ->     X - - - - - - -     - 0x1F
     *    ...
     *    0x38 ->     P 1 2 3 - - - -     - 0x3F
     *
     *    lightBufInd zeigt dabei immer auf die erste Stelle (S, P, F usw.).
     */

    ptr = strtok((char *)chr, ";");                           // String in Teilstrings zertrennen

    switch(*ptr)
    {
      /***********************************************************************/
      /* S - Farbe setzen */
      case 'S':
      case 's':

        sendLine(ind, "");

        if(lightCmdInd > 247)                                 // Prüfe, ob noch Platz im aktuellen Programm
        {                                                     // Es muss noch 1 Eintrag für eXit möglich sein!
          sendStringP(ind, PSTR("MF"));                       // Memory-Full-Meldung
        }
        else
        {
          lightBuf[lightBufInd] = 'S';                        // EEPROM-Array-Eintrag vorbereiten
          lightBuf[lightBufInd+1] = 0;
          lightBuf[lightBufInd+2] = 0;
          lightBuf[lightBufInd+3] = 0;

          isOkay = 1;

          // Rot-Wert
          ptr = strtok(NULL, ";");
          if((ptr != NULL) && (IS_NUMBER(*ptr)))                // Prüfe, ob eine Zahl folgt
          {
            k = (uint16_t)atoi(ptr);                            // Rot-Wert aus String holen
            if(k < 256)
            {
              lightBuf[lightBufInd + 4] = (uint8_t)k;           // Wert ins EEPROM-Array eintragen

              // Grün-Wert
              ptr = strtok(NULL, ";");
              if((ptr != NULL) && (IS_NUMBER(*ptr)))            // Prüfe, ob weitere Zahl folgt
              {
                k = (uint16_t)atoi(ptr);                        // Grün-Wert aus String holen
                if(k < 256)
                {
                  lightBuf[lightBufInd + 5] = (uint8_t)k;       // Wert ins EEPROM-Array eintragen

                  // Blau-Wert
                  ptr = strtok(NULL, ";");
                  if((ptr != NULL) & (IS_NUMBER(*ptr)))         // Prüfe, ob weitere Zahl folgt
                  {
                    k = (uint16_t)atoi(ptr);                    // Blau-Wert aus String holen
                    if(k < 256)
                    {
                      lightBuf[lightBufInd + 6] = (uint8_t)k;   // Wert ins EEPROM-Array eintragen
#if KANAL_WEISS == 1
                      // Weiß-Wert
                      ptr = strtok(NULL, ";");                  // ... und selbes Spiel nochmal für weiß, falls benötigt
                      if((ptr != NULL) & (IS_NUMBER(*ptr)))
                      {
                        k = (uint16_t)atoi(ptr);
                        if(k < 256)
                          lightBuf[lightBufInd + 7] = (uint8_t)k;
                        else
                          isOkay = 0;
                      }
#else
                      lightBuf[lightBufInd + 7] = 0;
#endif
                    }
                    else
                      isOkay = 0;
                  }
                  else
                    isOkay = 0;
                }
                else
                  isOkay = 0;
              }
              else
                isOkay = 0;
            }
            else
              isOkay = 0;
          }
          else
            isOkay = 0;

          if(isOkay == 1)
          {
            finishEEPArrayEntry();
            sendStringP(ind, PSTR("OK"));
          }
          else
          {
            sendStringP(ind, PSTR("??"));
          }
        }

          sendChar(ind, ' ');
          sendChar(ind,  ((lightCmdInd + 1) / 100) + 0x30);
          sendChar(ind, (((lightCmdInd + 1) % 100) / 10) + 0x30);
          sendChar(ind,  ((lightCmdInd + 1) % 10) + 0x30);
          sendChar(ind, '>');
        break;

      /***********************************************************************/
      /* P - Pause */
      case 'P':
      case 'p':

        sendLine(ind, "");

        if(lightCmdInd > 247)                                 // Prüfe, ob noch Platz im aktuellen Programm
        {                                                     // Es muss noch 1 Eintrag für eXit möglich sein!
          sendStringP(ind, PSTR("MF"));                       // Memory-Full-Meldung
        }
        else
        {
          lightBuf[lightBufInd] = 'P';
          lightBuf[lightBufInd + 1] = 0;
          lightBuf[lightBufInd + 4] = 0;
          lightBuf[lightBufInd + 5] = 0;
          lightBuf[lightBufInd + 6] = 0;
          lightBuf[lightBufInd + 7] = 0;

          isOkay = 1;

          // Wert für Pausenzeit holen
          ptr = strtok(NULL, ";");
          if((ptr != NULL) && (IS_NUMBER(*ptr)))
          {
            k = (uint16_t)atoi(ptr);
            if(k <= 65000)
            {
              lightBuf[lightBufInd + 2] = (uint8_t)((k & 0xFF00) >> 8);
              lightBuf[lightBufInd + 3] = (uint8_t)(k & 0x00FF);
            }
            else
              isOkay = 0;
          }
          else
            isOkay = 0;

          if(isOkay == 1)
          {
            finishEEPArrayEntry();                            // Eintrag im EEPROM-Array fertigstellen
            sendStringP(ind, PSTR("OK"));
          }
          else
          {
            sendStringP(ind, PSTR("??"));
          }
        }

        sendChar(ind, ' ');
        sendChar(ind,  ((lightCmdInd + 1) / 100) + 0x30);
        sendChar(ind, (((lightCmdInd + 1) % 100) / 10) + 0x30);
        sendChar(ind,  ((lightCmdInd + 1) % 10) + 0x30);
        sendChar(ind, '>');
        break;

      /***********************************************************************/
      /* F - Farbwechsel */
      case 'F':
      case 'f':

        sendLine(ind, "");

        if(lightCmdInd > 247)                                 // Prüfe, ob noch Platz im aktuellen Programm
        {                                                     // Es muss noch 1 Eintrag für eXit möglich sein!
          sendStringP(ind, PSTR("MF"));                       // Memory-Full-Meldung
        }
        else
        {
          lightBuf[lightBufInd] = 'F';
          lightBuf[lightBufInd + 1] = 0;

          isOkay = 1;

          // Wert für Farbwechselzeit holen
          ptr = strtok(NULL, ";");
          if((ptr != NULL) && (IS_NUMBER(*ptr)))
          {
            k = (uint16_t)atoi(ptr);
            if(k <= 65000)
            {
              lightBuf[lightBufInd + 2] = (uint8_t)((k & 0xFF00) >> 8);
              lightBuf[lightBufInd + 3] = (uint8_t)(k & 0x00FF);

              // Rot-Wert
              ptr = strtok(NULL, ";");
              if((ptr != NULL) && (IS_NUMBER(*ptr)))                // Prüfe, ob eine Zahl folgt
              {
                k = (uint16_t)atoi(ptr);                            // Rot-Wert aus String holen
                if(k < 256)
                {
                  lightBuf[lightBufInd + 4] = (uint8_t)k;           // Wert ins EEPROM-Array eintragen

                  // Grün-Wert
                  ptr = strtok(NULL, ";");
                  if((ptr != NULL) && (IS_NUMBER(*ptr)))            // Prüfe, ob weitere Zahl folgt
                  {
                    k = (uint16_t)atoi(ptr);                        // Grün-Wert aus String holen
                    if(k < 256)
                    {
                      lightBuf[lightBufInd + 5] = (uint8_t)k;       // Wert ins EEPROM-Array eintragen

                      // Blau-Wert
                      ptr = strtok(NULL, ";");
                      if((ptr != NULL) & (IS_NUMBER(*ptr)))         // Prüfe, ob weitere Zahl folgt
                      {
                        k = (uint16_t)atoi(ptr);                    // Blau-Wert aus String holen
                        if(k < 256)
                        {
                          lightBuf[lightBufInd + 6] = (uint8_t)k;   // Wert ins EEPROM-Array eintragen
#if KANAL_WEISS == 1
                          // Weiß-Wert
                          ptr = strtok(NULL, ";");                  // ... und selbes Spiel nochmal für weiß, falls benötigt
                          if((ptr != NULL) & (IS_NUMBER(*ptr)))
                          {
                            k = (uint16_t)atoi(ptr);
                            if(k < 256)
                              lightBuf[lightBufInd + 7] = (uint8_t)k;
                            else
                              isOkay = 0;
                          }
#else
                          lightBuf[lightBufInd + 7] = 0;
#endif
                        }
                        else
                          isOkay = 0;
                      }
                      else
                        isOkay = 0;
                    }
                    else
                      isOkay = 0;
                  }
                  else
                    isOkay = 0;
                }
                else
                  isOkay = 0;
              }
              else
                isOkay = 0;
            }
            else
              isOkay = 0;
          }
          else
            isOkay = 0;

          if(isOkay == 1)
          {
            finishEEPArrayEntry();                            // Eintrag im EEPROM-Array fertigstellen
            sendStringP(ind, PSTR("OK"));
          }
          else
          {
            sendStringP(ind, PSTR("??"));
          }
        }

        sendChar(ind, ' ');
        sendChar(ind,  ((lightCmdInd + 1) / 100) + 0x30);
        sendChar(ind, (((lightCmdInd + 1) % 100) / 10) + 0x30);
        sendChar(ind,  ((lightCmdInd + 1) % 10) + 0x30);
        sendChar(ind, '>');
        break;

      /***********************************************************************/
      /* X - Programmende */
      case 'X':
      case 'x':

        sendLine(ind, "");

        lightBuf[lightBufInd] = 'X';
        lightBuf[lightBufInd + 1] = 0;
        lightBuf[lightBufInd + 2] = 0;
        lightBuf[lightBufInd + 3] = 0;
        lightBuf[lightBufInd + 4] = 0;
        lightBuf[lightBufInd + 5] = 0;
        lightBuf[lightBufInd + 6] = 0;
        lightBuf[lightBufInd + 7] = 0;

        // Hier müssen wir auf jeden Fall ins EEPROM schreiben, da das Programm jetzt beendet ist.
        // Alte Einträge im lightBuf sind egal, können in der Page mitgeschrieben werden.

        k  = ((uint16_t)(lightProgNr - 1) << 11);       // Schreibadresse berechnen: Basisadresse des Programms
        k += (lightCmdInd & 0xF8) << 3;                 // Adresse innerhalb der EEPROM-Page
        k += 0x40;                                      // Offset: eine Page, da die erste Page den Namen enthält

        i2cEepromWritePage(k, lightBuf, 64);            // Page aus dem Buffer ablegen

        sendChar(ind,  ((lightCmdInd + 1) / 100) + 0x30);       // Anzahl eingegebener Befehle ausgeben
        sendChar(ind, (((lightCmdInd + 1) % 100) / 10) + 0x30);
        sendChar(ind,  ((lightCmdInd + 1) % 10) + 0x30);
        sendLineP(ind, PSTR(" commands entered."));

        lightBufInd = 0;                                // Variablen zurückstellen und Untermenü verlassen
        lightCmdInd = 0;
        subMenu = SM_NONE;
        break;

      /***********************************************************************/
      /* Unbekannter Befehl */
      default:

        sendStringP(ind, PSTR("?? "));
        sendChar(ind,  ((lightCmdInd + 1) / 100) + 0x30);
        sendChar(ind, (((lightCmdInd + 1) % 100) / 10) + 0x30);
        sendChar(ind,  ((lightCmdInd + 1) % 10) + 0x30);
        sendChar(ind, '>');
    }
  }
}


/* ------------------------- IP-Adresse prüfen -------------------------- */

/*!
 * \ingroup cmd
 * \brief Prüfen und Übernehmen einer neuen IP-Adresse
 *
 * Diese Funktion prüft eine in einem String übergebene IP-Adresse auf Plausibilität (d.h. ob vier Zahlen
 * enthalten sind, die max. 255 groß sind). Ist die IP-Adresse gültig, wird sie im EEPROM abgespeichert
 * und steht dann beim nächsten Neustart als neue IP-Adresse zur Verfügung.
 *
 * \param mac Zeiger auf den String mit der IP-Adresse. Der Pointer zeigt auf die erste Ziffer.
 *
 * \return 0: OK, neue IP-Adresse ins EEPROM geschrieben. 1: Keine gültige IP-Adresse.
 *
 * \sa getNewIpAddr
 */

uint8_t getNewIpAddr(uint8_t * ip)
{
  uint8_t isBad = 0;
  uint8_t newIp[4] = {0, 0, 0, 0};
  uint8_t i = 0, j = 0;

  unsigned long int tmp;

  // Format des übergebenen Strings:    1 9 2 . 1 6 8 . 0 . 1 2
  // Parsen der vier Stellen der IP-Adresse
  for(i = 0; i < 4; i++)
  {
    if(IS_NUMBER(ip[j]))                                // Ist eine Zahl vorhanden, diese dekodieren
    {
      tmp = strtoul((char *)&ip[j], NULL, 10);          // Zahl dekodieren

      if(tmp > 255)                                     // Prüfe, ob Zahl größer als erlaubt
      {
        isBad = 1;                                      // Wenn ja, abbrechen
        i = 4;
      }
      else
      {
        newIp[i] = (uint8_t)tmp;                        // Zahl gültig: übernehmen

        while(IS_NUMBER(ip[j]))                         // Weiter zum Trennzeichen
        {
          j++;
        }

        j++;                                            // ip[j] zeigt jetzt auf nächste Zahl
      }
    }
    else
    {
      isBad = 1;                                        // Keine Zahl: Fehler, parsen abbrechen
      i = 4;
    }
  }

  if(isBad == 0)                                        // War alles korrekt, dann IP ins EEPROM ablegen
  {
    eeprom_write_block(newIp, eeIpAddr, 4);
  }

  return isBad;
}

/*!
 * \ingroup cmd
 * \brief Prüfen und Übernehmen einer neuen MAC-Adresse
 *
 * Diese Funktion prüft eine in einem String übergebene MAC-Adresse auf Plausibilität (d.h. an
 * allen erwarteten Positionen muss eine ASCII-codierte Hexadezimalziffer stehen). Ist die
 * MAC-Adresse gültig, wird sie im EEPROM abgespeichert und steht dann beim nächsten Neustart
 * als neue MAC-Adresse zur Verfügung.
 *
 * \param mac Zeiger auf den String mit der MAC-Adresse. Der Pointer zeigt auf die erste Ziffer.
 *
 * \return 0: OK, neue MAC-Adresse ins EEPROM geschrieben. 1: Keine gültige MAC-Adresse.
 *
 * \sa getNewIpAddr
 */

uint8_t getNewMacAddr(uint8_t * mac)
{
  uint8_t newMac[6];
  uint8_t isBad = 0;
  uint8_t i;

  /* MAC-String:  0 1 : 2 3 : 4 5 : 6 7 : 8 9 : A B
   * Index:       0     3     6     9    12    15
   */

  for(i = 0; i < 6; i++)
  {
    // Prüfe, ob an erwartetem Index eine zweistellige Hex-Zahl steht
    if(IS_HEXNUMBER(mac[3*i]) && (IS_HEXNUMBER(mac[(3*i)+1])))
    {
      newMac[i] = (asctohex(mac[3*i]) << 4) + asctohex(mac[(3*i)+1]);
    }
    else
    {
      // Fehler gefunden, wir brauchen gar nicht weiter parsen ...
      isBad = 1;
      i = 6;
    }
  }

  // Hat alles gepasst, dann die neue MAC-Adresse ins EEPROM schreiben.
  // Sie wird dann beim nächsten Start zurückgelesen und übernommen.
  if(isBad == 0)
  {
    eeprom_write_block(newMac, eeMacAddr, 6);
  }

  return isBad;
}


/* ------------------------- Eintrag ins EEPROM-Array -------------------------- */

/*!
 * \ingroup cmd
 * \brief Fortführen der Lichtprogramm-Einträge im ext. EEPROM
 *
 * Diese Funktion prüft nach Eintrag eines neuen Lichtprogramm-Eintrags im Array lightBuf, ob
 * damit eine neue EEPROM-Page fertiggestellt wurde. Falls ja, wird diese Page ins EEPROM
 * geschrieben und eine neue Page begonnen. Andernfalls wird nur der nächste Befehl innerhalb
 * des Arrays begonnen.
 */

void finishEEPArrayEntry(void)
{
  uint16_t k;

  if(lightBufInd >= 0x38)                           // Ist eine neue EEPROM-Page fertiggeworden?
  {
    k  = ((uint16_t)(lightProgNr - 1) << 11);       // Schreibadresse berechnen: Basisadresse des Programms
    k += ((uint16_t)(lightCmdInd & 0xF8) << 3);     // Adresse innerhalb des Programms, abhängig von der Befehlsnummer
    k += 0x40;                                      // Offset hinzuaddieren (Befehl 1 beginnt 0x40 nach Basisadresse)

    sendString(255, "Address: 0x");
    sendHex(255, (uint8_t)((k & 0xFF00) >> 8), 0);
    sendHex(255, (uint8_t)(k & 0x00FF), 0);
    sendLine(255, "");

    i2cEepromWritePage(k, lightBuf, 64);            // Page aus dem Buffer ablegen

    lightBufInd = 0;                                // Eine neue EEPROM-Page beginnen
  }
  else
  {
    lightBufInd += 0x08;                            // Nächster Befehl innerhalb der Page
  }

  lightCmdInd++;                                    // Befehlszähler erhöhen
}

/* ------------------------- Sendebefehle -------------------------- */

void sendLine(uint8_t ind, char * str)
{
  sendString(ind, str);
  sendChar(ind, '\r');
  sendChar(ind, '\n');
}

void sendLineP(uint8_t ind, const char * str)
{
  while(pgm_read_byte(str) != '\0')
    sendChar(ind, pgm_read_byte(str++));

  sendChar(ind, '\r');
  sendChar(ind, '\n');
}

void sendString(uint8_t ind, char * str)
{
  while(*str != '\0')
    sendChar(ind, *str++);
}

void sendStringP(uint8_t ind, const char * str)
{
  while(pgm_read_byte(str) != '\0')
    sendChar(ind, pgm_read_byte(str++));
}

void sendHex(uint8_t ind, uint8_t hex, uint8_t upper)
{
  uint8_t tmp;

  tmp = (hex & 0xF0) >> 4;                                    // Obere 4 Bits holen

  if(tmp <= 9)
    sendChar(ind, tmp + 0x30);                                // Zahl 0-9
  else
    sendChar(ind, upper ? (tmp + 0x37) : (tmp + 0x57));       // Groß- oder Kleinbuchstabe A-F / a-f

  tmp = (hex & 0x0F);                                         // Untere 4 Bits holen

  if(tmp <= 9)
    sendChar(ind, tmp + 0x30);                                // Zahl 0-9
  else
    sendChar(ind, upper ? (tmp + 0x37) : (tmp + 0x57));       // Groß- oder Kleinbuchstabe A-F / a-f
}

void sendChar(uint8_t ind, char c)
{
  if(ind == 255)
  {
    usart_send_char(c);
  }
  else
  {
    tcpConnList[ind].txbuf[tcpConnList[ind].txwrit] = c;
    tcpConnList[ind].txwrit++;
    if(tcpConnList[ind].txwrit >= TCP_RXTX_BUFLEN)
      tcpConnList[ind].txwrit = 0;
  }
}

/* ------------------------------- Komando - Prüfbefehle -----------------------------------*/

/*!
 * \ingroup cmd
 * \brief checkt S2
 * Check ob Spezifikation 2 eine Dezimalzahl ist 00 - 99
 *
 */

int is_S2_dez(uint8_t s2, uint8_t s3)
{
  if (s2 > 0x2F && s2 < 0x3A && s3 > 0x2F && s3 < 0x3A)
    return 1;
  else
    return 0;
}

/*!
 * \ingroup cmd
 * \brief checkt S2
 * Check ob Spezifikation 2 eine Hexadezimal ist 00 - FF
 *
 */

int is_S2_hex(uint8_t s2, uint8_t s3)
{
  if ((((s2 > 0x2F) && (s2 < 0x3A)) || ((s2 > 0x40) && (s2 < 0x71))) && (((s3 > 0x2F) && (s3 < 0x3A)) || ((s3 > 0x40) && (s3 < 0x71))))
    return 1;
  else
    return 0;
}

/* ------------------------------- Komando - Konvertierungsbefehle -----------------------------------*/

/*!
 * \ingroup cmd
 * \brief Wandlung ASCII-Zeichen (hex) in uint8_t
 *
 * Diese Funktion wandelt ein einzelnes Hex-Zeichen in ASCII-Darstellung in den
 * zugehörigen Zahlenwert (uint8_t).
 *
 * \param a Zu wandelndes Hex-Zeichen (ASCII-codiert)
 *
 * \ret Zugehöriger Zahlenwert (uint8_t)
 */

uint8_t asctohex(char a)
{
  uint8_t c = 0;

  if((a >= '0') && (a <= '9'))
  {
    c = (uint8_t)a - '0';
  }
  else if((a >= 'A') && (a <= 'F'))
  {
    c = (uint8_t)a - 'A' + 10;
  }
  else if((a >= 'a') && (a <= 'f'))
  {
    c = (uint8_t)a - 'a' + 10;
  }

  return c;
}

