Plugin help

Moderators: grovkillen, Stuntteam, TD-er

Post Reply
Message
Author
rayE
Normal user
Posts: 144
Joined: 12 Oct 2017, 12:53
Location: Philippines

Plugin help

#1 Post by rayE » 21 Mar 2020, 03:11

Hi All,
I am modifying the serialProxy plugin to read serial data from battery inverters/UPS systems that support the Megatec serial protocol. It's working but im stuck on one thing. How do i read a value from a text box in the plugin web page and assign it to a variable?

The item im interested in is...........

Code: Select all

#define P099_BATTERY_V                PCONFIG_LONG(1)  
#define P099_BATTERY_V_LABEL     PCONFIG_LABEL(1) 
Complete code below

Code: Select all

#ifdef USES_P099

// #######################################################################################################
// #################### Plugin 099 Serial SNADI ##########################################################
// #######################################################################################################
//
// Interact with a device connected to serial
// Allows to redirect data to a controller
//

#include <ESPeasySerial.h>

#define PLUGIN_099
#define PLUGIN_ID_099           99
#define PLUGIN_NAME_099         "Communication - Serial SNADI [TESTING]"


#define P099_BAUDRATE           PCONFIG_LONG(0)
#define P099_BAUDRATE_LABEL     PCONFIG_LABEL(0)

#define P099_BATTERY_V           PCONFIG_LONG(1)    //RAE
#define P099_BATTERY_V_LABEL     PCONFIG_LABEL(1)

#define P099_QUERY_VALUE        0 // Temp placement holder until we know what selectors are needed.
#define P099_NR_OUTPUT_OPTIONS  1

#define P099_NR_OUTPUT_VALUES   1
#define P099_QUERY1_CONFIG_POS  3

#define P099_DEFAULT_BAUDRATE   2400
#define P099_DEFAULT_BATTERY_V   48

#define P99_Nlines              1 //Was 2 RAE
#define P99_Nchars              64

#define P099_INITSTRING         0
#define P099_EXITSTRING         1

long int batteryV = 0;
//batteryV = getFormItemInt(P099_BATTERY_V);
//batteryV = getFormItemInt(P099_DEFAULT_BATTERY_V);

struct P099_data_struct : public PluginTaskData_base {
  P099_data_struct() :  P099_easySerial(nullptr) {}

  ~P099_data_struct() {
    reset();
  }

  void reset() {
    if (P099_easySerial != nullptr) {
      delete P099_easySerial;
      P099_easySerial = nullptr;
    }
  }

  bool init(const int16_t serial_rx, const int16_t serial_tx, unsigned long baudrate) {
    if ((serial_rx < 0) && (serial_tx < 0)) {
      return false;
    }
    reset();
    P099_easySerial = new ESPeasySerial(serial_rx, serial_tx);

    if (isInitialized()) {
      P099_easySerial->begin(baudrate);
      return true;
    }
    return false;
  }

  bool isInitialized() const {
    return P099_easySerial != nullptr;
  }

  void sendString(String& data) {
  //void sendString(const String& data) {
    if (isInitialized()) {
      if (data.length() > 0) {
        data += '\r';       //added cr rae 
        P099_easySerial->write(data.c_str());

        if (loglevelActiveFor(LOG_LEVEL_INFO)) {
          String log = F("Proxy: Sending: ");
          log += data;
          addLog(LOG_LEVEL_INFO, log);
        }
      }
    }
  }

  bool loop() {
    if (!isInitialized()) {
      return false;
    }
    bool fullSentenceReceived = false;

    if (P099_easySerial != nullptr) {
      while (P099_easySerial->available() > 0 && !fullSentenceReceived) {
        // Look for end marker
        char c = P099_easySerial->read();

        switch (c) {
          case 13:
          {
            const size_t length = sentence_part.length();
            bool valid          = length > 0;

            for (size_t i = 0; i < length && valid; ++i) {
              if ((sentence_part[i] > 127) || (sentence_part[i] < 32)) {
                sentence_part = "";
                ++sentences_received_error;
                valid = false;
              }
            }

            if (valid) {
              //We have a valid serial string
              fullSentenceReceived = true;

              //*******************************************************************************
              parsedata(sentence_part);   //Function call to parse the data ..RAE
              calcBatteryVolts();         //Function call to calculate the battery voltage based on cell V ..RAE
              //*******************************************************************************
            }
            break;
          }
          case 10:

            // Ignore LF
            break;
          default:
            sentence_part += c;
            break;
        }

        if (max_length_reached()) { fullSentenceReceived = true; }
      }
    }

    if (fullSentenceReceived) {
      ++sentences_received;
      length_last_received = sentence_part.length();
    }
    return fullSentenceReceived;
  }

