#ifndef MBUS_DISABLE

#include "modules/out_modbus.h"
#include "Arduino.h"
#include "options.h"
#include "utils.h"
#include "Streaming.h"

#include "item.h"
#include <ModbusMaster.h>
#include "main.h"
#include <HardwareSerial.h>

extern aJsonObject *modbusObj;
extern ModbusMaster node;
extern short modbusBusy;
extern void modbusIdle(void) ;
uint32_t mbusSlenceTimer = 0;

struct reg_t
{
  const char verb[4];
  const uint8_t id;
};

/*
struct serial_t
{
  const char verb[4];
  const serialParamType mode;
};
*/

#define PAR_I16 1
#define PAR_I32 2
#define PAR_U16 3
#define PAR_U32 4
#define PAR_I8H 5
#define PAR_I8L 6
#define PAR_U8H 7
#define PAR_U8L 8
#define PAR_TENS 9
#define PAR_100 10
#define PAR_COIL 11


const reg_t regSize_P[] PROGMEM =
{
  { "i16", (uint8_t) PAR_I16 },
  { "i32", (uint8_t) PAR_I32 },
  { "u16", (uint8_t) PAR_U16 },
  { "u32", (uint8_t) PAR_U32 },
  { "i8h", (uint8_t) PAR_I8H },
  { "i8l", (uint8_t) PAR_I8L },
  { "u8h", (uint8_t) PAR_U8H },
  { "u8l", (uint8_t) PAR_U8L },
  { "x10", (uint8_t) PAR_TENS },
  { "100", (uint8_t) PAR_100 }
 // { "bit", (uint8_t) PAR_COIL }
} ;
#define regSizeNum sizeof(regSize_P)/sizeof(reg_t)

/**
 * @brief Меняет порядок байтов в 16-битном числе.
 * @param x Входное число.
 * @return Число с изменённым порядком байтов.
 */
uint16_t swap (uint16_t x) {return  ((x & 0xff) << 8) | ((x & 0xff00) >> 8);}

/**
 * @brief Преобразует строку в тип регистра.
 * @param str Строка с типом регистра.
 * @return Код типа регистра.
 */
int  str2regSize(char * str)
{
  for(uint8_t i=0; i<regSizeNum && str;i++)
      if (strcmp_P(str, regSize_P[i].verb) == 0)
           return pgm_read_byte_near(&regSize_P[i].id);
  return (int) PAR_I16;
}

//TODO irs etc
/**
 * @brief Получает имя параметра по номеру регистра.
 * @param parameters JSON-объект с параметрами.
 * @param regnum Номер регистра.
 * @return Имя параметра или NULL.
 */
char * getParamNameByReg(aJsonObject * parameters, int regnum)
{
  if (!parameters) return NULL;

  aJsonObject * i = parameters->child;
  while (i)
  {
  aJsonObject *  regObj = aJson.getObjectItem(i, "reg");
  if (regObj && regObj->type == aJson_Int && regObj->valueint == regnum) 
      {
        debugSerial<<F("MBUS: ")<<i->name<<F(" added by num ")<<regnum<<endl;
        return i->name;
      }
  i=i->next;
  }
return NULL;  
}

/**
 * @brief Проверяет наличие действия в JSON-объекте.
 * @param execObj JSON-объект.
 * @return true, если действие найдено, иначе false.
 */
bool haveAction(aJsonObject * execObj)
{
aJsonObject * j = execObj->child;  
switch (execObj->type) 
{
case aJson_Object:
      while (j)
        {
          if (j->name && *j->name && (*j->name != '@')) return true;
          j=j->next;
        }
      break;
case aJson_Array:
      while (j)
        {
          if (haveAction(j)) return true;
          j=j->next;
        }
}        
return false;
}

/**
 * @brief Загружает и применяет конфигурацию Modbus.
 * @return true, если конфигурация успешно загружена, иначе false.
 */
