#include "ltr43api.h"
#include <stdio.h> 
#include <stdlib.h>
#include <string.h>

#include "ltrmodule.h"
#include "ltr4x.h"


#define RS485_FRAME_CFG_START_DATA                     0x0
#define RS485_FRAME_CFG_SEND_SIZE                     0x10
#define RS485_FRAME_CFG_RECV_SIZE                     0x20
#define RS485_FRAME_CFG_DATA                          0x30


#define LTR43_CMD_BUFFER_SIZE 15

#define ERR_ACK_DATA                                  0
#define ERR_ACK_UNSUP_CMD                             1
#define ERR_ACK_INVALID_PARAM                         2

#define LTR43_STOP_TOUT            5000

#define LTR43_TX_ACTIVE_INTERVAL_MAX                 65750UL

#define LTR43_PORT_DIR_VALID(dir) ((dir == LTR43_PORT_DIR_IN) || (dir==LTR43_PORT_DIR_OUT))

#define IS_RS_EXTFRAME_SUPPORTED(priv)  (priv ? priv->ver >= 0x106 ? 1 : 0 : 0)

#ifdef _WIN32
BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    return TRUE;
}
#endif

typedef struct {
    WORD ver;
    WORD resp_tout;
    DWORD interval_tout;
} t_ltr43_priv;

static int f_rs485_config(PTLTR43 hnd);


/*******************************************************************************
      Функция заполнения структуры описания модуля значениями по умолчанию
      и инициализации интерфейсного канала
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_Init(PTLTR43 hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        memset(hnd, 0, sizeof(*hnd));
        hnd->size = sizeof(TLTR43);

        // По умолчания все линии ввода-вывода уст. на вход
        hnd->IO_Ports.Port1 = LTR43_PORT_DIR_IN;
        hnd->IO_Ports.Port2 = LTR43_PORT_DIR_IN;
        hnd->IO_Ports.Port3 = LTR43_PORT_DIR_IN;
        hnd->IO_Ports.Port4 = LTR43_PORT_DIR_IN;

        // По умолчанию частота выдачи данных при потоковом сборе 15 кГц
        hnd->StreamReadRate = 15000;

        // RS485
        hnd->RS485.FrameSize = 8;
        hnd->RS485.Baud = 4800;
        hnd->RS485.StopBit = 0;  // 1 стоп-бит
        hnd->RS485.Parity = 0;   // Проверка на четность выключена

        // Инициализация множителей таймаутов
        hnd->RS485.SendTimeoutMultiplier = 10;
        hnd->RS485.ReceiveTimeoutMultiplier = 10;

        hnd->Marks.SecondMark_Mode = LTR43_MARK_MODE_INTERNAL;
        hnd->Marks.StartMark_Mode  = LTR43_MARK_MODE_INTERNAL;

        err = LTR_Init(&hnd->Channel); // Инициализируем интерефейсный канал

        strcpy(hnd->ModuleInfo.Name, MODULE_DEFAULT_NAME);
    }
    return err;
}



/*******************************************************************************
   Функция проверки: не открыт ли канал
*******************************************************************************/
LTR43API_DllExport(INT) LTR43_IsOpened(PTLTR43 hnd) {
    return hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}

/*******************************************************************************
   Функция создания и открытия канала связи с LTR43. Также RESET модуля
*******************************************************************************/


LTR43API_DllExport (INT) LTR43_Open(PTLTR43 hnd, DWORD net_addr, WORD net_port,
                                    const CHAR *crate_sn, INT slot_num) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        WORD ver;
        err = LTR4X_Open(&hnd->Channel, net_addr, net_port, crate_sn, slot_num,
                         &hnd->ModuleInfo, &ver);
        if (err == LTR_OK) {
            /* выделяем приватный буфер, чтобы иметь возможность
               сохранить доп. информацию, не нарушая бинарную совместимость
               со старыми версиями библиотеки */
            err = LTR_PrivateBufAlloc(&hnd->Channel, 0, sizeof(t_ltr43_priv), 0);
            if (err == LTR_OK) {
                t_ltr43_priv *priv = LTR_PrivateBufGet(&hnd->Channel,0);
                if (priv != NULL) {
                    priv->ver = ver;
                    priv->interval_tout = 0;
                    priv->resp_tout = 0;
                }
            }
        }
    }
    return err;
}


