diff --git a/src/IR_RMT.cpp b/src/IR_RMT.cpp new file mode 100644 index 0000000..cbb45d9 --- /dev/null +++ b/src/IR_RMT.cpp @@ -0,0 +1,314 @@ +// https://github.com/espressif/esp-idf/blob/master/examples/peripherals/rmt_nec_tx_rx/main/infrared_nec_main.c + +#include + +#include + +#include "IR_RMT.h" + +static constexpr uint8_t CLK_DIV = 100; // Clock divisor (base clock is 80MHz) +static constexpr uint16_t TICK_10_US = (80000000 / CLK_DIV / 100000); // Number of clock ticks that represent 10us. 10 us = 1/100th msec. + +static constexpr uint8_t NEC_DATA_ITEM_NUM = 34; // NEC code item number: header + 32bit data + end + +#define NEC_HEADER_HIGH_US 9000 /*!< NEC protocol header: positive 9ms */ +#define NEC_HEADER_LOW_US 4500 /*!< NEC protocol header: negative 4.5ms*/ +#define NEC_BIT_ONE_HIGH_US 560 /*!< NEC protocol data bit 1: positive 0.56ms */ +#define NEC_BIT_ONE_LOW_US (2250-NEC_BIT_ONE_HIGH_US) /*!< NEC protocol data bit 1: negative 1.69ms */ +#define NEC_BIT_ZERO_HIGH_US 560 /*!< NEC protocol data bit 0: positive 0.56ms */ +#define NEC_BIT_ZERO_LOW_US (1120-NEC_BIT_ZERO_HIGH_US) /*!< NEC protocol data bit 0: negative 0.56ms */ +#define NEC_BIT_END 560 /*!< NEC protocol end: positive 0.56ms */ +#define NEC_REPEAT_LOW_US 2250 /*!< NEC repeat header: negative 2.25ms*/ +#define NEC_BIT_REPEAT_HIGH_US 560 +#define NEC_BIT_MARGIN 40 /*!< NEC parse margin time */ + +namespace { + +bool isInRange(rmt_item32_t item, int lowDuration, int highDuration, int tolerance) { + uint32_t lowValue = item.duration0 * 10 / TICK_10_US; + uint32_t highValue = item.duration1 * 10 / TICK_10_US; + /* + ESP_LOGD(tag, "lowValue=%d, highValue=%d, lowDuration=%d, highDuration=%d", + lowValue, highValue, lowDuration, highDuration); + */ + if (lowValue < (lowDuration - tolerance) || lowValue > (lowDuration + tolerance) || + (highValue != 0 && + (highValue < (highDuration - tolerance) || highValue > (highDuration + tolerance)))) { + return false; + } + return true; +} + +bool NEC_is0(rmt_item32_t item) { + return isInRange(item, NEC_BIT_ZERO_HIGH_US, NEC_BIT_ZERO_LOW_US, NEC_BIT_MARGIN); +} + +bool NEC_is1(rmt_item32_t item) { + return isInRange(item, NEC_BIT_ONE_HIGH_US, NEC_BIT_ONE_LOW_US, NEC_BIT_MARGIN); +} + +ir_data_t decodeNEC(rmt_item32_t *data, int numItems) { + ir_data_t irData = {0,0}; + + // check for repeat + if (numItems == 2) { + if (isInRange(data[0], NEC_HEADER_HIGH_US, NEC_REPEAT_LOW_US, NEC_BIT_MARGIN)) { +Serial.println("repeat header"); + if (isInRange(data[1], NEC_BIT_REPEAT_HIGH_US, 0, NEC_BIT_MARGIN)) { + irData = {REPEAT, REPEAT}; + return irData; + } + } + } + + if (!isInRange(data[0], NEC_HEADER_HIGH_US, NEC_HEADER_LOW_US, NEC_BIT_MARGIN) || numItems != 34) { + ESP_LOGD(tag, "Not an NEC"); + irData = {INVALID_PROTOCOL, INVALID_PROTOCOL}; + return irData; // 0 + } + int i; + uint8_t address = 0, notAddress = 0, command = 0, notCommand = 0; + int accumCounter = 0; + uint8_t accumValue = 0; + for (i=1; i> 1; + } else if (NEC_is1(data[i])) { + ESP_LOGD(tag, "%d: 1", i); + accumValue = (accumValue >> 1) | 0x80; + } else { + ESP_LOGD(tag, "Unknown"); + } + if (accumCounter == 7) { + accumCounter = 0; + ESP_LOGD(tag, "Byte: 0x%.2x", accumValue); + if (i==8) { + address = accumValue; + } else if (i==16) { + notAddress = accumValue; + } else if (i==24) { + command = accumValue; + } else if (i==32) { + notCommand = accumValue; + } + accumValue = 0; + } else { + accumCounter++; + } + } + ESP_LOGD(tag, "Address: 0x%.2x, NotAddress: 0x%.2x", address, notAddress ^ 0xff); + if (address != (notAddress ^ 0xff) || command != (notCommand ^ 0xff)) { + ESP_LOGD(tag, "Data mis match"); + irData = {INVALID_CHECKSUM, INVALID_CHECKSUM}; + return irData; // 0 + } + ESP_LOGD(tag, "Address: 0x%.2x, Command: 0x%.2x", address, command); + + irData.address = address; + irData.command = command; + + return irData; +} + + + +} /* anonymous namespace */ + + +/* + * @brief Build register value of waveform for NEC one data bit + */ +inline void IR_RMT::nec_fill_item_level(rmt_item32_t* item, int high_us, int low_us) +{ + item->level0 = inverted ? 0 : 1; + item->duration0 = (high_us) / 10 * TICK_10_US; + item->level1 = inverted ? 1 : 0; + item->duration1 = (low_us) / 10 * TICK_10_US; +} + +/* + * @brief Generate NEC header value: active 9ms + negative 4.5ms + */ +void IR_RMT::nec_fill_item_header(rmt_item32_t* item) +{ + nec_fill_item_level(item, NEC_HEADER_HIGH_US, NEC_HEADER_LOW_US); +} + +/* + * @brief Generate NEC data bit 1: positive 0.56ms + negative 1.69ms + */ +void IR_RMT::nec_fill_item_bit_one(rmt_item32_t* item) +{ + nec_fill_item_level(item, NEC_BIT_ONE_HIGH_US, NEC_BIT_ONE_LOW_US); +} + +/* + * @brief Generate NEC data bit 0: positive 0.56ms + negative 0.56ms + */ +void IR_RMT::nec_fill_item_bit_zero(rmt_item32_t* item) +{ + nec_fill_item_level(item, NEC_BIT_ZERO_HIGH_US, NEC_BIT_ZERO_LOW_US); +} + +/* + * @brief Generate NEC end signal: positive 0.56ms + */ +void IR_RMT::nec_fill_item_end(rmt_item32_t* item) +{ + nec_fill_item_level(item, NEC_BIT_END, 0x7fff); +} + +/* + * @brief Build NEC 32bit waveform. + */ +void IR_RMT::nec_build_items(rmt_item32_t* item, uint16_t addr, uint16_t cmd_data) +{ + nec_fill_item_header(item++); + for(int j = 0; j < 16; j++) { + if(addr & 0x1) { + nec_fill_item_bit_one(item); + } else { + nec_fill_item_bit_zero(item); + } + item++; + addr >>= 1; + } + for(int j = 0; j < 16; j++) { + if(cmd_data & 0x1) { + nec_fill_item_bit_one(item); + } else { + nec_fill_item_bit_zero(item); + } + item++; + cmd_data >>= 1; + } + nec_fill_item_end(item); +} + +bool IR_RMT::send(uint16_t address, uint16_t command) { + int item_num = NEC_DATA_ITEM_NUM; + size_t size = (sizeof(rmt_item32_t) * item_num); + + rmt_item32_t* item = (rmt_item32_t*)heap_caps_malloc(size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + memset((void*) item, 0, size); + + nec_build_items(item, ((~address) << 8) | address, ((~command) << 8) | command); + + //To send data according to the waveform items. + rmt_write_items(tx_channel, item, item_num, true); + //Wait until sending is done. + //rmt_wait_tx_done(tx_channel, portMAX_DELAY); + //before we free the data, make sure sending is already done. + free(item); + return true; +} + +bool IR_RMT::sendNEC32(uint32_t data) { + int item_num = NEC_DATA_ITEM_NUM; + size_t size = (sizeof(rmt_item32_t) * item_num); + + rmt_item32_t* item = (rmt_item32_t*)heap_caps_malloc(size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + memset((void*) item, 0, size); + + nec_build_items(item, (data >> 16), data); + + //To send data according to the waveform items. + rmt_write_items(tx_channel, item, item_num, true); + //Wait until sending is done. + //rmt_wait_tx_done(tx_channel, portMAX_DELAY); + //before we free the data, make sure sending is already done. + free(item); + return true; +} + +IR_RMT::IR_RMT(gpio_num_t rx_pin, gpio_num_t tx_pin, bool inverted) { + this->rx_pin = rx_pin; + this->tx_pin = tx_pin; + this->rx_channel = RMT_CHANNEL_7; + this->tx_channel = RMT_CHANNEL_6; + this->running = false; + this->inverted = inverted; +} + +bool IR_RMT::begin() { + if (rx_pin >= 0) { + rmt_config_t config; + config.rmt_mode = RMT_MODE_RX; + config.channel = rx_channel; + config.gpio_num = rx_pin; + config.mem_block_num = 1; + config.rx_config.filter_en = true; + config.rx_config.filter_ticks_thresh = 100; // 80000000/100 -> 800000 / 100 = 8000 = 125us + config.rx_config.idle_threshold = TICK_10_US * 100 * 20; + config.clk_div = CLK_DIV; + + ESP_ERROR_CHECK(rmt_config(&config)); + ESP_ERROR_CHECK(rmt_driver_install(config.channel, 1000, 0)); + rmt_get_ringbuf_handle(rx_channel, &ringBuf); + rmt_rx_start(rx_channel, 1); + } + + if (tx_pin >= 0) { + rmt_config_t rmt_tx; + rmt_tx.rmt_mode = RMT_MODE_TX; + rmt_tx.channel = tx_channel; + rmt_tx.gpio_num = tx_pin; + rmt_tx.mem_block_num = 1; + rmt_tx.clk_div = CLK_DIV; + rmt_tx.tx_config.loop_en = false; + rmt_tx.tx_config.carrier_duty_percent = 50; + rmt_tx.tx_config.carrier_freq_hz = 38000; + rmt_tx.tx_config.carrier_level = inverted ? RMT_CARRIER_LEVEL_LOW : RMT_CARRIER_LEVEL_HIGH; + rmt_tx.tx_config.carrier_en = true; + rmt_tx.tx_config.idle_level = inverted ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW; + rmt_tx.tx_config.idle_output_en = true; + ESP_ERROR_CHECK(rmt_config(&rmt_tx)); + ESP_ERROR_CHECK(rmt_driver_install(rmt_tx.channel, 0, 0)); + //rmt_tx_start(tx_channel, true); + } +} + +bool IR_RMT::stop() { + running = false; + rmt_tx_stop(tx_channel); + rmt_rx_stop(rx_channel); +} + +IR_RMT::~IR_RMT() { + rmt_driver_uninstall(tx_channel); + rmt_driver_uninstall(rx_channel); +} + +bool IR_RMT::register_callback(const ir_cb_t &cb) { + callbacks_.push_back(cb); + if (!rx_task_handle) { + xTaskCreate(&cTaskWrapper, "watch_ringbuf", 3072, this, tskIDLE_PRIORITY+2, &rx_task_handle); + } +} + +void IR_RMT::task_watch_ringbuf(void* pvParameters) { + size_t itemSize; + + running = true; + while(running && ringBuf) { + rmt_item32_t* data = (rmt_item32_t*) xRingbufferReceive(ringBuf, &itemSize, portMAX_DELAY); + int numItems = itemSize / sizeof(rmt_item32_t); + + ir_data_t val = decodeNEC(data, numItems); + + if (val.address != INVALID_CHECKSUM && val.address != INVALID_PROTOCOL) { + for (const auto &cb : callbacks_) { + cb(val); + } + } + + vRingbufferReturnItem(ringBuf, (void*) data); + } + rx_task_handle = NULL; + vTaskDelete(NULL); +} + +void IR_RMT::cTaskWrapper(void* parameters) { + static_cast(parameters)->task_watch_ringbuf(NULL); +} diff --git a/src/IR_RMT.h b/src/IR_RMT.h new file mode 100644 index 0000000..f3e2804 --- /dev/null +++ b/src/IR_RMT.h @@ -0,0 +1,53 @@ +#ifndef _IR_RMT_H +#define _IR_RMT_H + +#include +#include +#include + +#include + + +struct ir_data_t { + uint16_t address; + uint16_t command; +}; + +typedef std::function ir_cb_t; + +class IR_RMT { + public: + IR_RMT(gpio_num_t rx_pin, gpio_num_t tx_pin, bool inverted = false); + ~IR_RMT(); + bool begin(void); + bool stop(void); + bool register_callback(const ir_cb_t &cb); + bool send(uint16_t address, uint16_t command); + bool sendNEC32(uint32_t data); + private: + gpio_num_t rx_pin; + gpio_num_t tx_pin; + rmt_channel_t rx_channel; + rmt_channel_t tx_channel; + RingbufHandle_t ringBuf; + TaskHandle_t rx_task_handle; + bool running; + bool inverted; + void task_watch_ringbuf(void*); + static void cTaskWrapper(void*); + std::vector callbacks_; + protected: + void nec_fill_item_level(rmt_item32_t* item, int high_us, int low_us); + void nec_fill_item_header(rmt_item32_t* item); + void nec_fill_item_bit_one(rmt_item32_t* item); + void nec_fill_item_bit_zero(rmt_item32_t* item); + void nec_fill_item_end(rmt_item32_t* item); + void nec_build_items(rmt_item32_t* item, uint16_t addr, uint16_t cmd_data); +}; + +enum command_type { + REPEAT = 0x100, + INVALID_CHECKSUM = UINT16_MAX-1, + INVALID_PROTOCOL = UINT16_MAX }; + +#endif /* _IR_RMT_H */ diff --git a/src/main.cpp b/src/main.cpp index 4eea5be..ae19026 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,6 +29,8 @@ #define ARDUINO_SAMD_VARIANT_COMPLIANCE #include "SdsDustSensor.h" +#include "IR_RMT.h" + #include "network/XD0OTA.h" #include "network/XD0MQTT.h" @@ -53,6 +55,9 @@ SdsDustSensor sds(Serial2); Adafruit_VEML6075 uv = Adafruit_VEML6075(); BH1750 lightMeter; +static constexpr gpio_num_t PIN_IR_TX = (gpio_num_t)15; +IR_RMT ir((gpio_num_t)-1, PIN_IR_TX, true); + XD0OTA ota("esp32-weatherstation"); XD0MQTT mqtt; @@ -514,6 +519,10 @@ void sendValues() { } +void ir_received(ir_data_t data) { + Serial.println("IR DATA RECEIVED"); +} + /** * \brief Setup function * @@ -635,6 +644,9 @@ void setup() const char* fw_version_str = String(FW_VERSION).c_str(); mqtt.publish(topic_version.c_str(), fw_version_str, strlen(fw_version_str)); + ir.begin(); + ir.register_callback(ir_received); + ESP_LOGD(TAG, "setup done"); } diff --git a/src/network/XD0MQTT.cpp b/src/network/XD0MQTT.cpp index 65e2400..2ffb0cb 100644 --- a/src/network/XD0MQTT.cpp +++ b/src/network/XD0MQTT.cpp @@ -129,7 +129,7 @@ bool XD0MQTT::publish(const char* topic, const float data, const char* format, i ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); } -bool XD0MQTT::subscribe(const char* topic, const cb_t &cb, int qos) { +bool XD0MQTT::subscribe(const char* topic, const xd0mqtt_cb_t &cb, int qos) { subscription_t subscription = {topic, cb, qos}; subscriptions_.push_back(subscription); return true; diff --git a/src/network/XD0MQTT.h b/src/network/XD0MQTT.h index 54d64a2..fc0c5a3 100644 --- a/src/network/XD0MQTT.h +++ b/src/network/XD0MQTT.h @@ -17,11 +17,12 @@ #include "mqtt_client.h" -typedef std::function cb_t; + +typedef std::function xd0mqtt_cb_t; struct subscription_t { const char* topic; - cb_t cb; + xd0mqtt_cb_t cb; int qos; }; @@ -31,7 +32,7 @@ class XD0MQTT { bool begin(void); bool publish(const char* topic, const char* data, int len, int qos=1, int retain=0); bool publish(const char* topic, const float data, const char* format, int qos=1, int retain=0); - bool subscribe(const char* topic, const cb_t &cb, int qos=1); + bool subscribe(const char* topic, const xd0mqtt_cb_t &cb, int qos=1); bool unsubscribe(const char* topic); esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event); bool connected = false;