bool out_Modbus::getConfig()
{
  // Retrieve and store template values from global modbus settings
  if (!store || !item || !item->itemArg || (item->itemArg->type != aJson_Array) || aJson.getArraySize(item->itemArg)<2 || !modbusObj)
  {
    errorSerial<<F("MBUS: config failed:")<<(bool)store<<F(",")<<(bool)item<<F(",")<<(bool)item->itemArg<<F(",")<<(item->itemArg->type != aJson_Array)<<F(",")<< (aJson.getArraySize(item->itemArg)<2)<<endl;
    return false;
  }
  aJsonObject * templateIdObj = aJson.getArrayItem(item->itemArg, 1);
  if (templateIdObj->type != aJson_String || !templateIdObj->valuestring)
            {
              errorSerial<<F("MBUS: Invalid template.")<<endl;
              return false;
            }
  aJsonObject * templateObj = aJson.getObjectItem(modbusObj, templateIdObj->valuestring);
  if (! templateObj)
            {
              errorSerial<<F("MBUS: Modbus template not found: ")<<templateIdObj->valuestring<<endl;
              return false;
            }

  aJsonObject * serialParamObj=aJson.getObjectItem(templateObj, "serial");
  if (serialParamObj && serialParamObj->type == aJson_String) store->serialParam = str2SerialParam(serialParamObj->valuestring);
     else store->serialParam = SERIAL_8N1;

  aJsonObject * baudObj=aJson.getObjectItem(templateObj, "baud");
  if (baudObj && baudObj->type == aJson_Int && baudObj->valueint) store->baud = baudObj->valueint;
     else store->baud = 9600;

    #if defined (__SAM3X8E__)
    modbusSerial.begin(store->baud, static_cast <USARTClass::USARTModes> (store->serialParam));
    #elif defined (ARDUINO_ARCH_ESP8266)
    modbusSerial.begin(store->baud, static_cast <SerialConfig>(store->serialParam));
    #elif defined (ARDUINO_ARCH_ESP32)
    modbusSerial.begin(store->baud, (store->serialParam),MODBUS_UART_RX_PIN,MODBUS_UART_TX_PIN);
    #else
    modbusSerial.begin(store->baud, (store->serialParam));
    #endif

  aJsonObject * pollObj=aJson.getObjectItem(templateObj, "poll");
  if (pollObj && pollObj->type == aJson_Object)
    {
      store->pollingRegisters=aJson.getObjectItem(pollObj, "regs");
      store->pollingIrs=aJson.getObjectItem(pollObj, "irs");
      store->pollingCoils=aJson.getObjectItem(pollObj, "coils");
      store->poolingDiscreteIns=aJson.getObjectItem(pollObj, "dins");
      aJsonObject * delayObj= aJson.getObjectItem(pollObj, "delay");
      if (delayObj) store->pollingInterval = delayObj->valueint;
          else store->pollingInterval = 1000;

    }
  else {store->pollingRegisters=NULL;store->pollingInterval = 1000;store->pollingIrs=NULL;}

  store->parameters=aJson.getObjectItem(templateObj, "par");

  // initializing @S where needed
 
  if (store->parameters)
  {
  // Creating for parameters where prefetch required  
  debugSerial<<F("MBUS:")<<F("Adding prefetch regs:")<<endl;
  aJsonObject * i = store->parameters->child;
  while (i)
  {
  aJsonObject *  prefetchObj = aJson.getObjectItem(i, "prefetch");
  if (prefetchObj && prefetchObj->type == aJson_Boolean && prefetchObj->valuebool) 
      {
        createLastMeasured(i->name);
      }
  i=i->next;
  }

  debugSerial<<F("MBUS:")<<F("Adding referred regs:")<<endl;
  i = store->parameters->child;
  // Creating for parameters used in references from another parameters
  while (i)
  {
  aJsonObject *  mapObj = aJson.getObjectItem(i, "map");
  if (mapObj) 
      {
        aJsonObject *  defObj = aJson.getObjectItem(mapObj, "def");
        if (defObj && defObj->type == aJson_Int)
          {
            createLastMeasured(getParamNameByReg(store->parameters,defObj->valueint));
          }

  if (defObj && defObj->type == aJson_String)
          {
            createLastMeasured(defObj->valuestring);
          }        
  }
  i=i->next;
  }
 debugSerial<<F("MBUS:")<<F("Adding regs with actions:")<<endl;
  // Check - if  action configured for object and create
  aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
  if (itemParametersObj)
  {
    i = itemParametersObj->child;
    while (i)
    { 
      if (haveAction(i)) createLastMeasured(i);
      i=i->next;     
    }
  }

  }

  return true;
  //store->addr=item->getArg(0);
  }


/**
 * @brief Создаёт поле для хранения последнего измеренного значения по имени параметра.
 * @param name Имя параметра.
 * @return true, если успешно, иначе false.
 */
int  out_Modbus::createLastMeasured(char * name)
{
if (!name) return false;  
aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
return createLastMeasured(aJson.getObjectItem(itemParametersObj,name));          
}

/**
 * @brief Создаёт поле для хранения последнего измеренного значения по JSON-объекту.
 * @param execObj JSON-объект параметра.
 * @return true, если успешно, иначе false.
 */
int  out_Modbus::createLastMeasured(aJsonObject * execObj)
{
  if (!execObj) return false; 
        
  aJsonObject * markObj = execObj;
    if (execObj->type == aJson_Array) 
      {
      markObj = execObj->child;  
      //storeLastValue = true;
      }
  if (!markObj) return false;

  aJsonObject *lastMeasured = aJson.getObjectItem(markObj,"@S");
  if (lastMeasured) return false;

  debugSerial<<F("MBUS: Add @S: ")<<execObj->name<<endl;
  aJson.addNumberToObject(markObj, "@S", (long) 0);

  lastMeasured = aJson.getObjectItem(markObj,"@S");
  if (!lastMeasured) return false;
  lastMeasured->subtype |= LM_VALUE_EMPTY;
  return true;        
}

/**
 * @brief Получает объект последнего измеренного значения по имени параметра.
 * @param name Имя параметра.
 * @return JSON-объект или NULL.
 */
aJsonObject * out_Modbus::getLastMeasured(char * name)
{
if (!name) return NULL;  
aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
return getLastMeasured (aJson.getObjectItem(itemParametersObj,name));
}

/**
 * @brief Получает объект последнего измеренного значения по JSON-объекту.
 * @param execObj JSON-объект параметра.
 * @return JSON-объект или NULL.
 */
aJsonObject * out_Modbus::getLastMeasured(aJsonObject * execObj)
{
if (!execObj) return NULL;  
if (execObj->type == aJson_Array) execObj = execObj->child; 
aJsonObject *lastMeasured = aJson.getObjectItem(execObj,"@S");
if (lastMeasured && lastMeasured->type == aJson_Int) return lastMeasured;
return NULL;
}

void out_Modbus::setLastMeasured(aJsonObject * lastMeasured, int val)
{
 //aJsonObject *lastMeasured = getLastMeasured (execObj);
 if (lastMeasured)
      {
        lastMeasured->valueint = val;
        //lastMeasured->subtype&=~LM_VALUE_OUTDATED;
        //lastMeasured->subtype&=~LM_VALUE_EMPTY;
        lastMeasured->subtype = 0;

      }

}

/**
 * @brief Инициализирует канал Modbus и загружает конфигурацию.
 * @return 1 при успехе, 0 при ошибке.
 */
int  out_Modbus::Setup()
{
abstractOut::Setup();    
if (!store) store= (mbPersistent *)item->setPersistent(new mbPersistent);
if (!store)
              { errorSerial<<F("MBUS: Out of memory")<<endl;
                return 0;}

store->timestamp=millisNZ();
if (getConfig())
    {
        infoSerial<<F("MBUS: config loaded ")<< item->itemArr->name<<endl;
        setStatus(CST_INITIALIZED);
        return 1;
      }
else
 {  errorSerial<<F("MBUS: config error")<<endl;
    setStatus(CST_FAILED);
    Stop();
    return 0;
  }

}

/**
 * @brief Останавливает работу канала Modbus и освобождает ресурсы.
 * @return 1 при успехе.
 */
int  out_Modbus::Stop()
{
debugSerial.print("MBUS: De-Init ");
debugSerial.println(item->itemArr->name);

if (store) delete store;
item->setPersistent(NULL);
store = NULL;
return 1;
}




/**
 * @brief Читает данные из Modbus-устройства.
 * @param reg Номер регистра.
 * @param regType Тип регистра.
 * @param count Количество регистров.
 * @return true, если чтение успешно, иначе false.
 */
bool readModbus(uint16_t reg, int regType, int count)
{
uint8_t result;
switch (regType) {
    case MODBUS_HOLDING_REG_TYPE:
        result = node.readHoldingRegisters(reg, count);
        break;
    case MODBUS_COIL_REG_TYPE:
        result = node.readCoils(reg, count);
        break;
    case MODBUS_DISCRETE_REG_TYPE:
        result = node.readDiscreteInputs(reg, count);
        break;
    case MODBUS_INPUT_REG_TYPE:
        result = node.readInputRegisters(reg, count);
        break;
    default:
        debugSerial<<F("MBUS: Not supported reg type\n");
        return false;
 }
mbusSlenceTimer = millisNZ(); 
if (result != node.ku8MBSuccess) errorSerial<<F("MBUS: Polling error ")<<_HEX(result)<<endl;
return (result == node.ku8MBSuccess);
}



/**
 * @brief Находит и обрабатывает регистр Modbus, выполняет сопоставление и действия.
 * @param registerNum Номер регистра.
 * @param posInBuffer Позиция в буфере ответа.
 * @param regType Тип регистра.
 * @param registerFrom Начальный регистр диапазона.
 * @param registerTo Конечный регистр диапазона.
 * @param doExecution Выполнять ли действие.
 * @param submitParam Флаг для подавления повторных действий.
 * @return Команда itemCmd с результатом.
 */