/*******************************************************************************
               Функция окончания работы с модулем
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_Close(PTLTR43 hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        INT stop_err;
        stop_err = LTR_Close(&hnd->Channel);
        if (err == LTR_OK)
            err = stop_err;
    }
    return err;
}

LTR43API_DllExport (INT) LTR43_GetConfig(TLTR43 *hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR4X_GetConfig(&hnd->Channel, &hnd->ModuleInfo, NULL);
    }
    return err;
}

/**************************************************************************************
                 Функция конфигурирования модуля
**************************************************************************************/
LTR43API_DllExport (INT) LTR43_Config(PTLTR43 hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        if((hnd->Marks.StartMark_Mode < 0) || (hnd->Marks.StartMark_Mode > 2)) {
            err = LTR43_ERR_WRONG_START_MARK_CONF;
        } else if ((hnd->Marks.SecondMark_Mode < 0) || (hnd->Marks.StartMark_Mode > 2)) {
            err = LTR43_ERR_WRONG_SECOND_MARK_CONF;
        } else if ((hnd->StreamReadRate < LTR43_STREAM_READ_RATE_MIN) ||
                   (hnd->StreamReadRate > LTR43_STREAM_READ_RATE_MAX)) {
            err = LTR43_ERR_WRONG_STREAM_READ_FREQ_SETTINGS;
        } else if (!LTR43_PORT_DIR_VALID(hnd->IO_Ports.Port1) ||
                  !LTR43_PORT_DIR_VALID(hnd->IO_Ports.Port2) ||
                  !LTR43_PORT_DIR_VALID(hnd->IO_Ports.Port3) ||
                  !LTR43_PORT_DIR_VALID(hnd->IO_Ports.Port4)) {
            err = LTR43_ERR_WRONG_IO_LINES_CONF;
        }
    }

    if (err == LTR_OK) {
        DWORD command[2], command_ack[2];
        WORD Prescaler;
        BYTE PrescalerCode;
        BYTE Scaler;
        /* Слово определяет конфигурацию линий ввода вывода */
        BYTE Output_Groups = (hnd->IO_Ports.Port1) |
                ((hnd->IO_Ports.Port2) << 1) |
                ((hnd->IO_Ports.Port3) << 2) |
                ((hnd->IO_Ports.Port4) << 3);



        LTR4X_EvalScales(&hnd->StreamReadRate, &Prescaler, &PrescalerCode, &Scaler);
        command[0] = ltr_module_fill_cmd_parity(CONFIG, (hnd->Marks.StartMark_Mode << 12) |
                                               (hnd->Marks.SecondMark_Mode << 8) |
                                               Output_Groups);
        command[1] = ltr_module_fill_cmd_parity(CONFIG_READ_RATE, (Scaler << 8) | PrescalerCode);

        err = LTR4X_SendCmdWithAck(&hnd->Channel, command, command_ack,
                                   sizeof(command)/sizeof(command[0]));
    }

    // Выполняем конфигурацию RS485
    if (err == LTR_OK) {
        err = f_rs485_config(hnd);
    }
    return err;
}

LTR43API_DllExport (INT) LTR43_CalcStreamReadFreq(double freq, double *resultFreq) {
    WORD Prescaler;
    BYTE PrescalerCode;
    BYTE Scaler;
    INT err;

    if (freq < LTR43_STREAM_READ_RATE_MIN) {
        freq = LTR43_STREAM_READ_RATE_MIN;
    }
    if (freq > LTR43_STREAM_READ_RATE_MAX) {
        freq = LTR43_STREAM_READ_RATE_MAX;
    }

    err = LTR4X_EvalScales(&freq, &Prescaler, &PrescalerCode, &Scaler);
    if ((err == LTR_OK) && (resultFreq != NULL)) {
        *resultFreq = freq;
    }
    return err;
}


LTR43API_DllExport (INT) LTR43_WritePortRequest(TLTR43 *hnd, DWORD OutputData) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD data[4];

        /* Посылаем команду 4 пакета данных, т.е. две пары одинаковых пакетов.
           Каждая пара определяет 32-битное слово */
        /** @todo Почему посылаем две копии???? и при этом еще и ответ только один!
            как вообще AVR различает первую и вторую копию???? */
        data[0] = (OutputData & 0xFFFF0000) | 1; // Первым отправляем пакет со старшими 2-мя байтами
        data[1] = (OutputData & 0x0000FFFF) << 16; // Затем пакет с младшими 2-мя байтами

        data[2] = (OutputData & 0xFFFF0000) | 1; // Первым отправляем пакет со старшими 2-мя байтами
        data[3] = (OutputData & 0x0000FFFF) << 16; // Затем пакет с младшими 2-мя байтами

        err = ltr_module_send_cmd(&hnd->Channel, data, sizeof(data)/sizeof(data[0]));
    }
    return err;
}

LTR43API_DllExport (INT) LTR43_WritePortResponseWait(TLTR43 *hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD command_ack;
        err = ltr_module_recv_cmd_resp(&hnd->Channel, &command_ack, 1);
        if (err==LTR_OK) {
            err = LTR4X_CheckAck(command_ack, DATA_OUTPUT_CONFIRM);
        }
    }
    return err;
}

