diff --git a/.github/workflows/armhf-release.yml b/.github/workflows/armhf-release.yml index fb1f791..ce25619 100644 --- a/.github/workflows/armhf-release.yml +++ b/.github/workflows/armhf-release.yml @@ -1,65 +1,243 @@ -name: armhf release +name: ARM Cross-Platform Release on: push: - # Sequence of patterns matched against refs/tags tags: - - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 + - 'v*' env: - BUILD_TYPE: Release + PACKAGE_NAME: bb-config jobs: - armhf: - name: build-armhf - runs-on: ubuntu-latest - + build-packages: + name: Build ${{ matrix.arch }} Package + runs-on: ubuntu-22.04 + strategy: + matrix: + arch: [arm64, armhf] + include: + - arch: arm64 + docker_platform: linux/arm64 + deb_arch: arm64 + cpu_flags: "-mcpu=cortex-a53 -mtune=cortex-a53" + - arch: armhf + docker_platform: linux/arm + deb_arch: armhf + cpu_flags: "-mcpu=cortex-a8 -mtune=cortex-a8" + steps: - name: Checkout - uses: actions/checkout@v2 - - name: Install Deps + uses: actions/checkout@v4 + + - name: Extract Version + id: version run: | - sudo dpkg --add-architecture armhf - sudo apt install gcc-12-arm-linux-gnueabihf g++-12-arm-linux-gnueabihf libnm-dev:armhf libglib2.0-dev -y - - name: Build - id: armhf-artifact-download + VERSION=$(echo "${{ github.ref }}" | sed 's|refs/tags/||' | sed 's/^v//') + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Building ${{ env.PACKAGE_NAME }} version: $VERSION" + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + + - name: Setup Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build with Docker + id: build_package run: | - cd ${{ github.workspace }} - mkdir build - cd build - cmake .. -DCMAKE_CXX_COMPILER=$(which arm-linux-gnueabihf-g++-12) \ - -DCMAKE_C_COMPILER=$(which arm-linux-gnueabihf-gcc-12) \ - -DARMHF_DEB=ON \ - -DARCHITECTURE="armhf" \ - -DGIT_VERSION=$(echo ${{ github.ref }} | grep -o v'[0-9]\+\.[0-9]\+\.[0-9]\+') - make -j$(nproc) - make package - armhf__filename__=$(echo $(ls ${{steps.armhf-artifact-download.outputs.download-path}} | grep ..armhf.deb) | tr -d '\n') - name__=$(echo ${armhf__filename__%-*} | tr -d '\n') - version__=$(echo ${{ github.ref }} | grep -o v'[0-9]\+\.[0-9]\+\.[0-9]\+') - echo "name__=$name__" >> $GITHUB_ENV - echo "version__=$version__" >> $GITHUB_ENV - echo "armhf__filename__=$armhf__filename__" >> $GITHUB_ENV - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + echo "Building ${{ env.PACKAGE_NAME }} for ${{ matrix.arch }}" + + # Create final package name + FINAL_FILENAME="${{ env.PACKAGE_NAME }}-v${{ env.VERSION }}-${{ matrix.arch }}.deb" + echo "Final package will be: $FINAL_FILENAME" + echo "final_filename=$FINAL_FILENAME" >> $GITHUB_OUTPUT + + # Create a Dockerfile + cat > Dockerfile.build << 'DOCKERFILE' + FROM ubuntu:22.04 + + ARG VERSION + ARG CPU_FLAGS + ARG DEB_ARCH + ARG PACKAGE_NAME + ENV DEBIAN_FRONTEND=noninteractive + + RUN apt-get update && apt-get install -y \ + build-essential \ + cmake \ + pkg-config \ + libnm-dev \ + libglib2.0-dev \ + debhelper \ + dh-make \ + git \ + file \ + && rm -rf /var/lib/apt/lists/* + + WORKDIR /build + COPY . . + + RUN mkdir -p build && cd build && \ + cmake .. \ + -DCMAKE_C_FLAGS="$CPU_FLAGS -O2 -pipe" \ + -DCMAKE_CXX_FLAGS="$CPU_FLAGS -O2 -pipe" \ + -DARCHITECTURE="$DEB_ARCH" \ + -DGIT_VERSION="$VERSION" && \ + make -j$(nproc) && \ + cpack -G DEB -D CPACK_DEBIAN_PACKAGE_ARCHITECTURE=$DEB_ARCH + DOCKERFILE + + echo "Building Docker image..." + docker buildx build \ + --platform ${{ matrix.docker_platform }} \ + --tag ${{ matrix.arch }}-builder \ + --build-arg VERSION="${{ env.VERSION }}" \ + --build-arg CPU_FLAGS="${{ matrix.cpu_flags }}" \ + --build-arg DEB_ARCH="${{ matrix.deb_arch }}" \ + --build-arg PACKAGE_NAME="${{ env.PACKAGE_NAME }}" \ + --load \ + -f Dockerfile.build \ + . + + echo "Extracting package from container..." + docker create --name builder ${{ matrix.arch }}-builder + docker cp builder:/build/build/ ./build-output/ + docker rm builder + + # Find and rename the package + DEB_FILE=$(find build-output -name "*.deb" -type f | head -1) + if [ -n "$DEB_FILE" ]; then + # Get original package name and version + ORIG_NAME=$(basename "$DEB_FILE") + + # Rename to final format + mv "$DEB_FILE" "$FINAL_FILENAME" + echo "Package renamed from $ORIG_NAME to $FINAL_FILENAME" + echo "Package created: $FINAL_FILENAME" + ls -la "$FINAL_FILENAME" + else + echo "ERROR: No .deb file found!" + find build-output -type f + exit 1 + fi + + - name: Verify Package + run: | + FINAL_FILENAME="${{ steps.build_package.outputs.final_filename }}" + echo "Verifying package: $FINAL_FILENAME" + + if [ -f "$FINAL_FILENAME" ]; then + echo "Package info:" + dpkg-deb -I "$FINAL_FILENAME" || true + echo "" + echo "Package filename verified: $FINAL_FILENAME" + else + echo "ERROR: Package file '$FINAL_FILENAME' not found!" + exit 1 + fi + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.arch }}-package + path: ${{ steps.build_package.outputs.final_filename }} + retention-days: 5 + + create-release: + name: Create Release and Upload Assets + runs-on: ubuntu-22.04 + needs: build-packages + if: success() + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download All Artifacts + uses: actions/download-artifact@v4 with: - tag_name: ${{ env.version__ }} - release_name: Release ${{ env.version__ }} - draft: false - prerelease: false - - name: Upload Release Asset - id: upload-release-asset - uses: actions/upload-release-asset@v1 + path: artifacts/ + pattern: '*' + merge-multiple: true + + - name: List Downloaded Files + run: | + echo "Downloaded artifacts:" + find artifacts/ -type f -name "*.deb" | while read file; do + echo "Found: $file" + dpkg-deb -I "$file" 2>/dev/null | grep -E "(Package|Version|Architecture)" || echo "Could not read package info" + done + + - name: Extract Version + run: | + VERSION=$(echo "${{ github.ref }}" | sed 's|refs/tags/||' | sed 's/^v//') + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "PACKAGE_NAME=${{ env.PACKAGE_NAME }}" >> $GITHUB_ENV + + - name: Find Package Files + id: find_packages + run: | + # Find all .deb files in artifacts + ARM64_PKG=$(find artifacts/ -name "*arm64*.deb" -o -path "*/arm64-package/*.deb" | head -1) + ARMHF_PKG=$(find artifacts/ -name "*armhf*.deb" -o -path "*/armhf-package/*.deb" | head -1) + + if [ -n "$ARM64_PKG" ]; then + ARM64_BASENAME=$(basename "$ARM64_PKG") + echo "arm64_package=$ARM64_PKG" >> $GITHUB_OUTPUT + echo "arm64_basename=$ARM64_BASENAME" >> $GITHUB_OUTPUT + echo "Found ARM64 package: $ARM64_PKG" + else + echo "ERROR: ARM64 package not found!" + find artifacts/ -type f + exit 1 + fi + + if [ -n "$ARMHF_PKG" ]; then + ARMHF_BASENAME=$(basename "$ARMHF_PKG") + echo "armhf_package=$ARMHF_PKG" >> $GITHUB_OUTPUT + echo "armhf_basename=$ARMHF_BASENAME" >> $GITHUB_OUTPUT + echo "Found ARMHF package: $ARMHF_PKG" + else + echo "ERROR: ARMHF package not found!" + find artifacts/ -type f + exit 1 + fi + + - name: Create Release with Assets + uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, - # which include a `upload_url`. See this blog post for more info: - # https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ${{ github.workspace }}/build/${{ env.armhf__filename__ }} - asset_name: ${{ env.armhf__filename__ }} - asset_content_type: application/vnd.debian.binary-package + tag_name: ${{ github.ref_name }} + name: Release ${{ github.ref_name }} + draft: false + prerelease: false + body: | + ARM Cross-Platform Release v${{ env.VERSION }} + + This release contains Debian packages for both ARM architectures: + - **ARM64** (64-bit): For PocketBeagle 2 and other ARMv8/AArch64 devices + - **ARMHF** (32-bit): For ARMv7 devices with hard-float ABI + + ### Installation + ```bash + # ARM64 devices + sudo dpkg -i ${{ steps.find_packages.outputs.arm64_basename }} + + # ARMHF devices + sudo dpkg -i ${{ steps.find_packages.outputs.armhf_basename }} + ``` + + ### Files + - `${{ steps.find_packages.outputs.arm64_basename }}` (ARM64/64-bit) + - `${{ steps.find_packages.outputs.armhf_basename }}` (ARMHF/32-bit) + files: | + ${{ steps.find_packages.outputs.arm64_package }} + ${{ steps.find_packages.outputs.armhf_package }} + + - name: Verify Release Creation + run: | + echo "Release created successfully!" + echo "ARM64 package: ${{ steps.find_packages.outputs.arm64_basename }}" + echo "ARMHF package: ${{ steps.find_packages.outputs.armhf_basename }}" \ No newline at end of file diff --git a/.github/workflows/pull_request_build.yml b/.github/workflows/pull_request_build.yml index 904de2a..0f83494 100644 --- a/.github/workflows/pull_request_build.yml +++ b/.github/workflows/pull_request_build.yml @@ -7,21 +7,18 @@ on: - main jobs: - armhf: - name: Test build - runs-on: ubuntu-latest + test-build: + name: Test Build + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v2 - - name: Install Deps + uses: actions/checkout@v4 + + - name: Build with Docker run: | - sudo apt install gcc-12 g++-12 libnm-dev libglib2.0-dev -y - - name: Build - run: | - cd ${{ github.workspace }} - mkdir build - cd build - cmake .. -DCMAKE_CXX_COMPILER=$(which g++-12) \ - -DCMAKE_C_COMPILER=$(which gcc-12) - make -j$(nproc) + docker run --rm \ + -v "$(pwd):/build" \ + -w /build \ + ubuntu:22.04 \ + bash -c "apt-get update && apt-get install -y --no-install-recommends build-essential cmake pkg-config libnm-dev libglib2.0-dev git ca-certificates && rm -rf /var/lib/apt/lists/* && mkdir -p build && cd build && cmake .. && make -j\$(nproc)" \ No newline at end of file diff --git a/src/ui/panel/gpio/gpio_impl.cpp b/src/ui/panel/gpio/gpio_impl.cpp index bb3c065..2981d8e 100644 --- a/src/ui/panel/gpio/gpio_impl.cpp +++ b/src/ui/panel/gpio/gpio_impl.cpp @@ -1,5 +1,15 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for usleep +#include // for debug #include "ftxui/component/component.hpp" #include "ftxui/dom/elements.hpp" #include "ui/panel/panel.hpp" @@ -32,10 +42,12 @@ const std::vector edgeEntries = { class Gpio : public ComponentBase { public: - Gpio(std::string path, int* tab, int* next, int* limit) : path_(path) { + Gpio(std::string gpio_num, std::string label, int* tab, int* next, int* limit) + : gpio_num_(gpio_num), label_(label) { limit_ = limit; tab_ = tab; next_ = next; + path_ = "/sys/class/gpio/gpio" + gpio_num; Fetch(); BuildUI(); } @@ -48,11 +60,37 @@ class Gpio : public ComponentBase { private: void Fetch() { - std::ifstream(path_ + "/label") >> label_; - std::ifstream(path_ + "/edge") >> edge_; - std::ifstream(path_ + "/direction") >> direction_; - std::ifstream(path_ + "/value") >> value_; - std::ifstream(path_ + "/active_low") >> active_low_; + // Read direction + std::ifstream direction_file(path_ + "/direction"); + if (direction_file.is_open()) { + std::getline(direction_file, direction_); + } else { + direction_ = "in"; + } + + // Read edge + std::ifstream edge_file(path_ + "/edge"); + if (edge_file.is_open()) { + std::getline(edge_file, edge_); + } else { + edge_ = "none"; + } + + // Read value + std::ifstream value_file(path_ + "/value"); + if (value_file.is_open()) { + std::getline(value_file, value_); + } else { + value_ = "0"; + } + + // Read active_low + std::ifstream active_low_file(path_ + "/active_low"); + if (active_low_file.is_open()) { + std::getline(active_low_file, active_low_); + } else { + active_low_ = "0"; + } } void StoreDirection(std::string direction) { @@ -126,6 +164,17 @@ class Gpio : public ComponentBase { valToggle = Menu(&valueEntries, &valTog, valueToggleOpt); activeToggle = Menu(&activeEntries, &activeTog, activeToggleOpt); edgeToggle = Menu(&edgeEntries, &edgeTog, edgeToggleOpt); + + // Set toggle states based on current values + ioTog = (direction_ == "out") ? 1 : 0; + valTog = (value_ == "1") ? 1 : 0; + activeTog = (active_low_ == "1") ? 1 : 0; + + if (edge_ == "rising") edgeTog = 0; + else if (edge_ == "falling") edgeTog = 1; + else if (edge_ == "both") edgeTog = 2; + else edgeTog = 3; + Component actions = Renderer( Container::Vertical({ ioToggle, @@ -135,11 +184,11 @@ class Gpio : public ComponentBase { }), [&] { return vbox({ - text(label() + " Status "), - hbox(text(" * Direction : "), text(direction())), - hbox(text(" * Value : "), text(value())), - hbox(text(" * Active Low : "), text(active_low())), - hbox(text(" * Edge : "), text(edge())), + text(label_ + " (GPIO " + gpio_num_ + ") Status "), + hbox(text(" * Direction : "), text(direction_)), + hbox(text(" * Value : "), text(value_)), + hbox(text(" * Active Low : "), text(active_low_)), + hbox(text(" * Edge : "), text(edge_)), text(" Actions "), hbox(text(" * Direction : "), ioToggle->Render()), hbox(text(" * Value : "), valToggle->Render()), @@ -154,13 +203,13 @@ class Gpio : public ComponentBase { [&] { (*next_)--; if (*next_ < 0) { - *next_ = 0; + *next_ = (*limit_ > 0) ? *limit_ - 1 : 0; } }), Button("Next", [&] { (*next_)++; - if (*limit_ < *next_) { + if (*next_ >= *limit_) { *next_ = 0; } }), @@ -168,6 +217,7 @@ class Gpio : public ComponentBase { } std::string path_; + std::string gpio_num_; std::string label_; std::string direction_; std::string edge_; @@ -176,40 +226,322 @@ class Gpio : public ComponentBase { int* tab_; int* next_; int* limit_; - int ioTog; - int activeTog; - int valTog; - int edgeTog; + int ioTog = 0; + int activeTog = 0; + int valTog = 0; + int edgeTog = 3; Component ioToggle; Component activeToggle; Component valToggle; Component edgeToggle; }; +// Helper function to execute shell command and get output +std::string exec(const char* cmd) { + std::array buffer; + std::string result; + std::unique_ptr pipe(popen(cmd, "r"), pclose); + if (!pipe) { + return ""; + } + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + return result; +} + class GPIOImpl : public PanelBase { public: GPIOImpl() { BuildUI(); } std::string Title() override { return "GPIO"; } private: + // Get GPIO pin information using gpioinfo - ONLY P pins + std::vector> getGpioPins() { + std::vector> pins; + + // First try with sudo + std::string output = exec("sudo gpioinfo 2>/dev/null"); + + // If empty, try without sudo + if (output.empty()) { + output = exec("gpioinfo 2>/dev/null"); + } + + if (output.empty()) { + std::cerr << "ERROR: gpioinfo command failed or returned empty output\n"; + return pins; + } + + std::istringstream iss(output); + std::string line; + + // Map to store chip number to base GPIO number + std::map chip_to_base; + + // First pass: Collect chip base numbers from /sys + for (int chip_num = 0; chip_num < 10; chip_num++) { + std::string base_path = "/sys/class/gpio/gpiochip" + std::to_string(chip_num) + "/base"; + std::ifstream base_file(base_path); + if (base_file.is_open()) { + std::string base_str; + if (std::getline(base_file, base_str)) { + try { + int base = std::stoi(base_str); + chip_to_base[chip_num] = base; + std::cerr << "DEBUG: Chip " << chip_num << " base = " << base << "\n"; + } catch (...) { + // Ignore conversion errors + } + } + } + } + + int current_chip = -1; + bool reading_chip = false; + + // Second pass: Parse gpioinfo output + while (std::getline(iss, line)) { + // Trim leading/trailing whitespace + line.erase(0, line.find_first_not_of(" \t")); + line.erase(line.find_last_not_of(" \t") + 1); + + // Check for chip header + if (line.find("gpiochip") == 0) { + // Extract chip number from "gpiochipX" + std::string chip_str = line.substr(8); // Skip "gpiochip" + size_t space_pos = chip_str.find_first_of(" :"); + if (space_pos != std::string::npos) { + chip_str = chip_str.substr(0, space_pos); + } + + try { + current_chip = std::stoi(chip_str); + std::cerr << "DEBUG: Found chip " << current_chip << "\n"; + reading_chip = true; + } catch (...) { + current_chip = -1; + reading_chip = false; + } + continue; + } + + // Skip if we're not reading a chip or line doesn't contain "line" + if (!reading_chip || line.find("line") == std::string::npos) { + continue; + } + + // Parse line: "line X:" + size_t line_pos = line.find("line"); + if (line_pos == std::string::npos) continue; + + // Extract line number + size_t num_start = line_pos + 4; + while (num_start < line.size() && (line[num_start] == ' ' || line[num_start] == '\t')) { + num_start++; + } + + size_t num_end = num_start; + while (num_end < line.size() && isdigit(line[num_end])) { + num_end++; + } + + if (num_end == num_start) continue; + + try { + int line_num = std::stoi(line.substr(num_start, num_end - num_start)); + + // Now look for P pin in the line + // First check in quotes + std::string pin_name = ""; + size_t quote_start = line.find('\"'); + if (quote_start != std::string::npos) { + size_t quote_end = line.find('\"', quote_start + 1); + if (quote_end != std::string::npos) { + std::string quoted = line.substr(quote_start + 1, quote_end - quote_start - 1); + + // Check if quoted text starts with P followed by a digit + if (quoted.size() >= 2 && + (quoted[0] == 'P' || quoted[0] == 'p') && + isdigit(quoted[1])) { + pin_name = quoted; + } + } + } + + // If not found in quotes, search entire line for P followed by digit + if (pin_name.empty()) { + // Search for P or p followed by digit + for (size_t i = 0; i < line.size(); i++) { + if ((line[i] == 'P' || line[i] == 'p') && + i + 1 < line.size() && isdigit(line[i + 1])) { + + // Extract the pin name + size_t start = i; + size_t end = i + 1; + while (end < line.size() && + (isdigit(line[end]) || line[end] == '.' || + line[end] == '_' || line[end] == '(')) { + end++; + } + + pin_name = line.substr(start, end - start); + + // Clean up trailing special characters + while (!pin_name.empty() && + (pin_name.back() == '(' || pin_name.back() == ' ' || + pin_name.back() == '\t')) { + pin_name.pop_back(); + } + + break; + } + } + } + + // If we found a P pin, add it + if (!pin_name.empty()) { + // Calculate GPIO number + int gpio_num = -1; + + if (chip_to_base.find(current_chip) != chip_to_base.end()) { + gpio_num = chip_to_base[current_chip] + line_num; + } else { + // Estimate: assume chips are numbered sequentially with 32 lines each + // This is a common pattern for many boards + gpio_num = current_chip * 32 + line_num; + } + + std::cerr << "DEBUG: Found P pin - Chip " << current_chip + << ", Line " << line_num << ", GPIO " << gpio_num + << ", Name: " << pin_name << "\n"; + + pins.push_back({gpio_num, pin_name}); + } + + } catch (...) { + // Skip lines with conversion errors + continue; + } + } + + // Sort by GPIO number + std::sort(pins.begin(), pins.end(), + [](const auto& a, const auto& b) { return a.first < b.first; }); + + // Remove duplicates + pins.erase(std::unique(pins.begin(), pins.end(), + [](const auto& a, const auto& b) { return a.first == b.first; }), + pins.end()); + + std::cerr << "DEBUG: Total P pins found: " << pins.size() << "\n"; + for (const auto& pin : pins) { + std::cerr << " GPIO " << pin.first << ": " << pin.second << "\n"; + } + + return pins; + } + + // Alternative method if gpioinfo doesn't work + std::vector> getGpioPinsFallback() { + std::vector> pins; + + // Look for already exported GPIOs + std::string cmd = "ls -d /sys/class/gpio/gpio[0-9]* 2>/dev/null"; + std::string result = exec(cmd.c_str()); + + if (!result.empty()) { + std::istringstream iss(result); + std::string gpio_path; + while (std::getline(iss, gpio_path)) { + // Extract GPIO number + size_t gpio_pos = gpio_path.find("gpio"); + if (gpio_pos != std::string::npos) { + std::string num_str; + for (size_t i = gpio_pos + 4; i < gpio_path.size() && isdigit(gpio_path[i]); i++) { + num_str += gpio_path[i]; + } + + if (!num_str.empty()) { + try { + int gpio_num = std::stoi(num_str); + pins.push_back({gpio_num, "GPIO" + num_str}); + } catch (...) { + // Skip + } + } + } + } + } + + return pins; + } + + // Export a GPIO if not already exported + bool exportGpio(int gpio_num) { + std::string gpio_path = "/sys/class/gpio/gpio" + std::to_string(gpio_num); + + // Check if already exported + if (std::filesystem::exists(gpio_path)) { + return true; + } + + // Try to export + std::ofstream export_file("/sys/class/gpio/export"); + if (export_file.is_open()) { + export_file << gpio_num; + export_file.close(); + + // Wait for export to complete + usleep(50000); // 50ms + + return std::filesystem::exists(gpio_path); + } + + return false; + } + void BuildUI() { + // Get list of GPIO pins from gpioinfo + auto gpio_pins = getGpioPins(); + + // If no P pins found, try fallback + if (gpio_pins.empty()) { + std::cerr << "WARNING: No P pins found with gpioinfo, trying fallback\n"; + gpio_pins = getGpioPinsFallback(); + } + MenuOption menuOpt; menuOpt.on_enter = [&] { tab = 1; }; gpio_menu = Menu(&gpio_names, &selected, menuOpt); gpio_individual = Container::Vertical({}, &selected); - for (const auto& it : - std::filesystem::directory_iterator("/sys/class/gpio/")) { - std::string gpio_path = it.path(); - if (gpio_path.find("gpiochip") == std::string::npos && - gpio_path.find("gpio") != std::string::npos) { - auto gpio = std::make_shared(gpio_path, &tab, &selected, &limit); - if (gpio->label().find("P") != std::string::npos) { + + if (!gpio_pins.empty()) { + // Process pins from gpioinfo + for (const auto& pin : gpio_pins) { + int gpio_num = pin.first; + std::string label = pin.second; + + // Try to export the GPIO + if (exportGpio(gpio_num)) { + std::string gpio_num_str = std::to_string(gpio_num); + auto gpio = std::make_shared(gpio_num_str, label, &tab, &selected, &limit); children_.push_back(gpio); gpio_individual->Add(gpio); limit++; + } else { + std::cerr << "WARNING: Failed to export GPIO " << gpio_num << " (" << label << ")\n"; } } } + + // If no pins found, show error message + if (children_.empty()) { + error_message_ = "No GPIO pins found. Make sure:\n" + "1. gpiod is installed (sudo apt-get install gpiod)\n" + "2. Run with sudo privileges\n" + "3. GPIOs are accessible"; + } Add(Container::Tab( { @@ -225,16 +557,50 @@ class GPIOImpl : public PanelBase { gpio_names.push_back(child->label()); } - int i = 0; - if (tab == 1) - for (const auto& child : children_) { - if (i == selected) { - return child->Render(); + if (tab == 1) { + if (children_.empty()) { + return window(text("GPIO Control"), + vbox({ + text("No GPIO pins found."), + text(""), + text("Possible issues:"), + text("1. gpiod not installed - run: sudo apt-get install gpiod"), + text("2. Need sudo privileges - run program with sudo"), + text("3. GPIOs not exported"), + text(""), + text("Debug info:"), + text("Tried to find P pins from gpioinfo"), + text("Check terminal for debug output") + }) | center | flex); + } + + if (selected >= 0 && selected < static_cast(children_.size())) { + return children_[selected]->Render(); + } else { + selected = 0; + if (!children_.empty()) { + return children_[0]->Render(); } - i++; } + } + + // Display GPIO menu + if (children_.empty()) { + return window(text("GPIO Menu"), + vbox({ + text("Available GPIOs: 0"), + text(""), + text("No P pins detected."), + text("Looking for pins starting with 'P' (e.g., P8.10, P9.11)"), + text(""), + text("Check if:"), + text("1. Board has P pins"), + text("2. gpioinfo shows P pins"), + text("3. Run with: sudo gpioinfo | grep P") + }) | center | flex); + } - return window(text("GPIO Menu"), + return window(text("GPIO Menu - " + std::to_string(children_.size()) + " P pins"), gpio_menu->Render() | vscroll_indicator | frame | flex); } @@ -242,6 +608,7 @@ class GPIOImpl : public PanelBase { std::vector gpio_names; Component gpio_menu; Component gpio_individual; + std::string error_message_; int selected = 0; int tab = 0; int limit = 0; @@ -252,4 +619,4 @@ Panel GPIO() { return Make(); } } // namespace panel -} // namespace ui +} // namespace ui \ No newline at end of file