1 /*
2  * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 // FreeModbus Slave Example ESP32
8 
9 #include <stdio.h>
10 #include "esp_err.h"
11 #include "sdkconfig.h"
12 #include "esp_log.h"
13 #include "esp_system.h"
14 #include "esp_wifi.h"
15 #include "esp_event.h"
16 #include "esp_log.h"
17 #include "nvs_flash.h"
18 
19 #include "mdns.h"
20 #include "esp_netif.h"
21 #include "protocol_examples_common.h"
22 
23 #include "mbcontroller.h"       // for mbcontroller defines and api
24 #include "modbus_params.h"      // for modbus parameters structures
25 
26 #define MB_TCP_PORT_NUMBER      (CONFIG_FMB_TCP_PORT_DEFAULT)
27 #define MB_MDNS_PORT            (502)
28 
29 // Defines below are used to define register start address for each type of Modbus registers
30 #define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) >> 1))
31 #define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) >> 1))
32 #define MB_REG_DISCRETE_INPUT_START         (0x0000)
33 #define MB_REG_COILS_START                  (0x0000)
34 #define MB_REG_INPUT_START_AREA0            (INPUT_OFFSET(input_data0)) // register offset input area 0
35 #define MB_REG_INPUT_START_AREA1            (INPUT_OFFSET(input_data4)) // register offset input area 1
36 #define MB_REG_HOLDING_START_AREA0          (HOLD_OFFSET(holding_data0))
37 #define MB_REG_HOLDING_START_AREA1          (HOLD_OFFSET(holding_data4))
38 
39 #define MB_PAR_INFO_GET_TOUT                (10) // Timeout for get parameter info
40 #define MB_CHAN_DATA_MAX_VAL                (10)
41 #define MB_CHAN_DATA_OFFSET                 (1.1f)
42 
43 #define MB_READ_MASK                        (MB_EVENT_INPUT_REG_RD \
44                                                 | MB_EVENT_HOLDING_REG_RD \
45                                                 | MB_EVENT_DISCRETE_RD \
46                                                 | MB_EVENT_COILS_RD)
47 #define MB_WRITE_MASK                       (MB_EVENT_HOLDING_REG_WR \
48                                                 | MB_EVENT_COILS_WR)
49 #define MB_READ_WRITE_MASK                  (MB_READ_MASK | MB_WRITE_MASK)
50 
51 static const char *TAG = "SLAVE_TEST";
52 
53 static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED;
54 
55 #if CONFIG_MB_MDNS_IP_RESOLVER
56 
57 #define MB_ID_BYTE0(id) ((uint8_t)(id))
58 #define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF))
59 #define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF))
60 #define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF))
61 
62 #define MB_ID2STR(id) MB_ID_BYTE0(id), MB_ID_BYTE1(id), MB_ID_BYTE2(id), MB_ID_BYTE3(id)
63 
64 #if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
65 #define MB_DEVICE_ID (uint32_t)CONFIG_FMB_CONTROLLER_SLAVE_ID
66 #endif
67 
68 #define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR)
69 
70 #define MB_MDNS_INSTANCE(pref) pref"mb_slave_tcp"
71 
72 // convert mac from binary format to string
gen_mac_str(const uint8_t * mac,char * pref,char * mac_str)73 static inline char* gen_mac_str(const uint8_t* mac, char* pref, char* mac_str)
74 {
75     sprintf(mac_str, "%s%02X%02X%02X%02X%02X%02X", pref, MAC2STR(mac));
76     return mac_str;
77 }
78 
gen_id_str(char * service_name,char * slave_id_str)79 static inline char* gen_id_str(char* service_name, char* slave_id_str)
80 {
81     sprintf(slave_id_str, "%s%02X%02X%02X%02X", service_name, MB_ID2STR(MB_DEVICE_ID));
82     return slave_id_str;
83 }
84 
gen_host_name_str(char * service_name,char * name)85 static inline char* gen_host_name_str(char* service_name, char* name)
86 {
87     sprintf(name, "%s_%02X", service_name, MB_SLAVE_ADDR);
88     return name;
89 }
90 
start_mdns_service(void)91 static void start_mdns_service(void)
92 {
93     char temp_str[32] = {0};
94     uint8_t sta_mac[6] = {0};
95     ESP_ERROR_CHECK(esp_read_mac(sta_mac, ESP_MAC_WIFI_STA));
96     char* hostname = gen_host_name_str(MB_MDNS_INSTANCE(""), temp_str);
97     //initialize mDNS
98     ESP_ERROR_CHECK(mdns_init());
99     //set mDNS hostname (required if you want to advertise services)
100     ESP_ERROR_CHECK(mdns_hostname_set(hostname));
101     ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname);
102 
103     //set default mDNS instance name
104     ESP_ERROR_CHECK(mdns_instance_name_set(MB_MDNS_INSTANCE("esp32_")));
105 
106     //structure with TXT records
107     mdns_txt_item_t serviceTxtData[] = {
108         {"board","esp32"}
109     };
110 
111     //initialize service
112     ESP_ERROR_CHECK(mdns_service_add(hostname, "_modbus", "_tcp", MB_MDNS_PORT, serviceTxtData, 1));
113     //add mac key string text item
114     ESP_ERROR_CHECK(mdns_service_txt_item_set("_modbus", "_tcp", "mac", gen_mac_str(sta_mac, "\0", temp_str)));
115     //add slave id key txt item
116     ESP_ERROR_CHECK( mdns_service_txt_item_set("_modbus", "_tcp", "mb_id", gen_id_str("\0", temp_str)));
117 }
118 
stop_mdns_service(void)119 static void stop_mdns_service(void)
120 {
121     mdns_free();
122 }
123 
124 #endif
125 
126 // Set register values into known state
setup_reg_data(void)127 static void setup_reg_data(void)
128 {
129     // Define initial state of parameters
130     discrete_reg_params.discrete_input0 = 1;
131     discrete_reg_params.discrete_input1 = 0;
132     discrete_reg_params.discrete_input2 = 1;
133     discrete_reg_params.discrete_input3 = 0;
134     discrete_reg_params.discrete_input4 = 1;
135     discrete_reg_params.discrete_input5 = 0;
136     discrete_reg_params.discrete_input6 = 1;
137     discrete_reg_params.discrete_input7 = 0;
138 
139     holding_reg_params.holding_data0 = 1.34;
140     holding_reg_params.holding_data1 = 2.56;
141     holding_reg_params.holding_data2 = 3.78;
142     holding_reg_params.holding_data3 = 4.90;
143 
144     holding_reg_params.holding_data4 = 5.67;
145     holding_reg_params.holding_data5 = 6.78;
146     holding_reg_params.holding_data6 = 7.79;
147     holding_reg_params.holding_data7 = 8.80;
148     coil_reg_params.coils_port0 = 0x55;
149     coil_reg_params.coils_port1 = 0xAA;
150 
151     input_reg_params.input_data0 = 1.12;
152     input_reg_params.input_data1 = 2.34;
153     input_reg_params.input_data2 = 3.56;
154     input_reg_params.input_data3 = 4.78;
155     input_reg_params.input_data4 = 1.12;
156     input_reg_params.input_data5 = 2.34;
157     input_reg_params.input_data6 = 3.56;
158     input_reg_params.input_data7 = 4.78;
159 }
160 
slave_operation_func(void * arg)161 static void slave_operation_func(void *arg)
162 {
163     mb_param_info_t reg_info; // keeps the Modbus registers access information
164 
165     ESP_LOGI(TAG, "Modbus slave stack initialized.");
166     ESP_LOGI(TAG, "Start modbus test...");
167     // The cycle below will be terminated when parameter holding_data0
168     // incremented each access cycle reaches the CHAN_DATA_MAX_VAL value.
169     for(;holding_reg_params.holding_data0 < MB_CHAN_DATA_MAX_VAL;) {
170         // Check for read/write events of Modbus master for certain events
171         mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK);
172         const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE";
173         // Filter events and process them accordingly
174         if(event & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
175             // Get parameter information from parameter queue
176             ESP_ERROR_CHECK(mbc_slave_get_param_info(&reg_info, MB_PAR_INFO_GET_TOUT));
177             ESP_LOGI(TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
178                     rw_str,
179                     (uint32_t)reg_info.time_stamp,
180                     (uint32_t)reg_info.mb_offset,
181                     (uint32_t)reg_info.type,
182                     (uint32_t)reg_info.address,
183                     (uint32_t)reg_info.size);
184             if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0)
185             {
186                 portENTER_CRITICAL(&param_lock);
187                 holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
188                 if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) {
189                     coil_reg_params.coils_port1 = 0xFF;
190                 }
191                 portEXIT_CRITICAL(&param_lock);
192             }
193         } else if (event & MB_EVENT_INPUT_REG_RD) {
194             ESP_ERROR_CHECK(mbc_slave_get_param_info(&reg_info, MB_PAR_INFO_GET_TOUT));
195             ESP_LOGI(TAG, "INPUT READ (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
196                     (uint32_t)reg_info.time_stamp,
197                     (uint32_t)reg_info.mb_offset,
198                     (uint32_t)reg_info.type,
199                     (uint32_t)reg_info.address,
200                     (uint32_t)reg_info.size);
201         } else if (event & MB_EVENT_DISCRETE_RD) {
202             ESP_ERROR_CHECK(mbc_slave_get_param_info(&reg_info, MB_PAR_INFO_GET_TOUT));
203             ESP_LOGI(TAG, "DISCRETE READ (%u us): ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
204                                 (uint32_t)reg_info.time_stamp,
205                                 (uint32_t)reg_info.mb_offset,
206                                 (uint32_t)reg_info.type,
207                                 (uint32_t)reg_info.address,
208                                 (uint32_t)reg_info.size);
209         } else if (event & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
210             ESP_ERROR_CHECK(mbc_slave_get_param_info(&reg_info, MB_PAR_INFO_GET_TOUT));
211             ESP_LOGI(TAG, "COILS %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
212                                 rw_str,
213                                 (uint32_t)reg_info.time_stamp,
214                                 (uint32_t)reg_info.mb_offset,
215                                 (uint32_t)reg_info.type,
216                                 (uint32_t)reg_info.address,
217                                 (uint32_t)reg_info.size);
218             if (coil_reg_params.coils_port1 == 0xFF) break;
219         }
220     }
221     // Destroy of Modbus controller on alarm
222     ESP_LOGI(TAG,"Modbus controller destroyed.");
223     vTaskDelay(100);
224 }
225 
init_services(void)226 static esp_err_t init_services(void)
227 {
228     esp_err_t result = nvs_flash_init();
229     if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) {
230       ESP_ERROR_CHECK(nvs_flash_erase());
231       result = nvs_flash_init();
232     }
233     MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
234                             TAG,
235                             "nvs_flash_init fail, returns(0x%x).",
236                             (uint32_t)result);
237     result = esp_netif_init();
238     MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
239                             TAG,
240                             "esp_netif_init fail, returns(0x%x).",
241                             (uint32_t)result);
242     result = esp_event_loop_create_default();
243     MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
244                             TAG,
245                             "esp_event_loop_create_default fail, returns(0x%x).",
246                             (uint32_t)result);
247 #if CONFIG_MB_MDNS_IP_RESOLVER
248     // Start mdns service and register device
249     start_mdns_service();
250 #endif
251     // This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
252     // Read "Establishing Wi-Fi or Ethernet Connection" section in
253     // examples/protocols/README.md for more information about this function.
254     result = example_connect();
255     MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
256                                 TAG,
257                                 "example_connect fail, returns(0x%x).",
258                                 (uint32_t)result);
259 #if CONFIG_EXAMPLE_CONNECT_WIFI
260     result = esp_wifi_set_ps(WIFI_PS_NONE);
261     MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
262                                    TAG,
263                                    "esp_wifi_set_ps fail, returns(0x%x).",
264                                    (uint32_t)result);
265 #endif
266     return ESP_OK;
267 }
268 
destroy_services(void)269 static esp_err_t destroy_services(void)
270 {
271     esp_err_t err = ESP_OK;
272 
273     err = example_disconnect();
274     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
275                                    TAG,
276                                    "example_disconnect fail, returns(0x%x).",
277                                    (uint32_t)err);
278     err = esp_event_loop_delete_default();
279     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
280                                        TAG,
281                                        "esp_event_loop_delete_default fail, returns(0x%x).",
282                                        (uint32_t)err);
283     err = esp_netif_deinit();
284     MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_NOT_SUPPORTED), ESP_ERR_INVALID_STATE,
285                                         TAG,
286                                         "esp_netif_deinit fail, returns(0x%x).",
287                                         (uint32_t)err);
288     err = nvs_flash_deinit();
289     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
290                                 TAG,
291                                 "nvs_flash_deinit fail, returns(0x%x).",
292                                 (uint32_t)err);
293 #if CONFIG_MB_MDNS_IP_RESOLVER
294     stop_mdns_service();
295 #endif
296     return err;
297 }
298 
299 // Modbus slave initialization
slave_init(mb_communication_info_t * comm_info)300 static esp_err_t slave_init(mb_communication_info_t* comm_info)
301 {
302     mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
303 
304     void* slave_handler = NULL;
305 
306     // Initialization of Modbus controller
307     esp_err_t err = mbc_slave_init_tcp(&slave_handler);
308     MB_RETURN_ON_FALSE((err == ESP_OK && slave_handler != NULL), ESP_ERR_INVALID_STATE,
309                                 TAG,
310                                 "mb controller initialization fail.");
311 
312     comm_info->ip_addr = NULL; // Bind to any address
313     comm_info->ip_netif_ptr = (void*)get_example_netif();
314 
315     // Setup communication parameters and start stack
316     err = mbc_slave_setup((void*)comm_info);
317     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
318                                         TAG,
319                                         "mbc_slave_setup fail, returns(0x%x).",
320                                         (uint32_t)err);
321 
322     // The code below initializes Modbus register area descriptors
323     // for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs
324     // Initialization should be done for each supported Modbus register area according to register map.
325     // When external master trying to access the register in the area that is not initialized
326     // by mbc_slave_set_descriptor() API call then Modbus stack
327     // will send exception response for this register area.
328     reg_area.type = MB_PARAM_HOLDING; // Set type of register area
329     reg_area.start_offset = MB_REG_HOLDING_START_AREA0; // Offset of register area in Modbus protocol
330     reg_area.address = (void*)&holding_reg_params.holding_data0; // Set pointer to storage instance
331     reg_area.size = sizeof(float) << 2; // Set the size of register storage instance
332     err = mbc_slave_set_descriptor(reg_area);
333     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
334                                     TAG,
335                                     "mbc_slave_set_descriptor fail, returns(0x%x).",
336                                     (uint32_t)err);
337 
338     reg_area.type = MB_PARAM_HOLDING; // Set type of register area
339     reg_area.start_offset = MB_REG_HOLDING_START_AREA1; // Offset of register area in Modbus protocol
340     reg_area.address = (void*)&holding_reg_params.holding_data4; // Set pointer to storage instance
341     reg_area.size = sizeof(float) << 2; // Set the size of register storage instance
342     err = mbc_slave_set_descriptor(reg_area);
343     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
344                                     TAG,
345                                     "mbc_slave_set_descriptor fail, returns(0x%x).",
346                                     (uint32_t)err);
347 
348     // Initialization of Input Registers area
349     reg_area.type = MB_PARAM_INPUT;
350     reg_area.start_offset = MB_REG_INPUT_START_AREA0;
351     reg_area.address = (void*)&input_reg_params.input_data0;
352     reg_area.size = sizeof(float) << 2;
353     err = mbc_slave_set_descriptor(reg_area);
354     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
355                                         TAG,
356                                         "mbc_slave_set_descriptor fail, returns(0x%x).",
357                                         (uint32_t)err);
358     reg_area.type = MB_PARAM_INPUT;
359     reg_area.start_offset = MB_REG_INPUT_START_AREA1;
360     reg_area.address = (void*)&input_reg_params.input_data4;
361     reg_area.size = sizeof(float) << 2;
362     err = mbc_slave_set_descriptor(reg_area);
363     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
364                                         TAG,
365                                         "mbc_slave_set_descriptor fail, returns(0x%x).",
366                                         (uint32_t)err);
367 
368     // Initialization of Coils register area
369     reg_area.type = MB_PARAM_COIL;
370     reg_area.start_offset = MB_REG_COILS_START;
371     reg_area.address = (void*)&coil_reg_params;
372     reg_area.size = sizeof(coil_reg_params);
373     err = mbc_slave_set_descriptor(reg_area);
374     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
375                                     TAG,
376                                     "mbc_slave_set_descriptor fail, returns(0x%x).",
377                                     (uint32_t)err);
378 
379     // Initialization of Discrete Inputs register area
380     reg_area.type = MB_PARAM_DISCRETE;
381     reg_area.start_offset = MB_REG_DISCRETE_INPUT_START;
382     reg_area.address = (void*)&discrete_reg_params;
383     reg_area.size = sizeof(discrete_reg_params);
384     err = mbc_slave_set_descriptor(reg_area);
385     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
386                                     TAG,
387                                     "mbc_slave_set_descriptor fail, returns(0x%x).",
388                                     (uint32_t)err);
389 
390     // Set values into known state
391     setup_reg_data();
392 
393     // Starts of modbus controller and stack
394     err = mbc_slave_start();
395     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
396                                         TAG,
397                                         "mbc_slave_start fail, returns(0x%x).",
398                                         (uint32_t)err);
399     vTaskDelay(5);
400     return err;
401 }
402 
slave_destroy(void)403 static esp_err_t slave_destroy(void)
404 {
405     esp_err_t err = mbc_slave_destroy();
406     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
407                                 TAG,
408                                 "mbc_slave_destroy fail, returns(0x%x).",
409                                 (uint32_t)err);
410     return err;
411 }
412 
413 // An example application of Modbus slave. It is based on freemodbus stack.
414 // See deviceparams.h file for more information about assigned Modbus parameters.
415 // These parameters can be accessed from main application and also can be changed
416 // by external Modbus master host.
app_main(void)417 void app_main(void)
418 {
419     ESP_ERROR_CHECK(init_services());
420 
421     // Set UART log level
422     esp_log_level_set(TAG, ESP_LOG_INFO);
423 
424     mb_communication_info_t comm_info = { 0 };
425 
426 #if !CONFIG_EXAMPLE_CONNECT_IPV6
427     comm_info.ip_addr_type = MB_IPV4;
428 #else
429     comm_info.ip_addr_type = MB_IPV6;
430 #endif
431     comm_info.ip_mode = MB_MODE_TCP;
432 
433     comm_info.ip_port = MB_TCP_PORT_NUMBER;
434     ESP_ERROR_CHECK(slave_init(&comm_info));
435 
436     // The Modbus slave logic is located in this function (user handling of Modbus)
437     slave_operation_func(NULL);
438 
439     ESP_ERROR_CHECK(slave_destroy());
440     ESP_ERROR_CHECK(destroy_services());
441 }
442