/*******************************************************************************
    Функция отправки 32-битного слова в выходной порт модуля
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_WritePort(PTLTR43 hnd, DWORD OutputData) {
    INT err = LTR43_WritePortRequest(hnd, OutputData);
    if (err == LTR_OK) {
        err = LTR43_WritePortResponseWait(hnd);
    }
    return err;
}


LTR43API_DllExport (INT) LTR43_ReadPortRequest(TLTR43 *hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD command;
        command = ltr_module_fill_cmd_parity(READ_WORD, 0);
        err = ltr_module_send_cmd(&hnd->Channel, &command, 1);
    }
    return err;
}

LTR43API_DllExport (INT) LTR43_ReadPortResponseWait(TLTR43 *hnd, DWORD *InputData) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD ack[2], i;
        err = ltr_module_recv_cmd_resp(&hnd->Channel, ack, sizeof(ack)/sizeof(ack[0]));

        for (i = 0; (i < sizeof(ack)/sizeof(ack[0])) && (err == LTR_OK); ++i) {
            if((ack[i] & 0x8000))
                err = LTR43_ERR_CANT_READ_DATA;
        }

        if (err == LTR_OK) {
            if ((((ack[1] & 0xFF) - (ack[0] & 0xFF)) != 1) &&
                (((ack[0] & 0xFF) != 255) && ((ack[1] & 0xFF) != 0))) {
                err = LTR43_ERR_CANT_READ_DATA;
            }
        }

        if ((err == LTR_OK) && (InputData != NULL)) {
            *InputData = ((ack[0] & 0xFFFF0000) | (ack[1] >> 16));
        }
    }
    return err;
}


/*******************************************************************************
        Функция чтения 32-битного слова из входного порта модуля
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_ReadPort(PTLTR43 hnd, DWORD *InputData) {
    INT err = LTR43_ReadPortRequest(hnd);
    if (err == LTR_OK) {
        err = LTR43_ReadPortResponseWait(hnd, InputData);
    }
    return err;
}


/****************************************************************************************
   Функция посылки массива данных в модуль
****************************************************************************************/   
LTR43API_DllExport (INT) LTR43_WriteArray(PTLTR43 hnd, DWORD *OutputArray, BYTE ArraySize) {
    INT err = ((OutputArray == NULL) || (ArraySize == 0)) ? LTR_ERROR_PARAMETERS : LTR43_IsOpened(hnd);
    INT i;

    for (i=0; (i < ArraySize) && (err == LTR_OK); ++i) {
        DWORD data[4];
        /** @note Здесь еще и не было |1 для 0-го и 2-го слова - wtf??? */
        data[0] = (*OutputArray & 0xFFFF0000) | 1; // Первым отправляем пакет со старшими 2-мя байтами
        data[1] = (*OutputArray & 0x0000FFFF) << 16; // Затем пакет с младшими 2-мя байтами
        data[2] = (*OutputArray & 0xFFFF0000) | 1; // Первым отправляем пакет со старшими 2-мя байтами
        data[3] = (*OutputArray & 0x0000FFFF) << 16; // Затем пакет с младшими 2-мя байтами

        OutputArray++;
        /** @todo смотри LTR42_WriteArray*/
        err = ltr_module_send_cmd(&hnd->Channel, data, sizeof(data)/sizeof(data[0]));
    }

    if (err == LTR_OK) {
        /** @note В оригинале был прием только одного ответа!!! wtf???? */
        for (i = 0; (i < ArraySize) && (err == LTR_OK); ++i) {
            DWORD command_ack;
            err = ltr_module_recv_cmd_resp(&hnd->Channel, &command_ack, 1);
            if (err == LTR_OK) {
                err = LTR4X_CheckAck(command_ack, DATA_OUTPUT_CONFIRM);
            }
        }
    }
    return err;
}


/*******************************************************************************
   Функция старта потокового чтения данных из модуля
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_StartStreamRead(PTLTR43 hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD command, ack;
        command = ltr_module_fill_cmd_parity(START_STREAM_READ, 0);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &ack, 1);
    }
    return err;
}



/*******************************************************************************
   Функция получения массива входных входных данных при непрерывном вводе
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_Recv(PTLTR43 hnd, DWORD *data, DWORD *tmark,
                                    DWORD size, DWORD timeout) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR_Recv(&(hnd->Channel), data, tmark, size, timeout);
        if ((err >= 0) && (hnd->Channel.flags & LTR_FLAG_RBUF_OVF)) {
            err = LTR_ERROR_RECV_OVERFLOW;
        }
    }
    return err;
}


/*******************************************************************************
   Функция проверки входных данных при непрерывном вводе и
   формирования 32-битных слов
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_ProcessData(PTLTR43 hnd, const DWORD *src, DWORD *dest, DWORD *size) {
    DWORD i = 0, j = 0;
    INT err = ((src == NULL) || (size == NULL) || (*size & 1)) ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        DWORD proc_size = *size;
        DWORD done_size = 0;
        for (i = 0; (i < proc_size) && (err == LTR_OK); i += 2) {
            if((src[i] & 0x8000) || (src[i + 1] & 0x8000)) {
                err = LTR43_ERR_WRONG_IO_DATA;
            } else if ((((src[i + 1] & 0xFF) - (src[i] & 0xFF)) != 1) &&
                       (((src[i] & 0xFF) != 255) && ((src[i + 1] & 0xFF) != 0))) {
                err = LTR43_ERR_WRONG_IO_DATA;
            }

            if ((err == LTR_OK) && (dest != NULL)) {
                *dest++ = (src[i] & 0xFFFF0000) | (src[i + 1] >> 16);
                ++done_size;
            }
        }
        *size = done_size;
    }
    return err;
}

/*******************************************************************************
   Функция остановки потокового чтения данных из модуля
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_StopStreamRead(PTLTR43 hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR4X_StopStreamRead(&hnd->Channel);
    }
    return err;
}


/*******************************************************************************
                Функция включения генерации секундной метки
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_StartSecondMark(PTLTR43 hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD command, ack;
        command = ltr_module_fill_cmd_parity(START_SECOND_MARK, 0);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &ack, 1);
    }
    return err;
}

/*******************************************************************************
                Функция вЫключения генерации внутренней секундной метки
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_StopSecondMark(PTLTR43 hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD command, ack;
        command = ltr_module_fill_cmd_parity(STOP_SECOND_MARK, 0);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &ack, 1);
    }
    return err;
}


/*******************************************************************************
                Функция формирования метки "СТАРТ"
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_MakeStartMark(PTLTR43 hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD command, ack;
        command = ltr_module_fill_cmd_parity(MAKE_START_MARK, 0);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &ack, 1);
    }
    return err;
}


/*******************************************************************************
                Функция конфигурирования интерфейса RS485
 ******************************************************************************/