  void getSentence(String& string) {
    string        = sentence_part;
    sentence_part = "";
  }

  void getSentencesReceived(uint32_t& succes, uint32_t& error, uint32_t& length_last) const {
    succes      = sentences_received;
    error       = sentences_received_error;
    length_last = length_last_received;
  }

  void setMaxLength(uint16_t maxlenght) {
    max_length = maxlenght;
  }

private:

  bool max_length_reached() const {
    if (max_length == 0) { return false; }
    return sentence_part.length() >= max_length;
  }

  ESPeasySerial *P099_easySerial = nullptr;
  String         sentence_part;
  uint16_t       max_length               = 550;
  uint32_t       sentences_received       = 0;
  uint32_t       sentences_received_error = 0;
  uint32_t       length_last_received     = 0;
};


// Plugin settings:
// Validate:
// - [0..9]
// - "+", "-", "."
// - [A..Z]
// - [a..z]
// - ASCII 32 - 217
// Sentence start:  char
// Sentence end:  CR/CRLF/LF/char
// Max length sentence: 1k max
// Interpret as:
// - Float
// - int
// - String
// Init string (incl parsing CRLF like characters)
// Timeout between sentences.


boolean Plugin_099(byte function, struct EventStruct *event, String& string) {
  boolean success = false;

  switch (function) {
    case PLUGIN_DEVICE_ADD: {
      Device[++deviceCount].Number           = PLUGIN_ID_099;
      Device[deviceCount].Type               = DEVICE_TYPE_DUAL;
      Device[deviceCount].VType              = SENSOR_TYPE_STRING;
      Device[deviceCount].Ports              = 0;
      Device[deviceCount].PullUpOption       = false;
      Device[deviceCount].InverseLogicOption = false;
      Device[deviceCount].FormulaOption      = false;  
      Device[deviceCount].ValueCount         = 1;     //rae 
      Device[deviceCount].SendDataOption     = false;   //Send to controller rae
      Device[deviceCount].TimerOption        = true;
      Device[deviceCount].GlobalSyncOption   = false;
      break;
    }

    //Return the device name
    case PLUGIN_GET_DEVICENAME: {
      string = F(PLUGIN_NAME_099);
      break;
    }

    //called when the user opens the module configuration page
    //it allows to add a new row for each output variable of the plugin
    case PLUGIN_GET_DEVICEVALUENAMES: {
      for (byte i = 0; i < VARS_PER_TASK; ++i) {
        if (i < P099_NR_OUTPUT_VALUES) {
          const byte pconfigIndex = i + P099_QUERY1_CONFIG_POS;
          byte choice             = PCONFIG(pconfigIndex);
          safe_strncpy(
            ExtraTaskSettings.TaskDeviceValueNames[i],
            Plugin_099_valuename(choice, false),
            sizeof(ExtraTaskSettings.TaskDeviceValueNames[i]));
        } else {
          ZERO_FILL(ExtraTaskSettings.TaskDeviceValueNames[i]);
        }
      }
      break;
    }

    case PLUGIN_GET_DEVICEGPIONAMES: {
      serialHelper_getGpioNames(event, false, true); // TX optional
      break;
    }

    case PLUGIN_WEBFORM_SHOW_VALUES:
    {
      P099_data_struct *P099_data =
        static_cast<P099_data_struct *>(getPluginTaskData(event->TaskIndex));

      if ((nullptr != P099_data) && P099_data->isInitialized()) {
        uint32_t success, error, length_last;
        P099_data->getSentencesReceived(success, error, length_last);
        byte varNr = VARS_PER_TASK;
        addHtml(pluginWebformShowValue(event->TaskIndex, varNr++, F("Success"),     String(success)));
        addHtml(pluginWebformShowValue(event->TaskIndex, varNr++, F("Error"),       String(error)));
        addHtml(pluginWebformShowValue(event->TaskIndex, varNr++, F("Length Last"), String(length_last), true));

        // success = true;
      }
      break;
    }

    case PLUGIN_SET_DEFAULTS:
    {
      P099_BAUDRATE = P099_DEFAULT_BAUDRATE;
      //RAE
      //P099_BATTERY_V = P099_DEFAULT_BATTERY_V;
      //batteryV = P099_BATTERY_V;

      success = true;
      break;
    }

    case PLUGIN_WEBFORM_SHOW_CONFIG:
    {
      string += serialHelper_getSerialTypeLabel(event);
      success = true;
      break;
    }

    case PLUGIN_WEBFORM_LOAD: {
      serialHelper_webformLoad(event);

      /*
         P099_data_struct *P099_data =
            static_cast<P099_data_struct *>(getPluginTaskData(event->TaskIndex));
         if (nullptr != P099_data && P099_data->isInitialized()) {
            String detectedString = F("Detected: ");
            detectedString += String(P099_data->P099_easySerial->baudRate());
            addUnit(detectedString);
       */

      addFormNumericBox(F("Baudrate"), P099_BAUDRATE_LABEL, P099_BAUDRATE, 2400, 115200);
      addUnit(F("baud"));

      //RAE
      addFormNumericBox(F("Battery V"), P099_BATTERY_V_LABEL, P099_BATTERY_V, 12, 96);
      addUnit(F("12, 24, 48, 96 Volts"));

      /*
         {
         // In a separate scope to free memory of String array as soon as possible
         sensorTypeHelper_webformLoad_header();
         String options[P099_NR_OUTPUT_OPTIONS];

         for (int i = 0; i < P099_NR_OUTPUT_OPTIONS; ++i) {
          options[i] = Plugin_099_valuename(i, true);
         }

         for (byte i = 0; i < P099_NR_OUTPUT_VALUES; ++i) {
          const byte pconfigIndex = i + P099_QUERY1_CONFIG_POS;
          sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P099_NR_OUTPUT_OPTIONS, options);
         }
         }
       */

      {
        String strings[P99_Nlines];
        LoadCustomTaskSettings(event->TaskIndex, strings, P99_Nlines, P99_Nchars);

        for (byte varNr = 0; varNr < P99_Nlines; varNr++)
        {
          String label = F("Send control char ");
          label += String(varNr + 1);
          addFormTextBox(label, getPluginCustomArgName(varNr), strings[varNr], P99_Nchars);
        }
      }


      P099_html_show_stats(event);

      success = true;
      break;
    }

    //This defines the code to be executed when the form is submitted
    //the plugin settings should be saved to Settings.TaskDevicePluginConfig[event->TaskIndex][x]
    case PLUGIN_WEBFORM_SAVE: {
      serialHelper_webformSave(event);
      P099_BAUDRATE = getFormItemInt(P099_BAUDRATE_LABEL);
      //RAE
      P099_BATTERY_V = getFormItemInt(P099_BATTERY_V_LABEL);

      String error;
      char   P099_deviceTemplate[P99_Nlines][P99_Nchars];

      for (byte varNr = 0; varNr < P99_Nlines; varNr++)
      {
        if (!safe_strncpy(P099_deviceTemplate[varNr], WebServer.arg(getPluginCustomArgName(varNr)), P99_Nchars)) {
          error += getCustomTaskSettingsError(varNr);
        }
      }

      if (error.length() > 0) {
        addHtmlError(error);
      }
      SaveCustomTaskSettings(event->TaskIndex, (byte *)&P099_deviceTemplate, sizeof(P099_deviceTemplate));

      success = true;
      break;
    }

    case PLUGIN_INIT: {
      const int16_t serial_rx = CONFIG_PIN1;
      const int16_t serial_tx = CONFIG_PIN2;
      initPluginTaskData(event->TaskIndex, new P099_data_struct());
      P099_data_struct *P099_data =
        static_cast<P099_data_struct *>(getPluginTaskData(event->TaskIndex));

      if (nullptr == P099_data) {
        return success;
      }

      //batteryV = getFormItemInt(P099_BATTERY_V_LABEL);
      if (P099_data->init(serial_rx, serial_tx, P099_BAUDRATE)) {
        success = true;

        if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
          String log = F("Serial : Init OK  ESP GPIO-pin RX:");
          log += serial_rx;
          log += F(" TX:");
          log += serial_tx;
          addLog(LOG_LEVEL_DEBUG, log);
        }
      } else {
        clearPluginTaskData(event->TaskIndex);
      }
      break;
    }

    case PLUGIN_EXIT: {
      clearPluginTaskData(event->TaskIndex);
      success = true;
      break;
    }

    case PLUGIN_FIFTY_PER_SECOND: {
      if (Settings.TaskDeviceEnabled[event->TaskIndex]) {
        P099_data_struct *P099_data =
          static_cast<P099_data_struct *>(getPluginTaskData(event->TaskIndex));

        if ((nullptr != P099_data) && P099_data->loop()) {
          // schedule_task_device_timer(event->TaskIndex, millis() + 10);
          delay(0); // Processing a full sentence may take a while, run some
                    // background tasks.
          P099_data->getSentence(event->String2);

          sendData(event);
        }
        success = true;
      }
      break;
    }

    case PLUGIN_READ: {
      P099_data_struct *P099_data =
        static_cast<P099_data_struct *>(getPluginTaskData(event->TaskIndex));

      if ((nullptr != P099_data)) {
        String strings[P99_Nlines];
        LoadCustomTaskSettings(event->TaskIndex, strings, P99_Nlines, P99_Nchars);
        parseSystemVariables(strings[0], false);
        P099_data->sendString(strings[0]);
      }
      break;
    }
  }
  return success;
}

