├── .gitignore ├── extras ├── reports │ └── weather_report.pdf ├── BLETest │ ├── BLETest.ino │ └── BleSensors.h ├── OneWireTest │ └── OneWireTest.ino ├── time_rp2040_sleep │ ├── src │ │ ├── rosc.c │ │ ├── pico_rtc_utils.h │ │ ├── pico_rosc.h │ │ ├── pico_sleep.h │ │ ├── logging.h │ │ ├── pico_rtc_utils.cpp │ │ └── sleep.c │ └── time_rp2040_sleep.ino ├── customization │ ├── AppLayerMinimal.cpp │ └── AppLayerMinimal.h ├── time_esp32_sleep │ └── time_esp32_sleep.ino ├── time_set_epoch │ └── time_set_epoch.ino ├── time_y2038 │ └── time_y2038.ino ├── time_set_string │ └── time_set_string.ino └── RTCSet │ └── RTCSet.ino ├── docs └── influxdb_integration │ ├── images │ ├── image-00.png │ ├── image-01.png │ ├── image-02.png │ ├── image-03.png │ ├── image-04.png │ ├── image-05.png │ ├── image-06.png │ ├── image-07.png │ ├── image-08.png │ ├── image-09.png │ ├── image-10.png │ ├── image-11.png │ ├── image-12.png │ ├── image-13.png │ ├── image-14.png │ ├── image-15.png │ ├── image-16.png │ ├── image-17.png │ ├── image-18.png │ └── image-19.png │ └── influxdb_integration.md ├── .github ├── workflows │ ├── arduino-lint.yml │ ├── spell-check-exclude.txt │ ├── spell-check.yml │ ├── publish-codec-npm.yml │ ├── doxygen.yml │ └── CI.yml ├── dependabot.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── data ├── secrets.json └── node_config.json ├── scripts ├── metadata.json ├── bresserweathersensorlw-codec │ ├── package.json │ ├── metainfo.json │ ├── README.md │ └── index.js ├── helium_message_template.json └── examples.json ├── Debug_Output.md ├── secrets.h ├── LICENSE ├── package.json ├── doxygen2keywords.xsl ├── src ├── LoadSecrets.h ├── PayloadAnalog.h ├── rp2040 │ ├── rosc.c │ ├── pico_rtc_utils.h │ ├── pico_rosc.h │ ├── pico_sleep.h │ ├── pico_rtc_utils.cpp │ └── sleep.c ├── adc │ ├── adc.h │ └── adc.cpp ├── PayloadOneWire.h ├── PayloadAnalog.cpp ├── PayloadDigital.h ├── LoadNodeCfg.h ├── PayloadOneWire.cpp ├── PayloadBLE.h ├── PayloadDigital.cpp ├── LoadSecrets.cpp ├── logging.h ├── BleSensors │ └── BleSensors.h └── PayloadBresser.h ├── keywords.txt ├── doxygen_header.html └── CODE_OF_CONDUCT.md /.gitignore: -------------------------------------------------------------------------------- 1 | secrets.h 2 | secrets.json 3 | credentials.* 4 | -------------------------------------------------------------------------------- /extras/reports/weather_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/extras/reports/weather_report.pdf -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-00.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-01.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-02.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-03.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-04.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-05.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-06.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-07.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-08.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-09.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-10.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-11.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-12.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-13.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-14.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-15.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-16.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-17.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-18.png -------------------------------------------------------------------------------- /docs/influxdb_integration/images/image-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthias-bs/BresserWeatherSensorLW/HEAD/docs/influxdb_integration/images/image-19.png -------------------------------------------------------------------------------- /.github/workflows/arduino-lint.yml: -------------------------------------------------------------------------------- 1 | name: Arduino-lint 2 | 3 | on: [push, pull_request] 4 | jobs: 5 | lint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v5 9 | - uses: arduino/arduino-lint-action@v2 10 | with: 11 | library-manager: false 12 | project-type: sketch 13 | # compliance: strict 14 | -------------------------------------------------------------------------------- /.github/workflows/spell-check-exclude.txt: -------------------------------------------------------------------------------- 1 | DistanceSensor_A02YYUW_MEASSUREMENT_STATUS dstStatus; 2 | if (dstStatus != DistanceSensor_A02YYUW_MEASSUREMENT_STATUS_OK) 3 | (dstStatus != DistanceSensor_A02YYUW_MEASSUREMENT_STATUS_OK) && 4 | if (dstStatus == DistanceSensor_A02YYUW_MEASSUREMENT_STATUS_OK) 5 | dstStatus = distanceSensor.meassure(); 6 | // by Linar Yusupov 7 | -------------------------------------------------------------------------------- /data/secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "joinEUI": "0x0000000000000000", 3 | "devEUI": "0x123456789ABCDEF0", 4 | "nwkKey": ["0x11", "0x22", "0x33", "0x44", "0x55", "0x66", "0x77", "0x88", "0x99", "0xAA", "0xBB", "0xCC", "0xDD", "0xEE", "0xFF", "0x00"], 5 | "appKey": ["0x10", "0x20", "0x30", "0x40", "0x50", "0x60", "0x70", "0x80", "0x90", "0xA0", "0xB0", "0xC0", "0xD0", "0xE0", "0xF0", "0xFF"] 6 | } 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /scripts/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "codecId": "bresser-weather-sensor-lw", 3 | "description": "BresserWeatherSensorLW Payload Codec", 4 | "version": "0.1.0", 5 | "author": { 6 | "name": "Matthias Prinke", 7 | "email": "83612361+matthias-bs@users.noreply.github.com", 8 | "url": "https://github.com/matthias-bs" 9 | }, 10 | "codec": { 11 | "encodeDownlink": true, 12 | "decodeDownlink": true, 13 | "examples": true, 14 | "jsonSchemas": false 15 | }, 16 | "main": "index.js", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/matthias-bs/BresserWeatherSensorLW" 20 | }, 21 | "license": "MIT", 22 | "specVersion": "1.0.0" 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /data/node_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "timezone": "CET-1CEST,M3.5.0,M10.5.0/3", 3 | "voltage_eco_exit": 3581, 4 | "voltage_eco_enter": 2501, 5 | "voltage_critical": 3201, 6 | "battery_discharge_lim": 3201, 7 | "battery_charge_lim": 4201, 8 | "powerfeather": { 9 | "battery_capacity": 2201, 10 | "supply_maintain_voltage": 4600, 11 | "max_charge_current": 1000, 12 | "soc_eco_enter": 20, 13 | "soc_eco_exit": 25, 14 | "soc_critical": 3, 15 | "temperature_measurement": false, 16 | "battery_fuel_gauge": true 17 | }, 18 | "m5stack": { 19 | "soc_eco_enter": 20, 20 | "soc_eco_exit": 25, 21 | "soc_critical": 3 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/spell-check.yml: -------------------------------------------------------------------------------- 1 | name: Spell Check 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | spell-check: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v5 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v6 22 | with: 23 | python-version: '3.x' 24 | 25 | - name: Install codespell 26 | run: | 27 | python -m pip install --upgrade pip 28 | pip install codespell 29 | 30 | - name: Run codespell 31 | run: | 32 | codespell --ignore-words-list="thru" --skip="*.pdf" --exclude-file=.github/workflows/spell-check-exclude.txt . 33 | -------------------------------------------------------------------------------- /extras/BLETest/BLETest.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "BleSensors.h" 3 | 4 | // List of known sensors' BLE addresses 5 | #define KNOWN_BLE_ADDRESSES \ 6 | { \ 7 | "a4:c1:38:b8:1f:7f" \ 8 | } 9 | 10 | std::vector knownBLEAddresses; 11 | 12 | void setup() 13 | { 14 | Serial.begin(115200); 15 | Serial.setDebugOutput(true); 16 | Serial.println("BLETest"); 17 | 18 | knownBLEAddresses = KNOWN_BLE_ADDRESSES; 19 | BleSensors bleSensors = BleSensors(knownBLEAddresses); 20 | unsigned ble_scantime = 31; 21 | bool ble_active = false; 22 | 23 | for (const std::string &s : knownBLEAddresses) 24 | { 25 | (void)s; 26 | log_d("%s", s.c_str()); 27 | } 28 | 29 | bleSensors.getData(ble_scantime, ble_active); 30 | } 31 | 32 | void loop() 33 | { 34 | delay(100); 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/publish-codec-npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish NPM Package 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v5 15 | 16 | - name: Set up Node.js 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: '20' 20 | registry-url: 'https://registry.npmjs.org' 21 | 22 | - name: Install dependencies 23 | run: npm install 24 | working-directory: scripts/bresserweathersensorlw-codec 25 | 26 | - name: Run tests 27 | run: npm test 28 | working-directory: scripts/bresserweathersensorlw-codec 29 | 30 | - name: Publish to NPM 31 | run: npm publish --access public 32 | working-directory: scripts/bresserweathersensorlw-codec 33 | env: 34 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 35 | -------------------------------------------------------------------------------- /Debug_Output.md: -------------------------------------------------------------------------------- 1 | # Debug Output Configuration in Arduino IDE 2 | 3 | ## ESP32 4 | 5 | 1. Select appropriate (USB-)serial port for your board 6 | 7 | ![Arduino_IDE-Tools_Port](https://github.com/matthias-bs/BresserWeatherSensorTTN/assets/83612361/be496bf8-89ce-4db5-b1bf-c88a7f5e99cb) 8 | 9 | **or** 10 | 11 | ![Arduino_IDE-Select_Other_Board_and_Port](https://github.com/matthias-bs/BresserWeatherSensorTTN/assets/83612361/ac847f23-4fe6-4111-929f-ac6d36cb8a53) 12 | 13 | 2. Select desired debug level 14 | 15 | ![Arduino_IDE-Tools_CoreDebugLevel](https://github.com/matthias-bs/BresserWeatherSensorTTN/assets/83612361/72a8b1d9-8d39-41fc-9658-78b432b73d56) 16 | 17 | This passes the define `CORE_DEBUG_LEVEL`to the compiler accordingly. 18 | 19 | Refer to the following for some background information 20 | * https://thingpulse.com/esp32-logging/ 21 | * https://www.mischianti.org/2020/09/20/esp32-manage-multiple-serial-and-logging-for-debugging-3/ 22 | * https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-log.h 23 | -------------------------------------------------------------------------------- /secrets.h: -------------------------------------------------------------------------------- 1 | // JoinEUI (LoRaWAN v1.1.0) or AppEUI (LoRaWAN v1.0.4) 2 | // for development purposes you can use all zeros - see RadioLib wiki for details 3 | #define RADIOLIB_LORAWAN_JOIN_EUI 0x0000000000000000 4 | 5 | // The Device EUI & two keys can be generated on the TTN console (or any other LoRaWAN Network Service Provider's console) 6 | #pragma message("Replace the dummy values for RADIOLIB_LORAWAN_DEV_EUI, RADIOLIB_LORAWAN_APP_KEY and RADIOLIB_LORAWAN_NWK_KEY by your own credentials.") 7 | #ifndef RADIOLIB_LORAWAN_DEV_EUI // Replace with your Device EUI 8 | #define RADIOLIB_LORAWAN_DEV_EUI 0x0000000000000000 9 | #endif 10 | #ifndef RADIOLIB_LORAWAN_APP_KEY // Replace with your App Key 11 | #define RADIOLIB_LORAWAN_APP_KEY 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 12 | #endif 13 | #ifndef RADIOLIB_LORAWAN_NWK_KEY // Put your Nwk Key here 14 | #define RADIOLIB_LORAWAN_NWK_KEY 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 15 | #endif 16 | -------------------------------------------------------------------------------- /scripts/bresserweathersensorlw-codec/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bresserweathersensorlw-codec", 3 | "version": "1.1.2", 4 | "description": "LoRaWAN Codec API compliant codec for BresserWeatherSensorLW", 5 | "main": "index.js", 6 | "keywords": [ 7 | "lorawan", 8 | "codec", 9 | "uplink", 10 | "downlink", 11 | "bresser", 12 | "weather-sensor", 13 | "thethingsnetwork", 14 | "ttn", 15 | "helium-network" 16 | ], 17 | "author": "Matthias Prinke", 18 | "license": "MIT", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/matthias-bs/BresserWeatherSensorLW" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/matthias-bs/BresserWeatherSensorLW/issues" 25 | }, 26 | "homepage": "https://github.com/matthias-bs/BresserWeatherSensorLW", 27 | "files": [ 28 | "./index.js", 29 | "./downlink_formatter.js", 30 | "./uplink_formatter.js", 31 | "./examples.json", 32 | "./metainfo.json", 33 | "./test/codec.test.js" 34 | ], 35 | "scripts": { 36 | "test": "node --test" 37 | } 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Matthias Prinke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/doxygen.yml: -------------------------------------------------------------------------------- 1 | name: Doxygen 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | workflow_dispatch: 7 | permissions: 8 | contents: write 9 | jobs: 10 | doxygen: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Install Doxygen and Xsltproc 14 | run: | 15 | sudo apt-get update 16 | sudo apt-get install -y doxygen 17 | sudo apt-get install -y graphviz 18 | sudo apt-get install xsltproc 19 | - uses: actions/checkout@v5 20 | 21 | - name: Generate docs 22 | run: doxygen Doxyfile 23 | 24 | - name: Generate keywords.txt 25 | run: | 26 | xsltproc doxygen2keywords.xsl docs/xml/index.xml >keywords.txt 27 | echo "-----------------------------------------------------------" 28 | cat keywords.txt 29 | echo "-----------------------------------------------------------" 30 | 31 | - name: Copy confighelper.html 32 | run: | 33 | cp extras/confighelper/confighelper.html docs/html/ 34 | 35 | - name: Deploy to GitHub Pages 36 | uses: JamesIves/github-pages-deploy-action@releases/v4 37 | with: 38 | BRANCH: gh-pages 39 | FOLDER: docs/html 40 | 41 | -------------------------------------------------------------------------------- /scripts/bresserweathersensorlw-codec/metainfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bresserweathersensorlw-codec", 3 | "version": "1.1.2", 4 | "description": "LoRaWAN Codec API compliant codec for BresserWeatherSensorLW", 5 | "main": "index.js", 6 | "specVersion": "1.0.0", 7 | "codec": { 8 | "encodeDownlink": true, 9 | "decodeDownlink": true, 10 | "examples": true, 11 | "jsonSchemas": false 12 | }, 13 | "keywords": [ 14 | "lorawan", 15 | "codec", 16 | "uplink", 17 | "downlink", 18 | "bresser", 19 | "weather-sensor", 20 | "thethingsnetwork", 21 | "ttn", 22 | "helium-network" 23 | ], 24 | "author": "Matthias Prinke", 25 | "license": "MIT", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/matthias-bs/BresserWeatherSensorLW" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/matthias-bs/BresserWeatherSensorLW/issues" 32 | }, 33 | "homepage": "https://github.com/matthias-bs/BresserWeatherSensorLW", 34 | "files": [ 35 | "./index.js", 36 | "./downlink_formatter.js", 37 | "./uplink_formatter.js", 38 | "./examples.json", 39 | "./metainfo.json", 40 | "./test/codec.test.js" 41 | ], 42 | "scripts": { 43 | "test": "node --test" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BresserWeatherSensorLW", 3 | "version": "0.14.0", 4 | "description": "Bresser 868 MHz Weather Sensor Radio Receiver; provides data via LoRaWAN", 5 | "main": "BresserWeatherSensorLW.ino", 6 | "frameworks": "arduino", 7 | "platforms": [ 8 | "ESP32", 9 | "RP2040" 10 | ], 11 | "directories": { 12 | "src": "src" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/matthias-bs/BresserWeatherSensorLW.git" 17 | }, 18 | "author": "Matthias Prinke", 19 | "license": "MIT license", 20 | "bugs": { 21 | "url": "https://github.com/matthias-bs/BresserWeatherSensorLW/issues" 22 | }, 23 | "homepage": "https://github.com/matthias-bs/BresserWeatherSensorLW#README", 24 | "dependencies": { 25 | "BresserWeatherSensorReceiver": "matthias-bs/BresserWeatherSensorReceiver#semver:^0.36.2", 26 | "RadioLib": "jgromes/RadioLib#semver:^7.4.0", 27 | "lora-serialization": "thesolarnomad/lora-serialization#semver:^3.3.1", 28 | "OneWireNg": "https://github.com/pstolarz/OneWireNg#semver:^0.14.0", 29 | "Arduino-Temperature-Control-Library": "milesburton/Arduino-Temperature-Control-Library#semver:^4.0.5", 30 | "NimBLE-Arduino": "h2zero/NimBLE-Arduino#semver:^2.3.7", 31 | "ATC_MiThermometer": "matthias-bs/ATC_MiThermometer#semver:^0.5.0", 32 | "TheengsDecoder": "theengs/decoder#semver:^2.0.0", 33 | "Preferences": "vshymanskyy/Preferences#semver:^2.2.2", 34 | "ArduinoJson": "bblanchon/ArduinoJson#semver:^7.4.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /extras/OneWireTest/OneWireTest.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define PIN_ONEWIRE_BUS 5 5 | 6 | // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) 7 | static OneWire oneWire(PIN_ONEWIRE_BUS); //!< OneWire bus 8 | 9 | // Pass our oneWire reference to Dallas Temperature. 10 | static DallasTemperature owTempSensors(&oneWire); //!< Dallas temperature sensors connected to OneWire bus 11 | 12 | // Get temperature from Maxim OneWire Sensor 13 | float getOneWireTemperature(uint8_t index) 14 | { 15 | // Call sensors.requestTemperatures() to issue a global temperature 16 | // request to all devices on the bus 17 | owTempSensors.requestTemperatures(); 18 | 19 | // Get temperature by index 20 | float tempC = owTempSensors.getTempCByIndex(index); 21 | 22 | // Check if reading was successful 23 | if (tempC != DEVICE_DISCONNECTED_C) 24 | { 25 | log_i("Temperature = %.2f°C", tempC); 26 | } 27 | else 28 | { 29 | log_i("Error: Could not read temperature data"); 30 | } 31 | 32 | return tempC; 33 | }; 34 | 35 | void setup() 36 | { 37 | // Initialize the Dallas Temperature library 38 | owTempSensors.begin(); 39 | } 40 | 41 | void loop() 42 | { 43 | // Get temperature from the first sensor (index 0) 44 | float temperature = getOneWireTemperature(0); 45 | 46 | // Do something with the temperature value 47 | log_i("Temperature from OneWire sensor: %.2f°C", temperature); 48 | 49 | // Wait for a while before the next reading 50 | delay(5000); 51 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | **Context** 10 | - Which example sketch are you using? 11 | - Which changes to the code did you make (if any)? 12 | - Which sensor are you using? 13 | 14 | **Describe the bug** 15 | A clear and concise description of what the bug is. 16 | 17 | **To Reproduce** 18 | Steps to reproduce the behavior: 19 | 1. Step1 '...' 20 | 2. Step2 '....' 21 | 3. Step3 '....' 22 | 4. See error 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Debug Log** 28 | If applicable, add log file to help explain your problem. 29 | - Build log file (enable verbose output in the Arduino IDE: File->Preferences - "Show verbose output during: compile") 30 | - Serial console log file (see [Debug Output Configuration in Arduino IDE](https://github.com/matthias-bs/BresserWeatherSensorReceiver/blob/main/DEBUG_OUTPUT.md)) 31 | 32 | **Embedded Device (please complete the following information):** 33 | - Arduino IDE Version: [e.g. 2.3.2] 34 | - ESP32/ESP8266 Board Support Package Version: [e.g. ESP32 2.0.14] 35 | - Library Version [e.g. 0.1.0] 36 | - Your Board: [brand/model/version] (if unsure, attach a photo) 37 | - Selected Board in the Arduino IDE: [brand/model/version] 38 | - Your Radio Transceiver Module (if external): [brand/model/chipset/frequency] 39 | - **Pinning/Wiring** 40 | 41 | **LoRaWAN Network Service Provider 42 | - Which LoRaWAN Network Service Provider are you using? 43 | - Which LoRaWAN protocol version are you using? 44 | 45 | **Additional context** 46 | Add any other context about the problem here. 47 | -------------------------------------------------------------------------------- /doxygen2keywords.xsl: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | ####################################### 22 | # Syntax Coloring Map For the current project. 23 | # This file was generated by doxygen2keywords.xsl. 24 | ####################################### 25 | 26 | ####################################### 27 | # Classes and structs (KEYWORD1) 28 | ####################################### 29 | 30 | 31 | 32 | 33 | 34 | ####################################### 35 | # Methods (KEYWORD2) 36 | ####################################### 37 | 38 | 39 | 40 | 41 | 42 | ####################################### 43 | # Constants (LITERAL1) 44 | ####################################### 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | KEYWORD1 53 | 54 | 55 | 56 | 57 | KEYWORD2 58 | 59 | 60 | 61 | 62 | LITERAL1 63 | 64 | 65 | -------------------------------------------------------------------------------- /scripts/bresserweathersensorlw-codec/README.md: -------------------------------------------------------------------------------- 1 | # BresserWeatherSensorLW LoRaWAN Payload Codec 2 | 3 | LoRaWAN Codec API compliant codec for [BresserWeatherSensorLW](https://github.com/matthias-bs/BresserWeatherSensorLW). 4 | 5 | This package provides uplink and downlink encoding/decoding functions for use with The Things Network, The Things Stack, Helium, and other LoRaWAN platforms. 6 | 7 | ## Features 8 | 9 | - **Uplink decoding:** Converts sensor payload bytes to JSON objects. 10 | - **Downlink encoding:** Converts JSON commands to bytes for device control. 11 | - **Downlink decoding:** Converts downlink bytes to JSON for verification. 12 | - **LoRaWAN Codec API compliant:** Works with TTN, TTS, and similar platforms. 13 | 14 | ## Installation 15 | 16 | ```sh 17 | npm install bresserweathersensorlw-codec 18 | ``` 19 | 20 | ## Usage 21 | 22 | ```js 23 | const codec = require('bresserweathersensorlw-codec'); 24 | 25 | // Decode uplink 26 | const uplink = codec.decodeUplink({ bytes: Buffer.from([/* uplink bytes */]), fPort: 1 }); 27 | console.log(uplink); 28 | 29 | // Encode downlink 30 | const downlink = codec.encodeDownlink({ data: { sleep_interval: 300 }, fPort: 0x31 }); 31 | console.log(downlink); 32 | 33 | // Decode downlink 34 | const decodedDownlink = codec.decodeDownlink({ bytes: Buffer.from([0x01, 0x2C]), fPort: 0x31 }); 35 | console.log(decodedDownlink); 36 | ``` 37 | 38 | ## API 39 | 40 | ### `decodeUplink({ bytes, fPort })` 41 | 42 | Decodes uplink payload bytes to a JSON object. 43 | 44 | ### `encodeDownlink({ data, fPort })` 45 | 46 | Encodes a JSON object to downlink payload bytes. 47 | 48 | ### `decodeDownlink({ bytes, fPort })` 49 | 50 | Decodes downlink payload bytes to a JSON object. 51 | 52 | ## File Structure 53 | 54 | ``` 55 | bresserweathersensorlw-codec/ 56 | ├── package.json 57 | ├── index.js 58 | ├── README.md 59 | ├── examples.json 60 | ├── metainfo.json 61 | ├── downlink_formatter.js 62 | ├── uplink_formatter.js 63 | └── test/ 64 | └── codec.test.js 65 | ``` 66 | 67 | ## License 68 | 69 | MIT © 2025 Matthias Prinke 70 | 71 | ## Links 72 | 73 | - [BresserWeatherSensorLW Project](https://github.com/matthias-bs/BresserWeatherSensorLW) 74 | - [LoRaWAN Payload Codec API Specification](https://resources.lora-alliance.org/technical-specifications/ts013-1-0-0-payload-codec-api) 75 | -------------------------------------------------------------------------------- /src/LoadSecrets.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // LoadSecrets.h 3 | // 4 | // Load LoRaWAN secrets from file 'secrets.json' on LittleFS, if available 5 | // 6 | // created: 07/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2024 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // 32 | // History: 33 | // 34 | // 20240723 Created from BresserWeatherSensorLW.ino 35 | // 20240928 Refactoring & modification for LoRaWAN v1.0.4 (requires no nwkKey) 36 | // 37 | // ToDo: 38 | // - 39 | // 40 | /////////////////////////////////////////////////////////////////////////////// 41 | 42 | /*! \file LoadSecrets.h 43 | * \brief Load LoRaWAN secrets from file 'secrets.json' on LittleFS, if available 44 | */ 45 | 46 | #include 47 | #include 48 | #include 49 | #include "logging.h" 50 | 51 | /*! 52 | * \brief Load LoRaWAN secrets from file 'secrets.json' on LittleFS, if available 53 | * 54 | * Returns all values by reference/pointer 55 | * 56 | * Use https://github.com/earlephilhower/arduino-littlefs-upload for uploading 57 | * the file to Flash. 58 | * 59 | * \param requireNwkKey 60 | * \param joinEUI 61 | * \param devEUI 62 | * \param nwkKey 63 | * \param appKey 64 | */ 65 | void loadSecrets(bool requireNwkKey, uint64_t &joinEUI, uint64_t &devEUI, uint8_t *nwkKey, uint8_t *appKey); -------------------------------------------------------------------------------- /src/PayloadAnalog.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // PayloadAnalog.h 3 | // 4 | // Read analog input channels and encode as LoRaWAN payload 5 | // 6 | // created: 05/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2024 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // 32 | // History: 33 | // 34 | // 20240521 Created 35 | // 36 | // ToDo: 37 | // - 38 | // 39 | /////////////////////////////////////////////////////////////////////////////// 40 | 41 | /*! \file PayloadAnalog.h 42 | * \brief LoRaWAN node application layer - analog channels 43 | */ 44 | 45 | #if !defined(_PAYLOAD_ANALOG) 46 | #define _PAYLOAD_ANALOG 47 | 48 | #include "../BresserWeatherSensorLWCfg.h" 49 | #include "adc/adc.h" 50 | #include 51 | #include "logging.h" 52 | 53 | 54 | /*! 55 | * \brief LoRaWAN node application layer - analog channels 56 | * 57 | * Encodes data from analog input channels as LoRaWAN payload 58 | */ 59 | class PayloadAnalog 60 | { 61 | public: 62 | /*! 63 | * \brief Constructor 64 | */ 65 | PayloadAnalog(){}; 66 | 67 | /*! 68 | * \brief Analog channel startup code 69 | */ 70 | void begin(void); 71 | 72 | /*! 73 | * \brief Encode analog data channels for LoRaWAN transmission 74 | * 75 | * \param appPayloadCfg LoRaWAN payload configuration bitmaps 76 | * \param encoder LoRaWAN payload encoder object 77 | */ 78 | void encodeAnalog(uint8_t *appPayloadCfg, LoraEncoder &encoder); 79 | }; 80 | #endif //_PAYLOAD_ANALOG -------------------------------------------------------------------------------- /src/rp2040/rosc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | #if defined(ARDUINO_ARCH_RP2040) 7 | 8 | #include "pico.h" 9 | 10 | // For MHZ definitions etc 11 | #include "hardware/clocks.h" 12 | #include "pico_rosc.h" 13 | 14 | // Given a ROSC delay stage code, return the next-numerically-higher code. 15 | // Top result bit is set when called on maximum ROSC code. 16 | uint32_t next_rosc_code(uint32_t code) { 17 | return ((code | 0x08888888u) + 1u) & 0xf7777777u; 18 | } 19 | 20 | uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) { 21 | // TODO: This could be a lot better 22 | rosc_set_div(1); 23 | for (uint32_t code = 0; code <= 0x77777777u; code = next_rosc_code(code)) { 24 | rosc_set_freq(code); 25 | uint rosc_mhz = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC) / 1000; 26 | if ((rosc_mhz >= low_mhz) && (rosc_mhz <= high_mhz)) { 27 | return rosc_mhz; 28 | } 29 | } 30 | return 0; 31 | } 32 | 33 | void rosc_set_div(uint32_t div) { 34 | assert(div <= 31 && div >= 1); 35 | rosc_write(&rosc_hw->div, ROSC_DIV_VALUE_PASS + div); 36 | } 37 | 38 | void rosc_set_freq(uint32_t code) { 39 | rosc_write(&rosc_hw->freqa, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code & 0xffffu)); 40 | rosc_write(&rosc_hw->freqb, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code >> 16u)); 41 | } 42 | 43 | void rosc_set_range(uint range) { 44 | // Range should use enumvals from the headers and thus have the password correct 45 | rosc_write(&rosc_hw->ctrl, (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB) | range); 46 | } 47 | 48 | void rosc_disable(void) { 49 | uint32_t tmp = rosc_hw->ctrl; 50 | tmp &= (~ROSC_CTRL_ENABLE_BITS); 51 | tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB); 52 | rosc_write(&rosc_hw->ctrl, tmp); 53 | // Wait for stable to go away 54 | while(rosc_hw->status & ROSC_STATUS_STABLE_BITS); 55 | } 56 | 57 | void rosc_enable(void) 58 | { 59 | uint32_t tmp = rosc_hw->ctrl; 60 | tmp &= (~ROSC_CTRL_ENABLE_BITS); 61 | tmp |= (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB); 62 | rosc_write(&rosc_hw->ctrl, tmp); 63 | // Wait for stable 64 | while ((rosc_hw->status & ROSC_STATUS_STABLE_BITS) != ROSC_STATUS_STABLE_BITS); 65 | } 66 | 67 | void rosc_set_dormant(void) { 68 | // WARNING: This stops the rosc until woken up by an irq 69 | rosc_write(&rosc_hw->dormant, ROSC_DORMANT_VALUE_DORMANT); 70 | // Wait for it to become stable once woken up 71 | while(!(rosc_hw->status & ROSC_STATUS_STABLE_BITS)); 72 | } 73 | #endif /* ARDUINO_ARCH_RP2040 */ 74 | -------------------------------------------------------------------------------- /extras/time_rp2040_sleep/src/rosc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | #if defined(ARDUINO_ARCH_RP2040) 7 | 8 | #include "pico.h" 9 | 10 | // For MHZ definitions etc 11 | #include "hardware/clocks.h" 12 | #include "pico_rosc.h" 13 | 14 | // Given a ROSC delay stage code, return the next-numerically-higher code. 15 | // Top result bit is set when called on maximum ROSC code. 16 | uint32_t next_rosc_code(uint32_t code) { 17 | return ((code | 0x08888888u) + 1u) & 0xf7777777u; 18 | } 19 | 20 | uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) { 21 | // TODO: This could be a lot better 22 | rosc_set_div(1); 23 | for (uint32_t code = 0; code <= 0x77777777u; code = next_rosc_code(code)) { 24 | rosc_set_freq(code); 25 | uint rosc_mhz = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC) / 1000; 26 | if ((rosc_mhz >= low_mhz) && (rosc_mhz <= high_mhz)) { 27 | return rosc_mhz; 28 | } 29 | } 30 | return 0; 31 | } 32 | 33 | void rosc_set_div(uint32_t div) { 34 | assert(div <= 31 && div >= 1); 35 | rosc_write(&rosc_hw->div, ROSC_DIV_VALUE_PASS + div); 36 | } 37 | 38 | void rosc_set_freq(uint32_t code) { 39 | rosc_write(&rosc_hw->freqa, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code & 0xffffu)); 40 | rosc_write(&rosc_hw->freqb, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code >> 16u)); 41 | } 42 | 43 | void rosc_set_range(uint range) { 44 | // Range should use enumvals from the headers and thus have the password correct 45 | rosc_write(&rosc_hw->ctrl, (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB) | range); 46 | } 47 | 48 | void rosc_disable(void) { 49 | uint32_t tmp = rosc_hw->ctrl; 50 | tmp &= (~ROSC_CTRL_ENABLE_BITS); 51 | tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB); 52 | rosc_write(&rosc_hw->ctrl, tmp); 53 | // Wait for stable to go away 54 | while(rosc_hw->status & ROSC_STATUS_STABLE_BITS); 55 | } 56 | 57 | void rosc_enable(void) 58 | { 59 | uint32_t tmp = rosc_hw->ctrl; 60 | tmp &= (~ROSC_CTRL_ENABLE_BITS); 61 | tmp |= (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB); 62 | rosc_write(&rosc_hw->ctrl, tmp); 63 | // Wait for stable 64 | while ((rosc_hw->status & ROSC_STATUS_STABLE_BITS) != ROSC_STATUS_STABLE_BITS); 65 | } 66 | 67 | void rosc_set_dormant(void) { 68 | // WARNING: This stops the rosc until woken up by an irq 69 | rosc_write(&rosc_hw->dormant, ROSC_DORMANT_VALUE_DORMANT); 70 | // Wait for it to become stable once woken up 71 | while(!(rosc_hw->status & ROSC_STATUS_STABLE_BITS)); 72 | } 73 | #endif /* ARDUINO_ARCH_RP2040 */ 74 | -------------------------------------------------------------------------------- /scripts/helium_message_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "uplink_message": { 3 | "decoded_payload": { 4 | "bytes": { 5 | "a0_voltage_mv": "{{decoded.payload.uplink_message.decoded_payload.bytes.a0_voltage_mv}}", 6 | "ble0_humidity": "{{decoded.payload.uplink_message.decoded_payload.bytes.ble0_humidity}}", 7 | "ble0_temp_c": "{{decoded.payload.uplink_message.decoded_payload.bytes.ble0_temp_c}}", 8 | "ow0_temp_c": "{{decoded.payload.uplink_message.decoded_payload.bytes.ow0_temp_c}}", 9 | "soil1_moisture": "{{decoded.payload.uplink_message.decoded_payload.bytes.soil1_moisture}}", 10 | "soil1_temp_c": "{{decoded.payload.uplink_message.decoded_payload.bytes.soil1_temp_c}}", 11 | "ws_humidity": "{{decoded.payload.uplink_message.decoded_payload.bytes.ws_humidity}}", 12 | "ws_rain_daily_mm": "{{decoded.payload.uplink_message.decoded_payload.bytes.ws_rain_daily_mm}}", 13 | "ws_rain_hourly_mm": "{{decoded.payload.uplink_message.decoded_payload.bytes.ws_rain_hourly_mm}}", 14 | "ws_rain_mm": "{{decoded.payload.uplink_message.decoded_payload.bytes.ws_rain_mm}}", 15 | "ws_rain_monthly_mm": "{{decoded.payload.uplink_message.decoded_payload.bytes.ws_rain_monthly_mm}}", 16 | "ws_rain_weekly_mm": "{{decoded.payload.uplink_message.decoded_payload.bytes.ws_rain_weekly_mm}}", 17 | "ws_temp_c": "{{decoded.payload.uplink_message.decoded_payload.bytes.ws_temp_c}}", 18 | "ws_wind_avg_ms": "{{decoded.payload.uplink_message.decoded_payload.bytes.ws_wind_avg_ms}}", 19 | "ws_wind_dir_deg": "{{decoded.payload.uplink_message.decoded_payload.bytes.ws_wind_dir_deg}}", 20 | "ws_wind_gust_ms": "{{decoded.payload.uplink_message.decoded_payload.bytes.ws_wind_gust_ms}}" 21 | }, 22 | "status": "{{decoded.payload.uplink_message.decoded_payload.status}}" 23 | }, 24 | "f_port": "{{port}}" 25 | }, 26 | "dev_eui": "{{dev_eui}}", 27 | "devaddr": "{{devaddr}}", 28 | "fcnt": "{{fcnt}}", 29 | "hotspots": [ 30 | { 31 | "channel": "{{hotspots.0.channel}}", 32 | "frequency": "{{hotspots.0.frequency}}", 33 | "hold_time": "{{hotspots.0.hold_time}}", 34 | "id": "{{hotspots.0.id}}", 35 | "lat": "{{hotspots.0.lat}}", 36 | "long": "{{hotspots.0.long}}", 37 | "name": "{{hotspots.0.name}}", 38 | "reported_at": "{{hotspots.0.reported_at}}", 39 | "rssi": "{{hotspots.0.rssi}}", 40 | "snr": "{{hotspots.0.snr}}", 41 | "spreading": "{{hotspots.0.spreading}}", 42 | "status": "{{hotspots.0.status}}" 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /src/adc/adc.h: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // adc.h 3 | // 4 | // Analog/Digital Converter wrapper/convenience functions 5 | // 6 | // created: 04/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2024 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // History: 32 | // 20240405 Created 33 | // 20240410 Added RP2040 specific implementation 34 | // 20240413 Refactored ADC handling 35 | // 36 | // ToDo: 37 | // - 38 | // 39 | /////////////////////////////////////////////////////////////////////////////////////////////////// 40 | 41 | /*! \file adc.h 42 | * \brief ADC wrapper/convenience functions 43 | */ 44 | 45 | #if !defined(_ADC_H) 46 | #define _ADC_H 47 | 48 | #include 49 | #include "../../BresserWeatherSensorLWCfg.h" 50 | 51 | 52 | /*! 53 | * \brief Get supply / battery voltage 54 | * 55 | * Returns a voltage measurement with oversampling and divider 56 | * 57 | * \returns Voltage in mV 58 | */ 59 | 60 | uint16_t getVoltage(uint8_t pin = PIN_ADC_IN, uint8_t samples = UBATT_SAMPLES, float divider = UBATT_DIV); 61 | 62 | 63 | /*! 64 | * \brief Get battery voltage 65 | * 66 | * Returns the battery voltage or zero if not available (board specific) 67 | * 68 | * \returns Voltage in mV or zero if not available 69 | */ 70 | uint16_t getBatteryVoltage(void); 71 | 72 | /*! 73 | * \brief Get supply voltage 74 | * 75 | * Returns the battery voltage or zero if not available (board specific) 76 | * 77 | * \returns Voltage in mV or zero if not available 78 | */ 79 | 80 | uint16_t getSupplyVoltage(void); 81 | 82 | #endif // _ADC_H -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For the current project. 3 | # This file was generated by doxygen2keywords.xsl. 4 | ####################################### 5 | 6 | ####################################### 7 | # Classes and structs (KEYWORD1) 8 | ####################################### 9 | 10 | AppLayer KEYWORD1 11 | BleDataS KEYWORD1 12 | BleSensors KEYWORD1 13 | PayloadAnalog KEYWORD1 14 | PayloadBLE KEYWORD1 15 | PayloadBresser KEYWORD1 16 | PayloadDigital KEYWORD1 17 | PayloadOneWire KEYWORD1 18 | BleSensorsCallbacks::ScanCallbacks KEYWORD1 19 | SystemContext KEYWORD1 20 | 21 | ####################################### 22 | # Methods (KEYWORD2) 23 | ####################################### 24 | 25 | AppLayer KEYWORD2 26 | begin KEYWORD2 27 | decodeDownlink KEYWORD2 28 | genPayload KEYWORD2 29 | getPayloadStage1 KEYWORD2 30 | getPayloadStage2 KEYWORD2 31 | getConfigPayload KEYWORD2 32 | getAppStatusUplinkInterval KEYWORD2 33 | setAppPayloadCfg KEYWORD2 34 | BleSensors KEYWORD2 35 | setAddresses KEYWORD2 36 | clearScanResults KEYWORD2 37 | getData KEYWORD2 38 | resetData KEYWORD2 39 | PayloadAnalog KEYWORD2 40 | encodeAnalog KEYWORD2 41 | PayloadBLE KEYWORD2 42 | setBleAddr KEYWORD2 43 | getBleAddr KEYWORD2 44 | bleAddrInit KEYWORD2 45 | encodeBLE KEYWORD2 46 | PayloadBresser KEYWORD2 47 | scanBresser KEYWORD2 48 | encodeBresser KEYWORD2 49 | encodeWeatherSensor KEYWORD2 50 | encodeThermoHygroSensor KEYWORD2 51 | encodePoolThermometer KEYWORD2 52 | encodeSoilSensor KEYWORD2 53 | encodeLeakageSensor KEYWORD2 54 | encodeAirPmSensor KEYWORD2 55 | encodeLightningSensor KEYWORD2 56 | encodeCo2Sensor KEYWORD2 57 | encodeHchoVocSensor KEYWORD2 58 | isSpaceLeft KEYWORD2 59 | PayloadDigital KEYWORD2 60 | encodeDigital KEYWORD2 61 | PayloadOneWire KEYWORD2 62 | getOneWireTemperature KEYWORD2 63 | encodeOneWire KEYWORD2 64 | onDiscovered KEYWORD2 65 | onResult KEYWORD2 66 | onScanEnd KEYWORD2 67 | SystemContext KEYWORD2 68 | isFirstBoot KEYWORD2 69 | resetFailedJoinCount KEYWORD2 70 | sleepAfterFailedJoin KEYWORD2 71 | setTime KEYWORD2 72 | printDateTime KEYWORD2 73 | savePreferences KEYWORD2 74 | getVoltages KEYWORD2 75 | sleepIfSupplyLow KEYWORD2 76 | getBattlevel KEYWORD2 77 | sleepInterval KEYWORD2 78 | longSleepActive KEYWORD2 79 | isRtcSynched KEYWORD2 80 | getRtcTimeSource KEYWORD2 81 | rtcNeedsSync KEYWORD2 82 | sleepDuration KEYWORD2 83 | uplinkDelay KEYWORD2 84 | gotoSleep KEYWORD2 85 | getVoltage KEYWORD2 86 | getBatteryVoltage KEYWORD2 87 | getSupplyVoltage KEYWORD2 88 | loadNodeCfg KEYWORD2 89 | loadSecrets KEYWORD2 90 | oneWire KEYWORD2 91 | __attribute__ KEYWORD2 92 | 93 | ####################################### 94 | # Constants (LITERAL1) 95 | ####################################### 96 | 97 | BLE_SENSORS LITERAL1 98 | cb_log_i LITERAL1 99 | cb_log_w LITERAL1 100 | cb_log_d LITERAL1 101 | cb_log_e LITERAL1 102 | cb_log_v LITERAL1 103 | -------------------------------------------------------------------------------- /src/PayloadOneWire.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // PayloadOneWire.h 3 | // 4 | // Get 1-Wire temperature sensor values and encode as LoRaWAN payload 5 | // 6 | // created: 05/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2024 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // 32 | // History: 33 | // 34 | // 20240520 Created 35 | // 36 | // ToDo: 37 | // - 38 | // 39 | /////////////////////////////////////////////////////////////////////////////// 40 | 41 | /*! \file PayloadOneWire.h 42 | * \brief LoRaWAN node application layer - 1-Wire sensors 43 | */ 44 | 45 | #if !defined(_PAYLOAD_ONE_WIRE) 46 | #define _PAYLOAD_ONE_WIRE 47 | 48 | #include "../BresserWeatherSensorLWCfg.h" 49 | 50 | #ifdef ONEWIRE_EN 51 | 52 | // Dallas/Maxim OneWire Temperature Sensor 53 | #include 54 | 55 | #include 56 | #include "logging.h" 57 | 58 | /*! 59 | * \brief LoRaWAN node application layer - 1-Wire sensors 60 | * 61 | * Encodes data from 1-Wire sensors as LoRaWAN payload 62 | */ 63 | class PayloadOneWire 64 | { 65 | public: 66 | /*! 67 | * \brief Constructor 68 | */ 69 | PayloadOneWire(){}; 70 | 71 | /*! 72 | * \brief Get temperature from Maxim OneWire Sensor 73 | * 74 | * \param index sensor index 75 | * 76 | * \returns temperature in degrees Celsius or DEVICE_DISCONNECTED_C 77 | */ 78 | float getOneWireTemperature(uint8_t index); 79 | 80 | /*! 81 | * \brief Encode 1-Wire temperature sensor values for LoRaWAN transmission 82 | * 83 | * \param appPayloadCfg LoRaWAN payload configuration bitmaps 84 | * \param encoder LoRaWAN payload encoder object 85 | */ 86 | void encodeOneWire(uint8_t *appPayloadCfg, LoraEncoder &encoder); 87 | }; 88 | #endif // ONEWIRE_EN 89 | #endif //_PAYLOAD_ONE_WIRE -------------------------------------------------------------------------------- /src/rp2040/pico_rtc_utils.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // pico_rtc_utils.h 3 | // 4 | // RTC utility functions for RP2040 5 | // 6 | // Sleep/wakeup scheme based on 7 | // https://github.com/lyusupov/SoftRF/tree/master/software/firmware/source/libraries/RP2040_Sleep 8 | // by Linar Yusupov 9 | // 10 | // Using code from pico-extras: 11 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/pico_sleep/include/pico/sleep.h 12 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/pico_sleep/sleep.c 13 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/hardware_rosc/include/hardware/rosc.h 14 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/hardware_rosc/rosc.c 15 | // 16 | // created: 10/2023 17 | // 18 | // 19 | // MIT License 20 | // 21 | // Copyright (c) 2023 Matthias Prinke 22 | // 23 | // Permission is hereby granted, free of charge, to any person obtaining a copy 24 | // of this software and associated documentation files (the "Software"), to deal 25 | // in the Software without restriction, including without limitation the rights 26 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 27 | // copies of the Software, and to permit persons to whom the Software is 28 | // furnished to do so, subject to the following conditions: 29 | // 30 | // The above copyright notice and this permission notice shall be included in all 31 | // copies or substantial portions of the Software. 32 | // 33 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 35 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 36 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 37 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 39 | // SOFTWARE. 40 | // 41 | // 42 | // History: 43 | // 44 | // 20231006 Created 45 | // 46 | // ToDo: 47 | // - 48 | // 49 | /////////////////////////////////////////////////////////////////////////////// 50 | #if defined(ARDUINO_ARCH_RP2040) 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include "pico_sleep.h" 57 | #include "pico_rosc.h" 58 | #include "../logging.h" 59 | 60 | #ifndef PICO_RTC_UTILS_H 61 | #define PICO_RTC_UTILS_H 62 | 63 | struct tm *datetime_to_tm(datetime_t *dt, struct tm *ti); 64 | datetime_t *tm_to_datetime(struct tm *ti, datetime_t *dt); 65 | time_t datetime_to_epoch(datetime_t *dt, time_t *epoch); 66 | datetime_t *epoch_to_datetime(time_t *epoch, datetime_t *dt); 67 | 68 | void print_dt(datetime_t dt); 69 | void print_tm(struct tm ti); 70 | 71 | void pico_sleep(unsigned duration); 72 | 73 | #endif // PICO_RTC_UTILS_H 74 | #endif // defined(ARDUINO_ARCH_RP2040) 75 | -------------------------------------------------------------------------------- /src/PayloadAnalog.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // PayloadAnalog.cpp 3 | // 4 | // Read analog input channels and encode as LoRaWAN payload 5 | // 6 | // created: 05/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2024 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // 32 | // History: 33 | // 34 | // 20240521 Created 35 | // 20240524 Added payload size check, changed bitmap order 36 | // 20240528 Changesd order of channels, fixed log messages 37 | // 20250318 Renamed PAYLOAD_SIZE to MAX_UPLINK_SIZE 38 | // 39 | // ToDo: 40 | // - 41 | // 42 | /////////////////////////////////////////////////////////////////////////////// 43 | 44 | #include "PayloadAnalog.h" 45 | 46 | 47 | void PayloadAnalog::begin(void) 48 | { 49 | 50 | } 51 | 52 | void PayloadAnalog::encodeAnalog(uint8_t *appPayloadCfg, LoraEncoder &encoder) 53 | { 54 | unsigned ch = 0; 55 | for (int i = APP_PAYLOAD_BYTES_ANALOG - 1; i >= 0; i--) 56 | { 57 | for (uint8_t bit = 0; bit <= 7; bit++) 58 | { 59 | // Check if channel is enabled 60 | if ((appPayloadCfg[APP_PAYLOAD_OFFS_ANALOG + i] >> bit) & 0x1) { 61 | if ((ch == UBATT_CH) && (encoder.getLength() <= MAX_UPLINK_SIZE - 2)) 62 | { 63 | uint16_t uBatt = getBatteryVoltage(); 64 | log_i("ch %02u: U_batt: %04u mv", ch, uBatt); 65 | encoder.writeUint16(uBatt); 66 | } 67 | 68 | if ((ch == USUPPLY_CH) && (encoder.getLength() <= MAX_UPLINK_SIZE - 2)) 69 | { 70 | uint16_t uSupply = getSupplyVoltage(); 71 | log_i("ch %02u: U_supply: %04u mv", ch, uSupply); 72 | encoder.writeUint16(uSupply); 73 | } 74 | } 75 | ch++; 76 | } 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /extras/time_rp2040_sleep/src/pico_rtc_utils.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // pico_rtc_utils.h 3 | // 4 | // RTC utility functions for RP2040 5 | // 6 | // Sleep/wakeup scheme based on 7 | // https://github.com/lyusupov/SoftRF/tree/master/software/firmware/source/libraries/RP2040_Sleep 8 | // by Linar Yusupov 9 | // 10 | // Using code from pico-extras: 11 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/pico_sleep/include/pico/sleep.h 12 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/pico_sleep/sleep.c 13 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/hardware_rosc/include/hardware/rosc.h 14 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/hardware_rosc/rosc.c 15 | // 16 | // created: 10/2023 17 | // 18 | // 19 | // MIT License 20 | // 21 | // Copyright (c) 2023 Matthias Prinke 22 | // 23 | // Permission is hereby granted, free of charge, to any person obtaining a copy 24 | // of this software and associated documentation files (the "Software"), to deal 25 | // in the Software without restriction, including without limitation the rights 26 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 27 | // copies of the Software, and to permit persons to whom the Software is 28 | // furnished to do so, subject to the following conditions: 29 | // 30 | // The above copyright notice and this permission notice shall be included in all 31 | // copies or substantial portions of the Software. 32 | // 33 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 35 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 36 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 37 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 39 | // SOFTWARE. 40 | // 41 | // 42 | // History: 43 | // 44 | // 20231006 Created 45 | // 46 | // ToDo: 47 | // - 48 | // 49 | /////////////////////////////////////////////////////////////////////////////// 50 | #if defined(ARDUINO_ARCH_RP2040) 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include "pico_sleep.h" 57 | #include "pico_rosc.h" 58 | #include "logging.h" 59 | 60 | #ifndef PICO_RTC_UTILS_H 61 | #define PICO_RTC_UTILS_H 62 | 63 | struct tm *datetime_to_tm(datetime_t *dt, struct tm *ti); 64 | datetime_t *tm_to_datetime(struct tm *ti, datetime_t *dt); 65 | time_t datetime_to_epoch(datetime_t *dt, time_t *epoch); 66 | datetime_t *epoch_to_datetime(time_t *epoch, datetime_t *dt); 67 | 68 | void print_dt(datetime_t dt); 69 | void print_tm(struct tm ti); 70 | 71 | void pico_sleep(unsigned duration); 72 | 73 | #endif // PICO_RTC_UTILS_H 74 | #endif // defined(ARDUINO_ARCH_RP2040) 75 | -------------------------------------------------------------------------------- /doxygen_header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $projectname: $title 10 | $title 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | $treeview 20 | $search 21 | $mathjax 22 | $darkmode 23 | 24 | $extrastylesheet 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | 33 |
34 | 35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 56 | 57 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
45 |
$projectname $projectnumber 46 |
47 |
$projectbrief
48 | 53 |
58 |
$projectbrief
59 |
$searchbox
$searchbox
77 |
78 | 79 | 80 | -------------------------------------------------------------------------------- /src/rp2040/pico_rosc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | #if defined(ARDUINO_ARCH_RP2040) 7 | 8 | #ifndef _HARDWARE_ROSC_H_ 9 | #define _HARDWARE_ROSC_H_ 10 | 11 | #include "pico.h" 12 | #include "hardware/structs/rosc.h" 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | /** \file rosc.h 19 | * \defgroup hardware_rosc hardware_rosc 20 | * 21 | * Ring Oscillator (ROSC) API 22 | * 23 | * A Ring Oscillator is an on-chip oscillator that requires no external crystal. Instead, the output is generated from a series of 24 | * inverters that are chained together to create a feedback loop. RP2040 boots from the ring oscillator initially, meaning the 25 | * first stages of the bootrom, including booting from SPI flash, will be clocked by the ring oscillator. If your design has a 26 | * crystal oscillator, you’ll likely want to switch to this as your reference clock as soon as possible, because the frequency is 27 | * more accurate than the ring oscillator. 28 | */ 29 | 30 | /*! \brief Set frequency of the Ring Oscillator 31 | * \ingroup hardware_rosc 32 | * 33 | * \param code The drive strengths. See the RP2040 datasheet for information on this value. 34 | */ 35 | void rosc_set_freq(uint32_t code); 36 | 37 | /*! \brief Set range of the Ring Oscillator 38 | * \ingroup hardware_rosc 39 | * 40 | * Frequency range. Frequencies will vary with Process, Voltage & Temperature (PVT). 41 | * Clock output will not glitch when changing the range up one step at a time. 42 | * 43 | * \param range 0x01 Low, 0x02 Medium, 0x03 High, 0x04 Too High. 44 | */ 45 | void rosc_set_range(uint range); 46 | 47 | /*! \brief Disable the Ring Oscillator 48 | * \ingroup hardware_rosc 49 | * 50 | */ 51 | void rosc_disable(void); 52 | 53 | /*! \brief Enable the Ring Oscillator 54 | * \ingroup hardware_rosc 55 | * 56 | */ 57 | void rosc_enable(void); 58 | 59 | /*! \brief Put Ring Oscillator in to dormant mode. 60 | * \ingroup hardware_rosc 61 | * 62 | * The ROSC supports a dormant mode,which stops oscillation until woken up up by an asynchronous interrupt. 63 | * This can either come from the RTC, being clocked by an external clock, or a GPIO pin going high or low. 64 | * If no IRQ is configured before going into dormant mode the ROSC will never restart. 65 | * 66 | * PLLs should be stopped before selecting dormant mode. 67 | */ 68 | void rosc_set_dormant(void); 69 | 70 | // FIXME: Add doxygen 71 | 72 | uint32_t next_rosc_code(uint32_t code); 73 | 74 | uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz); 75 | 76 | void rosc_set_div(uint32_t div); 77 | 78 | inline static void rosc_clear_bad_write(void) { 79 | hw_clear_bits(&rosc_hw->status, ROSC_STATUS_BADWRITE_BITS); 80 | } 81 | 82 | inline static bool rosc_write_okay(void) { 83 | return !(rosc_hw->status & ROSC_STATUS_BADWRITE_BITS); 84 | } 85 | 86 | inline static void rosc_write(io_rw_32 *addr, uint32_t value) { 87 | rosc_clear_bad_write(); 88 | assert(rosc_write_okay()); 89 | *addr = value; 90 | assert(rosc_write_okay()); 91 | }; 92 | 93 | #ifdef __cplusplus 94 | } 95 | #endif 96 | 97 | #endif 98 | #endif /* ARDUINO_ARCH_RP2040 */ 99 | -------------------------------------------------------------------------------- /extras/time_rp2040_sleep/src/pico_rosc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | #if defined(ARDUINO_ARCH_RP2040) 7 | 8 | #ifndef _HARDWARE_ROSC_H_ 9 | #define _HARDWARE_ROSC_H_ 10 | 11 | #include "pico.h" 12 | #include "hardware/structs/rosc.h" 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | /** \file rosc.h 19 | * \defgroup hardware_rosc hardware_rosc 20 | * 21 | * Ring Oscillator (ROSC) API 22 | * 23 | * A Ring Oscillator is an on-chip oscillator that requires no external crystal. Instead, the output is generated from a series of 24 | * inverters that are chained together to create a feedback loop. RP2040 boots from the ring oscillator initially, meaning the 25 | * first stages of the bootrom, including booting from SPI flash, will be clocked by the ring oscillator. If your design has a 26 | * crystal oscillator, you’ll likely want to switch to this as your reference clock as soon as possible, because the frequency is 27 | * more accurate than the ring oscillator. 28 | */ 29 | 30 | /*! \brief Set frequency of the Ring Oscillator 31 | * \ingroup hardware_rosc 32 | * 33 | * \param code The drive strengths. See the RP2040 datasheet for information on this value. 34 | */ 35 | void rosc_set_freq(uint32_t code); 36 | 37 | /*! \brief Set range of the Ring Oscillator 38 | * \ingroup hardware_rosc 39 | * 40 | * Frequency range. Frequencies will vary with Process, Voltage & Temperature (PVT). 41 | * Clock output will not glitch when changing the range up one step at a time. 42 | * 43 | * \param range 0x01 Low, 0x02 Medium, 0x03 High, 0x04 Too High. 44 | */ 45 | void rosc_set_range(uint range); 46 | 47 | /*! \brief Disable the Ring Oscillator 48 | * \ingroup hardware_rosc 49 | * 50 | */ 51 | void rosc_disable(void); 52 | 53 | /*! \brief Enable the Ring Oscillator 54 | * \ingroup hardware_rosc 55 | * 56 | */ 57 | void rosc_enable(void); 58 | 59 | /*! \brief Put Ring Oscillator in to dormant mode. 60 | * \ingroup hardware_rosc 61 | * 62 | * The ROSC supports a dormant mode,which stops oscillation until woken up up by an asynchronous interrupt. 63 | * This can either come from the RTC, being clocked by an external clock, or a GPIO pin going high or low. 64 | * If no IRQ is configured before going into dormant mode the ROSC will never restart. 65 | * 66 | * PLLs should be stopped before selecting dormant mode. 67 | */ 68 | void rosc_set_dormant(void); 69 | 70 | // FIXME: Add doxygen 71 | 72 | uint32_t next_rosc_code(uint32_t code); 73 | 74 | uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz); 75 | 76 | void rosc_set_div(uint32_t div); 77 | 78 | inline static void rosc_clear_bad_write(void) { 79 | hw_clear_bits(&rosc_hw->status, ROSC_STATUS_BADWRITE_BITS); 80 | } 81 | 82 | inline static bool rosc_write_okay(void) { 83 | return !(rosc_hw->status & ROSC_STATUS_BADWRITE_BITS); 84 | } 85 | 86 | inline static void rosc_write(io_rw_32 *addr, uint32_t value) { 87 | rosc_clear_bad_write(); 88 | assert(rosc_write_okay()); 89 | *addr = value; 90 | assert(rosc_write_okay()); 91 | }; 92 | 93 | #ifdef __cplusplus 94 | } 95 | #endif 96 | 97 | #endif 98 | #endif /* ARDUINO_ARCH_RP2040 */ 99 | -------------------------------------------------------------------------------- /scripts/examples.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "uplink", 4 | "description": "an example of an uplink frame", 5 | "input": { 6 | "bytes": [0, 126, 7, 238, 42, 7, 0, 7, 0, 168, 7, 103, 102, 103, 68, 73, 16, 9, 185, 45, 6, 94, 33, 0, 0, 0, 0, 0, 0, 128, 191, 0, 0, 128, 191, 0, 0, 128, 191, 0, 0, 0, 0, 0, 0, 0], 7 | "fPort": 1, 8 | "recvTime": "2024-05-13T22:13:00.000+02:00" 9 | }, 10 | "output": { 11 | "bytes": { 12 | // decoded form of the uplink payload 13 | "ws_temp_c": "20.3", 14 | "ws_humidity": 42, 15 | "ble0_humidity": 45, 16 | "ble0_temp_c": "24.9", 17 | "lgt_ev_dist_km": 0, 18 | "lgt_ev_events": 0, 19 | "lgt_ev_time": { 20 | "time": "1970-01-01T00:00:00.000Z", 21 | "timestamp": 0 22 | }, 23 | "ws_rain_daily_mm": "-1.0", 24 | "ws_rain_hourly_mm": "0.0", 25 | "ws_rain_mmin_mm": "925.6", 26 | "ws_rain_monthly_mm": "-1.0", 27 | "ws_rain_weekly_mm": "-1.0", 28 | "soil1_moisture": 33, 29 | "soil1_temp_c": "16.3", 30 | "a0_voltage_mv": 4169, 31 | "ws_wind_avg_ms": "0.7", 32 | "ws_wind_dir_deg": "196.0", 33 | "ws_wind_gust_ms": "0.7" 34 | }, 35 | "warnings": [] 36 | } 37 | }, 38 | { 39 | "type": "downlink-encode", 40 | "description": "an example of a downlink frame to encode", 41 | "input": { 42 | "data": { 43 | // decoded form of the downlink payload 44 | "sleep_interval": 360 45 | } 46 | }, 47 | "output": { 48 | "bytes": [1, 104], 49 | "fPort": 168, 50 | "warnings": [] 51 | } 52 | }, 53 | { 54 | "type": "downlink-decode", 55 | "description": "an example of a downlink frame to decode", 56 | "input": { 57 | "bytes": [1, 104], 58 | "fPort": 168, 59 | "recvTime": "2024-05-13T08:17:00.000+02:00" 60 | }, 61 | "output": { 62 | "data": { 63 | // decoded form of the downlink payload 64 | "sleep_interval": 360 65 | }, 66 | "warnings": [] 67 | } 68 | }, 69 | { 70 | "type": "downlink-encode", 71 | "description": "an example of a downlink frame failed to encode", 72 | "input": { 73 | "data": { 74 | // decoded form of the downlink payload 75 | "cmd": "CMD_UNDEFINED" 76 | } 77 | }, 78 | "output": { 79 | "errors": ["Unknown command"], 80 | "warnings": [] 81 | } 82 | }, 83 | { 84 | "type": "downlink-decode", 85 | "description": "an example of a downlink frame to decode", 86 | "input": { 87 | "bytes": [0], 88 | "fPort": 32, 89 | "recvTime": "2024-05-13T08:22:00.000+02:00" 90 | }, 91 | "output": { 92 | "errors": ["Unknown FPort"], 93 | "warnings": [] 94 | } 95 | } 96 | ] 97 | -------------------------------------------------------------------------------- /src/PayloadDigital.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // PayloadDigital.h 3 | // 4 | // Read digital input channels and encode as LoRaWAN payload 5 | // 6 | // created: 05/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2024 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // 32 | // History: 33 | // 34 | // 20240520 Created 35 | // 36 | // ToDo: 37 | // - 38 | // 39 | /////////////////////////////////////////////////////////////////////////////// 40 | 41 | /*! \file PayloadDigital.h 42 | * \brief LoRaWAN node application layer - digital channels 43 | */ 44 | 45 | #if !defined(_PAYLOAD_DIGITAL) 46 | #define _PAYLOAD_DIGITAL 47 | 48 | #include "../BresserWeatherSensorLWCfg.h" 49 | 50 | #include 51 | #include "logging.h" 52 | 53 | #ifdef DISTANCESENSOR_EN 54 | // A02YYUW / DFRobot SEN0311 Ultrasonic Distance Sensor 55 | #include 56 | #endif 57 | 58 | /*! 59 | * \brief LoRaWAN node application layer - digital channels 60 | * 61 | * Encodes data from digital input channels as LoRaWAN payload 62 | */ 63 | class PayloadDigital 64 | { 65 | public: 66 | /*! 67 | * \brief Constructor 68 | */ 69 | PayloadDigital(){}; 70 | 71 | /*! 72 | * \brief Digital channel startup code 73 | */ 74 | void begin(void); 75 | 76 | /*! 77 | * \brief Encode digital data channels for LoRaWAN transmission 78 | * 79 | * \param appPayloadCfg LoRaWAN payload configuration bitmaps 80 | * \param encoder LoRaWAN payload encoder object 81 | */ 82 | void encodeDigital(uint8_t *appPayloadCfg, LoraEncoder &encoder); 83 | 84 | private: 85 | #ifdef DISTANCESENSOR_EN 86 | /*! 87 | * \brief Initialize ultrasonic distance sensor A02YYUW 88 | * 89 | * Initialize UART and power enable pin 90 | */ 91 | void initDistanceSensor(void); 92 | 93 | /*! 94 | * \brief Read ultrasonic distance sensor (A02YYUW) data 95 | * 96 | * \returns distance in mm (0 if invalid) 97 | */ 98 | uint16_t readDistanceSensor(void) 99 | #endif 100 | }; 101 | #endif //_PAYLOAD_DIGITAL -------------------------------------------------------------------------------- /extras/customization/AppLayerMinimal.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // AppLayerMinimal.cpp 3 | // 4 | // LoRaWAN node application layer - Minimal template for customization 5 | // 6 | // - Create data payload from sensor or simulated data 7 | // - Decode sensor specific commands 8 | // - Encode sensor specific status responses 9 | // - Retain sensor specific parameters 10 | // 11 | // created: 05/2024 12 | // 13 | // 14 | // MIT License 15 | // 16 | // Copyright (c) 2024 Matthias Prinke 17 | // 18 | // Permission is hereby granted, free of charge, to any person obtaining a copy 19 | // of this software and associated documentation files (the "Software"), to deal 20 | // in the Software without restriction, including without limitation the rights 21 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | // copies of the Software, and to permit persons to whom the Software is 23 | // furnished to do so, subject to the following conditions: 24 | // 25 | // The above copyright notice and this permission notice shall be included in all 26 | // copies or substantial portions of the Software. 27 | // 28 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | // SOFTWARE. 35 | // 36 | // 37 | // History: 38 | // 39 | // 20240513 Created 40 | // 20240730 Updated getPayloadStage1()/2() 41 | // 42 | // 43 | // ToDo: 44 | // - 45 | // 46 | /////////////////////////////////////////////////////////////////////////////// 47 | 48 | #include "AppLayer.h" 49 | 50 | uint8_t 51 | AppLayer::decodeDownlink(uint8_t port, uint8_t *payload, size_t size) 52 | { 53 | return 0; 54 | } 55 | 56 | void AppLayer::genPayload(uint8_t port, LoraEncoder &encoder) 57 | { 58 | (void)port; // suppress warning regarding unused parameter 59 | (void)encoder; // suppress warning regarding unused parameter 60 | } 61 | 62 | void AppLayer::getPayloadStage1(uint8_t &port, LoraEncoder &encoder) 63 | { 64 | (void)port; // suppress warning regarding unused parameter 65 | 66 | // Sensor status flags 67 | encoder.writeBitmap(true, 68 | false, 69 | false, 70 | false, 71 | false, 72 | false, 73 | false, 74 | false); 75 | 76 | // Example data 77 | encoder.writeUint8(0xaa); 78 | encoder.writeRawFloat(21.3); 79 | } 80 | 81 | void AppLayer::getPayloadStage2(uint8_t &port, LoraEncoder &encoder) 82 | { 83 | (void)port; // suppress warning regarding unused parameter 84 | (void)encoder; // suppress warning regarding unused parameter 85 | } 86 | 87 | void AppLayer::getConfigPayload(uint8_t cmd, uint8_t &port, LoraEncoder &encoder) 88 | { 89 | (void)cmd; // suppress warning regarding unused parameter 90 | (void)port; // suppress warning regarding unused parameter 91 | (void)encoder; // suppress warning regarding unused parameter 92 | } 93 | -------------------------------------------------------------------------------- /extras/time_esp32_sleep/time_esp32_sleep.ino: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // time_esp32_sleep.ino 3 | // 4 | // Test RTC running while ESP32 is in deep sleep mode 5 | // 6 | // - Initially, the RTC is set to epoch time INIT_TIME 7 | // - After initialization (flag rtcSet stored in RTC RAM), 8 | // the time is read from RTC and printed 9 | // 10 | // created: 08/2025 11 | // 12 | // 13 | // MIT License 14 | // 15 | // Copyright (c) 2025 Matthias Prinke 16 | // 17 | // Permission is hereby granted, free of charge, to any person obtaining a copy 18 | // of this software and associated documentation files (the "Software"), to deal 19 | // in the Software without restriction, including without limitation the rights 20 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | // copies of the Software, and to permit persons to whom the Software is 22 | // furnished to do so, subject to the following conditions: 23 | // 24 | // The above copyright notice and this permission notice shall be included in all 25 | // copies or substantial portions of the Software. 26 | // 27 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | // SOFTWARE. 34 | // 35 | // History: 36 | // 37 | // 20250809 Created 38 | // 39 | /////////////////////////////////////////////////////////////////////////////// 40 | 41 | #include 42 | #include 43 | 44 | #define TZINFO "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00" 45 | 46 | RTC_DATA_ATTR bool rtcSet = false; 47 | 48 | // 2025-01-01 00:00:00 UTC 49 | const time_t INIT_TIME = 1735689600; 50 | 51 | // Sleep time in seconds 52 | const unsigned int SLEEP_TIME = 30; 53 | 54 | /*! 55 | * \brief Print local time and date 56 | */ 57 | void printTime(void) 58 | { 59 | time_t now_t = time(nullptr); 60 | if (now_t == -1) 61 | { 62 | Serial.println("Failed to obtain time"); 63 | } 64 | 65 | struct tm *tm_info = localtime(&now_t); 66 | 67 | char buffer[20]; 68 | strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", tm_info); 69 | Serial.printf("Local time: %s\n\n", buffer); 70 | } 71 | 72 | void setup() 73 | { 74 | Serial.begin(115200); 75 | 76 | #ifndef ESP8266 77 | while (!Serial) 78 | ; // wait for serial port to connect. Needed for native USB 79 | #endif 80 | 81 | Serial.println("Test ESP32 RTC with deep sleep mode"); 82 | 83 | // Set timezone 84 | setenv("TZ", TZINFO, 1); 85 | tzset(); 86 | 87 | if (!rtcSet) 88 | { 89 | Serial.println("Initializing RTC"); 90 | 91 | // Set the internal RTC 92 | struct timeval tv = {INIT_TIME, 0}; // `t` is seconds, 0 is microseconds 93 | settimeofday(&tv, nullptr); 94 | 95 | rtcSet = true; 96 | } 97 | printTime(); 98 | Serial.println("Sleeping..."); 99 | Serial.flush(); 100 | esp_sleep_enable_timer_wakeup(SLEEP_TIME * 1000UL * 1000UL); // function uses uS 101 | esp_deep_sleep_start(); 102 | } 103 | 104 | void loop() 105 | { 106 | delay(100); 107 | } -------------------------------------------------------------------------------- /extras/time_set_epoch/time_set_epoch.ino: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // time_set_epoch.ino 3 | // 4 | // Test setting time from epoch (Unix time), e.g. from a LoRaWAN network 5 | // 6 | // See 7 | // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system_time.html 8 | // https://www.gnu.org/software/libc/manual/html_node/Calendar-Time.html 9 | // 10 | // created: 08/2025 11 | // 12 | // 13 | // MIT License 14 | // 15 | // Copyright (c) 2025 Matthias Prinke 16 | // 17 | // Permission is hereby granted, free of charge, to any person obtaining a copy 18 | // of this software and associated documentation files (the "Software"), to deal 19 | // in the Software without restriction, including without limitation the rights 20 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | // copies of the Software, and to permit persons to whom the Software is 22 | // furnished to do so, subject to the following conditions: 23 | // 24 | // The above copyright notice and this permission notice shall be included in all 25 | // copies or substantial portions of the Software. 26 | // 27 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | // SOFTWARE. 34 | // 35 | // History: 36 | // 37 | // 20250808 Created 38 | // 39 | /////////////////////////////////////////////////////////////////////////////// 40 | 41 | #include 42 | #include 43 | 44 | #define TZINFO "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00" 45 | 46 | /*! 47 | * \brief Set RTC from epoch 48 | * 49 | * \param epochTime epoch time 50 | */ 51 | void setLocalTimeFromEpoch(time_t epochTime) 52 | { 53 | timeval epoch = {epochTime, 0}; 54 | const timeval *tv = &epoch; 55 | timezone utc = {0, 0}; 56 | const timezone *tz = &utc; 57 | settimeofday(tv, tz); 58 | } 59 | 60 | /*! 61 | * \brief Print local time and date 62 | */ 63 | void printTime(void) 64 | { 65 | time_t now_t = time(nullptr); 66 | if (now_t == -1) 67 | { 68 | Serial.println("Failed to obtain time"); 69 | } 70 | 71 | struct tm *tm_info = localtime(&now_t); 72 | 73 | char buffer[20]; 74 | strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", tm_info); 75 | Serial.printf("Local time: %s\n\n", buffer); 76 | } 77 | 78 | void setup() 79 | { 80 | Serial.begin(115200); 81 | 82 | #ifndef ESP8266 83 | while (!Serial) 84 | ; // wait for serial port to connect. Needed for native USB 85 | #endif 86 | 87 | // Set timezone 88 | setenv("TZ", TZINFO, 1); 89 | tzset(); 90 | 91 | Serial.println("Set time from epoch (Unix time)"); 92 | 93 | Serial.println("Time set: 2025-01-01T00:00:00 UTC"); 94 | 95 | time_t epochTime = 1735689600; // Example epoch time (2025-01-01 00:00:00 UTC) 96 | setLocalTimeFromEpoch(epochTime); 97 | printTime(); 98 | 99 | Serial.println("Time set: 2025-06-01T00:00:00 UTC"); 100 | epochTime = 1748736000; // Example epoch time (2025-06-01 00:00:00 UTC) 101 | 102 | setLocalTimeFromEpoch(epochTime); 103 | printTime(); 104 | } 105 | 106 | void loop() 107 | { 108 | delay(100); 109 | } 110 | -------------------------------------------------------------------------------- /src/rp2040/pico_sleep.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | #if defined(ARDUINO_ARCH_RP2040) 7 | 8 | #ifndef _PICO_SLEEP_H_ 9 | #define _PICO_SLEEP_H_ 10 | 11 | #include "pico.h" 12 | #include "hardware/rtc.h" 13 | #include "hardware/clocks.h" 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif 18 | 19 | /** \file sleep.h 20 | * \defgroup hardware_sleep hardware_sleep 21 | * 22 | * Lower Power Sleep API 23 | * 24 | * The difference between sleep and dormant is that ALL clocks are stopped in dormant mode, 25 | * until the source (either xosc or rosc) is started again by an external event. 26 | * In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks 27 | * block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) 28 | * can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again. 29 | * 30 | * \subsection sleep_example Example 31 | * \addtogroup hardware_sleep 32 | * \include hello_sleep.c 33 | 34 | */ 35 | typedef enum { 36 | DORMANT_SOURCE_NONE, 37 | DORMANT_SOURCE_XOSC, 38 | DORMANT_SOURCE_ROSC 39 | } dormant_source_t; 40 | 41 | /*! \brief Set all clock sources to the the dormant clock source to prepare for sleep. 42 | * \ingroup hardware_sleep 43 | * 44 | * \param dormant_source The dormant clock source to use 45 | */ 46 | void sleep_run_from_dormant_source(dormant_source_t dormant_source); 47 | 48 | /*! \brief Set the dormant clock source to be the crystal oscillator 49 | * \ingroup hardware_sleep 50 | */ 51 | static inline void sleep_run_from_xosc(void) { 52 | sleep_run_from_dormant_source(DORMANT_SOURCE_XOSC); 53 | } 54 | 55 | /*! \brief Set the dormant clock source to be the ring oscillator 56 | * \ingroup hardware_sleep 57 | */ 58 | static inline void sleep_run_from_rosc(void) { 59 | sleep_run_from_dormant_source(DORMANT_SOURCE_ROSC); 60 | } 61 | 62 | /*! \brief Send system to sleep until the specified time 63 | * \ingroup hardware_sleep 64 | * 65 | * One of the sleep_run_* functions must be called prior to this call 66 | * 67 | * \param t The time to wake up 68 | * \param callback Function to call on wakeup. 69 | */ 70 | void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback); 71 | 72 | /*! \brief Send system to sleep until the specified GPIO changes 73 | * \ingroup hardware_sleep 74 | * 75 | * One of the sleep_run_* functions must be called prior to this call 76 | * 77 | * \param gpio_pin The pin to provide the wake up 78 | * \param edge true for leading edge, false for trailing edge 79 | * \param high true for active high, false for active low 80 | */ 81 | void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high); 82 | 83 | /*! \brief Send system to sleep until a leading high edge is detected on GPIO 84 | * \ingroup hardware_sleep 85 | * 86 | * One of the sleep_run_* functions must be called prior to this call 87 | * 88 | * \param gpio_pin The pin to provide the wake up 89 | */ 90 | static inline void sleep_goto_dormant_until_edge_high(uint gpio_pin) { 91 | sleep_goto_dormant_until_pin(gpio_pin, true, true); 92 | } 93 | 94 | /*! \brief Send system to sleep until a high level is detected on GPIO 95 | * \ingroup hardware_sleep 96 | * 97 | * One of the sleep_run_* functions must be called prior to this call 98 | * 99 | * \param gpio_pin The pin to provide the wake up 100 | */ 101 | static inline void sleep_goto_dormant_until_level_high(uint gpio_pin) { 102 | sleep_goto_dormant_until_pin(gpio_pin, false, true); 103 | } 104 | 105 | #ifdef __cplusplus 106 | } 107 | #endif 108 | 109 | #endif 110 | #endif /* ARDUINO_ARCH_RP2040 */ 111 | -------------------------------------------------------------------------------- /extras/time_rp2040_sleep/src/pico_sleep.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | #if defined(ARDUINO_ARCH_RP2040) 7 | 8 | #ifndef _PICO_SLEEP_H_ 9 | #define _PICO_SLEEP_H_ 10 | 11 | #include "pico.h" 12 | #include "hardware/rtc.h" 13 | #include "hardware/clocks.h" 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif 18 | 19 | /** \file sleep.h 20 | * \defgroup hardware_sleep hardware_sleep 21 | * 22 | * Lower Power Sleep API 23 | * 24 | * The difference between sleep and dormant is that ALL clocks are stopped in dormant mode, 25 | * until the source (either xosc or rosc) is started again by an external event. 26 | * In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks 27 | * block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) 28 | * can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again. 29 | * 30 | * \subsection sleep_example Example 31 | * \addtogroup hardware_sleep 32 | * \include hello_sleep.c 33 | 34 | */ 35 | typedef enum { 36 | DORMANT_SOURCE_NONE, 37 | DORMANT_SOURCE_XOSC, 38 | DORMANT_SOURCE_ROSC 39 | } dormant_source_t; 40 | 41 | /*! \brief Set all clock sources to the the dormant clock source to prepare for sleep. 42 | * \ingroup hardware_sleep 43 | * 44 | * \param dormant_source The dormant clock source to use 45 | */ 46 | void sleep_run_from_dormant_source(dormant_source_t dormant_source); 47 | 48 | /*! \brief Set the dormant clock source to be the crystal oscillator 49 | * \ingroup hardware_sleep 50 | */ 51 | static inline void sleep_run_from_xosc(void) { 52 | sleep_run_from_dormant_source(DORMANT_SOURCE_XOSC); 53 | } 54 | 55 | /*! \brief Set the dormant clock source to be the ring oscillator 56 | * \ingroup hardware_sleep 57 | */ 58 | static inline void sleep_run_from_rosc(void) { 59 | sleep_run_from_dormant_source(DORMANT_SOURCE_ROSC); 60 | } 61 | 62 | /*! \brief Send system to sleep until the specified time 63 | * \ingroup hardware_sleep 64 | * 65 | * One of the sleep_run_* functions must be called prior to this call 66 | * 67 | * \param t The time to wake up 68 | * \param callback Function to call on wakeup. 69 | */ 70 | void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback); 71 | 72 | /*! \brief Send system to sleep until the specified GPIO changes 73 | * \ingroup hardware_sleep 74 | * 75 | * One of the sleep_run_* functions must be called prior to this call 76 | * 77 | * \param gpio_pin The pin to provide the wake up 78 | * \param edge true for leading edge, false for trailing edge 79 | * \param high true for active high, false for active low 80 | */ 81 | void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high); 82 | 83 | /*! \brief Send system to sleep until a leading high edge is detected on GPIO 84 | * \ingroup hardware_sleep 85 | * 86 | * One of the sleep_run_* functions must be called prior to this call 87 | * 88 | * \param gpio_pin The pin to provide the wake up 89 | */ 90 | static inline void sleep_goto_dormant_until_edge_high(uint gpio_pin) { 91 | sleep_goto_dormant_until_pin(gpio_pin, true, true); 92 | } 93 | 94 | /*! \brief Send system to sleep until a high level is detected on GPIO 95 | * \ingroup hardware_sleep 96 | * 97 | * One of the sleep_run_* functions must be called prior to this call 98 | * 99 | * \param gpio_pin The pin to provide the wake up 100 | */ 101 | static inline void sleep_goto_dormant_until_level_high(uint gpio_pin) { 102 | sleep_goto_dormant_until_pin(gpio_pin, false, true); 103 | } 104 | 105 | #ifdef __cplusplus 106 | } 107 | #endif 108 | 109 | #endif 110 | #endif /* ARDUINO_ARCH_RP2040 */ 111 | -------------------------------------------------------------------------------- /src/LoadNodeCfg.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // LoadNodeCfg.cpp 3 | // 4 | // Load LoRaWAN node configuration 'node_config.json' from LittleFS, if available 5 | // 6 | // This configuration file is intended for hardware/deployment environment 7 | // specific settings (e.g. battery voltage thresholds, timezone) 8 | // 9 | // created: 07/2024 10 | // 11 | // 12 | // MIT License 13 | // 14 | // Copyright (c) 2024 Matthias Prinke 15 | // 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // 23 | // The above copyright notice and this permission notice shall be included in all 24 | // copies or substantial portions of the Software. 25 | // 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | // SOFTWARE. 33 | // 34 | // 35 | // History: 36 | // 37 | // 20240725 Created 38 | // 20240729 Added PowerFeather specific configuration 39 | // 20240804 Added max_charge_current 40 | // 20250827 Changed battery_low to voltage_critical 41 | // Changed battery_weak to voltage_eco_enter/exit 42 | // 20251017 Added soc_eco_enter/exit 43 | // 20251031 Added M5Stack configuration 44 | // 45 | // ToDo: 46 | // - 47 | // 48 | /////////////////////////////////////////////////////////////////////////////// 49 | 50 | /*! \file LoadNodeCfg.h 51 | * \brief Load LoRaWAN node configuration 'node_config.json' from LittleFS, if available 52 | * 53 | * This configuration file is intended for hardware/deployment environment 54 | * specific settings (e.g. battery voltage thresholds, timezone). 55 | */ 56 | 57 | #include 58 | #include 59 | #include 60 | #include "../BresserWeatherSensorLWCfg.h" 61 | #include "logging.h" 62 | 63 | /*! 64 | * \brief Load LoRaWAN node configuration 'node_config.json' from LittleFS, if available 65 | * 66 | * Returns all values by reference. Keeps the original value(s) if file not found, 67 | * cannot be parsed or any value is missing. 68 | * 69 | * JSON file format: 70 | * { 71 | * "timezone": "CET-1CEST,M3.5.0,M10.5.0/3", 72 | * "voltage_eco_exit": 3580, 73 | * "voltage_eco_enter": 3500, 74 | * "voltage_critical": 3300, 75 | * "battery_discharge_lim": 3200, 76 | * "battery_charge_lim": 4200, 77 | * "powerfeather": { 78 | * "battery_capacity":, 2200 79 | * "supply_maintain_voltage": 5500, 80 | * "max_charge_current": 50, 81 | * "soc_eco_enter": 20, 82 | * "soc_eco_exit": 25, 83 | * "soc_critical": 5, 84 | * "temperature_measurement": false, 85 | * "battery_fuel_gauge": true 86 | * }, 87 | * "m5stack": { 88 | * "soc_eco_enter": 20, 89 | * "soc_eco_exit": 25, 90 | * "soc_critical": 3 91 | * } 92 | * } 93 | */ 94 | void loadNodeCfg( 95 | String &tzinfo, 96 | uint16_t &voltage_eco_exit, 97 | uint16_t &voltage_eco_enter, 98 | uint16_t &voltage_critical, 99 | uint16_t &batt_discharge_lim, 100 | uint16_t &batt_charge_lim, 101 | struct sPowerFeatherCfg &powerFeatherCfg, 102 | struct sM5StackCfg &m5StackCfg 103 | ); -------------------------------------------------------------------------------- /extras/time_y2038/time_y2038.ino: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // time_y2038.ino 3 | // 4 | // Test for the year 2038 problem (no issue) 5 | // 6 | // See 7 | // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system_time.html#unix-time-2038-overflow 8 | // 9 | // created: 08/2025 10 | // 11 | // 12 | // MIT License 13 | // 14 | // Copyright (c) 2025 Matthias Prinke 15 | // 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // 23 | // The above copyright notice and this permission notice shall be included in all 24 | // copies or substantial portions of the Software. 25 | // 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | // SOFTWARE. 33 | // 34 | // History: 35 | // 36 | // 20250808 Created 37 | // 38 | /////////////////////////////////////////////////////////////////////////////// 39 | 40 | #include 41 | #include 42 | 43 | 44 | void setup() 45 | { 46 | Serial.begin(115200); 47 | 48 | #ifndef ESP8266 49 | while (!Serial) 50 | ; // wait for serial port to connect. Needed for native USB 51 | #endif 52 | 53 | Serial.println("Time Y2038 Test"); 54 | Serial.printf("sizeof(time_t) = %zu bytes\n", sizeof(time_t)); 55 | Serial.println("The time should advance past 2038-01-19 03:14:07 UTC"); 56 | String input_str = "2038-01-19 03:13:00"; 57 | 58 | // Parse the input string 59 | int year, month, day, hour, minute, second; 60 | if (sscanf(input_str.c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second) == 6) 61 | { 62 | struct tm timeinfo; 63 | timeinfo.tm_year = year - 1900; 64 | timeinfo.tm_mon = month - 1; 65 | timeinfo.tm_mday = day; 66 | timeinfo.tm_hour = hour; 67 | timeinfo.tm_min = minute; 68 | timeinfo.tm_sec = second; 69 | 70 | time_t t = mktime(&timeinfo); 71 | 72 | // Set the internal RTC 73 | struct timeval tv = {t, 0}; // `t` is seconds, 0 is microseconds 74 | settimeofday(&tv, nullptr); 75 | Serial.printf("Set time to %04d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second); 76 | Serial.printf("32-bit time_t value: 0x%llX\n", (long long)t); 77 | } 78 | } 79 | 80 | void loop() 81 | { 82 | time_t now_t = time(nullptr); 83 | if (now_t == -1) 84 | { 85 | Serial.println("Failed to obtain time"); 86 | } 87 | 88 | // Convert time_t to struct tm 89 | struct tm *tm_info = localtime(&now_t); 90 | 91 | Serial.printf("32-bit time_t value: 0x%llX\n", (long long)now_t); 92 | Serial.print("Current time: "); 93 | Serial.print(asctime(tm_info)); 94 | 95 | // Print the RTC time in ISO 8601 format 96 | char buffer[20]; 97 | strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", tm_info); 98 | Serial.print("ISO 8601 format: "); 99 | Serial.println(buffer); 100 | Serial.println(); 101 | 102 | // Wait for a while before the next loop iteration 103 | delay(5000); 104 | } 105 | -------------------------------------------------------------------------------- /scripts/bresserweathersensorlw-codec/index.js: -------------------------------------------------------------------------------- 1 | // LoRaWAN Codec API compliant implementation for BresserWeatherSensorLW 2 | // Robust handling of different formatter export styles. 3 | 4 | let uplinkFormatter; 5 | let downlinkFormatter; 6 | try { uplinkFormatter = require('./uplink_formatter.js'); } catch (e) { uplinkFormatter = undefined; } 7 | try { downlinkFormatter = require('./downlink_formatter.js'); } catch (e) { downlinkFormatter = undefined; } 8 | 9 | /** 10 | * Try calling formatter module with a target function name. 11 | * Supports many common export patterns and returns undefined if nothing matches. 12 | */ 13 | function callFormatter(mod, fnName, input) { 14 | if (!mod) return undefined; 15 | 16 | // module exported as function (common simple pattern) 17 | if (typeof mod === 'function') { 18 | try { return mod(input); } catch (e) { return undefined; } 19 | } 20 | 21 | // direct named export 22 | if (typeof mod[fnName] === 'function') { 23 | try { return mod[fnName](input); } catch (e) { return undefined; } 24 | } 25 | 26 | // default export as function or object 27 | if (mod.default) { 28 | if (typeof mod.default === 'function') { 29 | try { return mod.default(input); } catch (e) { /* ignore */ } 30 | } 31 | if (mod.default && typeof mod.default[fnName] === 'function') { 32 | try { return mod.default[fnName](input); } catch (e) { /* ignore */ } 33 | } 34 | } 35 | 36 | // generic encode/decode functions 37 | if (fnName.startsWith('decode') && typeof mod.decode === 'function') { 38 | try { return mod.decode(input); } catch (e) { return undefined; } 39 | } 40 | if (fnName.startsWith('encode') && typeof mod.encode === 'function') { 41 | try { return mod.encode(input); } catch (e) { return undefined; } 42 | } 43 | 44 | // some modules expose handler/process/run 45 | const alt = ['handler', 'process', 'run']; 46 | for (const a of alt) { 47 | if (typeof mod[a] === 'function') { 48 | try { return mod[a](input); } catch (e) { /* ignore */ } 49 | } 50 | if (mod[a] && typeof mod[a][fnName] === 'function') { 51 | try { return mod[a][fnName](input); } catch (e) { /* ignore */ } 52 | } 53 | } 54 | 55 | // nothing matched 56 | return undefined; 57 | } 58 | 59 | function normalizeBytes(x) { 60 | if (Buffer.isBuffer(x)) return x; 61 | if (!x) return Buffer.from([]); 62 | if (Array.isArray(x) || x instanceof Uint8Array) return Buffer.from(x); 63 | try { return Buffer.from(x); } catch (e) { return Buffer.from([]); } 64 | } 65 | 66 | /* Encode downlink (JSON -> bytes) */ 67 | function encodeDownlinkApi(input) { 68 | const result = callFormatter(downlinkFormatter, 'encodeDownlink', input) || {}; 69 | return { 70 | bytes: normalizeBytes(result.bytes), 71 | fPort: result.fPort || (input && input.fPort), 72 | warnings: result.warnings || [], 73 | errors: result.errors || [] 74 | }; 75 | } 76 | 77 | /* Decode downlink (bytes -> JSON) */ 78 | function decodeDownlinkApi(input) { 79 | const bytesArr = Array.isArray(input.bytes) ? input.bytes : Array.from(input.bytes || []); 80 | const result = callFormatter(downlinkFormatter, 'decodeDownlink', { bytes: bytesArr, fPort: input.fPort }) || {}; 81 | return { 82 | data: result.data || {}, 83 | warnings: result.warnings || [], 84 | errors: result.errors || [] 85 | }; 86 | } 87 | 88 | /* Decode uplink (bytes -> JSON) */ 89 | function decodeUplinkApi(input) { 90 | const bytesArr = Array.isArray(input.bytes) ? input.bytes : Array.from(input.bytes || []); 91 | const result = callFormatter(uplinkFormatter, 'decodeUplink', { bytes: bytesArr, fPort: input.fPort }) || {}; 92 | return { 93 | data: result.data || {}, 94 | warnings: result.warnings || [], 95 | errors: result.errors || [] 96 | }; 97 | } 98 | 99 | module.exports = { 100 | encodeDownlink: encodeDownlinkApi, 101 | decodeDownlink: decodeDownlinkApi, 102 | decodeUplink: decodeUplinkApi 103 | }; -------------------------------------------------------------------------------- /extras/time_rp2040_sleep/src/logging.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // logging.h 3 | // 4 | // Replacement for 5 | // https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-log.h 6 | // on RP2040 7 | // 8 | // - DEBUG_RP2040_PORT is set in Arduino IDE: 9 | // Tools->Debug port: "|||" 10 | // - CORE_DEBUG_LEVEL has to be set manually below 11 | // 12 | // created: 09/2023 13 | // 14 | // 15 | // MIT License 16 | // 17 | // Copyright (c) 2023 Matthias Prinke 18 | // 19 | // Permission is hereby granted, free of charge, to any person obtaining a copy 20 | // of this software and associated documentation files (the "Software"), to deal 21 | // in the Software without restriction, including without limitation the rights 22 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | // copies of the Software, and to permit persons to whom the Software is 24 | // furnished to do so, subject to the following conditions: 25 | // 26 | // The above copyright notice and this permission notice shall be included in all 27 | // copies or substantial portions of the Software. 28 | // 29 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | // SOFTWARE. 36 | // 37 | // 38 | // History: 39 | // 40 | // 20230927 Created from BresserWeatherSensorReceiver 41 | // 20231004 Added function names and line numbers to ESP8266/RP2040 debug logging 42 | // 20231005 Allowed re-definition of CORE_DEBUG_LEVEL and log_* macros 43 | // 44 | // ToDo: 45 | // - 46 | // 47 | /////////////////////////////////////////////////////////////////////////////// 48 | 49 | #ifndef LOGGING_H 50 | #define LOGGING_H 51 | 52 | #if defined(ARDUINO_ARCH_RP2040) 53 | 54 | #if defined(DEBUG_RP2040_PORT) 55 | #define DEBUG_PORT DEBUG_RP2040_PORT 56 | #endif 57 | 58 | #define ARDUHAL_LOG_LEVEL_NONE 0 59 | #define ARDUHAL_LOG_LEVEL_ERROR 1 60 | #define ARDUHAL_LOG_LEVEL_WARN 2 61 | #define ARDUHAL_LOG_LEVEL_INFO 3 62 | #define ARDUHAL_LOG_LEVEL_DEBUG 4 63 | #define ARDUHAL_LOG_LEVEL_VERBOSE 5 64 | 65 | // '#undef' allows to change a previous definition from WeatherSensor.h 66 | #undef log_e 67 | #undef log_w 68 | #undef log_i 69 | #undef log_d 70 | #undef log_v 71 | 72 | // Set desired level here! 73 | #undef CORE_DEBUG_LEVEL 74 | #define CORE_DEBUG_LEVEL ARDUHAL_LOG_LEVEL_DEBUG 75 | 76 | #if defined(DEBUG_PORT) && CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_NONE 77 | #define log_e(...) { DEBUG_PORT.printf("%s(), l.%d: ", __func__, __LINE__); DEBUG_PORT.println(); } 78 | #else 79 | #define log_e(...) {} 80 | #endif 81 | #if defined(DEBUG_PORT) && CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_ERROR 82 | #define log_w(...) { DEBUG_PORT.printf("%s(), l.%d: ", __func__, __LINE__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 83 | #else 84 | #define log_w(...) {} 85 | #endif 86 | #if defined(DEBUG_PORT) && CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_WARN 87 | #define log_i(...) { DEBUG_PORT.printf("%s(), l.%d: ", __func__, __LINE__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 88 | #else 89 | #define log_i(...) {} 90 | #endif 91 | #if defined(DEBUG_PORT) && CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_INFO 92 | #define log_d(...) { DEBUG_PORT.printf("%s(), l.%d: ", __func__, __LINE__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 93 | #else 94 | #define log_d(...) {} 95 | #endif 96 | #if defined(DEBUG_PORT) && CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_DEBUG 97 | #define log_v(...) { DEBUG_PORT.printf("%s(), l.%d: ", __func__, __LINE__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 98 | #else 99 | #define log_v(...) {} 100 | #endif 101 | 102 | #endif // defined(ARDUINO_ARCH_RP2040) 103 | #endif // LOGGING_H -------------------------------------------------------------------------------- /extras/time_rp2040_sleep/time_rp2040_sleep.ino: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // time_rp2040_sleep.ino 3 | // 4 | // Test RTC running while RP2040 is in sleep mode 5 | // (incl. hack to survive restart) 6 | // 7 | // - Initially, the SW RTC is set to epoch time INIT_TIME 8 | // - Before going to sleep, the HW RTC is set to the current time and date 9 | // - After wake-up, the HW RTC provides the current (i.e. updated) time and date 10 | // - To mimic the ESP32's wake-up from deep sleep, a restart is done 11 | // - The restart resets the RTC, therefore the epoch time is temporarily saved 12 | // in the Watchdog Scratch register, which retains its value durung restart 13 | // - After restart, the SW RTC is re-initialized with the saved RTC value 14 | // 15 | // created: 08/2025 16 | // 17 | // 18 | // MIT License 19 | // 20 | // Copyright (c) 2025 Matthias Prinke 21 | // 22 | // Permission is hereby granted, free of charge, to any person obtaining a copy 23 | // of this software and associated documentation files (the "Software"), to deal 24 | // in the Software without restriction, including without limitation the rights 25 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 26 | // copies of the Software, and to permit persons to whom the Software is 27 | // furnished to do so, subject to the following conditions: 28 | // 29 | // The above copyright notice and this permission notice shall be included in all 30 | // copies or substantial portions of the Software. 31 | // 32 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 35 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 36 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 37 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 38 | // SOFTWARE. 39 | // 40 | // History: 41 | // 42 | // 20250811 Created 43 | // 44 | /////////////////////////////////////////////////////////////////////////////// 45 | 46 | #include 47 | #if defined(ARDUINO_ARCH_RP2040) 48 | #include "src/pico_rtc_utils.h" 49 | #include 50 | #endif 51 | #include 52 | 53 | // 2025-01-01 00:00:00 UTC 54 | const time_t INIT_TIME = 1735689600; 55 | 56 | void wakeUp(void) 57 | { 58 | // see pico-sdk/src/rp2_common/hardware_rtc/rtc.c 59 | rtc_init(); 60 | 61 | time_t now; 62 | 63 | // Restore RTC from Watchdog Scratch register after reset 64 | if (watchdog_hw->scratch[0] == 0) { 65 | now = INIT_TIME; 66 | } else { 67 | now = watchdog_hw->scratch[0]; 68 | } 69 | 70 | datetime_t dt; 71 | epoch_to_datetime(&now, &dt); 72 | 73 | // Set HW clock (only used in sleep mode) 74 | rtc_set_datetime(&dt); 75 | 76 | // Set SW clock 77 | struct timeval tv = {now, 0}; // `t` is seconds, 0 is microseconds 78 | settimeofday(&tv, nullptr); 79 | } 80 | 81 | /*! 82 | * \brief Enter sleep mode (RP2040 variant) 83 | * 84 | * \param seconds sleep duration in seconds 85 | */ 86 | void gotoSleep(uint32_t seconds) 87 | { 88 | Serial.printf("Sleeping for %lu s\n", seconds); 89 | Serial.flush(); 90 | time_t t_now = time(nullptr); 91 | datetime_t dt; 92 | epoch_to_datetime(&t_now, &dt); 93 | rtc_set_datetime(&dt); 94 | sleep_us(64); 95 | pico_sleep(seconds); 96 | 97 | // Save the current time, because RTC will be reset (SIC!) 98 | rtc_get_datetime(&dt); 99 | time_t now = datetime_to_epoch(&dt, NULL); 100 | watchdog_hw->scratch[0] = now; 101 | Serial.printf("Now: %llu\n", now); 102 | 103 | rp2040.restart(); 104 | } 105 | 106 | /*! 107 | * \brief Print local time and date 108 | */ 109 | void printTime(void) 110 | { 111 | time_t now_t = time(nullptr); 112 | if (now_t == -1) 113 | { 114 | Serial.println("Failed to obtain time"); 115 | } 116 | 117 | struct tm *tm_info = localtime(&now_t); 118 | 119 | char buffer[20]; 120 | strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", tm_info); 121 | Serial.printf("Local time: %s\n", buffer); 122 | } 123 | 124 | void setup() 125 | { 126 | Serial.begin(115200); 127 | delay(3000); 128 | 129 | wakeUp(); 130 | printTime(); 131 | gotoSleep(30); 132 | } 133 | 134 | void loop() 135 | { 136 | delay(100); 137 | } 138 | -------------------------------------------------------------------------------- /src/PayloadOneWire.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // PayloadOneWire.cpp 3 | // 4 | // Get 1-Wire temperature sensor values and encode as LoRaWAN payload 5 | // 6 | // created: 05/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2024 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // 32 | // History: 33 | // 34 | // 20240520 Created 35 | // 20240524 Added payload size check, changed bitmap order 36 | // 20240528 Changed index count direction, fixed signalling of invalid data 37 | // 20250318 Renamed PAYLOAD_SIZE to MAX_UPLINK_SIZE 38 | // 20250625 Added missing call to owTempSensors.begin() 39 | // for DallasTemperature v4.0.3 40 | // 20250720 Fixed missing function call for temperature conversion 41 | // 42 | // ToDo: 43 | // - 44 | // 45 | /////////////////////////////////////////////////////////////////////////////// 46 | 47 | #include "PayloadOneWire.h" 48 | 49 | #ifdef ONEWIRE_EN 50 | 51 | // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) 52 | static OneWire oneWire(PIN_ONEWIRE_BUS); //!< OneWire bus 53 | 54 | // Pass our oneWire reference to Dallas Temperature. 55 | static DallasTemperature owTempSensors(&oneWire); //!< Dallas temperature sensors connected to OneWire bus 56 | 57 | // Get temperature from Maxim OneWire Sensor 58 | float PayloadOneWire::getOneWireTemperature(uint8_t index) 59 | { 60 | // Call sensors.requestTemperatures() to issue a global temperature 61 | // request to all devices on the bus 62 | owTempSensors.requestTemperatures(); 63 | 64 | // Get temperature by index 65 | float tempC = owTempSensors.getTempCByIndex(index); 66 | 67 | // Check if reading was successful 68 | if (tempC != DEVICE_DISCONNECTED_C) 69 | { 70 | log_i("Temperature = %.2f°C", tempC); 71 | } 72 | else 73 | { 74 | log_i("Error: Could not read temperature data"); 75 | } 76 | 77 | return tempC; 78 | }; 79 | 80 | // Encode 1-Wire temperature sensor values for LoRaWAN transmission 81 | void PayloadOneWire::encodeOneWire(uint8_t *appPayloadCfg, LoraEncoder &encoder) 82 | { 83 | // Initialize the Dallas Temperature library 84 | owTempSensors.begin(); 85 | 86 | unsigned index = 0; 87 | for (int i = APP_PAYLOAD_BYTES_ONEWIRE - 1; i >= 0; i--) 88 | { 89 | for (uint8_t ch = 0; ch <= 7; ch++) 90 | { 91 | // Check if enough space is left in payload buffer 92 | if (encoder.getLength() > MAX_UPLINK_SIZE - 2) 93 | return; 94 | 95 | // Check if sensor with given index is enabled 96 | if ((appPayloadCfg[APP_PAYLOAD_OFFS_ONEWIRE + i] >> ch) & 0x1) 97 | { 98 | // Get temperature by index 99 | float tempC = getOneWireTemperature(index); 100 | 101 | // Check if reading was successful 102 | if (tempC != DEVICE_DISCONNECTED_C) 103 | { 104 | log_i("Temperature[%d] = %.2f°C", index, tempC); 105 | encoder.writeTemperature(tempC); 106 | } 107 | else 108 | { 109 | log_i("Error: Could not read temperature[%d] data", index); 110 | encoder.writeTemperature(INV_TEMP); 111 | } 112 | 113 | 114 | } 115 | index++; 116 | } 117 | } 118 | } 119 | #endif 120 | -------------------------------------------------------------------------------- /extras/BLETest/BleSensors.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // BleSensors.h 3 | // 4 | // Wrapper class for Theeengs Decoder (https://github.com/theengs/decoder) 5 | // 6 | // Intended for compatibility to the ATC_MiThermometer library 7 | // (https://github.com/matthias-bs/ATC_MiThermometer) 8 | // 9 | // created: 02/2023 10 | // 11 | // 12 | // MIT License 13 | // 14 | // Copyright (c) 2025 Matthias Prinke 15 | // 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // 23 | // The above copyright notice and this permission notice shall be included in all 24 | // copies or substantial portions of the Software. 25 | // 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | // SOFTWARE. 33 | // 34 | // 35 | // History: 36 | // 37 | // 20230211 Created 38 | // 20240417 Added additional constructor and method setAddresses() 39 | // 20240427 Added parameter activeScan to getData() 40 | // 41 | // ToDo: 42 | // - 43 | // 44 | /////////////////////////////////////////////////////////////////////////////// 45 | 46 | #if !defined(BLE_SENSORS) && !defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) \ 47 | && !defined(ARDUINO_ARCH_RP2040) 48 | #define BLE_SENSORS 49 | 50 | #include 51 | #include //!< https://github.com/h2zero/NimBLE-Arduino 52 | #include //!< https://github.com/theengs/decoder 53 | #include //!< https://arduinojson.org/ 54 | 55 | /*! 56 | * \brief BLE sensor data 57 | */ 58 | struct BleDataS { 59 | bool valid; //!< data valid 60 | float temperature; //!< temperature in degC 61 | float humidity; //!< humidity in % 62 | uint8_t batt_level; //!< battery level in % 63 | int rssi; //!< RSSI in dBm 64 | }; 65 | 66 | typedef struct BleDataS ble_sensors_t; //!< Shortcut for struct BleDataS 67 | 68 | 69 | /*! 70 | * \brief BLE Sensor (e.g. thermometer/hygrometer) client 71 | */ 72 | class BleSensors { 73 | public: 74 | /*! 75 | * \brief Constructor. 76 | * 77 | * \param known_sensors Vector of BLE MAC addresses of known sensors, e.g. {"11:22:33:44:55:66", "AA:BB:CC:DD:EE:FF"} 78 | */ 79 | BleSensors(std::vector known_sensors) { 80 | _known_sensors = known_sensors; 81 | data.resize(known_sensors.size()); 82 | }; 83 | 84 | /*! 85 | * \brief Constructor. 86 | */ 87 | BleSensors(void) { 88 | }; 89 | 90 | /*! 91 | * \brief Set BLE MAC addresses of known sensors 92 | * 93 | * \param known_sensors vector of BLE MAC addresses (see constructor) 94 | */ 95 | void setAddresses(std::vector known_sensors) { 96 | _known_sensors = known_sensors; 97 | data.resize(known_sensors.size()); 98 | }; 99 | 100 | /*! 101 | * \brief Initialization. 102 | */ 103 | void begin(void) { 104 | }; 105 | 106 | /*! 107 | * \brief Delete results from BLEScan buffer to release memory. 108 | */ 109 | void clearScanResults(void); 110 | 111 | /*! 112 | * \brief Get data from sensors by running a BLE scan. 113 | * 114 | * \param duration Scan duration in seconds 115 | * \param activeScan 0: passive scan / 1: active scan 116 | */ 117 | unsigned getData(uint32_t duration, bool activeScan = true); 118 | 119 | /*! 120 | * \brief Set sensor data invalid. 121 | */ 122 | void resetData(void); 123 | 124 | /*! 125 | * \brief Sensor data. 126 | */ 127 | std::vector data; 128 | 129 | protected: 130 | std::vector _known_sensors; /// MAC addresses of known sensors 131 | NimBLEScan* _pBLEScan; /// NimBLEScan object 132 | }; 133 | #endif 134 | -------------------------------------------------------------------------------- /src/PayloadBLE.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // PayloadBLE.h 3 | // 4 | // Get BLE temperature/humidity sensor values and encode as LoRaWAN payload 5 | // 6 | // created: 05/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2024 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // 32 | // History: 33 | // 34 | // 20240531 Moved from AppLayer.h 35 | // 20240603 encodeBLE(): added appStatus parameter 36 | // 20250728 Fixed using ATC_MiThermometer library 37 | // 38 | // ToDo: 39 | // - 40 | // 41 | /////////////////////////////////////////////////////////////////////////////// 42 | 43 | /*! \file PayloadBLE.h 44 | * \brief LoRaWAN node application layer - BLE sensors 45 | */ 46 | 47 | #if !defined(_PAYLOAD_BLE) 48 | #define _PAYLOAD_BLE 49 | 50 | #include "../BresserWeatherSensorLWCfg.h" 51 | 52 | #if defined(MITHERMOMETER_EN) || defined(THEENGSDECODER_EN) 53 | 54 | #include 55 | 56 | #if defined(MITHERMOMETER_EN) 57 | // BLE Temperature/Humidity Sensor 58 | #include 59 | #endif 60 | #if defined(THEENGSDECODER_EN) 61 | #include "BleSensors/BleSensors.h" 62 | #endif 63 | 64 | #include 65 | #include "logging.h" 66 | 67 | 68 | /*! 69 | * \brief LoRaWAN node application layer - BLE sensors 70 | * 71 | * Encodes data from Bluetooth Low Energy sensors as LoRaWAN payload 72 | */ 73 | class PayloadBLE 74 | { 75 | private: 76 | /// Preferences (stored in flash memory) 77 | Preferences appPrefs; 78 | 79 | #ifdef THEENGSDECODER_EN 80 | /// Bluetooth Low Energy sensors 81 | BleSensors bleSensors; 82 | #endif 83 | 84 | /// Default BLE MAC addresses 85 | std::vector knownBLEAddressesDef; 86 | 87 | public: 88 | /// Actual BLE MAC addresses; either from Preferences or from defaults 89 | std::vector knownBLEAddresses; 90 | 91 | public: 92 | /*! 93 | * \brief Constructor 94 | */ 95 | PayloadBLE(){}; 96 | 97 | /*! 98 | * \brief BLE startup code 99 | */ 100 | void begin(void) 101 | { 102 | bleAddrInit(); 103 | }; 104 | 105 | /*! 106 | * Set BLE addresses in Preferences and bleSensors object 107 | * 108 | * \param bytes MAC addresses (6 bytes per address) 109 | * \param size size in bytes 110 | */ 111 | void setBleAddr(uint8_t *bytes, uint8_t size); 112 | 113 | /*! 114 | * Get BLE addresses from Preferences 115 | * 116 | * \param bytes buffer for addresses 117 | * 118 | * \returns number of bytes copied into buffer 119 | */ 120 | uint8_t getBleAddr(uint8_t *bytes); 121 | 122 | /*! 123 | * Get BLE addresses from Preferences 124 | * 125 | * \returns BLE addresses 126 | */ 127 | std::vector getBleAddr(void); 128 | 129 | /*! 130 | * \brief Initialize list of known BLE addresses from defaults or Preferences 131 | * 132 | * If available, addresses from Preferences are used, otherwise defaults from 133 | * BresserWeatherSensorLWCfg.h. 134 | * 135 | * BleSensors() requires Preferences, which uses the Flash FS, 136 | * which is not available before the sketches' begin() is called - 137 | * thus the following cannot be handled by the constructor! 138 | */ 139 | void bleAddrInit(void); 140 | 141 | /*! 142 | * \brief Encode BLE temperature/humidity sensor values for LoRaWAN transmission 143 | * 144 | * \param appPayloadCfg LoRaWAN payload configuration bitmaps 145 | * \param appStatus Application layer status (i.e. sensor battery status bits) 146 | * \param encoder LoRaWAN payload encoder object 147 | */ 148 | void encodeBLE(uint8_t *appPayloadCfg, uint8_t * appStatus, LoraEncoder &encoder); 149 | }; 150 | #endif // defined(MITHERMOMETER_EN) || defined(THEENGSDECODER_EN) 151 | #endif //_PAYLOAD_BLE 152 | -------------------------------------------------------------------------------- /src/PayloadDigital.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // PayloadDigital.cpp 3 | // 4 | // Read digital input channels and encode as LoRaWAN payload 5 | // 6 | // created: 05/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2024 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // 32 | // History: 33 | // 34 | // 20240520 Created 35 | // 20240524 Added payload size check, changed bitmap order 36 | // 20250318 Renamed PAYLOAD_SIZE to MAX_UPLINK_SIZE 37 | // 38 | // ToDo: 39 | // - 40 | // 41 | /////////////////////////////////////////////////////////////////////////////// 42 | 43 | #include "PayloadDigital.h" 44 | 45 | #ifdef DISTANCESENSOR_EN 46 | #if defined(ESP32) 47 | /// Ultrasonic distance sensor 48 | static DistanceSensor_A02YYUW distanceSensor(&Serial2); 49 | #else 50 | /// Ultrasonic distance sensor 51 | static DistanceSensor_A02YYUW distanceSensor(&Serial1); 52 | #endif 53 | #endif 54 | 55 | void PayloadDigital::begin(void) 56 | { 57 | #ifdef DISTANCESENSOR_EN 58 | initDistanceSensor(); 59 | #endif 60 | } 61 | 62 | void PayloadDigital::encodeDigital(uint8_t *appPayloadCfg, LoraEncoder &encoder) 63 | { 64 | unsigned ch = (APP_PAYLOAD_BYTES_DIGITAL * 8) - 1; 65 | for (int i = APP_PAYLOAD_BYTES_DIGITAL - 1; i >= 0; i--) 66 | { 67 | for (uint8_t bit = 0; bit <= 7; bit++) 68 | { 69 | if ((appPayloadCfg[APP_PAYLOAD_OFFS_DIGITAL + i] >> bit) & 0x1) 70 | { 71 | #ifdef DISTANCESENSOR_EN 72 | // Check if channel is enabled 73 | if ((ch == DISTANCESENSOR_CH) && (encoder.getLength() <= MAX_UPLINK_SIZE - 2)) 74 | { 75 | uint16_t distance_mm = readDistanceSensor(); 76 | if (distance_mm > 0) 77 | { 78 | log_i("ch %02u: Distance: %4d mm", ch, distance_mm); 79 | } 80 | else 81 | { 82 | log_i("ch %02u: Distance: ---- mm", ch); 83 | } 84 | encoder.writeUint16(distance_mm); 85 | } 86 | #endif 87 | } 88 | ch--; 89 | } 90 | } 91 | } 92 | 93 | #ifdef DISTANCESENSOR_EN 94 | void PayloadDigital::initDistanceSensor(void) 95 | { 96 | #if defined(ESP32) 97 | Serial2.begin(9600, SERIAL_8N1, DISTANCESENSOR_RX, DISTANCESENSOR_TX); 98 | pinMode(DISTANCESENSOR_PWR, OUTPUT); 99 | digitalWrite(DISTANCESENSOR_PWR, LOW); 100 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) 101 | Serial1.setRX(DISTANCESENSOR_RX); 102 | Serial1.setTX(DISTANCESENSOR_TX); 103 | Serial1.begin(9600, SERIAL_8N1); 104 | pinMode(DISTANCESENSOR_PWR, OUTPUT_12MA); 105 | digitalWrite(DISTANCESENSOR_PWR, LOW); 106 | #endif 107 | } 108 | 109 | uint16_t PayloadDigital::readDistanceSensor(void) 110 | { 111 | // Sensor power on 112 | digitalWrite(DISTANCESENSOR_PWR, HIGH); 113 | delay(500); 114 | 115 | int retries = 0; 116 | DistanceSensor_A02YYUW_MEASSUREMENT_STATUS dstStatus; 117 | do 118 | { 119 | dstStatus = distanceSensor.meassure(); 120 | 121 | if (dstStatus != DistanceSensor_A02YYUW_MEASSUREMENT_STATUS_OK) 122 | { 123 | log_e("Distance Sensor Error: %d", dstStatus); 124 | } 125 | } while ( 126 | (dstStatus != DistanceSensor_A02YYUW_MEASSUREMENT_STATUS_OK) && 127 | (++retries < DISTANCESENSOR_RETRIES)); 128 | 129 | uint16_t distance_mm; 130 | if (dstStatus == DistanceSensor_A02YYUW_MEASSUREMENT_STATUS_OK) 131 | { 132 | distance_mm = distanceSensor.getDistance(); 133 | } 134 | else 135 | { 136 | distance_mm = 0; 137 | } 138 | 139 | // Sensor power off 140 | digitalWrite(DISTANCESENSOR_PWR, LOW); 141 | 142 | return distance_mm; 143 | } 144 | #endif 145 | -------------------------------------------------------------------------------- /src/rp2040/pico_rtc_utils.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // pico_rtc_utils.cpp 3 | // 4 | // RTC utility functions for RP2040 5 | // 6 | // Sleep/wakeup scheme based on 7 | // https://github.com/lyusupov/SoftRF/tree/master/software/firmware/source/libraries/RP2040_Sleep 8 | // by Linar Yusupov 9 | // 10 | // Using code from pico-extras: 11 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/pico_sleep/include/pico/sleep.h 12 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/pico_sleep/sleep.c 13 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/hardware_rosc/include/hardware/rosc.h 14 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/hardware_rosc/rosc.c 15 | // 16 | // created: 10/2023 17 | // 18 | // 19 | // MIT License 20 | // 21 | // Copyright (c) 2023 Matthias Prinke 22 | // 23 | // Permission is hereby granted, free of charge, to any person obtaining a copy 24 | // of this software and associated documentation files (the "Software"), to deal 25 | // in the Software without restriction, including without limitation the rights 26 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 27 | // copies of the Software, and to permit persons to whom the Software is 28 | // furnished to do so, subject to the following conditions: 29 | // 30 | // The above copyright notice and this permission notice shall be included in all 31 | // copies or substantial portions of the Software. 32 | // 33 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 35 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 36 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 37 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 39 | // SOFTWARE. 40 | // 41 | // 42 | // History: 43 | // 44 | // 20231006 Created 45 | // 20240905 Removed clocks_init() - not available/not required 46 | // in pico-sdk v2.0.0 47 | // 48 | // ToDo: 49 | // - 50 | // 51 | /////////////////////////////////////////////////////////////////////////////// 52 | #if defined(ARDUINO_ARCH_RP2040) 53 | 54 | #include "pico_rtc_utils.h" 55 | 56 | struct tm *datetime_to_tm(datetime_t *dt, struct tm *ti) 57 | { 58 | ti->tm_sec = dt->sec; 59 | ti->tm_min = dt->min; 60 | ti->tm_hour = dt->hour; 61 | ti->tm_mday = dt->day; 62 | ti->tm_mon = dt->month - 1; 63 | ti->tm_year = dt->year - 1900; 64 | ti->tm_wday = dt->dotw; 65 | 66 | return ti; 67 | } 68 | 69 | datetime_t *tm_to_datetime(struct tm *ti, datetime_t *dt) 70 | { 71 | dt->sec = ti->tm_sec; 72 | dt->min = ti->tm_min; 73 | dt->hour = ti->tm_hour; 74 | dt->day = ti->tm_mday; 75 | dt->month = ti->tm_mon + 1; 76 | dt->year = ti->tm_year + 1900; 77 | dt->dotw = ti->tm_wday; 78 | 79 | return dt; 80 | } 81 | 82 | void print_dt(datetime_t dt) { 83 | log_i("%4d-%02d-%02d %02d:%02d:%02d", dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec); 84 | } 85 | 86 | void print_tm(struct tm ti) { 87 | log_i("%4d-%02d-%02d %02d:%02d:%02d", ti.tm_year+1900, ti.tm_mon+1, ti.tm_mday, ti.tm_hour, ti.tm_min, ti.tm_sec); 88 | } 89 | 90 | time_t datetime_to_epoch(datetime_t *dt, time_t *epoch) { 91 | struct tm ti; 92 | datetime_to_tm(dt, &ti); 93 | 94 | // Apply daylight saving time according to timezone and date 95 | ti.tm_isdst = -1; 96 | 97 | // Convert to epoch 98 | time_t _epoch = mktime(&ti); 99 | 100 | if (epoch) { 101 | *epoch = _epoch; 102 | } 103 | 104 | return _epoch; 105 | } 106 | 107 | datetime_t *epoch_to_datetime(time_t *epoch, datetime_t *dt) { 108 | struct tm ti; 109 | 110 | // Apply daylight saving time according to timezone and date 111 | ti.tm_isdst = -1; 112 | 113 | // Convert epoch to struct tm 114 | localtime_r(epoch, &ti); 115 | 116 | // Convert struct tm to datetime_t 117 | tm_to_datetime(&ti, dt); 118 | 119 | return dt; 120 | } 121 | 122 | // Sleep for seconds 123 | void pico_sleep(unsigned duration) { 124 | datetime_t dt; 125 | rtc_get_datetime(&dt); 126 | log_i("RTC time:"); 127 | print_dt(dt); 128 | 129 | time_t now; 130 | datetime_to_epoch(&dt, &now); 131 | 132 | // Add sleep_duration 133 | time_t wakeup = now + duration; 134 | 135 | epoch_to_datetime(&wakeup, &dt); 136 | 137 | log_i("Wakeup time:"); 138 | print_dt(dt); 139 | 140 | Serial.flush(); 141 | Serial1.end(); 142 | Serial2.end(); 143 | Serial.end(); 144 | 145 | // From 146 | // https://github.com/lyusupov/SoftRF/tree/master/software/firmware/source/libraries/RP2040_Sleep 147 | // also see src/pico_rtc 148 | // --8<----- 149 | #if defined(USE_TINYUSB) 150 | // Disable USB 151 | USBDevice.detach(); 152 | #endif /* USE_TINYUSB */ 153 | 154 | sleep_run_from_xosc(); 155 | 156 | sleep_goto_sleep_until(&dt, NULL); 157 | 158 | // back from dormant state 159 | rosc_enable(); 160 | // --8<----- 161 | } 162 | #endif -------------------------------------------------------------------------------- /extras/time_rp2040_sleep/src/pico_rtc_utils.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // pico_rtc_utils.cpp 3 | // 4 | // RTC utility functions for RP2040 5 | // 6 | // Sleep/wakeup scheme based on 7 | // https://github.com/lyusupov/SoftRF/tree/master/software/firmware/source/libraries/RP2040_Sleep 8 | // by Linar Yusupov 9 | // 10 | // Using code from pico-extras: 11 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/pico_sleep/include/pico/sleep.h 12 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/pico_sleep/sleep.c 13 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/hardware_rosc/include/hardware/rosc.h 14 | // https://github.com/raspberrypi/pico-extras/blob/master/src/rp2_common/hardware_rosc/rosc.c 15 | // 16 | // created: 10/2023 17 | // 18 | // 19 | // MIT License 20 | // 21 | // Copyright (c) 2023 Matthias Prinke 22 | // 23 | // Permission is hereby granted, free of charge, to any person obtaining a copy 24 | // of this software and associated documentation files (the "Software"), to deal 25 | // in the Software without restriction, including without limitation the rights 26 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 27 | // copies of the Software, and to permit persons to whom the Software is 28 | // furnished to do so, subject to the following conditions: 29 | // 30 | // The above copyright notice and this permission notice shall be included in all 31 | // copies or substantial portions of the Software. 32 | // 33 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 35 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 36 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 37 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 39 | // SOFTWARE. 40 | // 41 | // 42 | // History: 43 | // 44 | // 20231006 Created 45 | // 20240905 Removed clocks_init() - not available/not required 46 | // in pico-sdk v2.0.0 47 | // 48 | // ToDo: 49 | // - 50 | // 51 | /////////////////////////////////////////////////////////////////////////////// 52 | #if defined(ARDUINO_ARCH_RP2040) 53 | 54 | #include "pico_rtc_utils.h" 55 | 56 | struct tm *datetime_to_tm(datetime_t *dt, struct tm *ti) 57 | { 58 | ti->tm_sec = dt->sec; 59 | ti->tm_min = dt->min; 60 | ti->tm_hour = dt->hour; 61 | ti->tm_mday = dt->day; 62 | ti->tm_mon = dt->month - 1; 63 | ti->tm_year = dt->year - 1900; 64 | ti->tm_wday = dt->dotw; 65 | 66 | return ti; 67 | } 68 | 69 | datetime_t *tm_to_datetime(struct tm *ti, datetime_t *dt) 70 | { 71 | dt->sec = ti->tm_sec; 72 | dt->min = ti->tm_min; 73 | dt->hour = ti->tm_hour; 74 | dt->day = ti->tm_mday; 75 | dt->month = ti->tm_mon + 1; 76 | dt->year = ti->tm_year + 1900; 77 | dt->dotw = ti->tm_wday; 78 | 79 | return dt; 80 | } 81 | 82 | void print_dt(datetime_t dt) { 83 | log_i("%4d-%02d-%02d %02d:%02d:%02d", dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec); 84 | } 85 | 86 | void print_tm(struct tm ti) { 87 | log_i("%4d-%02d-%02d %02d:%02d:%02d", ti.tm_year+1900, ti.tm_mon+1, ti.tm_mday, ti.tm_hour, ti.tm_min, ti.tm_sec); 88 | } 89 | 90 | time_t datetime_to_epoch(datetime_t *dt, time_t *epoch) { 91 | struct tm ti; 92 | datetime_to_tm(dt, &ti); 93 | 94 | // Apply daylight saving time according to timezone and date 95 | ti.tm_isdst = -1; 96 | 97 | // Convert to epoch 98 | time_t _epoch = mktime(&ti); 99 | 100 | if (epoch) { 101 | *epoch = _epoch; 102 | } 103 | 104 | return _epoch; 105 | } 106 | 107 | datetime_t *epoch_to_datetime(time_t *epoch, datetime_t *dt) { 108 | struct tm ti; 109 | 110 | // Apply daylight saving time according to timezone and date 111 | ti.tm_isdst = -1; 112 | 113 | // Convert epoch to struct tm 114 | localtime_r(epoch, &ti); 115 | 116 | // Convert struct tm to datetime_t 117 | tm_to_datetime(&ti, dt); 118 | 119 | return dt; 120 | } 121 | 122 | // Sleep for seconds 123 | void pico_sleep(unsigned duration) { 124 | datetime_t dt; 125 | rtc_get_datetime(&dt); 126 | log_i("RTC time:"); 127 | print_dt(dt); 128 | 129 | time_t now; 130 | datetime_to_epoch(&dt, &now); 131 | 132 | // Add sleep_duration 133 | time_t wakeup = now + duration; 134 | 135 | epoch_to_datetime(&wakeup, &dt); 136 | 137 | log_i("Wakeup time:"); 138 | print_dt(dt); 139 | 140 | Serial.flush(); 141 | Serial1.end(); 142 | Serial2.end(); 143 | Serial.end(); 144 | 145 | // From 146 | // https://github.com/lyusupov/SoftRF/tree/master/software/firmware/source/libraries/RP2040_Sleep 147 | // also see src/pico_rtc 148 | // --8<----- 149 | #if defined(USE_TINYUSB) 150 | // Disable USB 151 | USBDevice.detach(); 152 | #endif /* USE_TINYUSB */ 153 | 154 | sleep_run_from_xosc(); 155 | 156 | sleep_goto_sleep_until(&dt, NULL); 157 | 158 | // back from dormant state 159 | rosc_enable(); 160 | // --8<----- 161 | } 162 | #endif -------------------------------------------------------------------------------- /extras/customization/AppLayerMinimal.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // AppLayer.h 3 | // 4 | // LoRaWAN node application layer - Minimal template for customization 5 | // 6 | // created: 05/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2025 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // 32 | // History: 33 | // 34 | // 20240513 Created 35 | // 20240607 Added getAppStatusUplinkInterval() for compatibility 36 | // 20240730 Updated getPayloadStage1()/2() 37 | // 38 | // ToDo: 39 | // - 40 | // 41 | /////////////////////////////////////////////////////////////////////////////// 42 | 43 | #if !defined(_APPLAYER_H) 44 | #define _APPLAYER_H 45 | 46 | #include 47 | #include // keep this to store data persistently 48 | #include "../BresserWeatherSensorLWCfg.h" 49 | #include "../BresserWeatherSensorLWCmd.h" // keep this for adding your commands 50 | #include "adc/adc.h" // keep this for using ADC functions 51 | #include // see https://github.com/thesolarnomad/lora-serialization 52 | 53 | /*! 54 | * \brief LoRaWAN node application layer 55 | * 56 | * Contains all device specific methods and attributes 57 | */ 58 | class AppLayer 59 | { 60 | private: 61 | ESP32Time *_rtc; 62 | time_t *_rtcLastClockSync; 63 | 64 | /// Preferences (stored in flash memory) 65 | Preferences appPrefs; 66 | 67 | public: 68 | /*! 69 | * \brief Constructor 70 | * 71 | * \param rtc Real time clock object 72 | * \param clocksync Timestamp of last clock synchronization 73 | */ 74 | AppLayer(ESP32Time *rtc, time_t *clocksync) 75 | { 76 | _rtc = rtc; 77 | _rtcLastClockSync = clocksync; 78 | }; 79 | 80 | /*! 81 | * \brief AppLayer initialization 82 | * 83 | * Use this if needed 84 | */ 85 | void begin(void) 86 | { 87 | }; 88 | 89 | /*! 90 | * \brief Get sensor status message uplink interval 91 | * 92 | * \returns status uplink interval in frame counts (o: disabled) 93 | */ 94 | uint8_t getAppStatusUplinkInterval(void) 95 | { 96 | return 0; 97 | }; 98 | 99 | /*! 100 | * \brief Decode app layer specific downlink messages 101 | * 102 | * \param port downlink message port 103 | * \param payload downlink message payload 104 | * \param size payload size in bytes 105 | * 106 | * \returns config uplink request or 0 107 | */ 108 | uint8_t decodeDownlink(uint8_t port, uint8_t *payload, size_t size); 109 | 110 | /*! 111 | * \brief Generate payload (by emulation) 112 | * 113 | * \param port LoRaWAN port 114 | * \param encoder uplink encoder object 115 | */ 116 | void genPayload(uint8_t port, LoraEncoder &encoder); 117 | 118 | /*! 119 | * \brief Prepare / get payload at startup 120 | * 121 | * Use this if 122 | * - A sensor needs some time for warm-up or data acquisition 123 | * - The data acquisition has to be done directly after startup 124 | * - The radio transceiver is used for sensor communication 125 | * before starting LoRaWAN activities. 126 | * 127 | * \param port LoRaWAN port 128 | * \param encoder uplink encoder object 129 | */ 130 | void getPayloadStage1(uint8_t &port, LoraEncoder &encoder); 131 | 132 | /*! 133 | * \brief Get payload before uplink 134 | * 135 | * Use this if 136 | * - The radio transceiver is NOT used for sensor communication 137 | * - The sensor preparation has been started in stage1 138 | * - The data aquistion has to be done immediately before uplink 139 | * 140 | * \param port LoRaWAN port 141 | * \param encoder uplink encoder object 142 | */ 143 | void getPayloadStage2(uint8_t &port, LoraEncoder &encoder); 144 | 145 | /*! 146 | * Get configuration data for uplink 147 | * 148 | * Get the configuration data requested in a downlink command and 149 | * prepare it as payload in a uplink response. 150 | * 151 | * \param cmd command 152 | * \param port uplink port 153 | * \param encoder uplink data encoder object 154 | */ 155 | void getConfigPayload(uint8_t cmd, uint8_t &port, LoraEncoder &encoder); 156 | }; 157 | #endif // _APPLAYER_H 158 | -------------------------------------------------------------------------------- /src/adc/adc.cpp: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // adc.cpp 3 | // 4 | // Analog/Digital Converter wrapper/convenience functions 5 | // 6 | // created: 04/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2024 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // History: 32 | // 20240405 Created 33 | // 20240410 Added RP2040 specific implementation 34 | // 20240413 Refactored ADC handling 35 | // 20240414 Added ESP32-S3 PowerFeather 36 | // Added getSupplyVoltage() 37 | // 20240423 Added define ARDUINO_heltec_wifi_lora_32_V3 38 | // 20240427 Added ADC input control and battery voltage measurement 39 | // for ARDUINO_heltec_wifi_lora_32_V3 40 | // 20240430 Modified getBatteryVoltage() 41 | // 20240504 Heltec WiFi 32 LoRa V3: Changed ADC input attenuation to get higher accuracy 42 | // 20240607 Changed ARDUINO_HELTEC_WIFI_LORA_32_V3 to uppercase 43 | // 20241203 Fixed getVoltage(): use parameter 'pin' instead of PIN_ADC_IN 44 | // 20250317 Removed ARDUINO_heltec_wifi_lora_32_V3 and ARDUINO_M5STACK_Core2 (now all uppercase) 45 | // 46 | // ToDo: 47 | // - 48 | // 49 | /////////////////////////////////////////////////////////////////////////////////////////////////// 50 | 51 | #include "adc.h" 52 | #include "../logging.h" 53 | 54 | #if defined(ARDUINO_M5STACK_CORE2) 55 | #include 56 | #elif defined(ARDUINO_ESP32S3_POWERFEATHER) 57 | #include 58 | using namespace PowerFeather; 59 | #endif 60 | 61 | // 62 | // Get voltage 63 | // 64 | uint16_t 65 | getVoltage(uint8_t pin, uint8_t samples, float div) 66 | { 67 | float voltage_raw = 0; 68 | for (uint8_t i = 0; i < UBATT_SAMPLES; i++) 69 | { 70 | #if defined(ESP32) 71 | voltage_raw += float(analogReadMilliVolts(pin)); 72 | #else 73 | voltage_raw += float(analogRead(pin)) / 4095.0 * 3300; 74 | #endif 75 | } 76 | uint16_t voltage = int(voltage_raw / UBATT_SAMPLES / UBATT_DIV); 77 | 78 | log_d("Voltage @GPIO%02d = %dmV", pin, voltage); 79 | 80 | return voltage; 81 | } 82 | 83 | uint16_t getBatteryVoltage(void) 84 | { 85 | #if defined(ARDUINO_TTGO_LoRa32_V1) || defined(ARDUINO_TTGO_LoRa32_V2) || defined(ARDUINO_TTGO_LoRa32_v21new) || \ 86 | defined(ARDUINO_FEATHER_ESP32) || defined(LORAWAN_NODE) || defined(FIREBEETLE_ESP32_COVER_LORA) || \ 87 | defined(ARDUINO_THINGPULSE_EPULSE_FEATHER) 88 | // Here come the good guys... 89 | return getVoltage(); 90 | 91 | #elif defined(ARDUINO_HELTEC_WIFI_LORA_32_V3) 92 | // Enable ADC input switch, measure voltage and disable ADC input switch 93 | uint16_t voltage; 94 | pinMode(ADC_CTRL, OUTPUT); 95 | digitalWrite(ADC_CTRL, LOW); 96 | analogSetPinAttenuation(PIN_ADC_IN, ADC_0db); 97 | delay(100); 98 | voltage = getVoltage(); 99 | pinMode(ADC_CTRL, INPUT); 100 | return voltage; 101 | 102 | #elif defined(ARDUINO_ARCH_RP2040) 103 | // Not implemented - no default VBAT input circuit (connect external divider to A0) 104 | return 0; 105 | 106 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) 107 | // Not implemented - no default VBAT input circuit (connect external divider to A0) 108 | return 0; 109 | 110 | #elif defined(ARDUINO_M5STACK_CORE2) 111 | // battery monitoring chip 112 | uint16_t voltage = M5.Power.getBatteryVoltage(); 113 | log_d("Voltage = %dmV", voltage); 114 | return voltage; 115 | 116 | #elif defined(ARDUINO_ESP32S3_POWERFEATHER) 117 | // battery monitoring chip 118 | uint16_t voltage; 119 | Result res = Board.getBatteryVoltage(voltage); 120 | if (res == Result::Ok) 121 | { 122 | log_d("Voltage = %dmV", voltage); 123 | return voltage; 124 | } 125 | else 126 | { 127 | return 0; 128 | } 129 | 130 | #else 131 | // Unknown implementation - zero indicates battery voltage measurement not available 132 | return 0; 133 | 134 | #endif 135 | } 136 | 137 | uint16_t getSupplyVoltage(void) 138 | { 139 | #if defined(ARDUINO_ESP32S3_POWERFEATHER) 140 | uint16_t voltage; 141 | Result res = Board.getSupplyVoltage(voltage); 142 | if (res == Result::Ok) 143 | { 144 | log_d("Voltage = %dmV", voltage); 145 | return voltage; 146 | } 147 | else 148 | { 149 | return 0; 150 | } 151 | #elif defined(PIN_SUPPLY_IN) 152 | return getVoltage(PIN_SUPPLY_IN, SUPPLY_SAMPLES, SUPPLY_DIV); 153 | #else 154 | return 0; 155 | #endif 156 | } 157 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | opensource@github.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /src/rp2040/sleep.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | #if defined(ARDUINO_ARCH_RP2040) 7 | 8 | #include "pico.h" 9 | 10 | #include "pico/stdlib.h" 11 | #include "pico_sleep.h" 12 | 13 | #include "hardware/rtc.h" 14 | #include "hardware/pll.h" 15 | #include "hardware/clocks.h" 16 | #include "hardware/xosc.h" 17 | #include "pico_rosc.h" 18 | #include "hardware/regs/io_bank0.h" 19 | // For __wfi 20 | #include "hardware/sync.h" 21 | // For scb_hw so we can enable deep sleep 22 | #include "hardware/structs/scb.h" 23 | 24 | // The difference between sleep and dormant is that ALL clocks are stopped in dormant mode, 25 | // until the source (either xosc or rosc) is started again by an external event. 26 | // In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks 27 | // block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) 28 | // can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again. 29 | 30 | 31 | // TODO: Optionally, memories can also be powered down. 32 | 33 | static dormant_source_t _dormant_source; 34 | 35 | bool dormant_source_valid(dormant_source_t dormant_source) { 36 | return (dormant_source == DORMANT_SOURCE_XOSC) || (dormant_source == DORMANT_SOURCE_ROSC); 37 | } 38 | 39 | // In order to go into dormant mode we need to be running from a stoppable clock source: 40 | // either the xosc or rosc with no PLLs running. This means we disable the USB and ADC clocks 41 | // and all PLLs 42 | void sleep_run_from_dormant_source(dormant_source_t dormant_source) { 43 | assert(dormant_source_valid(dormant_source)); 44 | _dormant_source = dormant_source; 45 | 46 | // FIXME: Just defining average rosc freq here. 47 | uint src_hz = (dormant_source == DORMANT_SOURCE_XOSC) ? XOSC_MHZ * MHZ : 6.5 * MHZ; 48 | uint clk_ref_src = (dormant_source == DORMANT_SOURCE_XOSC) ? 49 | CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC : 50 | CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH; 51 | 52 | // CLK_REF = XOSC or ROSC 53 | clock_configure(clk_ref, 54 | clk_ref_src, 55 | 0, // No aux mux 56 | src_hz, 57 | src_hz); 58 | 59 | // CLK SYS = CLK_REF 60 | clock_configure(clk_sys, 61 | CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, 62 | 0, // Using glitchless mux 63 | src_hz, 64 | src_hz); 65 | 66 | // CLK USB = 0MHz 67 | clock_stop(clk_usb); 68 | 69 | // CLK ADC = 0MHz 70 | clock_stop(clk_adc); 71 | 72 | // CLK RTC = ideally XOSC (12MHz) / 256 = 46875Hz but could be rosc 73 | uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC) ? 74 | CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC : 75 | CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH; 76 | 77 | clock_configure(clk_rtc, 78 | 0, // No GLMUX 79 | clk_rtc_src, 80 | src_hz, 81 | 46875); 82 | 83 | // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable 84 | clock_configure(clk_peri, 85 | 0, 86 | CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 87 | src_hz, 88 | src_hz); 89 | 90 | pll_deinit(pll_sys); 91 | pll_deinit(pll_usb); 92 | 93 | // Assuming both xosc and rosc are running at the moment 94 | if (dormant_source == DORMANT_SOURCE_XOSC) { 95 | // Can disable rosc 96 | rosc_disable(); 97 | } else { 98 | // Can disable xosc 99 | xosc_disable(); 100 | } 101 | #if 0 102 | // Reconfigure uart with new clocks 103 | setup_default_uart(); 104 | #endif 105 | } 106 | 107 | // Go to sleep until woken up by the RTC 108 | void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback) { 109 | // We should have already called the sleep_run_from_dormant_source function 110 | assert(dormant_source_valid(_dormant_source)); 111 | 112 | // Turn off all clocks when in sleep mode except for RTC 113 | clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS; 114 | clocks_hw->sleep_en1 = 0x0; 115 | 116 | rtc_set_alarm(t, callback); 117 | 118 | uint save = scb_hw->scr; 119 | // Enable deep sleep at the proc 120 | scb_hw->scr = save | M0PLUS_SCR_SLEEPDEEP_BITS; 121 | 122 | // Go to sleep 123 | __wfi(); 124 | } 125 | 126 | static void _go_dormant(void) { 127 | assert(dormant_source_valid(_dormant_source)); 128 | 129 | if (_dormant_source == DORMANT_SOURCE_XOSC) { 130 | xosc_dormant(); 131 | } else { 132 | rosc_set_dormant(); 133 | } 134 | } 135 | 136 | void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) { 137 | bool low = !high; 138 | bool level = !edge; 139 | 140 | // Configure the appropriate IRQ at IO bank 0 141 | assert(gpio_pin < NUM_BANK0_GPIOS); 142 | 143 | uint32_t event = 0; 144 | 145 | if (level && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS; 146 | if (level && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS; 147 | if (edge && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS; 148 | if (edge && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS; 149 | 150 | gpio_set_dormant_irq_enabled(gpio_pin, event, true); 151 | 152 | _go_dormant(); 153 | // Execution stops here until woken up 154 | 155 | // Clear the irq so we can go back to dormant mode again if we want 156 | gpio_acknowledge_irq(gpio_pin, event); 157 | } 158 | #endif /* ARDUINO_ARCH_RP2040 */ 159 | -------------------------------------------------------------------------------- /extras/time_rp2040_sleep/src/sleep.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | #if defined(ARDUINO_ARCH_RP2040) 7 | 8 | #include "pico.h" 9 | 10 | #include "pico/stdlib.h" 11 | #include "pico_sleep.h" 12 | 13 | #include "hardware/rtc.h" 14 | #include "hardware/pll.h" 15 | #include "hardware/clocks.h" 16 | #include "hardware/xosc.h" 17 | #include "pico_rosc.h" 18 | #include "hardware/regs/io_bank0.h" 19 | // For __wfi 20 | #include "hardware/sync.h" 21 | // For scb_hw so we can enable deep sleep 22 | #include "hardware/structs/scb.h" 23 | 24 | // The difference between sleep and dormant is that ALL clocks are stopped in dormant mode, 25 | // until the source (either xosc or rosc) is started again by an external event. 26 | // In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks 27 | // block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) 28 | // can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again. 29 | 30 | 31 | // TODO: Optionally, memories can also be powered down. 32 | 33 | static dormant_source_t _dormant_source; 34 | 35 | bool dormant_source_valid(dormant_source_t dormant_source) { 36 | return (dormant_source == DORMANT_SOURCE_XOSC) || (dormant_source == DORMANT_SOURCE_ROSC); 37 | } 38 | 39 | // In order to go into dormant mode we need to be running from a stoppable clock source: 40 | // either the xosc or rosc with no PLLs running. This means we disable the USB and ADC clocks 41 | // and all PLLs 42 | void sleep_run_from_dormant_source(dormant_source_t dormant_source) { 43 | assert(dormant_source_valid(dormant_source)); 44 | _dormant_source = dormant_source; 45 | 46 | // FIXME: Just defining average rosc freq here. 47 | uint src_hz = (dormant_source == DORMANT_SOURCE_XOSC) ? XOSC_MHZ * MHZ : 6.5 * MHZ; 48 | uint clk_ref_src = (dormant_source == DORMANT_SOURCE_XOSC) ? 49 | CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC : 50 | CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH; 51 | 52 | // CLK_REF = XOSC or ROSC 53 | clock_configure(clk_ref, 54 | clk_ref_src, 55 | 0, // No aux mux 56 | src_hz, 57 | src_hz); 58 | 59 | // CLK SYS = CLK_REF 60 | clock_configure(clk_sys, 61 | CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, 62 | 0, // Using glitchless mux 63 | src_hz, 64 | src_hz); 65 | 66 | // CLK USB = 0MHz 67 | clock_stop(clk_usb); 68 | 69 | // CLK ADC = 0MHz 70 | clock_stop(clk_adc); 71 | 72 | // CLK RTC = ideally XOSC (12MHz) / 256 = 46875Hz but could be rosc 73 | uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC) ? 74 | CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC : 75 | CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH; 76 | 77 | clock_configure(clk_rtc, 78 | 0, // No GLMUX 79 | clk_rtc_src, 80 | src_hz, 81 | 46875); 82 | 83 | // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable 84 | clock_configure(clk_peri, 85 | 0, 86 | CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 87 | src_hz, 88 | src_hz); 89 | 90 | pll_deinit(pll_sys); 91 | pll_deinit(pll_usb); 92 | 93 | // Assuming both xosc and rosc are running at the moment 94 | if (dormant_source == DORMANT_SOURCE_XOSC) { 95 | // Can disable rosc 96 | rosc_disable(); 97 | } else { 98 | // Can disable xosc 99 | xosc_disable(); 100 | } 101 | #if 0 102 | // Reconfigure uart with new clocks 103 | setup_default_uart(); 104 | #endif 105 | } 106 | 107 | // Go to sleep until woken up by the RTC 108 | void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback) { 109 | // We should have already called the sleep_run_from_dormant_source function 110 | assert(dormant_source_valid(_dormant_source)); 111 | 112 | // Turn off all clocks when in sleep mode except for RTC 113 | clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS; 114 | clocks_hw->sleep_en1 = 0x0; 115 | 116 | rtc_set_alarm(t, callback); 117 | 118 | uint save = scb_hw->scr; 119 | // Enable deep sleep at the proc 120 | scb_hw->scr = save | M0PLUS_SCR_SLEEPDEEP_BITS; 121 | 122 | // Go to sleep 123 | __wfi(); 124 | } 125 | 126 | static void _go_dormant(void) { 127 | assert(dormant_source_valid(_dormant_source)); 128 | 129 | if (_dormant_source == DORMANT_SOURCE_XOSC) { 130 | xosc_dormant(); 131 | } else { 132 | rosc_set_dormant(); 133 | } 134 | } 135 | 136 | void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) { 137 | bool low = !high; 138 | bool level = !edge; 139 | 140 | // Configure the appropriate IRQ at IO bank 0 141 | assert(gpio_pin < NUM_BANK0_GPIOS); 142 | 143 | uint32_t event = 0; 144 | 145 | if (level && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS; 146 | if (level && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS; 147 | if (edge && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS; 148 | if (edge && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS; 149 | 150 | gpio_set_dormant_irq_enabled(gpio_pin, event, true); 151 | 152 | _go_dormant(); 153 | // Execution stops here until woken up 154 | 155 | // Clear the irq so we can go back to dormant mode again if we want 156 | gpio_acknowledge_irq(gpio_pin, event); 157 | } 158 | #endif /* ARDUINO_ARCH_RP2040 */ 159 | -------------------------------------------------------------------------------- /extras/time_set_string/time_set_string.ino: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // time_set_string.ino 3 | // 4 | // Test setting time from UTC time and date string, e.g. GPS NMEA messages 5 | // 6 | // See 7 | // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system_time.html 8 | // https://www.gnu.org/software/libc/manual/html_node/Calendar-Time.html 9 | // 10 | // created: 08/2025 11 | // 12 | // 13 | // MIT License 14 | // 15 | // Copyright (c) 2025 Matthias Prinke 16 | // 17 | // Permission is hereby granted, free of charge, to any person obtaining a copy 18 | // of this software and associated documentation files (the "Software"), to deal 19 | // in the Software without restriction, including without limitation the rights 20 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | // copies of the Software, and to permit persons to whom the Software is 22 | // furnished to do so, subject to the following conditions: 23 | // 24 | // The above copyright notice and this permission notice shall be included in all 25 | // copies or substantial portions of the Software. 26 | // 27 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | // SOFTWARE. 34 | // 35 | // History: 36 | // 37 | // 20250808 Created 38 | // 39 | /////////////////////////////////////////////////////////////////////////////// 40 | 41 | #include 42 | #include 43 | #include 44 | 45 | #define TZINFO "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00" 46 | 47 | /*! 48 | * \brief Set RTC from epoch to local time 49 | * 50 | * \param epochTime epoch 51 | */ 52 | void setLocalTimeFromEpoch(time_t epochTime) 53 | { 54 | timeval epoch = {epochTime, 0}; 55 | const timeval *tv = &epoch; 56 | timezone utc = {0, 0}; 57 | const timezone *tz = &utc; 58 | settimeofday(tv, 0); 59 | } 60 | 61 | /*! 62 | * \brief Get epoch from a time and date string 63 | * 64 | * To avoid timezone conversion by mktime, the timezone is 65 | * temporarily set to UTC. 66 | * 67 | * \param timeString time and date in the format YYYY-MM-DD hh:mm:ss 68 | * 69 | * \returns epoch 70 | */ 71 | time_t getTimeFromString(String timeString) 72 | { 73 | // Parse the input string 74 | int year, month, day, hour, minute, second; 75 | if (sscanf(timeString.c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second) == 6) 76 | { 77 | struct tm timeinfo = {0}; 78 | timeinfo.tm_year = year - 1900; 79 | timeinfo.tm_mon = month - 1; 80 | timeinfo.tm_mday = day; 81 | timeinfo.tm_hour = hour; 82 | timeinfo.tm_min = minute; 83 | timeinfo.tm_sec = second; 84 | 85 | Serial.println("Switching to timezone: UTC"); 86 | setenv("TZ", "UTC+0UTC+0", 1); 87 | tzset(); 88 | 89 | Serial.printf("daylight : %d\n", _daylight); 90 | Serial.printf("tzname[0] : %s\n", tzname[0]); 91 | Serial.printf("tzname[1] : %s\n", tzname[1]); 92 | 93 | time_t t = mktime(&timeinfo); 94 | 95 | Serial.println("Switching to timezone: "); 96 | setenv("TZ", TZINFO, 1); 97 | tzset(); 98 | 99 | return t; 100 | } 101 | assert(false); 102 | } 103 | 104 | /*! 105 | * \brief Print UTC time and date 106 | */ 107 | void printUtc(time_t utc) 108 | { 109 | struct tm *tm_info = gmtime(&utc); 110 | 111 | char buffer[20]; 112 | strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", tm_info); 113 | Serial.printf("UTC : %s\n", buffer); 114 | } 115 | 116 | /*! 117 | * \brief Print local time and date 118 | */ 119 | void printTime(void) 120 | { 121 | time_t now_t = time(nullptr); 122 | if (now_t == -1) 123 | { 124 | Serial.println("Failed to obtain time"); 125 | } 126 | 127 | struct tm *tm_info = localtime(&now_t); 128 | 129 | char buffer[20]; 130 | strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", tm_info); 131 | Serial.printf("Local time: %s\n", buffer); 132 | } 133 | 134 | void setup() 135 | { 136 | Serial.begin(115200); 137 | 138 | #ifndef ESP8266 139 | while (!Serial) 140 | ; // wait for serial port to connect. Needed for native USB 141 | #endif 142 | setenv("TZ", TZINFO, 1); 143 | tzset(); 144 | 145 | Serial.println("Set time from GPS time and date (UTC)"); 146 | 147 | Serial.println("Timezone info:"); 148 | // Serial.printf("timezone: %ld\n", timezone); 149 | Serial.printf("daylight : %d\n", _daylight); 150 | Serial.printf("tzname[0] : %s\n", tzname[0]); 151 | Serial.printf("tzname[1] : %s\n\n", tzname[1]); 152 | 153 | time_t epochTime; 154 | 155 | String timeStr1 = "2025-01-01 00:00:00"; 156 | Serial.println("Set UTC : " + timeStr1); 157 | 158 | epochTime = getTimeFromString(timeStr1); 159 | Serial.printf("Epoch time: %lld\n", (long long)epochTime); 160 | printUtc(epochTime); 161 | setLocalTimeFromEpoch(epochTime); 162 | printTime(); 163 | Serial.println(); 164 | 165 | String timeStr2 = "2025-06-01 00:00:00"; 166 | Serial.println("Set UTC : " + timeStr2); 167 | epochTime = getTimeFromString(timeStr2); 168 | Serial.printf("Epoch time: %lld\n", (long long)epochTime); 169 | printUtc(epochTime); 170 | setLocalTimeFromEpoch(epochTime); 171 | printTime(); 172 | Serial.println(); 173 | } 174 | 175 | void loop() 176 | { 177 | delay(100); 178 | } 179 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | 12 | build: 13 | strategy: 14 | matrix: 15 | board: 16 | - esp32:esp32:esp32:DebugLevel=none 17 | - esp32:esp32:esp32:DebugLevel=verbose 18 | - esp32:esp32:firebeetle32 19 | #- esp32:esp32:ttgo-lora32:Revision=TTGO_LoRa32_V1 20 | #- esp32:esp32:ttgo-lora32:Revision=TTGO_LoRa32_V2 21 | - esp32:esp32:ttgo-lora32:Revision=TTGO_LoRa32_v21new 22 | - esp32:esp32:lilygo_t3s3:Revision=Radio_LR1121 23 | #- esp32:esp32:heltec_wireless_stick:PSRAM=disabled 24 | #- esp32:esp32:heltec_wifi_lora_32_V2 25 | - esp32:esp32:heltec_wifi_lora_32_V3 26 | #- esp32:esp32:featheresp32 27 | #- esp32:esp32:m5stack_core2 # Needs arduino-esp32 v3.0.7 28 | - esp32:esp32:esp32s3_powerfeather 29 | - esp32:esp32:adafruit_feather_esp32s2 30 | - rp2040:rp2040:adafruit_feather:dbgport=Serial 31 | 32 | runs-on: ubuntu-latest 33 | name: ${{ matrix.board }} 34 | env: 35 | GH_TOKEN: ${{ github.token }} 36 | run-build: ${{ contains(matrix.board, 'esp32:esp32') || contains(matrix.board, 'rp2040:rp2040') || contains(github.event.head_commit.message, 'CI_BUILD_ALL') || contains(github.event.head_commit.message, 'Bump version to') || contains(github.event.head_commit.message, format('{0}', matrix.board)) }} 37 | 38 | steps: 39 | - name: Install arduino-cli 40 | if: ${{ env.run-build == 'true' }} 41 | run: 42 | | 43 | mkdir -p ~/.local/bin 44 | echo "~/.local/bin" >> $GITHUB_PATH 45 | curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/.local/bin sh 46 | 47 | - name: Get platform name 48 | if: ${{ env.run-build == 'true' }} 49 | uses: jungwinter/split@v2 50 | id: split 51 | with: 52 | msg: ${{ matrix.board }} 53 | separator: ':' 54 | 55 | - name: Prepare platform-specific settings 56 | if: ${{ env.run-build == 'true' }} 57 | id: prep 58 | run: 59 | | 60 | # common settings - no extra options, skip nothing, all warnings 61 | echo "skip-pattern='extras/'" >> $GITHUB_OUTPUT 62 | echo "warnings='all'" >> $GITHUB_OUTPUT 63 | 64 | # platform-dependent settings - extra board options, board index URLs, skip patterns etc. 65 | if [[ "${{ contains(matrix.board, 'esp32:esp32') }}" == "true" ]]; then 66 | # ESP32 67 | python -m pip install pyserial 68 | echo "index-url=--additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json" >> $GITHUB_OUTPUT 69 | elif [[ "${{ contains(matrix.board, 'rp2040:rp2040') }}" == "true" ]]; then 70 | # RP2040 71 | echo "index-url=--additional-urls https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json" >> $GITHUB_OUTPUT 72 | fi 73 | 74 | - name: Install libraries 75 | if: ${{ env.run-build == 'true' }} 76 | run: 77 | | 78 | #declare -a required_libs=("https://github.com/matthias-bs/BresserWeatherSensorReceiver.git" 79 | declare -a required_libs=( 80 | "BresserWeatherSensorReceiver@0.36.2" 81 | "RadioLib@7.4.0" 82 | "LoRa Serialization@3.3.1" 83 | "OneWireNg@0.14.1" 84 | "DallasTemperature@4.0.5" 85 | "NimBLE-Arduino@2.3.7" 86 | "ATC_MiThermometer@0.5.0" 87 | "TheengsDecoder@2.0.0" 88 | "Preferences@2.2.2" 89 | "ArduinoJson@7.4.2" 90 | #"M5Unified@0.2.11" 91 | "powerfeather-sdk@1.1.1") 92 | for i in "${required_libs[@]}" 93 | do 94 | arduino-cli lib install "$i" 95 | done 96 | gh repo clone matthias-bs/DistanceSensor_A02YYUW 97 | cp -r DistanceSensor_A02YYUW /home/runner/Arduino/libraries/ 98 | 99 | - name: Install platform 100 | if: ${{ env.run-build == 'true' }} 101 | run: 102 | | 103 | arduino-cli core update-index ${{ format('{0}', steps.prep.outputs.index-url) }} 104 | arduino-cli core install ${{ format('{0}:{1} {2}', steps.split.outputs._0, steps.split.outputs._1, steps.prep.outputs.index-url) }} 105 | 106 | - name: Checkout repository 107 | if: ${{ env.run-build == 'true' }} 108 | uses: actions/checkout@v5 109 | 110 | - name: Build sketch 111 | if: ${{ env.run-build == 'true' }} 112 | run: 113 | | 114 | #for example in $(find $PWD/examples -name '*.ino' | sort); do 115 | # modified to compile a singe sketch (instead of a library's examples) 116 | for example in $(find $PWD -name '*.ino' | sort); do 117 | # check whether to skip this sketch 118 | if [ ! -z '${{ steps.prep.outputs.skip-pattern }}' ] && [[ ${example} =~ ${{ steps.prep.outputs.skip-pattern }} ]]; then 119 | # skip sketch 120 | echo -e "\n\033[1;33mSkipped ${example##*/} (matched with ${{ steps.prep.outputs.skip-pattern }})\033[0m"; 121 | else 122 | # build sketch 123 | echo -e "\n\033[1;33mBuilding ${example##*/} ... \033[0m"; 124 | arduino-cli compile --libraries /home/runner/work/BresserWeatherSensorReceiver --fqbn ${{ matrix.board }}${{ steps.prep.outputs.options }} $example --warnings=${{ steps.prep.outputs.warnings }} 125 | 126 | if [ $? -ne 0 ]; then 127 | echo -e "\033[1;31m${example##*/} build FAILED\033[0m\n"; 128 | exit 1; 129 | else 130 | echo -e "\033[1;32m${example##*/} build PASSED\033[0m\n"; 131 | fi 132 | fi 133 | done 134 | -------------------------------------------------------------------------------- /src/LoadSecrets.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // LoadSecrets.cpp 3 | // 4 | // Load LoRaWAN secrets from file 'secrets.json' on LittleFS, if available 5 | // 6 | // created: 07/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2024 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // 32 | // History: 33 | // 34 | // 20240723 Created from BresserWeatherSensorLW.ino 35 | // 20240928 Refactoring & modification for LoRaWAN v1.0.4 (requires no nwkKey) 36 | // 37 | // ToDo: 38 | // - 39 | // 40 | /////////////////////////////////////////////////////////////////////////////// 41 | 42 | #include "LoadSecrets.h" 43 | 44 | // Load LoRaWAN secrets from file 'secrets.json' on LittleFS, if available 45 | void loadSecrets(bool requireNwkKey, uint64_t &joinEUI, uint64_t &devEUI, uint8_t *nwkKey, uint8_t *appKey) 46 | { 47 | 48 | if (!LittleFS.begin( 49 | #if defined(ESP32) 50 | // Format the LittleFS partition on error; parameter only available for ESP32 51 | true 52 | #endif 53 | )) 54 | { 55 | log_e("Could not initialize LittleFS."); 56 | return; 57 | } 58 | 59 | File file = LittleFS.open("/secrets.json", "r"); 60 | 61 | if (!file) 62 | { 63 | log_i("File 'secrets.json' not found."); 64 | return; 65 | } 66 | 67 | log_d("Reading 'secrets.json'"); 68 | JsonDocument doc; 69 | 70 | // Deserialize the JSON document 71 | DeserializationError error = deserializeJson(doc, file); 72 | file.close(); 73 | 74 | if (error) 75 | { 76 | log_e("Failed to read JSON file, using defaults."); 77 | return; 78 | } 79 | 80 | const char *joinEUIStr = doc["joinEUI"]; 81 | if (joinEUIStr == nullptr) 82 | { 83 | log_e("Missing joinEUI."); 84 | return; 85 | } 86 | 87 | uint64_t _joinEUI = 0; 88 | for (int i = 2; i < 18; i += 2) 89 | { 90 | char tmpStr[3] = ""; 91 | unsigned int tmpByte; 92 | strncpy(tmpStr, &joinEUIStr[i], 2); 93 | sscanf(tmpStr, "%x", &tmpByte); 94 | _joinEUI = (_joinEUI << 8) | tmpByte; 95 | } 96 | // printf() cannot print 64-bit hex numbers (sic!), so we split it in two 32-bit numbers... 97 | log_d("joinEUI: 0x%08X%08X", static_cast(_joinEUI >> 32), static_cast(_joinEUI & 0xFFFFFFFF)); 98 | 99 | const char *devEUIStr = doc["devEUI"]; 100 | if (devEUIStr == nullptr) 101 | { 102 | log_e("Missing devEUI."); 103 | return; 104 | } 105 | 106 | uint64_t _devEUI = 0; 107 | for (int i = 2; i < 18; i += 2) 108 | { 109 | char tmpStr[3] = ""; 110 | unsigned int tmpByte; 111 | strncpy(tmpStr, &devEUIStr[i], 2); 112 | sscanf(tmpStr, "%x", &tmpByte); 113 | _devEUI = (_devEUI << 8) | tmpByte; 114 | } 115 | if (_devEUI == 0) 116 | { 117 | log_e("devEUI is zero."); 118 | return; 119 | } 120 | // printf() cannot print 64-bit hex numbers (sic!), so we split it in two 32-bit numbers... 121 | log_d("devEUI: 0x%08X%08X", static_cast(_devEUI >> 32), static_cast(_devEUI & 0xFFFFFFFF)); 122 | 123 | uint8_t check = 0; 124 | bool fail = false; 125 | 126 | log_i("appKey:"); 127 | uint8_t _appKey[16]; 128 | for (size_t i = 0; i < 16; i++) 129 | { 130 | const char *buf = doc["appKey"][i]; 131 | if (buf == nullptr) 132 | { 133 | fail = true; 134 | break; 135 | } 136 | unsigned int tmp; 137 | sscanf(buf, "%x", &tmp); 138 | _appKey[i] = static_cast(tmp); 139 | check |= _appKey[i]; 140 | #if CORE_DEBUG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 141 | printf("0x%02X", _appKey[i]); 142 | if (i < 15) 143 | { 144 | printf(", "); 145 | } 146 | #endif 147 | } // for all app_key bytes 148 | #if CORE_DEBUG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 149 | printf("\n"); 150 | #endif 151 | if (fail || (check == 0)) 152 | { 153 | log_e("appKey parse error"); 154 | return; 155 | } 156 | 157 | check = 0; 158 | if (requireNwkKey) 159 | { 160 | log_d("nwkKey:"); 161 | uint8_t _nwkKey[16]; 162 | for (size_t i = 0; i < 16; i++) 163 | { 164 | const char *buf = doc["nwkKey"][i]; 165 | if (buf == nullptr) 166 | { 167 | fail = true; 168 | break; 169 | } 170 | unsigned int tmp; 171 | sscanf(buf, "%x", &tmp); 172 | _nwkKey[i] = static_cast(tmp); 173 | check |= _nwkKey[i]; 174 | #if CORE_DEBUG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 175 | printf("0x%02X", _nwkKey[i]); 176 | if (i < 15) 177 | { 178 | printf(", "); 179 | } 180 | #endif 181 | } // for all nwk_key bytes 182 | #if CORE_DEBUG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 183 | printf("\n"); 184 | #endif 185 | if (fail || (check == 0)) 186 | { 187 | log_e("nwkKey parse error"); 188 | return; 189 | } 190 | memcpy(nwkKey, _nwkKey, 16); 191 | } // if (requireNwkKey) 192 | 193 | // Every check passed, copy intermediate values as result 194 | joinEUI = _joinEUI; 195 | devEUI = _devEUI; 196 | memcpy(appKey, _appKey, 16); 197 | 198 | return; 199 | } 200 | -------------------------------------------------------------------------------- /src/logging.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // logging.h 3 | // 4 | // Replacement for 5 | // https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-log.h 6 | // on RP2040 7 | // 8 | // - DEBUG_RP2040_PORT is set in Arduino IDE: 9 | // Tools->Debug port: "|||" 10 | // - CORE_DEBUG_LEVEL has to be set manually below 11 | // 12 | // created: 09/2023 13 | // 14 | // 15 | // MIT License 16 | // 17 | // Copyright (c) 2023 Matthias Prinke 18 | // 19 | // Permission is hereby granted, free of charge, to any person obtaining a copy 20 | // of this software and associated documentation files (the "Software"), to deal 21 | // in the Software without restriction, including without limitation the rights 22 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | // copies of the Software, and to permit persons to whom the Software is 24 | // furnished to do so, subject to the following conditions: 25 | // 26 | // The above copyright notice and this permission notice shall be included in all 27 | // copies or substantial portions of the Software. 28 | // 29 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | // SOFTWARE. 36 | // 37 | // 38 | // History: 39 | // 40 | // 20230927 Created from BresserWeatherSensorReceiver 41 | // 20231004 Added function names and line numbers to ESP8266/RP2040 debug logging 42 | // 20231005 Allowed re-definition of CORE_DEBUG_LEVEL and log_* macros 43 | // 20251031 Added option for logging via Serial2 44 | // 45 | // ToDo: 46 | // - 47 | // 48 | /////////////////////////////////////////////////////////////////////////////// 49 | 50 | /*! \file logging.h 51 | * \brief Replacement for esp32-hal-log.h on RP20240 52 | * 53 | * Replacement for 54 | * https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-log.h 55 | * on RP2040 56 | * 57 | * - DEBUG_RP2040_PORT is set in Arduino IDE: 58 | * Tools->Debug port: "|||" 59 | * - CORE_DEBUG_LEVEL has to be set manually below 60 | */ 61 | 62 | #ifndef LOGGING_H 63 | #define LOGGING_H 64 | 65 | // Enable to use Serial2 as debug output port 66 | // This is useful to debug power management without powering/charging via USB 67 | // #define SERIAL2_LOG_ENABLE 68 | 69 | #if CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_NONE 70 | const char *pathToFileName(const char *path); 71 | 72 | #if defined(SERIAL2_LOG_ENABLE) 73 | 74 | // Use Serial2 as debug port 75 | #define DEBUG_PORT Serial2 76 | #undef log_i 77 | #define log_i(...) { DEBUG_PORT.printf("[I][%s:%d] %s(): ", pathToFileName(__FILE__), __LINE__, __func__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 78 | #undef log_d 79 | #define log_d(...) { DEBUG_PORT.printf("[D][%s:%d] %s(): ", pathToFileName(__FILE__), __LINE__, __func__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 80 | #undef log_w 81 | #define log_w(...) { DEBUG_PORT.printf("[W][%s:%d] %s(): ", pathToFileName(__FILE__), __LINE__, __func__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 82 | #undef log_e 83 | #define log_e(...) { DEBUG_PORT.printf("[E][%s:%d] %s(): ", pathToFileName(__FILE__), __LINE__, __func__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 84 | #undef log_v 85 | #define log_v(...) { DEBUG_PORT.printf("[V][%s:%d] %s(): ", pathToFileName(__FILE__), __LINE__, __func__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 86 | #endif // SERIAL2_LOG_ENABLE 87 | #endif // CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_NONE 88 | 89 | #if defined(ARDUINO_ARCH_RP2040) 90 | const char *pathToFileName(const char *path); 91 | 92 | #if defined(DEBUG_RP2040_PORT) 93 | #define DEBUG_PORT DEBUG_RP2040_PORT 94 | #endif 95 | 96 | #define ARDUHAL_LOG_LEVEL_NONE 0 97 | #define ARDUHAL_LOG_LEVEL_ERROR 1 98 | #define ARDUHAL_LOG_LEVEL_WARN 2 99 | #define ARDUHAL_LOG_LEVEL_INFO 3 100 | #define ARDUHAL_LOG_LEVEL_DEBUG 4 101 | #define ARDUHAL_LOG_LEVEL_VERBOSE 5 102 | 103 | // '#undef' allows to change a previous definition from WeatherSensor.h 104 | #undef log_e 105 | #undef log_w 106 | #undef log_i 107 | #undef log_d 108 | #undef log_v 109 | 110 | // Set desired level here! 111 | #undef CORE_DEBUG_LEVEL 112 | #define CORE_DEBUG_LEVEL ARDUHAL_LOG_LEVEL_DEBUG 113 | 114 | #if defined(DEBUG_PORT) && CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_NONE 115 | #define log_e(...) { DEBUG_PORT.printf("[E][%d] %s(): ", __LINE__, __func__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 116 | #else 117 | #define log_e(...) {} 118 | #endif 119 | #if defined(DEBUG_PORT) && CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_ERROR 120 | #define log_w(...) { DEBUG_PORT.printf("[W][%d] %s(): ", __LINE__, __func__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 121 | #else 122 | #define log_w(...) {} 123 | #endif 124 | #if defined(DEBUG_PORT) && CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_WARN 125 | #define log_i(...) { DEBUG_PORT.printf("[I][%d] %s(): ", __LINE__, __func__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 126 | #else 127 | #define log_i(...) {} 128 | #endif 129 | #if defined(DEBUG_PORT) && CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_INFO 130 | #define log_d(...) { DEBUG_PORT.printf("[D][%d] %s(): ", __LINE__, __func__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 131 | #else 132 | #define log_d(...) {} 133 | #endif 134 | #if defined(DEBUG_PORT) && CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_DEBUG 135 | #define log_v(...) { DEBUG_PORT.printf("[V][%d] %s(): ", __LINE__, __func__); DEBUG_PORT.printf(__VA_ARGS__); DEBUG_PORT.println(); } 136 | #else 137 | #define log_v(...) {} 138 | #endif 139 | 140 | #endif // defined(ARDUINO_ARCH_RP2040) 141 | #endif // LOGGING_H -------------------------------------------------------------------------------- /extras/RTCSet/RTCSet.ino: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // RTCSet.ino 3 | // 4 | // Utility for setting/testing an external RTC chip connected via I2C bus 5 | // to ESP32 or RP2040. 6 | // 7 | // Uses Adafruit RTClib library. 8 | // 9 | // created: 08/2025 10 | // 11 | // 12 | // MIT License 13 | // 14 | // Copyright (c) 2025 Matthias Prinke 15 | // 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // 23 | // The above copyright notice and this permission notice shall be included in all 24 | // copies or substantial portions of the Software. 25 | // 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | // SOFTWARE. 33 | // 34 | // History: 35 | // 36 | // 20250803 Created 37 | // 20250804 Fixed and tested RP2040 implementation 38 | // 39 | /////////////////////////////////////////////////////////////////////////////// 40 | 41 | #include 42 | #include 43 | #include "RTClib.h" // https://github.com/adafruit/RTClib 44 | 45 | 46 | // Select your RTC chip 47 | RTC_DS3231 rtc; 48 | // RTC_DS1307 rtc; 49 | // RTC_PCF8523 rtc; 50 | // RTC_PCF8563 rtc; 51 | 52 | char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; 53 | 54 | // Sync the MCU's internal RTC with the external RTC 55 | // NOTE: 56 | // For RP2040, this syncs only the SW RTC, not the HW RTC which retains operation during sleep mode! 57 | void syncRTCWithExtRTC(void) 58 | { 59 | // Get the current time from the DS3231 60 | DateTime now = rtc.now(); 61 | 62 | // Convert DateTime to time_t 63 | struct tm timeinfo; 64 | timeinfo.tm_year = now.year() - 1900; 65 | timeinfo.tm_mon = now.month() - 1; 66 | timeinfo.tm_mday = now.day(); 67 | timeinfo.tm_hour = now.hour(); 68 | timeinfo.tm_min = now.minute(); 69 | timeinfo.tm_sec = now.second(); 70 | 71 | time_t t = mktime(&timeinfo); 72 | 73 | // Set the internal RTC 74 | struct timeval tv = {t, 0}; // `t` is seconds, 0 is microseconds 75 | settimeofday(&tv, nullptr); 76 | } 77 | 78 | void setup() 79 | { 80 | Serial.begin(115200); 81 | 82 | #ifndef ESP8266 83 | while (!Serial) 84 | ; // wait for serial port to connect. Needed for native USB 85 | #endif 86 | 87 | if (!rtc.begin()) 88 | { 89 | Serial.println("Couldn't find RTC"); 90 | Serial.flush(); 91 | while (1) 92 | delay(10); 93 | } 94 | 95 | if (rtc.lostPower()) 96 | { 97 | Serial.println("RTC lost power!"); 98 | } 99 | 100 | Serial.println("Input date and time in format YYYY-MM-DD HH:MM:SS (e.g. 2023-10-01 12:00:00),"); 101 | Serial.println("send to use compile time, or to skip setting the RTC."); 102 | 103 | while (!Serial.available()) 104 | { 105 | delay(10); // Wait for input 106 | } 107 | 108 | String input_str = Serial.readStringUntil('\n'); 109 | if (input_str.length() > 0) 110 | { 111 | if (input_str == "c" || input_str == "C") 112 | { 113 | Serial.println("Using compile time for RTC."); 114 | rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); 115 | Serial.println("RTC set to compile time."); 116 | } 117 | if (input_str == "s" || input_str == "S") 118 | { 119 | Serial.println("Skipping RTC setting."); 120 | } 121 | else 122 | { 123 | // Parse the input string 124 | int year, month, day, hour, minute, second; 125 | if (sscanf(input_str.c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second) == 6) 126 | { 127 | rtc.adjust(DateTime(year, month, day, hour, minute, second)); 128 | Serial.println("RTC set to user-defined date and time."); 129 | } 130 | else 131 | { 132 | Serial.println("Invalid date/time format, skipping."); 133 | } 134 | } 135 | } 136 | 137 | // Sync the MCU's internal RTC with the external RTC 138 | syncRTCWithExtRTC(); 139 | } 140 | 141 | void loop() 142 | { 143 | Serial.print("Temperature: "); 144 | Serial.print(rtc.getTemperature()); 145 | Serial.println(" C"); 146 | 147 | DateTime now = rtc.now(); 148 | 149 | Serial.print(now.year(), DEC); 150 | Serial.print('/'); 151 | Serial.print(now.month(), DEC); 152 | Serial.print('/'); 153 | Serial.print(now.day(), DEC); 154 | Serial.print(" ("); 155 | Serial.print(daysOfTheWeek[now.dayOfTheWeek()]); 156 | Serial.print(") "); 157 | Serial.print(now.hour(), DEC); 158 | Serial.print(':'); 159 | Serial.print(now.minute(), DEC); 160 | Serial.print(':'); 161 | Serial.print(now.second(), DEC); 162 | Serial.println(); 163 | 164 | time_t now_t = time(nullptr); 165 | if (now_t == -1) 166 | { 167 | Serial.println("Failed to obtain time"); 168 | } 169 | 170 | // Convert time_t to struct tm 171 | struct tm *tm_info = localtime(&now_t); 172 | 173 | Serial.print("Current time: "); 174 | Serial.print(asctime(tm_info)); 175 | 176 | // Print the RTC time in ISO 8601 format 177 | char buffer[20]; 178 | strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", tm_info); 179 | Serial.print("ISO 8601 format: "); 180 | Serial.println(buffer); 181 | Serial.println(); 182 | 183 | // Wait for a while before the next loop iteration 184 | delay(5000); 185 | } 186 | -------------------------------------------------------------------------------- /src/BleSensors/BleSensors.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // BleSensors.h 3 | // 4 | // Wrapper class for Theeengs Decoder (https://github.com/theengs/decoder) 5 | // 6 | // Intended for compatibility to the ATC_MiThermometer library 7 | // (https://github.com/matthias-bs/ATC_MiThermometer) 8 | // 9 | // created: 02/2023 10 | // 11 | // 12 | // MIT License 13 | // 14 | // Copyright (c) 2025 Matthias Prinke 15 | // 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // 23 | // The above copyright notice and this permission notice shall be included in all 24 | // copies or substantial portions of the Software. 25 | // 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | // SOFTWARE. 33 | // 34 | // 35 | // History: 36 | // 37 | // 20230211 Created 38 | // 20240417 Added additional constructor and method setAddresses() 39 | // 20240427 Added parameter activeScan to getData() 40 | // 20250121 Updated for NimBLE-Arduino v2.x 41 | // 20250808 Added specific logging macros in scan callback to avoid WDT reset 42 | // 20250926 Changed getData() to return number of known sensors found 43 | // 20251014 Added optional callback to abort scanning early 44 | // 45 | // ToDo: 46 | // - 47 | // 48 | /////////////////////////////////////////////////////////////////////////////// 49 | 50 | /*! \file BleSensors.h 51 | * \brief Wrapper class for Theeengs Decoder (https://github.com/theengs/decoder) 52 | */ 53 | 54 | #if !defined(BLE_SENSORS) && !defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) \ 55 | && !defined(ARDUINO_ARCH_RP2040) 56 | #define BLE_SENSORS 57 | 58 | #include 59 | #include //!< https://github.com/h2zero/NimBLE-Arduino 60 | #include //!< https://github.com/theengs/decoder 61 | 62 | // Extensive logging in the callback may lead to a watchdog reset 63 | // see 64 | // https://github.com/h2zero/NimBLE-Arduino/issues/329 65 | // https://github.com/h2zero/NimBLE-Arduino/issues/351 66 | 67 | //#define CB_LOGGING 68 | #if defined(CB_LOGGING) 69 | #define cb_log_i log_i //!< Info 70 | #define cb_log_w log_w //!< Warn 71 | #define cb_log_d log_d //!< Debug 72 | #define cb_log_e log_e //!< Error 73 | #define cb_log_v log_v //!< Verbose 74 | #else 75 | #define cb_log_i {} //!< Info 76 | #define cb_log_w {} //!< Warn 77 | #define cb_log_d {} //!< Debug 78 | #define cb_log_e {} //!< Error 79 | #define cb_log_v {} //!< Verbose 80 | #endif 81 | 82 | /*! 83 | * \brief BLE sensor data 84 | */ 85 | struct BleDataS { 86 | bool valid; //!< data valid 87 | float temperature; //!< temperature in degC 88 | float humidity; //!< humidity in % 89 | uint8_t batt_level; //!< battery level in % 90 | int rssi; //!< RSSI in dBm 91 | }; 92 | 93 | typedef struct BleDataS ble_sensors_t; //!< Shortcut for struct BleDataS 94 | 95 | 96 | /*! 97 | * \brief BLE Sensor (e.g. thermometer/hygrometer) client 98 | */ 99 | class BleSensors { 100 | public: 101 | /*! 102 | * \brief Constructor. 103 | * 104 | * \param known_sensors Vector of BLE MAC addresses of known sensors, e.g. {"11:22:33:44:55:66", "AA:BB:CC:DD:EE:FF"} 105 | */ 106 | BleSensors(std::vector known_sensors, bool (*stopScanCb)() = nullptr) { 107 | _known_sensors = known_sensors; 108 | data.resize(known_sensors.size()); 109 | _stopScanCb = stopScanCb; 110 | }; 111 | 112 | /*! 113 | * \brief Constructor. 114 | */ 115 | BleSensors(bool (*stopScanCb)() = nullptr) { 116 | data.resize(0); 117 | _stopScanCb = stopScanCb; 118 | }; 119 | 120 | /*! 121 | * \brief Set BLE MAC addresses of known sensors 122 | * 123 | * \param known_sensors vector of BLE MAC addresses (see constructor) 124 | */ 125 | void setAddresses(std::vector known_sensors) { 126 | _known_sensors = known_sensors; 127 | data.resize(known_sensors.size()); 128 | }; 129 | 130 | /*! 131 | * \brief Initialization. 132 | */ 133 | void begin(void) { 134 | }; 135 | 136 | /*! 137 | * \brief Delete results from BLEScan buffer to release memory. 138 | */ 139 | void clearScanResults(void); 140 | 141 | /*! 142 | * \brief Get data from sensors by running a BLE scan. 143 | * 144 | * \param duration Scan duration in seconds 145 | * \param activeScan 0: passive scan / 1: active scan 146 | * 147 | * \return Number of known sensors found (max. size of known_sensors vector) 148 | */ 149 | unsigned getData(uint32_t duration, bool activeScan = true); 150 | 151 | /*! 152 | * \brief Set sensor data invalid. 153 | */ 154 | void resetData(void); 155 | 156 | /*! 157 | * \brief Sensor data. 158 | */ 159 | std::vector data; 160 | 161 | protected: 162 | std::vector _known_sensors; /// MAC addresses of known sensors 163 | NimBLEScan* _pBLEScan; /// NimBLEScan object 164 | bool (*_stopScanCb)(); /// Pointer to optional callback function to stop scan early 165 | }; 166 | #endif 167 | -------------------------------------------------------------------------------- /docs/influxdb_integration/influxdb_integration.md: -------------------------------------------------------------------------------- 1 | # ChirpStack and InfluxDB Integration 2 | 3 | by [Davide D'Asaro](https://github.com/evon800c) 4 | 5 | ## Introduction 6 | 7 | I don’t know if my experience with LoRaWAN and my personal use of the BresserWeatherSensorLW project is interesting for anyone, but I am happy to share what I have realized for my experiment. 8 | 9 | LoRa and LoRaWAN and all other components that I have decided to use were completely new to me. My approach was very basic and I think that my description is not useful for advanced users. I think that it could be useful for users who are now starting the trip into this world. 10 | 11 | When we start talking about LoRaWAN, we should also talk about its fundamental elements.
12 | Some note from https://www.thethingsnetwork.org/docs/lorawan/architecture/ 13 | 14 | ![LoRaWAN Architecture](images/image-00.png) 15 | 16 | - Network Server 17 | - Gateway 18 | - Device 19 | - Application Server 20 | 21 | Instead of using a public LoRaWAN network (as for example TTN), I have decided to implement my personal, private one. 22 | 23 | ### Network Server 24 | 25 | To implement a LoRaWAN Network Server, I have decided to use ChirpStack (https://www.chirpstack.io) installed on a virtual machine in my lab environment. 26 | ChirpStack is an open source solution to implement a LoRaWAN Network Server. Documentation is available, clear and simple, so it is not necessary to explain it here. 27 | 28 | ### Gateway 29 | 30 | It is necessary to choose a solution that covers your personal needs. 31 | For my personal needs, I have decided to use a Raspberry PI 4 with RAK2245 PI HAT and ChirpStack Gateway OS (https://www.chirpstack.io/docs/chirpstack-gateway-os/index.html). 32 | Also in this case, documentation is quite simple and clear. 33 | 34 | ### Device 35 | 36 | I have used a "Heltec wifi LoRa 32 v3" board, but for Matthias' **BresserWeatherSensorLW** firmware, each compatible hardware listed in the initial project documentation can be a good choice. 37 | 38 | After initial configuration of the LoRaWAN Network Server and in **BresserWeatherSensorLW** code, we can flash the board. If everything goes right, you can see data (such as air temp, but not only that) on the LoRaWAN Network Server console coming as uplink from the device. 39 | 40 | ### Application Server 41 | 42 | Now we can speak about the Application Server. 43 | I have decided to use InfluxDB to store my data as timeseries. So an InfluxDB server will assume the Application Server role (you can have multiple application servers based on your needs). 44 | We need to install an InfluxDB server, and at https://docs.influxdata.com/influxdb/v2/install/ we can choose the InfluxDB installation that we prefer and find all information required to do that. 45 | 46 | After InfluxDB installation, we need to configure: Organization, Bucket and Token. 47 | 48 | In the images below, you can see how do this. 49 | 50 | #### Create Organization and Bucket 51 | 52 | ![InfluxDB - Create Organization](images/image-01.png) 53 | 54 | ![InfluxDB - Create Bucket](images/image-02.png) 55 | 56 | #### Create API Token 57 | 58 | ![InfluxDB - API Tokens](images/image-03.png) 59 | 60 | ![InfluxDb - Configure API Token](images/image-04.png) 61 | 62 | ![InfluxDb - Create API Token](images/image-05.png) 63 | 64 | ![InfluxDB - Copy API Token](images/image-06.png) 65 | 66 | Now, we have all elements and parameters, including the URL to access the management API, needed for next step. 67 | 68 | > [!NOTE] 69 | > Each public LoRaWAN Network Provider offers some specific interface and features to connect to an Application Server. Also ChirpStack (as a LoRaWAN Network Server) is not different. What I'm going to write will cover a large amount of situations, I think. 70 | 71 | 72 | For integrating ChirpStack and InfluxDB we need to: 73 | 74 | #### Create an Application in ChirpSTack 75 | 76 | ![Chirpstack - Create Application](images/image-07.png) 77 | 78 | #### Create a Connection in ChirpStack to the Application Server (InfluxDB in our case) 79 | 80 | This can be done using a small number of simple parameters, such as: 81 | 82 | - InfluxDB version 83 | - API endpoint (write) → http://InfluxDB:8086/api/v2/write (as example) 84 | - Organization (defined on InfluxDB side) → in my case "labnet" 85 | - Bucket (defined on InfluxDB side) → in my case "ChirpStack" 86 | - Token (defined on InfluxDB side) 87 | 88 | ![ChirpStack - Applications - Integrations](images/image-08.png) 89 | 90 | ![ChirpStack - Applications](images/image-09.png) 91 | 92 | ![ChirpStack - Applications - InfluxDB](images/image-10.png) 93 | 94 | Now, ChirpStack writes data coming from the device into InfluxDB. 95 | 96 | ## Exploring Data inside InfluxDB 97 | 98 | At this point, my first challenge was to understand how to explore data in InfluxDB. 99 | 100 | From the left column, switch to 'Organization' created before: 101 | 102 | ![InfluxDB - Switch Organizations](images/image-12.png) 103 | 104 | ![InfluxDB - Choose an Organization](images/image-13.png) 105 | 106 | Always from the left column on icon "arrow up", click on the 'Buckets' label and select the bucket created before (mybucket). 107 | 108 | ![InfluxDB - Get Started](images/image-14.png) 109 | 110 | ![InfluxDB - Load Data](images/image-15.png) 111 | 112 | Now, a data exploration interface is presented. Here we will be guided through the query construction. 113 | 114 | From left to right, you can select parameters for the query — in my case: 115 | + From: chirpstack 116 | + Filter: device_name → and select device name create on LoRaWan console 117 | + Filter: _measurement → device_frmpayload_data_bytes_air_temp_c - the name defined by Matthias code (for air temp) 118 | + Filter: _field → value 119 | 120 | Now click on 'Submit' button. 121 | 122 | ![InfluxDB - Submit](images/image-16.png) 123 | 124 | I suppose that you receive an error `unsupported input type for mean aggregate: string`, too. If so, near the "Submit" button, click on "Script Editor". Now you switch to the manual query writer and you can see the query code that you write.
125 | The issue is caused by the data type, which initially is string insteat of float. 126 | 127 | ![InfluxDB - unsupported input type](images/image-17.png) 128 | 129 | To be able to represent data in a "graph" visualization type, you need to cast the data from string to float, so add "|> toFloat()" as shown below and click the 'Submit' button. 130 | 131 | ![InfluxDB - Type conversion from string to float](images/image-18.png) 132 | 133 | This is the first simple query that I have written. 134 | 135 | If you want, you can save your query and visualization (graph) in a dashboard. If you don't have any dashboard, you can create one on the fly.
136 | On the same dashboard, you can add all visualizations/queries that you want.
137 | 138 | From the dashboard, you can select the time frame that you need to analyze. 139 | 140 | ![Dashboard](images/image-19.png) 141 | 142 | -------------------------------------------------------------------------------- /src/PayloadBresser.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // PayloadBresser.h 3 | // 4 | // Read Bresser sensor data and encode as LoRaWAN payload 5 | // 6 | // created: 05/2024 7 | // 8 | // 9 | // MIT License 10 | // 11 | // Copyright (c) 2025 Matthias Prinke 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | // 31 | // 32 | // History: 33 | // 34 | // 20240521 Created 35 | // 20240523 Added encodeWeatherSensor(), 36 | // added defines for signalling invalid data 37 | // 20240524 Moved rainGauge, appPrefs and time members from AppLayer 38 | // into the class 39 | // Added isSpaceLeft(), payloadSize[] & sensorTypes[] 40 | // 20240528 Moved encoding of invalid values to BresserWeatherSensorLWCmd.h 41 | // 20240530 Added missing entries in sensorTypes[] 42 | // 20240603 encodeBresser(): added appStatus parameter 43 | // 20240716 Added ws_scantime and scanBresser() 44 | // 20250209 Added Weather Station 8-in-1 45 | // 20250318 Renamed PAYLOAD_SIZE to MAX_UPLINK_SIZE 46 | // 20250828 Changed time functions to POSIX, added SystemContext 47 | // Added ws_postproc_int 48 | // 49 | // ToDo: 50 | // - 51 | // 52 | /////////////////////////////////////////////////////////////////////////////// 53 | 54 | /*! \file PayloadBresser.h 55 | * \brief LoRaWAN node application layer - Bresser sensors 56 | */ 57 | 58 | #if !defined(_PAYLOAD_BRESSER) 59 | #define _PAYLOAD_BRESSER 60 | 61 | #include "../BresserWeatherSensorLWCfg.h" 62 | #include "WeatherSensorCfg.h" 63 | #include 64 | #include 65 | #include 66 | 67 | #ifdef RAINDATA_EN 68 | #include "RainGauge.h" 69 | #endif 70 | #ifdef LIGHTNINGSENSOR_EN 71 | #include "Lightning.h" 72 | #endif 73 | 74 | #include 75 | #include "SystemContext.h" 76 | #include "logging.h" 77 | 78 | 79 | /*! 80 | * \brief LoRaWAN node application layer - Bresser sensors 81 | * 82 | * Encodes data from Bresser sensors received via radio messages as LoRaWAN payload 83 | */ 84 | class PayloadBresser 85 | { 86 | public: 87 | /// Bresser Weather Sensor Receiver 88 | WeatherSensor weatherSensor; 89 | 90 | /// Weather Sensor Scan Request 91 | uint8_t ws_scantime = 0; 92 | 93 | /// Weather Sensor Post-Processing Update Rate (0: auto, 1..255: minutes) 94 | uint8_t ws_postproc_interval = 0; 95 | 96 | /// Payload size in bytes per sensor type 97 | const uint8_t payloadSize[16] = { 98 | 0, 99 | 25, // SENSOR_TYPE_WEATHER<0|1|2> (max.) 100 | 3, // SENSOR_TYPE_THERMO_HYGRO 101 | 2, // SENSOR_TYPE_POOL_THERMO 102 | 3, // SENSOR_TYPE_SOIL 103 | 1, // SENSOR_TYPE_LEAKAGE 104 | 0, // reserved 105 | 0, // reserved 106 | 6, // SENSOR_TYPE_AIR_PM 107 | 3, // SENSOR_TYPE_LIGHTNING (min.) 108 | 2, // SENSOR_TYPE_CO2 109 | 3, // SENSOR_TYPE_HCHO_VOC 110 | 0, // reserved 111 | 0, // (Weather Station 8-in-1, included in [1]) 112 | 0, // reserved 113 | 0 // reserved 114 | }; 115 | 116 | #if CORE_DEBUG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 117 | /// Map sensor type ID to name 118 | const char * sensorTypes[16] = { 119 | "Weather", 120 | "Weather", 121 | "Thermo/Hygro", 122 | "Pool Temperature", 123 | "Soil", 124 | "Leakage", 125 | "reserved", 126 | "reserved", 127 | "Air Quality (PM)", 128 | "Lightning", 129 | "CO2", 130 | "Air Quality (HCHO/VOC)", 131 | "reserved", 132 | "Weather", // Weather Station 8-in-1 133 | "reserved", 134 | "reserved" 135 | }; 136 | #endif 137 | 138 | private: 139 | /// System context 140 | SystemContext *_sysCtx; 141 | 142 | /// Preferences (stored in flash memory) 143 | Preferences appPrefs; 144 | 145 | 146 | #ifdef RAINDATA_EN 147 | public: 148 | /// Rain data statistics 149 | RainGauge rainGauge; 150 | #endif 151 | 152 | #ifdef LIGHTNINGSENSOR_EN 153 | public: 154 | /// Lightning sensor post-processing 155 | Lightning lightningProc; 156 | 157 | private: 158 | time_t lightn_ts; 159 | int lightn_events; 160 | uint8_t lightn_distance; 161 | #endif 162 | 163 | public: 164 | /*! 165 | * \brief Constructor 166 | */ 167 | PayloadBresser(SystemContext* sysCtx) 168 | { 169 | _sysCtx = sysCtx; 170 | }; 171 | 172 | /*! 173 | * \brief Bresser sensors startup code 174 | */ 175 | void begin(void); 176 | 177 | /*! 178 | * \brief Scan for Bresser sensors 179 | * 180 | * \param ws_scantime Scan time in seconds 181 | * \param encoder LoRaWAN payload encoder object 182 | */ 183 | void scanBresser(uint8_t ws_scantime, LoraEncoder &encoder); 184 | 185 | /*! 186 | * \brief Encode Bresser sensor data for LoRaWAN transmission 187 | * 188 | * \param appPayloadCfg LoRaWAN payload configuration bitmaps 189 | * \param appStatus Application layer status (i.e. sensor battery status bits) 190 | * \param encoder LoRaWAN payload encoder object 191 | */ 192 | void encodeBresser(uint8_t *appPayloadCfg, uint8_t *appStatus, LoraEncoder &encoder); 193 | 194 | private: 195 | void encodeWeatherSensor(int idx, uint16_t flags, LoraEncoder &encoder); 196 | void encodeThermoHygroSensor(int idx, LoraEncoder &encoder); 197 | void encodePoolThermometer(int idx, LoraEncoder &encoder); 198 | void encodeSoilSensor(int idx, LoraEncoder &encoder); 199 | void encodeLeakageSensor(int idx, LoraEncoder &encoder); 200 | void encodeAirPmSensor(int idx, LoraEncoder &encoder); 201 | #ifdef LIGHTNINGSENSOR_EN 202 | void encodeLightningSensor(int idx, uint8_t flags, LoraEncoder &encoder); 203 | #endif 204 | void encodeCo2Sensor(int idx, LoraEncoder &encoder); 205 | void encodeHchoVocSensor(int idx, LoraEncoder &encoder); 206 | 207 | bool isSpaceLeft(LoraEncoder &encoder, uint8_t type) 208 | { 209 | return (encoder.getLength() + payloadSize[type] <= MAX_UPLINK_SIZE); 210 | }; 211 | }; 212 | #endif //_PAYLOAD_BRESSER 213 | --------------------------------------------------------------------------------