Merge branch 'use_boards_sd_card'
This commit is contained in:
commit
029c9bbf0e
17 changed files with 8451 additions and 9447 deletions
16
.github/workflows/daily-update.yml
vendored
16
.github/workflows/daily-update.yml
vendored
|
|
@ -39,18 +39,26 @@ jobs:
|
|||
run: |
|
||||
git config --global user.name "GitHub Actions Bot"
|
||||
git config --global user.email "actions@github.com"
|
||||
git submodule update --remote --recursive
|
||||
git submodule init
|
||||
git submodule update --recursive
|
||||
cd Wippersnapper_Boards
|
||||
git reset --hard origin/rp2040_datalogger_feather
|
||||
cd ../Wippersnapper_Components
|
||||
git reset --hard origin/main
|
||||
cd ..
|
||||
git add Wippersnapper_Boards Wippersnapper_Components
|
||||
git diff --staged --quiet || git commit -m "Update submodules to latest versions [skip ci]"
|
||||
|
||||
- name: Run conversion script
|
||||
run: |
|
||||
pip install requests beautifulsoup4
|
||||
pip install requests
|
||||
python convert_all_wippersnapper_definitions.py
|
||||
|
||||
- name: Commit changes if any
|
||||
run: |
|
||||
git add wippersnapper_boards.json wippersnapper_components.json
|
||||
git add wippersnapper_boards.json wippersnapper_components.json wippersnapper_boards.js wippersnapper_components.js firmware-data.js
|
||||
git diff --staged --quiet || git commit -m "Auto-update Wippersnapper definitions [skip ci]"
|
||||
git push origin main
|
||||
git push origin ${{ github.ref_name }}
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 86fd50d32b66abe1fbf8c9a1926f310308dc5bea
|
||||
Subproject commit 66d399578444320a039514f1a41160a0c2357212
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 848a7bc7fef4eb4701236ba2a0af4fb6f268e19d
|
||||
Subproject commit 5b154e5389df0a9f10089057a15d5c2be82d6913
|
||||
|
|
@ -2,6 +2,7 @@ import os
|
|||
import time
|
||||
from convert_boards_to_json import convert_boards_to_json
|
||||
from convert_components_to_json import convert_components_to_json
|
||||
from fetch_latest_release_info_and_assets import main as fetch_latest_release_info_and_assets
|
||||
|
||||
def main():
|
||||
"""
|
||||
|
|
@ -19,6 +20,9 @@ def main():
|
|||
# Convert components
|
||||
print("\n--- Converting Components ---")
|
||||
components = convert_components_to_json()
|
||||
|
||||
# fetch latest release info and assets
|
||||
release_info = fetch_latest_release_info_and_assets()
|
||||
|
||||
# Print summary
|
||||
elapsed_time = time.time() - start_time
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import os
|
|||
import json
|
||||
import glob
|
||||
from pathlib import Path
|
||||
from convert_components_to_json import get_image_from_adafruit_product_url
|
||||
|
||||
# Base directory for the boards
|
||||
BOARDS_DIR = r"./Wippersnapper_Boards/boards"
|
||||
|
|
@ -11,57 +12,7 @@ def add_custom_board_definitions(boards):
|
|||
"""
|
||||
Add custom board definitions for boards that don't have definition.json files
|
||||
"""
|
||||
# Adafruit Feather RP2040 Adalogger
|
||||
boards["feather-rp2040-adalogger"] = {
|
||||
"boardName": "Adafruit Feather RP2040 Adalogger",
|
||||
"mcuName": "RP2040",
|
||||
"referenceVoltage": 3.3,
|
||||
"displayName": "Adafruit Feather RP2040 Adalogger",
|
||||
"vendor": "Adafruit",
|
||||
"productURL": "https://www.adafruit.com/product/5980",
|
||||
"documentationURL": "https://learn.adafruit.com/adafruit-feather-rp2040-adalogger",
|
||||
"installMethod": "uf2",
|
||||
"totalGPIOPins": 22,
|
||||
"totalAnalogPins": 4,
|
||||
"pins": [
|
||||
{"number": 0, "name": "D0", "displayName": "GPIO0", "hasPWM": True, "hasServo": True},
|
||||
{"number": 1, "name": "D1", "displayName": "GPIO1", "hasPWM": True, "hasServo": True},
|
||||
{"number": 2, "name": "D2", "displayName": "GPIO2", "hasPWM": True, "hasServo": True},
|
||||
{"number": 3, "name": "D3", "displayName": "GPIO3", "hasPWM": True, "hasServo": True},
|
||||
{"number": 4, "name": "D4", "displayName": "GPIO4", "hasPWM": True, "hasServo": True},
|
||||
{"number": 5, "name": "D5", "displayName": "GPIO5", "hasPWM": True, "hasServo": True},
|
||||
{"number": 6, "name": "D6", "displayName": "GPIO6", "hasPWM": True, "hasServo": True},
|
||||
{"number": 7, "name": "D7", "displayName": "GPIO7", "hasPWM": True, "hasServo": True},
|
||||
{"number": 8, "name": "D8", "displayName": "GPIO8", "hasPWM": True, "hasServo": True},
|
||||
{"number": 9, "name": "D9", "displayName": "GPIO9", "hasPWM": True, "hasServo": True},
|
||||
{"number": 10, "name": "D10", "displayName": "GPIO10", "hasPWM": True, "hasServo": True},
|
||||
{"number": 11, "name": "D11", "displayName": "GPIO11", "hasPWM": True, "hasServo": True},
|
||||
{"number": 12, "name": "D12", "displayName": "GPIO12", "hasPWM": True, "hasServo": True},
|
||||
{"number": 13, "name": "D13", "displayName": "GPIO13 (LED)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 16, "name": "D16", "displayName": "GPIO16 (MOSI)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 17, "name": "D17", "displayName": "GPIO17 (A1/SCK/SD CS)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 18, "name": "D18", "displayName": "GPIO18 (MISO)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 19, "name": "D19", "displayName": "GPIO19", "hasPWM": True, "hasServo": True},
|
||||
{"number": 20, "name": "D20", "displayName": "GPIO20", "hasPWM": True, "hasServo": True},
|
||||
{"number": 24, "name": "D24", "displayName": "GPIO24 (A0)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 25, "name": "D25", "displayName": "GPIO25 (A3)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 29, "name": "D29", "displayName": "GPIO29 (A2)", "hasPWM": True, "hasServo": True}
|
||||
],
|
||||
"analogPins": [
|
||||
{"name": "A0", "displayName": "GPIO24 (A0)", "direction": "INPUT"},
|
||||
{"name": "A1", "displayName": "GPIO17 (A1/SD CS)", "direction": "INPUT"},
|
||||
{"name": "A2", "displayName": "GPIO29 (A2)", "direction": "INPUT"},
|
||||
{"name": "A3", "displayName": "GPIO25 (A3)", "direction": "INPUT"}
|
||||
],
|
||||
"defaultI2C": {
|
||||
"i2cPortId": 0,
|
||||
"SCL": "D3",
|
||||
"SDA": "D2"
|
||||
},
|
||||
"sdCardCS": 17, # GPIO17/A1 is the SD card CS pin
|
||||
"rtcType": "PCF8523", # Built-in RTC chip
|
||||
"image": None
|
||||
}
|
||||
|
||||
# Generic ESP32-S2 based board
|
||||
boards["generic-esp32-s2"] = {
|
||||
"boardName": "Generic ESP32-S2",
|
||||
|
|
@ -72,6 +23,9 @@ def add_custom_board_definitions(boards):
|
|||
"productURL": "",
|
||||
"documentationURL": "",
|
||||
"installMethod": "uf2",
|
||||
"installBoardName": "",
|
||||
"rtc": None,
|
||||
"sdCardCS": None,
|
||||
"totalGPIOPins": 43,
|
||||
"totalAnalogPins": 20,
|
||||
"pins": [
|
||||
|
|
@ -123,12 +77,12 @@ def add_custom_board_definitions(boards):
|
|||
],
|
||||
"defaultI2C": {
|
||||
"i2cPortId": 0,
|
||||
"SCL": "D9",
|
||||
"SDA": "D8"
|
||||
"SCL": 9,
|
||||
"SDA": 8
|
||||
},
|
||||
"image": None
|
||||
}
|
||||
|
||||
|
||||
# Generic ESP32-S3 based board
|
||||
boards["generic-esp32-s3"] = {
|
||||
"boardName": "Generic ESP32-S3",
|
||||
|
|
@ -139,6 +93,9 @@ def add_custom_board_definitions(boards):
|
|||
"productURL": "",
|
||||
"documentationURL": "",
|
||||
"installMethod": "uf2",
|
||||
"installBoardName": "",
|
||||
"rtc": None,
|
||||
"sdCardCS": None,
|
||||
"totalGPIOPins": 48,
|
||||
"totalAnalogPins": 20,
|
||||
"pins": [
|
||||
|
|
@ -192,12 +149,12 @@ def add_custom_board_definitions(boards):
|
|||
],
|
||||
"defaultI2C": {
|
||||
"i2cPortId": 0,
|
||||
"SCL": "D9",
|
||||
"SDA": "D8"
|
||||
"SCL": 9,
|
||||
"SDA": 8
|
||||
},
|
||||
"image": None
|
||||
}
|
||||
|
||||
|
||||
# Generic RP2040 based board
|
||||
boards["generic-rp2040"] = {
|
||||
"boardName": "Generic RP2040",
|
||||
|
|
@ -208,6 +165,9 @@ def add_custom_board_definitions(boards):
|
|||
"productURL": "",
|
||||
"documentationURL": "",
|
||||
"installMethod": "uf2",
|
||||
"installBoardName": "",
|
||||
"rtc": None,
|
||||
"sdCardCS": None,
|
||||
"totalGPIOPins": 30,
|
||||
"totalAnalogPins": 4,
|
||||
"pins": [
|
||||
|
|
@ -250,24 +210,27 @@ def add_custom_board_definitions(boards):
|
|||
],
|
||||
"defaultI2C": {
|
||||
"i2cPortId": 0,
|
||||
"SCL": "D3",
|
||||
"SDA": "D2"
|
||||
"SCL": 3,
|
||||
"SDA": 2
|
||||
},
|
||||
"image": None
|
||||
}
|
||||
|
||||
|
||||
# Generic RP23xx based board
|
||||
boards["generic-rp23xx"] = {
|
||||
"boardName": "Generic RP23xx",
|
||||
"mcuName": "RP23xx",
|
||||
"referenceVoltage": 3.3,
|
||||
"displayName": "Generic RP23xx Board",
|
||||
"displayName": "Generic RP2350/RP2354 board",
|
||||
"vendor": "Generic",
|
||||
"productURL": "",
|
||||
"documentationURL": "",
|
||||
"installMethod": "uf2",
|
||||
"totalGPIOPins": 30,
|
||||
"totalAnalogPins": 4,
|
||||
"installBoardName": "",
|
||||
"rtc": None,
|
||||
"sdCardCS": None,
|
||||
"totalGPIOPins": 48,
|
||||
"totalAnalogPins": 8,
|
||||
"pins": [
|
||||
{"number": 0, "name": "D0", "displayName": "GPIO0", "hasPWM": True, "hasServo": True},
|
||||
{"number": 1, "name": "D1", "displayName": "GPIO1", "hasPWM": True, "hasServo": True},
|
||||
|
|
@ -295,84 +258,49 @@ def add_custom_board_definitions(boards):
|
|||
{"number": 23, "name": "D23", "displayName": "GPIO23", "hasPWM": True, "hasServo": True},
|
||||
{"number": 24, "name": "D24", "displayName": "GPIO24", "hasPWM": True, "hasServo": True},
|
||||
{"number": 25, "name": "D25", "displayName": "GPIO25", "hasPWM": True, "hasServo": True},
|
||||
{"number": 26, "name": "D26", "displayName": "GPIO26", "hasPWM": True, "hasServo": True},
|
||||
{"number": 27, "name": "D27", "displayName": "GPIO27", "hasPWM": True, "hasServo": True},
|
||||
{"number": 28, "name": "D28", "displayName": "GPIO28", "hasPWM": True, "hasServo": True},
|
||||
{"number": 29, "name": "D29", "displayName": "GPIO29", "hasPWM": True, "hasServo": True}
|
||||
{"number": 26, "name": "A26", "displayName": "GPIO26 (ADC0)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 27, "name": "A27", "displayName": "GPIO27 (ADC1)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 28, "name": "A28", "displayName": "GPIO28 (ADC2)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 29, "name": "A29", "displayName": "GPIO29 (ADC3)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 30, "name": "D30", "displayName": "GPIO30", "hasPWM": True, "hasServo": True},
|
||||
{"number": 31, "name": "D31", "displayName": "GPIO31", "hasPWM": True, "hasServo": True},
|
||||
{"number": 32, "name": "D32", "displayName": "GPIO32", "hasPWM": True, "hasServo": True},
|
||||
{"number": 33, "name": "D33", "displayName": "GPIO33", "hasPWM": True, "hasServo": True},
|
||||
{"number": 34, "name": "D34", "displayName": "GPIO34", "hasPWM": True, "hasServo": True},
|
||||
{"number": 35, "name": "D35", "displayName": "GPIO35", "hasPWM": True, "hasServo": True},
|
||||
{"number": 36, "name": "D36", "displayName": "GPIO36", "hasPWM": True, "hasServo": True},
|
||||
{"number": 37, "name": "D37", "displayName": "GPIO37", "hasPWM": True, "hasServo": True},
|
||||
{"number": 38, "name": "D38", "displayName": "GPIO38", "hasPWM": True, "hasServo": True},
|
||||
{"number": 39, "name": "D39", "displayName": "GPIO39", "hasPWM": True, "hasServo": True},
|
||||
{"number": 40, "name": "A40", "displayName": "GPIO40 (ADC0)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 41, "name": "A41", "displayName": "GPIO41 (ADC1)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 42, "name": "A42", "displayName": "GPIO42 (ADC2)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 43, "name": "A43", "displayName": "GPIO43 (ADC3)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 44, "name": "D44", "displayName": "GPIO44", "hasPWM": True, "hasServo": True},
|
||||
{"number": 45, "name": "D45", "displayName": "GPIO45", "hasPWM": True, "hasServo": True},
|
||||
{"number": 46, "name": "D46", "displayName": "GPIO46", "hasPWM": True, "hasServo": True},
|
||||
{"number": 47, "name": "D47", "displayName": "GPIO47", "hasPWM": True, "hasServo": True},
|
||||
{"number": 48, "name": "D48", "displayName": "GPIO48", "hasPWM": True, "hasServo": True}
|
||||
],
|
||||
"analogPins": [
|
||||
{"name": "A0", "displayName": "ADC0", "direction": "INPUT"},
|
||||
{"name": "A1", "displayName": "ADC1", "direction": "INPUT"},
|
||||
{"name": "A2", "displayName": "ADC2", "direction": "INPUT"},
|
||||
{"name": "A3", "displayName": "ADC3", "direction": "INPUT"}
|
||||
{"name": "A26", "displayName": "GPIO26 (ADC0)", "direction": "INPUT"},
|
||||
{"name": "A27", "displayName": "GPIO27 (ADC1)", "direction": "INPUT"},
|
||||
{"name": "A28", "displayName": "GPIO28 (ADC2)", "direction": "INPUT"},
|
||||
{"name": "A29", "displayName": "GPIO29 (ADC3)", "direction": "INPUT"},
|
||||
{"name": "A40", "displayName": "GPIO40 (ADC0)", "direction": "INPUT"},
|
||||
{"name": "A41", "displayName": "GPIO41 (ADC1)", "direction": "INPUT"},
|
||||
{"name": "A42", "displayName": "GPIO42 (ADC2)", "direction": "INPUT"},
|
||||
{"name": "A43", "displayName": "GPIO43 (ADC3)", "direction": "INPUT"}
|
||||
],
|
||||
"defaultI2C": {
|
||||
"i2cPortId": 0,
|
||||
"SCL": "D3",
|
||||
"SDA": "D2"
|
||||
"SCL": 3,
|
||||
"SDA": 2
|
||||
},
|
||||
"image": None
|
||||
}
|
||||
|
||||
# Adafruit Metro RP2350 with SD card
|
||||
boards["metro-rp2350-sd"] = {
|
||||
"boardName": "Adafruit Metro RP2350",
|
||||
"mcuName": "RP2350",
|
||||
"referenceVoltage": 3.3,
|
||||
"displayName": "Adafruit Metro RP2350 (with SD card)",
|
||||
"vendor": "Adafruit",
|
||||
"productURL": "https://www.adafruit.com/product/5786",
|
||||
"documentationURL": "https://learn.adafruit.com/adafruit-metro-rp2350",
|
||||
"installMethod": "uf2",
|
||||
"totalGPIOPins": 30,
|
||||
"totalAnalogPins": 4,
|
||||
"pins": [
|
||||
{"number": 0, "name": "D0", "displayName": "GPIO0", "hasPWM": True, "hasServo": True},
|
||||
{"number": 1, "name": "D1", "displayName": "GPIO1", "hasPWM": True, "hasServo": True},
|
||||
{"number": 2, "name": "D2", "displayName": "GPIO2", "hasPWM": True, "hasServo": True},
|
||||
{"number": 3, "name": "D3", "displayName": "GPIO3", "hasPWM": True, "hasServo": True},
|
||||
{"number": 4, "name": "D4", "displayName": "GPIO4", "hasPWM": True, "hasServo": True},
|
||||
{"number": 5, "name": "D5", "displayName": "GPIO5", "hasPWM": True, "hasServo": True},
|
||||
{"number": 6, "name": "D6", "displayName": "GPIO6", "hasPWM": True, "hasServo": True},
|
||||
{"number": 7, "name": "D7", "displayName": "GPIO7", "hasPWM": True, "hasServo": True},
|
||||
{"number": 8, "name": "D8", "displayName": "GPIO8", "hasPWM": True, "hasServo": True},
|
||||
{"number": 9, "name": "D9", "displayName": "GPIO9", "hasPWM": True, "hasServo": True},
|
||||
{"number": 10, "name": "D10", "displayName": "GPIO10 (SD CS)", "hasPWM": True, "hasServo": True},
|
||||
{"number": 11, "name": "D11", "displayName": "GPIO11", "hasPWM": True, "hasServo": True},
|
||||
{"number": 12, "name": "D12", "displayName": "GPIO12", "hasPWM": True, "hasServo": True},
|
||||
{"number": 13, "name": "D13", "displayName": "GPIO13", "hasPWM": True, "hasServo": True},
|
||||
{"number": 14, "name": "D14", "displayName": "GPIO14", "hasPWM": True, "hasServo": True},
|
||||
{"number": 15, "name": "D15", "displayName": "GPIO15", "hasPWM": True, "hasServo": True},
|
||||
{"number": 16, "name": "D16", "displayName": "GPIO16", "hasPWM": True, "hasServo": True},
|
||||
{"number": 17, "name": "D17", "displayName": "GPIO17", "hasPWM": True, "hasServo": True},
|
||||
{"number": 18, "name": "D18", "displayName": "GPIO18", "hasPWM": True, "hasServo": True},
|
||||
{"number": 19, "name": "D19", "displayName": "GPIO19", "hasPWM": True, "hasServo": True},
|
||||
{"number": 20, "name": "D20", "displayName": "GPIO20", "hasPWM": True, "hasServo": True},
|
||||
{"number": 21, "name": "D21", "displayName": "GPIO21", "hasPWM": True, "hasServo": True},
|
||||
{"number": 22, "name": "D22", "displayName": "GPIO22", "hasPWM": True, "hasServo": True},
|
||||
{"number": 23, "name": "D23", "displayName": "GPIO23", "hasPWM": True, "hasServo": True},
|
||||
{"number": 24, "name": "D24", "displayName": "GPIO24", "hasPWM": True, "hasServo": True},
|
||||
{"number": 25, "name": "D25", "displayName": "GPIO25", "hasPWM": True, "hasServo": True},
|
||||
{"number": 26, "name": "D26", "displayName": "GPIO26", "hasPWM": True, "hasServo": True},
|
||||
{"number": 27, "name": "D27", "displayName": "GPIO27", "hasPWM": True, "hasServo": True},
|
||||
{"number": 28, "name": "D28", "displayName": "GPIO28", "hasPWM": True, "hasServo": True},
|
||||
{"number": 29, "name": "D29", "displayName": "GPIO29", "hasPWM": True, "hasServo": True}
|
||||
],
|
||||
"analogPins": [
|
||||
{"name": "A0", "displayName": "ADC0", "direction": "INPUT"},
|
||||
{"name": "A1", "displayName": "ADC1", "direction": "INPUT"},
|
||||
{"name": "A2", "displayName": "ADC2", "direction": "INPUT"},
|
||||
{"name": "A3", "displayName": "ADC3", "direction": "INPUT"}
|
||||
],
|
||||
"defaultI2C": {
|
||||
"i2cPortId": 0,
|
||||
"SCL": "D3",
|
||||
"SDA": "D2"
|
||||
},
|
||||
"sdCardCS": 10,
|
||||
"image": None
|
||||
}
|
||||
|
||||
|
||||
|
||||
return boards
|
||||
|
||||
def convert_boards_to_json():
|
||||
|
|
@ -381,24 +309,24 @@ def convert_boards_to_json():
|
|||
that can be used in the Wippersnapper Configuration Builder.
|
||||
"""
|
||||
boards = {}
|
||||
|
||||
|
||||
# Get all board directories
|
||||
board_dirs = [d for d in os.listdir(BOARDS_DIR) if os.path.isdir(os.path.join(BOARDS_DIR, d)) and not d.startswith('.')]
|
||||
|
||||
|
||||
for board_dir in board_dirs:
|
||||
board_path = os.path.join(BOARDS_DIR, board_dir)
|
||||
definition_file = os.path.join(board_path, "definition.json")
|
||||
|
||||
|
||||
# Skip if the definition.json doesn't exist
|
||||
if not os.path.exists(definition_file):
|
||||
print(f"Skipping {board_dir} - No definition.json found")
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
# Read the definition.json file
|
||||
with open(definition_file, 'r') as f:
|
||||
board_data = json.load(f)
|
||||
|
||||
|
||||
# Extract relevant information
|
||||
board_info = {
|
||||
"boardName": board_data.get("boardName"),
|
||||
|
|
@ -409,18 +337,28 @@ def convert_boards_to_json():
|
|||
"productURL": board_data.get("productURL", ""),
|
||||
"documentationURL": board_data.get("documentationURL", ""),
|
||||
"installMethod": board_data.get("installMethod", ""),
|
||||
"installBoardName": board_data.get("installBoardName", board_data.get("boardName", "")),
|
||||
"rtc": board_data.get("rtc", None),
|
||||
"sdCardCS": board_data.get("sdCardCS", None),
|
||||
"pins": [],
|
||||
"analogPins": [],
|
||||
"defaultI2C": {},
|
||||
"image": None
|
||||
}
|
||||
|
||||
|
||||
SUPPORTED_ARCHITECTURES = ["rp2350", "rp2040", "esp32s2", "esp32s3"]
|
||||
# Filter out boards that are not supported
|
||||
if board_info["mcuName"] not in SUPPORTED_ARCHITECTURES:
|
||||
print(f"Skipping {board_dir} - Unsupported architecture {board_info['mcuName']}")
|
||||
continue
|
||||
|
||||
# Get pins
|
||||
if "components" in board_data and "digitalPins" in board_data["components"]:
|
||||
for pin in board_data["components"]["digitalPins"]:
|
||||
# Extract the numeric part of the pin name (e.g., "D13" -> 13)
|
||||
pin_name = pin.get("name", "")
|
||||
if pin_name.startswith("D"):
|
||||
# if board_info["pins"].filter(lambda x: x["name"] == pin_name) == 0:
|
||||
try:
|
||||
pin_number = int(pin_name[1:])
|
||||
board_info["pins"].append({
|
||||
|
|
@ -432,20 +370,25 @@ def convert_boards_to_json():
|
|||
})
|
||||
except ValueError:
|
||||
print(f"Skipping pin {pin_name} - Cannot parse pin number")
|
||||
|
||||
|
||||
# Get analog pins
|
||||
if "components" in board_data and "analogPins" in board_data["components"]:
|
||||
analog_count = 0
|
||||
for pin in board_data["components"]["analogPins"]:
|
||||
pin_name = pin.get("name", "")
|
||||
board_info["analogPins"].append({
|
||||
"name": pin_name,
|
||||
"displayName": pin.get("displayName", pin_name),
|
||||
"direction": pin.get("direction", "INPUT")
|
||||
})
|
||||
analog_count += 1
|
||||
try:
|
||||
pin_number = int(pin_name[1:])
|
||||
board_info["analogPins"].append({
|
||||
"number": pin_number,
|
||||
"name": pin_name,
|
||||
"displayName": pin.get("displayName", pin_name),
|
||||
"direction": pin.get("direction", "INPUT")
|
||||
})
|
||||
analog_count += 1
|
||||
except ValueError:
|
||||
print(f"Skipping pin {pin_name} - Cannot parse pin number")
|
||||
board_info["totalAnalogPins"] = analog_count
|
||||
|
||||
|
||||
# Get I2C ports
|
||||
if "components" in board_data and "i2cPorts" in board_data["components"]:
|
||||
i2c_ports = board_data["components"]["i2cPorts"]
|
||||
|
|
@ -456,13 +399,13 @@ def convert_boards_to_json():
|
|||
"SCL": default_i2c.get("SCL"),
|
||||
"SDA": default_i2c.get("SDA")
|
||||
}
|
||||
|
||||
|
||||
# Store all I2C ports
|
||||
board_info["i2cPorts"] = i2c_ports
|
||||
|
||||
|
||||
# Count total GPIO pins
|
||||
board_info["totalGPIOPins"] = len(board_info["pins"])
|
||||
|
||||
|
||||
# Look for an image file
|
||||
image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.svg']
|
||||
for ext in image_extensions:
|
||||
|
|
@ -471,27 +414,31 @@ def convert_boards_to_json():
|
|||
# Store relative path to image
|
||||
board_info["image"] = f"boards/{board_dir}/image{ext}"
|
||||
break
|
||||
|
||||
|
||||
# Fetch missing images
|
||||
if board_info["image"] is None and board_info["productURL"] != "":
|
||||
board_info["image"] = get_image_from_adafruit_product_url(board_info["productURL"])
|
||||
|
||||
# Add to boards dictionary
|
||||
boards[board_dir] = board_info
|
||||
print(f"Processed {board_dir}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing {board_dir}: {str(e)}")
|
||||
|
||||
|
||||
# Add custom board definitions
|
||||
boards = add_custom_board_definitions(boards)
|
||||
|
||||
|
||||
# Write the consolidated JSON file
|
||||
with open(OUTPUT_FILE, 'w') as f:
|
||||
json.dump({"boards": boards}, f, indent=2)
|
||||
|
||||
|
||||
# Write the consolidated JS file
|
||||
with open(OUTPUT_FILE.replace('.json', '.js'), 'w') as f:
|
||||
f.write("window.jsonBoardObject = ")
|
||||
json.dump({"boards": boards}, f, indent=2)
|
||||
f.write(";\n")
|
||||
|
||||
|
||||
print(f"Successfully created {OUTPUT_FILE} with {len(boards)} boards")
|
||||
return boards
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import json
|
|||
import glob
|
||||
import re
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
# from bs4 import BeautifulSoup
|
||||
from pathlib import Path
|
||||
|
||||
# Base directory for the components
|
||||
|
|
@ -12,8 +12,8 @@ OUTPUT_FILE = r"./wippersnapper_components.json"
|
|||
|
||||
def get_image_from_adafruit_product_url(product_url):
|
||||
"""
|
||||
Fetch the product image URL from an Adafruit product page by extracting
|
||||
the og:image meta tag from the HTML.
|
||||
Fetch the product image URL from an Adafruit product API
|
||||
(was from prod page by extracting the og:image meta tag from the HTML).
|
||||
|
||||
Args:
|
||||
product_url (str): URL to an Adafruit product page
|
||||
|
|
@ -21,28 +21,43 @@ def get_image_from_adafruit_product_url(product_url):
|
|||
Returns:
|
||||
str or None: URL to the product image, or None if not found
|
||||
"""
|
||||
if not product_url or not re.match(r'https?://(?:www\.)?adafruit\.com/product/\d+', product_url):
|
||||
if not product_url or not re.match(r'https?://(?:www\.)?adafruit\.com/(?:product|category)/\d+', product_url):
|
||||
print(f"Invalid Adafruit product URL ({product_url}) provided. Skipping image fetch.")
|
||||
return None
|
||||
|
||||
# Grab product JSON from https://www.adafruit.com/api/products, cache, save as ISO date string filename so can be easily used as cache key
|
||||
try:
|
||||
response = requests.get(product_url, timeout=10)
|
||||
product_id = re.search(r'/product/(\d+)', product_url)
|
||||
category = re.search(r'/category/(\d+)', product_url)
|
||||
url_to_fetch = f"https://www.adafruit.com/api/{("category" if category else "product")}/{(product_id.groups(1)[0] if product_id else category.groups(1)[0])}"
|
||||
print(f"Fetching image from Adafruit API for {product_url}...\nGET {url_to_fetch}")
|
||||
response = requests.get(url_to_fetch, timeout=10)
|
||||
if response.status_code != 200:
|
||||
print(f"Failed to fetch product page: {product_url}, status code: {response.status_code}")
|
||||
print(f"Failed to fetch product data: {product_url}, status code: {response.status_code}")
|
||||
return None
|
||||
response_json = response.json()
|
||||
if (response_json is None or response_json == [] or response_json == {} or 'error' in response_json):
|
||||
print(f"Invalid response from API for {product_url}: {response_json}")
|
||||
return None
|
||||
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
og_image = soup.find('meta', property='og:image')
|
||||
|
||||
if og_image and 'content' in og_image.attrs:
|
||||
image_url = og_image['content']
|
||||
print(f"Found image URL from product page: {image_url}")
|
||||
if 'product_image' in response_json:
|
||||
image_url = response_json['product_image']
|
||||
print(f"Found image URL from API: {image_url}")
|
||||
return image_url
|
||||
else:
|
||||
print(f"No og:image meta tag found for: {product_url}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error fetching image from product URL {product_url}: {str(e)}")
|
||||
elif 'subcategories' in response_json:
|
||||
subcategories = response_json['subcategories']
|
||||
for subcategory in subcategories:
|
||||
if 'product_image' in subcategory:
|
||||
image_url = subcategory['product_image']
|
||||
print(f"Found image URL from API: {image_url}")
|
||||
return image_url
|
||||
print(f"No image found in subcategory for: {product_url}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error fetching image from API for {product_url}: {str(e)}")
|
||||
return None
|
||||
|
||||
## Consider removing beautifulsoup...
|
||||
|
||||
def map_datatypes_to_offline_types(datatype):
|
||||
"""
|
||||
|
|
@ -84,19 +99,29 @@ def convert_components_to_json():
|
|||
with open(definition_file, 'r') as f:
|
||||
component_data = json.load(f)
|
||||
|
||||
# Skip non-input components for now
|
||||
if category == "pin":
|
||||
if component_data.get("direction", "").lower() != "input":
|
||||
continue
|
||||
|
||||
# Extract relevant information
|
||||
component_info = {
|
||||
"id": component_dir,
|
||||
"displayName": component_data.get("displayName", component_dir),
|
||||
"name": component_data.get("name", component_dir),
|
||||
"description": component_data.get("description", ""),
|
||||
"category": category,
|
||||
"dataTypes": [],
|
||||
"image": None
|
||||
}
|
||||
|
||||
|
||||
# Store product URL if available
|
||||
if "productURL" in component_data:
|
||||
component_info["productUrl"] = component_data["productURL"]
|
||||
|
||||
# store documentation URL if available
|
||||
if "documentationURL" in component_data:
|
||||
component_info["documentationUrl"] = component_data["documentationURL"]
|
||||
|
||||
# Extract data types if available
|
||||
if "subcomponents" in component_data:
|
||||
|
|
@ -109,6 +134,10 @@ def convert_components_to_json():
|
|||
else:
|
||||
component_info["dataTypes"].append(map_datatypes_to_offline_types(meas_type))
|
||||
|
||||
# Handle pin category (MODE becomes componentAPI value)
|
||||
if category == "pin":
|
||||
component_info["componentAPI"] = component_data.get("mode", "digital").lower() + "io"
|
||||
|
||||
# Handle I2C-specific properties
|
||||
if category == "i2c":
|
||||
# Extract I2C address from parameters
|
||||
|
|
|
|||
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 137 KiB |
212
fetch_latest_release_info_and_assets.py
Normal file
212
fetch_latest_release_info_and_assets.py
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
# Take releases for wippersnapper, filtered to semver including -offline, get next page until finished if none.
|
||||
|
||||
# Later match board name to installBoardName from boards (which falls back to boardName)
|
||||
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
# Configuration
|
||||
REPO = "adafruit/Adafruit_Wippersnapper_Arduino"
|
||||
OUTPUT_DIR = "latest_firmware"
|
||||
RELEASES_API_URL = f"https://api.github.com/repos/{REPO}/releases"
|
||||
PER_PAGE = 30 # GitHub API default per page limit
|
||||
JS_OUTPUT_FILE = "firmware-data.js"
|
||||
|
||||
def fetch_latest_offline_release():
|
||||
"""Fetch the latest offline release from GitHub API."""
|
||||
print(f"Fetching releases information for {REPO}...")
|
||||
|
||||
headers = {
|
||||
"Accept": "application/vnd.github.v3+json"
|
||||
}
|
||||
|
||||
all_releases = []
|
||||
page = 1
|
||||
found_offline_release = False
|
||||
latest_offline_release = None
|
||||
|
||||
# Loop through pages until we find an offline release or run out of releases
|
||||
while not found_offline_release:
|
||||
page_url = f"{RELEASES_API_URL}?page={page}&per_page={PER_PAGE}"
|
||||
print(f"Fetching page {page} of releases...")
|
||||
|
||||
response = requests.get(page_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
page_releases = response.json()
|
||||
all_releases.extend(page_releases)
|
||||
|
||||
# Check if this page contains any offline releases
|
||||
offline_releases = [r for r in page_releases if "offline" in r.get("tag_name", "")]
|
||||
|
||||
offline_releases_count = len(offline_releases)
|
||||
print(f"Found {offline_releases_count} offline releases on page {page}")
|
||||
|
||||
if offline_releases_count > 0:
|
||||
found_offline_release = True
|
||||
# Get the latest offline release by published date
|
||||
latest_offline_release = sorted(
|
||||
offline_releases,
|
||||
key=lambda x: x.get("published_at", ""),
|
||||
reverse=True
|
||||
)[0]
|
||||
break
|
||||
|
||||
# Move to next page if we didn't find any offline releases and there are more pages
|
||||
page += 1
|
||||
|
||||
# Stop if we received fewer items than the per_page limit (meaning we're on the last page)
|
||||
if len(page_releases) < PER_PAGE:
|
||||
print(f"Reached the last page with {len(page_releases)} items (less than {PER_PAGE} per page)")
|
||||
break
|
||||
|
||||
# Check if we found an offline release
|
||||
if latest_offline_release is None:
|
||||
raise Exception(f"No offline release found after checking {page} pages of releases.")
|
||||
|
||||
tag_name = latest_offline_release.get("tag_name")
|
||||
release_name = latest_offline_release.get("name")
|
||||
release_id = latest_offline_release.get("id")
|
||||
release_html_url = latest_offline_release.get("html_url")
|
||||
release_published_date = latest_offline_release.get("published_at")
|
||||
|
||||
print(f"Found latest offline release: {release_name} (Tag: {tag_name}, ID: {release_id})")
|
||||
|
||||
return latest_offline_release, release_id, tag_name, release_name, release_html_url, release_published_date
|
||||
|
||||
def fetch_release_assets(release_id):
|
||||
"""Fetch all assets for a specific release."""
|
||||
assets_url = f"https://api.github.com/repos/{REPO}/releases/{release_id}/assets"
|
||||
print(f"Fetching complete assets list from: {assets_url}")
|
||||
|
||||
headers = {
|
||||
"Accept": "application/vnd.github.v3+json"
|
||||
}
|
||||
|
||||
all_assets = []
|
||||
assets_page = 1
|
||||
|
||||
# Loop through pages to get all assets
|
||||
while True:
|
||||
assets_page_url = f"{assets_url}?page={assets_page}&per_page={PER_PAGE}"
|
||||
print(f"Fetching page {assets_page} of assets...")
|
||||
|
||||
response = requests.get(assets_page_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
page_assets = response.json()
|
||||
|
||||
# Add to all assets - ensure it's a list
|
||||
if isinstance(page_assets, list):
|
||||
all_assets.extend(page_assets)
|
||||
else:
|
||||
all_assets.append(page_assets)
|
||||
|
||||
# Move to next page if there are more assets
|
||||
assets_page += 1
|
||||
|
||||
# Stop if we received fewer items than the per_page limit (meaning we're on the last page)
|
||||
if len(page_assets) < PER_PAGE:
|
||||
print(f"Reached the last page of assets with {len(page_assets)} items")
|
||||
break
|
||||
|
||||
# Filter for UF2 files
|
||||
print("Filtering for firmware files...")
|
||||
firmware_assets = [
|
||||
asset for asset in all_assets
|
||||
if asset.get("name", "").endswith(".uf2")# or asset.get("name", "").endswith(".bin")
|
||||
]
|
||||
|
||||
# Check if we found any assets
|
||||
if len(firmware_assets) == 0:
|
||||
print(f"No firmware files (.uf2) found in this release.")
|
||||
|
||||
return firmware_assets
|
||||
|
||||
def generate_js_data(release_info, assets):
|
||||
"""Generate JavaScript data structure with firmware information."""
|
||||
tag_name, release_name, release_html_url, release_published_date = release_info
|
||||
|
||||
# Format the published date
|
||||
try:
|
||||
dt = datetime.fromisoformat(release_published_date.replace("Z", "+00:00"))
|
||||
formatted_date = dt.strftime("%Y-%m-%d")
|
||||
except:
|
||||
formatted_date = release_published_date
|
||||
|
||||
# Create firmware data object
|
||||
firmware_data = {
|
||||
"releaseInfo": {
|
||||
"version": tag_name,
|
||||
"name": release_name,
|
||||
"url": release_html_url,
|
||||
"publishedDate": formatted_date
|
||||
},
|
||||
"firmwareFiles": []
|
||||
}
|
||||
|
||||
# Add firmware files info
|
||||
for asset in assets:
|
||||
if asset.get("name", "").endswith(".uf2") or asset.get("name", "").endswith(".bin"):
|
||||
firmware_data["firmwareFiles"].append({
|
||||
"name": asset.get("name"),
|
||||
"url": asset.get("browser_download_url"),
|
||||
"size": asset.get("size"),
|
||||
"downloadCount": asset.get("download_count", 0),
|
||||
"contentType": asset.get("content_type"),
|
||||
"createdAt": asset.get("created_at")
|
||||
})
|
||||
|
||||
return firmware_data
|
||||
|
||||
def save_js_data(firmware_data):
|
||||
"""Save firmware data as a JavaScript file."""
|
||||
js_content = f"""// Auto-generated on {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
|
||||
const FIRMWARE_DATA = {json.dumps(firmware_data, indent=2)};
|
||||
|
||||
// Export for use in other modules
|
||||
if (typeof module !== 'undefined' && module.exports) {{
|
||||
module.exports = {{ FIRMWARE_DATA }};
|
||||
}} else {{
|
||||
window.FIRMWARE_DATA = FIRMWARE_DATA;
|
||||
}}
|
||||
"""
|
||||
|
||||
with open(JS_OUTPUT_FILE, "w") as f:
|
||||
f.write(js_content)
|
||||
|
||||
print(f"JavaScript data saved to {JS_OUTPUT_FILE}")
|
||||
|
||||
def main():
|
||||
try:
|
||||
# Create output directory if it doesn't exist
|
||||
if not os.path.exists(OUTPUT_DIR):
|
||||
os.makedirs(OUTPUT_DIR)
|
||||
print(f"Created directory: {OUTPUT_DIR}")
|
||||
|
||||
# Fetch latest offline release
|
||||
release_data, release_id, tag_name, release_name, release_html_url, release_published_date = fetch_latest_offline_release()
|
||||
|
||||
# Fetch assets for this release
|
||||
firmware_assets = fetch_release_assets(release_id)
|
||||
|
||||
print(f"Found {len(firmware_assets)} firmware files.")
|
||||
|
||||
# Generate JavaScript data
|
||||
firmware_data = generate_js_data(
|
||||
(tag_name, release_name, release_html_url, release_published_date),
|
||||
firmware_assets
|
||||
)
|
||||
|
||||
# Save as JavaScript file
|
||||
save_js_data(firmware_data)
|
||||
|
||||
print("Process completed successfully.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
146
firmware-data.js
Normal file
146
firmware-data.js
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
// Auto-generated on 2025-04-25 17:55:57
|
||||
const FIRMWARE_DATA = {
|
||||
"releaseInfo": {
|
||||
"version": "1.0.0-offline-beta.2",
|
||||
"name": "WipperSnapper Offline 1.0.0-offline-beta.2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/tag/1.0.0-offline-beta.2",
|
||||
"publishedDate": "2025-04-14"
|
||||
},
|
||||
"firmwareFiles": [
|
||||
{
|
||||
"name": "wippersnapper.feather_esp32s2.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.feather_esp32s2.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2735616,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.feather_esp32s2_reverse_tft.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.feather_esp32s2_reverse_tft.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2735616,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.feather_esp32s2_tft.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.feather_esp32s2_tft.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2735616,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.feather_esp32s3.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.feather_esp32s3.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2792960,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.feather_esp32s3_4mbflash_2mbpsram.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.feather_esp32s3_4mbflash_2mbpsram.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2798592,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.feather_esp32s3_reverse_tft.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.feather_esp32s3_reverse_tft.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2798592,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.feather_esp32s3_tft.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.feather_esp32s3_tft.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2798592,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.feather_rp2040_adalogger_tinyusb.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.feather_rp2040_adalogger_tinyusb.1.0.0-offline-beta.2.uf2",
|
||||
"size": 648192,
|
||||
"downloadCount": 1,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.metroesp32s2.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.metroesp32s2.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2735616,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.metro_esp32s3.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.metro_esp32s3.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2802688,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.metro_rp2350_tinyusb.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.metro_rp2350_tinyusb.1.0.0-offline-beta.2.uf2",
|
||||
"size": 653824,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.pico_rp2040_tinyusb.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.pico_rp2040_tinyusb.1.0.0-offline-beta.2.uf2",
|
||||
"size": 646656,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.pico_rp2350_tinyusb.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.pico_rp2350_tinyusb.1.0.0-offline-beta.2.uf2",
|
||||
"size": 638464,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.qtpy_esp32s2.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.qtpy_esp32s2.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2735616,
|
||||
"downloadCount": 1,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.qtpy_esp32s3.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.qtpy_esp32s3.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2792960,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wippersnapper.qtpy_esp32s3_n4r2.1.0.0-offline-beta.2.uf2",
|
||||
"url": "https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases/download/1.0.0-offline-beta.2/wippersnapper.qtpy_esp32s3_n4r2.1.0.0-offline-beta.2.uf2",
|
||||
"size": 2798592,
|
||||
"downloadCount": 0,
|
||||
"contentType": "application/octet-stream",
|
||||
"createdAt": "2025-04-14T16:09:53Z"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Export for use in other modules
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { FIRMWARE_DATA };
|
||||
} else {
|
||||
window.FIRMWARE_DATA = FIRMWARE_DATA;
|
||||
}
|
||||
245
index.html
245
index.html
|
|
@ -3,8 +3,20 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||
<title>Wippersnapper Configuration Builder</title>
|
||||
<style>
|
||||
.not-available-yet {
|
||||
text-decoration: line-through;
|
||||
font-size: x-small;
|
||||
}
|
||||
.component-tabcontent > h3 {
|
||||
display: inline-block;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.component-tabcontent > .search-filter {
|
||||
display: inline-block;
|
||||
}
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
|
|
@ -45,6 +57,9 @@
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
scrollbar-width: auto;
|
||||
max-height: 80vh;
|
||||
overflow: scroll;
|
||||
}
|
||||
.component-card {
|
||||
border: 1px solid #ddd;
|
||||
|
|
@ -53,6 +68,12 @@
|
|||
width: 230px;
|
||||
background-color: white;
|
||||
}
|
||||
.component-card h4 {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
.component-card p {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
.component-card img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
|
|
@ -94,11 +115,26 @@
|
|||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.i2c-bus-config {
|
||||
.inline-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content:space-around;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.dashed-section {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
border: 1px dashed #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.dashed-section h3{
|
||||
padding-top: 0px;
|
||||
margin-top:0px;
|
||||
}
|
||||
|
||||
.dashed-section > :last-child {
|
||||
padding-bottom: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.tab {
|
||||
overflow: hidden;
|
||||
|
|
@ -184,6 +220,9 @@
|
|||
.board-details {
|
||||
flex: 1;
|
||||
}
|
||||
.board-details > p {
|
||||
margin: 5px 0 0 0;
|
||||
}
|
||||
.search-filter {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
|
@ -195,26 +234,36 @@
|
|||
border: 1px solid #ddd;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<h1>Wippersnapper Configuration Builder</h1>
|
||||
<button id="reset-config-btn-top" class="reset-btn" style="background-color: #dc3545; margin-left: 20px;">Reset Configurator</button>
|
||||
<div style="flex: 1;">
|
||||
<h1>Wippersnapper Offline <br/>Data Logger Configurator</h1>
|
||||
</div>
|
||||
<div style="flex: 1; text-align: right;">
|
||||
|
||||
<div style="display:inline-block; font-size: smaller;"><b>Latest Release:</b> <a id="release_name" href="https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases">Release Page</a></div>
|
||||
<div style="display:inline-block; margin: 5px; font-size: smaller;">Download: <a style="display: inline-flex;" id="firmware_file" href="https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/releases">UF2 firmware</a> </div>
|
||||
</div>
|
||||
<div style="flex: 1; text-align: right;">
|
||||
<button id="reset-config-btn-top" class="reset-btn" style="background-color: #dc3545; margin-left: 20px;">Reset Configurator</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div id="loading-indicator" class="section">
|
||||
<h2>Loading Wippersnapper Data</h2>
|
||||
<div class="loader"></div>
|
||||
<p>Loading board and component definitions...</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tab">
|
||||
<button class="tablinks active" onclick="openTab(event, 'BuildConfig')">Build Configuration</button>
|
||||
<button class="tablinks" onclick="openTab(event, 'ImportExport')">Import/Export</button>
|
||||
<button class="tablinks" onclick="openTab(event, 'ImportExport')">Import Config / Custom Board</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="BuildConfig" class="tabcontent" style="display: block;">
|
||||
<div class="section">
|
||||
<h2>1. Select Board</h2>
|
||||
|
|
@ -222,26 +271,27 @@
|
|||
<option value="">-- Select a Board --</option>
|
||||
<!-- Board options will be dynamically populated -->
|
||||
</select>
|
||||
|
||||
|
||||
<div id="board-details" class="hidden">
|
||||
<div class="board-preview">
|
||||
<img id="board-image" class="board-image hidden" src="" alt="Board image">
|
||||
<div class="board-details">
|
||||
<h3>Board Details</h3>
|
||||
<h3>Board Details <span id="board-purchase"></span> <span id="board-help"></span></h3>
|
||||
<p><strong>Reference Voltage:</strong> <span id="ref-voltage">3.3</span>V</p>
|
||||
<p><strong>Total GPIO Pins:</strong> <span id="total-gpio">0</span></p>
|
||||
<p><strong>Total Analog Pins:</strong> <span id="total-analog">0</span></p>
|
||||
<p><strong>Default I2C Bus:</strong> SCL: <span id="default-scl">0</span>, SDA: <span id="default-sda">0</span></p>
|
||||
<p><strong>Default I2C Bus:</strong> SCL: <span id="default-SCL">0</span>, SDA: <span id="default-SDA">0</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="companion-board-section" class="section hidden">
|
||||
<h2>2. Select Companion Board (Optional)</h2>
|
||||
<p>You can skip this step if you're not using a companion board.</p>
|
||||
<p>You can skip this step if you're not using a companion board.
|
||||
<br/><i><small>It's just a convenient way to prepopulate the Real-time clock model and SD Card's Chip Select pin option.</small></i></p>
|
||||
<select id="companion-board-select">
|
||||
<option value="">-- None --</option>
|
||||
<option value="">-- None --</option>
|
||||
<option value="adalogger">Adafruit Adalogger FeatherWing</option>
|
||||
<option value="ds3231-precision">Adafruit DS3231 Precision RTC FeatherWing</option>
|
||||
<option value="picowbell-adalogger">Adafruit PiCowbell Adalogger for Pico</option>
|
||||
|
|
@ -253,26 +303,31 @@
|
|||
<option value="airlift-shield">Adafruit AirLift Shield - ESP32 WiFi Co-Processor</option>
|
||||
|
||||
</select>
|
||||
|
||||
|
||||
<div id="companion-details" class="hidden">
|
||||
<h3>Companion Board Details</h3>
|
||||
<p><strong>RTC:</strong> <span id="companion-rtc">None</span></p>
|
||||
<p><strong>SD Card CS Pin:</strong> <span id="companion-sd-cs">None</span></p>
|
||||
<p><strong>Additional Components:</strong> <span id="companion-extras">None</span></p>
|
||||
<div class="board-preview">
|
||||
<img id="companion-image" class="board-image hidden" src="#" alt="Companion board image">
|
||||
<div class="board-details">
|
||||
<h3>Companion Board Details <span id="companion-purchase"></span> <span id="companion-help"></span></h3>
|
||||
<p><strong>Real-time clock (RTC):</strong> <span id="companion-rtc">None</span></p>
|
||||
<p><strong>SD Card CS Pin:</strong> <span id="companion-sd-cs">None</span></p>
|
||||
<p><strong>Additional Components:</strong> <span id="companion-extras">None</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="manual-config-section" class="section hidden">
|
||||
<h2>3. Manual Configuration</h2>
|
||||
|
||||
<div id="sd-card-config">
|
||||
|
||||
<div id="sd-card-config" class="dashed-section">
|
||||
<h3>SD Card Configuration</h3>
|
||||
<div id="sd-missing">
|
||||
<p>No SD card detected from companion board. Would you like to add an SD card? <b>(REQUIRED)</b></p>
|
||||
<label><input type="checkbox" id="add-sd-card"> Add SD Card</label>
|
||||
|
||||
|
||||
<div id="sd-card-pin-select" class="hidden">
|
||||
<p>Select SD Card CS Pin:</p>
|
||||
<p>Select SD Card CS Pin: <span id="manual-sd-cs-pin"></span></p>
|
||||
<div id="sd-pins-list" class="pins-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -280,12 +335,14 @@
|
|||
<p>SD Card CS Pin: <span id="sd-cs-pin"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rtc-config">
|
||||
<h3>RTC Configuration</h3>
|
||||
|
||||
<div id="rtc-config" class="dashed-section">
|
||||
<h3>Real-time clock (RTC) Configuration</h3>
|
||||
<p style="color: red; font-weight: bold;">Note: The RTC must be on the first (default) I2C bus.</p>
|
||||
<div id="rtc-missing">
|
||||
<p>No RTC detected from companion board. Select RTC type:</p>
|
||||
<select id="rtc-select">
|
||||
<p>No RTC detected from companion board.</p>
|
||||
<label for="rtc-select">Select RTC type: </label>
|
||||
<select id="rtc-select">
|
||||
<option value="soft">Software RTC</option>
|
||||
<option value="PCF8523">PCF8523</option>
|
||||
<option value="DS3231">DS3231</option>
|
||||
|
|
@ -296,57 +353,60 @@
|
|||
<p>RTC Type: <span id="rtc-type"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="status-led-config">
|
||||
|
||||
<div id="status-led-config" class="dashed-section">
|
||||
<h3>Status LED Configuration</h3>
|
||||
<label for="led-brightness">Status LED Brightness (0.0-1.0): </label>
|
||||
<input type="range" id="led-brightness" min="0" max="1" step="0.1" value="0.5">
|
||||
<span id="brightness-value">0.5</span>
|
||||
<div class="inline-row">
|
||||
<label for="led-brightness">Status LED Brightness (0.0-1.0): </label>
|
||||
<input type="range" id="led-brightness" min="0" max="1" step="0.01" value="0.5">
|
||||
<span id="brightness-value">0.5</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="i2c-bus-section" class="section hidden">
|
||||
<h2>4. I2C Bus Configuration</h2>
|
||||
|
||||
<div id="default-i2c-bus" class="i2c-bus-config">
|
||||
|
||||
<div id="default-i2c-bus" class="dashed-section">
|
||||
<h3>Default I2C Bus</h3>
|
||||
<p>SCL: <span id="default-i2c-scl"></span>, SDA: <span id="default-i2c-sda"></span></p>
|
||||
<p>SCL: <span id="default-i2c-SCL"></span>, SDA: <span id="default-i2c-SDA"></span></p>
|
||||
</div>
|
||||
|
||||
<div id="additional-i2c-bus-container">
|
||||
|
||||
<div id="additional-i2c-bus-container" class="dashed-section">
|
||||
<h3>Additional I2C Bus (Optional)</h3>
|
||||
<label><input type="checkbox" id="add-i2c-bus"> Add Additional I2C Bus</label>
|
||||
|
||||
<div id="additional-i2c-config" class="hidden i2c-bus-config">
|
||||
<p>Select SCL Pin:</p>
|
||||
<div id="scl-pins-list" class="pins-container"></div>
|
||||
<p>Select SDA Pin:</p>
|
||||
<div id="sda-pins-list" class="pins-container"></div>
|
||||
|
||||
<div id="additional-i2c-config" class="hidden dashed-section">
|
||||
<p>Select SCL Pin: <span id="alt-SCL-pin"></span></p>
|
||||
<div id="SCL-pins-list" class="pins-container"></div>
|
||||
<p>Select SDA Pin: <span id="alt-SDA-pin"></span></p>
|
||||
<div id="SDA-pins-list" class="pins-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="i2c-mux-container">
|
||||
|
||||
<div id="i2c-mux-container" class="dashed-section">
|
||||
<h3>I2C Multiplexers (Optional)</h3>
|
||||
<button id="add-mux-btn">Add I2C Multiplexer</button>
|
||||
|
||||
|
||||
<div id="mux-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="components-section" class="section hidden">
|
||||
<h2>5. Add Components</h2>
|
||||
|
||||
<p style="color: red;"><small><i>UART + other component categories are currently being implemented in offline mode (No ETA, but maybe reactive actions too!).</i></small></p>
|
||||
|
||||
<div id="component-type-tabs" class="tab">
|
||||
<button class="comp-tab active" onclick="openComponentTab(event, 'all-components')">All Components</button>
|
||||
<button class="comp-tab" onclick="openComponentTab(event, 'i2c-components')">I2C Components</button>
|
||||
<button class="comp-tab" onclick="openComponentTab(event, 'ds18x20-components')">DS18x20 Components</button>
|
||||
<button class="comp-tab" onclick="openComponentTab(event, 'pin-components')">Pin Components</button>
|
||||
<button class="comp-tab" onclick="openComponentTab(event, 'pixel-components')">Pixel Components</button>
|
||||
<button class="comp-tab" onclick="openComponentTab(event, 'pwm-components')">PWM Components</button>
|
||||
<button class="comp-tab" onclick="openComponentTab(event, 'servo-components')">Servo Components</button>
|
||||
<button class="comp-tab" onclick="openComponentTab(event, 'uart-components')">UART Components</button>
|
||||
<button class="comp-tab not-available-yet" onclick="openComponentTab(event, 'pixel-components')">Pixel Components</button>
|
||||
<button class="comp-tab not-available-yet" onclick="openComponentTab(event, 'pwm-components')">PWM Components</button>
|
||||
<button class="comp-tab not-available-yet" onclick="openComponentTab(event, 'servo-components')">Servo Components</button>
|
||||
<button class="comp-tab not-available-yet" onclick="openComponentTab(event, 'uart-components')">UART Components</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="all-components" class="component-tabcontent" style="display: block;">
|
||||
<h3>All Components</h3>
|
||||
<div class="search-filter">
|
||||
|
|
@ -356,24 +416,18 @@
|
|||
<!-- All components will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="i2c-components" class="component-tabcontent">
|
||||
<h3>I2C Components</h3>
|
||||
<div class="search-filter">
|
||||
<input type="text" id="i2c-search" class="component-search" placeholder="Search I2C components...">
|
||||
</div>
|
||||
<div>
|
||||
<label for="i2c-bus-select">Select I2C Bus: </label>
|
||||
<select id="i2c-bus-select">
|
||||
<option value="default">Default I2C Bus</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="component-list" id="i2c-component-list">
|
||||
<!-- I2C components will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="ds18x20-components" class="component-tabcontent">
|
||||
<h3>DS18x20 Components</h3>
|
||||
<div class="search-filter">
|
||||
|
|
@ -383,7 +437,7 @@
|
|||
<!-- DS18x20 components will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="pin-components" class="component-tabcontent">
|
||||
<h3>Pin Components</h3>
|
||||
<div class="search-filter">
|
||||
|
|
@ -393,7 +447,7 @@
|
|||
<!-- Pin components will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="pixel-components" class="component-tabcontent">
|
||||
<h3>Pixel Components</h3>
|
||||
<div class="search-filter">
|
||||
|
|
@ -403,7 +457,7 @@
|
|||
<!-- Pixel components will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="pwm-components" class="component-tabcontent">
|
||||
<h3>PWM Components</h3>
|
||||
<div class="search-filter">
|
||||
|
|
@ -413,7 +467,7 @@
|
|||
<!-- PWM components will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="servo-components" class="component-tabcontent">
|
||||
<h3>Servo Components</h3>
|
||||
<div class="search-filter">
|
||||
|
|
@ -423,7 +477,7 @@
|
|||
<!-- Servo components will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="uart-components" class="component-tabcontent">
|
||||
<h3>UART Components</h3>
|
||||
<div class="search-filter">
|
||||
|
|
@ -434,17 +488,18 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="selected-components-section" class="section hidden">
|
||||
<h2>6. Selected Components</h2>
|
||||
<div id="selected-components-list">
|
||||
<p>No components selected yet.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="generate-section" class="section hidden">
|
||||
<h2>7. Generate Configuration</h2>
|
||||
<button id="generate-config-btn">Generate Configuration</button>
|
||||
<input type="checkbox" id="use-auto-init" onchange="javascript:appState.enableautoConfig=this.checked;"> <label for="use-auto-init" title="Auto config fallback for I2C sensors that fail to initialise (selects alternative sensors at same address)">Use Auto Init fallback</label>
|
||||
<div id="config-output-container" class="hidden">
|
||||
<h3>Configuration JSON:</h3>
|
||||
<pre id="config-output" class="config-output"></pre>
|
||||
|
|
@ -455,24 +510,24 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="ImportExport" class="tabcontent">
|
||||
<div class="section">
|
||||
<h2>Import Configuration</h2>
|
||||
<p>Import a previously saved configuration file:</p>
|
||||
<input type="file" id="import-file" accept=".json">
|
||||
<button id="import-btn">Import</button>
|
||||
|
||||
|
||||
<p>Or paste your configuration JSON here:</p>
|
||||
<textarea id="import-json" class="json-input" placeholder="Paste your configuration JSON here..."></textarea>
|
||||
<button id="import-json-btn">Import from Text</button>
|
||||
|
||||
<div class="section">
|
||||
|
||||
<div class="section" style="visibility: hidden; height: 0px;">
|
||||
<h2>Export Configuration</h2>
|
||||
<p>Export the current configuration to a file:</p>
|
||||
<button id="export-btn">Export Configuration</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="section">
|
||||
<h2>Add Custom Board</h2>
|
||||
<p>Add a custom board to the boards collection (for boards without definition.json files):</p>
|
||||
|
|
@ -494,16 +549,16 @@
|
|||
<input type="number" id="custom-board-analog" value="0" min="0" max="100">
|
||||
</div>
|
||||
<div>
|
||||
<label for="custom-board-scl">Default I2C SCL Pin:</label>
|
||||
<input type="text" id="custom-board-scl" placeholder="e.g. SCL, D3">
|
||||
<label for="custom-board-SCL">Default I2C SCL Pin:</label>
|
||||
<input type="text" id="custom-board-SCL" placeholder="e.g. SCL, D3">
|
||||
</div>
|
||||
<div>
|
||||
<label for="custom-board-sda">Default I2C SDA Pin:</label>
|
||||
<input type="text" id="custom-board-sda" placeholder="e.g. SDA, D2">
|
||||
<label for="custom-board-SDA">Default I2C SDA Pin:</label>
|
||||
<input type="text" id="custom-board-SDA" placeholder="e.g. SDA, D2">
|
||||
</div>
|
||||
<button id="add-custom-board-btn">Add Custom Board</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="custom-boards-list" class="hidden">
|
||||
<h3>Added Custom Boards:</h3>
|
||||
<ul id="custom-boards-items">
|
||||
|
|
@ -527,33 +582,35 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Load release info -->
|
||||
<script src="firmware-data.js"></script>
|
||||
|
||||
<!-- Load the data loader script -->
|
||||
<script src="load-wippersnapper-data.js"></script>
|
||||
|
||||
|
||||
<!-- Load the original application script -->
|
||||
<script src="wippersnapper-config-builder.js"></script>
|
||||
|
||||
|
||||
<script>
|
||||
// Initialize functions from the original script
|
||||
|
||||
|
||||
// Tab Navigation functions
|
||||
function openTab(evt, tabName) {
|
||||
// Declare variables
|
||||
let i, tabcontent, tablinks;
|
||||
|
||||
|
||||
// Get all elements with class="tabcontent" and hide them
|
||||
tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].style.display = "none";
|
||||
}
|
||||
|
||||
|
||||
// Get all elements with class="tablinks" and remove the class "active"
|
||||
tablinks = document.getElementsByClassName("tablinks");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
||||
}
|
||||
|
||||
|
||||
// Show the current tab, and add an "active" class to the button that opened the tab
|
||||
document.getElementById(tabName).style.display = "block";
|
||||
if (evt) {
|
||||
|
|
@ -571,19 +628,19 @@
|
|||
function openComponentTab(evt, tabName) {
|
||||
// Declare variables
|
||||
let i, tabcontent, tablinks;
|
||||
|
||||
|
||||
// Get all elements with class="component-tabcontent" and hide them
|
||||
tabcontent = document.getElementsByClassName("component-tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].style.display = "none";
|
||||
}
|
||||
|
||||
|
||||
// Get all elements with class="comp-tab" and remove the class "active"
|
||||
tablinks = document.getElementsByClassName("comp-tab");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
||||
}
|
||||
|
||||
|
||||
// Show the current tab, and add an "active" class to the button that opened the tab
|
||||
document.getElementById(tabName).style.display = "block";
|
||||
if (evt) {
|
||||
|
|
@ -597,12 +654,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Initialize the application with data loading
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// This is handled by the load-wippersnapper-data.js script
|
||||
// It will call loadWippersnapperData() and initialize everything
|
||||
|
||||
|
||||
// Make sure only All Components tab is visible initially
|
||||
const tabcontents = document.getElementsByClassName("component-tabcontent");
|
||||
for (let i = 0; i < tabcontents.length; i++) {
|
||||
|
|
@ -611,14 +668,14 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Component search functionality
|
||||
document.querySelectorAll('.component-search').forEach(searchInput => {
|
||||
searchInput.addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
const componentType = this.id.split('-')[0]; // Extract type from ID (e.g., "i2c" from "i2c-search")
|
||||
const componentList = document.getElementById(`${componentType}-component-list`);
|
||||
|
||||
|
||||
// Filter components
|
||||
const components = componentList.querySelectorAll('.component-card');
|
||||
components.forEach(component => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Load Wippersnapper boards and components data
|
||||
|
||||
// Configuration
|
||||
const BOARDS_JSON_URL = 'https://raw.githubusercontent.com/tyeth/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/main/wippersnapper_boards.json'; //'wippersnapper_boards.json';
|
||||
const COMPONENTS_JSON_URL = 'https://raw.githubusercontent.com/tyeth/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/main/wippersnapper_components.json'; //'wippersnapper_components.json';
|
||||
const BOARDS_JSON_URL = 'https://raw.githubusercontent.com/tyeth/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/use_boards_sd_card/wippersnapper_boards.json'; //'wippersnapper_boards.json';
|
||||
const COMPONENTS_JSON_URL = 'https://raw.githubusercontent.com/tyeth/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/use_boards_sd_card/wippersnapper_components.json'; //'wippersnapper_components.json';
|
||||
|
||||
// Global app state
|
||||
const appState = {
|
||||
|
|
@ -10,6 +10,7 @@ const appState = {
|
|||
componentsData: null,
|
||||
isLoading: false,
|
||||
loadError: null,
|
||||
enableautoConfig: false,
|
||||
|
||||
// Application state properties (from original code)
|
||||
selectedBoard: null,
|
||||
|
|
@ -47,6 +48,14 @@ async function loadWippersnapperData() {
|
|||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
const boardsData = window['jsonBoardObject'];
|
||||
const componentsData = window['jsonComponentsObject'];
|
||||
const firmwareData = window['FIRMWARE_DATA'];
|
||||
if (firmwareData && firmwareData.releaseInfo) {
|
||||
// Update the release name and link in the UI
|
||||
document.getElementById('release_name').innerHTML = "(" + firmwareData.releaseInfo.publishedDate + ")<br/>" + firmwareData.releaseInfo.name;
|
||||
document.getElementById('release_name').href = firmwareData.releaseInfo.url;
|
||||
//TODO: set onchange for the boards to alter the download url in #firmware_file link
|
||||
}
|
||||
|
||||
appState.boardsData = boardsData.boards;
|
||||
appState.componentsData = componentsData.components;
|
||||
document.body.removeChild(componentsObject);
|
||||
|
|
@ -58,6 +67,10 @@ async function loadWippersnapperData() {
|
|||
appState.componentsData.i2c.push({
|
||||
id: 'pca9546',
|
||||
name: 'PCA9546 I2C Multiplexer',
|
||||
displayName: 'PCA9546 I2C Multiplexer',
|
||||
productURL: 'https://www.adafruit.com/product/5664',
|
||||
documentationURL: 'https://learn.adafruit.com/adafruit-pca9546-4-channel-stemma-qt-multiplexer',
|
||||
image: 'https://cdn-shop.adafruit.com/640x480/5664-00.jpg',
|
||||
address: '0x70',
|
||||
addresses: ['0x70', '0x71', '0x72', '0x73', '0x74', '0x75', '0x76', '0x77'],
|
||||
dataTypes: [],
|
||||
|
|
@ -68,6 +81,10 @@ async function loadWippersnapperData() {
|
|||
appState.componentsData.i2c.push({
|
||||
id: 'pca9548',
|
||||
name: 'PCA9548 I2C Multiplexer',
|
||||
displayName: 'PCA9548 I2C Multiplexer',
|
||||
productURL: 'https://www.adafruit.com/product/5626',
|
||||
documentationURL: 'https://learn.adafruit.com/adafruit-pca9548-8-channel-stemma-qt-qwiic-i2c-multiplexer',
|
||||
image: 'https://cdn-shop.adafruit.com/640x480/5626-06.jpg',
|
||||
address: '0x70',
|
||||
addresses: ['0x70', '0x71', '0x72', '0x73', '0x74', '0x75', '0x76', '0x77'],
|
||||
dataTypes: [],
|
||||
|
|
@ -78,6 +95,10 @@ async function loadWippersnapperData() {
|
|||
appState.componentsData.i2c.push({
|
||||
id: 'tca9546',
|
||||
name: 'TCA9546 I2C Multiplexer',
|
||||
displayName: 'TCA9546 4Ch I2C Multiplexer',
|
||||
productURL: 'https://www.adafruit.com/product/5663',
|
||||
documentationURL: 'https://learn.adafruit.com/adafruit-pca9546-4-channel-i2c-multiplexer',
|
||||
image: 'https://cdn-shop.adafruit.com/640x480/5663-04.jpg',
|
||||
address: '0x70',
|
||||
addresses: ['0x70', '0x71', '0x72', '0x73', '0x74', '0x75', '0x76', '0x77'],
|
||||
dataTypes: [],
|
||||
|
|
@ -88,6 +109,10 @@ async function loadWippersnapperData() {
|
|||
appState.componentsData.i2c.push({
|
||||
id: 'tca9548',
|
||||
name: 'TCA9548 I2C Multiplexer',
|
||||
displayName: 'TCA9548 8Ch I2C Multiplexer',
|
||||
productURL: 'https://www.adafruit.com/product/2717',
|
||||
documentationURL: 'https://learn.adafruit.com/adafruit-tca9548a-1-to-8-i2c-multiplexer-breakout',
|
||||
image: 'https://cdn-shop.adafruit.com/640x480/2717-00.jpg',
|
||||
address: '0x70',
|
||||
addresses: ['0x70', '0x71', '0x72', '0x73', '0x74', '0x75', '0x76', '0x77'],
|
||||
dataTypes: [],
|
||||
|
|
@ -136,16 +161,19 @@ function populateBoardSelect() {
|
|||
|
||||
// Filter boards to only include those with UF2 install method
|
||||
const filteredBoards = Object.entries(appState.boardsData)
|
||||
.filter(([boardId, board]) => ['uf2', 'web-native-usb'].includes(board.installMethod));
|
||||
.filter(([boardId, board]) => board.installMethod === 'uf2'); //['uf2', 'web-native-usb'].includes(board.installMethod)); //funhouse
|
||||
|
||||
// Sort boards by vendor and name
|
||||
const sortedBoards = filteredBoards
|
||||
.sort((a, b) => {
|
||||
const vendorA = a[1].vendor || '';
|
||||
const vendorB = b[1].vendor || '';
|
||||
|
||||
// Sort by vendor first
|
||||
if (vendorA !== vendorB) {
|
||||
if (vendorA === 'Generic') {
|
||||
return 1; // Sort Generic to the end
|
||||
} else if (vendorB === 'Generic') {
|
||||
return -1; // Sort Generic to the end
|
||||
} else if (vendorA !== vendorB) {
|
||||
// Sort by vendor first
|
||||
return vendorA.localeCompare(vendorB);
|
||||
}
|
||||
|
||||
|
|
@ -191,19 +219,14 @@ function convertBoardDataToConfig(boardId) {
|
|||
// Extract pin numbers from board data
|
||||
const pins = boardData.pins.map(pin => pin.number).filter(num => !isNaN(num));
|
||||
|
||||
boardConfig = boardData;
|
||||
// Create board config
|
||||
const boardConfig = {
|
||||
referenceVoltage: boardData.referenceVoltage,
|
||||
totalGPIOPins: boardData.totalGPIOPins,
|
||||
totalAnalogPins: boardData.totalAnalogPins || 0,
|
||||
defaultI2C: {
|
||||
scl: boardData.defaultI2C.SCL,
|
||||
sda: boardData.defaultI2C.SDA
|
||||
},
|
||||
pins: pins,
|
||||
displayName: boardData.displayName,
|
||||
image: boardData.image
|
||||
};
|
||||
boardConfig.totalAnalogPins= boardData.totalAnalogPins || 0;
|
||||
boardConfig.defaultI2C= {
|
||||
SCL: boardData.defaultI2C.SCL,
|
||||
SDA: boardData.defaultI2C.SDA
|
||||
};
|
||||
boardConfig.pins= pins;
|
||||
|
||||
return boardConfig;
|
||||
}
|
||||
|
|
@ -229,6 +252,7 @@ function convertComponentsDataToConfig() {
|
|||
componentsConfig.i2c.push({
|
||||
id: component.id,
|
||||
name: component.name,
|
||||
displayName: component.displayName || component.name,
|
||||
address: component.address || '0x00',
|
||||
addresses: component.addresses || [component.address || '0x00'],
|
||||
dataTypes: component.dataTypes || [],
|
||||
|
|
@ -242,6 +266,7 @@ function convertComponentsDataToConfig() {
|
|||
appState.componentsData.ds18x20.forEach(component => {
|
||||
componentsConfig.ds18x20.push({
|
||||
id: component.id,
|
||||
displayName: component.displayName || component.name,
|
||||
name: component.name,
|
||||
dataTypes: component.dataTypes || []
|
||||
});
|
||||
|
|
@ -253,6 +278,7 @@ function convertComponentsDataToConfig() {
|
|||
appState.componentsData.pin.forEach(component => {
|
||||
componentsConfig.pin.push({
|
||||
id: component.id,
|
||||
displayName: component.displayName || component.name,
|
||||
name: component.name,
|
||||
dataTypes: component.dataTypes || []
|
||||
});
|
||||
|
|
@ -264,6 +290,7 @@ function convertComponentsDataToConfig() {
|
|||
appState.componentsData.pixel.forEach(component => {
|
||||
componentsConfig.pixel.push({
|
||||
id: component.id,
|
||||
displayName: component.displayName || component.name,
|
||||
name: component.name,
|
||||
dataTypes: component.dataTypes || []
|
||||
});
|
||||
|
|
@ -275,6 +302,7 @@ function convertComponentsDataToConfig() {
|
|||
appState.componentsData.pwm.forEach(component => {
|
||||
componentsConfig.pwm.push({
|
||||
id: component.id,
|
||||
displayName: component.displayName || component.name,
|
||||
name: component.name,
|
||||
dataTypes: component.dataTypes || []
|
||||
});
|
||||
|
|
@ -286,6 +314,7 @@ function convertComponentsDataToConfig() {
|
|||
appState.componentsData.servo.forEach(component => {
|
||||
componentsConfig.servo.push({
|
||||
id: component.id,
|
||||
displayName: component.displayName || component.name,
|
||||
name: component.name,
|
||||
dataTypes: component.dataTypes || []
|
||||
});
|
||||
|
|
@ -297,6 +326,7 @@ function convertComponentsDataToConfig() {
|
|||
appState.componentsData.uart.forEach(component => {
|
||||
componentsConfig.uart.push({
|
||||
id: component.id,
|
||||
displayName: component.displayName || component.name,
|
||||
name: component.name,
|
||||
dataTypes: component.dataTypes || []
|
||||
});
|
||||
|
|
@ -310,81 +340,21 @@ function convertComponentsDataToConfig() {
|
|||
* Attach event listeners to the UI elements
|
||||
*/
|
||||
function attachEventListeners() {
|
||||
// Board selection handler
|
||||
document.getElementById('board-select').addEventListener('change', function() {
|
||||
const boardId = this.value;
|
||||
if (!boardId) {
|
||||
document.getElementById('board-details').classList.add('hidden');
|
||||
hideSubsequentSections();
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert board data to config format
|
||||
const boardConfig = convertBoardDataToConfig(boardId);
|
||||
appState.selectedBoard = {
|
||||
id: boardId,
|
||||
...boardConfig
|
||||
};
|
||||
|
||||
// Update board details display
|
||||
document.getElementById('ref-voltage').textContent = boardConfig.referenceVoltage;
|
||||
document.getElementById('total-gpio').textContent = boardConfig.totalGPIOPins;
|
||||
document.getElementById('total-analog').textContent = boardConfig.totalAnalogPins;
|
||||
document.getElementById('default-scl').textContent = boardConfig.defaultI2C.scl;
|
||||
document.getElementById('default-sda').textContent = boardConfig.defaultI2C.sda;
|
||||
document.getElementById('board-details').classList.remove('hidden');
|
||||
|
||||
// If there's a board image, show it
|
||||
const boardImageElem = document.getElementById('board-image');
|
||||
if (boardImageElem) {
|
||||
if (boardConfig.image) {
|
||||
if (!boardConfig.image.startsWith('http')) {
|
||||
boardImageElem.src = "https://raw.githubusercontent.com/adafruit/Wippersnapper_Boards/refs/heads/main/" + boardConfig.image;
|
||||
} else {
|
||||
boardImageElem.src = boardConfig.image;
|
||||
}
|
||||
boardImageElem.classList.remove('hidden');
|
||||
} else {
|
||||
boardImageElem.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Set up default I2C bus
|
||||
appState.i2cBuses = [{
|
||||
id: 'default',
|
||||
scl: boardConfig.defaultI2C.scl,
|
||||
sda: boardConfig.defaultI2C.sda
|
||||
}];
|
||||
|
||||
// Update default I2C bus display
|
||||
document.getElementById('default-i2c-scl').textContent = boardConfig.defaultI2C.scl;
|
||||
document.getElementById('default-i2c-sda').textContent = boardConfig.defaultI2C.sda;
|
||||
|
||||
// Mark default I2C pins as used
|
||||
appState.usedPins.add(boardConfig.defaultI2C.scl);
|
||||
appState.usedPins.add(boardConfig.defaultI2C.sda);
|
||||
|
||||
// Show companion board section
|
||||
document.getElementById('companion-board-section').classList.remove('hidden');
|
||||
|
||||
// Reset subsequent sections
|
||||
resetSubsequentSelections();
|
||||
|
||||
// Initialize SD and RTC sections based on board
|
||||
initializeManualConfig();
|
||||
|
||||
// Initialize pins lists for SD and I2C configuration
|
||||
populatePinsLists();
|
||||
|
||||
// Convert component data to config format
|
||||
const componentsConfig = convertComponentsDataToConfig();
|
||||
|
||||
// Initialize components sections with the loaded data
|
||||
populateComponentLists(componentsConfig);
|
||||
});
|
||||
// BOARD SELECTION HANDLER HAS BEEN REMOVED
|
||||
// The duplicate event handler from load-wippersnapper-data.js has been removed
|
||||
// to prevent conflicts with the handler in wippersnapper-config-builder.js
|
||||
|
||||
// Remaining event listeners should be added here or in the original script
|
||||
// ...
|
||||
// Instead, we'll prepare the data in the format expected by wippersnapper-config-builder.js
|
||||
console.log('Data loading complete, board selection handler is in wippersnapper-config-builder.js');
|
||||
|
||||
// Convert component data to config format
|
||||
const componentsConfig = convertComponentsDataToConfig();
|
||||
console.log('not using Components data converted to config format:', componentsConfig);
|
||||
// Update the components data in appState with the converted format
|
||||
// so it's ready for use in the other script
|
||||
// appState.componentsData = componentsConfig;
|
||||
|
||||
// No other event listeners needed here as they are handled in wippersnapper-config-builder.js
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
requests
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue