esp-modbus as a slave data validation before writing (IDFGH-14101)
Checklist
- [X] Checked the issue tracker for similar issues to ensure this is not a duplicate.
- [X] Provided a clear description of your suggestion.
- [X] Included any relevant context or examples.
Issue or Suggestion Description
I had a question regrading the code for the modbus slave. I have gone through the example for modbus rtu slave (https://github.com/espressif/esp-idf/blob/master/examples/protocols/modbus/serial/mb_slave/main/slave.c). It is working well. However, I have a possible issue with the way it is implemented that I need help with. Let's say that I write to the slave using fc 06 (write holding reg), via a modbus master. The library automatically writes the value to the specified register. However, I would like to see the incoming data, check whether it is valid (in range) and only then allow it to be written, otherwise send a modbus exception (Illegal Data Value 03). Please help me how to do this with esp-modbus.
An example case is let's say I am creating a thermostat. I would want my set-temperature to be allowed within range (let's say 16-40 C) . If a user enters a number out of range, I want to set a modbus exception, or at least prevent it being written. I am not able to understand how to do this with esp-modbus. I have read the slave API documentation and was unable to understand how to achieve this. Currently it writes the value instantly to specified register.
#82 is in the same boat as me I think. I am also unable to see the data being written by the master.
@kabirwbt,
I would want my set-temperature to be allowed within range (let's say 16-40 C) . If a user enters a number out of range, I want to set a modbus exception, or at least prevent it being written. I am not able to understand how to do this with esp-modbus.
Sorry for the delay with answer. This stack was designed to hide some Modbus internals from user and it is why this is not documented. This actually can be done by defining user handler functions for commands. There are several tricks to accomplish this. The one trick to override the callbacks in user code is described below:
Please apply this code above the app_main() in the slave example:
// The exception structure codes are defined in private file, let just override them here
typedef enum
{
MB_EX_NONE = 0x00,
MB_EX_ILLEGAL_FUNCTION = 0x01,
MB_EX_ILLEGAL_DATA_ADDRESS = 0x02,
MB_EX_ILLEGAL_DATA_VALUE = 0x03,
MB_EX_SLAVE_DEVICE_FAILURE = 0x04,
MB_EX_ACKNOWLEDGE = 0x05,
MB_EX_SLAVE_BUSY = 0x06,
MB_EX_MEMORY_PARITY_ERROR = 0x08,
MB_EX_GATEWAY_PATH_FAILED = 0x0A,
MB_EX_GATEWAY_TGT_FAILED = 0x0B
} e_mb_exception;
typedef e_mb_exception( *pfunc_handler_t ) ( uint8_t * pframe, uint16_t * plength );
// Will call the handler registration function from stack core
extern int eMBRegisterCB( uint8_t func_code, pfunc_handler_t pfunc_handler );
// Define your custom function handler to process the command received.
// Can return the error code if we need to inform master about specific failure
e_mb_exception my_input_read_registers_handler_func( uint8_t *pframe, uint16_t *plen )
{
// This error handler will be executed to check the request for the command 0x04
// See the `esp-modbus/freemodbus/modbus/functions/mbfuncinput.c` for more information
// pframe is pointer to command buffer, plen - is pointer to length
ESP_LOGE("TEST_HANDLER", "Overridden handler for command 0x04 is called.");
return MB_EX_ILLEGAL_DATA_VALUE;
}
// An example application of Modbus slave. It is based on freemodbus stack.
// See deviceparams.h file for more information about assigned Modbus parameters.
// These parameters can be accessed from main application and also can be changed
// by external Modbus master host.
void app_main(void)
{
mb_param_info_t reg_info; // keeps the Modbus registers access information
mb_communication_info_t comm_info; // Modbus communication parameters
mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
// Set UART log level
esp_log_level_set(TAG, ESP_LOG_INFO);
void* mbc_slave_handler = NULL;
ESP_ERROR_CHECK(mbc_slave_init(MB_PORT_SERIAL_SLAVE, &mbc_slave_handler)); // Initialization of Modbus controller
uint8_t override_command = 0x04;
// <<<<<<<<<<<<<<<<<<<<<< Register the custom function handler here
int err = eMBRegisterCB(override_command, NULL);
MB_RETURN_ON_FALSE(!err, ;, TAG,
"could not override func handler, returned (0x%x).", err);
err = eMBRegisterCB(override_command, my_input_read_registers_handler_func);
MB_RETURN_ON_FALSE(!err, ;, TAG,
"could not override func handler, returned (0x%x).", err);
ESP_LOGI("TEST_HANDLER", "Overridden function handler for the command 0x%x ", (int)override_command);
// Setup communication parameters and start stack
#if CONFIG_MB_COMM_MODE_ASCII
comm_info.mode = MB_MODE_ASCII;
#elif CONFIG_MB_COMM_MODE_RTU
comm_info.mode = MB_MODE_RTU;
#endif
comm_info.slave_addr = MB_SLAVE_ADDR;
comm_info.port = MB_PORT_NUM;
comm_info.baudrate = MB_DEV_SPEED;
comm_info.parity = MB_PARITY_NONE;
ESP_ERROR_CHECK(mbc_slave_setup((void*)&comm_info));
Note: the handler will be executed by stack from the polling task, so keep the handler as short as possible and safe. Would this work for you?
Hey @alisitsyn. Thank you for replying.
Your code looks good to me and it should work.
I shall test it in my application and then get back to you.
Hey @kabirwbt,
Any update on this?
Hi @alisitsyn sorry for not updating sooner. I was using esp-modbus in the arduino framework. Even though your code is great, it wasn't compiling for me because arduino framework pre-compiles the .c files of esp-idf libraries and only exposes the .h files to end-users. They only include the statically linked .a files in their code. I am obviously unable to call the int err = eMBRegisterCB(override_command, NULL) function in their code, since it is only in the .c file.
I am porting my project to esp-idf soon and can only test properly then. The second option is to try and compile with arduino framework for esp32 separately including esp-modbus, which i am not looking forward too :smile:. I am using master just fine. I will finish porting over my project to esp-idf in the coming time and will properly update then.
@kabirwbt,
Thank you for your feedback. Yes, this trick is probably for esp-idf only and works (verified). There are some other possible ways to accomplish this for arduino making changes in the internal component folder.
This approach may also help to patch the official component in platform io (will not work for arduino).
Once you have some results, please report here. Thanks.
@kabirwbt , there is update in version v2 to implement the custom function handlers. This was merged with commit cbe96056f2bcb44d4580355126b7255af332c3ba Custom function handlers, Documentation If it is possible, please take a look at this commit. This will allow to address your issue using public API to override standard handlers in your code.
@alisitsyn thanks for this commit. Sorry for the extremely late reply. I was facing some personal issues. I am currently working on a modbus master on esp-idf. I shall shortly start work on a modbus slave on esp-idf. I shall use this functionality and get back. Please pardon my late reply.
@kabirwbt ,
Thank you for the answer. No issues with this. Please take your time.
thank you very much. I do my bachelor thesis and also have this problem. Thanks for you solution
@myry07,
Thank you for report. The commit is merged and released v2.1.0. Please report if you have any issues.
The issue will be closed as resolved. Feel free to reopen if you have any further issues.