├── README.md ├── components └── sx127x │ ├── packet_transport │ ├── sx127x_transport.cpp │ ├── __init__.py │ └── sx127x_transport.h │ ├── automation.h │ ├── sx127x.h │ ├── sx127x_reg.h │ ├── __init__.py │ └── sx127x.cpp └── .gitignore /README.md: -------------------------------------------------------------------------------- 1 | SX127x component configures SX1276, SX1277, SX1278 or SX1279 hardware for use in ESPHome. Tested with the LILYGO LoRa32 V2.1_1.6. 2 | 3 | NOTE: this was merged into ESPHome 2025.7, this repo is deprecated. 4 | 5 | Docs can be found here: https://esphome.io/components/sx127x 6 | 7 | Issue can be filed here: https://github.com/esphome/esphome/issues 8 | -------------------------------------------------------------------------------- /components/sx127x/packet_transport/sx127x_transport.cpp: -------------------------------------------------------------------------------- 1 | #include "esphome/core/log.h" 2 | #include "esphome/core/application.h" 3 | #include "sx127x_transport.h" 4 | 5 | namespace esphome { 6 | namespace sx127x { 7 | 8 | static const char *const TAG = "sx127x_transport"; 9 | 10 | void SX127xTransport::setup() { 11 | PacketTransport::setup(); 12 | this->parent_->register_listener(this); 13 | } 14 | 15 | void SX127xTransport::update() { 16 | PacketTransport::update(); 17 | this->updated_ = true; 18 | this->resend_data_ = true; 19 | } 20 | 21 | void SX127xTransport::send_packet(const std::vector &buf) const { this->parent_->transmit_packet(buf); } 22 | 23 | void SX127xTransport::on_packet(const std::vector &packet, float rssi, float snr) { this->process_(packet); } 24 | 25 | } // namespace sx127x 26 | } // namespace esphome 27 | -------------------------------------------------------------------------------- /components/sx127x/packet_transport/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components.packet_transport import ( 3 | PacketTransport, 4 | new_packet_transport, 5 | transport_schema, 6 | ) 7 | import esphome.config_validation as cv 8 | from esphome.cpp_types import PollingComponent 9 | 10 | from .. import CONF_SX127X_ID, SX127x, SX127xListener, sx127x_ns 11 | 12 | SX127xTransport = sx127x_ns.class_( 13 | "SX127xTransport", PacketTransport, PollingComponent, SX127xListener 14 | ) 15 | 16 | CONFIG_SCHEMA = transport_schema(SX127xTransport).extend( 17 | { 18 | cv.GenerateID(CONF_SX127X_ID): cv.use_id(SX127x), 19 | } 20 | ) 21 | 22 | 23 | async def to_code(config): 24 | var, _ = await new_packet_transport(config) 25 | sx127x = await cg.get_variable(config[CONF_SX127X_ID]) 26 | cg.add(var.set_parent(sx127x)) 27 | -------------------------------------------------------------------------------- /components/sx127x/packet_transport/sx127x_transport.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/components/sx127x/sx127x.h" 5 | #include "esphome/components/packet_transport/packet_transport.h" 6 | #include 7 | 8 | namespace esphome { 9 | namespace sx127x { 10 | 11 | class SX127xTransport : public packet_transport::PacketTransport, public Parented, public SX127xListener { 12 | public: 13 | void setup() override; 14 | void update() override; 15 | void on_packet(const std::vector &packet, float rssi, float snr) override; 16 | float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } 17 | 18 | protected: 19 | void send_packet(const std::vector &buf) const override; 20 | bool should_send() override { return true; } 21 | size_t get_max_packet_size() override { return this->parent_->get_max_packet_size(); } 22 | }; 23 | 24 | } // namespace sx127x 25 | } // namespace esphome 26 | -------------------------------------------------------------------------------- /components/sx127x/automation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/automation.h" 5 | #include "esphome/components/sx127x/sx127x.h" 6 | 7 | namespace esphome { 8 | namespace sx127x { 9 | 10 | template class RunImageCalAction : public Action, public Parented { 11 | public: 12 | void play(Ts... x) override { this->parent_->run_image_cal(); } 13 | }; 14 | 15 | template class SendPacketAction : public Action, public Parented { 16 | public: 17 | void set_data_template(std::function(Ts...)> func) { 18 | this->data_func_ = func; 19 | this->static_ = false; 20 | } 21 | 22 | void set_data_static(const std::vector &data) { 23 | this->data_static_ = data; 24 | this->static_ = true; 25 | } 26 | 27 | void play(Ts... x) override { 28 | if (this->static_) { 29 | this->parent_->transmit_packet(this->data_static_); 30 | } else { 31 | this->parent_->transmit_packet(this->data_func_(x...)); 32 | } 33 | } 34 | 35 | protected: 36 | bool static_{false}; 37 | std::function(Ts...)> data_func_{}; 38 | std::vector data_static_{}; 39 | }; 40 | 41 | template class SetModeTxAction : public Action, public Parented { 42 | public: 43 | void play(Ts... x) override { this->parent_->set_mode_tx(); } 44 | }; 45 | 46 | template class SetModeRxAction : public Action, public Parented { 47 | public: 48 | void play(Ts... x) override { this->parent_->set_mode_rx(); } 49 | }; 50 | 51 | template class SetModeSleepAction : public Action, public Parented { 52 | public: 53 | void play(Ts... x) override { this->parent_->set_mode_sleep(); } 54 | }; 55 | 56 | template class SetModeStandbyAction : public Action, public Parented { 57 | public: 58 | void play(Ts... x) override { this->parent_->set_mode_standby(); } 59 | }; 60 | 61 | } // namespace sx127x 62 | } // namespace esphome 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Hide sublime text stuff 10 | *.sublime-project 11 | *.sublime-workspace 12 | 13 | # Intellij Idea 14 | .idea 15 | 16 | # Eclipse 17 | .project 18 | .cproject 19 | .pydevproject 20 | .settings/ 21 | 22 | # Vim 23 | *.swp 24 | 25 | # Hide some OS X stuff 26 | .DS_Store 27 | .AppleDouble 28 | .LSOverride 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Distribution / packaging 35 | .Python 36 | build/ 37 | develop-eggs/ 38 | dist/ 39 | downloads/ 40 | eggs/ 41 | .eggs/ 42 | lib/ 43 | lib64/ 44 | parts/ 45 | sdist/ 46 | var/ 47 | wheels/ 48 | *.egg-info/ 49 | .installed.cfg 50 | *.egg 51 | MANIFEST 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .coverage 61 | .coverage.* 62 | .cache 63 | .esphome 64 | nosetests.xml 65 | coverage.xml 66 | cov.xml 67 | *.cover 68 | .hypothesis/ 69 | .pytest_cache/ 70 | 71 | # Translations 72 | *.mo 73 | *.pot 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # Environments 79 | .env 80 | .venv 81 | env/ 82 | venv/ 83 | ENV/ 84 | env.bak/ 85 | venv.bak/ 86 | venv-*/ 87 | 88 | # mypy 89 | .mypy_cache/ 90 | 91 | .pioenvs 92 | .piolibdeps 93 | .pio 94 | .vscode/ 95 | !.vscode/tasks.json 96 | CMakeListsPrivate.txt 97 | CMakeLists.txt 98 | 99 | # User-specific stuff: 100 | .idea/**/workspace.xml 101 | .idea/**/tasks.xml 102 | .idea/dictionaries 103 | 104 | # Sensitive or high-churn files: 105 | .idea/**/dataSources/ 106 | .idea/**/dataSources.ids 107 | .idea/**/dataSources.xml 108 | .idea/**/dataSources.local.xml 109 | .idea/**/dynamic.xml 110 | 111 | # CMake 112 | cmake-build-*/ 113 | 114 | CMakeCache.txt 115 | CMakeFiles 116 | CMakeScripts 117 | Testing 118 | Makefile 119 | cmake_install.cmake 120 | install_manifest.txt 121 | compile_commands.json 122 | CTestTestfile.cmake 123 | /*.cbp 124 | 125 | .clang_complete 126 | .gcc-flags.json 127 | 128 | config/ 129 | tests/build/ 130 | tests/.esphome/ 131 | /.temp-clang-tidy.cpp 132 | /.temp/ 133 | .pio/ 134 | 135 | sdkconfig.* 136 | !sdkconfig.defaults 137 | 138 | .tests/ 139 | 140 | -------------------------------------------------------------------------------- /components/sx127x/sx127x.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sx127x_reg.h" 4 | #include "esphome/components/spi/spi.h" 5 | #include "esphome/core/automation.h" 6 | #include "esphome/core/component.h" 7 | #include 8 | 9 | namespace esphome { 10 | namespace sx127x { 11 | 12 | enum SX127xBw : uint8_t { 13 | SX127X_BW_2_6, 14 | SX127X_BW_3_1, 15 | SX127X_BW_3_9, 16 | SX127X_BW_5_2, 17 | SX127X_BW_6_3, 18 | SX127X_BW_7_8, 19 | SX127X_BW_10_4, 20 | SX127X_BW_12_5, 21 | SX127X_BW_15_6, 22 | SX127X_BW_20_8, 23 | SX127X_BW_25_0, 24 | SX127X_BW_31_3, 25 | SX127X_BW_41_7, 26 | SX127X_BW_50_0, 27 | SX127X_BW_62_5, 28 | SX127X_BW_83_3, 29 | SX127X_BW_100_0, 30 | SX127X_BW_125_0, 31 | SX127X_BW_166_7, 32 | SX127X_BW_200_0, 33 | SX127X_BW_250_0, 34 | SX127X_BW_500_0, 35 | }; 36 | 37 | enum class SX127xError { NONE = 0, TIMEOUT, INVALID_PARAMS }; 38 | 39 | class SX127xListener { 40 | public: 41 | virtual void on_packet(const std::vector &packet, float rssi, float snr) = 0; 42 | }; 43 | 44 | class SX127x : public Component, 45 | public spi::SPIDevice { 47 | public: 48 | size_t get_max_packet_size(); 49 | float get_setup_priority() const override { return setup_priority::PROCESSOR; } 50 | void setup() override; 51 | void loop() override; 52 | void dump_config() override; 53 | void set_auto_cal(bool auto_cal) { this->auto_cal_ = auto_cal; } 54 | void set_bandwidth(SX127xBw bandwidth) { this->bandwidth_ = bandwidth; } 55 | void set_bitrate(uint32_t bitrate) { this->bitrate_ = bitrate; } 56 | void set_bitsync(bool bitsync) { this->bitsync_ = bitsync; } 57 | void set_coding_rate(uint8_t coding_rate) { this->coding_rate_ = coding_rate; } 58 | void set_crc_enable(bool crc_enable) { this->crc_enable_ = crc_enable; } 59 | void set_deviation(uint32_t deviation) { this->deviation_ = deviation; } 60 | void set_dio0_pin(InternalGPIOPin *dio0_pin) { this->dio0_pin_ = dio0_pin; } 61 | void set_frequency(uint32_t frequency) { this->frequency_ = frequency; } 62 | void set_mode_rx(); 63 | void set_mode_tx(); 64 | void set_mode_standby(); 65 | void set_mode_sleep(); 66 | void set_modulation(uint8_t modulation) { this->modulation_ = modulation; } 67 | void set_pa_pin(uint8_t pin) { this->pa_pin_ = pin; } 68 | void set_pa_power(uint8_t power) { this->pa_power_ = power; } 69 | void set_pa_ramp(uint8_t ramp) { this->pa_ramp_ = ramp; } 70 | void set_packet_mode(bool packet_mode) { this->packet_mode_ = packet_mode; } 71 | void set_payload_length(uint8_t payload_length) { this->payload_length_ = payload_length; } 72 | void set_preamble_errors(uint8_t preamble_errors) { this->preamble_errors_ = preamble_errors; } 73 | void set_preamble_polarity(uint8_t preamble_polarity) { this->preamble_polarity_ = preamble_polarity; } 74 | void set_preamble_size(uint16_t preamble_size) { this->preamble_size_ = preamble_size; } 75 | void set_preamble_detect(uint8_t preamble_detect) { this->preamble_detect_ = preamble_detect; } 76 | void set_rst_pin(InternalGPIOPin *rst_pin) { this->rst_pin_ = rst_pin; } 77 | void set_rx_floor(float floor) { this->rx_floor_ = floor; } 78 | void set_rx_start(bool start) { this->rx_start_ = start; } 79 | void set_shaping(uint8_t shaping) { this->shaping_ = shaping; } 80 | void set_spreading_factor(uint8_t spreading_factor) { this->spreading_factor_ = spreading_factor; } 81 | void set_sync_value(const std::vector &sync_value) { this->sync_value_ = sync_value; } 82 | void run_image_cal(); 83 | void configure(); 84 | SX127xError transmit_packet(const std::vector &packet); 85 | void register_listener(SX127xListener *listener) { this->listeners_.push_back(listener); } 86 | Trigger, float, float> *get_packet_trigger() const { return this->packet_trigger_; }; 87 | 88 | protected: 89 | void configure_fsk_ook_(); 90 | void configure_lora_(); 91 | void set_mode_(uint8_t modulation, uint8_t mode); 92 | void write_fifo_(const std::vector &packet); 93 | void read_fifo_(std::vector &packet); 94 | void write_register_(uint8_t reg, uint8_t value); 95 | void call_listeners_(const std::vector &packet, float rssi, float snr); 96 | uint8_t read_register_(uint8_t reg); 97 | Trigger, float, float> *packet_trigger_{new Trigger, float, float>()}; 98 | std::vector listeners_; 99 | std::vector packet_; 100 | std::vector sync_value_; 101 | InternalGPIOPin *dio0_pin_{nullptr}; 102 | InternalGPIOPin *rst_pin_{nullptr}; 103 | SX127xBw bandwidth_; 104 | uint32_t bitrate_; 105 | uint32_t deviation_; 106 | uint32_t frequency_; 107 | uint32_t payload_length_; 108 | uint16_t preamble_size_; 109 | uint8_t coding_rate_; 110 | uint8_t modulation_; 111 | uint8_t pa_pin_; 112 | uint8_t pa_power_; 113 | uint8_t pa_ramp_; 114 | uint8_t preamble_detect_; 115 | uint8_t preamble_errors_; 116 | uint8_t preamble_polarity_; 117 | uint8_t shaping_; 118 | uint8_t spreading_factor_; 119 | float rx_floor_; 120 | bool auto_cal_{false}; 121 | bool bitsync_{false}; 122 | bool crc_enable_{false}; 123 | bool packet_mode_{false}; 124 | bool rx_start_{false}; 125 | }; 126 | 127 | } // namespace sx127x 128 | } // namespace esphome 129 | -------------------------------------------------------------------------------- /components/sx127x/sx127x_reg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/hal.h" 4 | 5 | namespace esphome { 6 | namespace sx127x { 7 | 8 | enum SX127xReg : uint8_t { 9 | // Common registers 10 | REG_FIFO = 0x00, 11 | REG_OP_MODE = 0x01, 12 | REG_BITRATE_MSB = 0x02, 13 | REG_BITRATE_LSB = 0x03, 14 | REG_FDEV_MSB = 0x04, 15 | REG_FDEV_LSB = 0x05, 16 | REG_FRF_MSB = 0x06, 17 | REG_FRF_MID = 0x07, 18 | REG_FRF_LSB = 0x08, 19 | REG_PA_CONFIG = 0x09, 20 | REG_PA_RAMP = 0x0A, 21 | REG_DIO_MAPPING1 = 0x40, 22 | REG_DIO_MAPPING2 = 0x41, 23 | REG_VERSION = 0x42, 24 | // FSK/OOK registers 25 | REG_RX_CONFIG = 0x0D, 26 | REG_RSSI_THRESH = 0x10, 27 | REG_RX_BW = 0x12, 28 | REG_OOK_PEAK = 0x14, 29 | REG_OOK_FIX = 0x15, 30 | REG_OOK_AVG = 0x16, 31 | REG_AFC_FEI = 0x1A, 32 | REG_PREAMBLE_DETECT = 0x1F, 33 | REG_PREAMBLE_SIZE_MSB = 0x25, 34 | REG_PREAMBLE_SIZE_LSB = 0x26, 35 | REG_SYNC_CONFIG = 0x27, 36 | REG_SYNC_VALUE1 = 0x28, 37 | REG_SYNC_VALUE2 = 0x29, 38 | REG_SYNC_VALUE3 = 0x2A, 39 | REG_SYNC_VALUE4 = 0x2B, 40 | REG_SYNC_VALUE5 = 0x2C, 41 | REG_SYNC_VALUE6 = 0x2D, 42 | REG_SYNC_VALUE7 = 0x2E, 43 | REG_SYNC_VALUE8 = 0x2F, 44 | REG_PACKET_CONFIG_1 = 0x30, 45 | REG_PACKET_CONFIG_2 = 0x31, 46 | REG_PAYLOAD_LENGTH_LSB = 0x32, 47 | REG_FIFO_THRESH = 0x35, 48 | REG_IMAGE_CAL = 0x3B, 49 | // LoRa registers 50 | REG_FIFO_ADDR_PTR = 0x0D, 51 | REG_FIFO_TX_BASE_ADDR = 0x0E, 52 | REG_FIFO_RX_BASE_ADDR = 0x0F, 53 | REG_FIFO_RX_CURR_ADDR = 0x10, 54 | REG_IRQ_FLAGS_MASK = 0x11, 55 | REG_IRQ_FLAGS = 0x12, 56 | REG_NB_RX_BYTES = 0x13, 57 | REG_MODEM_STAT = 0x18, 58 | REG_PKT_SNR_VALUE = 0x19, 59 | REG_PKT_RSSI_VALUE = 0x1A, 60 | REG_RSSI_VALUE = 0x1B, 61 | REG_HOP_CHANNEL = 0x1C, 62 | REG_MODEM_CONFIG1 = 0x1D, 63 | REG_MODEM_CONFIG2 = 0x1E, 64 | REG_SYMB_TIMEOUT_LSB = 0x1F, 65 | REG_PREAMBLE_LEN_MSB = 0x20, 66 | REG_PREAMBLE_LEN_LSB = 0x21, 67 | REG_PAYLOAD_LENGTH = 0x22, 68 | REG_HOP_PERIOD = 0x24, 69 | REG_FIFO_RX_BYTE_ADDR = 0x25, 70 | REG_MODEM_CONFIG3 = 0x26, 71 | REG_FEI_MSB = 0x28, 72 | REG_FEI_MIB = 0x29, 73 | REG_FEI_LSB = 0x2A, 74 | REG_DETECT_OPTIMIZE = 0x31, 75 | REG_INVERT_IQ = 0x33, 76 | REG_DETECT_THRESHOLD = 0x37, 77 | REG_SYNC_WORD = 0x39, 78 | }; 79 | 80 | enum SX127xOpMode : uint8_t { 81 | MOD_LORA = 0x80, 82 | ACCESS_FSK_REGS = 0x40, 83 | ACCESS_LORA_REGS = 0x00, 84 | MOD_OOK = 0x20, 85 | MOD_FSK = 0x00, 86 | ACCESS_LF_REGS = 0x08, 87 | ACCESS_HF_REGS = 0x00, 88 | MODE_CAD = 0x07, 89 | MODE_RX_SINGLE = 0x06, 90 | MODE_RX = 0x05, 91 | MODE_RX_FS = 0x04, 92 | MODE_TX = 0x03, 93 | MODE_TX_FS = 0x02, 94 | MODE_STDBY = 0x01, 95 | MODE_SLEEP = 0x00, 96 | MODE_MASK = 0x07, 97 | }; 98 | 99 | enum SX127xPaConfig : uint8_t { 100 | PA_PIN_BOOST = 0x80, 101 | PA_PIN_RFO = 0x00, 102 | PA_MAX_POWER = 0x70, 103 | }; 104 | 105 | enum SX127xPaRamp : uint8_t { 106 | CUTOFF_BR_X_2 = 0x40, 107 | CUTOFF_BR_X_1 = 0x20, 108 | GAUSSIAN_BT_0_3 = 0x60, 109 | GAUSSIAN_BT_0_5 = 0x40, 110 | GAUSSIAN_BT_1_0 = 0x20, 111 | SHAPING_NONE = 0x00, 112 | PA_RAMP_10 = 0x0F, 113 | PA_RAMP_12 = 0x0E, 114 | PA_RAMP_15 = 0x0D, 115 | PA_RAMP_20 = 0x0C, 116 | PA_RAMP_25 = 0x0B, 117 | PA_RAMP_31 = 0x0A, 118 | PA_RAMP_40 = 0x09, 119 | PA_RAMP_50 = 0x08, 120 | PA_RAMP_62 = 0x07, 121 | PA_RAMP_100 = 0x06, 122 | PA_RAMP_125 = 0x05, 123 | PA_RAMP_250 = 0x04, 124 | PA_RAMP_500 = 0x03, 125 | PA_RAMP_1000 = 0x02, 126 | PA_RAMP_2000 = 0x01, 127 | PA_RAMP_3400 = 0x00, 128 | }; 129 | 130 | enum SX127xDioMapping1 : uint8_t { 131 | DIO0_MAPPING_00 = 0x00, 132 | DIO0_MAPPING_01 = 0x40, 133 | DIO0_MAPPING_10 = 0x80, 134 | DIO0_MAPPING_11 = 0xC0, 135 | }; 136 | 137 | enum SX127xRxConfig : uint8_t { 138 | RESTART_ON_COLLISION = 0x80, 139 | RESTART_NO_LOCK = 0x40, 140 | RESTART_PLL_LOCK = 0x20, 141 | AFC_AUTO_ON = 0x10, 142 | AGC_AUTO_ON = 0x08, 143 | TRIGGER_NONE = 0x00, 144 | TRIGGER_RSSI = 0x01, 145 | TRIGGER_PREAMBLE = 0x06, 146 | TRIGGER_ALL = 0x07, 147 | }; 148 | 149 | enum SX127xRxBw : uint8_t { 150 | RX_BW_2_6 = 0x17, 151 | RX_BW_3_1 = 0x0F, 152 | RX_BW_3_9 = 0x07, 153 | RX_BW_5_2 = 0x16, 154 | RX_BW_6_3 = 0x0E, 155 | RX_BW_7_8 = 0x06, 156 | RX_BW_10_4 = 0x15, 157 | RX_BW_12_5 = 0x0D, 158 | RX_BW_15_6 = 0x05, 159 | RX_BW_20_8 = 0x14, 160 | RX_BW_25_0 = 0x0C, 161 | RX_BW_31_3 = 0x04, 162 | RX_BW_41_7 = 0x13, 163 | RX_BW_50_0 = 0x0B, 164 | RX_BW_62_5 = 0x03, 165 | RX_BW_83_3 = 0x12, 166 | RX_BW_100_0 = 0x0A, 167 | RX_BW_125_0 = 0x02, 168 | RX_BW_166_7 = 0x11, 169 | RX_BW_200_0 = 0x09, 170 | RX_BW_250_0 = 0x01, 171 | }; 172 | 173 | enum SX127xOokPeak : uint8_t { 174 | BIT_SYNC_ON = 0x20, 175 | BIT_SYNC_OFF = 0x00, 176 | OOK_THRESH_AVG = 0x10, 177 | OOK_THRESH_PEAK = 0x08, 178 | OOK_THRESH_FIXED = 0x00, 179 | OOK_THRESH_STEP_6_0 = 0x07, 180 | OOK_THRESH_STEP_5_0 = 0x06, 181 | OOK_THRESH_STEP_4_0 = 0x05, 182 | OOK_THRESH_STEP_3_0 = 0x04, 183 | OOK_THRESH_STEP_2_0 = 0x03, 184 | OOK_THRESH_STEP_1_5 = 0x02, 185 | OOK_THRESH_STEP_1_0 = 0x01, 186 | OOK_THRESH_STEP_0_5 = 0x00, 187 | }; 188 | 189 | enum SX127xOokAvg : uint8_t { 190 | OOK_THRESH_DEC_16 = 0xE0, 191 | OOK_THRESH_DEC_8 = 0xC0, 192 | OOK_THRESH_DEC_4 = 0xA0, 193 | OOK_THRESH_DEC_2 = 0x80, 194 | OOK_THRESH_DEC_1_8 = 0x60, 195 | OOK_THRESH_DEC_1_4 = 0x40, 196 | OOK_THRESH_DEC_1_2 = 0x20, 197 | OOK_THRESH_DEC_1 = 0x00, 198 | OOK_AVG_RESERVED = 0x10, 199 | }; 200 | 201 | enum SX127xAfcFei : uint8_t { 202 | AFC_AUTO_CLEAR_ON = 0x01, 203 | }; 204 | 205 | enum SX127xPreambleDetect : uint8_t { 206 | PREAMBLE_DETECTOR_ON = 0x80, 207 | PREAMBLE_DETECTOR_OFF = 0x00, 208 | PREAMBLE_DETECTOR_SIZE_SHIFT = 5, 209 | PREAMBLE_DETECTOR_TOL_SHIFT = 0, 210 | }; 211 | 212 | enum SX127xSyncConfig : uint8_t { 213 | AUTO_RESTART_PLL_LOCK = 0x80, 214 | AUTO_RESTART_NO_LOCK = 0x40, 215 | AUTO_RESTART_OFF = 0x00, 216 | PREAMBLE_55 = 0x20, 217 | PREAMBLE_AA = 0x00, 218 | SYNC_ON = 0x10, 219 | SYNC_OFF = 0x00, 220 | }; 221 | 222 | enum SX127xPacketConfig1 : uint8_t { 223 | VARIABLE_LENGTH = 0x80, 224 | FIXED_LENGTH = 0x00, 225 | CRC_ON = 0x10, 226 | CRC_OFF = 0x00, 227 | }; 228 | 229 | enum SX127xPacketConfig2 : uint8_t { 230 | CONTINUOUS_MODE = 0x00, 231 | PACKET_MODE = 0x40, 232 | }; 233 | 234 | enum SX127xFifoThresh : uint8_t { 235 | TX_START_FIFO_EMPTY = 0x80, 236 | TX_START_FIFO_LEVEL = 0x00, 237 | }; 238 | 239 | enum SX127xImageCal : uint8_t { 240 | AUTO_IMAGE_CAL_ON = 0x80, 241 | IMAGE_CAL_START = 0x40, 242 | IMAGE_CAL_RUNNING = 0x20, 243 | TEMP_CHANGE = 0x08, 244 | TEMP_THRESHOLD_20C = 0x06, 245 | TEMP_THRESHOLD_15C = 0x04, 246 | TEMP_THRESHOLD_10C = 0x02, 247 | TEMP_THRESHOLD_5C = 0x00, 248 | TEMP_MONITOR_OFF = 0x01, 249 | TEMP_MONITOR_ON = 0x00, 250 | }; 251 | 252 | enum SX127xIrqFlags : uint8_t { 253 | RX_TIMEOUT = 0x80, 254 | RX_DONE = 0x40, 255 | PAYLOAD_CRC_ERROR = 0x20, 256 | VALID_HEADER = 0x10, 257 | TX_DONE = 0x08, 258 | CAD_DONE = 0x04, 259 | FHSS_CHANGE_CHANNEL = 0x02, 260 | CAD_DETECTED = 0x01, 261 | }; 262 | 263 | enum SX127xModemCfg1 : uint8_t { 264 | BW_7_8 = 0x00, 265 | BW_10_4 = 0x10, 266 | BW_15_6 = 0x20, 267 | BW_20_8 = 0x30, 268 | BW_31_3 = 0x40, 269 | BW_41_7 = 0x50, 270 | BW_62_5 = 0x60, 271 | BW_125_0 = 0x70, 272 | BW_250_0 = 0x80, 273 | BW_500_0 = 0x90, 274 | CODING_RATE_4_5 = 0x02, 275 | CODING_RATE_4_6 = 0x04, 276 | CODING_RATE_4_7 = 0x06, 277 | CODING_RATE_4_8 = 0x08, 278 | IMPLICIT_HEADER = 0x01, 279 | EXPLICIT_HEADER = 0x00, 280 | }; 281 | 282 | enum SX127xModemCfg2 : uint8_t { 283 | SPREADING_FACTOR_SHIFT = 4, 284 | TX_CONTINOUS_MODE = 0x08, 285 | RX_PAYLOAD_CRC_ON = 0x04, 286 | RX_PAYLOAD_CRC_OFF = 0x00, 287 | }; 288 | 289 | enum SX127xModemCfg3 : uint8_t { 290 | LOW_DATA_RATE_OPTIMIZE_ON = 0x08, 291 | MODEM_AGC_AUTO_ON = 0x04, 292 | }; 293 | 294 | } // namespace sx127x 295 | } // namespace esphome 296 | -------------------------------------------------------------------------------- /components/sx127x/__init__.py: -------------------------------------------------------------------------------- 1 | from esphome import automation, pins 2 | import esphome.codegen as cg 3 | from esphome.components import spi 4 | import esphome.config_validation as cv 5 | from esphome.const import CONF_DATA, CONF_FREQUENCY, CONF_ID 6 | 7 | MULTI_CONF = True 8 | CODEOWNERS = ["@swoboda1337"] 9 | DEPENDENCIES = ["spi"] 10 | 11 | CONF_SX127X_ID = "sx127x_id" 12 | 13 | CONF_AUTO_CAL = "auto_cal" 14 | CONF_BANDWIDTH = "bandwidth" 15 | CONF_BITRATE = "bitrate" 16 | CONF_BITSYNC = "bitsync" 17 | CONF_CODING_RATE = "coding_rate" 18 | CONF_CRC_ENABLE = "crc_enable" 19 | CONF_DEVIATION = "deviation" 20 | CONF_DIO0_PIN = "dio0_pin" 21 | CONF_MODULATION = "modulation" 22 | CONF_ON_PACKET = "on_packet" 23 | CONF_PA_PIN = "pa_pin" 24 | CONF_PA_POWER = "pa_power" 25 | CONF_PA_RAMP = "pa_ramp" 26 | CONF_PACKET_MODE = "packet_mode" 27 | CONF_PAYLOAD_LENGTH = "payload_length" 28 | CONF_PREAMBLE_DETECT = "preamble_detect" 29 | CONF_PREAMBLE_ERRORS = "preamble_errors" 30 | CONF_PREAMBLE_POLARITY = "preamble_polarity" 31 | CONF_PREAMBLE_SIZE = "preamble_size" 32 | CONF_RST_PIN = "rst_pin" 33 | CONF_RX_FLOOR = "rx_floor" 34 | CONF_RX_START = "rx_start" 35 | CONF_SHAPING = "shaping" 36 | CONF_SPREADING_FACTOR = "spreading_factor" 37 | CONF_SYNC_VALUE = "sync_value" 38 | 39 | sx127x_ns = cg.esphome_ns.namespace("sx127x") 40 | SX127x = sx127x_ns.class_("SX127x", cg.Component, spi.SPIDevice) 41 | SX127xListener = sx127x_ns.class_("SX127xListener") 42 | SX127xBw = sx127x_ns.enum("SX127xBw") 43 | SX127xOpMode = sx127x_ns.enum("SX127xOpMode") 44 | SX127xPaConfig = sx127x_ns.enum("SX127xPaConfig") 45 | SX127xPaRamp = sx127x_ns.enum("SX127xPaRamp") 46 | SX127xModemCfg1 = sx127x_ns.enum("SX127xModemCfg1") 47 | 48 | BW = { 49 | "2_6kHz": SX127xBw.SX127X_BW_2_6, 50 | "3_1kHz": SX127xBw.SX127X_BW_3_1, 51 | "3_9kHz": SX127xBw.SX127X_BW_3_9, 52 | "5_2kHz": SX127xBw.SX127X_BW_5_2, 53 | "6_3kHz": SX127xBw.SX127X_BW_6_3, 54 | "7_8kHz": SX127xBw.SX127X_BW_7_8, 55 | "10_4kHz": SX127xBw.SX127X_BW_10_4, 56 | "12_5kHz": SX127xBw.SX127X_BW_12_5, 57 | "15_6kHz": SX127xBw.SX127X_BW_15_6, 58 | "20_8kHz": SX127xBw.SX127X_BW_20_8, 59 | "25_0kHz": SX127xBw.SX127X_BW_25_0, 60 | "31_3kHz": SX127xBw.SX127X_BW_31_3, 61 | "41_7kHz": SX127xBw.SX127X_BW_41_7, 62 | "50_0kHz": SX127xBw.SX127X_BW_50_0, 63 | "62_5kHz": SX127xBw.SX127X_BW_62_5, 64 | "83_3kHz": SX127xBw.SX127X_BW_83_3, 65 | "100_0kHz": SX127xBw.SX127X_BW_100_0, 66 | "125_0kHz": SX127xBw.SX127X_BW_125_0, 67 | "166_7kHz": SX127xBw.SX127X_BW_166_7, 68 | "200_0kHz": SX127xBw.SX127X_BW_200_0, 69 | "250_0kHz": SX127xBw.SX127X_BW_250_0, 70 | "500_0kHz": SX127xBw.SX127X_BW_500_0, 71 | } 72 | 73 | CODING_RATE = { 74 | "CR_4_5": SX127xModemCfg1.CODING_RATE_4_5, 75 | "CR_4_6": SX127xModemCfg1.CODING_RATE_4_6, 76 | "CR_4_7": SX127xModemCfg1.CODING_RATE_4_7, 77 | "CR_4_8": SX127xModemCfg1.CODING_RATE_4_8, 78 | } 79 | 80 | MOD = { 81 | "LORA": SX127xOpMode.MOD_LORA, 82 | "FSK": SX127xOpMode.MOD_FSK, 83 | "OOK": SX127xOpMode.MOD_OOK, 84 | } 85 | 86 | PA_PIN = { 87 | "RFO": SX127xPaConfig.PA_PIN_RFO, 88 | "BOOST": SX127xPaConfig.PA_PIN_BOOST, 89 | } 90 | 91 | RAMP = { 92 | "10us": SX127xPaRamp.PA_RAMP_10, 93 | "12us": SX127xPaRamp.PA_RAMP_12, 94 | "15us": SX127xPaRamp.PA_RAMP_15, 95 | "20us": SX127xPaRamp.PA_RAMP_20, 96 | "25us": SX127xPaRamp.PA_RAMP_25, 97 | "31us": SX127xPaRamp.PA_RAMP_31, 98 | "40us": SX127xPaRamp.PA_RAMP_40, 99 | "50us": SX127xPaRamp.PA_RAMP_50, 100 | "62us": SX127xPaRamp.PA_RAMP_62, 101 | "100us": SX127xPaRamp.PA_RAMP_100, 102 | "125us": SX127xPaRamp.PA_RAMP_125, 103 | "250us": SX127xPaRamp.PA_RAMP_250, 104 | "500us": SX127xPaRamp.PA_RAMP_500, 105 | "1000us": SX127xPaRamp.PA_RAMP_1000, 106 | "2000us": SX127xPaRamp.PA_RAMP_2000, 107 | "3400us": SX127xPaRamp.PA_RAMP_3400, 108 | } 109 | 110 | SHAPING = { 111 | "CUTOFF_BR_X_2": SX127xPaRamp.CUTOFF_BR_X_2, 112 | "CUTOFF_BR_X_1": SX127xPaRamp.CUTOFF_BR_X_1, 113 | "GAUSSIAN_BT_0_3": SX127xPaRamp.GAUSSIAN_BT_0_3, 114 | "GAUSSIAN_BT_0_5": SX127xPaRamp.GAUSSIAN_BT_0_5, 115 | "GAUSSIAN_BT_1_0": SX127xPaRamp.GAUSSIAN_BT_1_0, 116 | "NONE": SX127xPaRamp.SHAPING_NONE, 117 | } 118 | 119 | RunImageCalAction = sx127x_ns.class_( 120 | "RunImageCalAction", automation.Action, cg.Parented.template(SX127x) 121 | ) 122 | SendPacketAction = sx127x_ns.class_( 123 | "SendPacketAction", automation.Action, cg.Parented.template(SX127x) 124 | ) 125 | SetModeTxAction = sx127x_ns.class_( 126 | "SetModeTxAction", automation.Action, cg.Parented.template(SX127x) 127 | ) 128 | SetModeRxAction = sx127x_ns.class_( 129 | "SetModeRxAction", automation.Action, cg.Parented.template(SX127x) 130 | ) 131 | SetModeSleepAction = sx127x_ns.class_( 132 | "SetModeSleepAction", automation.Action, cg.Parented.template(SX127x) 133 | ) 134 | SetModeStandbyAction = sx127x_ns.class_( 135 | "SetModeStandbyAction", automation.Action, cg.Parented.template(SX127x) 136 | ) 137 | 138 | 139 | def validate_raw_data(value): 140 | if isinstance(value, str): 141 | return value.encode("utf-8") 142 | if isinstance(value, list): 143 | return cv.Schema([cv.hex_uint8_t])(value) 144 | raise cv.Invalid( 145 | "data must either be a string wrapped in quotes or a list of bytes" 146 | ) 147 | 148 | 149 | def validate_config(config): 150 | if config[CONF_MODULATION] == "LORA": 151 | bws = [ 152 | "7_8kHz", 153 | "10_4kHz", 154 | "15_6kHz", 155 | "20_8kHz", 156 | "31_3kHz", 157 | "41_7kHz", 158 | "62_5kHz", 159 | "125_0kHz", 160 | "250_0kHz", 161 | "500_0kHz", 162 | ] 163 | if config[CONF_BANDWIDTH] not in bws: 164 | raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA") 165 | if CONF_DIO0_PIN not in config: 166 | raise cv.Invalid("Cannot use LoRa without dio0_pin") 167 | if 0 < config[CONF_PREAMBLE_SIZE] < 6: 168 | raise cv.Invalid("Minimum preamble size is 6 with LORA") 169 | if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0: 170 | raise cv.Invalid("Payload length must be set when spreading factor is 6") 171 | else: 172 | if config[CONF_BANDWIDTH] == "500_0kHz": 173 | raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is only available with LORA") 174 | if CONF_BITSYNC not in config: 175 | raise cv.Invalid("Config 'bitsync' required with FSK/OOK") 176 | if CONF_PACKET_MODE not in config: 177 | raise cv.Invalid("Config 'packet_mode' required with FSK/OOK") 178 | if config[CONF_PACKET_MODE] and CONF_DIO0_PIN not in config: 179 | raise cv.Invalid("Config 'dio0_pin' required in packet mode") 180 | if config[CONF_PAYLOAD_LENGTH] > 64: 181 | raise cv.Invalid("Payload length must be <= 64 with FSK/OOK") 182 | if config[CONF_PA_PIN] == "RFO" and config[CONF_PA_POWER] > 15: 183 | raise cv.Invalid("PA power must be <= 15 dbm when using the RFO pin") 184 | if config[CONF_PA_PIN] == "BOOST" and config[CONF_PA_POWER] < 2: 185 | raise cv.Invalid("PA power must be >= 2 dbm when using the BOOST pin") 186 | return config 187 | 188 | 189 | CONFIG_SCHEMA = ( 190 | cv.Schema( 191 | { 192 | cv.GenerateID(): cv.declare_id(SX127x), 193 | cv.Optional(CONF_AUTO_CAL, default=True): cv.boolean, 194 | cv.Optional(CONF_BANDWIDTH, default="125_0kHz"): cv.enum(BW), 195 | cv.Optional(CONF_BITRATE, default=4800): cv.int_range(min=500, max=300000), 196 | cv.Optional(CONF_BITSYNC): cv.boolean, 197 | cv.Optional(CONF_CODING_RATE, default="CR_4_5"): cv.enum(CODING_RATE), 198 | cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean, 199 | cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000), 200 | cv.Optional(CONF_DIO0_PIN): pins.internal_gpio_input_pin_schema, 201 | cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000), 202 | cv.Required(CONF_MODULATION): cv.enum(MOD), 203 | cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), 204 | cv.Optional(CONF_PA_PIN, default="BOOST"): cv.enum(PA_PIN), 205 | cv.Optional(CONF_PA_POWER, default=17): cv.int_range(min=0, max=17), 206 | cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP), 207 | cv.Optional(CONF_PACKET_MODE): cv.boolean, 208 | cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256), 209 | cv.Optional(CONF_PREAMBLE_DETECT, default=0): cv.int_range(min=0, max=3), 210 | cv.Optional(CONF_PREAMBLE_ERRORS, default=0): cv.int_range(min=0, max=31), 211 | cv.Optional(CONF_PREAMBLE_POLARITY, default=0xAA): cv.All( 212 | cv.hex_int, cv.one_of(0xAA, 0x55) 213 | ), 214 | cv.Optional(CONF_PREAMBLE_SIZE, default=0): cv.int_range(min=0, max=65535), 215 | cv.Required(CONF_RST_PIN): pins.internal_gpio_output_pin_schema, 216 | cv.Optional(CONF_RX_FLOOR, default=-94): cv.float_range(min=-128, max=-1), 217 | cv.Optional(CONF_RX_START, default=True): cv.boolean, 218 | cv.Optional(CONF_SHAPING, default="NONE"): cv.enum(SHAPING), 219 | cv.Optional(CONF_SPREADING_FACTOR, default=7): cv.int_range(min=6, max=12), 220 | cv.Optional(CONF_SYNC_VALUE, default=[]): cv.ensure_list(cv.hex_uint8_t), 221 | }, 222 | ) 223 | .extend(cv.COMPONENT_SCHEMA) 224 | .extend(spi.spi_device_schema(True, 8e6, "mode0")) 225 | .add_extra(validate_config) 226 | ) 227 | 228 | 229 | async def to_code(config): 230 | var = cg.new_Pvariable(config[CONF_ID]) 231 | await cg.register_component(var, config) 232 | await spi.register_spi_device(var, config) 233 | if CONF_ON_PACKET in config: 234 | await automation.build_automation( 235 | var.get_packet_trigger(), 236 | [ 237 | (cg.std_vector.template(cg.uint8), "x"), 238 | (cg.float_, "rssi"), 239 | (cg.float_, "snr"), 240 | ], 241 | config[CONF_ON_PACKET], 242 | ) 243 | if CONF_DIO0_PIN in config: 244 | dio0_pin = await cg.gpio_pin_expression(config[CONF_DIO0_PIN]) 245 | cg.add(var.set_dio0_pin(dio0_pin)) 246 | rst_pin = await cg.gpio_pin_expression(config[CONF_RST_PIN]) 247 | cg.add(var.set_rst_pin(rst_pin)) 248 | cg.add(var.set_auto_cal(config[CONF_AUTO_CAL])) 249 | cg.add(var.set_bandwidth(config[CONF_BANDWIDTH])) 250 | cg.add(var.set_frequency(config[CONF_FREQUENCY])) 251 | cg.add(var.set_deviation(config[CONF_DEVIATION])) 252 | cg.add(var.set_modulation(config[CONF_MODULATION])) 253 | if config[CONF_MODULATION] != "LORA": 254 | cg.add(var.set_bitrate(config[CONF_BITRATE])) 255 | cg.add(var.set_bitsync(config[CONF_BITSYNC])) 256 | cg.add(var.set_packet_mode(config[CONF_PACKET_MODE])) 257 | cg.add(var.set_pa_pin(config[CONF_PA_PIN])) 258 | cg.add(var.set_pa_ramp(config[CONF_PA_RAMP])) 259 | cg.add(var.set_pa_power(config[CONF_PA_POWER])) 260 | cg.add(var.set_shaping(config[CONF_SHAPING])) 261 | cg.add(var.set_crc_enable(config[CONF_CRC_ENABLE])) 262 | cg.add(var.set_payload_length(config[CONF_PAYLOAD_LENGTH])) 263 | cg.add(var.set_preamble_detect(config[CONF_PREAMBLE_DETECT])) 264 | cg.add(var.set_preamble_size(config[CONF_PREAMBLE_SIZE])) 265 | cg.add(var.set_preamble_polarity(config[CONF_PREAMBLE_POLARITY])) 266 | cg.add(var.set_preamble_errors(config[CONF_PREAMBLE_ERRORS])) 267 | cg.add(var.set_coding_rate(config[CONF_CODING_RATE])) 268 | cg.add(var.set_spreading_factor(config[CONF_SPREADING_FACTOR])) 269 | cg.add(var.set_sync_value(config[CONF_SYNC_VALUE])) 270 | cg.add(var.set_rx_floor(config[CONF_RX_FLOOR])) 271 | cg.add(var.set_rx_start(config[CONF_RX_START])) 272 | 273 | 274 | NO_ARGS_ACTION_SCHEMA = automation.maybe_simple_id( 275 | { 276 | cv.GenerateID(): cv.use_id(SX127x), 277 | } 278 | ) 279 | 280 | 281 | @automation.register_action( 282 | "sx127x.run_image_cal", RunImageCalAction, NO_ARGS_ACTION_SCHEMA 283 | ) 284 | @automation.register_action( 285 | "sx127x.set_mode_tx", SetModeTxAction, NO_ARGS_ACTION_SCHEMA 286 | ) 287 | @automation.register_action( 288 | "sx127x.set_mode_rx", SetModeRxAction, NO_ARGS_ACTION_SCHEMA 289 | ) 290 | @automation.register_action( 291 | "sx127x.set_mode_sleep", SetModeSleepAction, NO_ARGS_ACTION_SCHEMA 292 | ) 293 | @automation.register_action( 294 | "sx127x.set_mode_standby", SetModeStandbyAction, NO_ARGS_ACTION_SCHEMA 295 | ) 296 | async def no_args_action_to_code(config, action_id, template_arg, args): 297 | var = cg.new_Pvariable(action_id, template_arg) 298 | await cg.register_parented(var, config[CONF_ID]) 299 | return var 300 | 301 | 302 | SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value( 303 | { 304 | cv.GenerateID(): cv.use_id(SX127x), 305 | cv.Required(CONF_DATA): cv.templatable(validate_raw_data), 306 | }, 307 | key=CONF_DATA, 308 | ) 309 | 310 | 311 | @automation.register_action( 312 | "sx127x.send_packet", SendPacketAction, SEND_PACKET_ACTION_SCHEMA 313 | ) 314 | async def send_packet_action_to_code(config, action_id, template_arg, args): 315 | var = cg.new_Pvariable(action_id, template_arg) 316 | await cg.register_parented(var, config[CONF_ID]) 317 | data = config[CONF_DATA] 318 | if isinstance(data, bytes): 319 | data = list(data) 320 | if cg.is_template(data): 321 | templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) 322 | cg.add(var.set_data_template(templ)) 323 | else: 324 | cg.add(var.set_data_static(data)) 325 | return var 326 | -------------------------------------------------------------------------------- /components/sx127x/sx127x.cpp: -------------------------------------------------------------------------------- 1 | #include "sx127x.h" 2 | #include "esphome/core/hal.h" 3 | #include "esphome/core/log.h" 4 | 5 | namespace esphome { 6 | namespace sx127x { 7 | 8 | static const char *const TAG = "sx127x"; 9 | static const uint32_t FXOSC = 32000000u; 10 | static const uint16_t RAMP[16] = {3400, 2000, 1000, 500, 250, 125, 100, 62, 50, 40, 31, 25, 20, 15, 12, 10}; 11 | static const uint32_t BW_HZ[22] = {2604, 3125, 3906, 5208, 6250, 7812, 10416, 12500, 15625, 20833, 25000, 12 | 31250, 41666, 50000, 62500, 83333, 100000, 125000, 166666, 200000, 250000, 500000}; 13 | static const uint8_t BW_LORA[22] = {BW_7_8, BW_7_8, BW_7_8, BW_7_8, BW_7_8, BW_7_8, BW_10_4, BW_15_6, 14 | BW_15_6, BW_20_8, BW_31_3, BW_31_3, BW_41_7, BW_62_5, BW_62_5, BW_125_0, 15 | BW_125_0, BW_125_0, BW_250_0, BW_250_0, BW_250_0, BW_500_0}; 16 | static const uint8_t BW_FSK_OOK[22] = {RX_BW_2_6, RX_BW_3_1, RX_BW_3_9, RX_BW_5_2, RX_BW_6_3, RX_BW_7_8, 17 | RX_BW_10_4, RX_BW_12_5, RX_BW_15_6, RX_BW_20_8, RX_BW_25_0, RX_BW_31_3, 18 | RX_BW_41_7, RX_BW_50_0, RX_BW_62_5, RX_BW_83_3, RX_BW_100_0, RX_BW_125_0, 19 | RX_BW_166_7, RX_BW_200_0, RX_BW_250_0, RX_BW_250_0}; 20 | static const int32_t RSSI_OFFSET_HF = 157; 21 | static const int32_t RSSI_OFFSET_LF = 164; 22 | 23 | uint8_t SX127x::read_register_(uint8_t reg) { 24 | this->enable(); 25 | this->write_byte(reg & 0x7F); 26 | uint8_t value = this->read_byte(); 27 | this->disable(); 28 | return value; 29 | } 30 | 31 | void SX127x::write_register_(uint8_t reg, uint8_t value) { 32 | this->enable(); 33 | this->write_byte(reg | 0x80); 34 | this->write_byte(value); 35 | this->disable(); 36 | } 37 | 38 | void SX127x::read_fifo_(std::vector &packet) { 39 | this->enable(); 40 | this->write_byte(REG_FIFO & 0x7F); 41 | this->read_array(packet.data(), packet.size()); 42 | this->disable(); 43 | } 44 | 45 | void SX127x::write_fifo_(const std::vector &packet) { 46 | this->enable(); 47 | this->write_byte(REG_FIFO | 0x80); 48 | this->write_array(packet.data(), packet.size()); 49 | this->disable(); 50 | } 51 | 52 | void SX127x::setup() { 53 | ESP_LOGCONFIG(TAG, "Running setup"); 54 | 55 | // setup reset 56 | this->rst_pin_->setup(); 57 | 58 | // setup dio0 59 | if (this->dio0_pin_) { 60 | this->dio0_pin_->setup(); 61 | } 62 | 63 | // start spi 64 | this->spi_setup(); 65 | 66 | // configure rf 67 | this->configure(); 68 | } 69 | 70 | void SX127x::configure() { 71 | // toggle chip reset 72 | this->rst_pin_->digital_write(false); 73 | delayMicroseconds(1000); 74 | this->rst_pin_->digital_write(true); 75 | delayMicroseconds(10000); 76 | 77 | // check silicon version to make sure hw is ok 78 | if (this->read_register_(REG_VERSION) != 0x12) { 79 | this->mark_failed(); 80 | return; 81 | } 82 | 83 | // enter sleep mode 84 | this->set_mode_(MOD_FSK, MODE_SLEEP); 85 | 86 | // set freq 87 | uint64_t frf = ((uint64_t) this->frequency_ << 19) / FXOSC; 88 | this->write_register_(REG_FRF_MSB, (uint8_t) ((frf >> 16) & 0xFF)); 89 | this->write_register_(REG_FRF_MID, (uint8_t) ((frf >> 8) & 0xFF)); 90 | this->write_register_(REG_FRF_LSB, (uint8_t) ((frf >> 0) & 0xFF)); 91 | 92 | // enter standby mode 93 | this->set_mode_(MOD_FSK, MODE_STDBY); 94 | 95 | // run image cal 96 | this->run_image_cal(); 97 | 98 | // go back to sleep 99 | this->set_mode_sleep(); 100 | 101 | // config pa 102 | if (this->pa_pin_ == PA_PIN_BOOST) { 103 | this->pa_power_ = std::max(this->pa_power_, (uint8_t) 2); 104 | this->pa_power_ = std::min(this->pa_power_, (uint8_t) 17); 105 | this->write_register_(REG_PA_CONFIG, (this->pa_power_ - 2) | this->pa_pin_ | PA_MAX_POWER); 106 | } else { 107 | this->pa_power_ = std::min(this->pa_power_, (uint8_t) 14); 108 | this->write_register_(REG_PA_CONFIG, (this->pa_power_ - 0) | this->pa_pin_ | PA_MAX_POWER); 109 | } 110 | if (this->modulation_ != MOD_LORA) { 111 | this->write_register_(REG_PA_RAMP, this->pa_ramp_ | this->shaping_); 112 | } else { 113 | this->write_register_(REG_PA_RAMP, this->pa_ramp_); 114 | } 115 | 116 | // configure modem 117 | if (this->modulation_ != MOD_LORA) { 118 | this->configure_fsk_ook_(); 119 | } else { 120 | this->configure_lora_(); 121 | } 122 | 123 | // switch to rx or sleep 124 | if (this->rx_start_) { 125 | this->set_mode_rx(); 126 | } else { 127 | this->set_mode_sleep(); 128 | } 129 | } 130 | 131 | void SX127x::configure_fsk_ook_() { 132 | // set the channel bw 133 | this->write_register_(REG_RX_BW, BW_FSK_OOK[this->bandwidth_]); 134 | 135 | // set fdev 136 | uint32_t fdev = std::min((this->deviation_ * 4096) / 250000, (uint32_t) 0x3FFF); 137 | this->write_register_(REG_FDEV_MSB, (uint8_t) ((fdev >> 8) & 0xFF)); 138 | this->write_register_(REG_FDEV_LSB, (uint8_t) ((fdev >> 0) & 0xFF)); 139 | 140 | // set bitrate 141 | uint64_t bitrate = (FXOSC + this->bitrate_ / 2) / this->bitrate_; // round up 142 | this->write_register_(REG_BITRATE_MSB, (uint8_t) ((bitrate >> 8) & 0xFF)); 143 | this->write_register_(REG_BITRATE_LSB, (uint8_t) ((bitrate >> 0) & 0xFF)); 144 | 145 | // configure rx and afc 146 | uint8_t trigger = (this->preamble_detect_ > 0) ? TRIGGER_PREAMBLE : TRIGGER_RSSI; 147 | this->write_register_(REG_AFC_FEI, AFC_AUTO_CLEAR_ON); 148 | if (this->modulation_ == MOD_FSK) { 149 | this->write_register_(REG_RX_CONFIG, AFC_AUTO_ON | AGC_AUTO_ON | trigger); 150 | } else { 151 | this->write_register_(REG_RX_CONFIG, AGC_AUTO_ON | trigger); 152 | } 153 | 154 | // configure packet mode 155 | if (this->packet_mode_) { 156 | uint8_t crc_mode = (this->crc_enable_) ? CRC_ON : CRC_OFF; 157 | this->write_register_(REG_FIFO_THRESH, TX_START_FIFO_EMPTY); 158 | if (this->payload_length_ > 0) { 159 | this->write_register_(REG_PAYLOAD_LENGTH_LSB, this->payload_length_); 160 | this->write_register_(REG_PACKET_CONFIG_1, crc_mode | FIXED_LENGTH); 161 | } else { 162 | this->write_register_(REG_PAYLOAD_LENGTH_LSB, this->get_max_packet_size() - 1); 163 | this->write_register_(REG_PACKET_CONFIG_1, crc_mode | VARIABLE_LENGTH); 164 | } 165 | this->write_register_(REG_PACKET_CONFIG_2, PACKET_MODE); 166 | } else { 167 | this->write_register_(REG_PACKET_CONFIG_2, CONTINUOUS_MODE); 168 | } 169 | this->write_register_(REG_DIO_MAPPING1, DIO0_MAPPING_00); 170 | 171 | // config bit synchronizer 172 | uint8_t polarity = (this->preamble_polarity_ == 0xAA) ? PREAMBLE_AA : PREAMBLE_55; 173 | if (!this->sync_value_.empty()) { 174 | uint8_t size = this->sync_value_.size() - 1; 175 | this->write_register_(REG_SYNC_CONFIG, AUTO_RESTART_PLL_LOCK | polarity | SYNC_ON | size); 176 | for (uint32_t i = 0; i < this->sync_value_.size(); i++) { 177 | this->write_register_(REG_SYNC_VALUE1 + i, this->sync_value_[i]); 178 | } 179 | } else { 180 | this->write_register_(REG_SYNC_CONFIG, AUTO_RESTART_PLL_LOCK | polarity); 181 | } 182 | 183 | // config preamble detector 184 | if (this->preamble_detect_ > 0) { 185 | uint8_t size = (this->preamble_detect_ - 1) << PREAMBLE_DETECTOR_SIZE_SHIFT; 186 | uint8_t tol = this->preamble_errors_ << PREAMBLE_DETECTOR_TOL_SHIFT; 187 | this->write_register_(REG_PREAMBLE_DETECT, PREAMBLE_DETECTOR_ON | size | tol); 188 | } else { 189 | this->write_register_(REG_PREAMBLE_DETECT, PREAMBLE_DETECTOR_OFF); 190 | } 191 | this->write_register_(REG_PREAMBLE_SIZE_MSB, this->preamble_size_ >> 16); 192 | this->write_register_(REG_PREAMBLE_SIZE_LSB, this->preamble_size_ & 0xFF); 193 | 194 | // config sync generation and setup ook threshold 195 | uint8_t bitsync = this->bitsync_ ? BIT_SYNC_ON : BIT_SYNC_OFF; 196 | this->write_register_(REG_OOK_PEAK, bitsync | OOK_THRESH_STEP_0_5 | OOK_THRESH_PEAK); 197 | this->write_register_(REG_OOK_AVG, OOK_AVG_RESERVED | OOK_THRESH_DEC_1_8); 198 | 199 | // set rx floor 200 | this->write_register_(REG_OOK_FIX, 256 + int(this->rx_floor_ * 2.0)); 201 | this->write_register_(REG_RSSI_THRESH, std::abs(int(this->rx_floor_ * 2.0))); 202 | } 203 | 204 | void SX127x::configure_lora_() { 205 | // config modem 206 | uint8_t header_mode = this->payload_length_ > 0 ? IMPLICIT_HEADER : EXPLICIT_HEADER; 207 | uint8_t crc_mode = (this->crc_enable_) ? RX_PAYLOAD_CRC_ON : RX_PAYLOAD_CRC_OFF; 208 | uint8_t spreading_factor = this->spreading_factor_ << SPREADING_FACTOR_SHIFT; 209 | this->write_register_(REG_MODEM_CONFIG1, BW_LORA[this->bandwidth_] | this->coding_rate_ | header_mode); 210 | this->write_register_(REG_MODEM_CONFIG2, spreading_factor | crc_mode); 211 | 212 | // config fifo and payload length 213 | this->write_register_(REG_FIFO_TX_BASE_ADDR, 0x00); 214 | this->write_register_(REG_FIFO_RX_BASE_ADDR, 0x00); 215 | this->write_register_(REG_PAYLOAD_LENGTH, std::max(this->payload_length_, (uint32_t) 1)); 216 | 217 | // config preamble 218 | if (this->preamble_size_ >= 6) { 219 | this->write_register_(REG_PREAMBLE_LEN_MSB, this->preamble_size_ >> 16); 220 | this->write_register_(REG_PREAMBLE_LEN_LSB, this->preamble_size_ & 0xFF); 221 | } 222 | 223 | // optimize detection 224 | float duration = 1000.0f * std::pow(2, this->spreading_factor_) / BW_HZ[this->bandwidth_]; 225 | if (duration > 16) { 226 | this->write_register_(REG_MODEM_CONFIG3, MODEM_AGC_AUTO_ON | LOW_DATA_RATE_OPTIMIZE_ON); 227 | } else { 228 | this->write_register_(REG_MODEM_CONFIG3, MODEM_AGC_AUTO_ON); 229 | } 230 | if (this->spreading_factor_ == 6) { 231 | this->write_register_(REG_DETECT_OPTIMIZE, 0xC5); 232 | this->write_register_(REG_DETECT_THRESHOLD, 0x0C); 233 | } else { 234 | this->write_register_(REG_DETECT_OPTIMIZE, 0xC3); 235 | this->write_register_(REG_DETECT_THRESHOLD, 0x0A); 236 | } 237 | 238 | // config sync word 239 | if (!this->sync_value_.empty()) { 240 | this->write_register_(REG_SYNC_WORD, this->sync_value_[0]); 241 | } 242 | } 243 | 244 | size_t SX127x::get_max_packet_size() { 245 | if (this->payload_length_ > 0) { 246 | return this->payload_length_; 247 | } 248 | if (this->modulation_ == MOD_LORA) { 249 | return 256; 250 | } else { 251 | return 64; 252 | } 253 | } 254 | 255 | SX127xError SX127x::transmit_packet(const std::vector &packet) { 256 | if (this->payload_length_ > 0 && this->payload_length_ != packet.size()) { 257 | ESP_LOGE(TAG, "Packet size does not match config"); 258 | return SX127xError::INVALID_PARAMS; 259 | } 260 | if (packet.empty() || packet.size() > this->get_max_packet_size()) { 261 | ESP_LOGE(TAG, "Packet size out of range"); 262 | return SX127xError::INVALID_PARAMS; 263 | } 264 | 265 | SX127xError ret = SX127xError::NONE; 266 | if (this->modulation_ == MOD_LORA) { 267 | this->set_mode_standby(); 268 | if (this->payload_length_ == 0) { 269 | this->write_register_(REG_PAYLOAD_LENGTH, packet.size()); 270 | } 271 | this->write_register_(REG_IRQ_FLAGS, 0xFF); 272 | this->write_register_(REG_FIFO_ADDR_PTR, 0); 273 | this->write_fifo_(packet); 274 | this->set_mode_tx(); 275 | } else { 276 | this->set_mode_standby(); 277 | if (this->payload_length_ == 0) { 278 | this->write_register_(REG_FIFO, packet.size()); 279 | } 280 | this->write_fifo_(packet); 281 | this->set_mode_tx(); 282 | } 283 | 284 | // wait until transmit completes, typically the delay will be less than 100 ms 285 | uint32_t start = millis(); 286 | while (!this->dio0_pin_->digital_read()) { 287 | if (millis() - start > 4000) { 288 | ESP_LOGE(TAG, "Transmit packet failure"); 289 | ret = SX127xError::TIMEOUT; 290 | break; 291 | } 292 | } 293 | if (this->rx_start_) { 294 | this->set_mode_rx(); 295 | } else { 296 | this->set_mode_sleep(); 297 | } 298 | return ret; 299 | } 300 | 301 | void SX127x::call_listeners_(const std::vector &packet, float rssi, float snr) { 302 | for (auto &listener : this->listeners_) { 303 | listener->on_packet(packet, rssi, snr); 304 | } 305 | this->packet_trigger_->trigger(packet, rssi, snr); 306 | } 307 | 308 | void SX127x::loop() { 309 | if (this->dio0_pin_ == nullptr || !this->dio0_pin_->digital_read()) { 310 | return; 311 | } 312 | 313 | if (this->modulation_ == MOD_LORA) { 314 | uint8_t status = this->read_register_(REG_IRQ_FLAGS); 315 | this->write_register_(REG_IRQ_FLAGS, 0xFF); 316 | if ((status & PAYLOAD_CRC_ERROR) == 0) { 317 | uint8_t bytes = this->read_register_(REG_NB_RX_BYTES); 318 | uint8_t addr = this->read_register_(REG_FIFO_RX_CURR_ADDR); 319 | uint8_t rssi = this->read_register_(REG_PKT_RSSI_VALUE); 320 | int8_t snr = (int8_t) this->read_register_(REG_PKT_SNR_VALUE); 321 | this->packet_.resize(bytes); 322 | this->write_register_(REG_FIFO_ADDR_PTR, addr); 323 | this->read_fifo_(this->packet_); 324 | if (this->frequency_ > 700000000) { 325 | this->call_listeners_(this->packet_, (float) rssi - RSSI_OFFSET_HF, (float) snr / 4); 326 | } else { 327 | this->call_listeners_(this->packet_, (float) rssi - RSSI_OFFSET_LF, (float) snr / 4); 328 | } 329 | } 330 | } else if (this->packet_mode_) { 331 | uint8_t payload_length = this->payload_length_; 332 | if (payload_length == 0) { 333 | payload_length = this->read_register_(REG_FIFO); 334 | } 335 | this->packet_.resize(payload_length); 336 | this->read_fifo_(this->packet_); 337 | this->call_listeners_(this->packet_, 0.0f, 0.0f); 338 | } 339 | } 340 | 341 | void SX127x::run_image_cal() { 342 | if (this->modulation_ == MOD_LORA) { 343 | this->set_mode_(MOD_FSK, MODE_SLEEP); 344 | this->set_mode_(MOD_FSK, MODE_STDBY); 345 | } 346 | if (this->auto_cal_) { 347 | this->write_register_(REG_IMAGE_CAL, IMAGE_CAL_START | AUTO_IMAGE_CAL_ON | TEMP_THRESHOLD_10C); 348 | } else { 349 | this->write_register_(REG_IMAGE_CAL, IMAGE_CAL_START); 350 | } 351 | uint32_t start = millis(); 352 | while (this->read_register_(REG_IMAGE_CAL) & IMAGE_CAL_RUNNING) { 353 | if (millis() - start > 20) { 354 | ESP_LOGE(TAG, "Image cal failure"); 355 | this->mark_failed(); 356 | break; 357 | } 358 | } 359 | if (this->modulation_ == MOD_LORA) { 360 | this->set_mode_(this->modulation_, MODE_SLEEP); 361 | this->set_mode_(this->modulation_, MODE_STDBY); 362 | } 363 | } 364 | 365 | void SX127x::set_mode_(uint8_t modulation, uint8_t mode) { 366 | uint32_t start = millis(); 367 | this->write_register_(REG_OP_MODE, modulation | mode); 368 | while (true) { 369 | uint8_t curr = this->read_register_(REG_OP_MODE) & MODE_MASK; 370 | if ((curr == mode) || (mode == MODE_RX && curr == MODE_RX_FS)) { 371 | if (mode == MODE_SLEEP) { 372 | this->write_register_(REG_OP_MODE, modulation | mode); 373 | } 374 | break; 375 | } 376 | if (millis() - start > 20) { 377 | ESP_LOGE(TAG, "Set mode failure"); 378 | this->mark_failed(); 379 | break; 380 | } 381 | } 382 | } 383 | 384 | void SX127x::set_mode_rx() { 385 | this->set_mode_(this->modulation_, MODE_RX); 386 | if (this->modulation_ == MOD_LORA) { 387 | this->write_register_(REG_IRQ_FLAGS_MASK, 0x00); 388 | this->write_register_(REG_DIO_MAPPING1, DIO0_MAPPING_00); 389 | } 390 | } 391 | 392 | void SX127x::set_mode_tx() { 393 | this->set_mode_(this->modulation_, MODE_TX); 394 | if (this->modulation_ == MOD_LORA) { 395 | this->write_register_(REG_IRQ_FLAGS_MASK, 0x00); 396 | this->write_register_(REG_DIO_MAPPING1, DIO0_MAPPING_01); 397 | } 398 | } 399 | 400 | void SX127x::set_mode_standby() { this->set_mode_(this->modulation_, MODE_STDBY); } 401 | 402 | void SX127x::set_mode_sleep() { this->set_mode_(this->modulation_, MODE_SLEEP); } 403 | 404 | void SX127x::dump_config() { 405 | ESP_LOGCONFIG(TAG, "SX127x:"); 406 | LOG_PIN(" CS Pin: ", this->cs_); 407 | LOG_PIN(" RST Pin: ", this->rst_pin_); 408 | LOG_PIN(" DIO0 Pin: ", this->dio0_pin_); 409 | const char *pa_pin = "RFO"; 410 | if (this->pa_pin_ == PA_PIN_BOOST) { 411 | pa_pin = "BOOST"; 412 | } 413 | ESP_LOGCONFIG(TAG, 414 | " Auto Cal: %s\n" 415 | " Frequency: %" PRIu32 " Hz\n" 416 | " Bandwidth: %" PRIu32 " Hz\n" 417 | " PA Pin: %s\n" 418 | " PA Power: %" PRIu8 " dBm\n" 419 | " PA Ramp: %" PRIu16 " us", 420 | TRUEFALSE(this->auto_cal_), this->frequency_, BW_HZ[this->bandwidth_], pa_pin, this->pa_power_, 421 | RAMP[this->pa_ramp_]); 422 | if (this->modulation_ == MOD_FSK) { 423 | ESP_LOGCONFIG(TAG, " Deviation: %" PRIu32 " Hz", this->deviation_); 424 | } 425 | if (this->modulation_ == MOD_LORA) { 426 | const char *cr = "4/8"; 427 | if (this->coding_rate_ == CODING_RATE_4_5) { 428 | cr = "4/5"; 429 | } else if (this->coding_rate_ == CODING_RATE_4_6) { 430 | cr = "4/6"; 431 | } else if (this->coding_rate_ == CODING_RATE_4_7) { 432 | cr = "4/7"; 433 | } 434 | ESP_LOGCONFIG(TAG, 435 | " Modulation: LORA\n" 436 | " Preamble Size: %" PRIu16 "\n" 437 | " Spreading Factor: %" PRIu8 "\n" 438 | " Coding Rate: %s\n" 439 | " CRC Enable: %s", 440 | this->preamble_size_, this->spreading_factor_, cr, TRUEFALSE(this->crc_enable_)); 441 | if (this->payload_length_ > 0) { 442 | ESP_LOGCONFIG(TAG, " Payload Length: %" PRIu32, this->payload_length_); 443 | } 444 | if (!this->sync_value_.empty()) { 445 | ESP_LOGCONFIG(TAG, " Sync Value: 0x%02x", this->sync_value_[0]); 446 | } 447 | } else { 448 | const char *shaping = "NONE"; 449 | if (this->modulation_ == MOD_FSK) { 450 | if (this->shaping_ == GAUSSIAN_BT_0_3) { 451 | shaping = "GAUSSIAN_BT_0_3"; 452 | } else if (this->shaping_ == GAUSSIAN_BT_0_5) { 453 | shaping = "GAUSSIAN_BT_0_5"; 454 | } else if (this->shaping_ == GAUSSIAN_BT_1_0) { 455 | shaping = "GAUSSIAN_BT_1_0"; 456 | } 457 | } else { 458 | if (this->shaping_ == CUTOFF_BR_X_2) { 459 | shaping = "CUTOFF_BR_X_2"; 460 | } else if (this->shaping_ == CUTOFF_BR_X_1) { 461 | shaping = "CUTOFF_BR_X_1"; 462 | } 463 | } 464 | ESP_LOGCONFIG(TAG, 465 | " Shaping: %s\n" 466 | " Modulation: %s\n" 467 | " Bitrate: %" PRIu32 "b/s\n" 468 | " Bitsync: %s\n" 469 | " Rx Start: %s\n" 470 | " Rx Floor: %.1f dBm\n" 471 | " Packet Mode: %s", 472 | shaping, this->modulation_ == MOD_FSK ? "FSK" : "OOK", this->bitrate_, TRUEFALSE(this->bitsync_), 473 | TRUEFALSE(this->rx_start_), this->rx_floor_, TRUEFALSE(this->packet_mode_)); 474 | if (this->packet_mode_) { 475 | ESP_LOGCONFIG(TAG, " CRC Enable: %s", TRUEFALSE(this->crc_enable_)); 476 | } 477 | if (this->payload_length_ > 0) { 478 | ESP_LOGCONFIG(TAG, " Payload Length: %" PRIu32, this->payload_length_); 479 | } 480 | if (!this->sync_value_.empty()) { 481 | ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str()); 482 | } 483 | if (this->preamble_size_ > 0 || this->preamble_detect_ > 0) { 484 | ESP_LOGCONFIG(TAG, 485 | " Preamble Polarity: 0x%X\n" 486 | " Preamble Size: %" PRIu16 "\n" 487 | " Preamble Detect: %" PRIu8 "\n" 488 | " Preamble Errors: %" PRIu8, 489 | this->preamble_polarity_, this->preamble_size_, this->preamble_detect_, this->preamble_errors_); 490 | } 491 | } 492 | if (this->is_failed()) { 493 | ESP_LOGE(TAG, "Configuring SX127x failed"); 494 | } 495 | } 496 | 497 | } // namespace sx127x 498 | } // namespace esphome 499 | --------------------------------------------------------------------------------