itemCmd out_Modbus::findRegister(uint16_t registerNum, uint16_t posInBuffer, uint8_t regType, uint16_t registerFrom, uint16_t registerTo, bool doExecution, bool * submitParam)
{
  aJsonObject * paramObj = store->parameters->child;
  
  bool tmpSubmitParam;  
  if (!submitParam) submitParam=&tmpSubmitParam;  
  *submitParam=true;

  //bool is8bit = false;
  while (paramObj)
          { 
            int8_t       parType = PAR_I16;
            aJsonObject *regObj=NULL;
            switch (regType) {
              case MODBUS_HOLDING_REG_TYPE: 
                regObj = aJson.getObjectItem(paramObj, "reg");
                break;
              case MODBUS_INPUT_REG_TYPE:  
                regObj = aJson.getObjectItem(paramObj, "ir");
                break;
              case MODBUS_COIL_REG_TYPE:  
                regObj = aJson.getObjectItem(paramObj, "coil");  
                parType = PAR_COIL;
                break;
              case MODBUS_DISCRETE_REG_TYPE:  
                regObj = aJson.getObjectItem(paramObj, "din");  
                parType = PAR_COIL;  
            }  

              if (regObj && regObj->valueint ==registerNum)
                    {
                    aJsonObject *idObj = aJson.getObjectItem(paramObj, "id");                 
                    aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
                    uint16_t data = node.getResponseBuffer(posInBuffer);
                    bool executeWithoutCheck=false; //Afler recurrent check, all dublicatess and suppressing checked by recurrent 
                    bool submitRecurrentOut = false; //false if recurrent check find duplicates 
                    char buf[16];

                    uint32_t  param =0;
                    itemCmd   mappedParam;
                    aJsonObject *typeObj = aJson.getObjectItem(paramObj, "type");
                    aJsonObject *mapObj = aJson.getObjectItem(paramObj, "map");

                    if (typeObj && typeObj->type == aJson_String) parType=str2regSize(typeObj->valuestring);
                    switch(parType) {

                      case PAR_I16:
                      //isSigned=true;
                      param=data;
                      mappedParam.Int((int32_t)(int16_t)data);
                      break;

                      case PAR_U16:
                      param=data;
                      mappedParam.Int((uint32_t)data);
                      break;

                      case PAR_I32:
                      //isSigned=true;
                      param = swap(data ) | swap(node.getResponseBuffer(posInBuffer+1)<<16);
                      mappedParam.Int((int32_t)param);
                      break;

                      case PAR_U32:
                      param = swap(data )| swap (node.getResponseBuffer(posInBuffer+1)<<16 );
                      mappedParam.Int((uint32_t)param);
                      break;

                      case PAR_U8L:
                      //is8bit=true;
                      param = data & 0xFF;
                      mappedParam.Int((uint32_t)param);
                      break;

                      case PAR_U8H:
                      //is8bit=true;
                      param = (data & 0xFF00) >> 8;
                      mappedParam.Int((uint32_t)param);
                      break;

                      case PAR_TENS:
                      param=data;
                      mappedParam.Tens((int16_t) data);
                      break;

                      case PAR_100:
                      param=data;
                      mappedParam.Tens_raw((int16_t) data * (TENS_BASE/100));
                      mappedParam.Float((int32_t) (int16_t) data/100.);
                      break;

                      case PAR_COIL:
                      param = (node.getResponseBuffer(posInBuffer/16) >> (posInBuffer % 16)) & 1;
                      mappedParam.Int((uint32_t)param);
                    }
                       
                    traceSerial << F("MBUSD: got ")<<mappedParam.toString(buf,sizeof(buf))<< F(" from type ")<<parType<<F(":")<<paramObj->name<<endl;             

                  if (mapObj && (mapObj->type==aJson_Array || mapObj->type==aJson_Object))
                    {
                       mappedParam=mappedParam.doReverseMapping(mapObj);
                       if (!mappedParam.isCommand() && !mappedParam.isValue())  //Not mapped
                          {  
                          aJsonObject *defMappingObj;
                          defMappingObj = aJson.getObjectItem(mapObj, "def");
                          if (defMappingObj)
                             {
                              switch (defMappingObj->type)
                               {
                                case aJson_Int: //register/coil/.. number
                                  traceSerial<<F("MBUSD: Searching reg#")<<defMappingObj->valueint<<endl; 
                                  if ((defMappingObj->valueint>= registerFrom) && (defMappingObj->valueint<=registerTo))  
                                      {   
                                      mappedParam = findRegister(defMappingObj->valueint,defMappingObj->valueint-registerFrom,regType,registerFrom,registerTo,false,&submitRecurrentOut);
                                      executeWithoutCheck=true;
                                      traceSerial<<"MBUSD: recurrent check res: "<<"SRO:"<<submitRecurrentOut<<endl;
                                      }
                                  else 
                                  {
                                  debugSerial<<F("def reg# ")<<defMappingObj->valueint<<F(" out of range buffer, fetching")<<endl;    
                                  ////to prevent CORRUPTION if using same buffer
                                  uint16_t localBuffer;
                                  node.setResponseBuffer(&localBuffer,1);
                                  if (readModbus(defMappingObj->valueint,regType,1))
                                    {
                                      mappedParam = findRegister(defMappingObj->valueint,0,regType,defMappingObj->valueint,defMappingObj->valueint,false,&submitRecurrentOut);
                                      executeWithoutCheck=true;
                                    }
                                  node.setDefaultResponseBuffer();                                   
                                  }

                                break;
                                case aJson_String: // parameter name
                                 traceSerial<<F("Searching reg: ")<<defMappingObj->valuestring<<endl; 
                                 if (itemParametersObj && itemParametersObj->type ==aJson_Object) 
                                    {      
                                      //Searching item param for nested mapping 
                                      //Retrive previous data
                                            aJsonObject *lastMeasured = getLastMeasured(defMappingObj->valuestring); 
                                            if (lastMeasured) 
                                            {
                                            traceSerial<<F("LastKnown value: ")<<lastMeasured->valueint<<endl; 
                                            //Searching template param for nested mapping
                                            aJsonObject * templateParObj = aJson.getObjectItem(store->parameters,defMappingObj->valuestring);
                                              if (templateParObj)
                                                 {
                                                  int8_t nestedParType = PAR_I16;
                                                  
                                                  aJsonObject * nestedTypeObj = aJson.getObjectItem(templateParObj, "type");
                                                  if (nestedTypeObj && nestedTypeObj->type == aJson_String) parType=str2regSize(nestedTypeObj->valuestring);

                                                  switch(nestedParType) {
                                                      case PAR_I16:
                                                      case PAR_I32:
                                                      mappedParam.Int((int32_t)lastMeasured->valueint);
                                                      break;

                                                      case PAR_U32:
                                                      case PAR_U16:
                                                      case PAR_U8L:
                                                      case PAR_U8H:
                                                      case PAR_COIL:
                                                      mappedParam.Int((uint32_t)lastMeasured->valueint);
                                                      break;

                                                      case PAR_TENS:
                                                      mappedParam.Tens((int16_t) data);
                                                      break;

                                                      case PAR_100:
                                                      mappedParam.Tens_raw((int16_t) lastMeasured->valueint * (TENS_BASE/100));
                                                      mappedParam.Float((int32_t) (int16_t) lastMeasured->valueint/100.);
                                                      break;
                                                      default: errorSerial<<F("Invalid regtype")<<endl;
                                                    }

                                                  aJsonObject * nestedMapObj = aJson.getObjectItem(templateParObj, "map");  
                                                  if (nestedMapObj && (nestedMapObj->type==aJson_Array || nestedMapObj->type==aJson_Object)) mappedParam=mappedParam.doReverseMapping(nestedMapObj);
                                                  traceSerial << F("MBUSD: NestedMapped:")<<mappedParam.toString(buf,sizeof(buf))<<endl; 

                                                  if (!(lastMeasured->subtype & LM_VALUE_OUTDATED))
                                                      {
                                                          executeWithoutCheck=true;
                                                          submitRecurrentOut=true;
                                                          lastMeasured->subtype|= LM_VALUE_OUTDATED;
                                                      }

                                                 } 
                                            } //nested have lastMeasured
                                    }
                                break;
                               }
                             }                          
             }  
                        else   
                           traceSerial << F("MBUSD: Mapped:")<<mappedParam.toString(buf,sizeof(buf))<<endl; 
                    } //mapping       

                    if (doExecution && idObj && idObj->type==aJson_Int) 
                       switch (idObj->valueint)
                        {
                          case S_CMD: 
                          mappedParam.saveItem(item,FLAG_COMMAND);
                            break;
                          case S_SET:
                          mappedParam.saveItem(item,FLAG_PARAMETERS);
                        }

                
                    if (itemParametersObj && itemParametersObj->type ==aJson_Object)
                          {
                          aJsonObject *execObj = aJson.getObjectItem(itemParametersObj,paramObj->name);
                          if (execObj) 
                                      {
                                   //   if (!doExecution || haveAction(execObj)) //if no action in execObj - do not save last value to avoid confuse further recurrent check
                                   //   {   
                                                    //Retrive previous data
                                                    aJsonObject *lastMeasured = getLastMeasured(execObj); 
                                                    if (lastMeasured)
                                                    { 
                                                                if   (lastMeasured->valueint == param && !(lastMeasured->subtype & LM_VALUE_EMPTY))
                                                                      {
                                                                        //if recurrent call but value was readed before
                                                                       if (!doExecution && !(lastMeasured->subtype & LM_VALUE_OUTDATED)) 
                                                                                    {
                                                                                    *submitParam=true; //never used
                                                                                    lastMeasured->subtype|=LM_VALUE_OUTDATED;
                                                                                    return mappedParam;
                                                                                    }
                                                                        *submitParam=false; //supress repeating execution for same val
                                                                      }
                                                                else  
                                                                      {
                                                                      traceSerial<<"MBUS: Stored "<<param<<" to @S of "<<paramObj->name<<endl;
                                                                      setLastMeasured(lastMeasured,param);
                                                                      }
                                                    }            
                                  //    }

                                       if (executeWithoutCheck)
                                            {
                                            
                                            if (doExecution && (submitRecurrentOut || *submitParam)) 
                                                  {
                                                  //debugSerial<<F("MBUS: exec ");mappedParam.debugOut();  
                                                  executeCommand(execObj, -1, mappedParam); 
                                                  *submitParam=true; //if requrrent check has submit smth - report it.  
                                                  }
                                                
                                            return mappedParam;  
                                            }

                                      if (*submitParam && doExecution)                                       
                                      {   
                                      // Compare with last submitted val (if @V NOT marked as NULL in config)
                                       aJsonObject * markObj = execObj;
                                       if (execObj->type == aJson_Array) 
                                            {
                                            markObj = execObj->child;  
                                            //storeLastValue = true;
                                            }
                                       aJsonObject *settedValue = aJson.getObjectItem(markObj,"@V");                      
                                       if (settedValue && settedValue->type==aJson_Int && (settedValue->valueint == param))
                                          {    
                                           traceSerial<<F("MBUSD: Ignored - equal with setted val")<<endl;
                                           *submitParam=false;
                                          }  
                                        else 
                                          {
                                          if (doExecution) 
                                                    {
                                                    //debugSerial<<F("MBUS: exec ");mappedParam.debugOut();    
                                                    executeCommand(execObj, -1, mappedParam);
                                                    }
                                          // if param updated by device and no new value queued to send - update @V to avoid "Ignored - equal with setted val" 
                                          if (settedValue && !(execObj->subtype & MB_NEED_SEND)) 
                                              settedValue->valueint=param;
                                          }
                                      } //to be executed
                                      } //ExecObj
                          } //item Parameters
                     
                    return mappedParam;
                    } //reg == regNum
            paramObj=paramObj->next;
          } //while
return itemCmd();
}


