From cf7c06297d04bade9cd04c056f9ed510e64dd7bd Mon Sep 17 00:00:00 2001
From: Uros Majstorovic <majstor@majstor.org>
Date: Wed, 5 Aug 2020 03:39:22 +0200
Subject: code -> fw

---
 fw/esp32/components/eos/at_cmd.c           | 164 ++++++
 fw/esp32/components/eos/bq25895.c          |  60 +++
 fw/esp32/components/eos/cell.c             |  68 +++
 fw/esp32/components/eos/cell_data.c        |  20 +
 fw/esp32/components/eos/cell_modem.c       | 767 +++++++++++++++++++++++++++++
 fw/esp32/components/eos/cell_pcm.c         | 258 ++++++++++
 fw/esp32/components/eos/cell_sms.c         |  22 +
 fw/esp32/components/eos/cell_ussd.c        |  34 ++
 fw/esp32/components/eos/cell_voice.c       |  20 +
 fw/esp32/components/eos/component.mk       |   0
 fw/esp32/components/eos/drv2605l.c         |  82 +++
 fw/esp32/components/eos/gsm.c              | 453 +++++++++++++++++
 fw/esp32/components/eos/gsm_cp.c           | 109 ++++
 fw/esp32/components/eos/i2c.c              | 103 ++++
 fw/esp32/components/eos/include/_net.h     |   1 +
 fw/esp32/components/eos/include/at_cmd.h   |  20 +
 fw/esp32/components/eos/include/bq25895.h  |   3 +
 fw/esp32/components/eos/include/cell.h     |  68 +++
 fw/esp32/components/eos/include/drv2605l.h |   3 +
 fw/esp32/components/eos/include/eos.h      |  23 +
 fw/esp32/components/eos/include/gsm.h      | 117 +++++
 fw/esp32/components/eos/include/i2c.h      |   9 +
 fw/esp32/components/eos/include/msgq.h     |  32 ++
 fw/esp32/components/eos/include/net.h      |  34 ++
 fw/esp32/components/eos/include/power.h    |  20 +
 fw/esp32/components/eos/include/sock.h     |  18 +
 fw/esp32/components/eos/include/unicode.h  |   1 +
 fw/esp32/components/eos/include/wifi.h     |  12 +
 fw/esp32/components/eos/msgq.c             |  69 +++
 fw/esp32/components/eos/net.c              | 285 +++++++++++
 fw/esp32/components/eos/power.c            | 325 ++++++++++++
 fw/esp32/components/eos/sock.c             | 163 ++++++
 fw/esp32/components/eos/unicode.c          |   1 +
 fw/esp32/components/eos/wifi.c             | 302 ++++++++++++
 34 files changed, 3666 insertions(+)
 create mode 100644 fw/esp32/components/eos/at_cmd.c
 create mode 100644 fw/esp32/components/eos/bq25895.c
 create mode 100644 fw/esp32/components/eos/cell.c
 create mode 100644 fw/esp32/components/eos/cell_data.c
 create mode 100644 fw/esp32/components/eos/cell_modem.c
 create mode 100644 fw/esp32/components/eos/cell_pcm.c
 create mode 100644 fw/esp32/components/eos/cell_sms.c
 create mode 100644 fw/esp32/components/eos/cell_ussd.c
 create mode 100644 fw/esp32/components/eos/cell_voice.c
 create mode 100644 fw/esp32/components/eos/component.mk
 create mode 100644 fw/esp32/components/eos/drv2605l.c
 create mode 100644 fw/esp32/components/eos/gsm.c
 create mode 100644 fw/esp32/components/eos/gsm_cp.c
 create mode 100644 fw/esp32/components/eos/i2c.c
 create mode 100644 fw/esp32/components/eos/include/_net.h
 create mode 100644 fw/esp32/components/eos/include/at_cmd.h
 create mode 100644 fw/esp32/components/eos/include/bq25895.h
 create mode 100644 fw/esp32/components/eos/include/cell.h
 create mode 100644 fw/esp32/components/eos/include/drv2605l.h
 create mode 100644 fw/esp32/components/eos/include/eos.h
 create mode 100644 fw/esp32/components/eos/include/gsm.h
 create mode 100644 fw/esp32/components/eos/include/i2c.h
 create mode 100644 fw/esp32/components/eos/include/msgq.h
 create mode 100644 fw/esp32/components/eos/include/net.h
 create mode 100644 fw/esp32/components/eos/include/power.h
 create mode 100644 fw/esp32/components/eos/include/sock.h
 create mode 120000 fw/esp32/components/eos/include/unicode.h
 create mode 100644 fw/esp32/components/eos/include/wifi.h
 create mode 100644 fw/esp32/components/eos/msgq.c
 create mode 100644 fw/esp32/components/eos/net.c
 create mode 100644 fw/esp32/components/eos/power.c
 create mode 100644 fw/esp32/components/eos/sock.c
 create mode 120000 fw/esp32/components/eos/unicode.c
 create mode 100755 fw/esp32/components/eos/wifi.c

(limited to 'fw/esp32/components/eos')

