diff --git a/src/mqtt_esp32/mqtt_esp32.ino b/src/mqtt_esp32/mqtt_esp32.ino new file mode 100644 index 0000000000000000000000000000000000000000..06d73765579118095b16077ae5aaafc9c09cb2d8 --- /dev/null +++ b/src/mqtt_esp32/mqtt_esp32.ino @@ -0,0 +1,466 @@ +#include <WiFi.h> +#include <PubSubClient.h> +#include <HardwareSerial.h> + +#define MATRIX_SIZE 10 // 10x10 +#define IMG_DATA_SIZE MATRIX_SIZE * MATRIX_SIZE * 3 //DATA SIZE FOR ONE MATRIX = RGB * MATRIX_SIZE +#define BUFFER_SIZE 128 +#define CLUSTER "cluster1" + +// WiFi +const char *ssid = "uni-ete2"; // Enter your WiFi name +const char *password = "uni-ete2-esp32"; // Enter WiFi password + +// MQTT Broker +const char *mqtt_broker = "192.168.1.101"; +const char *topic_send = "fromesp"; +const char *topic_img = CLUSTER"/image"; +const char *topic_txt = CLUSTER"/text"; +const char *topic_size = CLUSTER"/size"; +const int mqtt_port = 1883; + +// WIFI & MQTT +WiFiClient espClient; +PubSubClient client(espClient); + +// UART2 +HardwareSerial SerialPort(2); // use UART2 +char rcv_buffer[BUFFER_SIZE] = {0}; + +// MATRIX CLUSTER SIZE +uint8_t cluster_size_x = 1; +uint8_t cluster_size_y = 1; + +/** + *@brief + Setup all the peripherals of the ESP32 (UART2 & Software serial). + It will connect automatically to an Hardcoded wifi router, + then it will connect with the MQTT broker. + Finally it will publish a message to the topic, + and will subscribe to the needed sub-topic. + + *@params + void + *@return + void +*/ +void setup() +{ + // Set software serial baud to 115200; + Serial.begin(115200); + + // Set UART2 serial + SerialPort.begin(115200, SERIAL_8N1, 16, 17); // RX pin: 16, TX pin: 17 + + // Connecting to a WiFi network + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.println("Connecting to WiFi.."); + } + Serial.println("Connected to the Wi-Fi network"); + + // Connecting to a mqtt broker + client.setServer(mqtt_broker, mqtt_port); + client.setCallback(callback); + client.setBufferSize(2000); + while (!client.connected()) + { + String client_id = "esp32-client-"; + client_id += String(WiFi.macAddress()); + Serial.printf("The client %s connects to the public MQTT broker\n", client_id.c_str()); + if (client.connect(client_id.c_str())) + { + Serial.println("Public EMQX MQTT broker connected"); + } else + { + Serial.print("failed with state "); + Serial.print(client.state()); + delay(2000); + } + } + + // Publish and subscribe + client.publish(topic_send, "Hi, I'm ESP32 ^^"); + client.subscribe(topic_img); + client.subscribe(topic_txt); +} + +/** + *@brief + Send an UART message in Pin TX 17 from UART2. + The message will follow the Hepialight2 custom UART format. + + Format : + + Header : [0x77] + Len : [0x01-0xFF] + Data[0] : [0x00-0xFF] + Data[n] : [0x00-0xFF] + Crc : [0x00-0xFF] = Data[0] ^ Data[1] ^ Data[n] ^ Len + Footer : [0xAA] + + *@params + data : data to send to the Hepialight2 + length : length of the data + *@return + void +*/ +void send_uart_data(const char* data, uint8_t lenght) +{ + // SEND UART PACKET HEADER + SerialPort.print((char)0x77); + SerialPort.print((char)lenght); + + // SEND DATA AND COMPUTE CRC + uint8_t crc = 0; + for(int i = 0; i < lenght; i++) + { + if(data[i] == 0x77 || data[i] == 0x10 || data[i] == 0xAA) + { + crc = crc ^ 0x10; + SerialPort.print((char)0x10); + } + crc = crc ^ data[i]; + SerialPort.print((char)data[i]); + } + + // SEND UART PACKET FOOTER + SerialPort.print((char)(crc ^ lenght)); + SerialPort.print((char)0xAA); +} + +/** + *@brief + Receive an UART message in Pin RX 17 from UART2. + The received message should follow the Hepialight2 custom UART format. + + Format : + + Header : [0x77] + Len : [0x01-0xFF] + Data[0] : [0x00-0xFF] + Data[n] : [0x00-0xFF] + Crc : [0x00-0xFF] = Data[0] ^ Data[1] ^ Data[n] ^ Len + Footer : [0xAA] + + *@params + received_buffer : Will fill the bufeer with + a null-terminated string with the received data + *@return + void +*/ +void receive_uart_data(char* received_buffer) +{ + static bool receiving_data = false; // Indicator for data reception + static uint8_t buffer[BUFFER_SIZE]; // Receive buffer + static int buffer_index = 0; // Current buffer index + static int message_length = 0; // Length of the currently received message + static uint8_t expected_crc = 0; // Expected CRC + uint8_t received_crc = 0; + bool ctrl_byte = false; + + + while(SerialPort.available()) + { + uint8_t received_byte = SerialPort.read(); + + switch(received_byte) + { + case 0x77: + // IF 0x10 just before + if(ctrl_byte) + { + buffer[buffer_index++] = received_byte; + ctrl_byte = false; + continue; + } + + // UART HEADER + buffer_index = 0; + receiving_data = false; + break; + case 0xAA: + // IF 0x10 just before + if(ctrl_byte) + { + buffer[buffer_index++] = received_byte; + ctrl_byte = false; + continue; + } + + // UART HEADER + receiving_data = false; + if(expected_crc == received_crc) + { + memcpy(received_buffer, buffer, message_length); + buffer[buffer_index] = 0; + return; + } + break; + case 0x10: + // IF 0x10 just before + if(!ctrl_byte) + { + ctrl_byte = true; + continue; + } + + // DATA + buffer[buffer_index++] = received_byte; + ctrl_byte = false; + break; + default: + // DATA LEN + if(!receiving_data) + { + message_length = received_byte; + expected_crc = received_byte; // Expected CRC is initialized with the message length + receiving_data = true; + continue; + } + + // DATA + // GET MESSAGE CRC + if(buffer_index == message_length) + { + received_crc = received_byte; + continue; + } + + // Store message bytes in the buffer + buffer[buffer_index++] = received_byte; + expected_crc ^= received_byte; // Calculate expected CRC + break; + } + } +} + +/** + *@brief + Convert the 2d index to a 1d index. + + *@params + x : x index on a 2d space. + x : y index on a 2d space. + pitch : the lenght of the 2d space. + *@return + int : 1d array index +*/ +int d2_to_flat(uint8_t x, uint8_t y, uint32_t pitch) +{ + return (x * pitch) + y; +} + +/** + *@brief + Remove the header from the ppm image received from + a mqtt message. + + *@params + packet : mqtt packet data that contains the ppm image. + lenght : length of the packet data. + *@return + uint8_t* : returns the mqtt packet pointer offset by the header. +*/ +uint8_t* remove_header_from_ppm(uint8_t* packet, unsigned int* length) +{ + int cnt = 0; + for(int i = 0; i < *length; i++) + { + if(packet[i] == '\n') + { + cnt++; + } + + if(cnt >= 3) + { + *length -= i - 1; + return packet + i + 1; + } + } + + return NULL; +} + +/** + *@brief + Send the received ppm image to the Hepialight 2 by UART2. + The hepialight2 is adressed by his ID on a 2d space (Example : (0, 0)). + The 24 bits RGB data are refactored in 16 bits data (Hepialigt2 buffer size is limited). + + R : 8 bits -> 5 bits + G : 8 bits -> 6 bits + B : 8 bits -> 5 bits + + *@params + idx : Hepialight matrix ID-x. + idy : Hepialight matrix ID-y. + data : PPM image data (without PPM header). + lenght : length of the data. + *@return + void +*/ +void send_data_to_matrix(uint8_t idx, uint8_t idy, uint8_t* data, uint32_t length) +{ + uint8_t raw_data[220]; + char header[] = "PT;x,y;IMAGE;"; + strcpy((char*)raw_data, header); + raw_data[3] = '0' + idx; + raw_data[5] = '0' + idy; + + uint8_t send_len = 13; + + // RGB DATA PROCESS + // HARDCODED + for(int x = (cluster_size_y - 1 - idy) * 10; x < (cluster_size_y - idy) * 10; x+=1) + { + for(int y = idx * 30; y < (idx+1) * 30; y+=3) + { + // Serial.println() + int i = d2_to_flat(x, y, 30 * cluster_size_y); + + // COMPUTE COLOR IN 16 BITS + uint8_t r = data[i] & 0x1F; + uint8_t g = data[i+1] & 0x3F; + uint8_t b = data[i+2] & 0x1F ; + uint16_t rgb = r << 11 | g << 5 | b; + + // SPLIT ON 8 BITS + uint8_t rgb_0 = rgb >> 8; + uint8_t rgb_1 = rgb & 0xFF; + + // SEND RGB + raw_data[send_len++] = rgb_0; + raw_data[send_len++] = rgb_1; + } + } + + // SEND UART PACKET HEADER + send_uart_data((char*)raw_data, send_len); +} + +/** + *@brief + Callback function called when the ESP32 receive a mqtt packet. + Only when the ESP32 is subscribed to the right topic. + *@params + topic : The concerned topic. + payload : Received packet. + data : length of the received packet. + *@return + void +*/ +void callback(char *topic, byte *payload, unsigned int length) +{ + // TERMINAL SERIAL + Serial.print("Message arrived in topic: "); + Serial.println(topic); + + if(strcmp(topic, topic_txt) == 0) + { + char data[100]; + char header[] = "PT;0,0;MOVING;16711680;0.1;"; + memcpy(data, header, strlen(header) + 1); + + // MATRIX ID + data[3] = '0' + cluster_size_x - 1; + data[5] = '0'; + + strncat(data, (char*)payload, length); + uint32_t len = strlen(data); + + // SEND UART PACKET HEADER + SerialPort.print((char)0x77); + SerialPort.print((char)len); + uint8_t crc = 0; + + for(int i = 0; i < len; i++) + { + if(data[i] == 0x77 || data[i] == 0x10 || data[i] == 0xAA) + { + crc = crc ^ 0x10; + SerialPort.print((char)0x10); + } + crc = crc ^ data[i]; + SerialPort.print((char)data[i]); + } + + // SEND UART PACKET FOOTER + SerialPort.print((char)(crc ^ len)); + SerialPort.print((char)0xAA); + + return; + } + + // GET RAW DATA + uint8_t* raw_data = remove_header_from_ppm(payload, &length); + if(raw_data == NULL) + { + return; + } + + // SEND UART PACKET FOR EACH MATRIX + for(int x_mat = 0; x_mat < cluster_size_x; x_mat++) + { + for(int y_mat = cluster_size_y - 1; y_mat >= 0; y_mat--) + { + // SEND UART PACKET + send_data_to_matrix(x_mat, y_mat, raw_data, IMG_DATA_SIZE); + } + } +} + +/** + *@brief + Main loop. Handle the mqtt callback function. + When the ESP32 receive a "ESP" UART message, it will respond "OK". + When the ESP32 receive a "MATSIZE" message, + it will update the hepialight2 cluster size + and will publish to the /size subtopic to signal the broker. + + *@params + idx : Hepialight matrix ID-x + idy : Hepialight matrix ID-y + data : PPM image data (without PPM header) + lenght : length of the data. + *@return + void +*/ +void loop() +{ + client.loop(); + receive_uart_data(rcv_buffer); // READ UART BUFFER + + // CHECK PRESENCE FROM HEPIALIGHT + if(strcmp(rcv_buffer, "ESP") == 0) + { + Serial.println(rcv_buffer); + rcv_buffer[0] = 0; // CLEAR BUFFER + send_uart_data("OK", 2); // SEND OK + } + + // SET MATRIX SIZE + if(strncmp(rcv_buffer, "MATSIZE", 7) == 0) + { + Serial.println(rcv_buffer); + + // GET CLUSTER SIZE + cluster_size_x = (rcv_buffer[8] + 1) - '0'; + cluster_size_y = (rcv_buffer[10] + 1) - '0'; + rcv_buffer[0] = 0; // CLEAR BUFFER + + // PUBLISH NEW CLUSTER SIZE + char clust_size[20]; + char clust_y[10]; + itoa(cluster_size_x*10, clust_size, 10); + int i_0 = strlen(clust_size); + clust_size[i_0] = 'x'; + clust_size[i_0+1] = 0; + + itoa(cluster_size_y*10, clust_y, 10); + strcat(clust_size, clust_y); + + client.publish(topic_size, clust_size); + } +} \ No newline at end of file