String Plugin_099_valuename(byte value_nr, bool displayString) {
  switch (value_nr) {
    case P099_QUERY_VALUE: return displayString ? F("Value")          : F("v");
  }
  return "";
}

void P099_html_show_stats(struct EventStruct *event) {
  P099_data_struct *P099_data =
    static_cast<P099_data_struct *>(getPluginTaskData(event->TaskIndex));

  if ((nullptr == P099_data) || !P099_data->isInitialized()) {
    return;
  }
  {
    addRowLabel(F("Current Sentence"));
    String sentencePart;
    P099_data->getSentence(sentencePart);
    addHtml(sentencePart);
  }

  {
    addRowLabel(F("Sentences (pass/fail)"));
    String   chksumStats;
    uint32_t success, error, length_last;
    P099_data->getSentencesReceived(success, error, length_last);
    chksumStats  = success;
    chksumStats += '/';
    chksumStats += error;
    addHtml(chksumStats);
    addRowLabel(F("Length Last Sentence"));
    addHtml(String(length_last));
  }
}

//***RAE SNADI Functions****************************************************************************************************************************************************************
//********************************************************************************************************************************************************************************
void parsedata(String sentence_part)
  {
    //Print to debug Lenghth: string
    const size_t length = sentence_part.length();
    String log = F("SentencePart: ");      //rae
    log += length;
    log += ": ";
    log += sentence_part;                  //rae
    addLog(LOG_LEVEL_DEBUG, log);          //rae

    //Parse the data string
    sentence_part.remove(0, 1);                             //Remove '(' leading character
    float parsed_data[10];                                  //Float array for the parsed data
    int index = 0;                                          //Reset index counter
    String string_convert;                                  //String
    char sentence_array[50];                                //String array for raw data from the serial port
    sentence_part.toCharArray(sentence_array, length);      //Convert string raw data to char array      
    char *p = sentence_array;                               //create sentence_array address pointer p
    char *str;                                              //create str pointer
    index = 0;                                              //set counter to 0
              
    while ((str = strtok_r(p, " ", &p)) != NULL) {          //parse data use space as seperator
      string_convert = str;                                 //???
      parsed_data[index] = string_convert.toFloat();        //Convert string to float
      UserVar[0 + index] = parsed_data[index];              //Write 8 values to dummy vars in task 1+2
      index++;                                              //counter + 1 - loop count  
    }

    //Print to debug Lenghth: parsed_data
    log = F("Parsed Data: ");                              //Title     
    log += length;                                         //Data length
    log += ": ";                                           //Seperator
    for (int i = 0; i <= 7; i++) {                         //Print all vars
      log += parsed_data[i];                 
      log += ": ";
    }    
    addLog(LOG_LEVEL_DEBUG, log);  
            
    return;
  }