/**
 * @brief Опрос Modbus-устройства по списку регистров.
 * @param reg JSON-объект с регистрами.
 * @param regType Тип регистра.
 */
void out_Modbus::pollModbus(aJsonObject * reg, int regType)
    {
    if (!reg) return;
    reg=reg->child;  
    while (reg)
            {
            switch (reg->type)
              {
                case aJson_Int:
                {
                int registerNum = reg->valueint; 
                if (readModbus(registerNum,regType,1))
                  {
                    findRegister(registerNum,0,regType,registerNum,registerNum);
                  }
                }
                break;
                case aJson_Array:
                if (aJson.getArraySize(reg)==2)
                {
                  int registerFrom=aJson.getArrayItem(reg, 0)->valueint;
                  int registerTo=aJson.getArrayItem(reg, 1)->valueint;

                  if (readModbus(registerFrom,regType,registerTo-registerFrom+1))
                    { traceSerial<<endl;
                      for(int i=registerFrom;i<=registerTo;i++)
                        {
                          findRegister(i,i-registerFrom,regType,registerFrom,registerTo);
                        }
                    }

                }

              }
            reg = reg->next;
            }
    }

/**
 * @brief Инициализирует линию связи Modbus.
 */
void out_Modbus::initLine()
{
//store->serialParam=(USARTClass::USARTModes) SERIAL_8N1;
    #if defined (__SAM3X8E__)
    modbusSerial.begin(store->baud, static_cast <USARTClass::USARTModes> (store->serialParam));
    #elif defined (ARDUINO_ARCH_ESP8266)
    modbusSerial.begin(store->baud, static_cast <SerialConfig>(store->serialParam));
    #elif defined (ESP32)
    //modbusSerial.begin(store->baud, store->serialParam);
    //delay(100);
    modbusSerial.updateBaudRate (store->baud); //Some terrible error in ESP32 core with uart reinit
    #else
    modbusSerial.begin(store->baud, (store->serialParam));
    #endif
    //debugSerial<< store->baud << F("---")<< store->serialParam<<endl;
    node.begin(item->getArg(0), modbusSerial);
}

/**
 * @brief Отправляет значение в Modbus-устройство.
 * @param paramName Имя параметра.
 * @param outValue JSON-объект с отправляемым значением.
 * @return 0 при успехе, отрицательное значение при ошибке.
 */