diff --git a/fw/esp32/components/eos/at_cmd.c b/fw/esp32/components/eos/at_cmd.c
new file mode 100644
index 0000000..4712ad7
--- /dev/null
+++ b/fw/esp32/components/eos/at_cmd.c
@@ -0,0 +1,164 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
+#include <esp_log.h>
+
+#include "eos.h"
+
+#include "cell.h"
+#include "at_cmd.h"
+
+static const char *TAG = "EOS ATCMD";
+
+typedef struct ATURCItem {
+    regex_t re;
+    at_urc_cb_t cb;
+    char pattern[AT_SIZE_PATTERN];
+} ATURCItem;
+
+typedef struct ATURCList {
+    ATURCItem item[AT_SIZE_URC_LIST];
+    int len;
+} ATURCList;
+
+static ATURCList urc_list;
+static ATURCItem *urc_curr;
+static SemaphoreHandle_t mutex;
+
+static char at_buf[EOS_CELL_UART_SIZE_BUF];
+
+void at_init(void) {
+    memset(&urc_list, 0, sizeof(ATURCList));
+
+    mutex = xSemaphoreCreateBinary();
+    xSemaphoreGive(mutex);
+}
+
+int at_urc_process(char *urc) {
+    regmatch_t match[AT_SIZE_NMATCH];
+    at_urc_cb_t cb = NULL;
+    regmatch_t *m = NULL;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+
+    if (urc_curr == NULL) {
+        int i;
+
+        for (i=0; i<urc_list.len; i++) {
+            if (regexec(&urc_list.item[i].re, urc, AT_SIZE_NMATCH, match, 0) == 0) {
+                urc_curr = &urc_list.item[i];
+                m = match;
+                break;
+            }
+        }
+    }
+    if (urc_curr) cb = urc_curr->cb;
+
+    xSemaphoreGive(mutex);
+
+    if (cb) {
+        int r = cb(urc, m);
+
+        if (r != AT_URC_MORE) {
+            xSemaphoreTake(mutex, portMAX_DELAY);
+            urc_curr = NULL;
+            xSemaphoreGive(mutex);
+        }
+        ESP_LOGD(TAG, "URC Processed: %s", urc);
+        return 1;
+    }
+
+    ESP_LOGD(TAG, "URC NOT Processed: %s", urc);
+    return 0;
+}
+
+int at_urc_insert(char *pattern, at_urc_cb_t cb, int flags) {
+    int r;
+    int rv = EOS_OK;
+
+    if (strlen(pattern) >= AT_SIZE_PATTERN) return EOS_ERR;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+
+    r = regcomp(&urc_list.item[urc_list.len].re, pattern, flags);
+    if (r) rv = EOS_ERR;
+
+    if (!rv && (urc_list.len == AT_SIZE_URC_LIST)) rv = EOS_ERR_FULL;
+    if (!rv) {
+        strcpy(urc_list.item[urc_list.len].pattern, pattern);
+        urc_list.item[urc_list.len].cb = cb;
+        urc_list.len++;
+    }
+
+    xSemaphoreGive(mutex);
+
+    return rv;
+}
+
+int at_urc_delete(char *pattern) {
+    int i;
+    int rv = EOS_ERR_NOTFOUND;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+
+    for (i=0; i<urc_list.len; i++) {
+        if ((strcmp(pattern, urc_list.item[i].pattern) == 0)) {
+            if (i != urc_list.len - 1) memmove(&urc_list.item[i], &urc_list.item[i + 1], (urc_list.len - i - 1) * sizeof(ATURCItem));
+            urc_list.len--;
+            memset(&urc_list.item[urc_list.len], 0, sizeof(ATURCItem));
+            if (urc_curr) {
+                if (urc_curr == &urc_list.item[i]) {
+                    urc_curr = NULL;
+                } else if (urc_curr > &urc_list.item[i]) {
+                    urc_curr--;
+                }
+            }
+            rv = EOS_OK;
+            break;
+        }
+    }
+
+    xSemaphoreGive(mutex);
+
+    return rv;
+}
+
+void at_cmd(char *cmd) {
+    eos_modem_write(cmd, strlen(cmd));
+    ESP_LOGD(TAG, "Cmd: %s", cmd);
+}
+
+int at_expect(char *str_ok, char *str_err, uint32_t timeout) {
+    int rv;
+    regex_t re_ok;
+    regex_t re_err;
+    uint32_t e = 0;
+    uint64_t t_start = esp_timer_get_time();
+
+    if (str_ok) {
+        rv = regcomp(&re_ok, str_ok, REG_EXTENDED | REG_NOSUB);
+        if (rv) return EOS_ERR;
+    }
+
+    if (str_err) {
+        rv = regcomp(&re_err, str_err, REG_EXTENDED | REG_NOSUB);
+        if (rv) return EOS_ERR;
+    }
+
+    do {
+        rv = eos_modem_readln(at_buf, sizeof(at_buf), timeout - e);
+        ESP_LOGD(TAG, "Expect: %s", at_buf);
+
+        if (at_buf[0] != '\0') {
+            if (!rv && str_ok && (regexec(&re_ok, at_buf, 0, NULL, 0) == 0)) return 1;
+            if (!rv && str_err && (regexec(&re_err, at_buf, 0, NULL, 0) == 0)) return 0;
+
+            at_urc_process(at_buf);
+        }
+
+        e = (uint32_t)(esp_timer_get_time() - t_start) / 1000;
+        if (e > timeout) return EOS_ERR_TIMEOUT;
+    } while (1);
+}
diff --git a/fw/esp32/components/eos/bq25895.c b/fw/esp32/components/eos/bq25895.c
new file mode 100644
index 0000000..0d1bb8d
--- /dev/null
+++ b/fw/esp32/components/eos/bq25895.c
@@ -0,0 +1,60 @@
+#include <stdlib.h>
+
+#include <esp_log.h>
+
+#include "eos.h"
+#include "i2c.h"
+
+static const char *TAG = "EOS BQ25895";
+
+#define BQ25895_ADDR                0x6A
+
+void eos_bq25895_set_ilim(void) {
+    uint8_t data = 0;
+    eos_i2c_write8(BQ25895_ADDR, 0, 0x1c);
+    eos_i2c_write8(BQ25895_ADDR, 2, 0x28);
+    eos_i2c_write8(BQ25895_ADDR, 7, 0x8d);
+
+    data = eos_i2c_read8(BQ25895_ADDR, 0x00);
+    ESP_LOGI(TAG, "REG00: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x01);
+    ESP_LOGI(TAG, "REG01: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x02);
+    ESP_LOGI(TAG, "REG02: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x03);
+    ESP_LOGI(TAG, "REG03: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x04);
+    ESP_LOGI(TAG, "REG04: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x05);
+    ESP_LOGI(TAG, "REG05: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x06);
+    ESP_LOGI(TAG, "REG06: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x07);
+    ESP_LOGI(TAG, "REG07: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x08);
+    ESP_LOGI(TAG, "REG08: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x09);
+    ESP_LOGI(TAG, "REG09: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x0a);
+    ESP_LOGI(TAG, "REG0A: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x0b);
+    ESP_LOGI(TAG, "REG0B: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x0c);
+    ESP_LOGI(TAG, "REG0C: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x0d);
+    ESP_LOGI(TAG, "REG0D: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x0e);
+    ESP_LOGI(TAG, "REG0E: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x0f);
+    ESP_LOGI(TAG, "REG0F: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x10);
+    ESP_LOGI(TAG, "REG10: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x11);
+    ESP_LOGI(TAG, "REG11: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x12);
+    ESP_LOGI(TAG, "REG12: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x13);
+    ESP_LOGI(TAG, "REG13: %02x", data);
+    data = eos_i2c_read8(BQ25895_ADDR, 0x14);
+    ESP_LOGI(TAG, "REG14: %02x", data);
+}
diff --git a/fw/esp32/components/eos/cell.c b/fw/esp32/components/eos/cell.c
new file mode 100644
index 0000000..c2e03e1
--- /dev/null
+++ b/fw/esp32/components/eos/cell.c
@@ -0,0 +1,68 @@
+#include <stdlib.h>
+#include <stdint.h>
+
+#include <esp_log.h>
+
+#include "eos.h"
+#include "net.h"
+#include "cell.h"
+
+static uint8_t cell_mode;
+
+static void cell_handler(unsigned char _mtype, unsigned char *buffer, uint16_t size) {
+    uint8_t mtype;
+
+    if (size < 1) return;
+    mtype = buffer[0];
+    switch (mtype & EOS_CELL_MTYPE_MASK) {
+        case EOS_CELL_MTYPE_DEV:
+            switch (mtype) {
+                case EOS_CELL_MTYPE_UART_DATA:
+                    if (eos_modem_get_mode() == EOS_CELL_UART_MODE_RELAY) eos_modem_write(buffer+1, size-1);
+                    break;
+
+                case EOS_CELL_MTYPE_UART_TAKE:
+                    cell_mode = eos_modem_get_mode();
+                    eos_modem_set_mode(EOS_CELL_UART_MODE_RELAY);
+                    break;
+
+                case EOS_CELL_MTYPE_UART_GIVE:
+                    eos_modem_set_mode(cell_mode);
+                    break;
+
+                case EOS_CELL_MTYPE_PCM_DATA:
+                    eos_cell_pcm_push(buffer+1, size-1);
+                    break;
+
+                case EOS_CELL_MTYPE_PCM_START:
+                    eos_cell_pcm_start();
+                    break;
+
+                case EOS_CELL_MTYPE_PCM_STOP:
+                    eos_cell_pcm_stop();
+                    break;
+            }
+            break;
+
+        case EOS_CELL_MTYPE_VOICE:
+            eos_cell_voice_handler(mtype & ~EOS_CELL_MTYPE_MASK, buffer, size);
+            break;
+
+        case EOS_CELL_MTYPE_SMS:
+            eos_cell_sms_handler(mtype & ~EOS_CELL_MTYPE_MASK, buffer, size);
+            break;
+
+        case EOS_CELL_MTYPE_USSD:
+            eos_cell_ussd_handler(mtype & ~EOS_CELL_MTYPE_MASK, buffer, size);
+            break;
+
+        case EOS_CELL_MTYPE_DATA:
+            eos_cell_data_handler(mtype & ~EOS_CELL_MTYPE_MASK, buffer, size);
+            break;
+    }
+}
+
+void eos_cell_init(void) {
+    eos_net_set_handler(EOS_NET_MTYPE_CELL, cell_handler);
+}
+
diff --git a/fw/esp32/components/eos/cell_data.c b/fw/esp32/components/eos/cell_data.c
new file mode 100644
index 0000000..6732346
--- /dev/null
+++ b/fw/esp32/components/eos/cell_data.c
@@ -0,0 +1,20 @@
+#include <stdlib.h>
+
+#include <esp_log.h>
+
+#include "eos.h"
+#include "cell.h"
+
+void eos_cell_data_handler(unsigned char mtype, unsigned char *buffer, uint16_t size) {
+    int rv;
+
+    rv = eos_modem_take(1000);
+    if (rv) return;
+
+    buffer += 1;
+    size -= 1;
+    switch (mtype) {
+    }
+
+    eos_modem_give();
+}
diff --git a/fw/esp32/components/eos/cell_modem.c b/fw/esp32/components/eos/cell_modem.c
new file mode 100644
index 0000000..c6d718f
--- /dev/null
+++ b/fw/esp32/components/eos/cell_modem.c
@@ -0,0 +1,767 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
+#include <freertos/task.h>
+#include <freertos/queue.h>
+#include <netif/ppp/pppos.h>
+#include <netif/ppp/pppapi.h>
+#include <driver/uart.h>
+#include <driver/gpio.h>
+#include <esp_sleep.h>
+#include <esp_log.h>
+
+#include "eos.h"
+#include "net.h"
+#include "power.h"
+
+#include "at_cmd.h"
+#include "cell.h"
+
+// XXX: PPP reconnect on failure
+
+#define UART_SIZE_IO_BUF    1024
+
+#define UART_GPIO_TXD       16
+#define UART_GPIO_RXD       17
+#define UART_GPIO_DTR       32
+#define UART_GPIO_RI        35
+
+#define MODEM_ETYPE_INIT    1
+#define MODEM_ETYPE_RI      2
+
+#define AT_CMD_INIT_SIZE    3
+
+#define MIN(X, Y)           (((X) < (Y)) ? (X) : (Y))
+#define MAX(X, Y)           (((X) > (Y)) ? (X) : (Y))
+
+static const char *TAG = "EOS MODEM";
+
+static char *at_cmd_init[AT_CMD_INIT_SIZE] = {
+    "AT+CFGRI=1\r",
+    "AT+CSCLK=1\r",
+    "AT+CMGF=0\r"
+};
+
+static SemaphoreHandle_t mutex;
+
+static QueueHandle_t modem_queue;
+static QueueHandle_t uart_queue;
+
+static char urc_buf[EOS_CELL_UART_SIZE_BUF];
+static char uart_buf[EOS_CELL_UART_SIZE_BUF];
+static size_t uart_buf_len;
+
+static uint8_t uart_mode = EOS_CELL_UART_MODE_ATCMD;
+static SemaphoreHandle_t uart_mutex;
+
+static char ppp_apn[64];
+static char ppp_user[64];
+static char ppp_pass[64];
+static SemaphoreHandle_t ppp_mutex;
+
+static ppp_pcb *ppp_handle;
+static struct netif ppp_netif;
+
+typedef enum {
+    UART_EEVT_MODE = UART_EVENT_MAX
+} uart_eevt_type_t;
+
+typedef struct {
+    uint8_t type;
+} modem_event_t;
+
+static void modem_atcmd_read(size_t bsize);
+
+static void uart_data_read(uint8_t mode) {
+    unsigned char *buf;
+    int rd;
+    size_t bsize;
+
+    uart_get_buffered_data_len(UART_NUM_2, &bsize);
+    switch (mode) {
+        case EOS_CELL_UART_MODE_ATCMD:
+            modem_atcmd_read(bsize);
+            break;
+
+        case EOS_CELL_UART_MODE_PPP:
+            rd = 0;
+
+            do {
+                int _rd = eos_modem_read(uart_buf, MIN(bsize - rd, sizeof(uart_buf)), 100);
+                pppos_input_tcpip(ppp_handle, (uint8_t *)uart_buf, _rd);
+                rd += _rd;
+            } while (rd != bsize);
+            break;
+
+        case EOS_CELL_UART_MODE_RELAY:
+            rd = 0;
+
+            do {
+                int _rd;
+
+                buf = eos_net_alloc();
+                buf[0] = EOS_CELL_MTYPE_UART_DATA;
+                _rd = eos_modem_read(buf + 1, MIN(bsize - rd, EOS_NET_SIZE_BUF - 1), 100);
+                eos_net_send(EOS_NET_MTYPE_CELL, buf, _rd + 1, 0);
+                rd += _rd;
+            } while (rd != bsize);
+            break;
+
+        default:
+            break;
+
+    }
+}
+
+static void uart_event_task(void *pvParameters) {
+    char mode = EOS_CELL_UART_MODE_ATCMD;
+    char _mode = EOS_CELL_UART_MODE_ATCMD;
+    uart_event_t event;
+
+    xSemaphoreTake(uart_mutex, portMAX_DELAY);
+    while (1) {
+        /* Waiting for UART event.
+         */
+        if (xQueueReceive(uart_queue, &event, portMAX_DELAY)) {
+            switch (event.type) {
+                case UART_DATA:
+                    /* Event of UART receiving data
+                     */
+                    if (mode != EOS_CELL_UART_MODE_NONE) uart_data_read(mode);
+                    if ((mode != _mode) && (uart_buf_len == 0)) {
+                        if (_mode == EOS_CELL_UART_MODE_NONE) xSemaphoreGive(uart_mutex);
+                        mode = _mode;
+                    }
+                    break;
+
+                case UART_EEVT_MODE:
+                    /* Mode change
+                     */
+                    _mode = (char)event.size;
+                    if ((_mode != mode) && ((uart_buf_len == 0) || (mode == EOS_CELL_UART_MODE_NONE))) {
+                        if (mode == EOS_CELL_UART_MODE_NONE) {
+                            xSemaphoreTake(uart_mutex, portMAX_DELAY);
+                            uart_data_read(_mode);
+                        }
+                        if (_mode == EOS_CELL_UART_MODE_NONE) xSemaphoreGive(uart_mutex);
+                        mode = _mode;
+                    }
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    }
+    vTaskDelete(NULL);
+}
+
+static void IRAM_ATTR uart_ri_isr_handler(void *arg) {
+    modem_event_t evt;
+
+    evt.type = MODEM_ETYPE_RI;
+    xQueueSendFromISR(modem_queue, &evt, NULL);
+}
+
+static int modem_atcmd_init(void) {
+    unsigned char *buf;
+    int echo_on = 0;
+    int tries = 3;
+    int i, r;
+    int rv = EOS_OK;
+
+    rv = eos_modem_take(1000);
+    if (rv) return rv;
+
+    do {
+        at_cmd("AT\r");
+        r = at_expect("^AT", "^OK", 1000);
+        if (r >= 0) {
+            echo_on = r;
+            if (echo_on) {
+                r = at_expect("^OK", NULL, 1000);
+            }
+            break;
+        }
+        tries--;
+    } while (tries);
+
+    if (tries == 0) {
+        eos_modem_give();
+        return EOS_ERR_TIMEOUT;
+    }
+
+    if (echo_on) {
+        at_cmd("AT&F\r");
+        r = at_expect("^AT&F", NULL, 1000);
+        r = at_expect("^OK", NULL, 1000);
+    } else {
+        at_cmd("AT&F\r");
+        r = at_expect("^OK", NULL, 1000);
+
+    }
+    at_cmd("ATE0\r");
+    r = at_expect("^ATE0", NULL, 1000);
+    r = at_expect("^OK", "^ERROR", 1000);
+
+    for (i=0; i<AT_CMD_INIT_SIZE; i++) {
+        at_cmd(at_cmd_init[i]);
+        r = at_expect("^OK", "^ERROR", 1000);
+    }
+
+    buf = eos_net_alloc();
+    buf[0] = EOS_CELL_MTYPE_READY;
+    eos_net_send(EOS_NET_MTYPE_CELL, buf, 1, 0);
+
+    eos_modem_give();
+
+    return EOS_OK;
+}
+
+static void modem_atcmd_read(size_t bsize) {
+    char *ln_end;
+    int rd = 0;
+
+    do {
+        char *uart_curr = uart_buf + uart_buf_len;
+        int _rd = eos_modem_read(uart_curr, MIN(bsize - rd, sizeof(uart_buf) - uart_buf_len - 1), 100);
+
+        rd += _rd;
+        uart_buf_len += _rd;
+        uart_buf[uart_buf_len] = '\0';
+        while ((ln_end = strchr(uart_curr, '\n'))) {
+            size_t urc_buf_len = ln_end - uart_buf;
+
+            if ((ln_end > uart_buf) && (*(ln_end - 1) == '\r')) urc_buf_len--;
+            memcpy(urc_buf, uart_buf, urc_buf_len);
+            urc_buf[urc_buf_len] = '\0';
+
+            uart_buf_len -= ln_end - uart_buf + 1;
+            if (uart_buf_len) memmove(uart_buf, ln_end + 1, uart_buf_len);
+            uart_curr = uart_buf;
+            uart_buf[uart_buf_len] = '\0';
+
+            at_urc_process(urc_buf);
+        }
+        if (uart_buf_len == sizeof(uart_buf) - 1) {
+            uart_buf_len = 0;
+            memcpy(urc_buf, uart_buf, sizeof(urc_buf));
+            at_urc_process(urc_buf);
+        }
+    } while (rd != bsize);
+}
+
+int modem_urc_init_handler(char *urc, regmatch_t *m) {
+    modem_event_t evt;
+
+    evt.type = MODEM_ETYPE_INIT;
+    xQueueSend(modem_queue, &evt, portMAX_DELAY);
+
+    return AT_URC_OK;
+}
+
+static void modem_set_mode(uint8_t mode) {
+    uart_event_t evt;
+
+    evt.type = UART_EEVT_MODE;
+    evt.size = mode;
+    xQueueSend(uart_queue, &evt, portMAX_DELAY);
+}
+
+static void modem_event_task(void *pvParameters) {
+    modem_event_t evt;
+
+    while (1) {
+        if (xQueueReceive(modem_queue, &evt, portMAX_DELAY)) {
+            switch (evt.type) {
+                case MODEM_ETYPE_INIT:
+                    modem_atcmd_init();
+                    break;
+
+                case MODEM_ETYPE_RI:
+                    ESP_LOGI(TAG, "URC from RI");
+                    break;
+
+                default:
+                    break;
+            }
+
+            /* Obsolete!!!
+            uint64_t t_start = esp_timer_get_time();
+            if (xQueueReceive(modem_queue, &level, 200 / portTICK_RATE_MS) && (level == 1)) {
+                uint64_t t_end = esp_timer_get_time();
+                ESP_LOGI(TAG, "URC:%u", (uint32_t)(t_end - t_start));
+            } else {
+                ESP_LOGI(TAG, "RING");
+            }
+            */
+
+        }
+    }
+    vTaskDelete(NULL);
+}
+
+static char *memstr(char *mem, size_t size, char *str) {
+    size_t i = 0;
+    char *max_mem;
+
+    if (str[0] == '\0') return NULL;
+
+    max_mem = mem + size;
+
+    while (mem < max_mem) {
+        if (*mem != str[i]) {
+            mem -= i;
+            i = 0;
+        } else {
+            if (str[i+1] == '\0') return mem - i;
+            i++;
+        }
+        mem++;
+    }
+
+    return NULL;
+}
+
+static uint32_t ppp_output_cb(ppp_pcb *pcb, uint8_t *data, uint32_t len, void *ctx) {
+    size_t rv;
+
+	xSemaphoreTake(ppp_mutex, portMAX_DELAY);
+    rv = eos_modem_write(data, len);
+	xSemaphoreGive(ppp_mutex);
+
+    return rv;
+}
+
+/* PPP status callback */
+static void ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) {
+    // struct netif *pppif = ppp_netif(pcb);
+    // LWIP_UNUSED_ARG(ctx);
+
+    switch(err_code) {
+        case PPPERR_NONE: {
+            ESP_LOGE(TAG, "status_cb: Connect");
+            break;
+        }
+        case PPPERR_PARAM: {
+            ESP_LOGE(TAG, "status_cb: Invalid parameter");
+            break;
+        }
+        case PPPERR_OPEN: {
+            ESP_LOGE(TAG, "status_cb: Unable to open PPP session");
+            break;
+        }
+        case PPPERR_DEVICE: {
+            ESP_LOGE(TAG, "status_cb: Invalid I/O device for PPP");
+            break;
+        }
+        case PPPERR_ALLOC: {
+            ESP_LOGE(TAG, "status_cb: Unable to allocate resources");
+            break;
+        }
+        case PPPERR_USER: {
+            ESP_LOGE(TAG, "status_cb: User interrupt");
+            break;
+        }
+        case PPPERR_CONNECT: {
+            ESP_LOGE(TAG, "status_cb: Connection lost");
+            break;
+        }
+        case PPPERR_AUTHFAIL: {
+            ESP_LOGE(TAG, "status_cb: Failed authentication challenge");
+            break;
+        }
+        case PPPERR_PROTOCOL: {
+            ESP_LOGE(TAG, "status_cb: Failed to meet protocol");
+            break;
+        }
+        case PPPERR_PEERDEAD: {
+            ESP_LOGE(TAG, "status_cb: Connection timeout");
+            break;
+        }
+        case PPPERR_IDLETIMEOUT: {
+            ESP_LOGE(TAG, "status_cb: Idle Timeout");
+            break;
+        }
+        case PPPERR_CONNECTTIME: {
+            ESP_LOGE(TAG, "status_cb: Max connect time reached");
+            break;
+        }
+        case PPPERR_LOOPBACK: {
+            ESP_LOGE(TAG, "status_cb: Loopback detected");
+            break;
+        }
+        default: {
+            ESP_LOGE(TAG, "status_cb: Unknown error code %d", err_code);
+            break;
+        }
+    }
+}
+
+static int ppp_pause(uint32_t timeout, uint8_t retries) {
+    int done = 0;
+    int len = 0;
+    int rv = EOS_OK;
+    char *ok_str = NULL;
+    uint64_t t_start;
+
+    timeout += 1000;
+    xSemaphoreTake(ppp_mutex, portMAX_DELAY);
+    eos_modem_flush();
+    vTaskDelay(1000 / portTICK_PERIOD_MS);
+    modem_set_mode(EOS_CELL_UART_MODE_NONE);
+    xSemaphoreTake(uart_mutex, portMAX_DELAY);
+    at_cmd("+++");
+    t_start = esp_timer_get_time();
+
+    do {
+        len = eos_modem_read(uart_buf + uart_buf_len, sizeof(uart_buf) - uart_buf_len, 10);
+        if (len > 0) {
+            if (uart_buf_len > 5) {
+                ok_str = memstr(uart_buf + uart_buf_len - 5, len + 5, "\r\nOK\r\n");
+            } else {
+                ok_str = memstr(uart_buf, len + uart_buf_len, "\r\nOK\r\n");
+            }
+            uart_buf_len += len;
+        }
+        if (ok_str) {
+            pppos_input_tcpip(ppp_handle, (uint8_t *)uart_buf, ok_str - uart_buf);
+            ok_str += 6;
+            uart_buf_len -= ok_str - uart_buf;
+            if (uart_buf_len) memmove(uart_buf, ok_str, uart_buf_len);
+            done = 1;
+        } else if (uart_buf_len == sizeof(uart_buf)) {
+            pppos_input_tcpip(ppp_handle, (uint8_t *)uart_buf, sizeof(uart_buf) / 2);
+            memcpy(uart_buf, uart_buf + sizeof(uart_buf) / 2, sizeof(uart_buf) / 2);
+            uart_buf_len = sizeof(uart_buf) / 2;
+        }
+        if (timeout && !done && ((uint32_t)((esp_timer_get_time() - t_start) / 1000) > timeout)) {
+            if (!retries) {
+                modem_set_mode(EOS_CELL_UART_MODE_PPP);
+                xSemaphoreGive(uart_mutex);
+                xSemaphoreGive(ppp_mutex);
+                rv = EOS_ERR_TIMEOUT;
+                done = 1;
+            } else {
+                retries--;
+                at_cmd("+++");
+                t_start = esp_timer_get_time();
+            }
+        }
+    } while (!done);
+
+    return rv;
+}
+
+static int ppp_resume(void) {
+    int r;
+    int rv = EOS_OK;
+
+    at_cmd("ATO\r");
+    r = at_expect("^CONNECT", "^(ERROR|NO CARRIER)", 1000);
+    if (r <= 0) rv = EOS_ERR;
+
+    modem_set_mode(EOS_CELL_UART_MODE_PPP);
+    xSemaphoreGive(uart_mutex);
+    xSemaphoreGive(ppp_mutex);
+
+    return rv;
+}
+
+static int ppp_setup(void) {
+    int r;
+    char cmd[64];
+    int cmd_len = snprintf(cmd, sizeof(cmd), "AT+CGDCONT=1,\"IP\",\"%s\"\r", ppp_apn);
+
+    if ((cmd_len < 0) || (cmd_len >= sizeof(cmd))) return EOS_ERR;
+
+    modem_set_mode(EOS_CELL_UART_MODE_NONE);
+    r = xSemaphoreTake(uart_mutex, 1000 / portTICK_PERIOD_MS);
+    if (r == pdFALSE) {
+        modem_set_mode(uart_mode);
+        return EOS_ERR_TIMEOUT;
+    }
+
+    at_cmd(cmd);
+    r = at_expect("^OK", "^ERROR", 1000);
+    if (r <= 0) {
+        modem_set_mode(uart_mode);
+        xSemaphoreGive(uart_mutex);
+        return EOS_ERR;
+    }
+
+    at_cmd("AT+CGDATA=\"PPP\",1\r");
+    r = at_expect("^CONNECT", "^(ERROR|\\+CME ERROR|NO CARRIER)", 1000);
+    if (r <= 0) {
+        modem_set_mode(uart_mode);
+        xSemaphoreGive(uart_mutex);
+        return EOS_ERR;
+    }
+
+    ppp_handle = pppapi_pppos_create(&ppp_netif, ppp_output_cb, ppp_status_cb, NULL);
+    ppp_set_usepeerdns(ppp_handle, 1);
+    pppapi_set_default(ppp_handle);
+	pppapi_set_auth(ppp_handle, PPPAUTHTYPE_PAP, ppp_user, ppp_pass);
+    pppapi_connect(ppp_handle, 0);
+
+    modem_set_mode(EOS_CELL_UART_MODE_PPP);
+    xSemaphoreGive(uart_mutex);
+
+    return EOS_OK;
+}
+
+static int ppp_disconnect(void) {
+    int rv;
+
+    pppapi_close(ppp_handle, 0);
+
+    rv = ppp_pause(1000, 2);
+    if (rv) return rv;
+
+    at_cmd("ATH\r");
+    at_expect("^OK", NULL, 1000);
+
+    xSemaphoreGive(uart_mutex);
+    xSemaphoreGive(ppp_mutex);
+    ppp_handle = NULL;
+
+    return EOS_OK;
+}
+
+void eos_modem_init(void) {
+    /* Configure parameters of an UART driver,
+     * communication pins and install the driver */
+    uart_config_t uart_config = {
+       .baud_rate = 115200,
+       .data_bits = UART_DATA_8_BITS,
+       .parity    = UART_PARITY_DISABLE,
+       .stop_bits = UART_STOP_BITS_1,
+       .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
+    };
+    uart_param_config(UART_NUM_2, &uart_config);
+    uart_set_pin(UART_NUM_2, UART_GPIO_TXD, UART_GPIO_RXD, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
+    uart_driver_install(UART_NUM_2, UART_SIZE_IO_BUF, UART_SIZE_IO_BUF, 10, &uart_queue, 0);
+
+    // Configuration for the DTR/RI lines
+    gpio_config_t io_conf;
+
+    io_conf.intr_type = GPIO_INTR_DISABLE;
+    io_conf.mode = GPIO_MODE_OUTPUT;
+    io_conf.pin_bit_mask = ((uint64_t)1 << UART_GPIO_DTR);
+    io_conf.pull_up_en = 0;
+    io_conf.pull_down_en = 0;
+    gpio_config(&io_conf);
+    gpio_set_level(UART_GPIO_DTR, 0);
+
+    io_conf.intr_type = GPIO_INTR_NEGEDGE;
+    io_conf.mode = GPIO_MODE_INPUT;
+    io_conf.pin_bit_mask = ((uint64_t)1 << UART_GPIO_RI);
+    io_conf.pull_up_en = 0;
+    io_conf.pull_down_en = 0;
+    gpio_config(&io_conf);
+
+    mutex = xSemaphoreCreateBinary();
+    xSemaphoreGive(mutex);
+
+    uart_mutex = xSemaphoreCreateBinary();
+    xSemaphoreGive(uart_mutex);
+
+    ppp_mutex = xSemaphoreCreateBinary();
+    xSemaphoreGive(ppp_mutex);
+
+    modem_queue = xQueueCreate(4, sizeof(modem_event_t));
+    xTaskCreate(uart_event_task, "uart_event", EOS_TASK_SSIZE_UART, NULL, EOS_TASK_PRIORITY_UART, NULL);
+    xTaskCreate(modem_event_task, "modem_event", EOS_TASK_SSIZE_MODEM, NULL, EOS_TASK_PRIORITY_MODEM, NULL);
+
+    gpio_isr_handler_add(UART_GPIO_RI, uart_ri_isr_handler, NULL);
+
+    at_init();
+    at_urc_insert("^PB DONE", modem_urc_init_handler, REG_EXTENDED);
+    eos_modem_set_mode(EOS_CELL_UART_MODE_ATCMD);
+
+    ESP_LOGI(TAG, "INIT");
+}
+
+void eos_modem_flush(void){
+    uart_wait_tx_done(UART_NUM_2, portMAX_DELAY);
+}
+
+size_t eos_modem_write(void *data, size_t size) {
+    return uart_write_bytes(UART_NUM_2, (const char *)data, size);
+}
+
+size_t eos_modem_read(void *data, size_t size, uint32_t timeout) {
+    return uart_read_bytes(UART_NUM_2, (uint8_t *)data, size, timeout / portTICK_RATE_MS);
+}
+
+int eos_modem_readln(char *buf, size_t buf_size, uint32_t timeout) {
+    char *ln_end = NULL;
+    size_t buf_len = 0;
+    uint64_t t_start = esp_timer_get_time();
+
+    buf[0] = '\0';
+    if (uart_buf_len) {
+        buf_len = MIN(buf_size -1, uart_buf_len);
+        memcpy(buf, uart_buf, buf_len);
+        buf[buf_len] = '\0';
+        ln_end = strchr(buf, '\n');
+
+        uart_buf_len -= buf_len;
+        if (uart_buf_len) memmove(uart_buf, uart_buf + buf_len, uart_buf_len);
+    }
+
+    while (ln_end == NULL) {
+        int len;
+
+        if (buf_len == buf_size - 1) return EOS_ERR_FULL;
+        if (timeout && ((uint32_t)((esp_timer_get_time() - t_start) / 1000) > timeout)) return EOS_ERR_TIMEOUT;
+
+        len = eos_modem_read(buf + buf_len, MIN(buf_size - buf_len - 1, sizeof(uart_buf) - uart_buf_len), 10);
+        if (len > 0) {
+            buf[buf_len + len] = '\0';
+            ln_end = strchr(buf + buf_len, '\n');
+            buf_len += len;
+        }
+    }
+    buf_len -= ln_end - buf + 1;
+    if (buf_len) {
+        if (uart_buf_len) memmove(uart_buf + buf_len, uart_buf, uart_buf_len);
+        memcpy(uart_buf, ln_end + 1, buf_len);
+        uart_buf_len += buf_len;
+    }
+
+    if ((ln_end > buf) && (*(ln_end - 1) == '\r')) ln_end--;
+    *ln_end = '\0';
+
+    return EOS_OK;
+}
+
+uint8_t eos_modem_get_mode(void) {
+    uint8_t ret;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    ret = uart_mode;
+    xSemaphoreGive(mutex);
+
+    return ret;
+}
+
+int eos_modem_set_mode(uint8_t mode) {
+    int rv = EOS_OK;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    if (mode != uart_mode) {
+        if (uart_mode == EOS_CELL_UART_MODE_PPP) rv = ppp_disconnect();
+        if (!rv) {
+            if (mode == EOS_CELL_UART_MODE_PPP) {
+                rv = ppp_setup();
+            } else {
+                modem_set_mode(mode);
+            }
+            if (!rv) uart_mode = mode;
+        }
+    }
+    xSemaphoreGive(mutex);
+
+    return rv;
+}
+
+int eos_modem_take(uint32_t timeout) {
+    int rv = EOS_OK;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    if (uart_mode == EOS_CELL_UART_MODE_PPP) {
+        rv = ppp_pause(timeout, 0);
+    } else {
+        int r;
+
+        modem_set_mode(EOS_CELL_UART_MODE_NONE);
+        r = xSemaphoreTake(uart_mutex, timeout ? timeout / portTICK_PERIOD_MS : portMAX_DELAY);
+        if (r == pdFALSE) {
+            modem_set_mode(uart_mode);
+            rv = EOS_ERR_TIMEOUT;
+        }
+    }
+
+    if (rv) xSemaphoreGive(mutex);
+
+    return rv;
+}
+
+void eos_modem_give(void) {
+    if (uart_mode == EOS_CELL_UART_MODE_PPP) {
+        int rv = ppp_resume();
+        if (rv) ESP_LOGW(TAG, "PPP resume failed");
+    } else {
+        modem_set_mode(uart_mode);
+        xSemaphoreGive(uart_mutex);
+    }
+    xSemaphoreGive(mutex);
+}
+
+void eos_modem_sleep(uint8_t mode) {
+    int r;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    modem_set_mode(EOS_CELL_UART_MODE_NONE);
+    r = xSemaphoreTake(uart_mutex, 1000 / portTICK_PERIOD_MS);
+    if (r == pdFALSE) {
+        ESP_LOGE(TAG, "Obtaining mutex before sleep failed");
+    }
+    gpio_set_level(UART_GPIO_DTR, 1);
+    if (mode == EOS_PWR_SMODE_DEEP) {
+        gpio_hold_en(UART_GPIO_DTR);
+    }
+}
+
+void eos_modem_wake(uint8_t source, uint8_t mode) {
+    if (source == EOS_PWR_WAKE_UART) {
+        modem_event_t evt;
+
+        evt.type = MODEM_ETYPE_RI;
+        xQueueSend(modem_queue, &evt, portMAX_DELAY);
+    }
+
+    if (mode != EOS_PWR_SMODE_DEEP) {
+        gpio_set_intr_type(UART_GPIO_RI, GPIO_INTR_NEGEDGE);
+        gpio_isr_handler_add(UART_GPIO_RI, uart_ri_isr_handler, NULL);
+        gpio_set_level(UART_GPIO_DTR, 0);
+
+        modem_set_mode(uart_mode);
+        xSemaphoreGive(uart_mutex);
+        xSemaphoreGive(mutex);
+    } else {
+        gpio_hold_dis(UART_GPIO_DTR);
+    }
+}
+
+void eos_ppp_set_apn(char *apn) {
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    strncpy(ppp_apn, apn, sizeof(ppp_apn) - 1);
+    xSemaphoreGive(mutex);
+}
+
+void eos_ppp_set_auth(char *user, char *pass) {
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    strncpy(ppp_user, user, sizeof(ppp_user) - 1);
+    strncpy(ppp_pass, pass, sizeof(ppp_pass) - 1);
+    xSemaphoreGive(mutex);
+}
+
+int eos_ppp_connect(void) {
+    return eos_modem_set_mode(EOS_CELL_UART_MODE_PPP);
+}
+
+int eos_ppp_disconnect(void) {
+    int rv = eos_modem_set_mode(EOS_CELL_UART_MODE_ATCMD);
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    memset(ppp_apn, 0, sizeof(ppp_apn));
+    memset(ppp_user, 0, sizeof(ppp_user));
+    memset(ppp_pass, 0, sizeof(ppp_pass));
+    xSemaphoreGive(mutex);
+
+    return rv;
+}
\ No newline at end of file
diff --git a/fw/esp32/components/eos/cell_pcm.c b/fw/esp32/components/eos/cell_pcm.c
new file mode 100644
index 0000000..5c59643
--- /dev/null
+++ b/fw/esp32/components/eos/cell_pcm.c
@@ -0,0 +1,258 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
+#include <freertos/task.h>
+#include <freertos/queue.h>
+#include <driver/i2s.h>
+#include <driver/gpio.h>
+#include <esp_log.h>
+
+#include "eos.h"
+#include "net.h"
+#include "msgq.h"
+#include "cell.h"
+
+#define PCM_MIC_WM          128
+#define PCM_HOLD_CNT_TX     3
+#define PCM_HOLD_CNT_RX     3
+#define PCM_SIZE_BUFQ       4
+#define PCM_SIZE_BUF        (PCM_MIC_WM * 4)
+
+#define PCM_GPIO_BCK        33
+#define PCM_GPIO_WS         4
+#define PCM_GPIO_DIN        34
+#define PCM_GPIO_DOUT       2
+
+#define PCM_ETYPE_WRITE     1
+
+static EOSBufQ pcm_buf_q;
+static unsigned char *pcm_bufq_array[PCM_SIZE_BUFQ];
+
+static EOSMsgQ pcm_evt_q;
+static EOSMsgItem pcm_evtq_array[PCM_SIZE_BUFQ];
+static char pcm_hold_tx;
+
+static i2s_dev_t* I2S[I2S_NUM_MAX] = {&I2S0, &I2S1};
+
+static QueueHandle_t i2s_queue;
+static SemaphoreHandle_t mutex;
+
+static const char *TAG = "EOS PCM";
+
+static void i2s_event_task(void *pvParameters) {
+    i2s_event_t event;
+    unsigned char *buf;
+    unsigned char _type;
+    size_t bytes_w;
+    ssize_t bytes_r;
+    uint16_t bytes_e;
+    ssize_t hold_bytes_r = 0;
+    unsigned char *hold_buf = NULL;
+    char hold_cnt = 0;
+
+    while (1) {
+        // Waiting for I2S event.
+        if (xQueueReceive(i2s_queue, &event, portMAX_DELAY)) {
+            switch (event.type) {
+                case I2S_EVENT_RX_DONE:
+                    // Event of I2S receiving data
+                    if (!hold_cnt) {
+                        buf = eos_net_alloc();
+                        buf[0] = EOS_CELL_MTYPE_PCM_DATA;
+                        bytes_r = eos_cell_pcm_read(buf + 1, PCM_MIC_WM);
+                        eos_net_send(EOS_NET_MTYPE_CELL, buf, bytes_r + 1, 0);
+                    } else {
+                        hold_cnt--;
+                        if (hold_buf == NULL) {
+                            hold_buf = eos_net_alloc();
+                            hold_buf[0] = EOS_CELL_MTYPE_PCM_DATA;
+                        }
+                        if (1 + hold_bytes_r + PCM_MIC_WM <= EOS_NET_SIZE_BUF) hold_bytes_r += eos_cell_pcm_read(hold_buf + 1 + hold_bytes_r, PCM_MIC_WM);
+                        if (hold_cnt == 0) {
+                            eos_net_send(EOS_NET_MTYPE_CELL, hold_buf, hold_bytes_r + 1, 0);
+                            hold_bytes_r = 0;
+                            hold_buf = NULL;
+                        }
+                    }
+
+                    buf = NULL;
+                    xSemaphoreTake(mutex, portMAX_DELAY);
+                    if (pcm_hold_tx && (eos_msgq_len(&pcm_evt_q) == PCM_HOLD_CNT_TX)) pcm_hold_tx = 0;
+                    if (!pcm_hold_tx) eos_msgq_pop(&pcm_evt_q, &_type, &buf, &bytes_e, NULL);
+                    xSemaphoreGive(mutex);
+
+                    if (buf) {
+                        i2s_write(I2S_NUM_0, (const void *)buf, bytes_e, &bytes_w, portMAX_DELAY);
+                        xSemaphoreTake(mutex, portMAX_DELAY);
+                        eos_bufq_push(&pcm_buf_q, buf);
+                        xSemaphoreGive(mutex);
+                    }
+                    break;
+                case I2S_EVENT_DMA_ERROR:
+                    ESP_LOGE(TAG, "*** I2S DMA ERROR ***");
+                    break;
+                case I2S_EVENT_MAX:
+                    hold_cnt = PCM_HOLD_CNT_RX;
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+    vTaskDelete(NULL);
+}
+
+void eos_cell_pcm_init(void) {
+    int i;
+
+    i2s_config_t i2s_config = {
+        .mode = I2S_MODE_SLAVE | I2S_MODE_TX | I2S_MODE_RX,
+        .sample_rate = 32000,
+        .bits_per_sample = 32,
+        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
+        .communication_format = I2S_COMM_FORMAT_I2S,
+        .dma_buf_count = 4,
+        .dma_buf_len = PCM_MIC_WM,
+        .use_apll = true,
+        .fixed_mclk = 2048000 * 8,
+        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1
+    };
+
+    i2s_pin_config_t pin_config = {
+        .bck_io_num     = PCM_GPIO_BCK,
+        .ws_io_num      = PCM_GPIO_WS,
+        .data_in_num    = PCM_GPIO_DIN,
+        .data_out_num   = PCM_GPIO_DOUT
+    };
+    i2s_driver_install(I2S_NUM_0, &i2s_config, 10, &i2s_queue);   //install and start i2s driver
+    i2s_stop(I2S_NUM_0);
+    i2s_set_pin(I2S_NUM_0, &pin_config);
+    gpio_matrix_in(pin_config.ws_io_num, I2S0I_WS_IN_IDX, 1);
+    gpio_matrix_in(pin_config.bck_io_num, I2S0I_BCK_IN_IDX, 1);
+    ESP_LOGI(TAG, "TX FIFO:%d TX CHAN:%d RX FIFO:%d RX CHAN:%d", I2S[I2S_NUM_0]->fifo_conf.tx_fifo_mod, I2S[I2S_NUM_0]->conf_chan.tx_chan_mod, I2S[I2S_NUM_0]->fifo_conf.rx_fifo_mod, I2S[I2S_NUM_0]->conf_chan.rx_chan_mod);
+
+    I2S[I2S_NUM_0]->fifo_conf.tx_fifo_mod = 2;
+    I2S[I2S_NUM_0]->conf_chan.tx_chan_mod = 0;
+
+    I2S[I2S_NUM_0]->fifo_conf.rx_fifo_mod = 3;
+    I2S[I2S_NUM_0]->conf_chan.rx_chan_mod = 1;
+    // I2S[I2S_NUM_0]->conf.tx_mono = 1;
+    I2S[I2S_NUM_0]->conf.rx_mono = 1;
+    // I2S[I2S_NUM_0]->timing.tx_dsync_sw = 1
+    // I2S[I2S_NUM_0]->timing.rx_dsync_sw = 1
+    // I2S[I2S_NUM_0]->conf.sig_loopback = 0;
+
+    // I2S[I2S_NUM_0]->timing.tx_bck_in_inv = 1;
+
+    eos_msgq_init(&pcm_evt_q, pcm_evtq_array, PCM_SIZE_BUFQ);
+    eos_bufq_init(&pcm_buf_q, pcm_bufq_array, PCM_SIZE_BUFQ);
+    for (i=0; i<PCM_SIZE_BUFQ; i++) {
+        eos_bufq_push(&pcm_buf_q, malloc(PCM_SIZE_BUF));
+    }
+
+    mutex = xSemaphoreCreateBinary();
+    xSemaphoreGive(mutex);
+
+    // Create a task to handle i2s event from ISR
+    xTaskCreate(i2s_event_task, "i2s_event", EOS_TASK_SSIZE_I2S, NULL, EOS_TASK_PRIORITY_I2S, NULL);
+    ESP_LOGI(TAG, "INIT");
+}
+
+ssize_t eos_cell_pcm_read(unsigned char *data, size_t size) {
+    static unsigned char buf[PCM_SIZE_BUF];
+    size_t bytes_r;
+    int i;
+
+    if (size > PCM_MIC_WM) return EOS_ERR;
+
+    esp_err_t ret = i2s_read(I2S_NUM_0, (void *)buf, size * 4, &bytes_r, portMAX_DELAY);
+    if (ret != ESP_OK) return EOS_ERR;
+
+    for (i=0; i<size/2; i++) {
+        data[i * 2] = buf[i * 8 + 3];
+        data[i * 2 + 1] = buf[i * 8 + 2];
+    }
+
+    return bytes_r / 4;
+}
+
+static ssize_t pcm_expand(unsigned char *buf, unsigned char *data, size_t size) {
+    int i;
+
+    if (size > PCM_MIC_WM) return EOS_ERR;
+
+    memset(buf, 0, PCM_SIZE_BUF);
+    for (i=0; i<size/2; i++) {
+        buf[i * 8 + 3] = data[i * 2];
+        buf[i * 8 + 2] = data[i * 2 + 1];
+    }
+
+    return size * 4;
+}
+
+int eos_cell_pcm_push(unsigned char *data, size_t size) {
+    unsigned char *buf = NULL;
+    ssize_t esize;
+    int rv;
+
+    if (size > PCM_MIC_WM) return EOS_ERR;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    if (pcm_hold_tx && (eos_msgq_len(&pcm_evt_q) == PCM_HOLD_CNT_TX)) {
+        unsigned char _type;
+        uint16_t _len;
+
+        eos_msgq_pop(&pcm_evt_q, &_type, &buf, &_len, NULL);
+    } else {
+        buf = eos_bufq_pop(&pcm_buf_q);
+    }
+    xSemaphoreGive(mutex);
+
+    if (buf == NULL) return EOS_ERR_EMPTY;
+
+    esize = pcm_expand(buf, data, size);
+    if (esize < 0) {
+        xSemaphoreTake(mutex, portMAX_DELAY);
+        eos_bufq_push(&pcm_buf_q, buf);
+        xSemaphoreGive(mutex);
+        return esize;
+    }
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    rv = eos_msgq_push(&pcm_evt_q, PCM_ETYPE_WRITE, buf, esize, 0);
+    if (rv) eos_bufq_push(&pcm_buf_q, buf);
+    xSemaphoreGive(mutex);
+
+    return rv;
+}
+
+void eos_cell_pcm_start(void) {
+    i2s_event_t evt;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    while (1) {
+        unsigned char _type;
+        unsigned char *buf;
+        uint16_t len;
+
+        eos_msgq_pop(&pcm_evt_q, &_type, &buf, &len, NULL);
+        if (buf) {
+            eos_bufq_push(&pcm_buf_q, buf);
+        } else {
+            break;
+        }
+    }
+    pcm_hold_tx = 1;
+    xSemaphoreGive(mutex);
+
+    evt.type = I2S_EVENT_MAX;   /* my type */
+    xQueueSend(i2s_queue, &evt, portMAX_DELAY);
+    i2s_zero_dma_buffer(I2S_NUM_0);
+    i2s_start(I2S_NUM_0);
+}
+
+void eos_cell_pcm_stop(void) {
+    i2s_stop(I2S_NUM_0);
+}
diff --git a/fw/esp32/components/eos/cell_sms.c b/fw/esp32/components/eos/cell_sms.c
new file mode 100644
index 0000000..05acc50
--- /dev/null
+++ b/fw/esp32/components/eos/cell_sms.c
@@ -0,0 +1,22 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <esp_log.h>
+
+#include "eos.h"
+#include "cell.h"
+
+
+void eos_cell_sms_handler(unsigned char mtype, unsigned char *buffer, uint16_t size) {
+    int rv;
+
+    rv = eos_modem_take(1000);
+    if (rv) return;
+
+    buffer += 1;
+    size -= 1;
+    switch (mtype) {
+    }
+
+    eos_modem_give();
+}
diff --git a/fw/esp32/components/eos/cell_ussd.c b/fw/esp32/components/eos/cell_ussd.c
new file mode 100644
index 0000000..2daa00f
--- /dev/null
+++ b/fw/esp32/components/eos/cell_ussd.c
@@ -0,0 +1,34 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <esp_log.h>
+
+#include "at_cmd.h"
+#include "cell.h"
+#include "gsm.h"
+
+static char cmd[256];
+
+void eos_cell_ussd_handler(unsigned char mtype, unsigned char *buffer, uint16_t size) {
+    int cmd_len, rv;
+
+    rv = eos_modem_take(1000);
+    if (rv) return;
+
+    buffer += 1;
+    size -= 1;
+    switch (mtype) {
+        case EOS_CELL_MTYPE_USSD_REQUEST:
+            if (size == 0) return;
+
+            buffer[size] = '\0';
+            cmd_len = snprintf(cmd, sizeof(cmd), "AT+CUSD=1,\"%s\",15\r", buffer);
+            if ((cmd_len < 0) || (cmd_len >= sizeof(cmd))) return;
+            at_cmd(cmd);
+
+            break;
+    }
+
+    eos_modem_give();
+}
+
diff --git a/fw/esp32/components/eos/cell_voice.c b/fw/esp32/components/eos/cell_voice.c
new file mode 100644
index 0000000..3f6a2a5
--- /dev/null
+++ b/fw/esp32/components/eos/cell_voice.c
@@ -0,0 +1,20 @@
+#include <stdlib.h>
+
+#include <esp_log.h>
+
+#include "eos.h"
+#include "cell.h"
+
+void eos_cell_voice_handler(unsigned char mtype, unsigned char *buffer, uint16_t size) {
+    int rv;
+
+    rv = eos_modem_take(1000);
+    if (rv) return;
+
+    buffer += 1;
+    size -= 1;
+    switch (mtype) {
+    }
+
+    eos_modem_give();
+}
diff --git a/fw/esp32/components/eos/component.mk b/fw/esp32/components/eos/component.mk
new file mode 100644
index 0000000..e69de29
diff --git a/fw/esp32/components/eos/drv2605l.c b/fw/esp32/components/eos/drv2605l.c
new file mode 100644
index 0000000..3944289
--- /dev/null
+++ b/fw/esp32/components/eos/drv2605l.c
@@ -0,0 +1,82 @@
+#include <stdlib.h>
+
+#include <esp_log.h>
+
+#include "eos.h"
+#include "i2c.h"
+
+static const char *TAG = "EOS DRV2605L";
+
+#define DRV2605L_ADDR                0x5A
+
+#define DRV2605_REG_STATUS 0x00       ///< Status register
+#define DRV2605_REG_MODE 0x01         ///< Mode register
+#define DRV2605_MODE_INTTRIG 0x00     ///< Internal trigger mode
+#define DRV2605_MODE_EXTTRIGEDGE 0x01 ///< External edge trigger mode
+#define DRV2605_MODE_EXTTRIGLVL 0x02  ///< External level trigger mode
+#define DRV2605_MODE_PWMANALOG 0x03   ///< PWM/Analog input mode
+#define DRV2605_MODE_AUDIOVIBE 0x04   ///< Audio-to-vibe mode
+#define DRV2605_MODE_REALTIME 0x05    ///< Real-time playback (RTP) mode
+#define DRV2605_MODE_DIAGNOS 0x06     ///< Diagnostics mode
+#define DRV2605_MODE_AUTOCAL 0x07     ///< Auto calibration mode
+
+#define DRV2605_REG_RTPIN 0x02        ///< Real-time playback input register
+#define DRV2605_REG_LIBRARY 0x03      ///< Waveform library selection register
+#define DRV2605_REG_WAVESEQ1 0x04     ///< Waveform sequence register 1
+#define DRV2605_REG_WAVESEQ2 0x05     ///< Waveform sequence register 2
+#define DRV2605_REG_WAVESEQ3 0x06     ///< Waveform sequence register 3
+#define DRV2605_REG_WAVESEQ4 0x07     ///< Waveform sequence register 4
+#define DRV2605_REG_WAVESEQ5 0x08     ///< Waveform sequence register 5
+#define DRV2605_REG_WAVESEQ6 0x09     ///< Waveform sequence register 6
+#define DRV2605_REG_WAVESEQ7 0x0A     ///< Waveform sequence register 7
+#define DRV2605_REG_WAVESEQ8 0x0B     ///< Waveform sequence register 8
+
+#define DRV2605_REG_GO 0x0C           ///< Go register
+#define DRV2605_REG_OVERDRIVE 0x0D    ///< Overdrive time offset register
+#define DRV2605_REG_SUSTAINPOS 0x0E   ///< Sustain time offset, positive register
+#define DRV2605_REG_SUSTAINNEG 0x0F   ///< Sustain time offset, negative register
+#define DRV2605_REG_BREAK 0x10        ///< Brake time offset register
+#define DRV2605_REG_AUDIOCTRL 0x11    ///< Audio-to-vibe control register
+#define DRV2605_REG_AUDIOLVL 0x12     ///< Audio-to-vibe minimum input level register
+#define DRV2605_REG_AUDIOMAX 0x13     ///< Audio-to-vibe maximum input level register
+#define DRV2605_REG_AUDIOOUTMIN 0x14  ///< Audio-to-vibe minimum output drive register
+#define DRV2605_REG_AUDIOOUTMAX 0x15  ///< Audio-to-vibe maximum output drive register
+#define DRV2605_REG_RATEDV 0x16       ///< Rated voltage register
+#define DRV2605_REG_CLAMPV 0x17       ///< Overdrive clamp voltage register
+#define DRV2605_REG_AUTOCALCOMP 0x18  ///< Auto-calibration compensation result register
+#define DRV2605_REG_AUTOCALEMP 0x19   ///< Auto-calibration back-EMF result register
+#define DRV2605_REG_FEEDBACK 0x1A     ///< Feedback control register
+#define DRV2605_REG_CONTROL1 0x1B     ///< Control1 Register
+#define DRV2605_REG_CONTROL2 0x1C     ///< Control2 Register
+#define DRV2605_REG_CONTROL3 0x1D     ///< Control3 Register
+#define DRV2605_REG_CONTROL4 0x1E     ///< Control4 Register
+#define DRV2605_REG_VBAT 0x21         ///< Vbat voltage-monitor register
+#define DRV2605_REG_LRARESON 0x22     ///< LRA resonance-period register
+
+void eos_drv2605l_test(void) {
+    uint8_t data = 0;
+
+    int ret = eos_i2c_read(DRV2605L_ADDR, DRV2605_REG_STATUS, &data, 1);
+    if (ret) ESP_LOGE(TAG, "I2C ERROR!");
+
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_MODE, 0x00);      // out of standby
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_RTPIN, 0x00);     // no real-time-playback
+
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_WAVESEQ1, 1);     // strong click
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_WAVESEQ2, 0);     // end sequence
+
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_OVERDRIVE, 0);    // no overdrive
+
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_SUSTAINPOS, 0);
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_SUSTAINNEG, 0);
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_BREAK, 0);
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_AUDIOMAX, 0x64);
+
+    // LRA open loop
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_FEEDBACK, eos_i2c_read8(DRV2605L_ADDR, DRV2605_REG_FEEDBACK) | 0x80);     // turn on N_ERM_LRA
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_CONTROL3, eos_i2c_read8(DRV2605L_ADDR, DRV2605_REG_CONTROL3) | 0x01);     // turn on LRA_OPEN_LOOP
+
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_LIBRARY, 6);      // set LRA library
+    eos_i2c_write8(DRV2605L_ADDR, DRV2605_REG_GO, 1);           // go
+
+}
diff --git a/fw/esp32/components/eos/gsm.c b/fw/esp32/components/eos/gsm.c
new file mode 100644
index 0000000..788722e
--- /dev/null
+++ b/fw/esp32/components/eos/gsm.c
@@ -0,0 +1,453 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "gsm.h"
+
+#define DIVC(x,y)                   ((x) / (y) + ((x) % (y) != 0))
+
+uint8_t pdu_getc(char *pdu) {
+    int ch;
+    sscanf(pdu, "%2X", &ch);
+    return ch;
+}
+
+void pdu_putc(uint8_t ch, char *pdu) {
+    sprintf(pdu, "%.2X", ch);
+}
+
+void pdu_puts(uint8_t *s, int s_len, char *pdu) {
+    int i;
+
+    for (i=0; i<s_len; i++) {
+        sprintf(pdu + 2 * i, "%.2X", s[i]);
+    }
+}
+
+void pdu_gets(char *pdu, uint8_t *s, int s_len) {
+    int i, ch;
+
+    for (i=0; i<s_len; i++) {
+        sscanf(pdu + 2 * i, "%2X", &ch);
+        s[i] = ch;
+    }
+}
+
+void gsm_dcs_dec(uint8_t dcs, uint8_t *enc, uint16_t *flags) {
+    if ((dcs & GSM_DCS_GENERAL_IND) == 0) {
+        *enc = dcs & GSM_DCS_ENC;
+        if (dcs & GSM_DCS_CLASS_IND) {
+            *flags |= GSM_FLAG_CLASS;
+            *flags |= (uint16_t)(dcs & GSM_DCS_CLASS) << 8;
+        }
+        if (dcs & GSM_DCS_COMPRESS_IND) *flags |= GSM_FLAG_COMPRESS;
+        if (dcs & GSM_DCS_DELETE_IND) *flags |= GSM_FLAG_DELETE;
+    } else {
+        uint8_t group = dcs & GSM_DCS_GROUP;
+
+        switch (group) {
+            case GSM_DCS_MWI_DISCARD:
+            case GSM_DCS_MWI_STORE_GSM7:
+            case GSM_DCS_MWI_STORE_UCS2:
+                if (group == GSM_DCS_MWI_STORE_UCS2) {
+                    *enc = GSM_ENC_UCS2;
+                } else {
+                    *enc = GSM_ENC_7BIT;
+                }
+                if (GSM_DCS_MWI_DISCARD) *flags |= GSM_FLAG_DISCARD;
+                *flags |= GSM_FLAG_MWI;
+                *flags |= (uint16_t)(dcs & (GSM_DCS_MWI_SENSE | GSM_DCS_MWI_TYPE)) << 12;
+                break;
+
+            case GSM_DCS_ENCLASS:
+                *flags |= GSM_FLAG_CLASS;
+                *flags |= (uint16_t)(dcs & GSM_DCS_CLASS) << 8;
+                *enc = dcs & GSM_DCS_ENCLASS_ENC ? GSM_ENC_8BIT : GSM_ENC_7BIT;
+                break;
+        }
+    }
+}
+
+void gsm_dcs_enc(uint8_t enc, uint16_t flags, uint8_t *dcs) {
+    *dcs = enc;
+    if (flags & GSM_FLAG_CLASS) {
+        *dcs |= GSM_DCS_CLASS_IND;
+        *dcs |= (flags >> 8) & GSM_DCS_CLASS;
+    }
+    if (flags & GSM_FLAG_COMPRESS) *dcs |= GSM_DCS_COMPRESS_IND;
+    if (flags & GSM_FLAG_DELETE) *dcs |= GSM_DCS_DELETE_IND;
+}
+
+int gsm_ts_enc(char *ts, char *pdu, int pdu_size) {
+    uint8_t tz;
+    int tz_hh, tz_mm;
+
+    if (pdu_size < 14) return GSM_ERR;
+
+    pdu[1]  = ts[2];    // YY
+    pdu[0]  = ts[3];
+
+    pdu[3]  = ts[5];    // MM
+    pdu[2]  = ts[6];
+
+    pdu[5]  = ts[8];    // DD
+    pdu[4]  = ts[9];
+
+    pdu[7]  = ts[11];   // hh
+    pdu[6]  = ts[12];
+
+    pdu[9]  = ts[14];   // mm
+    pdu[8]  = ts[15];
+
+    pdu[11] = ts[17];   // ss
+    pdu[10] = ts[18];
+
+    sscanf(ts + 20, "%2d:%2d", &tz_hh, &tz_mm);
+    tz = tz_hh * 4 + tz_mm / 15;
+    tz = (tz / 10) | ((tz % 10) << 4);
+    if (ts[19] == '-') tz |= 0x08;
+
+    pdu_putc(tz, pdu + 12);
+
+    return 14;
+}
+
+int gsm_ts_dec(char *pdu, int pdu_len, char *ts) {
+    uint8_t tz;
+
+    if (pdu_len < 14) return GSM_ERR;
+
+    ts[0]  = '2';
+    ts[1]  = '0';
+    ts[2]  = pdu[1];    // YY
+    ts[3]  = pdu[0];
+    ts[4]  = '-';
+    ts[5]  = pdu[3];    // MM
+    ts[6]  = pdu[2];
+    ts[7]  = '-';
+    ts[8]  = pdu[5];    // DD
+    ts[9]  = pdu[4];
+    ts[10] = 'T';
+    ts[11] = pdu[7];    // hh
+    ts[12] = pdu[6];
+    ts[13] = ':';
+    ts[14] = pdu[9];    // mm
+    ts[15] = pdu[8];
+    ts[16] = ':';
+    ts[17] = pdu[11];   // ss
+    ts[18] = pdu[10];
+
+    tz = pdu_getc(pdu + 12);
+    if (tz & 0x08) {
+        ts[19] = '-';
+        tz = tz & ~0x08;
+    } else {
+        ts[19] = '+';
+    }
+    tz = (tz & 0x0f) * 10 + (tz >> 4);
+    sprintf(ts + 20, "%.2d:%.2d", tz / 4, (tz % 4) * 15);
+
+    return 14;
+}
+
+int gsm_7bit_enc(char *text, int text_len, char *pdu, int padb) {
+    uint8_t carry = 0;
+    int i = 0, pdu_len = 0, shc = 0;
+
+    if (!text_len) return 0;
+
+    if (padb) {
+        shc = 7 - padb;
+    } else {
+        carry = *text;
+        i++;
+    }
+
+    while (i < text_len) {
+        pdu_putc(carry | (*(text + i) << (7 - shc)), pdu + pdu_len);
+        pdu_len += 2;
+
+        shc++;
+        shc = shc % 7;
+        if (!shc) {
+            i++;
+            if (i == text_len) return pdu_len;
+        }
+
+        carry = *(text + i) >> shc;
+        i++;
+    }
+    pdu_putc(carry, pdu + pdu_len);
+    pdu_len += 2;
+
+    return pdu_len;
+}
+
+int gsm_7bit_dec(char *pdu, char *text, int text_len, int padb) {
+    uint8_t ch;
+    uint8_t carry = 0;
+    int i = 0, pdu_len = 0, shc = 0;
+
+    if (!text_len) return 0;
+
+    if (padb) {
+        ch = pdu_getc(pdu);
+        pdu_len += 2;
+        if (padb == 1) {
+            *text = ch >> 1;
+            i++;
+        } else {
+            carry = ch >> padb;
+            shc = 8 - padb;
+        }
+    }
+
+    while (i < text_len) {
+        ch = pdu_getc(pdu + pdu_len);
+        pdu_len += 2;
+
+        *(text + i) = ((ch << shc) | carry) & 0x7f;
+        carry = ch >> (7 - shc);
+        i++;
+
+        shc++;
+        shc = shc % 7;
+        if (!shc && (i < text_len)) {
+            *(text + i) = carry;
+            carry = 0;
+            i++;
+        }
+    }
+
+    return pdu_len;
+}
+
+int gsm_addr_enc(char *addr, int addr_len, uint8_t addr_type, char *pdu, int pdu_size) {
+    int _pdu_len;
+
+    addr_type |= GSM_EXT;
+
+    if ((addr_type & GSM_TON) == GSM_TON_ALPHANUMERIC) {
+        int _addr_len = DIVC(addr_len * 7, 4);
+
+        _pdu_len = 4 + DIVC(_addr_len, 2) * 2;
+        if (pdu_size < _pdu_len) return GSM_ERR;
+
+        pdu_putc(_addr_len, pdu);
+        pdu_putc(addr_type, pdu + 2);
+        gsm_7bit_enc(addr, addr_len, pdu, 0);
+    } else {
+        int i;
+
+        if (addr_type & GSM_TON_INTERNATIONAL) {
+            if (addr[0] != '+') return GSM_ERR;
+            addr++;
+            addr_len--;
+        }
+        _pdu_len = 4 + DIVC(addr_len, 2) * 2;
+        if (pdu_size < _pdu_len) return GSM_ERR;
+
+        pdu_putc(addr_len, pdu);
+        pdu_putc(addr_type, pdu + 2);
+        for (i=0; i<addr_len / 2; i++) {
+            pdu[4 + 2 * i] = addr[2 * i + 1];
+            pdu[4 + 2 * i + 1] = addr[2 * i];
+        }
+        if (addr_len % 2 != 0) {
+            pdu[4 + 2 * i] = 'F';
+            pdu[4 + 2 * i + 1] = addr[2 * i];
+        }
+    }
+
+    return _pdu_len;
+}
+
+int gsm_addr_dec(char *pdu, int pdu_len, char *addr, int addr_size, int *addr_len, uint8_t *addr_type) {
+    int _pdu_len;
+
+    if (pdu_len < 4) return GSM_ERR;
+
+    *addr_len = pdu_getc(pdu);
+    *addr_type = pdu_getc(pdu + 2);
+
+    if (!(*addr_type & GSM_EXT)) return GSM_ERR;
+
+    _pdu_len = 4 + DIVC(*addr_len, 2) * 2;
+    if (pdu_len < _pdu_len) return GSM_ERR;
+
+    if ((*addr_type & GSM_TON) == GSM_TON_ALPHANUMERIC) {
+        *addr_len = (*addr_len * 4) / 7;
+        if (addr_size < *addr_len) return GSM_ERR;
+
+        gsm_7bit_dec(pdu + 4, addr, *addr_len, 0);
+    } else {
+        int i;
+        int _addr_len = *addr_len;
+
+        if (*addr_type & GSM_TON_INTERNATIONAL) {
+            addr[0] = '+';
+            addr++;
+            (*addr_len)++;
+        }
+        if (addr_size < *addr_len) return GSM_ERR;
+
+        for (i=0; i<_addr_len / 2; i++) {
+            addr[2 * i] = pdu[4 + 2 * i + 1];
+            addr[2 * i + 1] = pdu[4 + 2 * i];
+        }
+        if (_addr_len % 2 != 0) {
+            addr[2 * i] = pdu[4 + 2 * i + 1];
+        }
+    }
+
+    return _pdu_len;
+}
+
+int gsm_sms_enc(uint8_t pid, char *addr, int addr_len, uint8_t addr_type, uint8_t *udh, int udh_len, uint8_t *msg, int msg_len, uint8_t enc, uint16_t flags, char *pdu, int pdu_size) {
+    int rv, _pdu_len = 0;
+    uint8_t mti = GSM_MTI_SUBMIT;
+    uint8_t dcs = 0;
+    uint8_t udl;
+
+    if (udh_len) mti |= GSM_UDHI;
+
+    if (pdu_size < 4) return GSM_ERR;
+    pdu_putc(mti, pdu);
+    pdu_putc(00, pdu + 2);
+    _pdu_len += 4;
+
+    rv = gsm_addr_enc(addr, addr_len, addr_type, pdu + _pdu_len, pdu_size - _pdu_len);
+    if (rv < 0) return rv;
+    _pdu_len += rv;
+
+    if (pdu_size < _pdu_len + 4) return GSM_ERR;
+    gsm_dcs_enc(enc, flags, &dcs);
+    pdu_putc(pid, pdu + _pdu_len);
+    pdu_putc(dcs, pdu + _pdu_len + 2);
+    _pdu_len += 4;
+
+    if (enc == GSM_ENC_7BIT) {
+        int udh_blen = 0;
+        int padb = 0;
+
+        if (udh_len) {
+            udh_blen = 8 * (udh_len + 1);
+            padb = DIVC(udh_blen, 7) * 7 - udh_blen;
+        }
+        udl = DIVC(udh_blen, 7) + msg_len;
+
+        if (pdu_size < _pdu_len + (DIVC(udl * 7, 8) + 1) * 2) return GSM_ERR;
+        pdu_putc(udl, pdu + _pdu_len);
+        _pdu_len += 2;
+
+        if (udh_len) {
+            pdu_putc(udh_len, pdu + _pdu_len);
+            pdu_puts(udh, udh_len, pdu + _pdu_len + 2);
+            _pdu_len += (udh_len + 1) * 2;
+        }
+
+        rv = gsm_7bit_enc((char *)msg, msg_len, pdu + _pdu_len, padb);
+        if (rv < 0) return rv;
+        _pdu_len += rv;
+    } else {
+        udl = msg_len + (udh_len ? udh_len + 1 : 0);
+
+        if (pdu_size < _pdu_len + (udl + 1) * 2) return GSM_ERR;
+        pdu_putc(udl, pdu + _pdu_len);
+        _pdu_len += 2;
+
+        if (udh_len) {
+            pdu_putc(udh_len, pdu + _pdu_len);
+            pdu_puts(udh, udh_len, pdu + _pdu_len + 2);
+            _pdu_len += (udh_len + 1) * 2;
+        }
+
+        pdu_puts(msg, msg_len, pdu + _pdu_len);
+        _pdu_len += msg_len * 2;
+    }
+
+    return _pdu_len;
+}
+
+int gsm_sms_dec(char *pdu, int pdu_len, uint8_t *pid, char *addr, int addr_size, int *addr_len, uint8_t *addr_type, uint8_t *udh, int udh_size, int *udh_len, uint8_t *msg, int msg_size, int *msg_len, char *ts, uint8_t *enc, uint16_t *flags) {
+    int rv, _pdu_len = 0;
+    uint8_t mti;
+    uint8_t dcs;
+    uint8_t udl;
+
+    if (pdu_len < 2) return GSM_ERR;
+    mti = pdu_getc(pdu);
+    _pdu_len += 2;
+    if ((mti & GSM_MTI) != GSM_MTI_DELIVER) return GSM_ERR;
+
+    rv = gsm_addr_dec(pdu + _pdu_len, pdu_len - _pdu_len, addr, addr_size, addr_len, addr_type);
+    if (rv < 0) return rv;
+    _pdu_len += rv;
+
+    if (pdu_len < _pdu_len + 4) return GSM_ERR;
+    *pid = pdu_getc(pdu + _pdu_len);
+    dcs = pdu_getc(pdu + _pdu_len + 2);
+    _pdu_len += 4;
+    gsm_dcs_dec(dcs, enc, flags);
+
+    rv = gsm_ts_dec(pdu + _pdu_len, pdu_len - _pdu_len, ts);
+    if (rv < 0) return rv;
+    _pdu_len += rv;
+
+    if (pdu_len < _pdu_len + 2) return GSM_ERR;
+    udl = pdu_getc(pdu + _pdu_len);
+    _pdu_len += 2;
+
+    if ((mti & GSM_UDHI) && (udl == 0)) return GSM_ERR;
+    *udh_len = 0;
+
+    if (*enc == GSM_ENC_7BIT) {
+        int udh_blen = 0;
+        int padb = 0;
+
+        if (pdu_len < _pdu_len + DIVC(udl * 7, 8) * 2) return GSM_ERR;
+
+        if (mti & GSM_UDHI) {
+            *udh_len = pdu_getc(pdu + _pdu_len);
+            udh_blen = 8 * (*udh_len + 1);
+            padb = DIVC(udh_blen, 7) * 7 - udh_blen;
+
+            if (udl * 7 < udh_blen) return GSM_ERR;
+            if (udh_size < *udh_len) return GSM_ERR;
+
+            pdu_gets(pdu + _pdu_len + 2, udh, *udh_len);
+            _pdu_len += (*udh_len + 1) * 2;
+        } else {
+            *udh_len = 0;
+        }
+
+        *msg_len = udl - DIVC(udh_blen, 7);
+        if (msg_size < *msg_len) return GSM_ERR;
+
+        rv = gsm_7bit_dec(pdu + _pdu_len, (char *)msg, *msg_len, padb);
+        if (rv < 0) return rv;
+        _pdu_len += rv;
+    } else {
+        if (pdu_len < _pdu_len + udl * 2) return GSM_ERR;
+
+        if (mti & GSM_UDHI) {
+            *udh_len = pdu_getc(pdu + _pdu_len);
+
+            if (udl < *udh_len + 1) return GSM_ERR;
+            if (udh_size < *udh_len) return GSM_ERR;
+
+            pdu_gets(pdu + _pdu_len + 2, udh, *udh_len);
+            _pdu_len += (*udh_len + 1) * 2;
+        } else {
+            *udh_len = 0;
+        }
+
+        *msg_len = udl - (*udh_len ? *udh_len + 1 : 0);
+        if (msg_size < *msg_len) return GSM_ERR;
+
+        pdu_gets(pdu + _pdu_len, msg, *msg_len);
+        _pdu_len += *msg_len * 2;
+    }
+
+    return _pdu_len;
+}
diff --git a/fw/esp32/components/eos/gsm_cp.c b/fw/esp32/components/eos/gsm_cp.c
new file mode 100644
index 0000000..3ab98c5
--- /dev/null
+++ b/fw/esp32/components/eos/gsm_cp.c
@@ -0,0 +1,109 @@
+#include <stdint.h>
+
+#include "gsm.h"
+
+#define UCS2_SUPL_SIZE  11
+
+static const uint16_t gsm7_to_ucs2[128] = {
+    0x0040, 0x00a3, 0x0024, 0x00a5, 0x00e8, 0x00e9, 0x00f9, 0x00ec, 0x00f2, 0x00c7, 0x000a, 0x00d8, 0x00f8, 0x000d, 0x00c5, 0x00e5,
+    0x0394, 0x005f, 0x03a6, 0x0393, 0x039b, 0x03a9, 0x03a0, 0x03a8, 0x03a3, 0x0398, 0x039e, 0x001b, 0x00c6, 0x00e6, 0x00df, 0x00c9,
+    0x0020, 0x0021, 0x0022, 0x0023, 0x00a4, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+    0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+    0x00a1, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x00c4, 0x00d6, 0x00d1, 0x00dc, 0x00a7,
+    0x00bf, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+    0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x00e4, 0x00f6, 0x00f1, 0x00fc, 0x00e0
+};
+
+static const uint16_t gsm7e_to_ucs2[128] = {
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x000c, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    0xffff, 0xffff, 0xffff, 0xffff, 0x005e, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x007b, 0x007d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x005c,
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x005b, 0x007e, 0x005d, 0xffff,
+    0x007c, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x20ac, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
+};
+
+static const uint16_t ucs2_to_gsm7[256] = {
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x000a, 0xffff, 0x1b0a, 0x000d, 0xffff, 0xffff,
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x001b, 0xffff, 0xffff, 0xffff, 0xffff,
+    0x0020, 0x0021, 0x0022, 0x0023, 0x0002, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+    0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+    0x0000, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x1b3c, 0x1b2f, 0x1b3e, 0x1b14, 0x0011,
+    0xffff, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+    0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x1b28, 0x1b40, 0x1b29, 0x1b3d, 0xffff,
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    0xffff, 0x0040, 0xffff, 0x0001, 0x0024, 0x0003, 0xffff, 0x005f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0060,
+    0xffff, 0xffff, 0xffff, 0xffff, 0x005b, 0x000e, 0x001c, 0x0009, 0xffff, 0x001f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    0xffff, 0x005d, 0xffff, 0xffff, 0xffff, 0xffff, 0x005c, 0xffff, 0x000b, 0xffff, 0xffff, 0xffff, 0x005e, 0xffff, 0xffff, 0x001e,
+    0x007f, 0xffff, 0xffff, 0xffff, 0x007b, 0x000f, 0x001d, 0xffff, 0x0004, 0x0005, 0xffff, 0xffff, 0x0007, 0xffff, 0xffff, 0xffff,
+    0xffff, 0x007d, 0x0008, 0xffff, 0xffff, 0xffff, 0x007c, 0xffff, 0x000c, 0x0006, 0xffff, 0xffff, 0x007e, 0xffff, 0xffff, 0xffff
+};
+
+static const uint16_t ucs2_to_gsm7_supl[UCS2_SUPL_SIZE][2] = {
+    {0x0394, 0x10},
+    {0x03a6, 0x12},
+    {0x0393, 0x13},
+    {0x039b, 0x14},
+    {0x03a9, 0x15},
+    {0x03a0, 0x16},
+    {0x03a8, 0x17},
+    {0x03a3, 0x18},
+    {0x0398, 0x19},
+    {0x039e, 0x1a},
+    {0x20ac, 0x1b65}
+};
+
+int gsm_ucs2_to_7bit(uint16_t ucs2, char *gsm7, int gsm7_size) {
+    uint16_t ch = 0xffff;
+    int ret = 0;
+
+    if (gsm7_size < 1) return GSM_ERR;
+
+    if (ucs2 < 256) {
+        ch = ucs2_to_gsm7[ucs2];
+    } else {
+        int i;
+
+        for (i=0; i<UCS2_SUPL_SIZE; i++) {
+            if (ucs2_to_gsm7_supl[i][0] == ucs2) {
+                ch = ucs2_to_gsm7_supl[i][1];
+                break;
+            }
+        }
+    }
+    if (ch == 0xffff) return GSM_ERR;
+    if (ch & 0xff00) {
+        if (gsm7_size < 2) return GSM_ERR;
+        *gsm7 = 0x1b;
+        gsm7++;
+        ret++;
+    }
+    *gsm7 = ch & 0x7f;
+    ret++;
+
+    return ret;
+}
+
+int gsm_7bit_to_ucs2(char *gsm7, int gsm7_len, uint16_t *ucs2) {
+    int ret;
+
+    if (gsm7_len < 1) return GSM_ERR;
+    if (*gsm7 != 0x1b) {
+        *ucs2 = gsm7_to_ucs2[*gsm7 & 0x7f];
+        ret = 1;
+    } else {
+        if (gsm7_len < 2) return GSM_ERR;
+        gsm7++;
+        ret = 2;
+        *ucs2 = gsm7e_to_ucs2[*gsm7 & 0x7f];
+    }
+    if (*ucs2 == 0xffff) return GSM_ERR;
+
+    return ret;
+}
diff --git a/fw/esp32/components/eos/i2c.c b/fw/esp32/components/eos/i2c.c
new file mode 100644
index 0000000..5b8fcc7
--- /dev/null
+++ b/fw/esp32/components/eos/i2c.c
@@ -0,0 +1,103 @@
+#include <stdlib.h>
+
+#include <esp_log.h>
+#include <driver/i2c.h>
+
+#include "eos.h"
+
+static const char *TAG = "EOS I2C";
+
+#define I2C_MASTER_NUM              I2C_NUM_0
+#define I2C_MASTER_FREQ_HZ          100000
+#define I2C_MASTER_GPIO_SCL         25
+#define I2C_MASTER_GPIO_SDA         26
+
+#define I2C_MASTER_TX_BUF_DISABLE   0       /*!< I2C master doesn't need buffer */
+#define I2C_MASTER_RX_BUF_DISABLE   0       /*!< I2C master doesn't need buffer */
+#define ACK_CHECK_EN                0x1     /*!< I2C master will check ack from slave*/
+#define ACK_CHECK_DIS               0x0     /*!< I2C master will not check ack from slave */
+#define ACK_VAL                     0x0     /*!< I2C ack value */
+#define NCK_VAL                     0x1     /*!< I2C nack value */
+
+/**
+ * @brief i2c initialization
+ */
+
+void eos_i2c_init(void) {
+    i2c_config_t conf;
+    conf.mode = I2C_MODE_MASTER;
+    conf.sda_io_num = I2C_MASTER_GPIO_SDA;
+    conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
+    conf.scl_io_num = I2C_MASTER_GPIO_SCL;
+    conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
+    conf.master.clk_speed = I2C_MASTER_FREQ_HZ;
+    i2c_param_config(I2C_MASTER_NUM, &conf);
+    i2c_driver_install(I2C_MASTER_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
+    ESP_LOGI(TAG, "INIT");
+}
+
+/**
+ * @brief i2c read
+ */
+
+int eos_i2c_read(uint8_t addr, uint8_t reg, uint8_t *data, size_t len) {
+    int i, ret;
+    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+    i2c_master_start(cmd);
+    i2c_master_write_byte(cmd, addr << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN);
+    i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
+    i2c_master_start(cmd);
+    i2c_master_write_byte(cmd, addr << 1 | I2C_MASTER_READ, ACK_CHECK_EN);
+    for (i=0; i < len - 1; i++) {
+        i2c_master_read_byte(cmd, data+i, ACK_VAL);
+    }
+    i2c_master_read_byte(cmd, data+i, NCK_VAL);
+    i2c_master_stop(cmd);
+    ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
+    i2c_cmd_link_delete(cmd);
+    if (ret != ESP_OK) {
+        return EOS_ERR;
+    }
+    return EOS_OK;
+}
+
+/**
+ * @brief i2c read8
+ */
+
+uint8_t eos_i2c_read8(uint8_t addr, uint8_t reg) {
+    uint8_t data;
+    eos_i2c_read(addr, reg, &data, 1);
+    return data;
+}
+
+/**
+ * @brief i2c write
+ */
+
+int eos_i2c_write(uint8_t addr, uint8_t reg, uint8_t *data, size_t len) {
+    int i, ret;
+    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+    i2c_master_start(cmd);
+    i2c_master_write_byte(cmd, addr << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN);
+    i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
+    for (i=0; i < len; i++) {
+        i2c_master_write_byte(cmd, *(data+i), ACK_CHECK_EN);
+    }
+    i2c_master_stop(cmd);
+    ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
+    i2c_cmd_link_delete(cmd);
+    if (ret != ESP_OK) {
+        return EOS_ERR;
+    }
+    return EOS_OK;
+}
+
+/**
+ * @brief i2c write8
+ */
+
+void eos_i2c_write8(uint8_t addr, uint8_t reg, uint8_t data) {
+    eos_i2c_write(addr, reg, &data, 1);
+}
+
diff --git a/fw/esp32/components/eos/include/_net.h b/fw/esp32/components/eos/include/_net.h
new file mode 100644
index 0000000..35b5308
--- /dev/null
+++ b/fw/esp32/components/eos/include/_net.h
@@ -0,0 +1 @@
+#include "net.h"
\ No newline at end of file
diff --git a/fw/esp32/components/eos/include/at_cmd.h b/fw/esp32/components/eos/include/at_cmd.h
new file mode 100644
index 0000000..615a1c1
--- /dev/null
+++ b/fw/esp32/components/eos/include/at_cmd.h
@@ -0,0 +1,20 @@
+#include <sys/types.h>
+#include <stdint.h>
+#include <regex.h>
+
+#define AT_SIZE_NMATCH      4
+#define AT_SIZE_PATTERN     64
+
+#define AT_SIZE_URC_LIST    16
+
+#define AT_URC_OK           0
+#define AT_URC_MORE         1
+
+typedef int (*at_urc_cb_t) (char *, regmatch_t[]);
+
+void at_init(void);
+int at_urc_process(char *urc);
+int at_urc_insert(char *pattern, at_urc_cb_t cb, int flags);
+int at_urc_delete(char *pattern);
+void at_cmd(char *cmd);
+int at_expect(char *str_ok, char *str_err, uint32_t timeout);
diff --git a/fw/esp32/components/eos/include/bq25895.h b/fw/esp32/components/eos/include/bq25895.h
new file mode 100644
index 0000000..b5a7f92
--- /dev/null
+++ b/fw/esp32/components/eos/include/bq25895.h
@@ -0,0 +1,3 @@
+#include <stdint.h>
+
+void eos_bq25895_set_ilim(void);
\ No newline at end of file
diff --git a/fw/esp32/components/eos/include/cell.h b/fw/esp32/components/eos/include/cell.h
new file mode 100644
index 0000000..3bf6b32
--- /dev/null
+++ b/fw/esp32/components/eos/include/cell.h
@@ -0,0 +1,68 @@
+#include <sys/types.h>
+#include <stdint.h>
+
+#define EOS_CELL_MTYPE_DEV          0x00
+#define EOS_CELL_MTYPE_VOICE        0x10
+#define EOS_CELL_MTYPE_SMS          0x20
+#define EOS_CELL_MTYPE_CBS          0x30
+#define EOS_CELL_MTYPE_USSD         0x40
+#define EOS_CELL_MTYPE_DATA         0x70
+
+#define EOS_CELL_MTYPE_MASK         0xf0
+#define EOS_CELL_MAX_MTYPE          8
+
+#define EOS_CELL_MTYPE_READY        0
+#define EOS_CELL_MTYPE_UART_DATA    1
+#define EOS_CELL_MTYPE_UART_TAKE    2
+#define EOS_CELL_MTYPE_UART_GIVE    3
+#define EOS_CELL_MTYPE_PCM_DATA     4
+#define EOS_CELL_MTYPE_PCM_START    5
+#define EOS_CELL_MTYPE_PCM_STOP     6
+
+#define EOS_CELL_MTYPE_VOICE_DIAL   1
+#define EOS_CELL_MTYPE_VOICE_RING   2
+#define EOS_CELL_MTYPE_VOICE_ANSWER 3
+#define EOS_CELL_MTYPE_VOICE_HANGUP 4
+#define EOS_CELL_MTYPE_VOICE_BEGIN  5
+#define EOS_CELL_MTYPE_VOICE_END    6
+
+#define EOS_CELL_MTYPE_USSD_REQUEST 1
+#define EOS_CELL_MTYPE_USSD_REPLY   2
+
+#define EOS_CELL_UART_MODE_NONE     0
+#define EOS_CELL_UART_MODE_ATCMD    1
+#define EOS_CELL_UART_MODE_PPP      2
+#define EOS_CELL_UART_MODE_RELAY    3
+
+#define EOS_CELL_UART_SIZE_BUF      128
+
+void eos_cell_init(void);
+
+void eos_modem_init(void);
+void eos_modem_flush(void);
+size_t eos_modem_write(void *data, size_t size);
+size_t eos_modem_read(void *data, size_t size, uint32_t timeout);
+int eos_modem_readln(char *buf, size_t buf_size, uint32_t timeout);
+int eos_modem_resp(char *ok_str, char *err_str, uint32_t timeout);
+uint8_t eos_modem_get_mode(void);
+int eos_modem_set_mode(uint8_t mode);
+int eos_modem_take(uint32_t timeout);
+void eos_modem_give(void);
+void eos_modem_sleep(uint8_t mode);
+void eos_modem_wake(uint8_t source, uint8_t mode);
+
+void eos_ppp_set_apn(char *apn);
+void eos_ppp_set_auth(char *user, char *pass);
+int eos_ppp_connect(void);
+int eos_ppp_disconnect(void);
+
+void eos_cell_pcm_init(void);
+ssize_t eos_cell_pcm_read(unsigned char *data, size_t size);
+int eos_cell_pcm_push(unsigned char *data, size_t size);
+void eos_cell_pcm_start(void);
+void eos_cell_pcm_stop(void);
+
+void eos_cell_voice_handler(unsigned char mtype, unsigned char *buffer, uint16_t size);
+void eos_cell_sms_handler(unsigned char mtype, unsigned char *buffer, uint16_t size);
+void eos_cell_ussd_handler(unsigned char mtype, unsigned char *buffer, uint16_t size);
+void eos_cell_data_handler(unsigned char mtype, unsigned char *buffer, uint16_t size);
diff --git a/fw/esp32/components/eos/include/drv2605l.h b/fw/esp32/components/eos/include/drv2605l.h
new file mode 100644
index 0000000..de222e4
--- /dev/null
+++ b/fw/esp32/components/eos/include/drv2605l.h
@@ -0,0 +1,3 @@
+#include <stdint.h>
+
+void eos_drv2605l_test(void);
\ No newline at end of file
diff --git a/fw/esp32/components/eos/include/eos.h b/fw/esp32/components/eos/include/eos.h
new file mode 100644
index 0000000..0e660fb
--- /dev/null
+++ b/fw/esp32/components/eos/include/eos.h
@@ -0,0 +1,23 @@
+#define EOS_OK                      0
+#define EOS_ERR                     -1
+#define EOS_ERR_TIMEOUT             -2
+#define EOS_ERR_BUSY                -3
+
+#define EOS_ERR_FULL                -10
+#define EOS_ERR_EMPTY               -11
+#define EOS_ERR_NOTFOUND            -12
+
+#define EOS_TASK_PRIORITY_UART      1
+#define EOS_TASK_PRIORITY_MODEM     1
+#define EOS_TASK_PRIORITY_I2S       1
+#define EOS_TASK_PRIORITY_NET_XCHG  1
+#define EOS_TASK_PRIORITY_UDP_RCVR  1
+#define EOS_TASK_PRIORITY_PWR       1
+
+#define EOS_TASK_SSIZE_UART         4096
+#define EOS_TASK_SSIZE_MODEM        4096
+#define EOS_TASK_SSIZE_I2S          4096
+#define EOS_TASK_SSIZE_NET_XCHG     8192
+#define EOS_TASK_SSIZE_UDP_RCVR     4096
+#define EOS_TASK_SSIZE_PWR          4096
+
diff --git a/fw/esp32/components/eos/include/gsm.h b/fw/esp32/components/eos/include/gsm.h
new file mode 100644
index 0000000..2c4f7b4
--- /dev/null
+++ b/fw/esp32/components/eos/include/gsm.h
@@ -0,0 +1,117 @@
+#define GSM_OK                      0
+#define GSM_ERR                     -1
+
+/* Message-Type-Indicator */
+#define GSM_MTI                     0x03
+#define GSM_MTI_DELIVER             0x00
+#define GSM_MTI_DELIVER_REPORT      0x00
+#define GSM_MTI_SUBMIT              0x01
+#define GSM_MTI_SUBMIT_REPORT       0x01
+#define GSM_MTI_COMMAND             0x02
+#define GSM_MTI_COMMAND_REPORT      0x02
+
+#define GSM_MMS                     0x04    /* More-Messages-to-Send */
+#define GSM_RD                      0x04    /* Reject-Duplicates */
+#define GSM_LP                      0x08    /* Loop-Prevention */
+
+/* Validity-Period-Format */
+#define GSM_VPF                     0x18
+#define GSM_VPF_NONE                0x00
+#define GSM_VPF_ENHANCED            0x08
+#define GSM_VPF_RELATIVE            0x10
+#define GSM_VPF_ABSOLUTE            0x18
+
+#define GSM_SRI                     0x20    /* Status-Report-Indication */
+#define GSM_SRR                     0x20    /* Status-Report-Request */
+#define GSM_SRQ                     0x20    /* Status-Report-Qualifier */
+#define GSM_UDHI                    0x40    /* User-Data-Header-Indicator  */
+#define GSM_RP                      0x80    /* Reply-Path */
+
+/* Type-of-Number */
+#define GSM_TON                     0x70
+#define GSM_TON_UNKNOWN             0x00
+#define GSM_TON_INTERNATIONAL       0x10
+#define GSM_TON_NATIONAL            0x20
+#define GSM_TON_NETWORK             0x30
+#define GSM_TON_SUBSCRIBER          0x40
+#define GSM_TON_ALPHANUMERIC        0x50
+#define GSM_TON_ABBRREVIATED        0x60
+
+/* Numbering-Plan-Identification */
+#define GSM_NPI                     0x0f
+#define GSM_NPI_UNKNOWN             0x00
+#define GSM_NPI_TELEPHONE           0x01
+#define GSM_NPI_DATA                0x03
+#define GSM_NPI_TELEX               0x04
+#define GSM_NPI_SCS1                0x05
+#define GSM_NPI_SCS2                0x06
+#define GSM_NPI_NATIONAL            0x08
+#define GSM_NPI_PRIVATE             0x09
+#define GSM_NPI_ERMES               0x0a
+
+#define GSM_EXT                     0x80
+
+/* Protocol-Identifier */
+#define GSM_PID_DEFAULT             0
+#define GSM_PID_TYPE0               64
+
+/* Data-Coding-Scheme */
+#define GSM_DCS_CLASS               0x03
+#define GSM_DCS_ENC                 0x0c
+
+#define GSM_DCS_CLASS_IND           0x10
+#define GSM_DCS_COMPRESS_IND        0x20
+#define GSM_DCS_DELETE_IND          0x40
+#define GSM_DCS_GENERAL_IND         0x80
+#define GSM_DCS_GROUP               0xf0
+
+#define GSM_DCS_MWI_DISCARD         0xc0
+#define GSM_DCS_MWI_STORE_GSM7      0xd0
+#define GSM_DCS_MWI_STORE_UCS2      0xe0
+#define GSM_DCS_MWI_SENSE           0x08
+#define GSM_DCS_MWI_TYPE            0x03
+
+#define GSM_DCS_ENCLASS             0xf0
+#define GSM_DCS_ENCLASS_ENC         0x04
+
+/* Parameter-Indicator */
+#define GSM_PI_PID                  0x01
+#define GSM_PI_DCS                  0x02
+#define GSM_PI_UD                   0x04
+#define GSM_PI_EXT                  0x08
+
+/* character set */
+#define GSM_ENC_7BIT                0x00
+#define GSM_ENC_8BIT                0x04
+#define GSM_ENC_UCS2                0x08
+
+/* message waiting indication */
+#define GSM_MWI_TYPE_VOICEMAIL      0x00
+#define GSM_MWI_TYPE_FAX            0x01
+#define GSM_MWI_TYPE_EMAIL          0x02
+#define GSM_MWI_TYPE_OTHER          0x03
+
+/* flags */
+#define GSM_FLAG_COMPRESS           0x0001
+#define GSM_FLAG_DELETE             0x0002
+#define GSM_FLAG_DISCARD            0x0004
+
+/* message class */
+#define GSM_FLAG_CLASS              0x0400
+#define GSM_FLAG_CLASS0             0x0000  /* Flash */
+#define GSM_FLAG_CLASS1             0x0100  /* ME-specific */
+#define GSM_FLAG_CLASS2             0x0200  /* (U)SIM-specific */
+#define GSM_FLAG_CLASS4             0x0300  /* TE-specific */
+#define GSM_FLAG_CLASS_MASK         0x0f00
+
+/* message waiting indication */
+#define GSM_FLAG_MWI                0x4000
+#define GSM_FLAG_MWI_SENSE          0x8000
+#define GSM_FLAG_MWI_VOICEMAIL      0x0000
+#define GSM_FLAG_MWI_FAX            0x1000
+#define GSM_FLAG_MWI_EMAIL          0x2000
+#define GSM_FLAG_MWI_OTHER          0x3000
+#define GSM_FLAG_MWI_MASK           0xf000
+
+int gsm_7bit_enc(char *text, int text_len, char *pdu, int padb);
+int gsm_7bit_dec(char *pdu, char *text, int text_len, int padb);
diff --git a/fw/esp32/components/eos/include/i2c.h b/fw/esp32/components/eos/include/i2c.h
new file mode 100644
index 0000000..144f5e1
--- /dev/null
+++ b/fw/esp32/components/eos/include/i2c.h
@@ -0,0 +1,9 @@
+#include <sys/types.h>
+#include <stdint.h>
+
+void eos_i2c_init(void);
+
+int eos_i2c_read(uint8_t addr, uint8_t reg, uint8_t *data, size_t len);
+uint8_t eos_i2c_read8(uint8_t addr, uint8_t reg);
+int eos_i2c_write(uint8_t addr, uint8_t reg, uint8_t *data, size_t len);
+void eos_i2c_write8(uint8_t addr, uint8_t reg, uint8_t data);
diff --git a/fw/esp32/components/eos/include/msgq.h b/fw/esp32/components/eos/include/msgq.h
new file mode 100644
index 0000000..86bb067
--- /dev/null
+++ b/fw/esp32/components/eos/include/msgq.h
@@ -0,0 +1,32 @@
+#include <stdint.h>
+
+typedef struct EOSMsgItem {
+    unsigned char type;
+    unsigned char *buffer;
+    uint16_t len;
+    uint8_t flags;
+} EOSMsgItem;
+
+typedef struct EOSMsgQ {
+    uint8_t idx_r;
+    uint8_t idx_w;
+    uint8_t size;
+    EOSMsgItem *array;
+} EOSMsgQ;
+
+void eos_msgq_init(EOSMsgQ *msgq, EOSMsgItem *array, uint8_t size);
+int eos_msgq_push(EOSMsgQ *msgq, unsigned char type, unsigned char *buffer, uint16_t len, uint8_t flags);
+void eos_msgq_pop(EOSMsgQ *msgq, unsigned char *type, unsigned char **buffer, uint16_t *len, uint8_t *flags);
+uint8_t eos_msgq_len(EOSMsgQ *msgq);
+
+typedef struct EOSBufQ {
+    uint8_t idx_r;
+    uint8_t idx_w;
+    uint8_t size;
+    unsigned char **array;
+} EOSBufQ;
+
+void eos_bufq_init(EOSBufQ *bufq, unsigned char **array, uint8_t size);
+int eos_bufq_push(EOSBufQ *bufq, unsigned char *buffer);
+unsigned char *eos_bufq_pop(EOSBufQ *bufq);
+uint8_t eos_bufq_len(EOSBufQ *bufq);
diff --git a/fw/esp32/components/eos/include/net.h b/fw/esp32/components/eos/include/net.h
new file mode 100644
index 0000000..54bad6d
--- /dev/null
+++ b/fw/esp32/components/eos/include/net.h
@@ -0,0 +1,34 @@
+#include <stdint.h>
+
+/* common */
+#define EOS_NET_SIZE_BUF            1500
+
+#define EOS_NET_MTYPE_SOCK          1
+#define EOS_NET_MTYPE_POWER         4
+
+#define EOS_NET_MTYPE_WIFI          5
+#define EOS_NET_MTYPE_CELL          6
+#define EOS_NET_MTYPE_SIP           7
+#define EOS_NET_MTYPE_APP           8
+
+#define EOS_NET_MAX_MTYPE           8
+
+#define EOS_NET_MTYPE_FLAG_ONEW     0x80
+
+/* esp32 specific */
+#define EOS_NET_SIZE_BUFQ           4
+#define EOS_NET_SIZE_SNDQ           4
+
+#define EOS_NET_FLAG_BFREE          0x1
+#define EOS_NET_FLAG_BCOPY          0x2
+
+typedef void (*eos_net_fptr_t) (unsigned char, unsigned char *, uint16_t);
+
+void eos_net_init(void);
+
+unsigned char *eos_net_alloc(void);
+void eos_net_free(unsigned char *buf);
+int eos_net_send(unsigned char mtype, unsigned char *buffer, uint16_t len, uint8_t flags);
+void eos_net_set_handler(unsigned char mtype, eos_net_fptr_t handler);
+void eos_net_sleep_done(uint8_t mode);
+void eos_net_wake(uint8_t source, uint8_t mode);
diff --git a/fw/esp32/components/eos/include/power.h b/fw/esp32/components/eos/include/power.h
new file mode 100644
index 0000000..0a57b19
--- /dev/null
+++ b/fw/esp32/components/eos/include/power.h
@@ -0,0 +1,20 @@
+#include <stdint.h>
+
+#define EOS_PWR_MTYPE_BUTTON    0
+
+#define EOS_PWR_WAKE_RST        0
+#define EOS_PWR_WAKE_BTN        1
+#define EOS_PWR_WAKE_NET        2
+#define EOS_PWR_WAKE_MSG        3
+#define EOS_PWR_WAKE_UART       4
+
+#define EOS_PWR_SMODE_LIGHT     1
+#define EOS_PWR_SMODE_DEEP      2
+
+void eos_power_init(void);
+
+void eos_power_wait4init(void);
+uint8_t eos_power_wakeup_cause(void);
+void eos_power_sleep(void);
+void eos_power_wake(uint8_t source);
+void eos_power_net_ready(void);
\ No newline at end of file
diff --git a/fw/esp32/components/eos/include/sock.h b/fw/esp32/components/eos/include/sock.h
new file mode 100644
index 0000000..7e937cb
--- /dev/null
+++ b/fw/esp32/components/eos/include/sock.h
@@ -0,0 +1,18 @@
+#include <stdint.h>
+
+#define EOS_SOCK_MTYPE_PKT          0
+#define EOS_SOCK_MTYPE_OPEN_DGRAM   1
+#define EOS_SOCK_MTYPE_CLOSE        127
+
+#define EOS_SOCK_MAX_SOCK           2
+
+#define EOS_SOCK_SIZE_UDP_HDR       8
+
+#define EOS_IPv4_ADDR_SIZE          4
+
+typedef struct EOSNetAddr {
+    unsigned char host[EOS_IPv4_ADDR_SIZE];
+    uint16_t port;
+} EOSNetAddr;
+
+void eos_sock_init(void);
\ No newline at end of file
diff --git a/fw/esp32/components/eos/include/unicode.h b/fw/esp32/components/eos/include/unicode.h
new file mode 120000
index 0000000..e859a65
--- /dev/null
+++ b/fw/esp32/components/eos/include/unicode.h
@@ -0,0 +1 @@
+../../../../fe310/eos/unicode.h
\ No newline at end of file
diff --git a/fw/esp32/components/eos/include/wifi.h b/fw/esp32/components/eos/include/wifi.h
new file mode 100644
index 0000000..6009f7c
--- /dev/null
+++ b/fw/esp32/components/eos/include/wifi.h
@@ -0,0 +1,12 @@
+#define EOS_WIFI_MTYPE_SCAN         0
+#define EOS_WIFI_MTYPE_CONNECT      1
+#define EOS_WIFI_MTYPE_DISCONNECT   2
+
+#define EOS_WIFI_MAX_MTYPE          3
+
+void eos_wifi_init(void);
+
+int eos_wifi_scan(void);
+int eos_wifi_set_auth(char *ssid, char *pass);
+int eos_wifi_connect(void);
+int eos_wifi_disconnect(void);
diff --git a/fw/esp32/components/eos/msgq.c b/fw/esp32/components/eos/msgq.c
new file mode 100644
index 0000000..c704399
--- /dev/null
+++ b/fw/esp32/components/eos/msgq.c
@@ -0,0 +1,69 @@
+#include <stdlib.h>
+
+#include "eos.h"
+#include "msgq.h"
+
+#define IDX_MASK(IDX, SIZE)     ((IDX) & ((SIZE) - 1))
+
+void eos_msgq_init(EOSMsgQ *msgq, EOSMsgItem *array, uint8_t size) {
+    msgq->idx_r = 0;
+    msgq->idx_w = 0;
+    msgq->size = size;
+    msgq->array = array;
+}
+
+int eos_msgq_push(EOSMsgQ *msgq, unsigned char type, unsigned char *buffer, uint16_t len, uint8_t flags) {
+    if ((uint8_t)(msgq->idx_w - msgq->idx_r) == msgq->size) return EOS_ERR_FULL;
+
+    uint8_t idx = IDX_MASK(msgq->idx_w, msgq->size);
+    msgq->array[idx].type = type;
+    msgq->array[idx].buffer = buffer;
+    msgq->array[idx].len = len;
+    msgq->array[idx].flags = flags;
+    msgq->idx_w++;
+    return EOS_OK;
+}
+
+void eos_msgq_pop(EOSMsgQ *msgq, unsigned char *type, unsigned char **buffer, uint16_t *len, uint8_t *flags) {
+    if (msgq->idx_r == msgq->idx_w) {
+        *type = 0;
+        *buffer = NULL;
+        *len = 0;
+        if (flags) *flags = 0;
+    } else {
+        uint8_t idx = IDX_MASK(msgq->idx_r, msgq->size);
+        *type = msgq->array[idx].type;
+        *buffer = msgq->array[idx].buffer;
+        *len = msgq->array[idx].len;
+        if (flags) *flags = msgq->array[idx].flags;
+        msgq->idx_r++;
+    }
+}
+
+uint8_t eos_msgq_len(EOSMsgQ *msgq) {
+    return (uint8_t)(msgq->idx_w - msgq->idx_r);
+}
+
+void eos_bufq_init(EOSBufQ *bufq, unsigned char **array, uint8_t size) {
+    bufq->idx_r = 0;
+    bufq->idx_w = 0;
+    bufq->size = size;
+    bufq->array = array;
+}
+
+int eos_bufq_push(EOSBufQ *bufq, unsigned char *buffer) {
+    if ((uint8_t)(bufq->idx_w - bufq->idx_r) == bufq->size) return EOS_ERR_FULL;
+
+    bufq->array[IDX_MASK(bufq->idx_w++, bufq->size)] = buffer;
+    return EOS_OK;
+}
+
+unsigned char *eos_bufq_pop(EOSBufQ *bufq) {
+    if (bufq->idx_r == bufq->idx_w) return NULL;
+
+    return bufq->array[IDX_MASK(bufq->idx_r++, bufq->size)];
+}
+
+uint8_t eos_bufq_len(EOSBufQ *bufq) {
+    return (uint8_t)(bufq->idx_w - bufq->idx_r);
+}
diff --git a/fw/esp32/components/eos/net.c b/fw/esp32/components/eos/net.c
new file mode 100644
index 0000000..9a4a024
--- /dev/null
+++ b/fw/esp32/components/eos/net.c
@@ -0,0 +1,285 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
+#include <freertos/task.h>
+
+#include <esp_system.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_heap_caps.h>
+#include <driver/gpio.h>
+#include <driver/spi_slave.h>
+
+#include "eos.h"
+#include "msgq.h"
+#include "power.h"
+#include "net.h"
+
+#define SPI_GPIO_RTS        22
+#define SPI_GPIO_CTS        21
+#define SPI_GPIO_MOSI       23
+#define SPI_GPIO_MISO       19
+#define SPI_GPIO_SCLK       18
+#define SPI_GPIO_CS         5
+
+#define SPI_SIZE_BUF        (EOS_NET_SIZE_BUF + 8)
+
+static volatile char net_sleep = 0;
+
+static EOSBufQ net_buf_q;
+static unsigned char *net_bufq_array[EOS_NET_SIZE_BUFQ];
+
+static EOSMsgQ net_send_q;
+static EOSMsgItem net_sndq_array[EOS_NET_SIZE_SNDQ];
+
+static SemaphoreHandle_t mutex;
+static SemaphoreHandle_t semaph;
+static TaskHandle_t net_xchg_task_handle;
+static const char *TAG = "EOS NET";
+
+static eos_net_fptr_t mtype_handler[EOS_NET_MAX_MTYPE];
+
+static void bad_handler(unsigned char mtype, unsigned char *buffer, uint16_t len) {
+    ESP_LOGE(TAG, "bad handler: %d len: %d", mtype, len);
+}
+
+// Called after a transaction is queued and ready for pickup by master. We use this to set the handshake line high.
+static void _post_setup_cb(spi_slave_transaction_t *trans) {
+    gpio_set_level(SPI_GPIO_CTS, 1);
+}
+
+// Called after transaction is sent/received. We use this to set the handshake line low.
+static void _post_trans_cb(spi_slave_transaction_t *trans) {
+    gpio_set_level(SPI_GPIO_CTS, 0);
+}
+
+static void net_xchg_task(void *pvParameters) {
+    int repeat = 0;
+    int wake = 0;
+    unsigned char mtype = 0;
+    unsigned char *buffer;
+    uint16_t len;
+    uint8_t flags;
+    unsigned char *buf_send = heap_caps_malloc(SPI_SIZE_BUF, MALLOC_CAP_DMA);
+    unsigned char *buf_recv = heap_caps_malloc(SPI_SIZE_BUF, MALLOC_CAP_DMA);
+    esp_err_t ret;
+    spi_slave_transaction_t spi_tr;
+
+    //Configuration for the SPI bus
+    spi_bus_config_t spi_bus_cfg = {
+        .mosi_io_num = SPI_GPIO_MOSI,
+        .miso_io_num = SPI_GPIO_MISO,
+        .sclk_io_num = SPI_GPIO_SCLK
+    };
+
+    //Configuration for the SPI slave interface
+    spi_slave_interface_config_t spi_slave_cfg = {
+        .mode = 0,
+        .spics_io_num = SPI_GPIO_CS,
+        .queue_size = 2,
+        .flags = 0,
+        .post_setup_cb = _post_setup_cb,
+        .post_trans_cb = _post_trans_cb
+    };
+
+    //Initialize SPI slave interface
+    ret = spi_slave_initialize(VSPI_HOST, &spi_bus_cfg, &spi_slave_cfg, 1);
+    assert(ret == ESP_OK);
+
+    memset(&spi_tr, 0, sizeof(spi_tr));
+    spi_tr.length = SPI_SIZE_BUF * 8;
+    spi_tr.tx_buffer = buf_send;
+    spi_tr.rx_buffer = buf_recv;
+
+    if (eos_power_wakeup_cause()) {
+        wake = 1;
+        repeat = 1;
+    }
+
+    eos_power_wait4init();
+    while (1) {
+        if (!repeat) {
+            xSemaphoreTake(mutex, portMAX_DELAY);
+
+            eos_msgq_pop(&net_send_q, &mtype, &buffer, &len, &flags);
+            if (mtype) {
+                buf_send[0] = mtype;
+                buf_send[1] = len >> 8;
+                buf_send[2] = len & 0xFF;
+                if (buffer) {
+                    memcpy(buf_send + 3, buffer, len);
+                    if (flags & EOS_NET_FLAG_BFREE) {
+                        free(buffer);
+                    } else {
+                        eos_bufq_push(&net_buf_q, buffer);
+                        xSemaphoreGive(semaph);
+                    }
+                }
+            } else {
+                gpio_set_level(SPI_GPIO_RTS, 0);
+                buf_send[0] = 0;
+                buf_send[1] = 0;
+                buf_send[2] = 0;
+            }
+
+            xSemaphoreGive(mutex);
+        }
+        repeat = 0;
+
+        buf_recv[0] = 0;
+        buf_recv[1] = 0;
+        buf_recv[2] = 0;
+        spi_slave_transmit(VSPI_HOST, &spi_tr, portMAX_DELAY);
+        ESP_LOGD(TAG, "RECV:%d", buf_recv[0]);
+
+        if (wake) {
+            eos_power_net_ready();
+            wake = 0;
+        }
+        if (buf_recv[0] == 0x00) continue;
+        if (buf_recv[0] == 0xFF) {  // Sleep req
+            if (buf_send[0] == 0) {
+                int abort = 0;
+
+                xSemaphoreTake(mutex, portMAX_DELAY);
+                net_sleep = 1;
+                if (eos_msgq_len(&net_send_q)) abort = 1;
+                xSemaphoreGive(mutex);
+
+                spi_slave_free(VSPI_HOST);
+
+                eos_power_sleep();
+                if (abort) eos_power_wake(EOS_PWR_WAKE_MSG);
+
+                vTaskSuspend(NULL);
+
+                xSemaphoreTake(mutex, portMAX_DELAY);
+                net_sleep = 0;
+                xSemaphoreGive(mutex);
+
+                spi_slave_initialize(VSPI_HOST, &spi_bus_cfg, &spi_slave_cfg, 1);
+                wake = 1;
+                repeat = 1;
+            }
+            continue;
+        }
+        mtype = buf_recv[0];
+        len   = (uint16_t)buf_recv[1] << 8;
+        len  |= (uint16_t)buf_recv[2] & 0xFF;
+        buffer = buf_recv + 3;
+        if (mtype & EOS_NET_MTYPE_FLAG_ONEW) {
+            mtype &= ~EOS_NET_MTYPE_FLAG_ONEW;
+            if (buf_send[0]) repeat = 1;
+        }
+        if (mtype <= EOS_NET_MAX_MTYPE) {
+            mtype_handler[mtype-1](mtype, buffer, len);
+        } else {
+            bad_handler(mtype, buffer, len);
+        }
+    }
+    vTaskDelete(NULL);
+}
+
+void eos_net_init(void) {
+    int i;
+
+    // Configuration for the handshake lines
+    gpio_config_t io_conf;
+
+    io_conf.intr_type = GPIO_INTR_DISABLE;
+    io_conf.mode = GPIO_MODE_OUTPUT;
+    io_conf.pull_up_en = 0;
+    io_conf.pull_down_en = 0;
+    io_conf.pin_bit_mask = ((uint64_t)1 << SPI_GPIO_CTS);
+    gpio_config(&io_conf);
+    gpio_set_level(SPI_GPIO_CTS, 0);
+
+    io_conf.intr_type = GPIO_INTR_DISABLE;
+    io_conf.mode = GPIO_MODE_OUTPUT;
+    io_conf.pull_up_en = 0;
+    io_conf.pull_down_en = 0;
+    io_conf.pin_bit_mask = ((uint64_t)1 << SPI_GPIO_RTS);
+    gpio_config(&io_conf);
+    gpio_set_level(SPI_GPIO_RTS, 0);
+
+    eos_msgq_init(&net_send_q, net_sndq_array, EOS_NET_SIZE_SNDQ);
+    eos_bufq_init(&net_buf_q, net_bufq_array, EOS_NET_SIZE_BUFQ);
+    for (i=0; i<EOS_NET_SIZE_BUFQ; i++) {
+        eos_bufq_push(&net_buf_q, malloc(EOS_NET_SIZE_BUF));
+    }
+
+    for (i=0; i<EOS_NET_MAX_MTYPE; i++) {
+        mtype_handler[i] = bad_handler;
+    }
+
+    semaph = xSemaphoreCreateCounting(EOS_NET_SIZE_BUFQ, EOS_NET_SIZE_BUFQ);
+    mutex = xSemaphoreCreateBinary();
+    xSemaphoreGive(mutex);
+    xTaskCreate(&net_xchg_task, "net_xchg", EOS_TASK_SSIZE_NET_XCHG, NULL, EOS_TASK_PRIORITY_NET_XCHG, &net_xchg_task_handle);
+    ESP_LOGI(TAG, "INIT");
+}
+
+unsigned char *eos_net_alloc(void) {
+    unsigned char *ret;
+
+    xSemaphoreTake(semaph, portMAX_DELAY);
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    ret = eos_bufq_pop(&net_buf_q);
+    xSemaphoreGive(mutex);
+
+    return ret;
+}
+
+void eos_net_free(unsigned char *buf) {
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    eos_bufq_push(&net_buf_q, buf);
+    xSemaphoreGive(semaph);
+    xSemaphoreGive(mutex);
+}
+
+int eos_net_send(unsigned char mtype, unsigned char *buffer, uint16_t len, uint8_t flags) {
+    int rv, sleep;
+
+    if (flags & EOS_NET_FLAG_BCOPY) xSemaphoreTake(semaph, portMAX_DELAY);
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    sleep = net_sleep;
+    gpio_set_level(SPI_GPIO_RTS, 1);
+    if (flags & EOS_NET_FLAG_BCOPY) {
+        unsigned char *b = eos_bufq_pop(&net_buf_q);
+        memcpy(b, buffer, len);
+        buffer = b;
+    }
+    rv = eos_msgq_push(&net_send_q, mtype, buffer, len, flags);
+    xSemaphoreGive(mutex);
+
+    if (sleep) eos_power_wake(EOS_PWR_WAKE_MSG);
+
+    return rv;
+}
+
+void eos_net_set_handler(unsigned char mtype, eos_net_fptr_t handler) {
+    mtype_handler[mtype-1] = handler;
+}
+
+void eos_net_sleep_done(uint8_t mode) {
+    gpio_set_level(SPI_GPIO_CTS, 1);
+    vTaskDelay(200 / portTICK_PERIOD_MS);
+    gpio_set_level(SPI_GPIO_CTS, 0);
+}
+
+void eos_net_wake(uint8_t source, uint8_t mode) {
+    int sleep;
+
+    if (mode == EOS_PWR_SMODE_DEEP) return;
+    do {
+        vTaskResume(net_xchg_task_handle);
+        vTaskDelay(10 / portTICK_PERIOD_MS);
+
+        xSemaphoreTake(mutex, portMAX_DELAY);
+        sleep = net_sleep;
+        xSemaphoreGive(mutex);
+    } while (sleep);
+}
diff --git a/fw/esp32/components/eos/power.c b/fw/esp32/components/eos/power.c
new file mode 100644
index 0000000..f07e67b
--- /dev/null
+++ b/fw/esp32/components/eos/power.c
@@ -0,0 +1,325 @@
+#include <stdlib.h>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/queue.h>
+#include <driver/gpio.h>
+#include <esp_sleep.h>
+#include <esp_pm.h>
+#include <esp_log.h>
+
+#include "eos.h"
+#include "net.h"
+#include "cell.h"
+#include "power.h"
+
+#define POWER_GPIO_BTN      0
+#define POWER_GPIO_NET      5
+#define POWER_GPIO_UART     35
+
+#define POWER_ETYPE_BTN     1
+#define POWER_ETYPE_SLEEP   2
+#define POWER_ETYPE_WAKE    3
+#define POWER_ETYPE_NETRDY  4
+
+typedef struct {
+    uint8_t type;
+    union {
+        uint8_t source;
+        uint8_t level;
+    };
+} power_event_t;
+
+static esp_pm_lock_handle_t power_lock_cpu_freq;
+static esp_pm_lock_handle_t power_lock_apb_freq;
+static esp_pm_lock_handle_t power_lock_no_sleep;
+
+static const char *TAG = "EOS POWER";
+
+static QueueHandle_t power_queue;
+
+static volatile int init_done = 0;
+
+static void IRAM_ATTR btn_handler(void *arg) {
+    power_event_t evt;
+
+    evt.type = POWER_ETYPE_BTN;
+    evt.level = gpio_get_level(POWER_GPIO_BTN);
+    xQueueSendFromISR(power_queue, &evt, NULL);
+}
+
+static void IRAM_ATTR btn_wake_handler(void *arg) {
+    power_event_t evt;
+
+    gpio_intr_disable(POWER_GPIO_BTN);
+
+    evt.type = POWER_ETYPE_WAKE;
+    evt.source = EOS_PWR_WAKE_BTN;
+    xQueueSendFromISR(power_queue, &evt, NULL);
+
+}
+
+static void IRAM_ATTR net_wake_handler(void *arg) {
+    power_event_t evt;
+
+    gpio_intr_disable(POWER_GPIO_NET);
+
+    evt.type = POWER_ETYPE_WAKE;
+    evt.source = EOS_PWR_WAKE_NET;
+    xQueueSendFromISR(power_queue, &evt, NULL);
+}
+
+static void IRAM_ATTR uart_wake_handler(void *arg) {
+    power_event_t evt;
+
+    gpio_intr_disable(POWER_GPIO_UART);
+
+    evt.type = POWER_ETYPE_WAKE;
+    evt.source = EOS_PWR_WAKE_UART;
+    xQueueSendFromISR(power_queue, &evt, NULL);
+}
+
+void power_sleep(uint8_t mode) {
+    gpio_config_t io_conf;
+
+    eos_modem_sleep(mode);
+    eos_net_sleep_done(mode);
+
+    switch (mode) {
+        case EOS_PWR_SMODE_LIGHT:
+            io_conf.intr_type = GPIO_INTR_DISABLE;
+            io_conf.mode = GPIO_MODE_INPUT;
+            io_conf.pin_bit_mask = ((uint64_t)1 << POWER_GPIO_NET);
+            io_conf.pull_up_en = 0;
+            io_conf.pull_down_en = 0;
+            gpio_config(&io_conf);
+
+            gpio_isr_handler_add(POWER_GPIO_BTN, btn_wake_handler, NULL);
+            gpio_isr_handler_add(POWER_GPIO_NET, net_wake_handler, NULL);
+            gpio_isr_handler_add(POWER_GPIO_UART, uart_wake_handler, NULL);
+
+            esp_sleep_enable_gpio_wakeup();
+            gpio_wakeup_enable(POWER_GPIO_BTN, GPIO_INTR_LOW_LEVEL);
+            gpio_wakeup_enable(POWER_GPIO_NET, GPIO_INTR_LOW_LEVEL);
+            gpio_wakeup_enable(POWER_GPIO_UART, GPIO_INTR_LOW_LEVEL);
+
+            ESP_LOGD(TAG, "SLEEP");
+
+            esp_pm_lock_release(power_lock_apb_freq);
+            esp_pm_lock_release(power_lock_no_sleep);
+
+            break;
+
+        case EOS_PWR_SMODE_DEEP:
+            gpio_deep_sleep_hold_en();
+            esp_sleep_enable_ext0_wakeup(POWER_GPIO_BTN, 0);
+            esp_sleep_enable_ext1_wakeup((uint64_t)1 << POWER_GPIO_UART, ESP_EXT1_WAKEUP_ALL_LOW);
+
+            ESP_LOGD(TAG, "SLEEP");
+
+            esp_deep_sleep_start();
+            break;
+
+        default:
+            break;
+    }
+}
+
+void power_wake_stage1(uint8_t source, uint8_t mode) {
+    gpio_config_t io_conf;
+
+    if (mode == EOS_PWR_SMODE_LIGHT) {
+        esp_pm_lock_acquire(power_lock_apb_freq);
+        esp_pm_lock_acquire(power_lock_no_sleep);
+
+        gpio_wakeup_disable(POWER_GPIO_BTN);
+        gpio_wakeup_disable(POWER_GPIO_NET);
+        gpio_wakeup_disable(POWER_GPIO_UART);
+
+        gpio_isr_handler_remove(POWER_GPIO_NET);
+        io_conf.intr_type = GPIO_INTR_DISABLE;
+        io_conf.mode = GPIO_MODE_DISABLE;
+        io_conf.pin_bit_mask = ((uint64_t)1 << POWER_GPIO_NET);
+        io_conf.pull_up_en = 0;
+        io_conf.pull_down_en = 0;
+        gpio_config(&io_conf);
+    }
+
+    gpio_intr_disable(POWER_GPIO_BTN);
+    if ((source != EOS_PWR_WAKE_BTN) && (source != EOS_PWR_WAKE_NET)) {
+        gpio_set_direction(POWER_GPIO_BTN, GPIO_MODE_OUTPUT);
+        gpio_set_level(POWER_GPIO_BTN, 0);
+        vTaskDelay(200 / portTICK_PERIOD_MS);
+        gpio_set_direction(POWER_GPIO_BTN, GPIO_MODE_INPUT);
+    }
+
+    eos_net_wake(source, mode);
+}
+
+void power_wake_stage2(uint8_t source, uint8_t mode) {
+    eos_modem_wake(source, mode);
+
+    gpio_set_intr_type(POWER_GPIO_BTN, GPIO_INTR_ANYEDGE);
+    gpio_isr_handler_add(POWER_GPIO_BTN, btn_handler, NULL);
+
+    ESP_LOGD(TAG, "WAKE");
+}
+
+static void power_event_task(void *pvParameters) {
+    unsigned char *buf;
+    power_event_t evt;
+    uint8_t source;
+    uint8_t wakeup_cause;
+    uint8_t mode;
+    int sleep;
+
+    source = 0;
+    wakeup_cause = eos_power_wakeup_cause();
+    if (wakeup_cause) {
+        mode = EOS_PWR_SMODE_DEEP;
+        sleep = 1;
+    } else {
+        mode = EOS_PWR_SMODE_LIGHT;
+        sleep = 0;
+    }
+
+    while (1) {
+        if (xQueueReceive(power_queue, &evt, portMAX_DELAY)) {
+            switch (evt.type) {
+                case POWER_ETYPE_SLEEP:
+                    if (!sleep) {
+                        mode = EOS_PWR_SMODE_DEEP;
+                        power_sleep(mode);
+                        sleep = 1;
+                    }
+                    break;
+
+                case POWER_ETYPE_WAKE:
+                    if (sleep) {
+                        source = evt.source;
+                        power_wake_stage1(source, mode);
+                    }
+                    break;
+
+                case POWER_ETYPE_NETRDY:
+                    if (sleep && source) {
+                        power_wake_stage2(source, mode);
+                        sleep = 0;
+                        source = 0;
+                    }
+                    break;
+
+                case POWER_ETYPE_BTN:
+                    buf = eos_net_alloc();
+                    buf[0] = EOS_PWR_MTYPE_BUTTON;
+                    buf[1] = evt.level;
+                    eos_net_send(EOS_NET_MTYPE_POWER, buf, 2, 0);
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    }
+    vTaskDelete(NULL);
+}
+
+void eos_power_init(void) {
+    esp_err_t ret;
+    gpio_config_t io_conf;
+    esp_pm_config_esp32_t pwr_conf;
+    uint8_t wakeup_cause;
+
+    io_conf.intr_type = GPIO_INTR_ANYEDGE;
+    io_conf.mode = GPIO_MODE_INPUT;
+    io_conf.pin_bit_mask = ((uint64_t)1 << POWER_GPIO_BTN);
+    io_conf.pull_up_en = 1;
+    io_conf.pull_down_en = 0;
+    gpio_config(&io_conf);
+    gpio_isr_handler_add(POWER_GPIO_BTN, btn_handler, NULL);
+
+    /*
+    ret = esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
+    assert(ret == ESP_OK);
+    ret = esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_ON);
+    assert(ret == ESP_OK);
+    ret = esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON);
+    assert(ret == ESP_OK);
+    */
+
+    ret = esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, NULL, &power_lock_cpu_freq);
+    assert(ret == ESP_OK);
+    ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, NULL, &power_lock_apb_freq);
+    assert(ret == ESP_OK);
+    ret = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, NULL, &power_lock_no_sleep);
+    assert(ret == ESP_OK);
+
+    ret = esp_pm_lock_acquire(power_lock_cpu_freq);
+    assert(ret == ESP_OK);
+    ret = esp_pm_lock_acquire(power_lock_apb_freq);
+    assert(ret == ESP_OK);
+    ret = esp_pm_lock_acquire(power_lock_no_sleep);
+    assert(ret == ESP_OK);
+
+    pwr_conf.max_freq_mhz = 160;
+    pwr_conf.min_freq_mhz = 80;
+    pwr_conf.light_sleep_enable = 1;
+
+    ret = esp_pm_configure(&pwr_conf);
+    assert(ret == ESP_OK);
+
+    power_queue = xQueueCreate(4, sizeof(power_event_t));
+    xTaskCreate(power_event_task, "power_event", EOS_TASK_SSIZE_PWR, NULL, EOS_TASK_PRIORITY_PWR, NULL);
+
+    wakeup_cause = eos_power_wakeup_cause();
+    if (wakeup_cause) eos_power_wake(wakeup_cause);
+
+    init_done = 1;
+    ESP_LOGI(TAG, "INIT");
+}
+
+void eos_power_wait4init(void) {
+    while (!init_done);
+}
+
+uint8_t eos_power_wakeup_cause(void) {
+    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
+
+    switch (cause) {
+        case ESP_SLEEP_WAKEUP_EXT0:
+            return EOS_PWR_WAKE_BTN;
+
+        case ESP_SLEEP_WAKEUP_EXT1:
+            return EOS_PWR_WAKE_UART;
+
+        default:
+        case ESP_SLEEP_WAKEUP_UNDEFINED:
+            return EOS_PWR_WAKE_RST;
+    }
+}
+
+void eos_power_sleep(void) {
+    power_event_t evt;
+
+    evt.type = POWER_ETYPE_SLEEP;
+    evt.source = 0;
+    xQueueSend(power_queue, &evt, portMAX_DELAY);
+}
+
+void eos_power_wake(uint8_t source) {
+    power_event_t evt;
+
+    evt.type = POWER_ETYPE_WAKE;
+    evt.source = source;
+
+    xQueueSend(power_queue, &evt, portMAX_DELAY);
+}
+
+void eos_power_net_ready(void) {
+    power_event_t evt;
+
+    evt.type = POWER_ETYPE_NETRDY;
+    evt.source = 0;
+
+    xQueueSend(power_queue, &evt, portMAX_DELAY);
+}
diff --git a/fw/esp32/components/eos/sock.c b/fw/esp32/components/eos/sock.c
new file mode 100644
index 0000000..17357e4
--- /dev/null
+++ b/fw/esp32/components/eos/sock.c
@@ -0,0 +1,163 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
+#include <freertos/task.h>
+
+#include <esp_system.h>
+#include <esp_event.h>
+#include <esp_event_loop.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_wifi.h>
+#include <nvs_flash.h>
+
+#include <lwip/sockets.h>
+#include <lwip/err.h>
+#include <lwip/sockets.h>
+#include <lwip/sys.h>
+#include <lwip/netdb.h>
+#include <lwip/dns.h>
+
+#include "eos.h"
+#include "net.h"
+#include "sock.h"
+
+static const char *TAG = "EOS SOCK";
+static SemaphoreHandle_t mutex;
+static int _socks[EOS_SOCK_MAX_SOCK];
+
+static int t_open_dgram(void) {
+    struct sockaddr_in _myaddr;
+    int sock;
+
+    sock = socket(PF_INET, SOCK_DGRAM, 0);
+    if (sock < 0) return sock;
+
+    memset((char *)&_myaddr, 0, sizeof(_myaddr));
+    _myaddr.sin_family = AF_INET;
+    _myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
+    _myaddr.sin_port = htons(3000);
+
+    int rv = bind(sock, (struct sockaddr *)&_myaddr, sizeof(_myaddr));
+    if (rv < 0) {
+        close(sock);
+        return rv;
+    }
+    return sock;
+}
+
+static void t_close(int sock) {
+    close(sock);
+}
+
+static ssize_t t_sendto(int sock, void *msg, size_t msg_size, EOSNetAddr *addr) {
+    struct sockaddr_in servaddr;
+
+    memset((void *)&servaddr, 0, sizeof(servaddr));
+    servaddr.sin_family = AF_INET;
+    servaddr.sin_port = addr->port;
+    memcpy((void *)&servaddr.sin_addr, addr->host, sizeof(addr->host));
+    return sendto(sock, msg, msg_size, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
+}
+
+static ssize_t t_recvfrom(int sock, void *msg, size_t msg_size, EOSNetAddr *addr) {
+    struct sockaddr_in servaddr;
+    socklen_t addrlen = sizeof(servaddr);
+    memset((void *)&servaddr, 0, sizeof(servaddr));
+
+    ssize_t recvlen = recvfrom(sock, msg, msg_size, 0, (struct sockaddr *)&servaddr, &addrlen);
+    if (recvlen < 0) return recvlen;
+
+    if (addr) {
+        addr->port = servaddr.sin_port;
+        memcpy(addr->host, (void *)&servaddr.sin_addr, sizeof(addr->host));
+    }
+    return recvlen;
+}
+
+static void udp_rcvr_task(void *pvParameters) {
+    EOSNetAddr addr;
+    uint8_t esock = (uint8_t)pvParameters;
+    int sock = _socks[esock-1];
+    unsigned char *buf;
+
+    do {
+        ssize_t rv;
+
+        buf = eos_net_alloc();
+        rv = t_recvfrom(sock, buf+EOS_SOCK_SIZE_UDP_HDR, EOS_NET_SIZE_BUF-EOS_SOCK_SIZE_UDP_HDR, &addr);
+        if (rv < 0) {
+            sock = 0;
+            ESP_LOGE(TAG, "UDP RECV ERR:%d", rv);
+            continue;
+        }
+        buf[0] = EOS_SOCK_MTYPE_PKT;
+        buf[1] = esock;
+        memcpy(buf+2, addr.host, sizeof(addr.host));
+        memcpy(buf+2+sizeof(addr.host), &addr.port, sizeof(addr.port));
+        eos_net_send(EOS_NET_MTYPE_SOCK, buf, rv+EOS_SOCK_SIZE_UDP_HDR, 0);
+    } while(sock);
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    _socks[esock-1] = 0;
+    xSemaphoreGive(mutex);
+    vTaskDelete(NULL);
+}
+
+static void sock_handler(unsigned char type, unsigned char *buffer, uint16_t size) {
+    EOSNetAddr addr;
+    uint8_t esock;
+    int sock, i;
+    unsigned char *rbuf;
+
+    if (size < 1) return;
+
+    switch(buffer[0]) {
+        case EOS_SOCK_MTYPE_PKT:
+            if (size < EOS_SOCK_SIZE_UDP_HDR) return;
+            sock = _socks[buffer[1]-1];
+            memcpy(addr.host, buffer+2, sizeof(addr.host));
+            memcpy(&addr.port, buffer+2+sizeof(addr.host), sizeof(addr.port));
+            t_sendto(sock, buffer+EOS_SOCK_SIZE_UDP_HDR, size-EOS_SOCK_SIZE_UDP_HDR, &addr);
+            break;
+
+        case EOS_SOCK_MTYPE_OPEN_DGRAM:
+            sock = t_open_dgram();
+            esock = 0;
+            if (sock > 0) {
+                xSemaphoreTake(mutex, portMAX_DELAY);
+                for (i=0; i<EOS_SOCK_MAX_SOCK; i++) {
+                    if (_socks[i] == 0) {
+                        esock = i+1;
+                        _socks[i] = sock;
+                    }
+                }
+                xSemaphoreGive(mutex);
+            }
+            // xTaskCreatePinnedToCore(&sock_receiver, "sock_receiver", EOS_TASK_SSIZE_UDP_RCVR, (void *)esock, EOS_TASK_PRIORITY_UDP_RCVR, NULL, 1);
+            xTaskCreate(&udp_rcvr_task, "udp_rcvr", EOS_TASK_SSIZE_UDP_RCVR, (void *)esock, EOS_TASK_PRIORITY_UDP_RCVR, NULL);
+            rbuf = eos_net_alloc();
+            rbuf[0] = EOS_SOCK_MTYPE_OPEN_DGRAM;
+            rbuf[1] = esock;
+            eos_net_send(EOS_NET_MTYPE_SOCK, rbuf, 2, 0);
+            break;
+
+        case EOS_SOCK_MTYPE_CLOSE:
+            if (size < 2) return;
+            sock = _socks[buffer[1]-1];
+            t_close(sock);
+            break;
+
+        default:
+            break;
+    }
+}
+
+void eos_sock_init(void) {
+    mutex = xSemaphoreCreateBinary();
+    xSemaphoreGive(mutex);
+    eos_net_set_handler(EOS_NET_MTYPE_SOCK, sock_handler);
+    ESP_LOGI(TAG, "INIT");
+}
\ No newline at end of file
diff --git a/fw/esp32/components/eos/unicode.c b/fw/esp32/components/eos/unicode.c
new file mode 120000
index 0000000..4cdffa7
--- /dev/null
+++ b/fw/esp32/components/eos/unicode.c
@@ -0,0 +1 @@
+../../../fe310/eos/unicode.c
\ No newline at end of file
diff --git a/fw/esp32/components/eos/wifi.c b/fw/esp32/components/eos/wifi.c
new file mode 100755
index 0000000..3dd90ba
--- /dev/null
+++ b/fw/esp32/components/eos/wifi.c
@@ -0,0 +1,302 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+
+#include <esp_system.h>
+#include <esp_event.h>
+#include <esp_event_loop.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_wifi.h>
+
+#include "eos.h"
+#include "net.h"
+#include "wifi.h"
+
+// XXX: WiFi fail due to no DHCP server
+
+#define WIFI_MAX_SCAN_RECORDS       20
+#define WIFI_MAX_CONNECT_ATTEMPTS   3
+
+#define WIFI_STATE_STOPPED          0
+#define WIFI_STATE_SCANNING         1
+#define WIFI_STATE_CONNECTING       2
+#define WIFI_STATE_CONNECTED        3
+#define WIFI_STATE_DISCONNECTING    4
+#define WIFI_STATE_DISCONNECTED     5
+
+#define WIFI_ACTION_NONE            0
+#define WIFI_ACTION_SCAN            1
+#define WIFI_ACTION_CONNECT         2
+#define WIFI_ACTION_DISCONNECT      3
+
+static const char *TAG = "EOS WIFI";
+
+static SemaphoreHandle_t mutex;
+
+static wifi_config_t wifi_sta_config;
+static wifi_scan_config_t wifi_scan_config;
+
+static int wifi_connect_cnt = 0;
+static uint8_t wifi_action;
+static uint8_t wifi_state;
+
+static esp_err_t wifi_event_handler(void *ctx, system_event_t *event) {
+    esp_err_t ret = ESP_OK;
+    char _disconnect;
+    uint8_t _action, _state;
+    unsigned char *rbuf;
+    wifi_ap_record_t scan_r[WIFI_MAX_SCAN_RECORDS];
+    uint16_t scan_n = WIFI_MAX_SCAN_RECORDS;
+
+    switch(event->event_id) {
+        case WIFI_EVENT_SCAN_DONE:
+            memset(scan_r, 0, sizeof(scan_r));
+            esp_wifi_scan_get_ap_records(&scan_n, scan_r);
+
+            xSemaphoreTake(mutex, portMAX_DELAY);
+            _state = wifi_state;
+            if (wifi_state == WIFI_STATE_CONNECTED) wifi_action = WIFI_ACTION_NONE;
+            xSemaphoreGive(mutex);
+
+            if (_state != WIFI_STATE_CONNECTED) ret = esp_wifi_stop();
+            break;
+
+        case WIFI_EVENT_STA_START:
+            xSemaphoreTake(mutex, portMAX_DELAY);
+            _action = wifi_action;
+            xSemaphoreGive(mutex);
+
+            switch (_action) {
+                case WIFI_ACTION_SCAN:
+                    ret = esp_wifi_scan_start(&wifi_scan_config, 0);
+                    break;
+
+                case WIFI_ACTION_CONNECT:
+                    ret = esp_wifi_connect();
+                    break;
+
+                default:
+                    break;
+            }
+            break;
+
+        case WIFI_EVENT_STA_STOP:
+            xSemaphoreTake(mutex, portMAX_DELAY);
+            wifi_state = WIFI_STATE_STOPPED;
+            wifi_action = WIFI_ACTION_NONE;
+            xSemaphoreGive(mutex);
+            break;
+
+        case WIFI_EVENT_STA_CONNECTED:
+            xSemaphoreTake(mutex, portMAX_DELAY);
+            wifi_state = WIFI_STATE_CONNECTED;
+            wifi_action = WIFI_ACTION_NONE;
+            wifi_connect_cnt = WIFI_MAX_CONNECT_ATTEMPTS;
+            xSemaphoreGive(mutex);
+            break;
+
+        case WIFI_EVENT_STA_DISCONNECTED:
+            xSemaphoreTake(mutex, portMAX_DELAY);
+            if (wifi_connect_cnt) wifi_connect_cnt--;
+            _action = wifi_action;
+            _disconnect = (wifi_connect_cnt == 0);
+            if (_disconnect) wifi_state = WIFI_STATE_DISCONNECTED;
+            xSemaphoreGive(mutex);
+
+            if (_disconnect) {
+                rbuf = eos_net_alloc();
+                if (_action == WIFI_ACTION_CONNECT) {
+                    rbuf[0] = EOS_WIFI_MTYPE_CONNECT;
+                    rbuf[1] = EOS_ERR;
+                    eos_net_send(EOS_NET_MTYPE_WIFI, rbuf, 2, 0);
+                } else {
+                    rbuf[0] = EOS_WIFI_MTYPE_DISCONNECT;
+                    eos_net_send(EOS_NET_MTYPE_WIFI, rbuf, 1, 0);
+                }
+                if (!_action) ret = esp_wifi_stop();
+            } else {
+                ret = esp_wifi_connect();
+            }
+            break;
+
+        case IP_EVENT_STA_GOT_IP:
+            ESP_LOGD(TAG, "IP address: " IPSTR, IP2STR(&event->event_info.got_ip.ip_info.ip));
+            if (event->event_info.got_ip.ip_changed) {
+                // send wifi reconnect
+            } else {
+                rbuf = eos_net_alloc();
+                rbuf[0] = EOS_WIFI_MTYPE_CONNECT;
+                rbuf[1] = EOS_OK;
+                eos_net_send(EOS_NET_MTYPE_WIFI, rbuf, 2, 0);
+            }
+            break;
+
+        default: // Ignore the other event types
+            break;
+    }
+    if (ret != ESP_OK) ESP_LOGD(TAG, "ESP WIFI ERR: %d", ret);
+
+    return ESP_OK;
+}
+
+static void wifi_handler(unsigned char _mtype, unsigned char *buffer, uint16_t size) {
+    int rv = EOS_OK;
+    uint8_t mtype = buffer[0];
+    char *ssid, *pass;
+
+    switch (mtype) {
+        case EOS_WIFI_MTYPE_SCAN:
+            rv = eos_wifi_scan();
+            break;
+
+        case EOS_WIFI_MTYPE_CONNECT:
+            ssid = (char *)buffer+1;
+            pass = ssid+strlen(ssid)+1;
+            rv = eos_wifi_set_auth(ssid, pass);
+            if (!rv) rv = eos_wifi_connect();
+            break;
+
+        case EOS_WIFI_MTYPE_DISCONNECT:
+            rv = eos_wifi_disconnect();
+            break;
+    }
+    if (rv) ESP_LOGD(TAG, "WIFI HANDLER ERR: %d", rv);
+}
+
+void eos_wifi_init(void) {
+    esp_err_t ret;
+    wifi_init_config_t wifi_config = WIFI_INIT_CONFIG_DEFAULT();
+
+    memset(&wifi_sta_config, 0, sizeof(wifi_sta_config));
+
+    ret = esp_event_loop_init(wifi_event_handler, NULL);
+    assert(ret == ESP_OK);
+
+    ret = esp_wifi_init(&wifi_config);
+    assert(ret == ESP_OK);
+
+    ret = esp_wifi_set_storage(WIFI_STORAGE_RAM);
+    assert(ret == ESP_OK);
+
+    ret = esp_wifi_set_mode(WIFI_MODE_STA);
+    assert(ret == ESP_OK);
+
+    ret = esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_sta_config);
+    assert(ret == ESP_OK);
+
+    ret = esp_wifi_stop();
+    assert(ret == ESP_OK);
+
+    mutex = xSemaphoreCreateBinary();
+    xSemaphoreGive(mutex);
+
+    eos_net_set_handler(EOS_NET_MTYPE_WIFI, wifi_handler);
+    ESP_LOGI(TAG, "INIT");
+}
+
+int eos_wifi_scan(void) {
+    int rv = EOS_OK;
+    esp_err_t ret = ESP_OK;
+    uint8_t _wifi_state = 0;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    if (!wifi_action) {
+        _wifi_state = wifi_state;
+
+        wifi_action = WIFI_ACTION_SCAN;
+        if (wifi_state == WIFI_STATE_STOPPED) wifi_state = WIFI_STATE_SCANNING;
+
+        memset(&wifi_scan_config, 0, sizeof(wifi_scan_config));
+    } else {
+        rv = EOS_ERR_BUSY;
+    }
+    xSemaphoreGive(mutex);
+
+    if (rv) return rv;
+
+    if (_wifi_state == WIFI_STATE_STOPPED) {
+        ret = esp_wifi_start();
+    } else {
+        ret = esp_wifi_scan_start(&wifi_scan_config, 0);
+    }
+    if (ret != ESP_OK) rv = EOS_ERR;
+
+    return rv;
+}
+
+int eos_wifi_set_auth(char *ssid, char *pass) {
+    int rv = EOS_OK;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    if (!wifi_action) {
+        if (ssid) strncpy((char *)wifi_sta_config.sta.ssid, ssid, sizeof(wifi_sta_config.sta.ssid) - 1);
+        if (pass) strncpy((char *)wifi_sta_config.sta.password, pass, sizeof(wifi_sta_config.sta.password) - 1);
+    } else {
+        rv = EOS_ERR_BUSY;
+
+    }
+    xSemaphoreGive(mutex);
+
+    return rv;
+}
+
+int eos_wifi_connect(void) {
+    int rv = EOS_OK;
+    esp_err_t ret = ESP_OK;
+    uint8_t _wifi_state = 0;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    if (!wifi_action) {
+        _wifi_state = wifi_state;
+
+        wifi_action = WIFI_ACTION_CONNECT;
+        wifi_state = WIFI_STATE_CONNECTING;
+        wifi_connect_cnt = WIFI_MAX_CONNECT_ATTEMPTS;
+    } else {
+        rv = EOS_ERR_BUSY;
+    }
+    xSemaphoreGive(mutex);
+
+    if (rv) return rv;
+
+    esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_sta_config);
+
+    if (_wifi_state == WIFI_STATE_STOPPED) {
+        ret = esp_wifi_start();
+    } else {
+        ret = esp_wifi_connect();
+    }
+    if (ret != ESP_OK) rv = EOS_ERR;
+
+    return rv;
+}
+
+int eos_wifi_disconnect(void) {
+    int rv = EOS_OK;
+    esp_err_t ret = ESP_OK;
+
+    xSemaphoreTake(mutex, portMAX_DELAY);
+    if (!wifi_action) {
+        wifi_action = WIFI_ACTION_DISCONNECT;
+        wifi_state = WIFI_STATE_DISCONNECTING;
+        wifi_connect_cnt = 0;
+
+        memset(wifi_sta_config.sta.ssid, 0, sizeof(wifi_sta_config.sta.ssid));
+        memset(wifi_sta_config.sta.password, 0, sizeof(wifi_sta_config.sta.password));
+    } else {
+        rv = EOS_ERR_BUSY;
+    }
+    xSemaphoreGive(mutex);
+
+    if (rv) return rv;
+
+    ret = esp_wifi_stop();
+    if (ret != ESP_OK) rv = EOS_ERR;
+
+    return rv;
+}
+
-- 
cgit v1.2.3