1 /*
2  * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 // FreeModbus Master Example ESP32
8 
9 #include <string.h>
10 #include <sys/queue.h>
11 #include "esp_log.h"
12 #include "esp_system.h"
13 #include "esp_wifi.h"
14 #include "esp_event.h"
15 #include "esp_log.h"
16 #include "nvs_flash.h"
17 #include "esp_netif.h"
18 #include "mdns.h"
19 #include "protocol_examples_common.h"
20 
21 #include "modbus_params.h"  // for modbus parameters structures
22 #include "mbcontroller.h"
23 #include "sdkconfig.h"
24 
25 #define MB_TCP_PORT                     (CONFIG_FMB_TCP_PORT_DEFAULT)   // TCP port used by example
26 
27 // The number of parameters that intended to be used in the particular control process
28 #define MASTER_MAX_CIDS num_device_parameters
29 
30 // Number of reading of parameters from slave
31 #define MASTER_MAX_RETRY                (30)
32 
33 // Timeout to update cid over Modbus
34 #define UPDATE_CIDS_TIMEOUT_MS          (500)
35 #define UPDATE_CIDS_TIMEOUT_TICS        (UPDATE_CIDS_TIMEOUT_MS / portTICK_RATE_MS)
36 
37 // Timeout between polls
38 #define POLL_TIMEOUT_MS                 (1)
39 #define POLL_TIMEOUT_TICS               (POLL_TIMEOUT_MS / portTICK_RATE_MS)
40 #define MB_MDNS_PORT                    (502)
41 
42 // The macro to get offset for parameter in the appropriate structure
43 #define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1))
44 #define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1))
45 #define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1))
46 #define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1))
47 #define STR(fieldname) ((const char*)( fieldname ))
48 
49 // Options can be used as bit masks or parameter limits
50 #define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val }
51 
52 #define MB_ID_BYTE0(id) ((uint8_t)(id))
53 #define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF))
54 #define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF))
55 #define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF))
56 
57 #define MB_ID2STR(id) MB_ID_BYTE0(id), MB_ID_BYTE1(id), MB_ID_BYTE2(id), MB_ID_BYTE3(id)
58 
59 #if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
60 #define MB_DEVICE_ID (uint32_t)CONFIG_FMB_CONTROLLER_SLAVE_ID
61 #else
62 #define MB_DEVICE_ID (uint32_t)0x00112233
63 #endif
64 
65 #define MB_MDNS_INSTANCE(pref) pref"mb_master_tcp"
66 static const char *TAG = "MASTER_TEST";
67 
68 // Enumeration of modbus device addresses accessed by master device
69 // Each address in the table is a index of TCP slave ip address in mb_communication_info_t::tcp_ip_addr table
70 enum {
71     MB_DEVICE_ADDR1 = 1, // Slave address 1
72     MB_DEVICE_ADDR2 = 200,
73     MB_DEVICE_ADDR3 = 35
74 };
75 
76 // Enumeration of all supported CIDs for device (used in parameter definition table)
77 enum {
78     CID_INP_DATA_0 = 0,
79     CID_HOLD_DATA_0,
80     CID_INP_DATA_1,
81     CID_HOLD_DATA_1,
82     CID_INP_DATA_2,
83     CID_HOLD_DATA_2,
84     CID_RELAY_P1,
85     CID_RELAY_P2,
86     CID_COUNT
87 };
88 
89 // Example Data (Object) Dictionary for Modbus parameters:
90 // The CID field in the table must be unique.
91 // Modbus Slave Addr field defines slave address of the device with correspond parameter.
92 // Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such).
93 // Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly.
94 // The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value.
95 // Data Type, Data Size specify type of the characteristic and its data size.
96 // Parameter Options field specifies the options that can be used to process parameter value (limits or masks).
97 // Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc).
98 const mb_parameter_descriptor_t device_parameters[] = {
99     // { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode}
100     { CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2,
101                     INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
102     { CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2,
103             HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
104     { CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 2,
105             INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
106     { CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING, 2, 2,
107             HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
108     { CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR2, MB_PARAM_INPUT, 4, 2,
109             INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
110     { CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR3, MB_PARAM_HOLDING, 4, 2,
111             HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
112     { CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 0, 8,
113             COIL_OFFSET(coils_port0), PARAM_TYPE_U16, 2, OPTS( BIT1, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
114     { CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 8, 8,
115             COIL_OFFSET(coils_port1), PARAM_TYPE_U16, 2, OPTS( BIT0, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }
116 };
117 
118 // Calculate number of parameters in the table
119 const uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0]));
120 
121 // This table represents slave IP addresses that correspond to the short address field of the slave in device_parameters structure
122 // Modbus TCP stack shall use these addresses to be able to connect and read parameters from slave
123 char* slave_ip_address_table[] = {
124 #if CONFIG_MB_SLAVE_IP_FROM_STDIN
125     "FROM_STDIN",     // Address corresponds to MB_DEVICE_ADDR1 and set to predefined value by user
126     "FROM_STDIN",     // Corresponds to characteristic MB_DEVICE_ADDR2
127     "FROM_STDIN",     // Corresponds to characteristic MB_DEVICE_ADDR3
128     NULL              // End of table condition (must be included)
129 #elif CONFIG_MB_MDNS_IP_RESOLVER
130     NULL,
131     NULL,
132     NULL,
133     NULL
134 #endif
135 };
136 
137 const size_t ip_table_sz = (size_t)(sizeof(slave_ip_address_table) / sizeof(slave_ip_address_table[0]));
138 
139 #if CONFIG_MB_SLAVE_IP_FROM_STDIN
140 
141 // Scan IP address according to IPV settings
master_scan_addr(int * index,char * buffer)142 char* master_scan_addr(int* index, char* buffer)
143 {
144     char* ip_str = NULL;
145     unsigned int a[8] = {0};
146     int buf_cnt = 0;
147 #if !CONFIG_EXAMPLE_CONNECT_IPV6
148     buf_cnt = sscanf(buffer, "IP%d="IPSTR, index, &a[0], &a[1], &a[2], &a[3]);
149     if (buf_cnt == 5) {
150         if (-1 == asprintf(&ip_str, IPSTR, a[0], a[1], a[2], a[3])) {
151             abort();
152         }
153     }
154 #else
155     buf_cnt = sscanf(buffer, "IP%d="IPV6STR, index, &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7]);
156     if (buf_cnt == 9) {
157         if (-1 == asprintf(&ip_str, IPV6STR, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7])) {
158             abort();
159         }
160     }
161 #endif
162     return ip_str;
163 }
164 
master_get_slave_ip_stdin(char ** addr_table)165 static int master_get_slave_ip_stdin(char** addr_table)
166 {
167     char buf[128];
168     int index;
169     char* ip_str = NULL;
170     int buf_cnt = 0;
171     int ip_cnt = 0;
172 
173     if (!addr_table) {
174         return 0;
175     }
176 
177     ESP_ERROR_CHECK(example_configure_stdin_stdout());
178     while(1) {
179         if (addr_table[ip_cnt] && strcmp(addr_table[ip_cnt], "FROM_STDIN") == 0) {
180             printf("Waiting IP%d from stdin:\r\n", ip_cnt);
181             while (fgets(buf, sizeof(buf), stdin) == NULL) {
182                 fputs(buf, stdout);
183             }
184             buf_cnt = strlen(buf);
185             buf[buf_cnt - 1] = '\0';
186             fputc('\n', stdout);
187             ip_str = master_scan_addr(&index, buf);
188             if (ip_str != NULL) {
189                 ESP_LOGI(TAG, "IP(%d) = [%s] set from stdin.", ip_cnt, ip_str);
190                 if ((ip_cnt >= ip_table_sz) || (index != ip_cnt)) {
191                     addr_table[ip_cnt] = NULL;
192                     break;
193                 }
194                 addr_table[ip_cnt++] = ip_str;
195             } else {
196                 // End of configuration
197                 addr_table[ip_cnt++] = NULL;
198                 break;
199             }
200         } else {
201             if (addr_table[ip_cnt]) {
202                 ESP_LOGI(TAG, "Leave IP(%d) = [%s] set manually.", ip_cnt, addr_table[ip_cnt]);
203                 ip_cnt++;
204             } else {
205                 ESP_LOGI(TAG, "IP(%d) is not set in the table.", ip_cnt);
206                 break;
207             }
208         }
209     }
210     return ip_cnt;
211 }
212 
213 #elif CONFIG_MB_MDNS_IP_RESOLVER
214 
215 typedef struct slave_addr_entry_s {
216     uint16_t index;
217     char* ip_address;
218     uint8_t slave_addr;
219     void* p_data;
220     LIST_ENTRY(slave_addr_entry_s) entries;
221 } slave_addr_entry_t;
222 
223 LIST_HEAD(slave_addr_, slave_addr_entry_s) slave_addr_list = LIST_HEAD_INITIALIZER(slave_addr_list);
224 
225 // convert MAC from binary format to string
gen_mac_str(const uint8_t * mac,char * pref,char * mac_str)226 static inline char* gen_mac_str(const uint8_t* mac, char* pref, char* mac_str)
227 {
228     sprintf(mac_str, "%s%02X%02X%02X%02X%02X%02X", pref, MAC2STR(mac));
229     return mac_str;
230 }
231 
gen_id_str(char * service_name,char * slave_id_str)232 static inline char* gen_id_str(char* service_name, char* slave_id_str)
233 {
234     sprintf(slave_id_str, "%s%02X%02X%02X%02X", service_name, MB_ID2STR(MB_DEVICE_ID));
235     return slave_id_str;
236 }
237 
master_start_mdns_service()238 static void master_start_mdns_service()
239 {
240     char temp_str[32] = {0};
241     uint8_t sta_mac[6] = {0};
242     ESP_ERROR_CHECK(esp_read_mac(sta_mac, ESP_MAC_WIFI_STA));
243     char* hostname = gen_mac_str(sta_mac, MB_MDNS_INSTANCE("")"_", temp_str);
244     // initialize mDNS
245     ESP_ERROR_CHECK(mdns_init());
246     // set mDNS hostname (required if you want to advertise services)
247     ESP_ERROR_CHECK(mdns_hostname_set(hostname));
248     ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname);
249 
250     // set default mDNS instance name
251     ESP_ERROR_CHECK(mdns_instance_name_set(MB_MDNS_INSTANCE("esp32_")));
252 
253     // structure with TXT records
254     mdns_txt_item_t serviceTxtData[] = {
255         {"board","esp32"}
256     };
257 
258     // initialize service
259     ESP_ERROR_CHECK(mdns_service_add(MB_MDNS_INSTANCE(""), "_modbus", "_tcp", MB_MDNS_PORT, serviceTxtData, 1));
260     // add mac key string text item
261     ESP_ERROR_CHECK(mdns_service_txt_item_set("_modbus", "_tcp", "mac", gen_mac_str(sta_mac, "\0", temp_str)));
262     // add slave id key txt item
263     ESP_ERROR_CHECK( mdns_service_txt_item_set("_modbus", "_tcp", "mb_id", gen_id_str("\0", temp_str)));
264 }
265 
master_get_slave_ip_str(mdns_ip_addr_t * address,mb_tcp_addr_type_t addr_type)266 static char* master_get_slave_ip_str(mdns_ip_addr_t* address, mb_tcp_addr_type_t addr_type)
267 {
268     mdns_ip_addr_t* a = address;
269     char* slave_ip_str = NULL;
270 
271     while (a) {
272         if ((a->addr.type == ESP_IPADDR_TYPE_V6) && (addr_type == MB_IPV6)) {
273             if (-1 == asprintf(&slave_ip_str, IPV6STR, IPV62STR(a->addr.u_addr.ip6))) {
274                 abort();
275             }
276         } else if ((a->addr.type == ESP_IPADDR_TYPE_V4) && (addr_type == MB_IPV4)) {
277             if (-1 == asprintf(&slave_ip_str, IPSTR, IP2STR(&(a->addr.u_addr.ip4)))) {
278                 abort();
279             }
280         }
281         if (slave_ip_str) {
282             break;
283         }
284         a = a->next;
285     }
286     return slave_ip_str;
287 }
288 
master_resolve_slave(uint8_t short_addr,mdns_result_t * result,char ** resolved_ip,mb_tcp_addr_type_t addr_type)289 static esp_err_t master_resolve_slave(uint8_t short_addr, mdns_result_t* result, char** resolved_ip,
290                                         mb_tcp_addr_type_t addr_type)
291 {
292     if (!short_addr || !result || !resolved_ip) {
293         return ESP_ERR_INVALID_ARG;
294     }
295     mdns_result_t* r = result;
296     int t;
297     char* slave_ip = NULL;
298     char slave_name[22] = {0};
299 
300     if (sprintf(slave_name, "mb_slave_tcp_%02X", short_addr) < 0) {
301         ESP_LOGE(TAG, "Fail to create instance name for index: %d", short_addr);
302         abort();
303     }
304     for (; r ; r = r->next) {
305         if ((r->ip_protocol == MDNS_IP_PROTOCOL_V4) && (addr_type == MB_IPV6)) {
306             continue;
307         } else if ((r->ip_protocol == MDNS_IP_PROTOCOL_V6) && (addr_type == MB_IPV4)) {
308             continue;
309         }
310         // Check host name for Modbus short address and
311         // append it into slave ip address table
312         if ((strcmp(r->instance_name, slave_name) == 0) && (r->port == CONFIG_FMB_TCP_PORT_DEFAULT)) {
313             printf("  PTR : %s\n", r->instance_name);
314             if (r->txt_count) {
315                 printf("  TXT : [%u] ", r->txt_count);
316                 for ( t = 0; t < r->txt_count; t++) {
317                     printf("%s=%s; ", r->txt[t].key, r->txt[t].value?r->txt[t].value:"NULL");
318                 }
319                 printf("\n");
320             }
321             slave_ip = master_get_slave_ip_str(r->addr, addr_type);
322             if (slave_ip) {
323                 ESP_LOGI(TAG, "Resolved slave %s[%s]:%u", r->hostname, slave_ip, r->port);
324                 *resolved_ip = slave_ip;
325                 return ESP_OK;
326             }
327         }
328     }
329     *resolved_ip = NULL;
330     ESP_LOGD(TAG, "Fail to resolve slave: %s", slave_name);
331     return ESP_ERR_NOT_FOUND;
332 }
333 
master_create_slave_list(mdns_result_t * results,char ** addr_table,int addr_table_size,mb_tcp_addr_type_t addr_type)334 static int master_create_slave_list(mdns_result_t* results, char** addr_table,
335                                         int addr_table_size, mb_tcp_addr_type_t addr_type)
336 {
337     if (!results) {
338         return -1;
339     }
340     int i, slave_addr, cid_resolve_cnt = 0;
341     int ip_index = 0;
342     const mb_parameter_descriptor_t* pdescr = &device_parameters[0];
343     char** ip_table = addr_table;
344     char* slave_ip = NULL;
345     slave_addr_entry_t *it;
346 
347     for (i = 0; (i < num_device_parameters && pdescr); i++, pdescr++)
348     {
349         slave_addr = pdescr->mb_slave_addr;
350 
351         it = NULL;
352         // Is the slave address already registered?
353         LIST_FOREACH(it, &slave_addr_list, entries) {
354             if (slave_addr == it->slave_addr) {
355                 break;
356             }
357         }
358         if (!it) {
359             // Resolve new slave IP address using its short address
360             esp_err_t err = master_resolve_slave(slave_addr, results, &slave_ip, addr_type);
361             if (err != ESP_OK) {
362                 ESP_LOGE(TAG, "Index: %d, sl_addr: %d, failed to resolve!", i, slave_addr);
363                 // Set correspond index to NULL indicate host not resolved
364                 ip_table[ip_index] = NULL;
365                 continue;
366             }
367             // Register new slave address information
368             slave_addr_entry_t* new_slave_entry = (slave_addr_entry_t*) heap_caps_malloc(sizeof(slave_addr_entry_t),
369                                                        MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
370             MB_RETURN_ON_FALSE((new_slave_entry != NULL), ESP_ERR_NO_MEM,
371                                                  TAG, "Can not allocate memory for slave entry.");
372             new_slave_entry->index = i;
373             new_slave_entry->ip_address = slave_ip;
374             new_slave_entry->slave_addr = slave_addr;
375             new_slave_entry->p_data = NULL;
376             LIST_INSERT_HEAD(&slave_addr_list, new_slave_entry, entries);
377             ip_table[ip_index] = slave_ip;
378             ESP_LOGI(TAG, "Index: %d, sl_addr: %d, resolved to IP: [%s]",
379                                                 i, slave_addr, slave_ip);
380             cid_resolve_cnt++;
381             if (ip_index < addr_table_size) {
382                 ip_index++;
383             }
384         } else {
385             ip_table[ip_index] = it ? it->ip_address : ip_table[ip_index];
386             ESP_LOGI(TAG, "Index: %d, sl_addr: %d, set to IP: [%s]",
387                                     i, slave_addr, ip_table[ip_index]);
388             cid_resolve_cnt++;
389         }
390     }
391     ESP_LOGI(TAG, "Resolved %d cids, with %d IP addresses", cid_resolve_cnt, ip_index);
392     return cid_resolve_cnt;
393 }
394 
master_query_slave_service(const char * service_name,const char * proto,mb_tcp_addr_type_t addr_type)395 static int master_query_slave_service(const char * service_name, const char * proto,
396                                         mb_tcp_addr_type_t addr_type)
397 {
398     ESP_LOGI(TAG, "Query PTR: %s.%s.local", service_name, proto);
399 
400     mdns_result_t* results = NULL;
401     int count = 0;
402 
403     esp_err_t err = mdns_query_ptr(service_name, proto, 3000, 20, &results);
404     if(err){
405         ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err));
406         return count;
407     }
408     if(!results){
409         ESP_LOGW(TAG, "No results found!");
410         return count;
411     }
412 
413     count = master_create_slave_list(results, slave_ip_address_table, ip_table_sz, addr_type);
414 
415     mdns_query_results_free(results);
416     return count;
417 }
418 #endif
419 
master_destroy_slave_list(char ** table,size_t ip_table_size)420 static void master_destroy_slave_list(char** table, size_t ip_table_size)
421 {
422 #if CONFIG_MB_MDNS_IP_RESOLVER
423     slave_addr_entry_t *it;
424     LIST_FOREACH(it, &slave_addr_list, entries) {
425         LIST_REMOVE(it, entries);
426         free(it);
427     }
428 #endif
429     for (int i = 0; ((i < ip_table_size) && table[i] != NULL); i++) {
430         if (table[i]) {
431 #if CONFIG_MB_SLAVE_IP_FROM_STDIN
432             free(table[i]);
433             table[i] = "FROM_STDIN";
434 #elif CONFIG_MB_MDNS_IP_RESOLVER
435             table[i] = NULL;
436 #endif
437         }
438     }
439 }
440 
441 // The function to get pointer to parameter storage (instance) according to parameter description table
master_get_param_data(const mb_parameter_descriptor_t * param_descriptor)442 static void* master_get_param_data(const mb_parameter_descriptor_t* param_descriptor)
443 {
444     assert(param_descriptor != NULL);
445     void* instance_ptr = NULL;
446     if (param_descriptor->param_offset != 0) {
447        switch(param_descriptor->mb_param_type)
448        {
449            case MB_PARAM_HOLDING:
450                instance_ptr = ((void*)&holding_reg_params + param_descriptor->param_offset - 1);
451                break;
452            case MB_PARAM_INPUT:
453                instance_ptr = ((void*)&input_reg_params + param_descriptor->param_offset - 1);
454                break;
455            case MB_PARAM_COIL:
456                instance_ptr = ((void*)&coil_reg_params + param_descriptor->param_offset - 1);
457                break;
458            case MB_PARAM_DISCRETE:
459                instance_ptr = ((void*)&discrete_reg_params + param_descriptor->param_offset - 1);
460                break;
461            default:
462                instance_ptr = NULL;
463                break;
464        }
465     } else {
466         ESP_LOGE(TAG, "Wrong parameter offset for CID #%d", param_descriptor->cid);
467         assert(instance_ptr != NULL);
468     }
469     return instance_ptr;
470 }
471 
472 // User operation function to read slave values and check alarm
master_operation_func(void * arg)473 static void master_operation_func(void *arg)
474 {
475     esp_err_t err = ESP_OK;
476     float value = 0;
477     bool alarm_state = false;
478     const mb_parameter_descriptor_t* param_descriptor = NULL;
479 
480     ESP_LOGI(TAG, "Start modbus test...");
481 
482     for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) {
483         // Read all found characteristics from slave(s)
484         for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++)
485         {
486             // Get data from parameters description table
487             // and use this information to fill the characteristics description table
488             // and having all required fields in just one table
489             err = mbc_master_get_cid_info(cid, &param_descriptor);
490             if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
491                 void* temp_data_ptr = master_get_param_data(param_descriptor);
492                 assert(temp_data_ptr);
493                 uint8_t type = 0;
494                 err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key,
495                                                     (uint8_t*)&value, &type);
496                 if (err == ESP_OK) {
497                     *(float*)temp_data_ptr = value;
498                     if ((param_descriptor->mb_param_type == MB_PARAM_HOLDING) ||
499                         (param_descriptor->mb_param_type == MB_PARAM_INPUT)) {
500                         ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%x) read successful.",
501                                         param_descriptor->cid,
502                                         (char*)param_descriptor->param_key,
503                                         (char*)param_descriptor->param_units,
504                                         value,
505                                         *(uint32_t*)temp_data_ptr);
506                         if (((value > param_descriptor->param_opts.max) ||
507                             (value < param_descriptor->param_opts.min))) {
508                                 alarm_state = true;
509                                 break;
510                         }
511                     } else {
512                         uint16_t state = *(uint16_t*)temp_data_ptr;
513                         const char* rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
514                         ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %s (0x%x) read successful.",
515                                         param_descriptor->cid,
516                                         (char*)param_descriptor->param_key,
517                                         (char*)param_descriptor->param_units,
518                                         (const char*)rw_str,
519                                         *(uint16_t*)temp_data_ptr);
520                         if (state & param_descriptor->param_opts.opt1) {
521                             alarm_state = true;
522                             break;
523                         }
524                     }
525                 } else {
526                     ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = %d (%s).",
527                                         param_descriptor->cid,
528                                         (char*)param_descriptor->param_key,
529                                         (int)err,
530                                         (char*)esp_err_to_name(err));
531                 }
532                 vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
533             }
534         }
535         vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS);
536     }
537 
538     if (alarm_state) {
539         ESP_LOGI(TAG, "Alarm triggered by cid #%d.",
540                                         param_descriptor->cid);
541     } else {
542         ESP_LOGE(TAG, "Alarm is not triggered after %d retries.",
543                                         MASTER_MAX_RETRY);
544     }
545     ESP_LOGI(TAG, "Destroy master...");
546     vTaskDelay(100);
547 }
548 
init_services(mb_tcp_addr_type_t ip_addr_type)549 static esp_err_t init_services(mb_tcp_addr_type_t ip_addr_type)
550 {
551     esp_err_t result = nvs_flash_init();
552     if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) {
553       ESP_ERROR_CHECK(nvs_flash_erase());
554       result = nvs_flash_init();
555     }
556     MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
557                             TAG,
558                             "nvs_flash_init fail, returns(0x%x).",
559                             (uint32_t)result);
560     result = esp_netif_init();
561     MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
562                             TAG,
563                             "esp_netif_init fail, returns(0x%x).",
564                             (uint32_t)result);
565     result = esp_event_loop_create_default();
566     MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
567                             TAG,
568                             "esp_event_loop_create_default fail, returns(0x%x).",
569                             (uint32_t)result);
570 #if CONFIG_MB_MDNS_IP_RESOLVER
571     // Start mdns service and register device
572     master_start_mdns_service();
573 #endif
574     // This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
575     // Read "Establishing Wi-Fi or Ethernet Connection" section in
576     // examples/protocols/README.md for more information about this function.
577     result = example_connect();
578     MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
579                                 TAG,
580                                 "example_connect fail, returns(0x%x).",
581                                 (uint32_t)result);
582 #if CONFIG_EXAMPLE_CONNECT_WIFI
583    result = esp_wifi_set_ps(WIFI_PS_NONE);
584    MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
585                                    TAG,
586                                    "esp_wifi_set_ps fail, returns(0x%x).",
587                                    (uint32_t)result);
588 #endif
589 #if CONFIG_MB_MDNS_IP_RESOLVER
590     int res = 0;
591     for (int retry = 0; (res < num_device_parameters) && (retry < 10); retry++) {
592         res = master_query_slave_service("_modbus", "_tcp", ip_addr_type);
593     }
594     if (res < num_device_parameters) {
595         ESP_LOGE(TAG, "Could not resolve one or more slave IP addresses, resolved: %d out of %d.", res, num_device_parameters );
596         ESP_LOGE(TAG, "Make sure you configured all slaves according to device parameter table and they alive in the network.");
597         return ESP_ERR_NOT_FOUND;
598     }
599     mdns_free();
600 #elif CONFIG_MB_SLAVE_IP_FROM_STDIN
601     int ip_cnt = master_get_slave_ip_stdin(slave_ip_address_table);
602     if (ip_cnt) {
603         ESP_LOGI(TAG, "Configured %d IP addresse(s).", ip_cnt);
604     } else {
605         ESP_LOGE(TAG, "Fail to get IP address from stdin. Continue.");
606         return ESP_ERR_NOT_FOUND;
607     }
608 #endif
609     return ESP_OK;
610 }
611 
destroy_services(void)612 static esp_err_t destroy_services(void)
613 {
614     esp_err_t err = ESP_OK;
615     master_destroy_slave_list(slave_ip_address_table, ip_table_sz);
616 
617     err = example_disconnect();
618     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
619                                    TAG,
620                                    "example_disconnect fail, returns(0x%x).",
621                                    (uint32_t)err);
622     err = esp_event_loop_delete_default();
623     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
624                                        TAG,
625                                        "esp_event_loop_delete_default fail, returns(0x%x).",
626                                        (uint32_t)err);
627     err = esp_netif_deinit();
628     MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_NOT_SUPPORTED), ESP_ERR_INVALID_STATE,
629                                         TAG,
630                                         "esp_netif_deinit fail, returns(0x%x).",
631                                         (uint32_t)err);
632     err = nvs_flash_deinit();
633     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
634                                 TAG,
635                                 "nvs_flash_deinit fail, returns(0x%x).",
636                                 (uint32_t)err);
637     return err;
638 }
639 
640 // Modbus master initialization
master_init(mb_communication_info_t * comm_info)641 static esp_err_t master_init(mb_communication_info_t* comm_info)
642 {
643     void* master_handler = NULL;
644 
645     esp_err_t err = mbc_master_init_tcp(&master_handler);
646     MB_RETURN_ON_FALSE((master_handler != NULL), ESP_ERR_INVALID_STATE,
647                                 TAG,
648                                 "mb controller initialization fail.");
649     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
650                             TAG,
651                             "mb controller initialization fail, returns(0x%x).",
652                             (uint32_t)err);
653 
654     err = mbc_master_setup((void*)comm_info);
655     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
656                             TAG,
657                             "mb controller setup fail, returns(0x%x).",
658                             (uint32_t)err);
659 
660     err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters);
661     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
662                                 TAG,
663                                 "mb controller set descriptor fail, returns(0x%x).",
664                                 (uint32_t)err);
665     ESP_LOGI(TAG, "Modbus master stack initialized...");
666 
667     err = mbc_master_start();
668     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
669                             TAG,
670                             "mb controller start fail, returns(0x%x).",
671                             (uint32_t)err);
672     vTaskDelay(5);
673     return err;
674 }
675 
master_destroy(void)676 static esp_err_t master_destroy(void)
677 {
678     esp_err_t err = mbc_master_destroy();
679     MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
680                                 TAG,
681                                 "mbc_master_destroy fail, returns(0x%x).",
682                                 (uint32_t)err);
683     ESP_LOGI(TAG, "Modbus master stack destroy...");
684     return err;
685 }
686 
app_main(void)687 void app_main(void)
688 {
689     mb_tcp_addr_type_t ip_addr_type;
690 #if !CONFIG_EXAMPLE_CONNECT_IPV6
691     ip_addr_type = MB_IPV4;
692 #else
693     ip_addr_type = MB_IPV6;
694 #endif
695     ESP_ERROR_CHECK(init_services(ip_addr_type));
696 
697     mb_communication_info_t comm_info = { 0 };
698     comm_info.ip_port = MB_TCP_PORT;
699     comm_info.ip_addr_type = ip_addr_type;
700     comm_info.ip_mode = MB_MODE_TCP;
701     comm_info.ip_addr = (void*)slave_ip_address_table;
702     comm_info.ip_netif_ptr = (void*)get_example_netif();
703 
704     ESP_ERROR_CHECK(master_init(&comm_info));
705     vTaskDelay(50);
706 
707     master_operation_func(NULL);
708     ESP_ERROR_CHECK(master_destroy());
709     ESP_ERROR_CHECK(destroy_services());
710 }
711