406 lines
15 KiB
JavaScript
406 lines
15 KiB
JavaScript
// Load Wippersnapper boards and components data
|
|
|
|
// Configuration - technically unused (instead ./ relative links) but useful for reference
|
|
const BOARDS_JSON_URL = 'https://raw.githubusercontent.com/adafruit/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/use_boards_sd_card/wippersnapper_boards.json'; //'wippersnapper_boards.json';
|
|
const COMPONENTS_JSON_URL = 'https://raw.githubusercontent.com/adafruit/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/use_boards_sd_card/wippersnapper_components.json'; //'wippersnapper_components.json';
|
|
|
|
// Global app state
|
|
const appState = {
|
|
boardsData: null,
|
|
componentsData: null,
|
|
isLoading: false,
|
|
loadError: null,
|
|
enableautoConfig: false,
|
|
|
|
// Application state properties (from original code)
|
|
selectedBoard: null,
|
|
companionBoard: null,
|
|
sdCardCS: null,
|
|
rtcType: 'soft',
|
|
statusLEDBrightness: 0.5,
|
|
i2cBuses: [],
|
|
i2cMultiplexers: [],
|
|
selectedComponents: [],
|
|
usedPins: new Set(),
|
|
nextComponentId: 1
|
|
};
|
|
|
|
/**
|
|
* Load the Wippersnapper boards and components data from JSON files
|
|
*/
|
|
async function loadWippersnapperData() {
|
|
try {
|
|
appState.isLoading = true;
|
|
console.log('Loading Wippersnapper data from local file copy');
|
|
// create <object id="jsonBoardObject" data="data.json" type="application/json"></object> in header
|
|
const jsonObject = document.createElement('script');
|
|
jsonObject.id = 'jsonBoardObject';
|
|
jsonObject.src = 'wippersnapper_boards.js';
|
|
document.body.appendChild(jsonObject);
|
|
console.log('Loaded boards data from local file copy');
|
|
// components
|
|
const componentsObject = document.createElement('script');
|
|
componentsObject.id = 'jsonComponentsObject';
|
|
componentsObject.src = 'wippersnapper_components.js';
|
|
document.body.appendChild(componentsObject);
|
|
console.log('Loaded components data from local file copy');
|
|
// Store data in app state
|
|
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);
|
|
document.body.removeChild(jsonObject);
|
|
|
|
// Add I2C multiplexer components manually since they're not in the JSON data
|
|
if (appState.componentsData.i2c) {
|
|
// Add PCA9546 - 4-channel I2C multiplexer
|
|
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: [],
|
|
channels: 4
|
|
});
|
|
|
|
// Add PCA9548 - 8-channel I2C multiplexer
|
|
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: [],
|
|
channels: 8
|
|
});
|
|
|
|
// Add TCA9546 - 4-channel I2C multiplexer
|
|
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: [],
|
|
channels: 4
|
|
});
|
|
|
|
// Add TCA9548 - 8-channel I2C multiplexer
|
|
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: [],
|
|
channels: 8
|
|
});
|
|
}
|
|
|
|
// Initialize the UI with the data
|
|
initializeUI();
|
|
|
|
console.log('Successfully loaded Wippersnapper data', {
|
|
boards: Object.keys(appState.boardsData).length,
|
|
components: Object.keys(appState.componentsData)
|
|
.filter(key => !key.endsWith('_metadata'))
|
|
.reduce((acc, key) => acc + appState.componentsData[key].length, 0)
|
|
});
|
|
|
|
appState.isLoading = false;
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Error loading Wippersnapper data:', error);
|
|
appState.loadError = error.message;
|
|
appState.isLoading = false;
|
|
showLoadError(error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the UI with the loaded data
|
|
*/
|
|
function initializeUI() {
|
|
// Populate board select dropdown
|
|
populateBoardSelect();
|
|
|
|
// Set up event listeners
|
|
attachEventListeners();
|
|
}
|
|
|
|
/**
|
|
* Populate the board select dropdown with available boards
|
|
*/
|
|
function populateBoardSelect() {
|
|
const boardSelect = document.getElementById('board-select');
|
|
boardSelect.innerHTML = '<option value="">-- Select a Board --</option>';
|
|
|
|
// Filter boards to only include those with UF2 install method
|
|
const filteredBoards = Object.entries(appState.boardsData)
|
|
.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 || '';
|
|
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);
|
|
}
|
|
|
|
// Then by display name
|
|
return a[1].displayName.localeCompare(b[1].displayName);
|
|
});
|
|
|
|
// Group boards by vendor
|
|
const boardsByVendor = {};
|
|
sortedBoards.forEach(([boardId, board]) => {
|
|
const vendor = board.vendor || 'Other';
|
|
if (!boardsByVendor[vendor]) {
|
|
boardsByVendor[vendor] = [];
|
|
}
|
|
boardsByVendor[vendor].push([boardId, board]);
|
|
});
|
|
|
|
// Add boards to select, grouped by vendor
|
|
Object.entries(boardsByVendor).forEach(([vendor, boards]) => {
|
|
const optgroup = document.createElement('optgroup');
|
|
optgroup.label = vendor;
|
|
|
|
boards.forEach(([boardId, board]) => {
|
|
const option = document.createElement('option');
|
|
option.value = boardId;
|
|
option.textContent = board.displayName;
|
|
optgroup.appendChild(option);
|
|
});
|
|
|
|
boardSelect.appendChild(optgroup);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Convert the loaded board data to the format expected by the application
|
|
* @param {string} boardId The ID of the selected board
|
|
* @returns {Object} The board configuration object
|
|
*/
|
|
function convertBoardDataToConfig(boardId) {
|
|
const boardData = appState.boardsData[boardId];
|
|
if (!boardData) return null;
|
|
|
|
// Extract pin numbers from board data
|
|
const pins = boardData.pins.map(pin => pin.number).filter(num => !isNaN(num));
|
|
|
|
boardConfig = boardData;
|
|
// Create board config
|
|
boardConfig.totalAnalogPins= boardData.totalAnalogPins || 0;
|
|
boardConfig.defaultI2C= {
|
|
SCL: boardData.defaultI2C.SCL,
|
|
SDA: boardData.defaultI2C.SDA
|
|
};
|
|
boardConfig.pins= pins;
|
|
|
|
return boardConfig;
|
|
}
|
|
|
|
/**
|
|
* Convert the loaded component data to the format expected by the application
|
|
* @returns {Object} The components configuration object
|
|
*/
|
|
function convertComponentsDataToConfig() {
|
|
const componentsConfig = {
|
|
i2c: [],
|
|
ds18x20: [],
|
|
pin: [],
|
|
pixel: [],
|
|
pwm: [],
|
|
servo: [],
|
|
uart: []
|
|
};
|
|
|
|
// Process I2C components
|
|
if (appState.componentsData.i2c) {
|
|
appState.componentsData.i2c.forEach(component => {
|
|
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 || [],
|
|
channels: component.channels || 0
|
|
});
|
|
});
|
|
}
|
|
|
|
// Process DS18x20 components
|
|
if (appState.componentsData.ds18x20) {
|
|
appState.componentsData.ds18x20.forEach(component => {
|
|
componentsConfig.ds18x20.push({
|
|
id: component.id,
|
|
displayName: component.displayName || component.name,
|
|
name: component.name,
|
|
dataTypes: component.dataTypes || []
|
|
});
|
|
});
|
|
}
|
|
|
|
// Process Pin components
|
|
if (appState.componentsData.pin) {
|
|
appState.componentsData.pin.forEach(component => {
|
|
componentsConfig.pin.push({
|
|
id: component.id,
|
|
displayName: component.displayName || component.name,
|
|
name: component.name,
|
|
dataTypes: component.dataTypes || []
|
|
});
|
|
});
|
|
}
|
|
|
|
// Process Pixel components
|
|
if (appState.componentsData.pixel) {
|
|
appState.componentsData.pixel.forEach(component => {
|
|
componentsConfig.pixel.push({
|
|
id: component.id,
|
|
displayName: component.displayName || component.name,
|
|
name: component.name,
|
|
dataTypes: component.dataTypes || []
|
|
});
|
|
});
|
|
}
|
|
|
|
// Process PWM components
|
|
if (appState.componentsData.pwm) {
|
|
appState.componentsData.pwm.forEach(component => {
|
|
componentsConfig.pwm.push({
|
|
id: component.id,
|
|
displayName: component.displayName || component.name,
|
|
name: component.name,
|
|
dataTypes: component.dataTypes || []
|
|
});
|
|
});
|
|
}
|
|
|
|
// Process Servo components
|
|
if (appState.componentsData.servo) {
|
|
appState.componentsData.servo.forEach(component => {
|
|
componentsConfig.servo.push({
|
|
id: component.id,
|
|
displayName: component.displayName || component.name,
|
|
name: component.name,
|
|
dataTypes: component.dataTypes || []
|
|
});
|
|
});
|
|
}
|
|
|
|
// Process UART components
|
|
if (appState.componentsData.uart) {
|
|
appState.componentsData.uart.forEach(component => {
|
|
componentsConfig.uart.push({
|
|
id: component.id,
|
|
displayName: component.displayName || component.name,
|
|
name: component.name,
|
|
dataTypes: component.dataTypes || []
|
|
});
|
|
});
|
|
}
|
|
|
|
return componentsConfig;
|
|
}
|
|
|
|
/**
|
|
* Attach event listeners to the UI elements
|
|
*/
|
|
function attachEventListeners() {
|
|
// 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
|
|
|
|
// 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
|
|
}
|
|
|
|
/**
|
|
* Display an error message to the user when data loading fails
|
|
* @param {string} message The error message to display
|
|
*/
|
|
function showLoadError(message) {
|
|
// Create or update an error message element
|
|
let errorElem = document.getElementById('load-error');
|
|
|
|
if (!errorElem) {
|
|
errorElem = document.createElement('div');
|
|
errorElem.id = 'load-error';
|
|
errorElem.style.backgroundColor = '#ffdddd';
|
|
errorElem.style.color = '#cc0000';
|
|
errorElem.style.padding = '15px';
|
|
errorElem.style.margin = '15px 0';
|
|
errorElem.style.borderRadius = '5px';
|
|
|
|
// Insert at the top of the body
|
|
document.body.insertBefore(errorElem, document.body.firstChild);
|
|
}
|
|
|
|
errorElem.innerHTML = `
|
|
<h3>Error Loading Data</h3>
|
|
<p>${message}</p>
|
|
<p>Please check that the JSON files are available and properly formatted.</p>
|
|
<button onclick="retryLoading()">Retry</button>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Retry loading the Wippersnapper data
|
|
*/
|
|
function retryLoading() {
|
|
// Remove the error message
|
|
const errorElem = document.getElementById('load-error');
|
|
if (errorElem) {
|
|
errorElem.remove();
|
|
}
|
|
|
|
// Try loading again
|
|
loadWippersnapperData();
|
|
}
|
|
|
|
// Initialize the application by loading the data when the document is ready
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadWippersnapperData();
|
|
});
|