void calcBatteryVolts() 
  {
    //Snadi reports the cell voltage so we need to multiply this 
    //given 2v per cell to get the real battery voltage.
    float batteryCalcV = 0.0;
    float batteryCellV = UserVar[0 + 5];

    switch (batteryV) {
      case 12:
        batteryCalcV = batteryCellV * 6;
      break;
      case 24:
        batteryCalcV = batteryCellV * 12;
        break;
      case 48:
        batteryCalcV = batteryCellV * 24;
        break;
      case 96:
        batteryCalcV = batteryCellV * 48;
      break;
    }
    UserVar[0 + 5] = batteryCalcV;

    //Print to debug Lenghth: parsed_data
    String log;
    log = F("batteryCalcV: ");                                  
    log += batteryCalcV;                                            
    addLog(LOG_LEVEL_DEBUG, log); 
     
    return;
  }

#endif // USES_P099
Plugin GUI (Battery V is the item of interest)

Image
Attachments
1.png
1.png (12.48 KiB) Viewed 6266 times

User avatar
ThomasB
Normal user
Posts: 1064
Joined: 17 Jun 2018, 20:41
Location: USA

Re: Plugin help

#2 Post by ThomasB » 21 Mar 2020, 03:55

I suggest looking at the _P004_Dallas.ino for inspiration. The basic idea is to:

