From 95200e8f6b532d4498cd8e7738109d7996db54b3 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 18 Aug 2025 16:33:52 -0500 Subject: [PATCH 1/4] Adds rfswitch on Portduino (#7663) * Initial attempt to get rfswitch working on Portduino * Make portduino_config global --- src/mesh/LR11x0Interface.cpp | 15 ++++----- src/platform/portduino/PortduinoGlue.cpp | 43 ++++++++++++++++++++++++ src/platform/portduino/PortduinoGlue.h | 10 +++++- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index a20db808e..a0d992c42 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -6,6 +6,10 @@ #include "mesh/NodeDB.h" #ifdef LR11X0_DIO_AS_RF_SWITCH #include "rfswitch.h" +#elif ARCH_PORTDUINO +#include "PortduinoGlue.h" +#define rfswitch_dio_pins portduino_config.rfswitch_dio_pins +#define rfswitch_table portduino_config.rfswitch_table #else static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; static const Module::RfSwitchMode_t rfswitch_table[] = { @@ -14,10 +18,6 @@ static const Module::RfSwitchMode_t rfswitch_table[] = { }; #endif -#ifdef ARCH_PORTDUINO -#include "PortduinoGlue.h" -#endif - // Particular boards might define a different max power based on what their hardware can do, default to max power output if not // specified (may be dangerous if using external PA and LR11x0 power config forgotten) #if ARCH_PORTDUINO @@ -117,17 +117,14 @@ template bool LR11x0Interface::init() #ifdef LR11X0_DIO_AS_RF_SWITCH bool dioAsRfSwitch = true; #elif defined(ARCH_PORTDUINO) - bool dioAsRfSwitch = false; - if (settingsMap[dio2_as_rf_switch]) { - dioAsRfSwitch = true; - } + bool dioAsRfSwitch = portduino_config.has_rfswitch_table; #else bool dioAsRfSwitch = false; #endif if (dioAsRfSwitch) { lora.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table); - LOG_DEBUG("Set DIO RF switch", res); + LOG_DEBUG("Set DIO RF switch"); } if (res == RADIOLIB_ERR_NONE) { diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 5f99ec2c3..ac4e79af1 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -29,6 +29,7 @@ std::map settingsMap; std::map settingsStrings; +portduino_config_struct portduino_config; std::ofstream traceFile; Ch341Hal *ch341Hal = nullptr; char *configPath = nullptr; @@ -553,6 +554,48 @@ bool loadConfig(const char *configPath) } } } + if (yamlConfig["Lora"]["rfswitch_table"]) { + portduino_config.has_rfswitch_table = true; + portduino_config.rfswitch_table[0].mode = LR11x0::MODE_STBY; + portduino_config.rfswitch_table[1].mode = LR11x0::MODE_RX; + portduino_config.rfswitch_table[2].mode = LR11x0::MODE_TX; + portduino_config.rfswitch_table[3].mode = LR11x0::MODE_TX_HP; + portduino_config.rfswitch_table[4].mode = LR11x0::MODE_TX_HF; + portduino_config.rfswitch_table[5].mode = LR11x0::MODE_GNSS; + portduino_config.rfswitch_table[6].mode = LR11x0::MODE_WIFI; + portduino_config.rfswitch_table[7] = END_OF_MODE_TABLE; + + for (int i = 0; i < 5; i++) { + + // set up the pin array first + if (yamlConfig["Lora"]["rfswitch_table"]["pins"][i].as("") == "DIO5") + portduino_config.rfswitch_dio_pins[i] = RADIOLIB_LR11X0_DIO5; + if (yamlConfig["Lora"]["rfswitch_table"]["pins"][i].as("") == "DIO6") + portduino_config.rfswitch_dio_pins[i] = RADIOLIB_LR11X0_DIO6; + if (yamlConfig["Lora"]["rfswitch_table"]["pins"][i].as("") == "DIO7") + portduino_config.rfswitch_dio_pins[i] = RADIOLIB_LR11X0_DIO7; + if (yamlConfig["Lora"]["rfswitch_table"]["pins"][i].as("") == "DIO8") + portduino_config.rfswitch_dio_pins[i] = RADIOLIB_LR11X0_DIO8; + if (yamlConfig["Lora"]["rfswitch_table"]["pins"][i].as("") == "DIO10") + portduino_config.rfswitch_dio_pins[i] = RADIOLIB_LR11X0_DIO10; + + // now fill in the table + if (yamlConfig["Lora"]["rfswitch_table"]["MODE_STBY"][i].as("") == "HIGH") + portduino_config.rfswitch_table[0].values[i] = HIGH; + if (yamlConfig["Lora"]["rfswitch_table"]["MODE_RX"][i].as("") == "HIGH") + portduino_config.rfswitch_table[1].values[i] = HIGH; + if (yamlConfig["Lora"]["rfswitch_table"]["MODE_TX"][i].as("") == "HIGH") + portduino_config.rfswitch_table[2].values[i] = HIGH; + if (yamlConfig["Lora"]["rfswitch_table"]["MODE_TX_HP"][i].as("") == "HIGH") + portduino_config.rfswitch_table[3].values[i] = HIGH; + if (yamlConfig["Lora"]["rfswitch_table"]["MODE_TX_HF"][i].as("") == "HIGH") + portduino_config.rfswitch_table[4].values[i] = HIGH; + if (yamlConfig["Lora"]["rfswitch_table"]["MODE_GNSS"][i].as("") == "HIGH") + portduino_config.rfswitch_table[5].values[i] = HIGH; + if (yamlConfig["Lora"]["rfswitch_table"]["MODE_WIFI"][i].as("") == "HIGH") + portduino_config.rfswitch_table[6].values[i] = HIGH; + } + } } if (yamlConfig["GPIO"]) { settingsMap[userButtonPin] = yamlConfig["GPIO"]["User"].as(RADIOLIB_NC); diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 288870eef..64277322a 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -3,6 +3,8 @@ #include #include +#include "LR11x0Interface.h" +#include "Module.h" #include "platform/portduino/USBHal.h" // Product strings for auto-configuration @@ -126,4 +128,10 @@ bool loadConfig(const char *configPath); static bool ends_with(std::string_view str, std::string_view suffix); void getMacAddr(uint8_t *dmac); bool MAC_from_string(std::string mac_str, uint8_t *dmac); -std::string exec(const char *cmd); \ No newline at end of file +std::string exec(const char *cmd); + +extern struct portduino_config_struct { + bool has_rfswitch_table = false; + uint32_t rfswitch_dio_pins[5] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + Module::RfSwitchMode_t rfswitch_table[8]; +} portduino_config; \ No newline at end of file From f65e2c639ea87acd2d5d871508932e6d1cde76b5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 05:35:25 -0500 Subject: [PATCH 2/4] Update protobufs (#7679) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/protobufs b/protobufs index 5dd723fe6..be5137698 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 5dd723fe6f33a8613ec81acf5e15be26365c7cce +Subproject commit be5137698027f9e9fe6e68d5d5d638049f61ba8f diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index f758995c2..9af095e78 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -99,7 +99,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* Sensirion SFA30 Formaldehyde sensor */ meshtastic_TelemetrySensorType_SFA30 = 42, /* SEN5X PM SENSORS */ - meshtastic_TelemetrySensorType_SEN5X = 43 + meshtastic_TelemetrySensorType_SEN5X = 43, + /* TSL2561 light sensor */ + meshtastic_TelemetrySensorType_TSL2561 = 44 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -434,8 +436,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SEN5X -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SEN5X+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL2561 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL2561+1)) From 2d7818797db11663eaa4428ba8806977f87ccbd1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 05:43:10 -0500 Subject: [PATCH 3/4] Update platform-native digest to cd32f4e (#7662) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- arch/portduino/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index ae68159eb..33a4c4d05 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -2,7 +2,7 @@ [portduino_base] platform = # renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop - https://github.com/meshtastic/platform-native/archive/6cb7a455b440dd0738e8ed74a18136ed5cf7ea63.zip + https://github.com/meshtastic/platform-native/archive/cd32f4ed20812d1fe9c8f74c0b6e80dc93dfce54.zip framework = arduino build_src_filter = From 1691e885f21d69382c9f4e8381e38b8af84e1a40 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 19 Aug 2025 06:00:29 -0500 Subject: [PATCH 4/4] Display test results --- .github/workflows/pr_tests.yml | 244 +++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 .github/workflows/pr_tests.yml diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml new file mode 100644 index 000000000..f6a678a45 --- /dev/null +++ b/.github/workflows/pr_tests.yml @@ -0,0 +1,244 @@ +name: Tests + +on: + pull_request: + branches: [master, develop] + paths-ignore: + - "**.md" + - "docs/**" + - "images/**" + pull_request_target: + branches: [master, develop] + paths-ignore: + - "**.md" + - "docs/**" + - "images/**" + +concurrency: + group: tests-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + actions: read + checks: write + pull-requests: write + +jobs: + native-tests: + name: "πŸ§ͺ Native Tests" + if: github.repository == 'meshtastic/firmware' + uses: ./.github/workflows/test_native.yml + permissions: + contents: read + actions: read + checks: write + + test-summary: + name: "πŸ“Š Test Results" + runs-on: ubuntu-latest + needs: [native-tests] + if: always() + permissions: + contents: read + actions: read + checks: write + pull-requests: write + steps: + - uses: actions/checkout@v5 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Get release version string + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Download test artifacts + if: needs.native-tests.result != 'skipped' + uses: actions/download-artifact@v5 + with: + name: platformio-test-report-${{ steps.version.outputs.long }}.zip + merge-multiple: true + + - name: Parse test results and create detailed summary + id: test-results + run: | + echo "## πŸ§ͺ Test Results Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Check overall job status first + if [[ "${{ needs.native-tests.result }}" == "success" ]]; then + echo "βœ… **Overall Status**: PASSED" >> $GITHUB_STEP_SUMMARY + elif [[ "${{ needs.native-tests.result }}" == "failure" ]]; then + echo "❌ **Overall Status**: FAILED" >> $GITHUB_STEP_SUMMARY + elif [[ "${{ needs.native-tests.result }}" == "cancelled" ]]; then + echo "⏸️ **Overall Status**: CANCELLED" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Tests were cancelled before completion." >> $GITHUB_STEP_SUMMARY + exit 0 + else + echo "⚠️ **Overall Status**: SKIPPED" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Tests were skipped." >> $GITHUB_STEP_SUMMARY + exit 0 + fi + + echo "" >> $GITHUB_STEP_SUMMARY + + # Parse detailed test results if available + if [ -f "testreport.xml" ]; then + echo "### πŸ” Individual Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + python3 << 'EOF' + import xml.etree.ElementTree as ET + import os + + try: + tree = ET.parse('testreport.xml') + root = tree.getroot() + + total_tests = 0 + passed_tests = 0 + failed_tests = 0 + skipped_tests = 0 + + # Parse testsuite elements + for testsuite in root.findall('.//testsuite'): + suite_name = testsuite.get('name', 'Unknown') + suite_tests = int(testsuite.get('tests', '0')) + suite_failures = int(testsuite.get('failures', '0')) + suite_errors = int(testsuite.get('errors', '0')) + suite_skipped = int(testsuite.get('skipped', '0')) + + total_tests += suite_tests + failed_tests += suite_failures + suite_errors + skipped_tests += suite_skipped + passed_tests += suite_tests - suite_failures - suite_errors - suite_skipped + + if suite_tests > 0: + status = "βœ…" if (suite_failures + suite_errors) == 0 else "❌" + print(f"**{status} Test Suite: {suite_name}**") + print(f"- Total: {suite_tests}") + print(f"- Passed: βœ… {suite_tests - suite_failures - suite_errors - suite_skipped}") + print(f"- Failed: ❌ {suite_failures + suite_errors}") + if suite_skipped > 0: + print(f"- Skipped: ⏭️ {suite_skipped}") + print("") + + # Show individual test results for failed suites + if suite_failures + suite_errors > 0: + print("**Failed Tests:**") + for testcase in testsuite.findall('testcase'): + test_name = testcase.get('name', 'Unknown') + failure = testcase.find('failure') + error = testcase.find('error') + + if failure is not None: + msg = failure.get('message', 'Unknown error')[:100] + print(f"- ❌ `{test_name}`: {msg}") + elif error is not None: + msg = error.get('message', 'Unknown error')[:100] + print(f"- ❌ `{test_name}`: ERROR - {msg}") + print("") + else: + # Show passed tests for successful suites + passed_count = 0 + for testcase in testsuite.findall('testcase'): + if testcase.find('failure') is None and testcase.find('error') is None: + if passed_count < 5: # Limit to first 5 to avoid spam + test_name = testcase.get('name', 'Unknown') + print(f"- βœ… `{test_name}`: PASSED") + passed_count += 1 + if passed_count > 5: + print(f"- ... and {passed_count - 5} more tests passed") + print("") + + # Summary statistics + print("### πŸ“Š Test Statistics") + print(f"- **Total Tests**: {total_tests}") + print(f"- **Passed**: βœ… {passed_tests}") + print(f"- **Failed**: ❌ {failed_tests}") + if skipped_tests > 0: + print(f"- **Skipped**: ⏭️ {skipped_tests}") + + if failed_tests > 0: + print(f"\n❌ **{failed_tests} tests failed out of {total_tests} total**") + else: + print(f"\nβœ… **All {total_tests} tests passed!**") + + except Exception as e: + print(f"❌ Error parsing test results: {e}") + EOF + else + echo "⚠️ **No detailed test report available**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Test artifacts may not have been generated properly." >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "View detailed logs in the [Actions tab](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY + + - name: Comment test results on PR + if: github.event_name == 'pull_request' && needs.native-tests.result != 'skipped' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + + // Read the step summary to use as PR comment + let testSummary = "## πŸ§ͺ Test Results Summary\n\n"; + + if ("${{ needs.native-tests.result }}" === "success") { + testSummary += "βœ… **All tests passed!**\n\n"; + } else if ("${{ needs.native-tests.result }}" === "failure") { + testSummary += "❌ **Some tests failed.**\n\n"; + } else { + testSummary += "⚠️ **Tests did not complete normally.**\n\n"; + } + + testSummary += `View detailed results: [Actions Run](${context.payload.repository.html_url}/actions/runs/${context.runId})\n\n`; + testSummary += "---\n"; + testSummary += "*This comment will be automatically updated when new commits are pushed.*"; + + // Find existing comment + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + const botComment = comments.data.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('πŸ§ͺ Test Results Summary') + ); + + if (botComment) { + // Update existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: testSummary + }); + } else { + // Create new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: testSummary + }); + } + + - name: Set overall status + run: | + if [[ "${{ needs.native-tests.result }}" == "success" ]]; then + echo "All tests passed! βœ…" + exit 0 + else + echo "Some tests failed! ❌" + exit 1 + fi