static int f_rs485_config(PTLTR43 hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        if((hnd->RS485.Baud<2400)) {
            err = LTR43_ERR_RS485_WRONG_BAUDRATE;
        } else if((hnd->RS485.FrameSize<5) || (hnd->RS485.FrameSize>9)) {
            err = LTR43_ERR_RS485_WRONG_FRAME_SIZE;
        } else if((hnd->RS485.Parity<0)||(hnd->RS485.Parity>2)) {
            err = LTR43_ERR_RS485_WRONG_PARITY_CONF;
        } else if((hnd->RS485.StopBit<0)||(hnd->RS485.StopBit>1)) {
            err = LTR43_ERR_RS485_WRONG_STOPBIT_CONF;
        }
    }

    if (err == LTR_OK) {
        DWORD command[3];
        WORD BAUD_UBR; // битрейт. Число для регистра
        BAUD_UBR = (WORD) ((F_OSC / (16 * hnd->RS485.Baud) - 1) + 0.5);
        /* устанавливаем частоту, которая реально получилась */
        hnd->RS485.Baud=(INT) (F_OSC / (16 * (BAUD_UBR + 1)) + 0.5);

        command[0] = ltr_module_fill_cmd_parity(RS485_CONFIG, BAUD_UBR);
        command[1] = LTR_MODULE_FILL_CMD_PARITY_BYTES(RS485_CONFIG,
                                            (hnd->RS485.StopBit << 2) | hnd->RS485.Parity,
                                            hnd->RS485.FrameSize == 9 ? 0x7 : hnd->RS485.FrameSize - 5);
        command[2] = LTR_MODULE_FILL_CMD_PARITY_BYTES(RS485_CONFIG, hnd->RS485.SendTimeoutMultiplier,
                                            hnd->RS485.ReceiveTimeoutMultiplier);

        err = ltr_module_send_cmd(&hnd->Channel, command, sizeof(command)/sizeof(command[0]));
    }

    if (err == LTR_OK) {
        DWORD ack;
        err = ltr_module_recv_cmd_resp(&hnd->Channel, &ack, 1);
        if (err == LTR_OK) {
            err = LTR4X_CheckAck(ack, RS485_CONFIG);
        }
    }
    return err;
}

LTR43API_DllExport (INT) LTR43_RS485_SetResponseTout(PTLTR43 hnd, DWORD tout) {
     INT err = (tout > 0xFFFF) ? LTR_ERROR_PARAMETERS : LTR43_IsOpened(hnd);
     t_ltr43_priv *priv = NULL;
     /* проверяем, что версия AVR поддерживает нужную команду */
     if (err == LTR_OK) {
         priv = LTR_PrivateBufGet(&hnd->Channel, 0);
         if (!IS_RS_EXTFRAME_SUPPORTED(priv)) {
             err = LTR_ERROR_UNSUP_BY_FIRM_VER;
         }
     }
     if (err == LTR_OK) {
         DWORD cmd[2], ack[2];


         cmd[0] = LTR_MODULE_FILL_CMD_PARITY_BYTES(CMD_CONFIG_PAR_EX, PARAM_RS485_RESPONSE_TOUT_L,
                                tout&0xFF);
         cmd[1] = LTR_MODULE_FILL_CMD_PARITY_BYTES(CMD_CONFIG_PAR_EX, PARAM_RS485_RESPONSE_TOUT_H,
                                (tout>>8)&0xFF);

         err = LTR4X_SendCmdWithAck(&hnd->Channel, cmd, ack, 2);

         if (err == LTR_OK) {
             priv->resp_tout = tout&0xFFFF;
         }
     }
     return err;
}

LTR43API_DllExport(INT) LTR43_RS485_SetIntervalTout(PTLTR43 hnd, DWORD mks_tout) {
    INT err = LTR43_IsOpened(hnd);
    DWORD tout = (mks_tout + 249) / 250;
    t_ltr43_priv *priv = NULL;

    if ((err == LTR_OK) && ((tout > 255) || (tout < 7)))
        err = LTR_ERROR_PARAMETERS;

    if (err == LTR_OK) {
        priv = LTR_PrivateBufGet(&hnd->Channel, 0);
        if (!IS_RS_EXTFRAME_SUPPORTED(priv)) {
            err = LTR_ERROR_UNSUP_BY_FIRM_VER;
        }
    }

    if (err == LTR_OK) {
        DWORD cmd, ack;
        cmd = LTR_MODULE_FILL_CMD_PARITY_BYTES(CMD_CONFIG_PAR_EX, PARAM_RS485_INTERVAL_TOUT, tout&0xFFFF);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &cmd, &ack, 1);
        if (err == LTR_OK) {
            priv->interval_tout = mks_tout;
        }
    }
    return err;
}