int out_Modbus::sendModbus(char * paramName, aJsonObject * outValue)
{
 if (!store) {errorSerial<<F(" internal send error - no store")<<endl; return -1;}

 aJsonObject * templateParamObj = aJson.getObjectItem(store->parameters, paramName);
 if (!templateParamObj) {errorSerial<<F(" internal send error - no template")<<endl; return -1;}

 aJsonObject * regObj = aJson.getObjectItem(templateParamObj, "reg");

 if (!regObj)
    {  
    regObj = aJson.getObjectItem(templateParamObj, "coil");
    if (!regObj) {errorSerial<<F(" internal send error - no reg/coil")<<endl; return -2;}
       else outValue->subtype = PAR_COIL;
    } 
 if (regObj->type != aJson_Int) {errorSerial<<F(" Reg/coil must be int")<<endl; return -2;}   

 aJsonObject * prefetchObj = aJson.getObjectItem(templateParamObj, "prefetch");
 aJsonObject *lastMeasured =  NULL;

 int res = -1;

if (prefetchObj && (prefetchObj->type == aJson_Boolean) && prefetchObj->valuebool) 
                                      {
                                                      int modbusRegType =  (outValue->subtype == PAR_COIL) ? MODBUS_COIL_REG_TYPE:MODBUS_HOLDING_REG_TYPE;
                                                      debugSerial<<F(" prefetch ")<<paramName<<F(" #") <<regObj->valueint << " type:" << modbusRegType << " "; 

                                                      /// to prevent CORRUPTIOIN if using same buffer
                                                      uint16_t localBuffer;
                                                      node.setResponseBuffer(&localBuffer,1);

                                                      bool successRead = readModbus(regObj->valueint,modbusRegType,1);
                                                      mbusSlenceTimer = millisNZ();   
                                                      

                                                      if (successRead)
                                                        {
                                                          #ifdef PREFETCH_EXEC_IMMEDIALELLY
                                                          // option to execute if changed immediatelly
                                                          bool submited = false;
                                                          findRegister(regObj->valueint,0,modbusRegType,regObj->valueint,regObj->valueint,true,&submited);
                                                          node.setDefaultResponseBuffer();

                                                          if (submited)
                                                                {
                                                                  debugSerial << F("MBUS:")<<paramName<< (" val changed. Write cancelled")<<endl;
                                                                  return -3;
                                                                }
                                                           else   debugSerial << F("MBUS:")<<paramName<< F(" val not changed. Continue")<<endl;  
                                                           
                                                          
                                                          #else
                                                          // Option to skip writing if change, let next polling take of change           
                                                          aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
                                                          if (itemParametersObj && itemParametersObj->type ==aJson_Object)
                                                                                    {
                                                                                    aJsonObject *execObj = aJson.getObjectItem(itemParametersObj,paramName);
                                                                                    if (execObj) 
                                                                                                {
                                                                                                //aJsonObject * markObj = execObj;
                                                                                                //if (execObj->type == aJson_Array) markObj = execObj->child;  
                                                                                                //Retrive previous data
                                                                                                lastMeasured = getLastMeasured(execObj);// aJson.getObjectItem(markObj,"@S");
                                                                                                if (lastMeasured)
                                                                                                { 
                                                                                                          //if   (lastMeasured->type == aJson_Int)
                                                                                                           // {
                                                                                                            traceSerial<<F(" Last:")<<lastMeasured->valueint<< F(" Now:") << localBuffer<<endl;

                                                                                                            if   (lastMeasured->valueint != localBuffer)
                                                                                                                  {
                                                                                                                    debugSerial << F("MBUS:")<<paramName<< F(" val changed.")<<endl;
                                                                                                                    node.setDefaultResponseBuffer();
                                                                                                                    return -3;
                                                                                                                  }
                                                                                                            else  
                                                                                                                  {
                                                                                                                  if (outValue->valueint == localBuffer)
                                                                                                                  {
                                                                                                                    debugSerial << F("MBUS:")<<paramName<< F("=")<<localBuffer<<F(": equal targert.")<<endl;
                                                                                                                    node.setDefaultResponseBuffer();
                                                                                                                    return -4;
                                                                                                                  }

                                                                                                                  debugSerial << F("MBUS:")<<paramName<< F(" val not changed. Continue")<<endl;  
                                                                                                                  }
                                                                                                            //}
                                                                                                }            
                                                                                                }
                                                                                    }
                                                        #endif  
                                                       }
                                                       node.setDefaultResponseBuffer();
                                                      

                                      }


                    switch(outValue->subtype) {
                      case PAR_U16:
                      case PAR_I16:  
                      case PAR_TENS:
                      case PAR_100:
                      res = node.writeSingleRegister(regObj->valueint,outValue->valueint);
                      break;
            
                      break;
                      case PAR_I32:
                      case PAR_U32:
                      res = node.writeSingleRegister(regObj->valueint,swap(outValue->valueint & 0xFFFF));
                      res += node.writeSingleRegister(regObj->valueint+1,swap(outValue->valueint >> 16)) ;
                      break;

                      case PAR_U8L:
                      case PAR_I8L:
                      res = node.writeSingleRegister(regObj->valueint,outValue->valueint & 0xFF);                 
                      break;

                      case PAR_U8H:
                      case PAR_I8H:
                      res = node.writeSingleRegister(regObj->valueint,(outValue->valueint & 0xFFFF)>> 8);
                      break;
                      case PAR_COIL:
                      res = node.writeSingleCoil (regObj->valueint,outValue->valueint);
                      break;
                    }
 mbusSlenceTimer = millisNZ();                   
 debugSerial<<F("MBUS res: ")<<res<<F(" ")<<paramName<<" reg:"<<regObj->valueint<<F(" val:")<<outValue->valueint<<endl;

//If wrote - suppress action on poll 
if ((res ==0) && (outValue->type == aJson_Int) && lastMeasured && (lastMeasured->type == aJson_Int)) setLastMeasured(lastMeasured,outValue->valueint);


 return ( res == 0);
}

/**
 * @brief Осуществляет опрос и отправку команд Modbus.
 * @param cause Причина вызова (например, медленный опрос).
 * @return Интервал следующего опроса.
 */
