diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 208faaf..4d85daf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,13 @@ { "python.analysis.extraPaths": [ "${workspaceFolder}", - "${workspaceFolder}/shared" + "${workspaceFolder}/shared", + "${workspaceFolder}/micro_ondes/esp_lora/lib" ], "python.autoComplete.extraPaths": [ "${workspaceFolder}", - "${workspaceFolder}/shared" + "${workspaceFolder}/shared", + "${workspaceFolder}/micro_ondes/esp_lora/lib" ], "python.defaultInterpreterPath": "${workspaceFolder}/venv/bin/python", "r.lsp.promptToInstall": false, diff --git a/README.md b/README.md index ea26785..c660c5b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ -`export PYTHONPATH=/home/pi/SmartWave` \ No newline at end of file +`export PYTHONPATH=/home/pi/SmartWave` + +`export PYTHONPATH=/home/ninluc/Documents/school/IoT/smartWave` \ No newline at end of file diff --git a/cloud/app.py b/cloud/app.py index e69de29..87b4b21 100644 --- a/cloud/app.py +++ b/cloud/app.py @@ -0,0 +1,7 @@ +from flask import Flask + +app = Flask(__name__) + +@app.route("/") +def hello_world(): + return "
Hello, World!
" \ No newline at end of file diff --git a/deploy.sh b/deploy.sh index 4196f84..0a36e7b 100755 --- a/deploy.sh +++ b/deploy.sh @@ -2,12 +2,12 @@ # Configuration des ports (À remplacer par tes propres chemins by-id) # Pour trouver tes chemins, branche tes ESP et tape : ls -l /dev/serial/by-id/ -PORT_ESP_WIFI="/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0" -PORT_ESP_LORA="/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0002-if00-port0" +PORT_ESP_WIFI="/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port1" +PORT_ESP_LORA="/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0" # Ajoute les autres si besoin... # Configuration Raspberry Pi -RPI_HOST="10.85.213.190" +RPI_HOST="192.168.50.1" RPI_USER="pi" RPI_DEST="/home/pi/SmartWave" RPI_SYSTEMD_SERVICE="smartwave.service" @@ -44,10 +44,10 @@ deploy_to_esp() { mpremote connect "$PORT" cp -r "$TARGET_DIR"/* : echo "📦 Copie des certificats MQTT..." - mpremote connect "$PORT" cp -r orchestrateur/mqtt/certs :certs + mpremote connect "$PORT" cp -r orchestrateur/mqtt/certs/ : echo "🔄 Redémarrage de l'ESP..." - mpremote connect "$PORT" soft-reset + mpremote connect "$PORT" reset echo "✅ [$NAME] mis à jour avec succès !" echo "" diff --git a/micro_ondes/README.md b/micro_ondes/README.md new file mode 100644 index 0000000..f59daff --- /dev/null +++ b/micro_ondes/README.md @@ -0,0 +1,5 @@ + +# LoRa + +`mpremote connect /dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0 repl` + diff --git a/micro_ondes/esp_lora/boot.py b/micro_ondes/esp_lora/boot.py new file mode 100644 index 0000000..ebf63d1 --- /dev/null +++ b/micro_ondes/esp_lora/boot.py @@ -0,0 +1,23 @@ +# This file is executed on every boot (including wake-boot from deepsleep) +#import esp +#esp.osdebug(None) +#import webrepl +#webrepl.start() + +from machine import Pin +import time +import os + +print("--- Démarrage de l'ESP32 (boot.py) ---") + +# On s'assure que la ligne d'alimentation Vext est allumée dès le boot +vext = Pin(36, Pin.OUT) +vext.value(0) # Allume l'alimentation écran/LoRa + +# On initialise le reset de l'OLED pour qu'il ne reste pas figé +oled_rst = Pin(21, Pin.OUT) +oled_rst.value(0) +time.sleep(0.05) +oled_rst.value(1) + +print("Matériel de base initialisé. Lancement de main.py...") \ No newline at end of file diff --git a/micro_ondes/esp_lora/device_id.txt b/micro_ondes/esp_lora/device_id.txt new file mode 100644 index 0000000..d8263ee --- /dev/null +++ b/micro_ondes/esp_lora/device_id.txt @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/micro_ondes/esp_lora/lib/_sx126x.py b/micro_ondes/esp_lora/lib/_sx126x.py new file mode 100644 index 0000000..d29ed2b --- /dev/null +++ b/micro_ondes/esp_lora/lib/_sx126x.py @@ -0,0 +1,413 @@ +from sys import implementation + +if implementation.name == 'micropython': + from utime import sleep_ms + +if implementation.name == 'circuitpython': + from time import sleep + def sleep_ms(ms): + sleep(ms/1000) + +def ASSERT(state): + assert state == ERR_NONE, ERROR[state] + +def yield_(): + sleep_ms(1) + +SX126X_FREQUENCY_STEP_SIZE = 0.9536743164 +SX126X_MAX_PACKET_LENGTH = const(255) +SX126X_CRYSTAL_FREQ = 32.0 +SX126X_DIV_EXPONENT = const(25) +SX126X_CMD_NOP = const(0x00) +SX126X_CMD_SET_SLEEP = const(0x84) +SX126X_CMD_SET_STANDBY = const(0x80) +SX126X_CMD_SET_FS = const(0xC1) +SX126X_CMD_SET_TX = const(0x83) +SX126X_CMD_SET_RX = const(0x82) +SX126X_CMD_STOP_TIMER_ON_PREAMBLE = const(0x9F) +SX126X_CMD_SET_RX_DUTY_CYCLE = const(0x94) +SX126X_CMD_SET_CAD = const(0xC5) +SX126X_CMD_SET_TX_CONTINUOUS_WAVE = const(0xD1) +SX126X_CMD_SET_TX_INFINITE_PREAMBLE = const(0xD2) +SX126X_CMD_SET_REGULATOR_MODE = const(0x96) +SX126X_CMD_CALIBRATE = const(0x89) +SX126X_CMD_CALIBRATE_IMAGE = const(0x98) +SX126X_CMD_SET_PA_CONFIG = const(0x95) +SX126X_CMD_SET_RX_TX_FALLBACK_MODE = const(0x93) +SX126X_CMD_WRITE_REGISTER = const(0x0D) +SX126X_CMD_READ_REGISTER = const(0x1D) +SX126X_CMD_WRITE_BUFFER = const(0x0E) +SX126X_CMD_READ_BUFFER = const(0x1E) +SX126X_CMD_SET_DIO_IRQ_PARAMS = const(0x08) +SX126X_CMD_GET_IRQ_STATUS = const(0x12) +SX126X_CMD_CLEAR_IRQ_STATUS = const(0x02) +SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL = const(0x9D) +SX126X_CMD_SET_DIO3_AS_TCXO_CTRL = const(0x97) +SX126X_CMD_SET_RF_FREQUENCY = const(0x86) +SX126X_CMD_SET_PACKET_TYPE = const(0x8A) +SX126X_CMD_GET_PACKET_TYPE = const(0x11) +SX126X_CMD_SET_TX_PARAMS = const(0x8E) +SX126X_CMD_SET_MODULATION_PARAMS = const(0x8B) +SX126X_CMD_SET_PACKET_PARAMS = const(0x8C) +SX126X_CMD_SET_CAD_PARAMS = const(0x88) +SX126X_CMD_SET_BUFFER_BASE_ADDRESS = const(0x8F) +SX126X_CMD_SET_LORA_SYMB_NUM_TIMEOUT = const(0x0A) +SX126X_CMD_GET_STATUS = const(0xC0) +SX126X_CMD_GET_RSSI_INST = const(0x15) +SX126X_CMD_GET_RX_BUFFER_STATUS = const(0x13) +SX126X_CMD_GET_PACKET_STATUS = const(0x14) +SX126X_CMD_GET_DEVICE_ERRORS = const(0x17) +SX126X_CMD_CLEAR_DEVICE_ERRORS = const(0x07) +SX126X_CMD_GET_STATS = const(0x10) +SX126X_CMD_RESET_STATS = const(0x00) +SX126X_REG_WHITENING_INITIAL_MSB = const(0x06B8) +SX126X_REG_WHITENING_INITIAL_LSB = const(0x06B9) +SX126X_REG_CRC_INITIAL_MSB = const(0x06BC) +SX126X_REG_CRC_INITIAL_LSB = const(0x06BD) +SX126X_REG_CRC_POLYNOMIAL_MSB = const(0x06BE) +SX126X_REG_CRC_POLYNOMIAL_LSB = const(0x06BF) +SX126X_REG_SYNC_WORD_0 = const(0x06C0) +SX126X_REG_SYNC_WORD_1 = const(0x06C1) +SX126X_REG_SYNC_WORD_2 = const(0x06C2) +SX126X_REG_SYNC_WORD_3 = const(0x06C3) +SX126X_REG_SYNC_WORD_4 = const(0x06C4) +SX126X_REG_SYNC_WORD_5 = const(0x06C5) +SX126X_REG_SYNC_WORD_6 = const(0x06C6) +SX126X_REG_SYNC_WORD_7 = const(0x06C7) +SX126X_REG_NODE_ADDRESS = const(0x06CD) +SX126X_REG_BROADCAST_ADDRESS = const(0x06CE) +SX126X_REG_LORA_SYNC_WORD_MSB = const(0x0740) +SX126X_REG_LORA_SYNC_WORD_LSB = const(0x0741) +SX126X_REG_RANDOM_NUMBER_0 = const(0x0819) +SX126X_REG_RANDOM_NUMBER_1 = const(0x081A) +SX126X_REG_RANDOM_NUMBER_2 = const(0x081B) +SX126X_REG_RANDOM_NUMBER_3 = const(0x081C) +SX126X_REG_RX_GAIN = const(0x08AC) +SX126X_REG_OCP_CONFIGURATION = const(0x08E7) +SX126X_REG_XTA_TRIM = const(0x0911) +SX126X_REG_XTB_TRIM = const(0x0912) +SX126X_REG_SENSITIVITY_CONFIG = const(0x0889) +SX126X_REG_TX_CLAMP_CONFIG = const(0x08D8) +SX126X_REG_RTC_STOP = const(0x0920) +SX126X_REG_RTC_EVENT = const(0x0944) +SX126X_REG_IQ_CONFIG = const(0x0736) +SX126X_REG_RX_GAIN_RETENTION_0 = const(0x029F) +SX126X_REG_RX_GAIN_RETENTION_1 = const(0x02A0) +SX126X_REG_RX_GAIN_RETENTION_2 = const(0x02A1) +SX126X_SLEEP_START_COLD = const(0b00000000) +SX126X_SLEEP_START_WARM = const(0b00000100) +SX126X_SLEEP_RTC_OFF = const(0b00000000) +SX126X_SLEEP_RTC_ON = const(0b00000001) +SX126X_STANDBY_RC = const(0x00) +SX126X_STANDBY_XOSC = const(0x01) +SX126X_RX_TIMEOUT_NONE = const(0x000000) +SX126X_RX_TIMEOUT_INF = const(0xFFFFFF) +SX126X_TX_TIMEOUT_NONE = const(0x000000) +SX126X_STOP_ON_PREAMBLE_OFF = const(0x00) +SX126X_STOP_ON_PREAMBLE_ON = const(0x01) +SX126X_REGULATOR_LDO = const(0x00) +SX126X_REGULATOR_DC_DC = const(0x01) +SX126X_CALIBRATE_IMAGE_OFF = const(0b00000000) +SX126X_CALIBRATE_IMAGE_ON = const(0b01000000) +SX126X_CALIBRATE_ADC_BULK_P_OFF = const(0b00000000) +SX126X_CALIBRATE_ADC_BULK_P_ON = const(0b00100000) +SX126X_CALIBRATE_ADC_BULK_N_OFF = const(0b00000000) +SX126X_CALIBRATE_ADC_BULK_N_ON = const(0b00010000) +SX126X_CALIBRATE_ADC_PULSE_OFF = const(0b00000000) +SX126X_CALIBRATE_ADC_PULSE_ON = const(0b00001000) +SX126X_CALIBRATE_PLL_OFF = const(0b00000000) +SX126X_CALIBRATE_PLL_ON = const(0b00000100) +SX126X_CALIBRATE_RC13M_OFF = const(0b00000000) +SX126X_CALIBRATE_RC13M_ON = const(0b00000010) +SX126X_CALIBRATE_RC64K_OFF = const(0b00000000) +SX126X_CALIBRATE_RC64K_ON = const(0b00000001) +SX126X_CALIBRATE_ALL = const(0b01111111) +SX126X_CAL_IMG_430_MHZ_1 = const(0x6B) +SX126X_CAL_IMG_430_MHZ_2 = const(0x6F) +SX126X_CAL_IMG_470_MHZ_1 = const(0x75) +SX126X_CAL_IMG_470_MHZ_2 = const(0x81) +SX126X_CAL_IMG_779_MHZ_1 = const(0xC1) +SX126X_CAL_IMG_779_MHZ_2 = const(0xC5) +SX126X_CAL_IMG_863_MHZ_1 = const(0xD7) +SX126X_CAL_IMG_863_MHZ_2 = const(0xDB) +SX126X_CAL_IMG_902_MHZ_1 = const(0xE1) +SX126X_CAL_IMG_902_MHZ_2 = const(0xE9) +SX126X_PA_CONFIG_HP_MAX = const(0x07) +SX126X_PA_CONFIG_PA_LUT = const(0x01) +SX126X_PA_CONFIG_SX1262_8 = const(0x00) +SX126X_RX_TX_FALLBACK_MODE_FS = const(0x40) +SX126X_RX_TX_FALLBACK_MODE_STDBY_XOSC = const(0x30) +SX126X_RX_TX_FALLBACK_MODE_STDBY_RC = const(0x20) +SX126X_IRQ_TIMEOUT = const(0b1000000000) +SX126X_IRQ_CAD_DETECTED = const(0b0100000000) +SX126X_IRQ_CAD_DONE = const(0b0010000000) +SX126X_IRQ_CRC_ERR = const(0b0001000000) +SX126X_IRQ_HEADER_ERR = const(0b0000100000) +SX126X_IRQ_HEADER_VALID = const(0b0000010000) +SX126X_IRQ_SYNC_WORD_VALID = const(0b0000001000) +SX126X_IRQ_PREAMBLE_DETECTED = const(0b0000000100) +SX126X_IRQ_RX_DONE = const(0b0000000010) +SX126X_IRQ_TX_DONE = const(0b0000000001) +SX126X_IRQ_ALL = const(0b1111111111) +SX126X_IRQ_NONE = const(0b0000000000) +SX126X_DIO2_AS_IRQ = const(0x00) +SX126X_DIO2_AS_RF_SWITCH = const(0x01) +SX126X_DIO3_OUTPUT_1_6 = const(0x00) +SX126X_DIO3_OUTPUT_1_7 = const(0x01) +SX126X_DIO3_OUTPUT_1_8 = const(0x02) +SX126X_DIO3_OUTPUT_2_2 = const(0x03) +SX126X_DIO3_OUTPUT_2_4 = const(0x04) +SX126X_DIO3_OUTPUT_2_7 = const(0x05) +SX126X_DIO3_OUTPUT_3_0 = const(0x06) +SX126X_DIO3_OUTPUT_3_3 = const(0x07) +SX126X_PACKET_TYPE_GFSK = const(0x00) +SX126X_PACKET_TYPE_LORA = const(0x01) +SX126X_PA_RAMP_10U = const(0x00) +SX126X_PA_RAMP_20U = const(0x01) +SX126X_PA_RAMP_40U = const(0x02) +SX126X_PA_RAMP_80U = const(0x03) +SX126X_PA_RAMP_200U = const(0x04) +SX126X_PA_RAMP_800U = const(0x05) +SX126X_PA_RAMP_1700U = const(0x06) +SX126X_PA_RAMP_3400U = const(0x07) +SX126X_GFSK_FILTER_NONE = const(0x00) +SX126X_GFSK_FILTER_GAUSS_0_3 = const(0x08) +SX126X_GFSK_FILTER_GAUSS_0_5 = const(0x09) +SX126X_GFSK_FILTER_GAUSS_0_7 = const(0x0A) +SX126X_GFSK_FILTER_GAUSS_1 = const(0x0B) +SX126X_GFSK_RX_BW_4_8 = const(0x1F) +SX126X_GFSK_RX_BW_5_8 = const(0x17) +SX126X_GFSK_RX_BW_7_3 = const(0x0F) +SX126X_GFSK_RX_BW_9_7 = const(0x1E) +SX126X_GFSK_RX_BW_11_7 = const(0x16) +SX126X_GFSK_RX_BW_14_6 = const(0x0E) +SX126X_GFSK_RX_BW_19_5 = const(0x1D) +SX126X_GFSK_RX_BW_23_4 = const(0x15) +SX126X_GFSK_RX_BW_29_3 = const(0x0D) +SX126X_GFSK_RX_BW_39_0 = const(0x1C) +SX126X_GFSK_RX_BW_46_9 = const(0x14) +SX126X_GFSK_RX_BW_58_6 = const(0x0C) +SX126X_GFSK_RX_BW_78_2 = const(0x1B) +SX126X_GFSK_RX_BW_93_8 = const(0x13) +SX126X_GFSK_RX_BW_117_3 = const(0x0B) +SX126X_GFSK_RX_BW_156_2 = const(0x1A) +SX126X_GFSK_RX_BW_187_2 = const(0x12) +SX126X_GFSK_RX_BW_234_3 = const(0x0A) +SX126X_GFSK_RX_BW_312_0 = const(0x19) +SX126X_GFSK_RX_BW_373_6 = const(0x11) +SX126X_GFSK_RX_BW_467_0 = const(0x09) +SX126X_LORA_BW_7_8 = const(0x00) +SX126X_LORA_BW_10_4 = const(0x08) +SX126X_LORA_BW_15_6 = const(0x01) +SX126X_LORA_BW_20_8 = const(0x09) +SX126X_LORA_BW_31_25 = const(0x02) +SX126X_LORA_BW_41_7 = const(0x0A) +SX126X_LORA_BW_62_5 = const(0x03) +SX126X_LORA_BW_125_0 = const(0x04) +SX126X_LORA_BW_250_0 = const(0x05) +SX126X_LORA_BW_500_0 = const(0x06) +SX126X_LORA_CR_4_5 = const(0x01) +SX126X_LORA_CR_4_6 = const(0x02) +SX126X_LORA_CR_4_7 = const(0x03) +SX126X_LORA_CR_4_8 = const(0x04) +SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF = const(0x00) +SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON = const(0x01) +SX126X_GFSK_PREAMBLE_DETECT_OFF = const(0x00) +SX126X_GFSK_PREAMBLE_DETECT_8 = const(0x04) +SX126X_GFSK_PREAMBLE_DETECT_16 = const(0x05) +SX126X_GFSK_PREAMBLE_DETECT_24 = const(0x06) +SX126X_GFSK_PREAMBLE_DETECT_32 = const(0x07) +SX126X_GFSK_ADDRESS_FILT_OFF = const(0x00) +SX126X_GFSK_ADDRESS_FILT_NODE = const(0x01) +SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST = const(0x02) +SX126X_GFSK_PACKET_FIXED = const(0x00) +SX126X_GFSK_PACKET_VARIABLE = const(0x01) +SX126X_GFSK_CRC_OFF = const(0x01) +SX126X_GFSK_CRC_1_BYTE = const(0x00) +SX126X_GFSK_CRC_2_BYTE = const(0x02) +SX126X_GFSK_CRC_1_BYTE_INV = const(0x04) +SX126X_GFSK_CRC_2_BYTE_INV = const(0x06) +SX126X_GFSK_WHITENING_OFF = const(0x00) +SX126X_GFSK_WHITENING_ON = const(0x01) +SX126X_LORA_HEADER_EXPLICIT = const(0x00) +SX126X_LORA_HEADER_IMPLICIT = const(0x01) +SX126X_LORA_CRC_OFF = const(0x00) +SX126X_LORA_CRC_ON = const(0x01) +SX126X_LORA_IQ_STANDARD = const(0x00) +SX126X_LORA_IQ_INVERTED = const(0x01) +SX126X_CAD_ON_1_SYMB = const(0x00) +SX126X_CAD_ON_2_SYMB = const(0x01) +SX126X_CAD_ON_4_SYMB = const(0x02) +SX126X_CAD_ON_8_SYMB = const(0x03) +SX126X_CAD_ON_16_SYMB = const(0x04) +SX126X_CAD_GOTO_STDBY = const(0x00) +SX126X_CAD_GOTO_RX = const(0x01) +SX126X_STATUS_MODE_STDBY_RC = const(0b00100000) +SX126X_STATUS_MODE_STDBY_XOSC = const(0b00110000) +SX126X_STATUS_MODE_FS = const(0b01000000) +SX126X_STATUS_MODE_RX = const(0b01010000) +SX126X_STATUS_MODE_TX = const(0b01100000) +SX126X_STATUS_DATA_AVAILABLE = const(0b00000100) +SX126X_STATUS_CMD_TIMEOUT = const(0b00000110) +SX126X_STATUS_CMD_INVALID = const(0b00001000) +SX126X_STATUS_CMD_FAILED = const(0b00001010) +SX126X_STATUS_TX_DONE = const(0b00001100) +SX126X_STATUS_SPI_FAILED = const(0b11111111) +SX126X_GFSK_RX_STATUS_PREAMBLE_ERR = const(0b10000000) +SX126X_GFSK_RX_STATUS_SYNC_ERR = const(0b01000000) +SX126X_GFSK_RX_STATUS_ADRS_ERR = const(0b00100000) +SX126X_GFSK_RX_STATUS_CRC_ERR = const(0b00010000) +SX126X_GFSK_RX_STATUS_LENGTH_ERR = const(0b00001000) +SX126X_GFSK_RX_STATUS_ABORT_ERR = const(0b00000100) +SX126X_GFSK_RX_STATUS_PACKET_RECEIVED = const(0b00000010) +SX126X_GFSK_RX_STATUS_PACKET_SENT = const(0b00000001) +SX126X_PA_RAMP_ERR = const(0b100000000) +SX126X_PLL_LOCK_ERR = const(0b001000000) +SX126X_XOSC_START_ERR = const(0b000100000) +SX126X_IMG_CALIB_ERR = const(0b000010000) +SX126X_ADC_CALIB_ERR = const(0b000001000) +SX126X_PLL_CALIB_ERR = const(0b000000100) +SX126X_RC13M_CALIB_ERR = const(0b000000010) +SX126X_RC64K_CALIB_ERR = const(0b000000001) +SX126X_SYNC_WORD_PUBLIC = const(0x34) +SX126X_SYNC_WORD_PRIVATE = const(0x12) + +ERR_NONE = const(0) +ERR_UNKNOWN = const(-1) +ERR_CHIP_NOT_FOUND = const(-2) +ERR_MEMORY_ALLOCATION_FAILED = const(-3) +ERR_PACKET_TOO_LONG = const(-4) +ERR_TX_TIMEOUT = const(-5) +ERR_RX_TIMEOUT = const(-6) +ERR_CRC_MISMATCH = const(-7) +ERR_INVALID_BANDWIDTH = const(-8) +ERR_INVALID_SPREADING_FACTOR = const(-9) +ERR_INVALID_CODING_RATE = const(-10) +ERR_INVALID_BIT_RANGE = const(-11) +ERR_INVALID_FREQUENCY = const(-12) +ERR_INVALID_OUTPUT_POWER = const(-13) +PREAMBLE_DETECTED = const(-14) +CHANNEL_FREE = const(-15) +ERR_SPI_WRITE_FAILED = const(-16) +ERR_INVALID_CURRENT_LIMIT = const(-17) +ERR_INVALID_PREAMBLE_LENGTH = const(-18) +ERR_INVALID_GAIN = const(-19) +ERR_WRONG_MODEM = const(-20) +ERR_INVALID_NUM_SAMPLES = const(-21) +ERR_INVALID_RSSI_OFFSET = const(-22) +ERR_INVALID_ENCODING = const(-23) +ERR_INVALID_BIT_RATE = const(-101) +ERR_INVALID_FREQUENCY_DEVIATION = const(-102) +ERR_INVALID_BIT_RATE_BW_RATIO = const(-103) +ERR_INVALID_RX_BANDWIDTH = const(-104) +ERR_INVALID_SYNC_WORD = const(-105) +ERR_INVALID_DATA_SHAPING = const(-106) +ERR_INVALID_MODULATION = const(-107) +ERR_AT_FAILED = const(-201) +ERR_URL_MALFORMED = const(-202) +ERR_RESPONSE_MALFORMED_AT = const(-203) +ERR_RESPONSE_MALFORMED = const(-204) +ERR_MQTT_CONN_VERSION_REJECTED = const(-205) +ERR_MQTT_CONN_ID_REJECTED = const(-206) +ERR_MQTT_CONN_SERVER_UNAVAILABLE = const(-207) +ERR_MQTT_CONN_BAD_USERNAME_PASSWORD = const(-208) +ERR_MQTT_CONN_NOT_AUTHORIZED = const(-208) +ERR_MQTT_UNEXPECTED_PACKET_ID = const(-209) +ERR_MQTT_NO_NEW_PACKET_AVAILABLE = const(-210) +ERR_CMD_MODE_FAILED = const(-301) +ERR_FRAME_MALFORMED = const(-302) +ERR_FRAME_INCORRECT_CHECKSUM = const(-303) +ERR_FRAME_UNEXPECTED_ID = const(-304) +ERR_FRAME_NO_RESPONSE = const(-305) +ERR_INVALID_RTTY_SHIFT = const(-401) +ERR_UNSUPPORTED_ENCODING = const(-402) +ERR_INVALID_DATA_RATE = const(-501) +ERR_INVALID_ADDRESS_WIDTH = const(-502) +ERR_INVALID_PIPE_NUMBER = const(-503) +ERR_ACK_NOT_RECEIVED = const(-504) +ERR_INVALID_NUM_BROAD_ADDRS = const(-601) +ERR_INVALID_CRC_CONFIGURATION = const(-701) +LORA_DETECTED = const(-702) +ERR_INVALID_TCXO_VOLTAGE = const(-703) +ERR_INVALID_MODULATION_PARAMETERS = const(-704) +ERR_SPI_CMD_TIMEOUT = const(-705) +ERR_SPI_CMD_INVALID = const(-706) +ERR_SPI_CMD_FAILED = const(-707) +ERR_INVALID_SLEEP_PERIOD = const(-708) +ERR_INVALID_RX_PERIOD = const(-709) +ERR_INVALID_CALLSIGN = const(-801) +ERR_INVALID_NUM_REPEATERS = const(-802) +ERR_INVALID_REPEATER_CALLSIGN = const(-803) +ERR_INVALID_PACKET_TYPE = const(-804) +ERR_INVALID_PACKET_LENGTH = const(-805) + +ERROR = { + 0: 'ERR_NONE', + -1: 'ERR_UNKNOWN', + -2: 'ERR_CHIP_NOT_FOUND', + -3: 'ERR_MEMORY_ALLOCATION_FAILED', + -4: 'ERR_PACKET_TOO_LONG', + -5: 'ERR_TX_TIMEOUT', + -6: 'ERR_RX_TIMEOUT', + -7: 'ERR_CRC_MISMATCH', + -8: 'ERR_INVALID_BANDWIDTH', + -9: 'ERR_INVALID_SPREADING_FACTOR', + -10: 'ERR_INVALID_CODING_RATE', + -11: 'ERR_INVALID_BIT_RANGE', + -12: 'ERR_INVALID_FREQUENCY', + -13: 'ERR_INVALID_OUTPUT_POWER', + -14: 'PREAMBLE_DETECTED', + -15: 'CHANNEL_FREE', + -16: 'ERR_SPI_WRITE_FAILED', + -17: 'ERR_INVALID_CURRENT_LIMIT', + -18: 'ERR_INVALID_PREAMBLE_LENGTH', + -19: 'ERR_INVALID_GAIN', + -20: 'ERR_WRONG_MODEM', + -21: 'ERR_INVALID_NUM_SAMPLES', + -22: 'ERR_INVALID_RSSI_OFFSET', + -23: 'ERR_INVALID_ENCODING', + -101: 'ERR_INVALID_BIT_RATE', + -102: 'ERR_INVALID_FREQUENCY_DEVIATION', + -103: 'ERR_INVALID_BIT_RATE_BW_RATIO', + -104: 'ERR_INVALID_RX_BANDWIDTH', + -105: 'ERR_INVALID_SYNC_WORD', + -106: 'ERR_INVALID_DATA_SHAPING', + -107: 'ERR_INVALID_MODULATION', + -201: 'ERR_AT_FAILED', + -202: 'ERR_URL_MALFORMED', + -203: 'ERR_RESPONSE_MALFORMED_AT', + -204: 'ERR_RESPONSE_MALFORMED', + -205: 'ERR_MQTT_CONN_VERSION_REJECTED', + -206: 'ERR_MQTT_CONN_ID_REJECTED', + -207: 'ERR_MQTT_CONN_SERVER_UNAVAILABLE', + -208: 'ERR_MQTT_CONN_BAD_USERNAME_PASSWORD', + -208: 'ERR_MQTT_CONN_NOT_AUTHORIZED', + -209: 'ERR_MQTT_UNEXPECTED_PACKET_ID', + -210: 'ERR_MQTT_NO_NEW_PACKET_AVAILABLE', + -301: 'ERR_CMD_MODE_FAILED', + -302: 'ERR_FRAME_MALFORMED', + -303: 'ERR_FRAME_INCORRECT_CHECKSUM', + -304: 'ERR_FRAME_UNEXPECTED_ID', + -305: 'ERR_FRAME_NO_RESPONSE', + -401: 'ERR_INVALID_RTTY_SHIFT', + -402: 'ERR_UNSUPPORTED_ENCODING', + -501: 'ERR_INVALID_DATA_RATE', + -502: 'ERR_INVALID_ADDRESS_WIDTH', + -503: 'ERR_INVALID_PIPE_NUMBER', + -504: 'ERR_ACK_NOT_RECEIVED', + -601: 'ERR_INVALID_NUM_BROAD_ADDRS', + -701: 'ERR_INVALID_CRC_CONFIGURATION', + -702: 'LORA_DETECTED', + -703: 'ERR_INVALID_TCXO_VOLTAGE', + -704: 'ERR_INVALID_MODULATION_PARAMETERS', + -705: 'ERR_SPI_CMD_TIMEOUT', + -706: 'ERR_SPI_CMD_INVALID', + -707: 'ERR_SPI_CMD_FAILED', + -708: 'ERR_INVALID_SLEEP_PERIOD', + -709: 'ERR_INVALID_RX_PERIOD', + -801: 'ERR_INVALID_CALLSIGN', + -802: 'ERR_INVALID_NUM_REPEATERS', + -803: 'ERR_INVALID_REPEATER_CALLSIGN', + -804: 'ERR_INVALID_PACKET_TYPE', + -805: 'ERR_INVALID_PACKET_LENGTH' + } diff --git a/micro_ondes/esp_lora/lib/ssd1306.py b/micro_ondes/esp_lora/lib/ssd1306.py new file mode 100644 index 0000000..6359c85 --- /dev/null +++ b/micro_ondes/esp_lora/lib/ssd1306.py @@ -0,0 +1,155 @@ +# MicroPython SSD1306 OLED driver, I2C and SPI interfaces + +from micropython import const +import framebuf + + +# register definitions +SET_CONTRAST = const(0x81) +SET_ENTIRE_ON = const(0xA4) +SET_NORM_INV = const(0xA6) +SET_DISP = const(0xAE) +SET_MEM_ADDR = const(0x20) +SET_COL_ADDR = const(0x21) +SET_PAGE_ADDR = const(0x22) +SET_DISP_START_LINE = const(0x40) +SET_SEG_REMAP = const(0xA0) +SET_MUX_RATIO = const(0xA8) +SET_COM_OUT_DIR = const(0xC0) +SET_DISP_OFFSET = const(0xD3) +SET_COM_PIN_CFG = const(0xDA) +SET_DISP_CLK_DIV = const(0xD5) +SET_PRECHARGE = const(0xD9) +SET_VCOM_DESEL = const(0xDB) +SET_CHARGE_PUMP = const(0x8D) + +# Subclassing FrameBuffer provides support for graphics primitives +# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html +class SSD1306(framebuf.FrameBuffer): + def __init__(self, width, height, external_vcc): + self.width = width + self.height = height + self.external_vcc = external_vcc + self.pages = self.height // 8 + self.buffer = bytearray(self.pages * self.width) + super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) + self.init_display() + + def init_display(self): + for cmd in ( + SET_DISP | 0x00, # off + # address setting + SET_MEM_ADDR, + 0x00, # horizontal + # resolution and layout + SET_DISP_START_LINE | 0x00, + SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 + SET_MUX_RATIO, + self.height - 1, + SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 + SET_DISP_OFFSET, + 0x00, + SET_COM_PIN_CFG, + 0x02 if self.width > 2 * self.height else 0x12, + # timing and driving scheme + SET_DISP_CLK_DIV, + 0x80, + SET_PRECHARGE, + 0x22 if self.external_vcc else 0xF1, + SET_VCOM_DESEL, + 0x30, # 0.83*Vcc + # display + SET_CONTRAST, + 0xFF, # maximum + SET_ENTIRE_ON, # output follows RAM contents + SET_NORM_INV, # not inverted + # charge pump + SET_CHARGE_PUMP, + 0x10 if self.external_vcc else 0x14, + SET_DISP | 0x01, + ): # on + self.write_cmd(cmd) + self.fill(0) + self.show() + + def poweroff(self): + self.write_cmd(SET_DISP | 0x00) + + def poweron(self): + self.write_cmd(SET_DISP | 0x01) + + def contrast(self, contrast): + self.write_cmd(SET_CONTRAST) + self.write_cmd(contrast) + + def invert(self, invert): + self.write_cmd(SET_NORM_INV | (invert & 1)) + + def show(self): + x0 = 0 + x1 = self.width - 1 + if self.width == 64: + # displays with width of 64 pixels are shifted by 32 + x0 += 32 + x1 += 32 + self.write_cmd(SET_COL_ADDR) + self.write_cmd(x0) + self.write_cmd(x1) + self.write_cmd(SET_PAGE_ADDR) + self.write_cmd(0) + self.write_cmd(self.pages - 1) + self.write_data(self.buffer) + + +class SSD1306_I2C(SSD1306): + def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): + self.i2c = i2c + self.addr = addr + self.temp = bytearray(2) + self.write_list = [b"\x40", None] # Co=0, D/C#=1 + super().__init__(width, height, external_vcc) + + def write_cmd(self, cmd): + self.temp[0] = 0x80 # Co=1, D/C#=0 + self.temp[1] = cmd + self.i2c.writeto(self.addr, self.temp) + + def write_data(self, buf): + self.write_list[1] = buf + self.i2c.writevto(self.addr, self.write_list) + + +class SSD1306_SPI(SSD1306): + def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): + self.rate = 10 * 1024 * 1024 + dc.init(dc.OUT, value=0) + res.init(res.OUT, value=0) + cs.init(cs.OUT, value=1) + self.spi = spi + self.dc = dc + self.res = res + self.cs = cs + import time + + self.res(1) + time.sleep_ms(1) + self.res(0) + time.sleep_ms(10) + self.res(1) + super().__init__(width, height, external_vcc) + + def write_cmd(self, cmd): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs(1) + self.dc(0) + self.cs(0) + self.spi.write(bytearray([cmd])) + self.cs(1) + + def write_data(self, buf): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs(1) + self.dc(1) + self.cs(0) + self.spi.write(buf) + self.cs(1) diff --git a/micro_ondes/esp_lora/lib/sx1261.py b/micro_ondes/esp_lora/lib/sx1261.py new file mode 100644 index 0000000..e6893b0 --- /dev/null +++ b/micro_ondes/esp_lora/lib/sx1261.py @@ -0,0 +1,273 @@ +from _sx126x import * +from sx126x import SX126X + +_SX126X_PA_CONFIG_SX1261 = const(0x01) + +class SX1261(SX126X): + TX_DONE = SX126X_IRQ_TX_DONE + RX_DONE = SX126X_IRQ_RX_DONE + ADDR_FILT_OFF = SX126X_GFSK_ADDRESS_FILT_OFF + ADDR_FILT_NODE = SX126X_GFSK_ADDRESS_FILT_NODE + ADDR_FILT_NODE_BROAD = SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST + PREAMBLE_DETECT_OFF = SX126X_GFSK_PREAMBLE_DETECT_OFF + PREAMBLE_DETECT_8 = SX126X_GFSK_PREAMBLE_DETECT_8 + PREAMBLE_DETECT_16 = SX126X_GFSK_PREAMBLE_DETECT_16 + PREAMBLE_DETECT_24 = SX126X_GFSK_PREAMBLE_DETECT_24 + PREAMBLE_DETECT_32 = SX126X_GFSK_PREAMBLE_DETECT_32 + STATUS = ERROR + + def __init__(self, spi_bus, clk, mosi, miso, cs, irq, rst, gpio): + super().__init__(spi_bus, clk, mosi, miso, cs, irq, rst, gpio) + self._callbackFunction = self._dummyFunction + + def begin(self, freq=434.0, bw=125.0, sf=9, cr=7, syncWord=SX126X_SYNC_WORD_PRIVATE, + power=14, currentLimit=60.0, preambleLength=8, implicit=False, implicitLen=0xFF, + crcOn=True, txIq=False, rxIq=False, tcxoVoltage=1.6, useRegulatorLDO=False, + blocking=True): + state = super().begin(bw, sf, cr, syncWord, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO, txIq, rxIq) + ASSERT(state) + + if not implicit: + state = super().explicitHeader() + else: + state = super().implicitHeader(implicitLen) + ASSERT(state) + + state = super().setCRC(crcOn) + ASSERT(state) + + state = self.setFrequency(freq) + ASSERT(state) + + state = self.setOutputPower(power) + ASSERT(state) + + state = super().fixPaClamping() + ASSERT(state) + + state = self.setBlockingCallback(blocking) + + return state + + def beginFSK(self, freq=434.0, br=48.0, freqDev=50.0, rxBw=156.2, power=14, currentLimit=60.0, + preambleLength=16, dataShaping=0.5, syncWord=[0x2D, 0x01], syncBitsLength=16, + addrFilter=SX126X_GFSK_ADDRESS_FILT_OFF, addr=0x00, crcLength=2, crcInitial=0x1D0F, crcPolynomial=0x1021, + crcInverted=True, whiteningOn=True, whiteningInitial=0x0100, + fixedPacketLength=False, packetLength=0xFF, preambleDetectorLength=SX126X_GFSK_PREAMBLE_DETECT_16, + tcxoVoltage=1.6, useRegulatorLDO=False, + blocking=True): + state = super().beginFSK(br, freqDev, rxBw, currentLimit, preambleLength, dataShaping, preambleDetectorLength, tcxoVoltage, useRegulatorLDO) + ASSERT(state) + + state = super().setSyncBits(syncWord, syncBitsLength) + ASSERT(state) + + if addrFilter == SX126X_GFSK_ADDRESS_FILT_OFF: + state = super().disableAddressFiltering() + elif addrFilter == SX126X_GFSK_ADDRESS_FILT_NODE: + state = super().setNodeAddress(addr) + elif addrFilter == SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST: + state = super().setBroadcastAddress(addr) + else: + state = ERR_UNKNOWN + ASSERT(state) + + state = super().setCRC(crcLength, crcInitial, crcPolynomial, crcInverted) + ASSERT(state) + + state = super().setWhitening(whiteningOn, whiteningInitial) + ASSERT(state) + + if fixedPacketLength: + state = super().fixedPacketLengthMode(packetLength) + else: + state = super().variablePacketLengthMode(packetLength) + ASSERT(state) + + state = self.setFrequency(freq) + ASSERT(state) + + state = self.setOutputPower(power) + ASSERT(state) + + state = super().fixPaClamping() + ASSERT(state) + + state = self.setBlockingCallback(blocking) + + return state + + def setFrequency(self, freq, calibrate=True): + if freq < 150.0 or freq > 960.0: + return ERR_INVALID_FREQUENCY + + state = ERR_NONE + + if calibrate: + data = bytearray(2) + if freq > 900.0: + data[0] = SX126X_CAL_IMG_902_MHZ_1 + data[1] = SX126X_CAL_IMG_902_MHZ_2 + elif freq > 850.0: + data[0] = SX126X_CAL_IMG_863_MHZ_1 + data[1] = SX126X_CAL_IMG_863_MHZ_2 + elif freq > 770.0: + data[0] = SX126X_CAL_IMG_779_MHZ_1 + data[1] = SX126X_CAL_IMG_779_MHZ_2 + elif freq > 460.0: + data[0] = SX126X_CAL_IMG_470_MHZ_1 + data[1] = SX126X_CAL_IMG_470_MHZ_2 + else: + data[0] = SX126X_CAL_IMG_430_MHZ_1 + data[1] = SX126X_CAL_IMG_430_MHZ_2 + state = super().calibrateImage(data) + ASSERT(state) + + return super().setFrequencyRaw(freq) + + def setOutputPower(self, power): + if not ((power >= -17) and (power <= 14)): + return ERR_INVALID_OUTPUT_POWER + + ocp = bytearray(1) + ocp_mv = memoryview(ocp) + state = super().readRegister(SX126X_REG_OCP_CONFIGURATION, ocp_mv, 1) + ASSERT(state) + + state = super().setPaConfig(0x04, _SX126X_PA_CONFIG_SX1261, 0x00) + ASSERT(state) + + state = super().setTxParams(power) + ASSERT(state) + + return super().writeRegister(SX126X_REG_OCP_CONFIGURATION, ocp, 1) + + def setTxIq(self, txIq): + self._txIq = txIq + + def setRxIq(self, rxIq): + self._rxIq = rxIq + if not self.blocking: + ASSERT(super().startReceive()) + + def setPreambleDetectorLength(self, preambleDetectorLength): + self._preambleDetectorLength = preambleDetectorLength + if not self.blocking: + ASSERT(super().startReceive()) + + def setBlockingCallback(self, blocking, callback=None): + self.blocking = blocking + if not self.blocking: + state = super().startReceive() + ASSERT(state) + if callback != None: + self._callbackFunction = callback + super().setDio1Action(self._onIRQ) + else: + self._callbackFunction = self._dummyFunction + super().clearDio1Action() + return state + else: + state = super().standby() + ASSERT(state) + self._callbackFunction = self._dummyFunction + super().clearDio1Action() + return state + + def recv(self, len=0, timeout_en=False, timeout_ms=0): + if not self.blocking: + return self._readData(len) + else: + return self._receive(len, timeout_en, timeout_ms) + + def send(self, data): + if not self.blocking: + return self._startTransmit(data) + else: + return self._transmit(data) + + def _events(self): + return super().getIrqStatus() + + def _receive(self, len_=0, timeout_en=False, timeout_ms=0): + state = ERR_NONE + + length = len_ + + if len_ == 0: + length = SX126X_MAX_PACKET_LENGTH + + data = bytearray(length) + data_mv = memoryview(data) + + try: + state = super().receive(data_mv, length, timeout_en, timeout_ms) + except AssertionError as e: + state = list(ERROR.keys())[list(ERROR.values()).index(str(e))] + + if state == ERR_NONE or state == ERR_CRC_MISMATCH: + if len_ == 0: + length = super().getPacketLength(False) + data = data[:length] + + else: + return b'', state + + return bytes(data), state + + def _transmit(self, data): + if isinstance(data, bytes) or isinstance(data, bytearray): + pass + else: + return 0, ERR_INVALID_PACKET_TYPE + + # --- AJOUT : FORÇAGE DU CRC À OFF AVANT L'ENVOI --- + super().setCRC(False) + + state = super().transmit(data, len(data)) + return len(data), state + + def _readData(self, len_=0): + state = ERR_NONE + + length = super().getPacketLength() + + if len_ < length and len_ != 0: + length = len_ + + data = bytearray(length) + data_mv = memoryview(data) + + try: + state = super().readData(data_mv, length) + except AssertionError as e: + state = list(ERROR.keys())[list(ERROR.values()).index(str(e))] + + ASSERT(super().startReceive()) + + if state == ERR_NONE or state == ERR_CRC_MISMATCH: + return bytes(data), state + + else: + return b'', state + + def _startTransmit(self, data): + if isinstance(data, bytes) or isinstance(data, bytearray): + pass + else: + return 0, ERR_INVALID_PACKET_TYPE + + # --- AJOUT : FORÇAGE DU CRC À OFF AVANT L'ENVOI --- + super().setCRC(False) + + state = super().startTransmit(data, len(data)) + return len(data), state + + def _dummyFunction(self, *args): + pass + + def _onIRQ(self, callback): + events = self._events() + if events & SX126X_IRQ_TX_DONE: + super().startReceive() + self._callbackFunction(events) diff --git a/micro_ondes/esp_lora/lib/sx1262.py b/micro_ondes/esp_lora/lib/sx1262.py new file mode 100644 index 0000000..8a00c40 --- /dev/null +++ b/micro_ondes/esp_lora/lib/sx1262.py @@ -0,0 +1,267 @@ +from _sx126x import * +from sx126x import SX126X + +_SX126X_PA_CONFIG_SX1262 = const(0x00) + +class SX1262(SX126X): + TX_DONE = SX126X_IRQ_TX_DONE + RX_DONE = SX126X_IRQ_RX_DONE + ADDR_FILT_OFF = SX126X_GFSK_ADDRESS_FILT_OFF + ADDR_FILT_NODE = SX126X_GFSK_ADDRESS_FILT_NODE + ADDR_FILT_NODE_BROAD = SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST + PREAMBLE_DETECT_OFF = SX126X_GFSK_PREAMBLE_DETECT_OFF + PREAMBLE_DETECT_8 = SX126X_GFSK_PREAMBLE_DETECT_8 + PREAMBLE_DETECT_16 = SX126X_GFSK_PREAMBLE_DETECT_16 + PREAMBLE_DETECT_24 = SX126X_GFSK_PREAMBLE_DETECT_24 + PREAMBLE_DETECT_32 = SX126X_GFSK_PREAMBLE_DETECT_32 + STATUS = ERROR + + def __init__(self, spi_bus, clk, mosi, miso, cs, irq, rst, gpio): + super().__init__(spi_bus, clk, mosi, miso, cs, irq, rst, gpio) + self._callbackFunction = self._dummyFunction + + def begin(self, freq=434.0, bw=125.0, sf=9, cr=7, syncWord=SX126X_SYNC_WORD_PRIVATE, + power=14, currentLimit=60.0, preambleLength=8, implicit=False, implicitLen=0xFF, + crcOn=True, txIq=False, rxIq=False, tcxoVoltage=1.6, useRegulatorLDO=False, + blocking=True): + state = super().begin(bw, sf, cr, syncWord, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO, txIq, rxIq) + ASSERT(state) + + if not implicit: + state = super().explicitHeader() + else: + state = super().implicitHeader(implicitLen) + ASSERT(state) + + state = super().setCRC(crcOn) + ASSERT(state) + + state = self.setFrequency(freq) + ASSERT(state) + + state = self.setOutputPower(power) + ASSERT(state) + + state = super().fixPaClamping() + ASSERT(state) + + state = self.setBlockingCallback(blocking) + + return state + + def beginFSK(self, freq=434.0, br=48.0, freqDev=50.0, rxBw=156.2, power=14, currentLimit=60.0, + preambleLength=16, dataShaping=0.5, syncWord=[0x2D, 0x01], syncBitsLength=16, + addrFilter=SX126X_GFSK_ADDRESS_FILT_OFF, addr=0x00, crcLength=2, crcInitial=0x1D0F, crcPolynomial=0x1021, + crcInverted=True, whiteningOn=True, whiteningInitial=0x0100, + fixedPacketLength=False, packetLength=0xFF, preambleDetectorLength=SX126X_GFSK_PREAMBLE_DETECT_16, + tcxoVoltage=1.6, useRegulatorLDO=False, + blocking=True): + state = super().beginFSK(br, freqDev, rxBw, currentLimit, preambleLength, dataShaping, preambleDetectorLength, tcxoVoltage, useRegulatorLDO) + ASSERT(state) + + state = super().setSyncBits(syncWord, syncBitsLength) + ASSERT(state) + + if addrFilter == SX126X_GFSK_ADDRESS_FILT_OFF: + state = super().disableAddressFiltering() + elif addrFilter == SX126X_GFSK_ADDRESS_FILT_NODE: + state = super().setNodeAddress(addr) + elif addrFilter == SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST: + state = super().setBroadcastAddress(addr) + else: + state = ERR_UNKNOWN + ASSERT(state) + + state = super().setCRC(crcLength, crcInitial, crcPolynomial, crcInverted) + ASSERT(state) + + state = super().setWhitening(whiteningOn, whiteningInitial) + ASSERT(state) + + if fixedPacketLength: + state = super().fixedPacketLengthMode(packetLength) + else: + state = super().variablePacketLengthMode(packetLength) + ASSERT(state) + + state = self.setFrequency(freq) + ASSERT(state) + + state = self.setOutputPower(power) + ASSERT(state) + + state = super().fixPaClamping() + ASSERT(state) + + state = self.setBlockingCallback(blocking) + + return state + + def setFrequency(self, freq, calibrate=True): + if freq < 150.0 or freq > 960.0: + return ERR_INVALID_FREQUENCY + + state = ERR_NONE + + if calibrate: + data = bytearray(2) + if freq > 900.0: + data[0] = SX126X_CAL_IMG_902_MHZ_1 + data[1] = SX126X_CAL_IMG_902_MHZ_2 + elif freq > 850.0: + data[0] = SX126X_CAL_IMG_863_MHZ_1 + data[1] = SX126X_CAL_IMG_863_MHZ_2 + elif freq > 770.0: + data[0] = SX126X_CAL_IMG_779_MHZ_1 + data[1] = SX126X_CAL_IMG_779_MHZ_2 + elif freq > 460.0: + data[0] = SX126X_CAL_IMG_470_MHZ_1 + data[1] = SX126X_CAL_IMG_470_MHZ_2 + else: + data[0] = SX126X_CAL_IMG_430_MHZ_1 + data[1] = SX126X_CAL_IMG_430_MHZ_2 + state = super().calibrateImage(data) + ASSERT(state) + + return super().setFrequencyRaw(freq) + + def setOutputPower(self, power): + if not ((power >= -9) and (power <= 22)): + return ERR_INVALID_OUTPUT_POWER + + ocp = bytearray(1) + ocp_mv = memoryview(ocp) + state = super().readRegister(SX126X_REG_OCP_CONFIGURATION, ocp_mv, 1) + ASSERT(state) + + state = super().setPaConfig(0x04, _SX126X_PA_CONFIG_SX1262) + ASSERT(state) + + state = super().setTxParams(power) + ASSERT(state) + + return super().writeRegister(SX126X_REG_OCP_CONFIGURATION, ocp, 1) + + def setTxIq(self, txIq): + self._txIq = txIq + + def setRxIq(self, rxIq): + self._rxIq = rxIq + if not self.blocking: + ASSERT(super().startReceive()) + + def setPreambleDetectorLength(self, preambleDetectorLength): + self._preambleDetectorLength = preambleDetectorLength + if not self.blocking: + ASSERT(super().startReceive()) + + def setBlockingCallback(self, blocking, callback=None): + self.blocking = blocking + if not self.blocking: + state = super().startReceive() + ASSERT(state) + if callback != None: + self._callbackFunction = callback + super().setDio1Action(self._onIRQ) + else: + self._callbackFunction = self._dummyFunction + super().clearDio1Action() + return state + else: + state = super().standby() + ASSERT(state) + self._callbackFunction = self._dummyFunction + super().clearDio1Action() + return state + + def recv(self, len=0, timeout_en=False, timeout_ms=0): + if not self.blocking: + return self._readData(len) + else: + return self._receive(len, timeout_en, timeout_ms) + + def send(self, data): + if not self.blocking: + return self._startTransmit(data) + else: + return self._transmit(data) + + def _events(self): + return super().getIrqStatus() + + def _receive(self, len_=0, timeout_en=False, timeout_ms=0): + state = ERR_NONE + + length = len_ + + if len_ == 0: + length = SX126X_MAX_PACKET_LENGTH + + data = bytearray(length) + data_mv = memoryview(data) + + try: + state = super().receive(data_mv, length, timeout_en, timeout_ms) + except AssertionError as e: + state = list(ERROR.keys())[list(ERROR.values()).index(str(e))] + + if state == ERR_NONE or state == ERR_CRC_MISMATCH: + if len_ == 0: + length = super().getPacketLength(False) + data = data[:length] + + else: + return b'', state + + return bytes(data), state + + def _transmit(self, data): + if isinstance(data, bytes) or isinstance(data, bytearray): + pass + else: + return 0, ERR_INVALID_PACKET_TYPE + + state = super().transmit(data, len(data)) + return len(data), state + + def _readData(self, len_=0): + state = ERR_NONE + + length = super().getPacketLength() + + if len_ < length and len_ != 0: + length = len_ + + data = bytearray(length) + data_mv = memoryview(data) + + try: + state = super().readData(data_mv, length) + except AssertionError as e: + state = list(ERROR.keys())[list(ERROR.values()).index(str(e))] + + ASSERT(super().startReceive()) + + if state == ERR_NONE or state == ERR_CRC_MISMATCH: + return bytes(data), state + + else: + return b'', state + + def _startTransmit(self, data): + if isinstance(data, bytes) or isinstance(data, bytearray): + pass + else: + return 0, ERR_INVALID_PACKET_TYPE + + state = super().startTransmit(data, len(data)) + return len(data), state + + def _dummyFunction(self, *args): + pass + + def _onIRQ(self, callback): + events = self._events() + if events & SX126X_IRQ_TX_DONE: + super().startReceive() + self._callbackFunction(events) diff --git a/micro_ondes/esp_lora/lib/sx1268.py b/micro_ondes/esp_lora/lib/sx1268.py new file mode 100644 index 0000000..f3a4b50 --- /dev/null +++ b/micro_ondes/esp_lora/lib/sx1268.py @@ -0,0 +1,261 @@ +from _sx126x import * +from sx126x import SX126X + +_SX126X_PA_CONFIG_SX1268 = const(0x00) + +class SX1268(SX126X): + TX_DONE = SX126X_IRQ_TX_DONE + RX_DONE = SX126X_IRQ_RX_DONE + ADDR_FILT_OFF = SX126X_GFSK_ADDRESS_FILT_OFF + ADDR_FILT_NODE = SX126X_GFSK_ADDRESS_FILT_NODE + ADDR_FILT_NODE_BROAD = SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST + PREAMBLE_DETECT_OFF = SX126X_GFSK_PREAMBLE_DETECT_OFF + PREAMBLE_DETECT_8 = SX126X_GFSK_PREAMBLE_DETECT_8 + PREAMBLE_DETECT_16 = SX126X_GFSK_PREAMBLE_DETECT_16 + PREAMBLE_DETECT_24 = SX126X_GFSK_PREAMBLE_DETECT_24 + PREAMBLE_DETECT_32 = SX126X_GFSK_PREAMBLE_DETECT_32 + STATUS = ERROR + + def __init__(self, spi_bus, clk, mosi, miso, cs, irq, rst, gpio): + super().__init__(spi_bus, clk, mosi, miso, cs, irq, rst, gpio) + self._callbackFunction = self._dummyFunction + + def begin(self, freq=434.0, bw=125.0, sf=9, cr=7, syncWord=SX126X_SYNC_WORD_PRIVATE, + power=14, currentLimit=60.0, preambleLength=8, implicit=False, implicitLen=0xFF, + crcOn=True, txIq=False, rxIq=False, tcxoVoltage=1.6, useRegulatorLDO=False, + blocking=True): + state = super().begin(bw, sf, cr, syncWord, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO, txIq, rxIq) + ASSERT(state) + + if not implicit: + state = super().explicitHeader() + else: + state = super().implicitHeader(implicitLen) + ASSERT(state) + + state = super().setCRC(crcOn) + ASSERT(state) + + state = self.setFrequency(freq) + ASSERT(state) + + state = self.setOutputPower(power) + ASSERT(state) + + state = super().fixPaClamping() + ASSERT(state) + + state = self.setBlockingCallback(blocking) + + return state + + def beginFSK(self, freq=434.0, br=48.0, freqDev=50.0, rxBw=156.2, power=14, currentLimit=60.0, + preambleLength=16, dataShaping=0.5, syncWord=[0x2D, 0x01], syncBitsLength=16, + addrFilter=SX126X_GFSK_ADDRESS_FILT_OFF, addr=0x00, crcLength=2, crcInitial=0x1D0F, crcPolynomial=0x1021, + crcInverted=True, whiteningOn=True, whiteningInitial=0x0100, + fixedPacketLength=False, packetLength=0xFF, preambleDetectorLength=SX126X_GFSK_PREAMBLE_DETECT_16, + tcxoVoltage=1.6, useRegulatorLDO=False, + blocking=True): + state = super().beginFSK(br, freqDev, rxBw, currentLimit, preambleLength, dataShaping, preambleDetectorLength, tcxoVoltage, useRegulatorLDO) + ASSERT(state) + + state = super().setSyncBits(syncWord, syncBitsLength) + ASSERT(state) + + if addrFilter == SX126X_GFSK_ADDRESS_FILT_OFF: + state = super().disableAddressFiltering() + elif addrFilter == SX126X_GFSK_ADDRESS_FILT_NODE: + state = super().setNodeAddress(addr) + elif addrFilter == SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST: + state = super().setBroadcastAddress(addr) + else: + state = ERR_UNKNOWN + ASSERT(state) + + state = super().setCRC(crcLength, crcInitial, crcPolynomial, crcInverted) + ASSERT(state) + + state = super().setWhitening(whiteningOn, whiteningInitial) + ASSERT(state) + + if fixedPacketLength: + state = super().fixedPacketLengthMode(packetLength) + else: + state = super().variablePacketLengthMode(packetLength) + ASSERT(state) + + state = self.setFrequency(freq) + ASSERT(state) + + state = self.setOutputPower(power) + ASSERT(state) + + state = super().fixPaClamping() + ASSERT(state) + + state = self.setBlockingCallback(blocking) + + return state + + def setFrequency(self, freq, calibrate=True): + if freq < 410.0 or freq > 810.0: + return ERR_INVALID_FREQUENCY + + state = ERR_NONE + + if calibrate: + data = bytearray(2) + if freq > 770.0: + data[0] = SX126X_CAL_IMG_779_MHZ_1 + data[1] = SX126X_CAL_IMG_779_MHZ_2 + elif freq > 460.0: + data[0] = SX126X_CAL_IMG_470_MHZ_1 + data[1] = SX126X_CAL_IMG_470_MHZ_2 + else: + data[0] = SX126X_CAL_IMG_430_MHZ_1 + data[1] = SX126X_CAL_IMG_430_MHZ_2 + state = super().calibrateImage(data) + ASSERT(state) + + return super().setFrequencyRaw(freq) + + def setOutputPower(self, power): + if not ((power >= -9) and (power <= 22)): + return ERR_INVALID_OUTPUT_POWER + + ocp = bytearray(1) + ocp_mv = memoryview(ocp) + state = super().readRegister(SX126X_REG_OCP_CONFIGURATION, ocp_mv, 1) + ASSERT(state) + + state = super().setPaConfig(0x04, _SX126X_PA_CONFIG_SX1268) + ASSERT(state) + + state = super().setTxParams(power) + ASSERT(state) + + return super().writeRegister(SX126X_REG_OCP_CONFIGURATION, ocp, 1) + + def setTxIq(self, txIq): + self._txIq = txIq + + def setRxIq(self, rxIq): + self._rxIq = rxIq + if not self.blocking: + ASSERT(super().startReceive()) + + def setPreambleDetectorLength(self, preambleDetectorLength): + self._preambleDetectorLength = preambleDetectorLength + if not self.blocking: + ASSERT(super().startReceive()) + + def setBlockingCallback(self, blocking, callback=None): + self.blocking = blocking + if not self.blocking: + state = super().startReceive() + ASSERT(state) + if callback != None: + self._callbackFunction = callback + super().setDio1Action(self._onIRQ) + else: + self._callbackFunction = self._dummyFunction + super().clearDio1Action() + return state + else: + state = super().standby() + ASSERT(state) + self._callbackFunction = self._dummyFunction + super().clearDio1Action() + return state + + def recv(self, len=0, timeout_en=False, timeout_ms=0): + if not self.blocking: + return self._readData(len) + else: + return self._receive(len, timeout_en, timeout_ms) + + def send(self, data): + if not self.blocking: + return self._startTransmit(data) + else: + return self._transmit(data) + + def _events(self): + return super().getIrqStatus() + + def _receive(self, len_=0, timeout_en=False, timeout_ms=0): + state = ERR_NONE + + length = len_ + + if len_ == 0: + length = SX126X_MAX_PACKET_LENGTH + + data = bytearray(length) + data_mv = memoryview(data) + + try: + state = super().receive(data_mv, length, timeout_en, timeout_ms) + except AssertionError as e: + state = list(ERROR.keys())[list(ERROR.values()).index(str(e))] + + if state == ERR_NONE or state == ERR_CRC_MISMATCH: + if len_ == 0: + length = super().getPacketLength(False) + data = data[:length] + + else: + return b'', state + + return bytes(data), state + + def _transmit(self, data): + if isinstance(data, bytes) or isinstance(data, bytearray): + pass + else: + return 0, ERR_INVALID_PACKET_TYPE + + state = super().transmit(data, len(data)) + return len(data), state + + def _readData(self, len_=0): + state = ERR_NONE + + length = super().getPacketLength() + + if len_ < length and len_ != 0: + length = len_ + + data = bytearray(length) + data_mv = memoryview(data) + + try: + state = super().readData(data_mv, length) + except AssertionError as e: + state = list(ERROR.keys())[list(ERROR.values()).index(str(e))] + + ASSERT(super().startReceive()) + + if state == ERR_NONE or state == ERR_CRC_MISMATCH: + return bytes(data), state + + else: + return b'', state + + def _startTransmit(self, data): + if isinstance(data, bytes) or isinstance(data, bytearray): + pass + else: + return 0, ERR_INVALID_PACKET_TYPE + + state = super().startTransmit(data, len(data)) + return len(data), state + + def _dummyFunction(self, *args): + pass + + def _onIRQ(self, callback): + events = self._events() + if events & SX126X_IRQ_TX_DONE: + super().startReceive() + self._callbackFunction(events) diff --git a/micro_ondes/esp_lora/lib/sx126x.py b/micro_ondes/esp_lora/lib/sx126x.py new file mode 100644 index 0000000..5456cae --- /dev/null +++ b/micro_ondes/esp_lora/lib/sx126x.py @@ -0,0 +1,1415 @@ +from _sx126x import * + +from sys import implementation + +if implementation.name == 'micropython': + from machine import SPI, Pin + from utime import sleep_ms, sleep_us, ticks_ms, ticks_us, ticks_diff + +if implementation.name == 'circuitpython': + import digitalio + import busio + from time import sleep, monotonic_ns + + _MS_PER_NS = const(1000000) + _US_PER_NS = const(1000) + _MS_PER_S = const(1000) + _TICKS_MAX = const(536870911) + _TICKS_PERIOD = const(536870912) + _TICKS_HALFPERIOD = const(268435456) + + def sleep_ms(ms): + sleep(ms/1000) + + def sleep_us(us): + sleep(us/1000000) + + def ticks_ms(): + return (monotonic_ns() // _MS_PER_NS) & _TICKS_MAX + + def ticks_us(): + return (monotonic_ns() // _US_PER_NS) & _TICKS_MAX + + def ticks_diff(end, start): + diff = (end - start) & _TICKS_MAX + diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD + return diff + +class SX126X: + + def __init__(self, spi_bus, clk, mosi, miso, cs, irq, rst, gpio): + self._irq = irq + if implementation.name == 'micropython': + try: + self.spi = SPI(spi_bus, mode=SPI.MASTER, baudrate=2000000, pins=(clk, mosi, miso)) # Pycom variant uPy + except: + self.spi = SPI(spi_bus, baudrate=2000000, sck=Pin(clk), mosi=Pin(mosi), miso=Pin(miso)) # Generic variant uPy + self.cs = Pin(cs, mode=Pin.OUT) + self.irq = Pin(irq, mode=Pin.IN) + self.rst = Pin(rst, mode=Pin.OUT) + self.gpio = Pin(gpio, mode=Pin.IN) + + if implementation.name == 'circuitpython': + self.spi = busio.SPI(clk, MOSI=mosi, MISO=miso) + while not self.spi.try_lock(): + pass + self.spi.configure(baudrate=2000000, phase=0, polarity=0, bits=8) + self.spi.unlock() + self.cs = digitalio.DigitalInOut(cs) + self.cs.switch_to_output(value=True) + self.irq = digitalio.DigitalInOut(irq) + self.irq.switch_to_input() + self.rst = digitalio.DigitalInOut(rst) + self.rst.switch_to_output(value=True) + self.gpio = digitalio.DigitalInOut(gpio) + self.gpio.switch_to_input() + + self._bwKhz = 0 + self._sf = 0 + self._bw = 0 + self._cr = 0 + self._ldro = 0 + self._crcType = 0 + self._preambleLength = 0 + self._tcxoDelay = 0 + self._headerType = 0 + self._implicitLen = 0 + self._txIq = 0 + self._rxIq = 0 + self._invertIQ = 0 + self._ldroAuto = True + + self._br = 0 + self._freqDev = 0 + self._rxBw = 0 + self._rxBwKhz = 0 + self._pulseShape = 0 + self._crcTypeFSK = 0 + self._preambleLengthFSK = 0 + self._addrComp = 0 + self._syncWordLength = 0 + self._whitening = 0 + self._packetType = 0 + self._dataRate = 0 + self._packetLength = 0 + self._preambleDetectorLength = 0 + + def begin(self, bw, sf, cr, syncWord, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO=False, txIq=False, rxIq=False): + self._bwKhz = bw + self._sf = sf + + self._bw = SX126X_LORA_BW_125_0 + self._cr = SX126X_LORA_CR_4_7 + self._ldro = 0x00 + self._crcType = SX126X_LORA_CRC_ON + self._preambleLength = preambleLength + self._tcxoDelay = 0 + self._headerType = SX126X_LORA_HEADER_EXPLICIT + self._implicitLen = 0xFF + + self._txIq = txIq + self._rxIq = rxIq + self._invertIQ = SX126X_LORA_IQ_STANDARD + + state = self.reset() + ASSERT(state) + + state = self.standby() + ASSERT(state) + + if tcxoVoltage > 0.0: + state = self.setTCXO(tcxoVoltage) + ASSERT(state) + + state = self.config(SX126X_PACKET_TYPE_LORA) + ASSERT(state) + + if useRegulatorLDO: + state = self.setRegulatorLDO() + else: + state = self.setRegulatorDCDC() + ASSERT(state) + + state = self.setSpreadingFactor(sf) + ASSERT(state) + + state = self.setBandwidth(bw) + ASSERT(state) + + state = self.setCodingRate(cr) + ASSERT(state) + + state = self.setSyncWord(syncWord) + ASSERT(state) + + state = self.setCurrentLimit(currentLimit) + ASSERT(state) + + state = self.setPreambleLength(preambleLength) + ASSERT(state) + + state = self.setDio2AsRfSwitch(True) + ASSERT(state) + + return state + + def beginFSK(self, br, freqDev, rxBw, currentLimit, preambleLength, dataShaping, preambleDetectorLength, tcxoVoltage, useRegulatorLDO=False): + self._br = 21333 + self._freqDev = 52428 + self._rxBw = SX126X_GFSK_RX_BW_156_2 + self._rxBwKhz = 156.2 + self._pulseShape = SX126X_GFSK_FILTER_GAUSS_0_5 + self._crcTypeFSK = SX126X_GFSK_CRC_2_BYTE_INV + self._preambleLengthFSK = preambleLength + self._addrComp = SX126X_GFSK_ADDRESS_FILT_OFF + self._preambleDetectorLength = preambleDetectorLength + + state = self.reset() + ASSERT(state) + + state = self.standby() + ASSERT(state) + + if tcxoVoltage > 0.0: + state = self.setTCXO(tcxoVoltage) + ASSERT(state) + + state = self.config(SX126X_PACKET_TYPE_GFSK) + ASSERT(state) + + if useRegulatorLDO: + state = self.setRegulatorLDO() + else: + state = self.setRegulatorDCDC() + ASSERT(state) + + state = self.setBitRate(br) + ASSERT(state) + + state = self.setFrequencyDeviation(freqDev) + ASSERT(state) + + state = self.setRxBandwidth(rxBw) + ASSERT(state) + + state = self.setCurrentLimit(currentLimit) + ASSERT(state) + + state = self.setDataShaping(dataShaping) + ASSERT(state) + + state = self.setPreambleLength(preambleLength) + ASSERT(state) + + sync = [0x2D, 0x01] + state = self.setSyncWord(sync, 2) + ASSERT(state) + + state = self.setWhitening(True, 0x0100) + ASSERT(state) + + state = self.variablePacketLengthMode(SX126X_MAX_PACKET_LENGTH) + ASSERT(state) + + state = self.setDio2AsRfSwitch(True) + ASSERT(state) + + return state + + def reset(self, verify=True): + if implementation.name == 'micropython': + self.rst.value(1) + sleep_us(150) + self.rst.value(0) + sleep_us(150) + self.rst.value(1) + sleep_us(150) + + if implementation.name == 'circuitpython': + self.rst.value = True + sleep_us(150) + self.rst.value = False + sleep_us(150) + self.rst.value = True + sleep_us(150) + + if not verify: + return ERR_NONE + + start = ticks_ms() + while True: + state = self.standby() + if state == ERR_NONE: + return ERR_NONE + if abs(ticks_diff(start, ticks_ms())) >= 3000: + return state + sleep_ms(10) + + def transmit(self, data, len_, addr=0): + state = self.standby() + ASSERT(state) + + if len_ > SX126X_MAX_PACKET_LENGTH: + return ERR_PACKET_TOO_LONG + + timeout = 0 + + modem = self.getPacketType() + if modem == SX126X_PACKET_TYPE_LORA: + timeout = int((self.getTimeOnAir(len_) * 3) / 2) + + elif modem == SX126X_PACKET_TYPE_GFSK: + timeout = int(self.getTimeOnAir(len_) * 5) + + else: + return ERR_UNKNOWN + + state = self.startTransmit(data, len_, addr) + ASSERT(state) + + start = ticks_us() + if implementation.name == 'micropython': + while not self.irq.value(): + yield_() + if abs(ticks_diff(start, ticks_us())) > timeout: + self.clearIrqStatus() + self.standby() + return ERR_TX_TIMEOUT + if implementation.name == 'circuitpython': + while not self.irq.value: + yield_() + if abs(ticks_diff(start, ticks_us())) > timeout: + self.clearIrqStatus() + self.standby() + return ERR_TX_TIMEOUT + + elapsed = abs(ticks_diff(start, ticks_us())) + + self._dataRate = (len_*8.0)/(float(elapsed)/1000000.0) + + state = self.clearIrqStatus() + ASSERT(state) + + state = self.standby() + + return state + + def receive(self, data, len_, timeout_en, timeout_ms): + state = self.standby() + ASSERT(state) + + timeout = 0 + + modem = self.getPacketType() + if modem == SX126X_PACKET_TYPE_LORA: + symbolLength = float(1 << self._sf) / float(self._bwKhz) + timeout = int(symbolLength * 100.0 * 1000.0) + elif modem == SX126X_PACKET_TYPE_GFSK: + maxLen = len_ + if len_ == 0: + maxLen = 0xFF + brBps = (float(SX126X_CRYSTAL_FREQ) * 1000000.0 * 32.0) / float(self._br) + timeout = int(((maxLen * 8.0) / brBps) * 1000000.0 * 5.0) + else: + return ERR_UNKNOWN + + if timeout_ms == 0: + pass + else: + timeout = timeout_ms * 1000 + + if timeout_en: + timeoutValue = int(float(timeout) / 15.625) + else: + timeoutValue = SX126X_RX_TIMEOUT_NONE + + state = self.startReceive(timeoutValue) + ASSERT(state) + + start = ticks_us() + if implementation.name == 'micropython': + while not self.irq.value(): + yield_() + if timeout_en: + if abs(ticks_diff(start, ticks_us())) > timeout: + self.fixImplicitTimeout() + self.clearIrqStatus() + self.standby() + return ERR_RX_TIMEOUT + if implementation.name == 'circuitpython': + while not self.irq.value: + yield_() + if timeout_en: + if abs(ticks_diff(start, ticks_us())) > timeout: + self.fixImplicitTimeout() + self.clearIrqStatus() + self.standby() + return ERR_RX_TIMEOUT + + if self._headerType == SX126X_LORA_HEADER_IMPLICIT and self.getPacketType() == SX126X_PACKET_TYPE_LORA: + state = self.fixImplicitTimeout() + ASSERT(state) + + return self.readData(data, len_) + + def transmitDirect(self, frf=0): + state = ERR_NONE + if frf != 0: + state = self.setRfFrequency(frf) + ASSERT(state) + + data = [SX126X_CMD_NOP] + return self.SPIwriteCommand([SX126X_CMD_SET_TX_CONTINUOUS_WAVE], 1, data, 1) + + def receiveDirect(self): + return ERR_UNKNOWN + + def scanChannel(self): + if self.getPacketType() != SX126X_PACKET_TYPE_LORA: + return ERR_WRONG_MODEM + + state = self.standby() + ASSERT(state) + + state = self.setDioIrqParams(SX126X_IRQ_CAD_DETECTED | SX126X_IRQ_CAD_DONE, SX126X_IRQ_CAD_DETECTED | SX126X_IRQ_CAD_DONE) + ASSERT(state) + + state = self.clearIrqStatus() + ASSERT(state) + + state = self.setCad() + ASSERT(state) + + if implementation.name == 'micropython': + while not self.irq.value(): + yield_() + if implementation.name == 'circuitpython': + while not self.irq.value: + yield_() + + + cadResult = self.getIrqStatus() + if cadResult & SX126X_IRQ_CAD_DETECTED: + self.clearIrqStatus() + return LORA_DETECTED + elif cadResult & SX126X_IRQ_CAD_DONE: + self.clearIrqStatus() + return CHANNEL_FREE + + return ERR_UNKNOWN + + def sleep(self, retainConfig=True): + sleepMode = [SX126X_SLEEP_START_WARM | SX126X_SLEEP_RTC_OFF] + if not retainConfig: + sleepMode = [SX126X_SLEEP_START_COLD | SX126X_SLEEP_RTC_OFF] + state = self.SPIwriteCommand([SX126X_CMD_SET_SLEEP], 1, sleepMode, 1, False) + + sleep_us(500) + + return state + + def standby(self, mode=SX126X_STANDBY_RC): + data = [mode] + return self.SPIwriteCommand([SX126X_CMD_SET_STANDBY], 1, data, 1) + + def setDio1Action(self, func): + try: + self.irq.callback(trigger=Pin.IRQ_RISING, handler=func) # Pycom variant uPy + except: + self.irq.irq(trigger=Pin.IRQ_RISING, handler=func) # Generic variant uPy + + def clearDio1Action(self): + if implementation.name == 'micropython': + self.irq = Pin(self._irq, mode=Pin.IN) + + if implementation.name == 'circuitpython': + self.irq.deinit() + self.irq = digitalio.DigitalInOut(self._irq) + self.irq.switch_to_input() + + def startTransmit(self, data, len_, addr=0): + if len_ > SX126X_MAX_PACKET_LENGTH: + return ERR_PACKET_TOO_LONG + + if self._addrComp != SX126X_GFSK_ADDRESS_FILT_OFF and len_ > (SX126X_MAX_PACKET_LENGTH - 1): + return ERR_PACKET_TOO_LONG + + state = ERR_NONE + modem = self.getPacketType() + if modem == SX126X_PACKET_TYPE_LORA: + if self._txIq: + self._invertIQ = SX126X_LORA_IQ_INVERTED + else: + self._invertIQ = SX126X_LORA_IQ_STANDARD + + if self._headerType == SX126X_LORA_HEADER_IMPLICIT: + if len_ != self._implicitLen: + return ERR_INVALID_PACKET_LENGTH + + state = self.setPacketParams(self._preambleLength, self._crcType, len_, self._headerType, self._invertIQ) + elif modem == SX126X_PACKET_TYPE_GFSK: + if self._packetType == SX126X_GFSK_PACKET_FIXED: + if len_ != self._packetLength: + return ERR_INVALID_PACKET_LENGTH + + state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, len_, self._preambleDetectorLength) + else: + return ERR_UNKNOWN + ASSERT(state) + + state = self.setDioIrqParams(SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT, SX126X_IRQ_TX_DONE) + ASSERT(state) + + state = self.setBufferBaseAddress() + ASSERT(state) + + state = self.writeBuffer(data, len_) + ASSERT(state) + + state = self.clearIrqStatus() + ASSERT(state) + + state = self.fixSensitivity() + ASSERT(state) + + state = self.setTx(SX126X_TX_TIMEOUT_NONE) + ASSERT(state) + + if implementation.name == 'micropython': + while self.gpio.value(): + yield_() + + if implementation.name == 'circuitpython': + while self.gpio.value: + yield_() + + return state + + def startReceive(self, timeout=SX126X_RX_TIMEOUT_INF): + state = ERR_NONE + modem = self.getPacketType() + if modem == SX126X_PACKET_TYPE_LORA: + if self._rxIq: + self._invertIQ = SX126X_LORA_IQ_INVERTED + else: + self._invertIQ = SX126X_LORA_IQ_STANDARD + + state = self.setPacketParams(self._preambleLength, self._crcType, self._implicitLen, self._headerType, self._invertIQ) + elif modem == SX126X_PACKET_TYPE_GFSK: + state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength) + else: + return ERR_UNKNOWN + ASSERT(state) + + state = self.startReceiveCommon() + ASSERT(state) + + state = self.setRx(timeout) + + return state + + def startReceiveDutyCycle(self, rxPeriod, sleepPeriod): + transitionTime = int(self._tcxoDelay + 1000) + sleepPeriod -= transitionTime + + rxPeriodRaw = int((rxPeriod * 8) / 125) + sleepPeriodRaw = int((sleepPeriod * 8) / 125) + + if rxPeriodRaw & 0xFF000000 or rxPeriodRaw == 0: + return ERR_INVALID_RX_PERIOD + + if sleepPeriodRaw & 0xFF000000 or sleepPeriodRaw == 0: + return ERR_INVALID_SLEEP_PERIOD + + state = self.startReceiveCommon() + ASSERT(state) + + data = [int((rxPeriodRaw >> 16) & 0xFF), int((rxPeriodRaw >> 8) & 0xFF), int(rxPeriodRaw & 0xFF), + int((sleepPeriodRaw >> 16) & 0xFF),int((sleepPeriodRaw >> 8) & 0xFF),int(sleepPeriodRaw & 0xFF)] + return self.SPIwriteCommand([SX126X_CMD_SET_RX_DUTY_CYCLE], 1, data, 6) + + def startReceiveDutyCycleAuto(self, senderPreambleLength=0, minSymbols=8): + if senderPreambleLength == 0: + senderPreambleLength = self._preambleLength + + sleepSymbols = int(senderPreambleLength - 2 * minSymbols) + + if (2 * minSymbols) > senderPreambleLength: + return self.startReceive() + + symbolLength = int(((10*1000) << self._sf) / (10 * self._bwKhz)) + sleepPeriod = symbolLength * sleepSymbols + + wakePeriod = int(max((symbolLength * (senderPreambleLength + 1) - (sleepPeriod - 1000)) / 2, symbolLength * (minSymbols + 1))) + + if sleepPeriod < (self._tcxoDelay + 1016): + return self.startReceive() + + return self.startReceiveDutyCycle(wakePeriod, sleepPeriod) + + def startReceiveCommon(self): + state = self.setDioIrqParams(SX126X_IRQ_RX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR | SX126X_IRQ_HEADER_ERR, SX126X_IRQ_RX_DONE) + ASSERT(state) + + state = self.setBufferBaseAddress() + ASSERT(state) + + state = self.clearIrqStatus() + + modem = self.getPacketType() + if modem == SX126X_PACKET_TYPE_LORA: + state = self.setPacketParams(self._preambleLength, self._crcType, self._implicitLen, self._headerType, self._invertIQ) + elif modem == SX126X_PACKET_TYPE_GFSK: + state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType) + else: + return ERR_UNKNOWN + + return state + + def readData(self, data, len_): + state = self.standby() + ASSERT(state) + + irq = self.getIrqStatus() + crcState = ERR_NONE + if irq & SX126X_IRQ_CRC_ERR or irq & SX126X_IRQ_HEADER_ERR: + crcState = ERR_CRC_MISMATCH + + length = len_ + if len_ == SX126X_MAX_PACKET_LENGTH: + length = self.getPacketLength() + + state = self.readBuffer(data, length) + ASSERT(state) + + state = self.clearIrqStatus() + + ASSERT(crcState) + + return state + + def setBandwidth(self, bw): + if self.getPacketType() != SX126X_PACKET_TYPE_LORA: + return ERR_WRONG_MODEM + + if not ((bw > 0) and (bw < 510)): + return ERR_INVALID_BANDWIDTH + + bw_div2 = int(bw / 2 + 0.01) + switch = {3: SX126X_LORA_BW_7_8, + 5: SX126X_LORA_BW_10_4, + 7: SX126X_LORA_BW_15_6, + 10: SX126X_LORA_BW_20_8, + 15: SX126X_LORA_BW_31_25, + 20: SX126X_LORA_BW_41_7, + 31: SX126X_LORA_BW_62_5, + 62: SX126X_LORA_BW_125_0, + 125: SX126X_LORA_BW_250_0, + 250: SX126X_LORA_BW_500_0} + try: + self._bw = switch[bw_div2] + except: + return ERR_INVALID_BANDWIDTH + + self._bwKhz = bw + return self.setModulationParams(self._sf, self._bw, self._cr, self._ldro) + + def setSpreadingFactor(self, sf): + if self.getPacketType() != SX126X_PACKET_TYPE_LORA: + return ERR_WRONG_MODEM + + if not ((sf >= 5) and (sf <= 12)): + return ERR_INVALID_SPREADING_FACTOR + + self._sf = sf + return self.setModulationParams(self._sf, self._bw, self._cr, self._ldro) + + def setCodingRate(self, cr): + if self.getPacketType() != SX126X_PACKET_TYPE_LORA: + return ERR_WRONG_MODEM + + if not ((cr >= 5) and (cr <= 8)): + return ERR_INVALID_CODING_RATE + + self._cr = cr - 4 + return self.setModulationParams(self._sf, self._bw, self._cr, self._ldro) + + def setSyncWord(self, syncWord, *args): + if self.getPacketType() == SX126X_PACKET_TYPE_LORA: + if len(args) > 0: + controlBits = args[0] + else: + controlBits = 0x44 + data = [int((syncWord & 0xF0) | ((controlBits & 0xF0) >> 4)), int(((syncWord & 0x0F) << 4) | (controlBits & 0x0F))] + return self.writeRegister(SX126X_REG_LORA_SYNC_WORD_MSB, data, 2) + + elif self.getPacketType() == SX126X_PACKET_TYPE_GFSK: + len_ = args[0] + if len_ > 8: + return ERR_INVALID_SYNC_WORD + + state = self.writeRegister(SX126X_REG_SYNC_WORD_0, syncWord, len_) + ASSERT(state) + + self._syncWordLength = len_ * 8 + state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength) + + return state + + else: + return ERR_WRONG_MODEM + + def setCurrentLimit(self, currentLimit): + if not ((currentLimit >= 0) and (currentLimit <= 140)): + return ERR_INVALID_CURRENT_LIMIT + + rawLimit = [int(currentLimit / 2.5)] + + return self.writeRegister(SX126X_REG_OCP_CONFIGURATION, rawLimit, 1) + + def getCurrentLimit(self): + ocp = bytearray(1) + ocp_mv = memoryview(ocp) + self.readRegister(SX126X_REG_OCP_CONFIGURATION, ocp_mv, 1) + + return float(ocp[0]) * 2.5 + + def setPreambleLength(self, preambleLength): + modem = self.getPacketType() + if modem == SX126X_PACKET_TYPE_LORA: + self._preambleLength = preambleLength + return self.setPacketParams(self._preambleLength, self._crcType, self._implicitLen, self._headerType, self._invertIQ) + elif modem == SX126X_PACKET_TYPE_GFSK: + self._preambleLengthFSK = preambleLength + return self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength) + + return ERR_UNKNOWN + + def setFrequencyDeviation(self, freqDev): + if self.getPacketType() != SX126X_PACKET_TYPE_GFSK: + return ERR_WRONG_MODEM + + if not (freqDev <= 200.0): + return ERR_INVALID_FREQUENCY_DEVIATION + + freqDevRaw = int(((freqDev * 1000.0) * float(1 << 25)) / (SX126X_CRYSTAL_FREQ * 1000000.0)) + + self._freqDev = freqDevRaw + return self.setModulationParamsFSK(self._br, self._pulseShape, self._rxBw, self._freqDev) + + def setBitRate(self, br): + if self.getPacketType() != SX126X_PACKET_TYPE_GFSK: + return ERR_WRONG_MODEM + + if not ((br >= 0.6) and (br <= 300.0)): + return ERR_INVALID_BIT_RATE + + brRaw = int((SX126X_CRYSTAL_FREQ * 1000000.0 * 32.0) / (br * 1000.0)) + + self._br = brRaw + + return self.setModulationParamsFSK(self._br, self._pulseShape, self._rxBw, self._freqDev) + + def setRxBandwidth(self, rxBw): + if self.getPacketType() != SX126X_PACKET_TYPE_GFSK: + return ERR_WRONG_MODEM + + self._rxBwKhz = rxBw + + if abs(rxBw - 4.8) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_4_8 + elif abs(rxBw - 5.8) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_5_8 + elif abs(rxBw - 7.3) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_7_3 + elif abs(rxBw - 9.7) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_9_7 + elif abs(rxBw - 11.7) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_11_7 + elif abs(rxBw - 14.6) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_14_6 + elif abs(rxBw - 19.5) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_19_5 + elif abs(rxBw - 23.4) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_23_4 + elif abs(rxBw - 29.3) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_29_3 + elif abs(rxBw - 39.0) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_39_0 + elif abs(rxBw - 46.9) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_46_9 + elif abs(rxBw - 58.6) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_58_6 + elif abs(rxBw - 78.2) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_78_2 + elif abs(rxBw - 93.8) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_93_8 + elif abs(rxBw - 117.3) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_117_3 + elif abs(rxBw - 156.2) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_156_2 + elif abs(rxBw - 187.2) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_187_2 + elif abs(rxBw - 234.3) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_234_3 + elif abs(rxBw - 312.0) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_312_0 + elif abs(rxBw - 373.6) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_373_6 + elif abs(rxBw - 467.0) <= 0.001: + self._rxBw = SX126X_GFSK_RX_BW_467_0 + else: + return ERR_INVALID_RX_BANDWIDTH + + return self.setModulationParamsFSK(self._br, self._pulseShape, self._rxBw, self._freqDev) + + def setDataShaping(self, sh): + if self.getPacketType() != SX126X_PACKET_TYPE_GFSK: + return ERR_WRONG_MODEM + + sh *= 10.0 + if abs(sh - 0.0) <= 0.001: + self._pulseShape = SX126X_GFSK_FILTER_NONE + elif abs(sh - 3.0) <= 0.001: + self._pulseShape = SX126X_GFSK_FILTER_GAUSS_0_3 + elif abs(sh - 5.0) <= 0.001: + self._pulseShape = SX126X_GFSK_FILTER_GAUSS_0_5 + elif abs(sh - 7.0) <= 0.001: + self._pulseShape = SX126X_GFSK_FILTER_GAUSS_0_7 + elif abs(sh - 10.0) <= 0.001: + self._pulseShape = SX126X_GFSK_FILTER_GAUSS_1 + else: + return ERR_INVALID_DATA_SHAPING + + return self.setModulationParamsFSK(self._br, self._pulseShape, self._rxBw, self._freqDev) + + def setSyncBits(self, syncWord, bitsLen): + if self.getPacketType() != SX126X_PACKET_TYPE_GFSK: + return ERR_WRONG_MODEM + + if bitsLen > 0x40: + return ERR_INVALID_SYNC_WORD + + bytesLen = int(bitsLen / 8) + if (bitsLen % 8) != 0: + bytesLen += 1 + + state = self.writeRegister(SX126X_REG_SYNC_WORD_0, syncWord, bytesLen) + ASSERT(state) + + self._syncWordLength = bitsLen + state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength) + + return state + + def setNodeAddress(self, nodeAddr): + if self.getPacketType() != SX126X_PACKET_TYPE_GFSK: + return ERR_WRONG_MODEM + + self._addrComp = SX126X_GFSK_ADDRESS_FILT_NODE + + state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength) + ASSERT(state) + + state = self.writeRegister(SX126X_REG_NODE_ADDRESS, [nodeAddr], 1) + + return state + + def setBroadcastAddress(self, broadAddr): + if self.getPacketType() != SX126X_PACKET_TYPE_GFSK: + return ERR_WRONG_MODEM + + self._addrComp = SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST + state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength) + ASSERT(state) + + state = self.writeRegister(SX126X_REG_BROADCAST_ADDRESS, [broadAddr], 1) + + return state + + def disableAddressFiltering(self): + if self.getPacketType() != SX126X_PACKET_TYPE_GFSK: + return ERR_WRONG_MODEM + + self._addrComp = SX126X_GFSK_ADDRESS_FILT_OFF + return self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength) + + def setCRC(self, len_, initial=0x1D0F, polynomial=0x1021, inverted=True): + modem = self.getPacketType() + + if modem == SX126X_PACKET_TYPE_GFSK: + if len_ == 0: + self._crcTypeFSK = SX126X_GFSK_CRC_OFF + elif len_ == 1: + if inverted: + self._crcTypeFSK = SX126X_GFSK_CRC_1_BYTE_INV + else: + self._crcTypeFSK = SX126X_GFSK_CRC_1_BYTE + elif len_ == 2: + if inverted: + self._crcTypeFSK = SX126X_GFSK_CRC_2_BYTE_INV + else: + self._crcTypeFSK = SX126X_GFSK_CRC_2_BYTE + else: + return ERR_INVALID_CRC_CONFIGURATION + + state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength) + ASSERT(state) + + data = [int((initial >> 8) & 0xFF), int(initial & 0xFF)] + state = self.writeRegister(SX126X_REG_CRC_INITIAL_MSB, data, 2) + ASSERT(state) + + data[0] = int((polynomial >> 8) & 0xFF) + data[1] = int(polynomial & 0xFF) + state = self.writeRegister(SX126X_REG_CRC_POLYNOMIAL_MSB, data, 2) + + return state + + elif modem == SX126X_PACKET_TYPE_LORA: + + if len_: + self._crcType = SX126X_LORA_CRC_ON + else: + self._crcType = SX126X_LORA_CRC_OFF + + return self.setPacketParams(self._preambleLength, self._crcType, self._implicitLen, self._headerType, self._invertIQ) + + return ERR_UNKNOWN + + def setWhitening(self, enabled, initial=0x0100): + if self.getPacketType() != SX126X_PACKET_TYPE_GFSK: + return ERR_WRONG_MODEM + + state = ERR_NONE + if enabled != True: + self._whitening = SX126X_GFSK_WHITENING_OFF + + state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength) + ASSERT(state) + else: + self._whitening = SX126X_GFSK_WHITENING_ON + + data = bytearray(1) + data_mv = memoryview(data) + state = self.readRegister(SX126X_REG_WHITENING_INITIAL_MSB, data_mv, 1) + ASSERT(state) + data2 = [(data[0] & 0xFE) | int((initial >> 8) & 0x01), int(initial & 0xFF)] + state = self.writeRegister(SX126X_REG_WHITENING_INITIAL_MSB, data2, 2) + ASSERT(state) + + state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength) + ASSERT(state) + return state + + def getDataRate(self): + return self._dataRate + + def getRSSI(self): + packetStatus = self.getPacketStatus() + rssiPkt = int(packetStatus & 0xFF) + return -1.0 * rssiPkt/2.0 + + def getSNR(self): + if self.getPacketType() != SX126X_PACKET_TYPE_LORA: + return ERR_WRONG_MODEM + + packetStatus = self.getPacketStatus() + snrPkt = int((packetStatus >> 8) & 0xFF) + if snrPkt < 128: + return snrPkt/4.0 + else: + return (snrPkt - 256)/4.0 + + def getPacketLength(self, update=True): + rxBufStatus = bytearray(2) + rxBufStatus_mv = memoryview(rxBufStatus) + self.SPIreadCommand([SX126X_CMD_GET_RX_BUFFER_STATUS], 1, rxBufStatus_mv, 2) + return rxBufStatus[0] + + def fixedPacketLengthMode(self, len_=SX126X_MAX_PACKET_LENGTH): + return self.setPacketMode(SX126X_GFSK_PACKET_FIXED, len_) + + def variablePacketLengthMode(self, maxLen=SX126X_MAX_PACKET_LENGTH): + return self.setPacketMode(SX126X_GFSK_PACKET_VARIABLE, maxLen) + + def getTimeOnAir(self, len_): + if self.getPacketType() == SX126X_PACKET_TYPE_LORA: + symbolLength_us = int(((1000 * 10) << self._sf) / (self._bwKhz * 10)) + sfCoeff1_x4 = 17 + sfCoeff2 = 8 + if self._sf == 5 or self._sf == 6: + sfCoeff1_x4 = 25 + sfCoeff2 = 0 + sfDivisor = 4*self._sf + if symbolLength_us >= 16000: + sfDivisor = 4*(self._sf - 2) + bitsPerCrc = 16 + N_symbol_header = 20 if self._headerType == SX126X_LORA_HEADER_EXPLICIT else 0 + + bitCount = int(8 * len_ + self._crcType * bitsPerCrc - 4 * self._sf + sfCoeff2 + N_symbol_header) + if bitCount < 0: + bitCount = 0 + + nPreCodedSymbols = int((bitCount + (sfDivisor - 1)) / sfDivisor) + + nSymbol_x4 = int((self._preambleLength + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * (self._cr + 4) * 4) + + return int((symbolLength_us * nSymbol_x4) / 4) + else: + return int((len_ * 8 * self._br) / (SX126X_CRYSTAL_FREQ * 32)) + + def implicitHeader(self, len_): + return self.setHeaderType(SX126X_LORA_HEADER_IMPLICIT, len_) + + def explicitHeader(self): + return self.setHeaderType(SX126X_LORA_HEADER_EXPLICIT) + + def setRegulatorLDO(self): + return self.setRegulatorMode(SX126X_REGULATOR_LDO) + + def setRegulatorDCDC(self): + return self.setRegulatorMode(SX126X_REGULATOR_DC_DC) + + def setEncoding(self, encoding): + return self.setWhitening(encoding) + + def forceLDRO(self, enable): + if self.getPacketType() != SX126X_PACKET_TYPE_LORA: + return ERR_WRONG_MODEM + + self._ldroAuto = False + self._ldro = enable + return self.setModulationParams(self._sf, self._bw, self._cr, self._ldro) + + def autoLDRO(self): + if self.getPacketType() != SX126X_PACKET_TYPE_LORA: + return ERR_WRONG_MODEM + + self._ldroAuto = True + return self.setModulationParams(self._sf, self._bw, self._cr, self._ldro) + + def setTCXO(self, voltage, delay=5000): + self.standby() + + if self.getDeviceErrors() & SX126X_XOSC_START_ERR: + self.clearDeviceErrors() + + if abs(voltage - 0.0) <= 0.001: + return self.reset() + + data = [0,0,0,0] + if abs(voltage - 1.6) <= 0.001: + data[0] = SX126X_DIO3_OUTPUT_1_6 + elif abs(voltage - 1.7) <= 0.001: + data[0] = SX126X_DIO3_OUTPUT_1_7 + elif abs(voltage - 1.8) <= 0.001: + data[0] = SX126X_DIO3_OUTPUT_1_8 + elif abs(voltage - 2.2) <= 0.001: + data[0] = SX126X_DIO3_OUTPUT_2_2 + elif abs(voltage - 2.4) <= 0.001: + data[0] = SX126X_DIO3_OUTPUT_2_4 + elif abs(voltage - 2.7) <= 0.001: + data[0] = SX126X_DIO3_OUTPUT_2_7 + elif abs(voltage - 3.0) <= 0.001: + data[0] = SX126X_DIO3_OUTPUT_3_0 + elif abs(voltage - 3.3) <= 0.001: + data[0] = SX126X_DIO3_OUTPUT_3_3 + else: + return ERR_INVALID_TCXO_VOLTAGE + + delayValue = int(float(delay) / 15.625) + data[1] = int((delayValue >> 16) & 0xFF) + data[2] = int((delayValue >> 8) & 0xFF) + data[3] = int(delayValue & 0xFF) + + self._tcxoDelay = delay + + return self.SPIwriteCommand([SX126X_CMD_SET_DIO3_AS_TCXO_CTRL], 1, data, 4) + + def setDio2AsRfSwitch(self, enable=True): + data = [0] + if enable: + data = [SX126X_DIO2_AS_RF_SWITCH] + else: + data = [SX126X_DIO2_AS_IRQ] + return self.SPIwriteCommand([SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL], 1, data, 1) + + def setTx(self, timeout=0): + data = [int((timeout >> 16) & 0xFF), int((timeout >> 8) & 0xFF), int(timeout & 0xFF)] + return self.SPIwriteCommand([SX126X_CMD_SET_TX], 1, data, 3) + + def setRx(self, timeout): + data = [int((timeout >> 16) & 0xFF), int((timeout >> 8) & 0xFF), int(timeout & 0xFF)] + return self.SPIwriteCommand([SX126X_CMD_SET_RX], 1, data, 3) + + def setCad(self): + return self.SPIwriteCommand([SX126X_CMD_SET_CAD], 1, [], 0) + + def setPaConfig(self, paDutyCycle, deviceSel, hpMax=SX126X_PA_CONFIG_HP_MAX, paLut=SX126X_PA_CONFIG_PA_LUT): + data = [paDutyCycle, hpMax, deviceSel, paLut] + return self.SPIwriteCommand([SX126X_CMD_SET_PA_CONFIG], 1, data, 4) + + def writeRegister(self, addr, data, numBytes): + cmd = [SX126X_CMD_WRITE_REGISTER, int((addr >> 8) & 0xFF), int(addr & 0xFF)] + state = self.SPIwriteCommand(cmd, 3, data, numBytes) + return state + + def readRegister(self, addr, data, numBytes): + cmd = [SX126X_CMD_READ_REGISTER, int((addr >> 8) & 0xFF), int(addr & 0xFF)] + return self.SPItransfer(cmd, 3, False, [], data, numBytes, True) + + def writeBuffer(self, data, numBytes, offset=0x00): + cmd = [SX126X_CMD_WRITE_BUFFER, offset] + state = self.SPIwriteCommand(cmd, 2, data, numBytes) + + return state + + def readBuffer(self, data, numBytes): + cmd = [SX126X_CMD_READ_BUFFER, SX126X_CMD_NOP] + state = self.SPIreadCommand(cmd, 2, data, numBytes) + + return state + + def setDioIrqParams(self, irqMask, dio1Mask, dio2Mask=SX126X_IRQ_NONE, dio3Mask=SX126X_IRQ_NONE): + data = [int((irqMask >> 8) & 0xFF), int(irqMask & 0xFF), + int((dio1Mask >> 8) & 0xFF), int(dio1Mask & 0xFF), + int((dio2Mask >> 8) & 0xFF), int(dio2Mask & 0xFF), + int((dio3Mask >> 8) & 0xFF), int(dio3Mask & 0xFF)] + return self.SPIwriteCommand([SX126X_CMD_SET_DIO_IRQ_PARAMS], 1, data, 8) + + def getIrqStatus(self): + data = bytearray(2) + data_mv = memoryview(data) + self.SPIreadCommand([SX126X_CMD_GET_IRQ_STATUS], 1, data_mv, 2) + return int((data[0] << 8) | data[1]) + + def clearIrqStatus(self, clearIrqParams=SX126X_IRQ_ALL): + data = [int((clearIrqParams >> 8) & 0xFF), int(clearIrqParams & 0xFF)] + return self.SPIwriteCommand([SX126X_CMD_CLEAR_IRQ_STATUS], 1, data, 2) + + def setRfFrequency(self, frf): + data = [int((frf >> 24) & 0xFF), + int((frf >> 16) & 0xFF), + int((frf >> 8) & 0xFF), + int(frf & 0xFF)] + return self.SPIwriteCommand([SX126X_CMD_SET_RF_FREQUENCY], 1, data, 4) + + def calibrateImage(self, data): + return self.SPIwriteCommand([SX126X_CMD_CALIBRATE_IMAGE], 1, data, 2) + + def getPacketType(self): + data = bytearray([0xFF]) + data_mv = memoryview(data) + self.SPIreadCommand([SX126X_CMD_GET_PACKET_TYPE], 1, data_mv, 1) + return data[0] + + def setTxParams(self, power, rampTime=SX126X_PA_RAMP_200U): + if power < 0: + power += 256 + data = [power, rampTime] + return self.SPIwriteCommand([SX126X_CMD_SET_TX_PARAMS], 1, data, 2) + + def setPacketMode(self, mode, len_): + if self.getPacketType() != SX126X_PACKET_TYPE_GFSK: + return ERR_WRONG_MODEM + + state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, mode, len_, self._preambleDetectorLength) + ASSERT(state) + + self._packetType = mode + self._packetLength = len_ + return state + + def setHeaderType(self, headerType, len_=0xFF): + if self.getPacketType() != SX126X_PACKET_TYPE_LORA: + return ERR_WRONG_MODEM + + state = self.setPacketParams(self._preambleLength, self._crcType, len_, headerType, self._invertIQ) + ASSERT(state) + + self._headerType = headerType + self._implicitLen = len_ + + return state + + def setModulationParams(self, sf, bw, cr, ldro): + if self._ldroAuto: + symbolLength = float((1 << self._sf)) / float(self._bwKhz) + if symbolLength >= 16.0: + self._ldro = SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON + else: + self._ldro = SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF + else: + self._ldro = ldro + + data = [sf, bw, cr, self._ldro] + return self.SPIwriteCommand([SX126X_CMD_SET_MODULATION_PARAMS], 1, data, 4) + + def setModulationParamsFSK(self, br, pulseShape, rxBw, freqDev): + data = [int((br >> 16) & 0xFF), int((br >> 8) & 0xFF), int(br & 0xFF), + pulseShape, rxBw, + int((freqDev >> 16) & 0xFF), int((freqDev >> 8) & 0xFF), int(freqDev & 0xFF)] + return self.SPIwriteCommand([SX126X_CMD_SET_MODULATION_PARAMS], 1, data, 8) + + def setPacketParams(self, preambleLength, crcType, payloadLength, headerType, invertIQ=SX126X_LORA_IQ_STANDARD): + state = self.fixInvertedIQ(invertIQ) + ASSERT(state) + data = [int((preambleLength >> 8) & 0xFF), int(preambleLength & 0xFF), + headerType, payloadLength, crcType, invertIQ] + return self.SPIwriteCommand([SX126X_CMD_SET_PACKET_PARAMS], 1, data, 6) + + def setPacketParamsFSK(self, preambleLength, crcType, syncWordLength, addrComp, whitening, packetType=SX126X_GFSK_PACKET_VARIABLE, payloadLength=0xFF, preambleDetectorLength=SX126X_GFSK_PREAMBLE_DETECT_16): + data = [int((preambleLength >> 8) & 0xFF), int(preambleLength & 0xFF), + preambleDetectorLength, syncWordLength, addrComp, + packetType, payloadLength, crcType, whitening] + return self.SPIwriteCommand([SX126X_CMD_SET_PACKET_PARAMS], 1, data, 9) + + def setBufferBaseAddress(self, txBaseAddress=0x00, rxBaseAddress=0x00): + data = [txBaseAddress, rxBaseAddress] + return self.SPIwriteCommand([SX126X_CMD_SET_BUFFER_BASE_ADDRESS], 1, data, 2) + + def setRegulatorMode(self, mode): + data = [mode] + return self.SPIwriteCommand([SX126X_CMD_SET_REGULATOR_MODE], 1, data, 1) + + def getStatus(self): + data = bytearray(1) + data_mv = memoryview(data) + self.SPIreadCommand([SX126X_CMD_GET_STATUS], 1, data_mv, 1) + return data[0] + + def getPacketStatus(self): + data = bytearray(3) + data_mv = memoryview(data) + self.SPIreadCommand([SX126X_CMD_GET_PACKET_STATUS], 1, data_mv, 3) + return (data[0] << 16) | (data[1] << 8) | data[2] + + def getDeviceErrors(self): + data = bytearray(2) + data_mv = memoryview(data) + self.SPIreadCommand([SX126X_CMD_GET_DEVICE_ERRORS], 1, data_mv, 2) + opError = ((data[0] & 0xFF) << 8) & data[1] + return opError + + def clearDeviceErrors(self): + data = [SX126X_CMD_NOP, SX126X_CMD_NOP] + return self.SPIwriteCommand([SX126X_CMD_CLEAR_DEVICE_ERRORS], 1, data, 2) + + def setFrequencyRaw(self, freq): + frf = int((freq * (1 << SX126X_DIV_EXPONENT)) / SX126X_CRYSTAL_FREQ) + return self.setRfFrequency(frf) + + def fixSensitivity(self): + sensitivityConfig = bytearray(1) + sensitivityConfig_mv = memoryview(sensitivityConfig) + state = self.readRegister(SX126X_REG_SENSITIVITY_CONFIG, sensitivityConfig_mv, 1) + ASSERT(state) + + if self.getPacketType() == SX126X_PACKET_TYPE_LORA and abs(self._bwKhz - 500.0) <= 0.001: + sensitivityConfig_mv[0] &= 0xFB + else: + sensitivityConfig_mv[0] |= 0x04 + return self.writeRegister(SX126X_REG_SENSITIVITY_CONFIG, sensitivityConfig, 1) + + def fixPaClamping(self): + clampConfig = bytearray(1) + clampConfig_mv = memoryview(clampConfig) + state = self.readRegister(SX126X_REG_TX_CLAMP_CONFIG, clampConfig_mv, 1) + ASSERT(state) + + clampConfig_mv[0] |= 0x1E + return self.writeRegister(SX126X_REG_TX_CLAMP_CONFIG, clampConfig, 1) + + def fixImplicitTimeout(self): + if not (self._headerType == SX126X_LORA_HEADER_IMPLICIT and self.getPacketType() == SX126X_PACKET_TYPE_LORA): + return ERR_WRONG_MODEM + + rtcStop = [0x00] + state = self.writeRegister(SX126X_REG_RTC_STOP, rtcStop, 1) + ASSERT(state) + + rtcEvent = bytearray(1) + rtcEvent_mv = memoryview(rtcEvent) + state = self.readRegister(SX126X_REG_RTC_EVENT, rtcEvent_mv, 1) + ASSERT(state) + + rtcEvent_mv[0] |= 0x02 + return self.writeRegister(SX126X_REG_RTC_EVENT, rtcEvent, 1) + + def fixInvertedIQ(self, iqConfig): + iqConfigCurrent = bytearray(1) + iqConfigCurrent_mv = memoryview(iqConfigCurrent) + state = self.readRegister(SX126X_REG_IQ_CONFIG, iqConfigCurrent_mv, 1) + ASSERT(state) + + if iqConfig == SX126X_LORA_IQ_STANDARD: + iqConfigCurrent_mv[0] &= 0xFB + else: + iqConfigCurrent_mv[0] |= 0x04 + + return self.writeRegister(SX126X_REG_IQ_CONFIG, iqConfigCurrent, 1) + + def config(self, modem): + state = self.setBufferBaseAddress() + ASSERT(state) + + data = [0,0,0,0,0,0,0] + data[0] = modem + state = self.SPIwriteCommand([SX126X_CMD_SET_PACKET_TYPE], 1, data, 1) + ASSERT(state) + + data[0] = SX126X_RX_TX_FALLBACK_MODE_STDBY_RC + state = self.SPIwriteCommand([SX126X_CMD_SET_RX_TX_FALLBACK_MODE], 1, data, 1) + ASSERT(state) + + data[0] = SX126X_CAD_ON_8_SYMB + data[1] = self._sf + 13 + data[2] = 10 + data[3] = SX126X_CAD_GOTO_STDBY + data[4] = 0x00 + data[5] = 0x00 + data[6] = 0x00 + state = self.SPIwriteCommand([SX126X_CMD_SET_CAD_PARAMS], 1, data, 7) + ASSERT(state) + + state = self.clearIrqStatus() + state |= self.setDioIrqParams(SX126X_IRQ_NONE, SX126X_IRQ_NONE) + ASSERT(state) + + data[0] = SX126X_CALIBRATE_ALL + state = self.SPIwriteCommand([SX126X_CMD_CALIBRATE], 1, data, 1) + ASSERT(state) + + sleep_ms(5) + + if implementation.name == 'micropython': + while self.gpio.value(): + yield_() + + if implementation.name == 'circuitpython': + while self.gpio.value: + yield_() + + return ERR_NONE + + def SPIwriteCommand(self, cmd, cmdLen, data, numBytes, waitForBusy=True): + return self.SPItransfer(cmd, cmdLen, True, data, [], numBytes, waitForBusy) + + def SPIreadCommand(self, cmd, cmdLen, data, numBytes, waitForBusy=True): + return self.SPItransfer(cmd, cmdLen, False, [], data, numBytes, waitForBusy) + + def SPItransfer(self, cmd, cmdLen, write, dataOut, dataIn, numBytes, waitForBusy, timeout=5000): + if implementation.name == 'micropython': + self.cs.value(0) + + start = ticks_ms() + while self.gpio.value(): + yield_() + if abs(ticks_diff(start, ticks_ms())) >= timeout: + self.cs.value(1) + return ERR_SPI_CMD_TIMEOUT + + for i in range(cmdLen): + self.spi.write(bytes([cmd[i]])) + + if implementation.name == 'circuitpython': + while not self.spi.try_lock(): + pass + self.cs.value = False + + start = ticks_ms() + while self.gpio.value: + yield_() + if abs(ticks_diff(start, ticks_ms())) >= timeout: + self.cs.value = True + self.spi.unlock() + return ERR_SPI_CMD_TIMEOUT + + for i in range(cmdLen): + self.spi.write(bytes([cmd[i]])) + + in_ = bytearray(1) + + status = 0 + + if write: + for i in range(numBytes): + if implementation.name == 'micropython': + try: + in_ = self.spi.read(1, dataOut[i]) + except: + in_ = self.spi.read(1, write=dataOut[i]) + + if implementation.name == 'circuitpython': + self.spi.write_readinto(bytes([dataOut[i]]), in_) + + if (in_[0] & 0b00001110) == SX126X_STATUS_CMD_TIMEOUT or\ + (in_[0] & 0b00001110) == SX126X_STATUS_CMD_INVALID or\ + (in_[0] & 0b00001110) == SX126X_STATUS_CMD_FAILED: + status = in_[0] & 0b00001110 + break + elif (in_[0] == 0x00) or (in_[0] == 0xFF): + status = SX126X_STATUS_SPI_FAILED + break + else: + if implementation.name == 'micropython': + try: + in_ = self.spi.read(1, SX126X_CMD_NOP) + except: + in_ = self.spi.read(1, write=SX126X_CMD_NOP) + + if implementation.name == 'circuitpython': + self.spi.readinto(in_) + + if (in_[0] & 0b00001110) == SX126X_STATUS_CMD_TIMEOUT or\ + (in_[0] & 0b00001110) == SX126X_STATUS_CMD_INVALID or\ + (in_[0] & 0b00001110) == SX126X_STATUS_CMD_FAILED: + status = in_[0] & 0b00001110 + elif (in_[0] == 0x00) or (in_[0] == 0xFF): + status = SX126X_STATUS_SPI_FAILED + else: + if implementation.name == 'micropython': + for i in range(numBytes): + try: + dataIn[i] = self.spi.read(1, SX126X_CMD_NOP)[0] + except: + dataIn[i] = self.spi.read(1, write=SX126X_CMD_NOP)[0] + + if implementation.name == 'circuitpython': + for i in range(numBytes): + self.spi.readinto(in_) + dataIn[i] = in_[0] + + if implementation.name == 'micropython': + self.cs.value(1) + + if implementation.name == 'circuitpython': + self.cs.value = True + self.spi.unlock() + + if waitForBusy: + sleep_us(1) + start = ticks_ms() + if implementation.name == 'micropython': + while self.gpio.value(): + yield_() + if abs(ticks_diff(start, ticks_ms())) >= timeout: + status = SX126X_STATUS_CMD_TIMEOUT + break + + if implementation.name == 'circuitpython': + while self.gpio.value: + yield_() + if abs(ticks_diff(start, ticks_ms())) >= timeout: + status = SX126X_STATUS_CMD_TIMEOUT + break + + switch = {SX126X_STATUS_CMD_TIMEOUT: ERR_SPI_CMD_TIMEOUT, + SX126X_STATUS_CMD_INVALID: ERR_SPI_CMD_INVALID, + SX126X_STATUS_CMD_FAILED: ERR_SPI_CMD_FAILED, + SX126X_STATUS_SPI_FAILED: ERR_CHIP_NOT_FOUND} + try: + return switch[status] + except: + return ERR_NONE diff --git a/micro_ondes/esp_lora/main.py b/micro_ondes/esp_lora/main.py index e69de29..9a1c695 100644 --- a/micro_ondes/esp_lora/main.py +++ b/micro_ondes/esp_lora/main.py @@ -0,0 +1,57 @@ +import _thread +from machine import Pin +from shared import get_lora +from shared import deviceTypes +from shared import config +import time + +# --- Configuration Matérielle --- +vext = Pin(19, Pin.OUT) +vext.value(0) +time.sleep_ms(100) + +# --- Lecture de l'ID unique de l'ESP --- +try: + with open("device_id.txt", "r") as f: + DEVICE_ID = f.read().strip() +except Exception: + DEVICE_ID = "ESP32_Inconnu" + +# --- Initialisation LoRa --- +lora = get_lora() +lora.configure(freq=868.1, sf=7) + +print(f"ESP32 initialisé avec l'ID : '{DEVICE_ID}' (Type : {deviceTypes.DEVICE_TYPES['MICROWAVE']})") + +def heartbeat_loop(): + while True: + print(f"\nESP32 : Envoi du Heartbeat...") + # Envoi périodique + ping_payload = { + "id": DEVICE_ID, + "type": deviceTypes.DEVICE_TYPES["MICROWAVE"] + } + lora.send(ping_payload) + + # Le receive_packet est maintenant protégé par le lock dans lora_device + # Si le main thread utilise la radio, ce thread attendra son tour + paquet = lora.receive_packet(timeout_ms=2000) + + if paquet and not paquet["raw"]: + donnees = paquet["data"] + # Vérification si le paquet reçu est bien la réponse attendue de l'orchestrateur + if donnees.get("type") == deviceTypes.DEVICE_TYPES["ORCHESTRATOR"]: + print(f"ESP32 : Réponse reçue de l'orchestrateur '{donnees.get('id')}' ! [Statut: ALIVE]") + else: + print(f"ESP32 : Paquet reçu d'un type inattendu : {donnees.get('type')}") + else: + print("ESP32 : Pas de réponse de l'orchestrateur (Le RPI est-il éteint ?)") + + time.sleep(config.HEARTBEAT_INTERVAL) + +# Lancer la boucle de heartbeat dans un thread séparé +_thread.start_new_thread(heartbeat_loop, ()) + +while True: + # Fait rien pour l'instant + time.sleep(1) \ No newline at end of file diff --git a/micro_ondes/esp_lora/tests/main_lora.py b/micro_ondes/esp_lora/tests/main_lora.py new file mode 100644 index 0000000..0c0f4ac --- /dev/null +++ b/micro_ondes/esp_lora/tests/main_lora.py @@ -0,0 +1,28 @@ +from machine import Pin +from shared import get_lora +import time + +vext = Pin(19, Pin.OUT) +vext.value(0) +time.sleep_ms(100) + +lora = get_lora() +lora.configure(freq=868.1, sf=7) + +while True: + print("\nESP32 : Envoi des mesures...") + mesures = {"id": "ESP32_Salon", "temp": 22.4, "hum": 55.2} + + # Envoi direct (le pilote s'occupe de mettre le groupe \x02) + lora.send(b'\x02' + lora.send_json_bytes_helper if False else bytes([2]) + lora.send_helper if False else b'\x02' + __import__('ujson').dumps(mesures).encode('utf-8')) + + # Réception propre + paquet = lora.receive_packet(3000) + if paquet: + # paquet est un dict : {"group": 2, "data": {...}, "raw": False} + print(f"ESP32 : Message reçu du groupe {paquet['group']}") + print(f"ESP32 : Données utiles -> {paquet['data']}") + else: + print("ESP32 : Pas de réponse.") + + time.sleep(5) \ No newline at end of file diff --git a/micro_ondes/esp_lora/tests/main_oled.py b/micro_ondes/esp_lora/tests/main_oled.py new file mode 100644 index 0000000..4182bd2 --- /dev/null +++ b/micro_ondes/esp_lora/tests/main_oled.py @@ -0,0 +1,42 @@ +from machine import Pin, SoftI2C +import framebuf +import ssd1306 +import time + + +image_cards_hearts_bits = bytearray(b'\x00\x00\x00\x0088||\xfe\xfe\xff\xfe\xff\xfe\xff\xfe\x7f\xfc?\xf8\x1f\xf0\x0f\xe0\x07\xc0\x03\x80\x01\x00\x00\x00') +image_clock_solid_full_bits = bytearray(b'\x00\x1f\x00\x00\x00\xff\xe0\x00\x03\xff\xf8\x00\x07\xff\xfc\x00\x0f\xff\xfe\x00\x1f\xff\xff\x00?\xf3\xff\x00?\xf3\xff\x80\x7f\xf3\xff\x80\x7f\xf3\xff\xc0\x7f\xf3\xff\xc0\xff\xf3\xff\xc0\xff\xf3\xff\xc0\xff\xf1\xff\xc0\xff\xf8\x7f\xc0\xff\xfe?\xc0\x7f\xff\x1f\xc0\x7f\xff\xdf\xc0\x7f\xff\xff\x80?\xff\xff\x80?\xff\xff\x00\x1f\xff\xff\x00\x0f\xff\xfe\x00\x07\xff\xfc\x00\x03\xff\xf0\x00\x00\x7f\xc0\x00') + +def draw(): + # Layer 1 + display.rect(0, 0, 128, 64, 1) + + # Layer 2 + display.text("HELP", 1, 2, 1) + + # cards_hearts + fb_image_cards_hearts_bits = framebuf.FrameBuffer(image_cards_hearts_bits, 15, 16, framebuf.MONO_HLSB) + display.blit(fb_image_cards_hearts_bits, 111, 47) + + # clock_solid_full + fb_image_clock_solid_full_bits = framebuf.FrameBuffer(image_clock_solid_full_bits, 26, 26, framebuf.MONO_HLSB) + display.blit(fb_image_clock_solid_full_bits, 51, 19) + + display.show() + + + +# 3. Initialize I2C (using Software I2C for better stability on ESP32-S3 pins) +# SDA is GPIO 17, SCL is GPIO 18 +scl_pin = Pin(18, Pin.OUT, pull=Pin.PULL_UP) +sda_pin = Pin(17, Pin.OUT, pull=Pin.PULL_UP) +i2c = SoftI2C(scl=scl_pin, sda=sda_pin, freq=100000) + +# 4. Bind the SSD1306 driver to our I2C bus (default address is 0x3C) +# Display is 128x64 pixels +display = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C) + +# 5. Clear and write Hello World! +draw() + +print("Hello World printed to OLED successfully!") \ No newline at end of file diff --git a/orchestrateur/README.md b/orchestrateur/README.md index 5aa823c..d32e0a7 100644 --- a/orchestrateur/README.md +++ b/orchestrateur/README.md @@ -1,5 +1,5 @@ # IP Adresses -- AP (dynamic) : `10.85.213.190` +- AP (dynamic) : `10.110.0.190` - Generated Wifi : `192.168.50.1` \ No newline at end of file diff --git a/orchestrateur/compose.yaml b/orchestrateur/compose.yaml index 38bdea2..c064870 100644 --- a/orchestrateur/compose.yaml +++ b/orchestrateur/compose.yaml @@ -4,7 +4,7 @@ services: environment: MQTT_TLS_ENABLED: ${MQTT_TLS_ENABLED:-true} ports: - - "8884:8884" + - "192.168.50.1:8884:8884" volumes: - ./mqtt/mosquitto-tls.conf:/mosquitto/config/mosquitto-tls.conf:ro - ./mqtt/mosquitto-plain.conf:/mosquitto/config/mosquitto-plain.conf:ro diff --git a/orchestrateur/device_id.txt b/orchestrateur/device_id.txt new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/orchestrateur/device_id.txt @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/orchestrateur/launch.sh b/orchestrateur/launch.sh index e56d6cf..6968720 100644 --- a/orchestrateur/launch.sh +++ b/orchestrateur/launch.sh @@ -8,18 +8,43 @@ PYTHON_SCRIPT="${1:-$SCRIPT_DIR/main.py}" REQUIREMENTS_FILE="$REPO_ROOT/requirements.txt" if [ ! -f "$PYTHON_SCRIPT" ]; then - echo "Python script not found: $PYTHON_SCRIPT" >&2 - exit 1 + echo "Python script not found: $PYTHON_SCRIPT" >&2 + exit 1 fi -export PYTHONPATH="$REPO_ROOT${PYTHONPATH:+:$PYTHONPATH}" +# 1. On définit proprement le PYTHONPATH pour tout le script +export PYTHONPATH="$REPO_ROOT" + cd "$SCRIPT_DIR" -docker compose pull -docker compose up -d +# 2. On lance Docker en arrière-plan +echo "Démarrage des conteneurs Docker..." +docker compose pull +docker compose up -d --remove-orphans + +# 3. Installation des dépendances (sans '--user' si on est déjà root sous systemd) if [ -f "$REQUIREMENTS_FILE" ]; then - "$PYTHON_BIN" -m pip install --user -r "$REQUIREMENTS_FILE" + echo "Vérification des dépendances Python..." + if [ "$(id -u)" -eq 0 ]; then + "$PYTHON_BIN" -m pip install -r "$REQUIREMENTS_FILE" + else + "$PYTHON_BIN" -m pip install --user -r "$REQUIREMENTS_FILE" + fi fi +# 4. Sécurité anti-conflit : On vérifie si Docker n'a pas verrouillé le port USB de la LA66 +PORT="/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0" +if [ -e "$PORT" ]; then + echo "Vérification de la disponibilité du port série..." + # Si le port est occupé, on force sa libération en tuant le processus docker/screen persistant + if lsof "$PORT" >/dev/null 2>&1; then + echo "Le port série est occupé ! Libération en cours..." + fuser -k "$PORT" || true + sleep 1 + fi +fi + +# 5. Lancement propre du script Python +echo "Launching Python script: $PYTHON_SCRIPT" cd "$REPO_ROOT" -exec "$PYTHON_BIN" "$PYTHON_SCRIPT" +exec "$PYTHON_BIN" "$PYTHON_SCRIPT" \ No newline at end of file diff --git a/orchestrateur/main.py b/orchestrateur/main.py index e69de29..78d1bbc 100644 --- a/orchestrateur/main.py +++ b/orchestrateur/main.py @@ -0,0 +1,69 @@ +import threading +import queue +import time +from shared import get_lora, deviceTypes + +# --- Lecture de l'ID unique du Raspberry Pi --- +try: + with open("device_id.txt", "r") as f: + DEVICE_ID = f.read().strip() +except Exception: + # Alternative si le script est lancé depuis un autre dossier + try: + with open("/home/pi/SmartWave/orchestrateur/device_id.txt", "r") as f: + DEVICE_ID = f.read().strip() + except Exception: + DEVICE_ID = "RPI_Orchestrateur_Default" + +# Création de la file d'attente pour les messages (thread-safe) +data_queue = queue.Queue() + +lora = get_lora() +lora.configure() + +def lora_listener(): + """Thread de fond : écoute en permanence et répond aux Heartbeats.""" + print("Thread Écouteur démarré.") + while True: + # On attend un paquet (timeout court pour rester réactif) + paquet = lora.receive_packet(timeout_ms=1000) + + if paquet: + donnees = paquet["data"] + expediteur_type = donnees.get("type") + + # --- Cas 1 : Gestion automatique du Heartbeat --- + if expediteur_type == deviceTypes.DEVICE_TYPES["MICROWAVE"]: + print(f"\n[Thread Fond] Heartbeat reçu de {donnees.get('id')}") + + reponse = { + "id": DEVICE_ID, # Ou lecture de ton fichier device_id.txt + "type": deviceTypes.DEVICE_TYPES["ORCHESTRATOR"] + } + lora.send(reponse) + + # --- Cas 2 : Donnée applicative, on l'envoie vers le thread principal --- + else: + data_queue.put(paquet) + +# 2. Lancement du thread d'écoute +listener_thread = threading.Thread(target=lora_listener, daemon=True) +listener_thread.start() + +# 3. Boucle principale (Main Thread) : tu es libre de faire autre chose ! +print("Orchestrateur prêt. Le main loop est libre.") +while True: + try: + # On regarde si on a reçu des données applicatives (non-heartbeat) + # On utilise block=False pour ne pas bloquer si la queue est vide + try: + msg = data_queue.get(block=False) + print(f"\n[Main Loop] Données traitées : {msg['data']}") + except queue.Empty: + pass # Rien à traiter, on fait autre chose... + + # Ici tu peux faire tes autres tâches + time.sleep(1) + + except KeyboardInterrupt: + break \ No newline at end of file diff --git a/orchestrateur/mqtt/mosquitto-plain.conf b/orchestrateur/mqtt/mosquitto-plain.conf index f9855df..faed2ca 100644 --- a/orchestrateur/mqtt/mosquitto-plain.conf +++ b/orchestrateur/mqtt/mosquitto-plain.conf @@ -8,5 +8,5 @@ log_type notice log_type information allow_anonymous true -listener 8884 +listener 8884 192.168.50.1 protocol mqtt \ No newline at end of file diff --git a/orchestrateur/mqtt/mosquitto-tls.conf b/orchestrateur/mqtt/mosquitto-tls.conf index 8b3cde3..8d58795 100644 --- a/orchestrateur/mqtt/mosquitto-tls.conf +++ b/orchestrateur/mqtt/mosquitto-tls.conf @@ -8,7 +8,7 @@ log_type notice log_type information allow_anonymous true -listener 8884 +listener 8884 192.168.50.1 protocol mqtt cafile /mosquitto/certs/ca.crt certfile /mosquitto/certs/server.crt diff --git a/orchestrateur/tests/main_lora.py b/orchestrateur/tests/main_lora.py new file mode 100644 index 0000000..835cfe2 --- /dev/null +++ b/orchestrateur/tests/main_lora.py @@ -0,0 +1,31 @@ +from shared import get_lora +import time + +lora = get_lora() +lora.configure() + +print("Raspberry Pi : En attente active de JSON...") + +while True: + paquet = lora.receive_packet(timeout_ms=5000) + if paquet: + # Plus besoin de décoder du HEX ou de parser du JSON manuellement ! + groupe = paquet['group'] + donnees = paquet['data'] + + print(f"\nRPI : Reçu du Groupe {groupe} -> {donnees}") + if not paquet['raw']: # C'est bien du JSON (un dictionnaire Python) + print(f"RPI : Température du capteur = {donnees.get('temp')}°C") + + # Préparation de la réponse (on passe directement un dictionnaire !) + reponse = { + "status": "ack", + "msg": "Donnees bien recues, merci !", + "time": int(time.time()) + } + + time.sleep(0.5) + print("RPI : Envoi de la réponse...") + lora.send(reponse) # Le pilote sérialise et encode en HEX à ta place ! + else: + print("RPI : Rien reçu.") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 266bbe9..309d736 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ paho-mqtt>=1.6,<3 +pyserial>=3.5,<4 diff --git a/shared/__init__.py b/shared/__init__.py index ec6c0b8..0f66a7e 100644 --- a/shared/__init__.py +++ b/shared/__init__.py @@ -1,6 +1,17 @@ +# shared/__init__.py """Shared helpers for the smartWave project.""" -from .db import DRIVER_NAME, Database, connect, execute, fetchall, fetchone -from .mqtt import BACKEND_NAME as MQTT_BACKEND_NAME, BrokerClient, connect as mqtt_connect, publish as mqtt_publish +import shared.deviceTypes as deviceTypes +import shared.config as config +def get_lora(*args, **kwargs): + from .lora_device import get_lora_device + return get_lora_device(*args, **kwargs) +def get_database(*args, **kwargs): + from .db import Database + return Database(*args, **kwargs) + +def get_mqtt_client(*args, **kwargs): + from .mqtt import BrokerClient + return BrokerClient(*args, **kwargs) \ No newline at end of file diff --git a/shared/config.py b/shared/config.py new file mode 100644 index 0000000..9afb033 --- /dev/null +++ b/shared/config.py @@ -0,0 +1,2 @@ + +HEARTBEAT_INTERVAL = 10 diff --git a/shared/deviceTypes.py b/shared/deviceTypes.py new file mode 100644 index 0000000..c1d0fa4 --- /dev/null +++ b/shared/deviceTypes.py @@ -0,0 +1,7 @@ + +# Un simple dictionnaire servant de registre des types d'appareils +DEVICE_TYPES = { + "MICROWAVE": "microwave", + "ORCHESTRATOR": "orchestrator", + "EXTERNAL_DISPLAY": "externalDisplay" +} \ No newline at end of file diff --git a/shared/lora_device.py b/shared/lora_device.py new file mode 100644 index 0000000..6cead42 --- /dev/null +++ b/shared/lora_device.py @@ -0,0 +1,183 @@ +import sys +import time + +IS_MICROPYTHON = sys.implementation.name == 'micropython' + +if IS_MICROPYTHON: + import _thread + from machine import Pin, SPI + import ubinascii + import ujson as json + + # --- PILOTE SPI DIRECT (ESP32 / Heltec V3) --- + class LoraHardwareSPI: + def __init__(self, spi_bus=1, clk=9, mosi=10, miso=11, cs=8, irq=14, rst=12, gpio=13): + from sx1262 import SX1262 + self.lora = SX1262( + spi_bus=spi_bus, clk=clk, mosi=mosi, miso=miso, + cs=cs, irq=irq, rst=rst, gpio=gpio + ) + self.default_group = 2 # On définit le groupe par défaut ici + self.lock = _thread.allocate_lock() # Création du verrou + + def configure(self, freq=868.1, bw=125.0, sf=7, cr=5, power=14): + self.lora.begin( + freq=freq, bw=bw, sf=sf, cr=cr, power=power, + useRegulatorLDO=False, crcOn=True, preambleLength=8, implicit=False + ) + self.lora.setSyncWord(0x14) + + def send(self, payload, group=None): + """Encode la payload en JSON si nécessaire, et injecte automatiquement l'octet de groupe.""" + with self.lock: + if group is None: + group = self.default_group + + # Si c'est un dictionnaire ou une liste, on le convertit en JSON textuel + if isinstance(payload, (dict, list)): + payload = json.dumps(payload) + + if isinstance(payload, str): + payload = payload.encode('utf-8') + + # Insertion automatique de l'octet de groupe au tout début de la trame physique + paquet_physique = bytes([group]) + payload + self.lora.send(paquet_physique) + + def receive_packet(self, timeout_ms=1000): + """Écoute, nettoie, extrait le groupe, gère le HEX et parse le JSON.""" + with self.lock: + data, state = self.lora.recv(len=0, timeout_en=True, timeout_ms=timeout_ms) + if state == 0 and len(data) > 1: + group = data[0] + payload_brute = data[1:].strip(b'\x00 \r\n\t') + + try: + text = payload_brute.decode('utf-8').strip('\x00 \r\n\t') + except UnicodeError: + return None + + if text.startswith('{') or text.startswith('['): + decoded_text = text + elif text.lower().startswith('7b') or text.lower().startswith('5b'): + try: + decoded_text = ubinascii.unhexlify(text).decode('utf-8').strip('\x00 \r\n\t') + except Exception: + decoded_text = text + else: + decoded_text = text + + try: + parsed_json = json.loads(decoded_text) + return {"group": group, "data": parsed_json, "raw": False} + except ValueError: + return {"group": group, "data": decoded_text, "raw": True} + + return None + +else: + import threading + import serial + import json + + # --- PILOTE SÉRIE (Raspberry Pi / Dragino LA66) --- + class LoraSerialAT: + def __init__(self, port): + self.port = port + self.ser = serial.Serial( + port=self.port, + baudrate=9600, + bytesize=serial.EIGHTBITS, + parity=serial.PARITY_NONE, + stopbits=serial.STOPBITS_ONE, + timeout=0.1 + ) + self.ser.reset_input_buffer() + self.ser.reset_output_buffer() + self.lock = threading.Lock() + def configure(self, **kwargs): + pass + + def send(self, payload): + """Encode automatiquement la payload en HEX pour l'envoi via la clé.""" + with self.lock: + if isinstance(payload, (dict, list)): + payload = json.dumps(payload) + + if isinstance(payload, str): + payload = payload.encode('utf-8') + + hex_payload = payload.hex() + self.ser.reset_input_buffer() + + # La clé ajoute d'elle-même l'octet de groupe configuré dans ses registres + cmd = f"AT+SEND=1,{hex_payload},1,3\r\n" + print(f"RPI : Envoi de la commande HEX -> AT+SEND=1,[HEX_DATA],1,3") + self.ser.write(cmd.encode('utf-8')) + + time.sleep(0.2) + response = "" + start_wait = time.time() + while (time.time() - start_wait) < 1.5: + if self.ser.in_waiting > 0: + response += self.ser.readline().decode('utf-8', errors='ignore') + time.sleep(0.05) + + print(f"[RPI LA66 TX STATUS] :\n{response.strip()}") + + def receive_packet(self, timeout_ms=5000): + with self.lock: + start_time = time.time() + timeout_s = timeout_ms / 1000.0 + + while (time.time() - start_time) < timeout_s: + if self.ser.in_waiting > 0: + line = self.ser.readline().decode('utf-8', errors='ignore').strip() + if line: + payload_bytes = None + + if "(HEX:)" in line: + hex_part = line.split("(HEX:)")[1].strip().replace(" ", "") + try: + payload_bytes = bytes.fromhex(hex_part) + except ValueError: + pass + elif "Data:" in line: + payload_bytes = line.split("Data:")[1].strip().encode('utf-8') + + if payload_bytes and len(payload_bytes) > 1: + group = payload_bytes[0] + payload_clean = payload_bytes[1:].strip(b'\x00 \r\n\t') + + try: + text = payload_clean.decode('utf-8').strip('\x00 \r\n\t') + except UnicodeError: + continue + + if text.startswith('{') or text.startswith('['): + decoded_text = text + elif text.lower().startswith('7b') or text.lower().startswith('5b'): + try: + decoded_text = bytes.fromhex(text).decode('utf-8').strip('\x00 \r\n\t') + except Exception: + decoded_text = text + else: + decoded_text = text + + try: + parsed_json = json.loads(decoded_text) + return {"group": group, "data": parsed_json, "raw": False} + except json.JSONDecodeError: + return {"group": group, "data": decoded_text, "raw": True} + + time.sleep(0.01) + return None + + +def get_lora_device(port_or_pins=None): + if IS_MICROPYTHON: + pins = port_or_pins if port_or_pins else {} + return LoraHardwareSPI(**pins) + else: + port = port_or_pins if port_or_pins else "/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0" + return LoraSerialAT(port) \ No newline at end of file diff --git a/tests/mqtt/cpython_example_listener_zephyrus.py b/tests/mqtt/cpython_example_listener_zephyrus.py new file mode 100644 index 0000000..f3430cc --- /dev/null +++ b/tests/mqtt/cpython_example_listener_zephyrus.py @@ -0,0 +1,64 @@ +"""Small CPython MQTT example. + +Listens and prints all received messages on the subscribed channel continuously. +Set MQTT_BROKER_HOST to the broker IP address, not localhost. +""" + +import os +import time +from pathlib import Path + +from shared.mqtt import BrokerClient + + +BROKER_HOST = os.getenv("MQTT_BROKER_HOST", "192.168.50.1") +TOPIC = "smartwave/demo" +USE_TLS = os.getenv("MQTT_CLIENT_TLS", "true").lower() in {"1", "true", "yes", "on"} +CA_FILE = os.getenv( + "MQTT_CA_FILE", + str(Path(__file__).resolve().parents[2] / "orchestrateur" / "mqtt" / "certs" / "ca.crt"), +) + + +def main(): + client = BrokerClient( + host=BROKER_HOST, + client_id="zephyrus", + use_tls=USE_TLS, + cafile=CA_FILE, + keepalive=30, + ) + + print(f"Connexion au broker: {BROKER_HOST}...") + client.connect() + + print(f"Souscription au topic: {TOPIC}...") + client.subscribe(TOPIC, qos=2) + + print("En attente de messages... (Appuyez sur Ctrl+C pour quitter)") + + try: + while True: + # Traite le réseau et récupère les paquets MQTT entrants + client.poll(0.1) + + # Récupère le message dans la file d'attente de la librairie + message = client.get_message() + if message is not None: + # Décodage du payload pour un affichage plus propre en texte + try: + payload_str = message['payload'].decode('utf-8') + except Exception: + payload_str = message['payload'] + + print(f"[{message['topic']}] reçu : {payload_str}") + + except KeyboardInterrupt: + print("\nArrêt utilisateur") + finally: + client.close() + print("Connexion MQTT fermée.") + + +if __name__ == "__main__": + main() \ No newline at end of file