LTR43API_DllExport (INT) LTR43_RS485_SetTxActiveInterval(PTLTR43 hnd, DWORD start_of_packet,
                                                         DWORD end_of_packet) {
    INT err = (start_of_packet > LTR43_TX_ACTIVE_INTERVAL_MAX) ||
            (end_of_packet > LTR43_TX_ACTIVE_INTERVAL_MAX) ?
             LTR_ERROR_PARAMETERS : LTR43_IsOpened(hnd);
    DWORD sop_tout = (start_of_packet + 249) / 250;
    DWORD eop_tout = (end_of_packet + 249) / 250;
    t_ltr43_priv *priv = NULL;

    /* проверяем версию прошивки */
    if (err == LTR_OK) {
        priv = LTR_PrivateBufGet(&hnd->Channel, 0);
        if (!IS_RS_EXTFRAME_SUPPORTED(priv)) {
            err = LTR_ERROR_UNSUP_BY_FIRM_VER;
        }
    }

    if (err == LTR_OK) {
        DWORD cmd[2], ack[2];
        cmd[0] = LTR_MODULE_FILL_CMD_PARITY_BYTES(CMD_CONFIG_PAR_EX, PARAM_RS485_TX_ACTIVE_SOP_INTERVAL, sop_tout & 0xFFFF);
        cmd[1] = LTR_MODULE_FILL_CMD_PARITY_BYTES(CMD_CONFIG_PAR_EX, PARAM_RS485_TX_ACTIVE_EOP_INTERVAL, eop_tout & 0xFFFF);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, cmd, ack, 2);
    }
    return err;
}

/* Обработка принятых слов по RS485.
 * Слова беруться из recv_arr (размером rcv_size*2), проверяются на ошибки, из
 * каждых двух слов получается один ответ по RS485, которые складываются в
 * put_array. По выходу в rcv_size записывается кол-во правильно обработанных слов */
static INT f_proc_rs_recv(SHORT* put_array, const DWORD *recv_arr, DWORD *rcv_size, INT ex_mode) {
    INT size = *rcv_size, proc_size = 0, i;
    INT err = LTR_OK;
    for (i = 0; (i < 2*size) && (err == LTR_OK); i += 2) {
        err = LTR4X_CheckAck(recv_arr[i], ex_mode ? ACK_RS485_DATA_L : RS485_SEND_BYTE);
        if (err == LTR_OK)
            err = LTR4X_CheckAck(recv_arr[i+1], ex_mode ? ACK_RS485_DATA_H : RS485_SEND_BYTE);

        if (err == LTR_OK) {
            /* во втором слове помимо 9-го бита приходят еще и
               признаки ошибок кадра и четности RS485 */
            if((recv_arr[i + 1] >> 23) & 1) {
                err = LTR43_ERR_RS485_PARITY_ERR_RCV;
            } else if ((recv_arr[i + 1] >> 22) & 1) {
                err = LTR43_ERR_RS485_FRAME_ERR_RCV;
            }
        }

        if (err == LTR_OK) {
            ++proc_size;
            if (put_array != NULL) {
                *put_array++ = (recv_arr[i] >> 16) | (((recv_arr[i + 1] >> 16) & 0x01) << 8);
            }
        }
    }

    if (err == LTR_OK)
        *rcv_size = proc_size;
    return err;
}

