Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

esp-modbus as a slave data validation before writing (IDFGH-14101) #83

Open
3 tasks done
kabirwbt opened this issue Nov 19, 2024 · 6 comments
Open
3 tasks done
Assignees

Comments

@kabirwbt
Copy link

Checklist

  • Checked the issue tracker for similar issues to ensure this is not a duplicate.
  • Provided a clear description of your suggestion.
  • 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.

@kabirwbt
Copy link
Author

kabirwbt commented Nov 19, 2024

#82 is in the same boat as me I think. I am also unable to see the data being written by the master.

@github-actions github-actions bot changed the title esp-modbus as a slave data validation before writing esp-modbus as a slave data validation before writing (IDFGH-14101) Nov 19, 2024
@alisitsyn
Copy link
Collaborator

alisitsyn commented Nov 22, 2024

@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?

@alisitsyn alisitsyn self-assigned this Nov 22, 2024
@kabirwbt
Copy link
Author

kabirwbt commented Nov 23, 2024

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.

@alisitsyn
Copy link
Collaborator

Hey @kabirwbt,

Any update on this?

@kabirwbt
Copy link
Author

kabirwbt commented Dec 4, 2024

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 😄. 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.

@alisitsyn
Copy link
Collaborator

alisitsyn commented Dec 5, 2024

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants