Hi all,
I have an application where there are multiple edge devices (32 devices) connected to the master device and communicating over RS485 Protocol. Application flow -
The master devices has the Internet facility over Ethernet with W5500 Module and communicating with edge devices and get the data based the GET request it receives from Server. The edge device has an ultrasonic sensor with each one of them, reading the sensor data and sending it to the master based on the request received. I have attached the codes for both slave and master device.
The problem I am facing is the response time to get the data. It is a local server, there is no traffic on that network, the network connection is pretty stable but to receive the data, it takes variable amount of time - sometimes 80ms, sometimes 300ms and going up to 1 sec also.
Our application requirement is to get the data in less than 100 ms everytime.
Can you please let me know what I am doing wrong in the code or it is not possible to get the response in less than 100 ms ?
Minimal code for Slave Device - ATMEGA328PB
#include "Ultrasonic.h"
#include <EEPROM.h>
#include <SoftwareSerial.h>
#include <avr/wdt.h>
#include <avr/pgmspace.h>
#define DEVICE_ID_LOCATION_1 1
#define DEVICE_ID_LOCATION_2 2
#define DEVICE_ID_LOCATION_3 3
#define DEVICE_HEIGHT_LOCATION 5
#define DEVICE_VACATE_LED_COLOR_LOCATION 6
#define DEVICE_OCCUPIED_LED_COLOR_LOCATION 7
/*
Default Values for below parameters
*/
const uint16_t health_check_led_blink_interval = 2000;
/*
Reading 50 samples of sensor data periodically at 200ms and averaging it every 10 sec to get the accurate result
*/
const uint16_t sensor_status_read_interval = 2000;
const uint8_t sensor_sample_count = 5;
const uint8_t sensor_read_height_hysteresis = 7; //+/- 7cm considerable for object detection
/*
Timer interrupt variable to hold the elapsed time value
*/
unsigned long previous_health_check_led_millis = 0;
unsigned long previous_occupancy_sensor_millis = 0;
long prev_sensor_status_read_in_cm = 0;
long current_sensor_status_read_in_cm = 0;
uint8_t occupancy_detected_status = 0;
uint8_t sensor_send_distance_measured = 0;
byte current_led_color = 0;
uint8_t Current_sensor_sample = 0;
byte data_received_from_zonal[15];
int rcv_data_index = 0;
byte crc1, crc2;
bool data_rcv_flag = false;
enum sensor_req_cmd_frame {
GET_DEVICE_STATUS = 0,
GET_DEVICE_CONFIG,
SET_DEVICE_CONFIG,
};
Ultrasonic ultrasonic(occupancy_sensor_Pin);
// Set up a new SoftwareSerial object
SoftwareSerial mySerial(8, 9);
void sensor_send_data_over_modbus(uint8_t req_data_type, uint8_t data[] = {0}, uint8_t size_of_data = 0);
void update_health_check_led_state() {
unsigned long current_led_check_time = millis();
if (health_check_led_state == LOW) {
//If the led is OFF, we must wait for the interval to expire before turning it ON
if (current_led_check_time - previous_health_check_led_millis >= health_check_led_blink_interval) {
//time is up so change the state to HIGH
health_check_led_state = HIGH;
digitalWrite(health_check_led_Pin, health_check_led_state);
//and save the time when we made the changes
previous_health_check_led_millis = current_led_check_time;
}
}
else { //i.e. if health_check_led_state is HIGH
//if led is ON, we must wait for the interval to expire before turning it OFF
if (current_led_check_time - previous_health_check_led_millis >= health_check_led_blink_interval) {
//time is up so change the state to LOW
health_check_led_state = LOW;
digitalWrite(health_check_led_Pin, health_check_led_state);
//and save the time when we made the changes
previous_health_check_led_millis = current_led_check_time;
}
}
}
void occupancy_sensor_Status_read() {
unsigned long current_sensor_read_time = millis();
uint8_t vacate_led_color = EEPROM.read(DEVICE_VACATE_LED_COLOR_LOCATION);
uint8_t occupied_led_color = EEPROM.read(DEVICE_OCCUPIED_LED_COLOR_LOCATION);
uint16_t sensor_height_read = EEPROM.read(DEVICE_HEIGHT_LOCATION);
sensor_height_read = sensor_height_read * 10; //to get the height in cm
uint16_t min_sensor_height_read = sensor_height_read - sensor_read_height_hysteresis;
uint16_t max_sensor_height_read = sensor_height_read + sensor_read_height_hysteresis;
/*
This only reads sensor state after the sensor interval has elapsed
this also avoids multiple flashes if the sensor bounces
*/
if (current_sensor_read_time - previous_occupancy_sensor_millis > sensor_status_read_interval) {
previous_occupancy_sensor_millis = current_sensor_read_time;
if(data_rcv_flag == false){
current_sensor_status_read_in_cm = ultrasonic.MeasureInCentimeters(500000);
}
if (Current_sensor_sample < sensor_sample_count) {
prev_sensor_status_read_in_cm += current_sensor_status_read_in_cm;
Current_sensor_sample++;
}
else if (Current_sensor_sample == sensor_sample_count) {
prev_sensor_status_read_in_cm = prev_sensor_status_read_in_cm / sensor_sample_count;
mySerial.print("Total Distance read in cm: ");
mySerial.println(prev_sensor_status_read_in_cm);
sensor_send_distance_measured = prev_sensor_status_read_in_cm / 10;
//if object detected
if ( prev_sensor_status_read_in_cm <= min_sensor_height_read) {
occupancy_detected_status = sensor_slot_occupied_data_frame;
current_led_color = occupied_led_color;
if ( occupied_led_color == sensor_green_led_color_data_frame ) {
digitalWrite( led_indicator_red_Pin, LOW );
digitalWrite( led_indicator_green_Pin, HIGH );
}
else if ( occupied_led_color == sensor_red_led_color_data_frame ) {
digitalWrite( led_indicator_green_Pin, LOW );
digitalWrite( led_indicator_red_Pin, HIGH );
}
}
//if object isn't detected
else if (prev_sensor_status_read_in_cm >= max_sensor_height_read) {
occupancy_detected_status = sensor_slot_empty_data_frame;
current_led_color = vacate_led_color;
if ( vacate_led_color == sensor_green_led_color_data_frame ) {
digitalWrite( led_indicator_red_Pin, LOW );
digitalWrite( led_indicator_green_Pin, HIGH );
}
else if ( vacate_led_color == sensor_red_led_color_data_frame ) {
digitalWrite( led_indicator_green_Pin, LOW );
digitalWrite( led_indicator_red_Pin, HIGH );
}
}
Current_sensor_sample = 0;
prev_sensor_status_read_in_cm = 0;
}
}
}
void zonal_recv_data_over_modbus() {
rcv_data_index = 0;
data_rcv_flag = false;
while (Serial.available()) {
byte byteArray[sizeof(byte)];
Serial.readBytes(byteArray, sizeof(byteArray));
mySerial.println(byteArray[0], HEX);
if (byteArray[0] == zonal_start_frame) {
data_rcv_flag = true;
rcv_data_index = 0;
}
data_received_from_zonal[rcv_data_index] = byteArray[0];
rcv_data_index = rcv_data_index + 1;
if (byteArray[0] == zonal_stop_frame) {
break;
}
}
if (rcv_data_index >= zonal_get_req_frame_size && rcv_data_index <= zonal_set_req_frame_size && data_rcv_flag == true ) {
uint8_t device_id[] = {device_id_1, device_id_2, device_id_3};
if ( data_received_from_zonal[0] == zonal_start_frame and data_received_from_zonal[rcv_data_index - 1] == zonal_stop_frame ) {
if (data_received_from_zonal[1] == device_id_1 && data_received_from_zonal[2] == device_id_2 && data_received_from_zonal[3] == device_id_3 ) {
if (rcv_data_index == zonal_get_req_frame_size) {
if ( data_received_from_zonal[4] == zonal_get_status_cmd_frame && data_received_from_zonal[5] == zonal_default_get_request_data ) {
uint8_t data[] = {zonal_default_get_request_data};
zonal_sensor_crc_calculation(&crc1, &crc2, device_id, zonal_get_status_cmd_frame, data, sizeof(data));
if (crc1 == data_received_from_zonal[6] && crc2 == data_received_from_zonal[7]) {
sensor_send_data_over_modbus(GET_DEVICE_STATUS);
}
}
else if (data_received_from_zonal[4] == zonal_get_config_cmd_frame && data_received_from_zonal[5] == zonal_default_get_request_data ) {
uint8_t data[] = {zonal_default_get_request_data};
zonal_sensor_crc_calculation(&crc1, &crc2, device_id, zonal_get_config_cmd_frame, data, sizeof(data));
if (crc1 == data_received_from_zonal[6] && crc2 == data_received_from_zonal[7]) {
sensor_send_data_over_modbus(GET_DEVICE_CONFIG);
}
}
}
else if (rcv_data_index == zonal_set_req_frame_size && data_received_from_zonal[4] == zonal_set_config_cmd_frame) {
byte height_threshold_value = data_received_from_zonal[5];
byte vacate_led_color = data_received_from_zonal[6];
byte occupied_led_color = data_received_from_zonal[7];
uint8_t data[] = {height_threshold_value, vacate_led_color, occupied_led_color};
zonal_sensor_crc_calculation(&crc1, &crc2, device_id, zonal_set_config_cmd_frame, data, sizeof(data));
if (crc1 == data_received_from_zonal[8] && crc2 == data_received_from_zonal[9]) {
//mySerial.println(sensor_min_height_threshold);
//mySerial.println(height_threshold_value);
if (height_threshold_value >= sensor_min_height_threshold && height_threshold_value <= sensor_max_height_threshold) {
EEPROM.update(DEVICE_HEIGHT_LOCATION, height_threshold_value);
}
else {
data[0] = sensor_fail_write_data_response;
}
if (vacate_led_color == sensor_green_led_color_data_frame || vacate_led_color == sensor_red_led_color_data_frame ) {
EEPROM.update(DEVICE_VACATE_LED_COLOR_LOCATION, vacate_led_color);
}
else {
data[1] = sensor_fail_write_data_response;
}
if (occupied_led_color == sensor_green_led_color_data_frame || occupied_led_color == sensor_red_led_color_data_frame) {
EEPROM.update(DEVICE_OCCUPIED_LED_COLOR_LOCATION, occupied_led_color);
}
else {
data[2] = sensor_fail_write_data_response;
}
uint8_t size_of_data = sizeof(data);
sensor_send_data_over_modbus(SET_DEVICE_CONFIG, data, size_of_data);
}
}
}
}
}
}
void sensor_send_data_over_modbus(uint8_t req_data_type, uint8_t data[], uint8_t size_of_data) {
uint8_t device_id[] = {device_id_1, device_id_2, device_id_3} ;
delay(10);
digitalWrite(modbus_enable_Pin, HIGH);
if (req_data_type == GET_DEVICE_STATUS) {
char distance_measured_hex[5];
sprintf(distance_measured_hex, "%2X", sensor_send_distance_measured);
uint8_t data_to_be_send[] = {occupancy_detected_status, sensor_send_distance_measured, current_led_color};
zonal_sensor_crc_calculation(&crc1, &crc2, device_id, zonal_get_status_cmd_frame, data_to_be_send, sizeof(data_to_be_send));
byte sendData[] = {sensor_start_frame, device_id_1, device_id_2, device_id_3, zonal_get_status_cmd_frame, data_to_be_send[0], data_to_be_send[1], data_to_be_send[2], crc1, crc2, sensor_stop_frame};
for (int i = 0; i < sizeof(sendData); i++) {
Serial.write(sendData[i]);
}
}
else if (req_data_type == GET_DEVICE_CONFIG) {
byte height_threshold = EEPROM.read(DEVICE_HEIGHT_LOCATION);
byte vacate_led_color = EEPROM.read(DEVICE_VACATE_LED_COLOR_LOCATION);
byte occupied_led_color = EEPROM.read(DEVICE_OCCUPIED_LED_COLOR_LOCATION);
uint8_t data_to_be_send[] = {height_threshold, vacate_led_color, occupied_led_color};
zonal_sensor_crc_calculation(&crc1, &crc2, device_id, zonal_get_config_cmd_frame, data_to_be_send, sizeof(data_to_be_send));
byte sendData[] = {sensor_start_frame, device_id_1, device_id_2, device_id_3, zonal_get_config_cmd_frame, data_to_be_send[0], data_to_be_send[1], data_to_be_send[2], crc1, crc2, sensor_stop_frame};
for (int i = 0; i < sizeof(sendData); i++) {
Serial.write(sendData[i]);
}
}
else if (req_data_type == SET_DEVICE_CONFIG and size_of_data == 3) {
zonal_sensor_crc_calculation(&crc1, &crc2, device_id, zonal_set_config_cmd_frame, data, size_of_data);
byte sendData[] = {sensor_start_frame, device_id_1, device_id_2, device_id_3, zonal_set_config_cmd_frame, data[0], data[1], data[2], crc1, crc2, sensor_stop_frame};
for (int i = 0; i < sizeof(sendData); i++) {
Serial.write(sendData[i]);
}
}
Serial.flush();
digitalWrite(modbus_enable_Pin, LOW);
}
void setup() {
Serial.begin(9600);
mySerial.begin(9600);
Serial.flush();
pinMode(occupancy_sensor_Pin, INPUT);
pinMode(led_indicator_red_Pin, OUTPUT);
pinMode(led_indicator_green_Pin, OUTPUT);
pinMode(health_check_led_Pin, OUTPUT);
pinMode(modbus_enable_Pin, OUTPUT);
delay(10);
digitalWrite(modbus_enable_Pin, LOW);
delayMicroseconds(50);
//Read the sensor address from the eeprom location
device_id_1 = EEPROM.read(DEVICE_ID_LOCATION_1);
device_id_2 = EEPROM.read(DEVICE_ID_LOCATION_2);
device_id_3 = EEPROM.read(DEVICE_ID_LOCATION_3);
mySerial.print("Device ID is: ");
mySerial.println(String(device_id_1, HEX) + String(device_id_2, HEX) + String(device_id_3, HEX));
}
void loop() {
update_health_check_led_state();
occupancy_sensor_Status_read();
zonal_recv_data_over_modbus();
}
Master Code - Minimal (ATMEGA2560)
#include <SPI.h>
#include <Ethernet.h>
#include <ArduinoJson.h>
#include <avr/io.h>
#include <util/delay.h>
#include <SoftwareSerial.h>
#define RS485Transmit HIGH
#define RS485Receive LOW
const int modbus_enable_pin = 2;
const int ethernet_cs_pin = 53;
byte mac[] = {
0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02
};
const int MAX_HEX_VALUES = 15; // Adjust this based on the maximum number of hex values you expect to receive
byte json_get_status_data[MAX_HEX_VALUES];
byte data_received_from_sensor[30];
uint8_t rcv_data_index = 0;
bool data_send_rcv_flag = false;
EthernetServer server(4812);
IPAddress zonal_ip;
IPAddress local_server_IP(192, 168, 4, x);
int local_server_port = 80;
unsigned long previousMillis = 0;
const long interval = 300000; // Interval in milliseconds (e.g., 5000ms = 5 seconds)
void setup() {
Serial1.begin(9600);
Serial.begin(9600);
pinMode(modbus_enable_pin, OUTPUT);
Ethernet.init(ethernet_cs_pin);
delay(500);
Serial.println("Zonal started");
// start the Ethernet connection and the server:
if (Ethernet.begin(mac) == 0) {
Serial.println("Failed to configure Ethernet using DHCP");
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :(");
} else if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected.");
}
while (true) {
delay(1);
}
}
// start the server
server.begin();
zonal_ip = Ethernet.localIP();
Serial.println(zonal_ip);
}
void loop() {
// listen for incoming clients
EthernetClient client = server.available();
zonal_rcv_data_from_server(client);
// Check if it's time to send health check data
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// Send health check data to the client
Serial.println("Health check send");
sendHealthCheckData();
previousMillis = currentMillis;
}
}
void zonal_rcv_data_from_server(EthernetClient client) {
if (client) {
client.setTimeout(10);
String request = client.readStringUntil('\r');
client.flush();
if (request.indexOf("/get-zonal-mac-ip") != -1) {
sendZonalMacIpJsonResponse(client);
} else if (request.indexOf("/get-zonal-data-status") != -1) {
zonal_send_data_to_sensor(client);
} else if (request.indexOf("/get-zonal-data-config") != -1) {
zonal_send_data_to_sensor(client);
} else if (request.indexOf("/set-zonal-data-config") != -1) {
zonal_send_data_to_sensor(client);
} else {
// Send a default response for other URLs
sendDefaultJsonResponse(client);
}
// Close the connection
delay(1);
client.stop();
}
}
void zonal_send_data_to_sensor(EthernetClient client) {
// Read the JSON data from the request
String jsonData = "";
rcv_data_index = 0;
bool data_rcv_flag = false;
while (client.available()) {
jsonData = client.readStringUntil('\n');
if (jsonData == "\r") {
break;
}
}
jsonData = client.readString(); //PAYLOAD
//const size_t bufferSize = JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(32) + 32 * JSON_ARRAY_SIZE(9);
// Parse the JSON data
StaticJsonDocument<200> jsonDoc;
DeserializationError error = deserializeJson(jsonDoc, jsonData);
if (error) {
Serial.println("Error processing json");
Serial.println(error.f_str());
return;
}
const char* value = jsonDoc["data"];
Serial.println(value);
// Print the key and value
int hexCount = 9; //byte_calculate(value);
// Allocate memory for the byte array
byte sendByte[hexCount];
// Convert the string to a byte array
char* token;
char* rest = (char*)value;
int index = 0;
while ((token = strtok_r(rest, ", ", &rest))) {
sendByte[index++] = strtol(token, NULL, 0);
}
Serial.println('2');
digitalWrite(modbus_enable_pin, RS485Transmit);
Serial1.write(sendByte, hexCount);
Serial1.flush();
delay(5);
digitalWrite(modbus_enable_pin, RS485Receive);
delay(10);
bool flag = false;
Serial.println('3');
while (Serial1.available() > 0) {
byte receivedByte = Serial1.read();
Serial.println(receivedByte, HEX);
if (receivedByte == 0xEE) {
flag = false;
data_rcv_flag = true;
rcv_data_index = 0;
}
if (flag == false) {
data_received_from_sensor[rcv_data_index] = receivedByte;
rcv_data_index = rcv_data_index + 1;
}
if (receivedByte == 0xAA) {
flag = true;
}
}
if (data_rcv_flag == true and rcv_data_index == 11) {
StaticJsonDocument<200> recvJson;
String byteArrayString = byteArrayToString(data_received_from_sensor, rcv_data_index);
recvJson["status"] = "1";
recvJson["sensor_data"] = byteArrayString;
String jsonString;
serializeJson(recvJson, jsonString);
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: application/json");
client.println();
client.println(jsonString);
}
else {
StaticJsonDocument<200> jsonDoc;
jsonDoc["status"] = "1";
jsonDoc["sensor_data"] = "";
String jsonString;
serializeJson(jsonDoc, jsonString);
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: application/json");
client.println();
client.println(jsonString);
}
}
int byte_calculate(char* value) {
// Count the number of bytes based on the number of comma separators + 1
int numBytes = 1;
for (int i = 0; value[i] != '\0'; i++) {
if (value[i] == ',') {
numBytes++;
}
}
return numBytes;
}
String byteArrayToString(byte* byteArray, int size) {
String result = "";
for (int i = 0; i < size; i++) {
// Convert each byte to a two-digit hexadecimal representation
if (byteArray[i] < 0x10) {
result += "0"; // Add leading zero for single-digit values
}
result += String(byteArray[i], HEX);
result += ","; // Add a space separator
}
result.toUpperCase();
return result;
}
void sendZonalMacIpJsonResponse(EthernetClient client) {
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// Create a JSON object for zonal MAC and IP
StaticJsonDocument<200> jsonDoc;
jsonDoc["mac"] = macStr;
jsonDoc["zonal_ip"] = zonal_ip;
// Serialize the JSON object to a string
String jsonString;
serializeJson(jsonDoc, jsonString);
// Send HTTP headers
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: application/json");
//client.println("Connection: close");
client.println();
// Send the JSON response
client.println(jsonString);
}
void sendDefaultJsonResponse(EthernetClient client) {
// Create a JSON object for default response
StaticJsonDocument<200> jsonDoc;
jsonDoc["message"] = "Invalid URL";
jsonDoc["error"] = "The requested URL is not supported";
// Serialize the JSON object to a string
String jsonString;
serializeJson(jsonDoc, jsonString);
// Send HTTP headers
client.println("HTTP/1.1 404 Not Found");
client.println("Content-Type: application/json");
//client.println("Connection: close");
client.println();
// Send the JSON response
client.println(jsonString);
}
void sendHealthCheckData() {
EthernetClient client;
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// Create a JSON object for zonal MAC and IP
StaticJsonDocument<200> jsonDoc;
jsonDoc["mac_address"] = macStr;
jsonDoc["ip_address"] = zonal_ip;
// Serialize the JSON object to a string
String jsonString;
serializeJson(jsonDoc, jsonString);
// Send HTTP headers
if (client.connect(local_server_IP, local_server_port)) {
Serial.println("connected");
client.println("POST http://192.168.4.x:80/api/v1/pgs/mac_ip HTTP/1.1");
client.println("Host: 192.168.4.x");
client.println("User-Agent: Arduino/1.0");
client.println("Connection: close");
client.print("Content-Length: ");
client.println(jsonString.length());
client.println();
client.println(jsonString);
} else {
Serial.println("connection failed");
}
client.stop();
}
Any help will be appreciated. Also, sometimes, getting the blank data also if fetching in less time.