/*******************************************************************************
          Функция передачи пакета слов (5-9 бит) по интерфейсу RS485
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_RS485_ExchangeEx(PTLTR43 hnd, const SHORT *PackToSend,
                                                SHORT *ReceivedPack, INT OutPackSize, INT InPackSize,
                                                DWORD flags, INT *ReceivedSize) {
    INT err = LTR43_IsOpened(hnd);
    INT recved_size = 0;
    INT ex_frame = 0; /* признак, поддерживается ли новый формат команд */
    t_ltr43_priv *priv = NULL;

    /* указатель на массив может быть недействителен только в
     * случае, если размер на прием/передачу равны 0 */
    if (((OutPackSize != 0) && (PackToSend == NULL)) ||
            ((InPackSize != 0) && (ReceivedPack == NULL))) {
        err = LTR_ERROR_PARAMETERS;
    }

    if (err == LTR_OK) {
        /* проверяем поддерживает ли версия новые команды */
        priv = LTR_PrivateBufGet(&hnd->Channel, 0);
        if (IS_RS_EXTFRAME_SUPPORTED(priv)) {
            ex_frame = 1;
        }

        /* для старой версии проверяем старые размеры */
        if (!ex_frame) {
            if (((OutPackSize < 1) || (OutPackSize > 10)) || ((InPackSize < 0) || (InPackSize > 10))) {
                err = LTR43_ERR_RS485_WRONG_PACK_SIZE;
            } else if((hnd->RS485.SendTimeoutMultiplier * OutPackSize) > 255) {
                err = LTR43_ERR_RS485_WRONG_OUT_TIMEOUT;
            } else if((hnd->RS485.ReceiveTimeoutMultiplier * InPackSize) > 255) {
                err = LTR43_ERR_RS485_WRONG_IN_TIMEOUT;
            }
        } else {
            /* для новой версии поддерживаем размеры кадров до 256, и ReceiveTimeoutMultiplier
               может быть до 255 (независимо от OutPackSize) */
            if ((OutPackSize < 0) || (OutPackSize > 256) || (InPackSize < 0)||(InPackSize > 256) ||
                    ((OutPackSize == 0) && (InPackSize == 0))) {
                err = LTR43_ERR_RS485_WRONG_PACK_SIZE;
            } else if ((hnd->RS485.ReceiveTimeoutMultiplier > 255)
                       || ((hnd->RS485.ReceiveTimeoutMultiplier * InPackSize + priv->resp_tout) > 0xFFFF)) {
                err = LTR43_ERR_RS485_WRONG_IN_TIMEOUT;
            }
        }
    }

    if (err == LTR_OK) {
        WORD cmd_code = RS485_SEND_BYTE;
        if (ex_frame) {
            DWORD cmd[LTR43_CMD_BUFFER_SIZE], ack[LTR43_CMD_BUFFER_SIZE];
            INT put_pos = 0;

            /* в новом режиме сначала посылаем 3 слова с параметрами обмена */
            cmd[put_pos++] = ltr_module_fill_cmd_parity(CMD_RS485_FRAME_CFG,
                                         (RS485_FRAME_CFG_SEND_SIZE <<8) | OutPackSize);
            cmd[put_pos++] = ltr_module_fill_cmd_parity(CMD_RS485_FRAME_CFG,
                                         (RS485_FRAME_CFG_RECV_SIZE <<8) | InPackSize);
            cmd[put_pos++] = ltr_module_fill_cmd_parity(CMD_RS485_FRAME_CFG,
                                         (RS485_FRAME_CFG_START_DATA <<8) | (flags & 0xFF));

            if (OutPackSize > 0) {
                /* затем передаем данные, при этом их передаем порциями по
                 * LTR43_CMD_BUFFER_SIZE с ожиданием подтверждения, чтобы
                 * не было переполнения внутреннего буфера команд AVR.
                 * первую порцию данных отправляем вместе с сформированным до этого
                 * заголовком (чтобы ожидать подтверждение сразу на все) */
                while ((err == LTR_OK) && (OutPackSize > 0)) {
                    INT i = 0;
                    INT snd_size = OutPackSize;
                    if (snd_size > (LTR43_CMD_BUFFER_SIZE - put_pos))
                        snd_size = LTR43_CMD_BUFFER_SIZE - put_pos;

                    for (i = 0 ; i < snd_size; ++i) {
                        cmd[put_pos++] = ltr_module_fill_cmd_parity(
                                    CMD_RS485_FRAME_CFG, (RS485_FRAME_CFG_DATA << 8) | *PackToSend);
                        ++PackToSend;
                    }
                    err = LTR4X_SendCmdWithAck(&hnd->Channel, cmd, ack, put_pos);
                    put_pos = 0;
                    if (err == LTR_OK) {
                        OutPackSize -= snd_size;
                    }
                }
            } else {
                /* если нет данных - просто посылаем заголовок */
                err = LTR4X_SendCmdWithAck(&hnd->Channel, cmd, ack, put_pos);
            }

            /* передаем команду по которой запускаем обмен */
            if (err == LTR_OK) {
                cmd[0] = ltr_module_fill_cmd_parity(CMD_RS485_START_EXCHANGE, 0);
                err = ltr_module_send_cmd(&hnd->Channel, cmd,1);
            }
        } else {
            /* Вариант передачи слов для версии 1.5 */
            INT i;
            DWORD cmd[11];
            /* В первой команде мы должны указать количество байт для отправки и для
               приема */
            cmd[0] = LTR_MODULE_FILL_CMD_PARITY_BYTES(cmd_code, OutPackSize, InPackSize);
            /* затем передаем данные на передачу в виде того же пакета */
            for (i = 0; i < OutPackSize; ++i) {
                cmd[i + 1] = ltr_module_fill_cmd_parity(cmd_code, *PackToSend++);
            }
            err = ltr_module_send_cmd(&hnd->Channel, cmd, OutPackSize + 1);
        }
    }

    /* Прием ответа. В варианте до 1.6, если кол-во принимаемых слов равно 0,
     * то ответа не будет вообще. В расширенном режиме ответ есть всегда */
    if ((err == LTR_OK) && ((InPackSize != 0) || ex_frame)) {
        DWORD ack[20];

        INT recv_cnt = 0;
        /* рассчитываем таймаут на прием */
        DWORD tout = LTR_MODULE_CMD_RECV_TIMEOUT + hnd->RS485.ReceiveTimeoutMultiplier*InPackSize;
        if (ex_frame)
            tout += priv->resp_tout;

        /* принимаем сперва одно слово - чтобы проверить - это сообщение об
           ошибке или нам пришли данные */
        recv_cnt = LTR_Recv(&(hnd->Channel), ack, NULL, 1, tout);
        if (recv_cnt < 0) {
            err = recv_cnt;
        } else if (recv_cnt == 0) {
            err = LTR_ERROR_NO_CMD_RESPONSE;
        } else {
            if (ex_frame) {
                /* если первое слово не ошибка, то это первое слово ответа,
                   состоящего из 2-х слов и содержащего размер принятых данных */
                err = LTR4X_CheckAck(ack[0], ACK_RS485_RESPONSE_L);
                /* ответ состоит из двух байт, в которых идет
                   размер принятых данных по RS485 (+ резервные флаги) */
                if (err == LTR_OK) {
                    err = ltr_module_recv_cmd_resp(&hnd->Channel, &ack[1], 1);
                }
                if (err == LTR_OK) {
                    err = LTR4X_CheckAck(ack[1], ACK_RS485_RESPONSE_H);
                }
                if (err == LTR_OK) {
                    recv_cnt = (ack[0] >> 16) & 0xFF;
                    recv_cnt |= ((ack[1] >> 16) & 1) << 8;
                }

                /* обрабатываем принятые данные. Обрабатываем пачками по
                 * 10, чтобы не выделять динамически еще массив */
                while ((recv_cnt != recved_size) && (err == LTR_OK)) {
                    DWORD rcv_part_size = recv_cnt - recved_size;
                    if (rcv_part_size > sizeof(ack) / (2*sizeof(ack[0])))
                        rcv_part_size = sizeof(ack) / (2*sizeof(ack[0]));
                    err = ltr_module_recv_cmd_resp(&hnd->Channel, ack, rcv_part_size * 2);
                    if (err == LTR_OK) {
                        err = f_proc_rs_recv(ReceivedPack, ack, &rcv_part_size, ex_frame);

                        ReceivedPack += rcv_part_size;
                        recved_size += rcv_part_size;
                    }
                }
            } else {
                /* в старом варианте первое слово - либо ошибка, либо первое
                 * слово, принятое по RS485 */
                err = LTR4X_CheckAck(ack[0], RS485_SEND_BYTE);
                /* если не ошибка - принимаем оставшиеся слова */
                if (err == LTR_OK)
                    err = ltr_module_recv_cmd_resp(&hnd->Channel, &ack[1], InPackSize*2-1);
                if (err == LTR_OK) {
                    recved_size = InPackSize;
                    err = f_proc_rs_recv(ReceivedPack, ack, (DWORD*)&recved_size, ex_frame);
                }
            }
        }
    }

    if (ReceivedSize != NULL) {
        *ReceivedSize = recved_size;
    }
    return err;
}