int out_Modbus::Poll(short cause)
{
if (cause==POLLING_SLOW) return 0;  
bool lineInitialized = false;  

if (modbusBusy || (Status() != CST_INITIALIZED) || ( mbusSlenceTimer && !isTimeOver(mbusSlenceTimer,millis(),200))) return 0;

aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
if (itemParametersObj && itemParametersObj->type ==aJson_Object)
           {
            aJsonObject *execObj = itemParametersObj->child; 
            bool onceSendOk=false;
            while (execObj && ((execObj->type == aJson_Object) || (execObj->type == aJson_Array)) && !onceSendOk)
                          {     
                             
                                 if ((execObj->subtype & MB_NEED_SEND) && !(execObj->subtype & MB_SEND_ERROR))
                                 {

                                aJsonObject * markObj = execObj;
                                if (execObj->type == aJson_Array) markObj = execObj->child; 

                                 aJsonObject *outValue = aJson.getObjectItem(markObj,"@V");                      
                                      if (outValue)
                                          {    
                                                modbusBusy=1;
                                                if (!lineInitialized)
                                                    {
                                                      lineInitialized=true;  
                                                      initLine();
                                                    }

                                              int sendRes;
                                              int savedValue;
                                              bool needResend;
                                              do
                                                  {
                                                  savedValue = outValue->valueint;  
                                                  debugSerial<<"MBUS: SEND "<<item->itemArr->name<<"/"<<execObj->name<<"="<<outValue->valueint<<endl;   
                                                  sendRes = sendModbus(execObj->name,outValue);
                                                  needResend = (savedValue != outValue->valueint);
                                                  while(needResend && mbusSlenceTimer && !isTimeOver(mbusSlenceTimer,millis(),200)) modbusIdle();
                                                  }
                                              while (needResend); //repeat sending if target value changed while we're waited for mbus responce

                                              switch (sendRes) 
                                              {
                                               case 1: //success
                                               case -4: //equal tatget
                                                 //execObj->subtype&=~ MB_NEED_SEND;
                                                 execObj->subtype = 0;
                                                 onceSendOk=true;
                                                 ///return 1; //relax
                                                 break;
                                               case 0: //fault 
                                                 execObj->subtype |= MB_SEND_ERROR;  
                                                 errorSerial<<F("MBUS:  ")<<item->itemArr->name<<"/"<<execObj->name<<F(" send error. ");
                                                 if ((execObj->subtype & 3) != MB_SEND_ATTEMPTS) execObj->subtype++;
                                                 errorSerial<<F("MBUS:  ")<<item->itemArr->name<<"/"<<execObj->name<<" Attempt: "<< (execObj->subtype & 3) <<endl;
                                                 break;
                                               case -3:
                                                 errorSerial<<F("MBUS:  param ")<<item->itemArr->name<<"/"<<execObj->name<<F(" sending cancelled")<<endl;
                                                 //outValue->valueint=
                                                 //execObj->subtype&=~ MB_NEED_SEND; 
                                                 execObj->subtype = 0;
                                                 break;                                              
                                               default: //param not found
                                                 errorSerial<<F("MBUS:  param ")<<item->itemArr->name<<"/"<<execObj->name<<F(" not found")<<endl;
                                                 execObj->subtype&=~ MB_NEED_SEND;
                                               }    
                                          }
                                  }   
                            execObj=execObj->next;          
                           }
           modbusBusy=0;                
           }
  

if (isTimeOver(store->timestamp,millis(),store->pollingInterval) && ( !mbusSlenceTimer || isTimeOver(mbusSlenceTimer,millis(),200)))
{

// Clean_up FLAG_SEND_ERROR flag 
if (itemParametersObj && itemParametersObj->type ==aJson_Object)
           {
            aJsonObject *execObj = itemParametersObj->child; 
            while (execObj && ((execObj->type == aJson_Object) || (execObj->type == aJson_Array)))
                          {     
                                 if (execObj->subtype & MB_SEND_ERROR) execObj->subtype&=~ MB_SEND_ERROR;
                                 if ((execObj->subtype & 0x3) >= MB_SEND_ATTEMPTS) 
                                                                        {
                                                                        //execObj->subtype&=~ MB_NEED_SEND;
                                                                        //Clean ERROR, NEED, Attempts
                                                                        errorSerial<<"MBUS: send failed "<<item->itemArr->name<<"/"<<execObj->name<<endl;
                                                                        execObj->subtype = 0;
                                                                        }

                                 execObj=execObj->next;
                          }     
           }                

// if some polling configured
if (store->pollingRegisters || store->pollingIrs || store->pollingCoils || store->poolingDiscreteIns)
  {
    traceSerial<<F("MBUSD: Poll ")<< item->itemArr->name << endl;
    modbusBusy=1;

    if (!lineInitialized)
    {
    lineInitialized=true;  
    initLine();
    }

    pollModbus(store->pollingRegisters,MODBUS_HOLDING_REG_TYPE);
    pollModbus(store->pollingIrs,MODBUS_INPUT_REG_TYPE);
    pollModbus(store->pollingCoils,MODBUS_COIL_REG_TYPE);  
    pollModbus(store->poolingDiscreteIns ,MODBUS_DISCRETE_REG_TYPE);  
    traceSerial<<F("MBUSD: endPoll ")<< item->itemArr->name << endl;

  //Non blocking waiting to release line
  uint32_t time = millis();
  while (!isTimeOver(time,millis(),50))
             modbusIdle();

  modbusBusy =0;
  }
 store->timestamp=millisNZ();  
}

return store->pollingInterval;
};

/**
 * @brief Возвращает тип канала.
 * @return CH_MBUS.
 */
int out_Modbus::getChanType()
{
   return CH_MBUS;
}

/**
 * @brief Отправляет команду itemCmd в Modbus по шаблону параметра.
 * @param templateParamObj JSON-объект шаблона параметра.
 * @param cmd Команда itemCmd.
 * @return 1 при успехе, 0 при ошибке.
 */
