11 networking and communications
Jakob and Niclas
Gauntlet-Keyboard
For the group assignment, me and Jakob wanted to let our project communicate together.
The Idea was to control my LED matrix with Jakobs IMU and to activate his LEDs with my touch-inputs.
We both use the ESP32 MCUs and so we wanted to try out our RF-Modules because of our projects (I want optionally let my keyboard-modules communicate with each other wirelessly).
And there we choose to try out the ESPNOW protocol: - used with Bluetooth of Wifi - lays above the Link layer (so in the same Layer as IP) - also supports encryption with AES and key exchange an ECDH
For the implementation, we used the ESP-IDF.
First we tested out the basic example, where just 2 ESPs send each other random data.
TL;DR the code works like this:
- Initialises the Wifi (setup for the link-layer)
- Initialises ESPNOW stack
- registers callbacks for received and send messages (called whenever a message is received/send)
- adds a peer to its list (first only the broadcast-channel)
- Starts receiving and sending task
- initially sends a message to the broadcast-channel on link-layer
- does it, until it receives an answer, then adds the device to the list
- continues receiving and sending to this device
:::{note} They use a queue to add events to on every callback call, which then gets popped in the receiving and sending task. :::
We then simplified the code and added our parts to it to control the LEDs and send Key-presses:
We adjusted:
- the parse and prepare function
/* Parse received ESPNOW data. */
// The received content `data` is written into a data structure `seq`.
// Postprocessing of received data.
int espnow_data_parse(uint8_t *data, uint16_t data_len) {
ReportData rep_dat;
memcpy(rep_dat.data, data, data_len);
int ret = process_report(rep_dat);
if (ret != 1) {
ESP_LOGE(TAG, "Error on processing data");
return -1;
}
ESP_LOGI(TAG, "Received %s",
rep_dat.Report.type == REPORT_GYRO ? "REPORT_GYRO" : "REPORT_TOUCH");
return 1;
}
/* Prepare ESPNOW data to be sent. */
// Preprocessing of data to be sent. `send_param` is the output of this
// function.
void espnow_data_prepare(espnow_send_param_t *send_param) {
ReportData tmp_rep_dat = gather_report();
memcpy(send_param->buffer, &tmp_rep_dat, sizeof(ReportData));
}
- and then implemented the
process_report
andgather_report
-functions
keyboard.h
int process_report(ReportData rep) {
// no roll
if (rep.Report.type != REPORT_GYRO) {
ESP_LOGW(TOUCH_TAG, "Did not receive Gyro Report!!");
// return -1;
};
ReportGyro rep_to;
memcpy(rep_to.data, rep.Report.report, sizeof(ReportGyro));
ESP_LOGI(TOUCH_TAG, "Recieved: %d, %d", rep_to.Entries.roll,
rep_to.Entries.pitch);
switch (rep_to.Entries.pitch) {
case 42 ... 127:
gpio_set_level(LED_V0, 1);
gpio_set_level(LED_V1, 0);
gpio_set_level(LED_V2, 0);
break;
case -41 ... 41:
gpio_set_level(LED_V0, 0);
gpio_set_level(LED_V1, 1);
gpio_set_level(LED_V2, 0);
break;
case -128 ... - 42:
gpio_set_level(LED_V0, 0);
gpio_set_level(LED_V1, 0);
gpio_set_level(LED_V2, 1);
break;
default:
ESP_LOGE(TOUCH_TAG, "UNREACHABLE switch process");
}
return 1;
};
ReportData gather_report() {
ReportTouch rep_gy;
for (int i = 0; i < NUM_OF_TOUCHPADS; i++) {
uint32_t touch_value = 0;
uint8_t scaled_value = 0;
touch_pad_read_raw_data(button[i], &touch_value);
printf("T%d: [%4" PRIu32 "] ", button[i], touch_value);
scaled_value =
(touch_value - TOUCH_DEFAULT_LEVEL) * UINT8_MAX / TOUCH_MAX_LEVEL;
switch (i) {
case 0:
rep_gy.Entries.first = scaled_value;
break;
case 1:
rep_gy.Entries.second = scaled_value;
break;
case 2:
rep_gy.Entries.third = scaled_value;
break;
default:
ESP_LOGE(TOUCH_TAG, "UNREACHABLE switch gather");
}
printf("\n");
}
ReportData rep;
memcpy(rep.Report.report, rep_gy.data, sizeof(ReportTouch));
rep.Report.type = REPORT_TOUCH;
return rep;
};
gauntlet.h
int process_report(ReportData rep) {
if (rep.Report.type != REPORT_TOUCH) {
ESP_LOGW(GAUNTLET_TAG, "Did not receive Touch Report!!");
// return -1;
};
ReportTouch rep_to;
memcpy(rep_to.data, rep.Report.report, sizeof(ReportTouch));
ESP_LOGI(GAUNTLET_TAG, "Recieved: %d, %d, %d", rep_to.Entries.first,
rep_to.Entries.second, rep_to.Entries.third);
led_strip_set_pixel(led_strip, 0, rep_to.Entries.first, rep_to.Entries.first,
rep_to.Entries.first);
led_strip_set_pixel(led_strip, 1, rep_to.Entries.second,
rep_to.Entries.second, rep_to.Entries.second);
led_strip_set_pixel(led_strip, 2, rep_to.Entries.third, rep_to.Entries.third,
rep_to.Entries.third);
led_strip_refresh(led_strip);
return 1;
};
ReportData gather_report() {
if (pitch < 127) {
pitch++;
} else {
pitch = -128;
}
ReportGyro rep_gy;
rep_gy.Entries.pitch = pitch;
rep_gy.Entries.roll = 0; // Roll stays always zero for now.
ReportData rep;
memcpy(rep.Report.report, rep_gy.data, sizeof(ReportGyro));
rep.Report.type = REPORT_GYRO;
return rep;
};
Then for building flashing, we used:
idf.py set-target eps32s3
idf.py -p /dev/ttyACM0 flash
I also needed to download the LED Strip library for controlling the Neopixel-LEDs.
Then we added out sensors and it blinked :D