LTR43API_DllExport (INT) LTR43_RS485_Exchange(PTLTR43 hnd, const SHORT *PackToSend,
                                              SHORT *ReceivedPack, INT OutPackSize,
                                              INT InPackSize) {
    INT recvd_size = 0;
    INT err = LTR43_RS485_ExchangeEx(hnd, PackToSend, ReceivedPack, OutPackSize, InPackSize,
                                     0, &recvd_size);
    if ((err == LTR_OK) && (recvd_size < InPackSize)) {
        err = LTR_ERROR_RECV_INSUFFICIENT_DATA;
    }
    return err;
}


/*******************************************************************************
    Тестовая функция для ожидания приема байта по интерфейсу RS485
    и отправки подстверждения
*******************************************************************************/
LTR43API_DllExport (INT)  LTR43_RS485_TestReceiveByte(PTLTR43 hnd, INT OutPackSize,
                                                      INT InPackSize) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
         DWORD command, command_ack;
         command = LTR_MODULE_FILL_CMD_PARITY_BYTES(RS485_RECEIVE, OutPackSize, InPackSize);
         err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &command_ack,1);
    }
    return err;
}

/*******************************************************************************
                Функция остановки ожидания приема при тестировании RS-485
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_RS485_TestStopReceive(PTLTR43 hnd) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
         DWORD command, command_ack;
         command = ltr_module_fill_cmd_parity(RS485_STOP_RECEIVE, 0);
         err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &command_ack,1);
    }
    return err;
}

static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    { LTR43_ERR_RS485_FRAME_ERR_RCV,      "Ошибка кадра при получении подтверждения при обмене по RS-485"},
    { LTR43_ERR_RS485_PARITY_ERR_RCV,     "Ошибка четности при получении подтверждения при обмене по RS-485"},
    { LTR43_ERR_RS485_WRONG_BAUDRATE,     "Неверная скорость обмена по RS-485"},
    { LTR43_ERR_RS485_WRONG_FRAME_SIZE,   "Неверный размер кадра RS-485"},
    { LTR43_ERR_RS485_WRONG_PARITY_CONF,  "Неверная конфигурация четности для кадра RS-485"},
    { LTR43_ERR_RS485_WRONG_STOPBIT_CONF, "Неверная конфигурация СТОП-бита для кадра RS-485"},
    { LTR43_ERR_DATA_TRANSMISSON_ERROR,   "Ошибка при передаче данных модулю"},
    { LTR43_ERR_RS485_CONFIRM_TIMEOUT,    "Таймаут получения подтверждения при обмене по RS-485 истек"},
    { LTR43_ERR_RS485_SEND_TIMEOUT,       "Таймаут отправки по RS-485 истек"},
    { LTR43_ERR_WRONG_IO_LINES_CONF,      "Неверная конфигурация линий ввода-вывода"},
    { LTR43_ERR_WRONG_SECOND_MARK_CONF,   "Неверная конфигурация секундных меток"},
    { LTR43_ERR_WRONG_START_MARK_CONF,    "Неверная конфигурация метки СТАРТ"},
    { LTR43_ERR_CANT_READ_DATA,           "Ошибка при чтении значений входов"},
    { LTR43_ERR_RS485_WRONG_PACK_SIZE,    "Неверный размер пакета при передаче по RS-485"},
    { LTR43_ERR_RS485_WRONG_OUT_TIMEOUT,  "Неверный таймаут для исходящего пакета по RS-485"},
    { LTR43_ERR_RS485_WRONG_IN_TIMEOUT,   "Неверный таймаут для пакета-подтверждения по RS-485"},
    { LTR43_ERR_WRONG_IO_DATA,            "Неверные данные с портов ввода-вывода"},
    { LTR43_ERR_WRONG_STREAM_READ_FREQ_SETTINGS, "Неверные установки часоты данных при потоковом вводе"}
};


/*****************************************************************************************************
                Функция определения строки ошибки
******************************************************************************************************/   