int out_Modbus::sendItemCmd(aJsonObject *templateParamObj, itemCmd cmd)
{
if (templateParamObj)
{
int suffixCode = cmd.getSuffix(); 
//bool isCommand = cmd.isCommand();
if (!suffixCode) return 0;

char *suffixStr =templateParamObj->name;   
// We have find template for suffix or suffixCode
itemCmd  cmdValue =  itemCmd(ST_VOID,CMD_VOID);
long Value = 0;
int8_t    regType = PAR_I16;
aJsonObject * typeObj = aJson.getObjectItem(templateParamObj, "type");
aJsonObject * mapObj = aJson.getObjectItem(templateParamObj, "map");
aJsonObject * sendchangesObj = aJson.getObjectItem(templateParamObj, "sendchanges");


                    if (typeObj && typeObj->type == aJson_String) regType=str2regSize(typeObj->valuestring);
                    switch(regType) {
                      case PAR_I16:
                      case PAR_U16:
                      case PAR_I32:
                      case PAR_U32:
                      case PAR_U8L:
                      case PAR_U8H: 
                      case PAR_I8H:
                      case PAR_I8L:
                     
                        cmdValue=cmd.doMapping(mapObj);
                        if (!cmdValue.isValue())return 0;
                        Value=cmdValue.getInt();
                      break;
                      case PAR_TENS:
                        cmdValue=cmd.doMapping(mapObj);
                        if (!cmdValue.isValue())return 0;
                        Value=cmdValue.getTens();
                      break;  
                      case PAR_100:  
                        cmdValue=cmd.doMapping(mapObj);
                        if (!cmdValue.isValue())return 0;
                        Value=cmdValue.getTens_raw()*(100/TENS_BASE);
                    }

traceSerial<<F("MBUSD: suffix:")<<suffixStr<< F(" Val: ")<<Value<<endl;
aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
if (itemParametersObj && itemParametersObj->type ==aJson_Object)
           {
            aJsonObject *execObj =  aJson.getObjectItem(itemParametersObj,suffixStr); 
            if (execObj && ((execObj->type == aJson_Object) || (execObj->type == aJson_Array)))
                          {   

                                      
                                      aJsonObject * markObj = execObj;
                                      if (execObj->type == aJson_Array) markObj = execObj->child;
                                       aJsonObject *outValue = aJson.getObjectItem(markObj,"@V");  
                                       aJsonObject *lastMeasured = getLastMeasured(markObj);
                                       //Todo - check if already good value
                                       if (outValue && sendchangesObj && sendchangesObj->type == aJson_Boolean && sendchangesObj->valuebool)
                                       {
                                        if (lastMeasured)
                                           {
                                           if (lastMeasured->valueint == Value &&  !(lastMeasured->subtype))
                                            {
                                              debugSerial<<"MBUS: Value2send equal retrieved"<<endl;
                                              return 1; 
                                            }
                                           }
                                        else if ((outValue->valueint == Value) && (outValue->subtype == (regType & 0xF)))
                                            {
                                              debugSerial<<"MBUS: Value2send equal prev sent"<<endl;
                                              return 1;
                                            }

                                       }


                                       //Schedule update
                                        execObj->subtype |= MB_NEED_SEND;

                                                            
                                        if (outValue) // Existant. Preserve original @type
                                            {    
                                            outValue->valueint=Value;
                                            outValue->subtype =regType & 0xF; 
                                            }
                                        else //No container to store value yet 
                                        // If no  @V in config - creating with INT type - normal behavior - no supress in-to-out 
                                            {
                                              debugSerial<<F("Add @V: ")<<execObj->name<<endl;
                                              aJson.addNumberToObject(markObj, "@V", Value);
                                              outValue = aJson.getObjectItem(markObj,"@V");  
                                              if (outValue) outValue->subtype =regType & 0xF; 
                                            }
/* Conflict with pre-fetching
                                        aJsonObject *polledValue = aJson.getObjectItem(markObj,"@S"); 
                                        if (polledValue && outValue->type == aJson_Int) 
                                                                {
                                                                traceSerial<<"MBUS: not Stored "<<Value<<" to @S of "<<item->itemArr->name<<":"<<templateParamObj->name<<endl;  
                                                                polledValue->valueint=Value; //to pevent suppressing to change back to previously polled value if this occurs before next polling
                                                                polledValue->subtype&=~MB_VALUE_OUTDATED;
                                                                }
     */                                                           

                                                
                           }  
           }
return 1;  
}
else return 0;
}

//!Control unified Modbus item  
// Priority of selection sub-items control to:
// 1. if defined standard suffix Code inside cmd
// 2. custom textual subItem
// 3. non-standard numeric  suffix Code equal param id

/**
 * @brief Унифицированное управление Modbus-каналом.
 * @param cmd Команда itemCmd.
 * @param subItem Имя подэлемента.
 * @param toExecute Выполнять ли команду.
 * @param authorized Авторизовано ли выполнение.
 * @return Результат выполнения.
 */
int out_Modbus::Ctrl(itemCmd cmd,   char* subItem, bool toExecute,bool authorized)
{
if (!store) return -1;

int          suffixCode = cmd.getSuffix();
aJsonObject *templateParamObj = NULL;
int          res = -1;

// trying to find parameter in template with name == subItem (NB!! standard suffixes dint working here)
if (subItem && strlen (subItem) && store && store->parameters) 
      
      {
      templateParamObj = aJson.getObjectItem(store->parameters, subItem);
      res= sendItemCmd(templateParamObj,cmd);
      }
else

// No subitem, trying to find suffix with root item  - (Trying to find template parameter where id == suffixCode)
      {
      if (store && store->parameters) templateParamObj = store->parameters->child;
      bool suffixFinded = false;
        while (templateParamObj)
                {
                  aJsonObject *idObj = aJson.getObjectItem(templateParamObj, "id");
                  if (idObj && idObj->type==aJson_Int && idObj->valueint == suffixCode) 
                                  {
                                    res= sendItemCmd(templateParamObj,cmd);
                                    suffixFinded = true;
                                  }
                  templateParamObj=templateParamObj->next;                       
                }
         if (!suffixFinded) errorSerial<<F("MBUS: No template for ")<<subItem<<F(" or suffix ")<<suffixCode<<endl;                       
      } 
return res;          
}


#endif