1. Define a PLUGIN_VALUENAME1 (value name for voltage), see line 20.
https://github.com/letscontrolit/ESPEas ... as.ino#L20

For example,

Code: Select all

#define PLUGIN_VALUENAME1_099 "VDC"
2. Get the value name var for webform presentation, see line 63.
https://github.com/letscontrolit/ESPEas ... as.ino#L63

3. Assign the measured voltage data to the value var (float precision), see line 207.
https://github.com/letscontrolit/ESPEas ... s.ino#L207

4. Tell the data handler that a new voltage value is available. This is done by returning success=true, see line 209.
https://github.com/letscontrolit/ESPEas ... s.ino#L209

That's a basic summary. The code to do this is up to you. Good luck.

- Thomas

TD-er
Core team member
Posts: 8643
Joined: 01 Sep 2017, 22:13
Location: the Netherlands
Contact:

Re: Plugin help

#3 Post by TD-er » 21 Mar 2020, 11:16

I will make a pull request of the changes I've been working on for this plugin.
It does allow for a bit more interaction as it also has a separate send function to send data to the serial device (instead of just a single "init" string)

rayE
Normal user
Posts: 144
Joined: 12 Oct 2017, 12:53
Location: Philippines

Re: Plugin help

#4 Post by rayE » 22 Mar 2020, 03:47

Hi Thomas,
Thanks for that detailed walk through and it did help in my understanding of plugin's. It seems that the example you gave is for the plugin output values, i need the following.

1. The battery V textbox is a user INPUT (one time) that indicates the actual battery voltage the system operates on. This value once entered should be saved to flash and still be present after a cold boot. This seems to be the case as after a cold boot the value first entered is present in the text box.

2. I need to read and use this battery V value in my plugin code and this is the part im stuck with. I assume the value in the text box is stored in the locations defined by

Code: Select all

 #define P099_BATTERY_V           PCONFIG_LONG(1)
I have tried a few variants of the following to read the value but i get a compiler error stating "event not declared in this scope".

Code: Select all

batteryV = getFormItemInt(P099_BATTERY_V);      //Get battery_V from GUI
So my question is how do i read this value?

TIA
Ray

User avatar
ThomasB
Normal user
Posts: 1064
Joined: 17 Jun 2018, 20:41
Location: USA

Re: Plugin help

#5 Post by ThomasB » 22 Mar 2020, 18:03

Sorry, I misunderstood your original question. For entering a fixed numerical value (signed long) into the web form you would use addFormNumericBox(). Or if a list of pre-defined choices is needed then you can use addFormSelector().

These functions are used in several ESPEasy plugins. So you will find plenty of examples for using them in your custom plugin.

- Thomas

rayE
Normal user
Posts: 144
Joined: 12 Oct 2017, 12:53
Location: Philippines

Re: Plugin help

#6 Post by rayE » 23 Mar 2020, 08:54

Got it! Looking at plugin 60 helped, this is the fix.

Declair the variable that i copy the gui BATTERY_V (PCONFIG_LONG(1)) into as a global.

Code: Select all

int batteryV;                     //This needs to be global if using in your own functions outside of PLUGIN_READ
Copy the web gui PCONFIG_LONG(1 to my variable in the PLUGIN_READ

Code: Select all

 batteryV = PCONFIG_LONG(1);   //Battery_V from GUI    RAE
Thanks again.
All is good :-)

Post Reply

Who is online

Users browsing this forum: No registered users and 28 guests