LTR43API_DllExport (LPCSTR) LTR43_GetErrorString(int err) {
    size_t i;
    for (i = 0; (i < sizeof(f_err_tbl) / sizeof(f_err_tbl[0])); ++i) {
        if (f_err_tbl[i].code == err)
            return f_err_tbl[i].message;
    }
    return LTR_GetErrorString(err);
}

/*******************************************************************************
        Функция записи байта в указанную ячейку ППЗУ
 ******************************************************************************/
LTR43API_DllExport (INT) LTR43_WriteEEPROM(PTLTR43 hnd, INT Address, BYTE val) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR4X_WriteEEPROM(&hnd->Channel, Address, val);
    }
    return err;
}

/*******************************************************************************
        Функция чтения байта из указанной ячейки ППЗУ
*******************************************************************************/
LTR43API_DllExport (INT) LTR43_ReadEEPROM(PTLTR43 hnd, INT Address, BYTE *val) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR4X_ReadEEPROM(&hnd->Channel, Address, val);
    }
    return err;
}


LTR43API_DllExport (INT) LTR43_GetAvrFreeStackSize(PTLTR43 hnd, DWORD* stack_size) {
    INT err = LTR43_IsOpened(hnd);

    if (err == LTR_OK) {
        t_ltr43_priv *priv = LTR_PrivateBufGet(&hnd->Channel, 0);
        if (!IS_RS_EXTFRAME_SUPPORTED(priv)) {
            err = LTR_ERROR_UNSUP_BY_FIRM_VER;
        }
    }

    if (err == LTR_OK) {
        DWORD cmd, ack;
        cmd = LTR_MODULE_FILL_CMD_PARITY_BYTES(CMD_CONFIG_PAR_EX, PARAM_STACK_SIZE, 0);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &cmd, &ack, 1);
        if ((err == LTR_OK) && (stack_size != NULL)) {
            *stack_size = (ack >> 16) & 0xFF;
        }
    }
    return err;
}

LTR43API_DllExport (INT) LTR43_SetStartMarkPulseTime(TLTR43 *hnd, DWORD time_mks) {
    INT err = LTR43_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR4X_SetStartMarkPulseTime(&hnd->Channel, time_mks, hnd->ModuleInfo.FirmwareVersion);
    }
    return err;
}

/** @note что это была за функция? она же нигде не экспортируется???? */
#if 0
int HC_LE41_CONNECT(PTLTR43 hnd)
/*Подключиться к единственному модулю.
 В отличие от HC_LE41_SELECT, переводит в активное состояние все модули, подключенные к COM-порту,
 поэтому, должна применяться только тогда, когда подключен один модуль.
Результат:
  Код ошибки. 0 - успешное выполнение.
*/
{
    BYTE outbuf[3];
    SHORT PackToSend[5];
    SHORT BytesToReceive[3];
    WORD CRC;
    int err;

    outbuf[0]= 0xB0;
    outbuf[1]= 0xB5;
    outbuf[2]= 1;

    CRC=0;

    CRC=CRC_Calc(outbuf, 3);

    PackToSend[0]=(WORD) outbuf[0];
    PackToSend[1]=(WORD) outbuf[1];
    PackToSend[2]=(WORD) outbuf[2];
    PackToSend[3]=CRC&0xFF;
    PackToSend[4]=CRC>>8;


    err=LTR43_RS485_Exchange(hnd, PackToSend, BytesToReceive, 5, 3);


    outbuf[0]= 5;
    outbuf[1]= 0;
    outbuf[2]= 0;

    CRC=0;

    CRC=CRC_Calc(outbuf, 3);

    PackToSend[0]=(WORD) outbuf[0];
    PackToSend[1]=(WORD) outbuf[1];
    PackToSend[2]=(WORD) outbuf[2];
    PackToSend[3]=CRC&0xFF;
    PackToSend[4]=CRC>>8;


    err=LTR43_RS485_Exchange(hnd, PackToSend, BytesToReceive, 5, 3);


    return err;
}
#endif
