Сохрани адресацию отдельных регистров GAS 0x3 и выдели слотам тип 0x4 с маской Word0. Добавь единое C-ядро GAS_WATCH2, тонкие Python/JNI-порты и проверки общих эталонных байтов. Совместимость: тип 0x4 больше не обслуживает Modbus Coil. Потребителям PCAN_MSG_MODBUS_COIL и ProtoCanMsgToModbusCoil нужно перейти на GAS_SLOT и отдельный API слотов; регистровый GAS остаётся на 0x3.
95 lines
4.1 KiB
C
95 lines
4.1 KiB
C
/*
|
|
* Проверки modbus_server: фиксируют ожидаемое поведение на воспроизводимых входных данных.
|
|
* Тестовые значения и подмены окружения относятся к проверке контракта; они не являются
|
|
* настройками рабочего устройства.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "pcan_id.h"
|
|
#include "pcan_modbus_server.h"
|
|
|
|
static uint16_t holding[32];
|
|
static uint16_t input[32];
|
|
static uint16_t discretes = 0x0014U; /* Входы 2 и 4 установлены для проверки сохранённого битового чтения типа 0x5. */
|
|
|
|
static bool read_word(void *user, pcan_modbus_bank_t bank,
|
|
uint16_t address, uint16_t *value)
|
|
{
|
|
(void)user;
|
|
if (address >= 32U) return false;
|
|
if (bank == PCAN_MODBUS_HOLDING) *value = holding[address];
|
|
else if (bank == PCAN_MODBUS_INPUT) *value = input[address];
|
|
else if (bank == PCAN_MODBUS_DISCRETE && address < 16U) *value = (uint16_t)((discretes >> address) & 1U); /* Не сдвигаем 16-битное поле на недопустимое число позиций. */
|
|
else return false;
|
|
return true;
|
|
}
|
|
|
|
static bool write_word(void *user, pcan_modbus_bank_t bank,
|
|
uint16_t address, uint16_t value)
|
|
{
|
|
(void)user;
|
|
if (address >= 32U) return false;
|
|
if (bank == PCAN_MODBUS_HOLDING) holding[address] = value;
|
|
else return false; /* Битовые входы и новый слотный GAS не записываются через Modbus provider. */
|
|
return true;
|
|
}
|
|
|
|
static pcan_frame_t request(uint8_t type, uint16_t address, uint8_t count)
|
|
{
|
|
pcan_id_t id = {0};
|
|
pcan_frame_t frame = {0};
|
|
id.priority = PCAN_PRIORITY_STANDARD;
|
|
id.route = PCAN_ROUTE_FROM_PM;
|
|
id.device_type = 7U;
|
|
id.device_id = 3U;
|
|
id.msg_type = type;
|
|
id.msg_body = pcan_body_modbus(address, count);
|
|
frame.id = pcan_id_pack(&id);
|
|
frame.flags = PCAN_FLAG_IDE;
|
|
return frame;
|
|
}
|
|
|
|
#define CHECK(x) do { if (!(x)) { \
|
|
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #x); return 1; \
|
|
} } while (0)
|
|
|
|
int main(void)
|
|
{
|
|
pcan_modbus_server_t server;
|
|
pcan_frame_t req;
|
|
pcan_frame_t rsp;
|
|
pcan_id_t id;
|
|
memset(holding, 0, sizeof(holding));
|
|
memset(input, 0, sizeof(input));
|
|
input[10] = 0x1234U;
|
|
input[11] = 0xABCDU;
|
|
|
|
CHECK(pcan_modbus_server_init(&server, 7U, 3U, read_word, write_word, NULL));
|
|
req = request(PCAN_MODBUS_INPUT, 10U, 2U);
|
|
CHECK(pcan_modbus_server_handle(&server, &req, &rsp) == PCAN_MODBUS_RESPONSE);
|
|
CHECK(rsp.dlc == 4U && rsp.data[0] == 0x34U && rsp.data[1] == 0x12U);
|
|
CHECK(rsp.data[2] == 0xCDU && rsp.data[3] == 0xABU);
|
|
pcan_id_unpack(rsp.id, &id);
|
|
CHECK(id.route == PCAN_ROUTE_FROM_DEVICE);
|
|
|
|
req = request(PCAN_MODBUS_HOLDING, 3U, 2U);
|
|
req.dlc = 4U;
|
|
req.data[0] = 0x22U; req.data[1] = 0x11U;
|
|
req.data[2] = 0x44U; req.data[3] = 0x33U;
|
|
CHECK(pcan_modbus_server_handle(&server, &req, &rsp) == PCAN_MODBUS_RESPONSE);
|
|
CHECK(holding[3] == 0x1122U && holding[4] == 0x3344U);
|
|
|
|
req = request(PCAN_MODBUS_DISCRETE, 2U, 3U); /* Читаем три входа 2…4 через сохранённый тип 0x5. */
|
|
CHECK(pcan_modbus_server_handle(&server, &req, &rsp) == PCAN_MODBUS_RESPONSE);
|
|
CHECK(rsp.dlc == 2U && rsp.data[0] == 0x05U && rsp.data[1] == 0U); /* Маска прочитанных входов должна сохранять порядок младших битов. */
|
|
req = request(PCAN_MSG_GAS_SLOT, 2U, 3U); /* Передаём бывший номер Coil, теперь занятый независимым GAS SLOT. */
|
|
CHECK(pcan_modbus_server_handle(&server, &req, &rsp) == PCAN_MODBUS_NOT_FOR_US); /* Слотный кадр не должен вызывать Modbus provider и менять его счётчики. */
|
|
|
|
req = request(PCAN_MODBUS_INPUT, 0U, 5U);
|
|
CHECK(pcan_modbus_server_handle(&server, &req, &rsp) == PCAN_MODBUS_HANDLED_NO_RESPONSE);
|
|
CHECK(server.requests == 4U && server.responses == 3U && server.rejected == 1U);
|
|
return 0;
|
|
}
|