Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
d490b116b9 | |||
5bc6192b6f | |||
2202d9a1aa | |||
7dbca0ab87 | |||
24b3521f83 | |||
6c9f290bac | |||
|
eab937d6ca | ||
|
27ef8399e4 | ||
2920159f32 | |||
2e19bccfa9 | |||
859e89431e | |||
6dc26ca51f | |||
0becae7ed6 | |||
3d31833f50 | |||
599cc47443 | |||
b1f7923770 | |||
c78c20979d | |||
e79c522e46 | |||
cf8cce72a5 | |||
0b356609d1 | |||
01f1e123ac | |||
012f91851e |
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,5 +1,47 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.5.4] - 2025-07-22
|
||||||
|
### Added
|
||||||
|
- Adds new feature to write and read location tags
|
||||||
|
- Adds slight debouncing to the scale loop weight logic
|
||||||
|
- add loadcell desc.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- update platformio.ini for version v1.5.4
|
||||||
|
- Merge branch 'main' of github.com:ManuelW77/Filaman
|
||||||
|
- Merge pull request #39 from janecker/location_tags
|
||||||
|
- Merge pull request #38 from janecker/scale_debouncing
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- uncomment monitor_port configuration in platformio.ini
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.3] - 2025-04-25
|
||||||
|
### Changed
|
||||||
|
- update platformio.ini for version v1.5.3
|
||||||
|
- Affiliate Links
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- update spool weight conditionally based on NFC ID
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.2] - 2025-04-23
|
||||||
|
### Added
|
||||||
|
- implement multi-color filament display and styles for dropdown options
|
||||||
|
- add remaining weight logging for PUT requests and improve error reporting in sendToApi function
|
||||||
|
- add remaining weight logging and display after successful spool update
|
||||||
|
- add weight field to update payload in updateSpoolTagId function
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- update platformio.ini for version v1.5.2
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- update weight field in update payload to only include values greater than 10
|
||||||
|
- increase stack size for sendToApi task to improve stability
|
||||||
|
- adjust tare weight tolerance to ignore deviations of 2g
|
||||||
|
- improve weight stability check before sending to API
|
||||||
|
|
||||||
|
|
||||||
## [1.5.1] - 2025-03-30
|
## [1.5.1] - 2025-03-30
|
||||||
### Changed
|
### Changed
|
||||||
- update version to 1.5.1 and improve OTA update handling with task management
|
- update version to 1.5.1 and improve OTA update handling with task management
|
||||||
|
@@ -54,7 +54,7 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
|
|||||||
|
|
||||||
## Hardware-Anforderungen
|
## Hardware-Anforderungen
|
||||||
|
|
||||||
### Komponenten
|
### Komponenten (Affiliate Links)
|
||||||
- **ESP32 Development Board:** Any ESP32 variant.
|
- **ESP32 Development Board:** Any ESP32 variant.
|
||||||
[Amazon Link](https://amzn.to/3FHea6D)
|
[Amazon Link](https://amzn.to/3FHea6D)
|
||||||
- **HX711 5kg Load Cell Amplifier:** For weight measurement.
|
- **HX711 5kg Load Cell Amplifier:** For weight measurement.
|
||||||
@@ -90,6 +90,12 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
*Die Wägezelle wird bei den meisten HX711 Modulen folgendermaßen verkabelt:
|
||||||
|
E+ rot
|
||||||
|
E- schwarz
|
||||||
|
A- weiß
|
||||||
|
A+ grün*
|
||||||
|
|
||||||
## Software-Abhängigkeiten
|
## Software-Abhängigkeiten
|
||||||
|
|
||||||
### ESP32-Bibliotheken
|
### ESP32-Bibliotheken
|
||||||
|
@@ -58,7 +58,7 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
|
|||||||
|
|
||||||
## Hardware Requirements
|
## Hardware Requirements
|
||||||
|
|
||||||
### Components
|
### Components (Affiliate Links)
|
||||||
- **ESP32 Development Board:** Any ESP32 variant.
|
- **ESP32 Development Board:** Any ESP32 variant.
|
||||||
[Amazon Link](https://amzn.to/3FHea6D)
|
[Amazon Link](https://amzn.to/3FHea6D)
|
||||||
- **HX711 5kg Load Cell Amplifier:** For weight measurement.
|
- **HX711 5kg Load Cell Amplifier:** For weight measurement.
|
||||||
@@ -94,6 +94,12 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
*The load cell is connected to most HX711 modules as follows:
|
||||||
|
E+ red
|
||||||
|
E- black
|
||||||
|
A- white
|
||||||
|
A+ green*
|
||||||
|
|
||||||
## Software Dependencies
|
## Software Dependencies
|
||||||
|
|
||||||
### ESP32 Libraries
|
### ESP32 Libraries
|
||||||
|
@@ -139,6 +139,18 @@
|
|||||||
<p id="nfcInfo" class="nfc-status"></p>
|
<p id="nfcInfo" class="nfc-status"></p>
|
||||||
<button id="writeNfcButton" class="btn btn-primary hidden" onclick="writeNfcTag()">Write Tag</button>
|
<button id="writeNfcButton" class="btn btn-primary hidden" onclick="writeNfcTag()">Write Tag</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-box">
|
||||||
|
<h2>Spoolman Locations</h2>
|
||||||
|
<label for="locationSelect">Location:</label>
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<select id="locationSelect" class="styled-select">
|
||||||
|
<option value="">Please choose...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p id="nfcInfoLocation" class="nfc-status"></p>
|
||||||
|
<button id="writeLocationNfcButton" class="btn btn-primary hidden" onclick="writeLocationNfcTag()">Write Location Tag</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
78
html/rfid.js
78
html/rfid.js
@@ -215,20 +215,6 @@ document.addEventListener('filamentSelected', function (event) {
|
|||||||
updateSpoolButtons(selectedText !== "Please choose...");
|
updateSpoolButtons(selectedText !== "Please choose...");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hilfsfunktion für kontrastreiche Textfarbe
|
|
||||||
function getContrastColor(hexcolor) {
|
|
||||||
// Konvertiere Hex zu RGB
|
|
||||||
const r = parseInt(hexcolor.substr(0,2),16);
|
|
||||||
const g = parseInt(hexcolor.substr(2,2),16);
|
|
||||||
const b = parseInt(hexcolor.substr(4,2),16);
|
|
||||||
|
|
||||||
// Berechne Helligkeit (YIQ Formel)
|
|
||||||
const yiq = ((r*299)+(g*587)+(b*114))/1000;
|
|
||||||
|
|
||||||
// Return schwarz oder weiß basierend auf Helligkeit
|
|
||||||
return (yiq >= 128) ? '#000000' : '#FFFFFF';
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNfcInfo() {
|
function updateNfcInfo() {
|
||||||
const selectedText = document.getElementById("selected-filament").textContent;
|
const selectedText = document.getElementById("selected-filament").textContent;
|
||||||
const nfcInfo = document.getElementById("nfcInfo");
|
const nfcInfo = document.getElementById("nfcInfo");
|
||||||
@@ -640,11 +626,11 @@ function writeNfcTag() {
|
|||||||
|
|
||||||
// Erstelle das NFC-Datenpaket mit korrekten Datentypen
|
// Erstelle das NFC-Datenpaket mit korrekten Datentypen
|
||||||
const nfcData = {
|
const nfcData = {
|
||||||
color_hex: selectedSpool.filament.color_hex || "FFFFFF",
|
//color_hex: selectedSpool.filament.color_hex || "FFFFFF",
|
||||||
type: selectedSpool.filament.material,
|
//type: selectedSpool.filament.material,
|
||||||
min_temp: minTemp,
|
//min_temp: minTemp,
|
||||||
max_temp: maxTemp,
|
//max_temp: maxTemp,
|
||||||
brand: selectedSpool.filament.vendor.name,
|
//brand: selectedSpool.filament.vendor.name,
|
||||||
sm_id: String(selectedSpool.id) // Konvertiere zu String
|
sm_id: String(selectedSpool.id) // Konvertiere zu String
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -661,16 +647,56 @@ function writeNfcTag() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function writeLocationNfcTag() {
|
||||||
|
const selectedText = document.getElementById("locationSelect").value;
|
||||||
|
if (selectedText === "Please choose...") {
|
||||||
|
alert('Please select a location first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Erstelle das NFC-Datenpaket mit korrekten Datentypen
|
||||||
|
const nfcData = {
|
||||||
|
location: String(selectedText)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (socket?.readyState === WebSocket.OPEN) {
|
||||||
|
const writeButton = document.getElementById("writeLocationNfcButton");
|
||||||
|
writeButton.classList.add("writing");
|
||||||
|
writeButton.textContent = "Writing";
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
type: 'writeNfcTag',
|
||||||
|
payload: nfcData
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
alert('Not connected to Server. Please check connection.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleWriteNfcTagResponse(success) {
|
function handleWriteNfcTagResponse(success) {
|
||||||
const writeButton = document.getElementById("writeNfcButton");
|
const writeButton = document.getElementById("writeNfcButton");
|
||||||
writeButton.classList.remove("writing");
|
const writeLocationButton = document.getElementById("writeLocationNfcButton");
|
||||||
writeButton.classList.add(success ? "success" : "error");
|
if(writeButton.classList.contains("writing")){
|
||||||
writeButton.textContent = success ? "Write success" : "Write failed";
|
writeButton.classList.remove("writing");
|
||||||
|
writeButton.classList.add(success ? "success" : "error");
|
||||||
|
writeButton.textContent = success ? "Write success" : "Write failed";
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
writeButton.classList.remove("success", "error");
|
writeButton.classList.remove("success", "error");
|
||||||
writeButton.textContent = "Write Tag";
|
writeButton.textContent = "Write Tag";
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(writeLocationButton.classList.contains("writing")){
|
||||||
|
writeLocationButton.classList.remove("writing");
|
||||||
|
writeLocationButton.classList.add(success ? "success" : "error");
|
||||||
|
writeLocationButton.textContent = success ? "Write success" : "Write failed";
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
writeLocationButton.classList.remove("success", "error");
|
||||||
|
writeLocationButton.textContent = "Write Location Tag";
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showNotification(message, isSuccess) {
|
function showNotification(message, isSuccess) {
|
||||||
|
118
html/spoolman.js
118
html/spoolman.js
@@ -1,6 +1,7 @@
|
|||||||
// Globale Variablen
|
// Globale Variablen
|
||||||
let spoolmanUrl = '';
|
let spoolmanUrl = '';
|
||||||
let spoolsData = [];
|
let spoolsData = [];
|
||||||
|
let locationData = [];
|
||||||
|
|
||||||
// Hilfsfunktionen für Datenmanipulation
|
// Hilfsfunktionen für Datenmanipulation
|
||||||
function processSpoolData(data) {
|
function processSpoolData(data) {
|
||||||
@@ -133,6 +134,26 @@ function populateVendorDropdown(data, selectedSmId = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dropdown-Funktionen
|
||||||
|
function populateLocationDropdown(data) {
|
||||||
|
const locationSelect = document.getElementById("locationSelect");
|
||||||
|
if (!locationSelect) {
|
||||||
|
console.error('locationSelect Element nicht gefunden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
locationSelect.innerHTML = '<option value="">Bitte wählen...</option>';
|
||||||
|
// Dropdown mit gefilterten Herstellern befüllen - alphabetisch sortiert
|
||||||
|
Object.entries(data)
|
||||||
|
.sort(([, nameA], [, nameB]) => nameA.localeCompare(nameB)) // Sort vendors alphabetically by name
|
||||||
|
.forEach(([id, name]) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = name;
|
||||||
|
option.textContent = name;
|
||||||
|
locationSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function updateFilamentDropdown(selectedSmId = null) {
|
function updateFilamentDropdown(selectedSmId = null) {
|
||||||
const vendorId = document.getElementById("vendorSelect").value;
|
const vendorId = document.getElementById("vendorSelect").value;
|
||||||
const dropdownContentInner = document.getElementById("filament-dropdown-content");
|
const dropdownContentInner = document.getElementById("filament-dropdown-content");
|
||||||
@@ -169,9 +190,32 @@ function updateFilamentDropdown(selectedSmId = null) {
|
|||||||
option.setAttribute("data-value", spool.filament.id);
|
option.setAttribute("data-value", spool.filament.id);
|
||||||
option.setAttribute("data-nfc-id", spool.extra.nfc_id || "");
|
option.setAttribute("data-nfc-id", spool.extra.nfc_id || "");
|
||||||
|
|
||||||
const colorHex = spool.filament.color_hex || 'FFFFFF';
|
|
||||||
|
// Generate color representation based on filament type (single or multi color)
|
||||||
|
let colorHTML = '';
|
||||||
|
|
||||||
|
// Check if this is a multicolor filament
|
||||||
|
if (spool.filament.multi_color_hexes) {
|
||||||
|
// Parse multi color hexes from comma-separated string
|
||||||
|
const colors = spool.filament.multi_color_hexes.replace(/#/g, '').split(',');
|
||||||
|
|
||||||
|
// Determine the display style based on direction
|
||||||
|
const direction = spool.filament.multi_color_direction || 'coaxial';
|
||||||
|
|
||||||
|
// Generate color circles for each color
|
||||||
|
colorHTML = '<div class="option-colors">';
|
||||||
|
colors.forEach(color => {
|
||||||
|
colorHTML += `<div class="option-color multi-color ${direction}" style="background-color: #${color}"></div>`;
|
||||||
|
});
|
||||||
|
colorHTML += '</div>';
|
||||||
|
} else {
|
||||||
|
// Single color filament
|
||||||
|
const colorHex = spool.filament.color_hex || 'FFFFFF';
|
||||||
|
colorHTML = `<div class="option-color" style="background-color: #${colorHex}"></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
option.innerHTML = `
|
option.innerHTML = `
|
||||||
<div class="option-color" style="background-color: #${colorHex}"></div>
|
${colorHTML}
|
||||||
<span>${spool.id} | ${spool.filament.name} (${spool.filament.material})</span>
|
<span>${spool.id} | ${spool.filament.name} (${spool.filament.material})</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -185,12 +229,41 @@ function updateFilamentDropdown(selectedSmId = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateLocationSelect(){
|
||||||
|
const writeLocationNfcButton = document.getElementById('writeLocationNfcButton');
|
||||||
|
if(writeLocationNfcButton){
|
||||||
|
writeLocationNfcButton.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function selectFilament(spool) {
|
function selectFilament(spool) {
|
||||||
const selectedColor = document.getElementById("selected-color");
|
const selectedColor = document.getElementById("selected-color");
|
||||||
const selectedText = document.getElementById("selected-filament");
|
const selectedText = document.getElementById("selected-filament");
|
||||||
const dropdownContent = document.getElementById("filament-dropdown-content");
|
const dropdownContent = document.getElementById("filament-dropdown-content");
|
||||||
|
|
||||||
selectedColor.style.backgroundColor = `#${spool.filament.color_hex || 'FFFFFF'}`;
|
// Update the selected color display
|
||||||
|
if (spool.filament.multi_color_hexes) {
|
||||||
|
// Handle multicolor filament display in the selection header
|
||||||
|
const colors = spool.filament.multi_color_hexes.replace(/#/g, '').split(',');
|
||||||
|
const direction = spool.filament.multi_color_direction || 'coaxial';
|
||||||
|
|
||||||
|
// Replace the single color div with multiple color divs
|
||||||
|
selectedColor.innerHTML = '';
|
||||||
|
colors.forEach(color => {
|
||||||
|
const colorDiv = document.createElement('div');
|
||||||
|
colorDiv.className = `color-segment multi-color ${direction}`;
|
||||||
|
colorDiv.style.backgroundColor = `#${color}`;
|
||||||
|
selectedColor.appendChild(colorDiv);
|
||||||
|
});
|
||||||
|
// Add multiple color class to the container
|
||||||
|
selectedColor.classList.add('multi-color-container');
|
||||||
|
} else {
|
||||||
|
// Single color filament - reset to default display
|
||||||
|
selectedColor.innerHTML = '';
|
||||||
|
selectedColor.classList.remove('multi-color-container');
|
||||||
|
selectedColor.style.backgroundColor = `#${spool.filament.color_hex || 'FFFFFF'}`;
|
||||||
|
}
|
||||||
|
|
||||||
selectedText.textContent = `${spool.id} | ${spool.filament.name} (${spool.filament.material})`;
|
selectedText.textContent = `${spool.id} | ${spool.filament.name} (${spool.filament.material})`;
|
||||||
dropdownContent.classList.remove("show");
|
dropdownContent.classList.remove("show");
|
||||||
|
|
||||||
@@ -216,10 +289,18 @@ async function initSpoolman() {
|
|||||||
|
|
||||||
const fetchedData = await fetchSpoolData();
|
const fetchedData = await fetchSpoolData();
|
||||||
spoolsData = processSpoolData(fetchedData);
|
spoolsData = processSpoolData(fetchedData);
|
||||||
|
|
||||||
document.dispatchEvent(new CustomEvent('spoolDataLoaded', {
|
document.dispatchEvent(new CustomEvent('spoolDataLoaded', {
|
||||||
detail: spoolsData
|
detail: spoolsData
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
locationData = await fetchLocationData();
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent('locationDataLoaded', {
|
||||||
|
detail: locationData
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Initialisieren von Spoolman:', error);
|
console.error('Fehler beim Initialisieren von Spoolman:', error);
|
||||||
document.dispatchEvent(new CustomEvent('spoolmanError', {
|
document.dispatchEvent(new CustomEvent('spoolmanError', {
|
||||||
@@ -247,6 +328,25 @@ async function fetchSpoolData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchLocationData() {
|
||||||
|
try {
|
||||||
|
if (!spoolmanUrl) {
|
||||||
|
throw new Error('Spoolman URL ist nicht initialisiert');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${spoolmanUrl}/api/v1/location`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Abrufen der Location-Daten:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Event Listener
|
// Event Listener
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initSpoolman();
|
initSpoolman();
|
||||||
@@ -255,6 +355,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (vendorSelect) {
|
if (vendorSelect) {
|
||||||
vendorSelect.addEventListener('change', () => updateFilamentDropdown());
|
vendorSelect.addEventListener('change', () => updateFilamentDropdown());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const locationSelect = document.getElementById('locationSelect');
|
||||||
|
if (locationSelect) {
|
||||||
|
locationSelect.addEventListener('change', () => updateLocationSelect());
|
||||||
|
}
|
||||||
|
|
||||||
const onlyWithoutSmId = document.getElementById('onlyWithoutSmId');
|
const onlyWithoutSmId = document.getElementById('onlyWithoutSmId');
|
||||||
if (onlyWithoutSmId) {
|
if (onlyWithoutSmId) {
|
||||||
@@ -267,6 +372,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
document.addEventListener('spoolDataLoaded', (event) => {
|
document.addEventListener('spoolDataLoaded', (event) => {
|
||||||
populateVendorDropdown(event.detail);
|
populateVendorDropdown(event.detail);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener('locationDataLoaded', (event) => {
|
||||||
|
populateLocationDropdown(event.detail);
|
||||||
|
});
|
||||||
|
|
||||||
window.onclick = function(event) {
|
window.onclick = function(event) {
|
||||||
if (!event.target.closest('.custom-dropdown')) {
|
if (!event.target.closest('.custom-dropdown')) {
|
||||||
@@ -297,6 +406,7 @@ window.getSpoolData = () => spoolsData;
|
|||||||
window.setSpoolData = (data) => { spoolsData = data; };
|
window.setSpoolData = (data) => { spoolsData = data; };
|
||||||
window.reloadSpoolData = initSpoolman;
|
window.reloadSpoolData = initSpoolman;
|
||||||
window.populateVendorDropdown = populateVendorDropdown;
|
window.populateVendorDropdown = populateVendorDropdown;
|
||||||
|
window.populateLocationDropdown = populateLocationDropdown;
|
||||||
window.updateFilamentDropdown = updateFilamentDropdown;
|
window.updateFilamentDropdown = updateFilamentDropdown;
|
||||||
window.toggleFilamentDropdown = () => {
|
window.toggleFilamentDropdown = () => {
|
||||||
const content = document.getElementById("filament-dropdown-content");
|
const content = document.getElementById("filament-dropdown-content");
|
||||||
|
@@ -759,6 +759,50 @@ a:hover {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Multi-color filament styles */
|
||||||
|
.option-colors {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-color {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Coaxial pattern (horizontal stripes) */
|
||||||
|
.multi-color.coaxial {
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Longitudinal pattern (vertical stripes) */
|
||||||
|
.multi-color.longitudinal {
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container for multiple colors in selected display */
|
||||||
|
.multi-color-container {
|
||||||
|
display: flex !important;
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-segment {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
@@ -927,31 +971,35 @@ input[type="submit"]:disabled,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Schreib-Button */
|
/* Schreib-Button */
|
||||||
#writeNfcButton {
|
#writeNfcButton, #writeLocationNfcButton {
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
transition: background-color 0.3s, color 0.3s;
|
transition: background-color 0.3s, color 0.3s;
|
||||||
width: 160px;
|
width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#writeNfcButton.writing {
|
#writeNfcButton.writing, #writeLocationNfcButton.writing {
|
||||||
background-color: #ffc107;
|
background-color: #ffc107;
|
||||||
color: black;
|
color: black;
|
||||||
width: 160px;
|
width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#writeNfcButton.success {
|
#writeNfcButton.success, #writeLocationNfcButton.success {
|
||||||
background-color: #28a745;
|
background-color: #28a745;
|
||||||
color: white;
|
color: white;
|
||||||
width: 160px;
|
width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#writeNfcButton.error {
|
#writeNfcButton.error, #writeLocationNfcButton.error {
|
||||||
background-color: #dc3545;
|
background-color: #dc3545;
|
||||||
color: white;
|
color: white;
|
||||||
width: 160px;
|
width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#writeLocationNfcButton{
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes dots {
|
@keyframes dots {
|
||||||
0% { content: ""; }
|
0% { content: ""; }
|
||||||
33% { content: "."; }
|
33% { content: "."; }
|
||||||
@@ -959,7 +1007,7 @@ input[type="submit"]:disabled,
|
|||||||
100% { content: "..."; }
|
100% { content: "..."; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#writeNfcButton.writing::after {
|
#writeNfcButton.writing::after, #writeLocationNfcButton.writing::after {
|
||||||
content: "...";
|
content: "...";
|
||||||
animation: dots 1s steps(3, end) infinite;
|
animation: dots 1s steps(3, end) infinite;
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
; https://docs.platformio.org/page/projectconf.html
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
[common]
|
[common]
|
||||||
version = "1.5.1"
|
version = "1.5.4"
|
||||||
to_old_version = "1.5.0"
|
to_old_version = "1.5.0"
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -18,6 +18,7 @@ platform = espressif32
|
|||||||
board = esp32dev
|
board = esp32dev
|
||||||
framework = arduino
|
framework = arduino
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
|
#monitor_port = /dev/cu.usbmodem01
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
tzapu/WiFiManager @ ^2.0.17
|
tzapu/WiFiManager @ ^2.0.17
|
||||||
|
83
src/api.cpp
83
src/api.cpp
@@ -11,6 +11,7 @@ String octoUrl = "";
|
|||||||
String octoToken = "";
|
String octoToken = "";
|
||||||
|
|
||||||
struct SendToApiParams {
|
struct SendToApiParams {
|
||||||
|
SpoolmanApiRequestType requestType;
|
||||||
String httpType;
|
String httpType;
|
||||||
String spoolsUrl;
|
String spoolsUrl;
|
||||||
String updatePayload;
|
String updatePayload;
|
||||||
@@ -90,6 +91,7 @@ void sendToApi(void *parameter) {
|
|||||||
SendToApiParams* params = (SendToApiParams*)parameter;
|
SendToApiParams* params = (SendToApiParams*)parameter;
|
||||||
|
|
||||||
// Extrahiere die Werte
|
// Extrahiere die Werte
|
||||||
|
SpoolmanApiRequestType requestType = params->requestType;
|
||||||
String httpType = params->httpType;
|
String httpType = params->httpType;
|
||||||
String spoolsUrl = params->spoolsUrl;
|
String spoolsUrl = params->spoolsUrl;
|
||||||
String updatePayload = params->updatePayload;
|
String updatePayload = params->updatePayload;
|
||||||
@@ -109,8 +111,31 @@ void sendToApi(void *parameter) {
|
|||||||
|
|
||||||
if (httpCode == HTTP_CODE_OK) {
|
if (httpCode == HTTP_CODE_OK) {
|
||||||
Serial.println("Spoolman erfolgreich aktualisiert");
|
Serial.println("Spoolman erfolgreich aktualisiert");
|
||||||
|
|
||||||
|
// Restgewicht der Spule auslesen
|
||||||
|
String payload = http.getString();
|
||||||
|
JsonDocument doc;
|
||||||
|
DeserializationError error = deserializeJson(doc, payload);
|
||||||
|
if (error) {
|
||||||
|
Serial.print("Fehler beim Parsen der JSON-Antwort: ");
|
||||||
|
Serial.println(error.c_str());
|
||||||
|
} else {
|
||||||
|
if (requestType == API_REQUEST_SPOOL_WEIGHT_UPDATE) {
|
||||||
|
uint16_t remaining_weight = doc["remaining_weight"].as<float>();
|
||||||
|
Serial.print("Aktuelles Gewicht: ");
|
||||||
|
Serial.println(remaining_weight);
|
||||||
|
oledShowMessage("Remaining: " + String(remaining_weight) + "g");
|
||||||
|
}
|
||||||
|
else if ( requestType == API_REQUEST_SPOOL_LOCATION_UPDATE) {
|
||||||
|
oledShowMessage("Location updated!");
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(3000 / portTICK_PERIOD_MS);
|
||||||
|
doc.clear();
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Serial.println("Fehler beim Senden an Spoolman!");
|
Serial.println("Fehler beim Senden an Spoolman! HTTP Code: " + String(httpCode));
|
||||||
oledShowMessage("Spoolman update failed");
|
oledShowMessage("Spoolman update failed");
|
||||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||||
}
|
}
|
||||||
@@ -158,6 +183,7 @@ bool updateSpoolTagId(String uidString, const char* payload) {
|
|||||||
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
|
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
params->requestType = API_REQUEST_SPOOL_TAG_ID_UPDATE;
|
||||||
params->httpType = "PATCH";
|
params->httpType = "PATCH";
|
||||||
params->spoolsUrl = spoolsUrl;
|
params->spoolsUrl = spoolsUrl;
|
||||||
params->updatePayload = updatePayload;
|
params->updatePayload = updatePayload;
|
||||||
@@ -166,7 +192,7 @@ bool updateSpoolTagId(String uidString, const char* payload) {
|
|||||||
BaseType_t result = xTaskCreate(
|
BaseType_t result = xTaskCreate(
|
||||||
sendToApi, // Task-Funktion
|
sendToApi, // Task-Funktion
|
||||||
"SendToApiTask", // Task-Name
|
"SendToApiTask", // Task-Name
|
||||||
4096, // Stackgröße in Bytes
|
6144, // Stackgröße in Bytes
|
||||||
(void*)params, // Parameter
|
(void*)params, // Parameter
|
||||||
0, // Priorität
|
0, // Priorität
|
||||||
NULL // Task-Handle (nicht benötigt)
|
NULL // Task-Handle (nicht benötigt)
|
||||||
@@ -174,6 +200,9 @@ bool updateSpoolTagId(String uidString, const char* payload) {
|
|||||||
|
|
||||||
updateDoc.clear();
|
updateDoc.clear();
|
||||||
|
|
||||||
|
// Update Spool weight
|
||||||
|
if (weight > 10) updateSpoolWeight(doc["sm_id"].as<String>(), weight);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +225,7 @@ uint8_t updateSpoolWeight(String spoolId, uint16_t weight) {
|
|||||||
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
|
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
params->requestType = API_REQUEST_SPOOL_WEIGHT_UPDATE;
|
||||||
params->httpType = "PUT";
|
params->httpType = "PUT";
|
||||||
params->spoolsUrl = spoolsUrl;
|
params->spoolsUrl = spoolsUrl;
|
||||||
params->updatePayload = updatePayload;
|
params->updatePayload = updatePayload;
|
||||||
@@ -204,7 +234,46 @@ uint8_t updateSpoolWeight(String spoolId, uint16_t weight) {
|
|||||||
BaseType_t result = xTaskCreate(
|
BaseType_t result = xTaskCreate(
|
||||||
sendToApi, // Task-Funktion
|
sendToApi, // Task-Funktion
|
||||||
"SendToApiTask", // Task-Name
|
"SendToApiTask", // Task-Name
|
||||||
4096, // Stackgröße in Bytes
|
6144, // Stackgröße in Bytes
|
||||||
|
(void*)params, // Parameter
|
||||||
|
0, // Priorität
|
||||||
|
NULL // Task-Handle (nicht benötigt)
|
||||||
|
);
|
||||||
|
|
||||||
|
updateDoc.clear();
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t updateSpoolLocation(String spoolId, String location){
|
||||||
|
String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + spoolId;
|
||||||
|
Serial.print("Update Spule mit URL: ");
|
||||||
|
Serial.println(spoolsUrl);
|
||||||
|
|
||||||
|
// Update Payload erstellen
|
||||||
|
JsonDocument updateDoc;
|
||||||
|
updateDoc["location"] = location;
|
||||||
|
|
||||||
|
String updatePayload;
|
||||||
|
serializeJson(updateDoc, updatePayload);
|
||||||
|
Serial.print("Update Payload: ");
|
||||||
|
Serial.println(updatePayload);
|
||||||
|
|
||||||
|
SendToApiParams* params = new SendToApiParams();
|
||||||
|
if (params == nullptr) {
|
||||||
|
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
params->requestType = API_REQUEST_SPOOL_LOCATION_UPDATE;
|
||||||
|
params->httpType = "PATCH";
|
||||||
|
params->spoolsUrl = spoolsUrl;
|
||||||
|
params->updatePayload = updatePayload;
|
||||||
|
|
||||||
|
// Erstelle die Task
|
||||||
|
BaseType_t result = xTaskCreate(
|
||||||
|
sendToApi, // Task-Funktion
|
||||||
|
"SendToApiTask", // Task-Name
|
||||||
|
6144, // Stackgröße in Bytes
|
||||||
(void*)params, // Parameter
|
(void*)params, // Parameter
|
||||||
0, // Priorität
|
0, // Priorität
|
||||||
NULL // Task-Handle (nicht benötigt)
|
NULL // Task-Handle (nicht benötigt)
|
||||||
@@ -234,6 +303,7 @@ bool updateSpoolOcto(int spoolId) {
|
|||||||
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
|
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
params->requestType = API_REQUEST_OCTO_SPOOL_UPDATE;
|
||||||
params->httpType = "POST";
|
params->httpType = "POST";
|
||||||
params->spoolsUrl = spoolsUrl;
|
params->spoolsUrl = spoolsUrl;
|
||||||
params->updatePayload = updatePayload;
|
params->updatePayload = updatePayload;
|
||||||
@@ -243,7 +313,7 @@ bool updateSpoolOcto(int spoolId) {
|
|||||||
BaseType_t result = xTaskCreate(
|
BaseType_t result = xTaskCreate(
|
||||||
sendToApi, // Task-Funktion
|
sendToApi, // Task-Funktion
|
||||||
"SendToApiTask", // Task-Name
|
"SendToApiTask", // Task-Name
|
||||||
4096, // Stackgröße in Bytes
|
6144, // Stackgröße in Bytes
|
||||||
(void*)params, // Parameter
|
(void*)params, // Parameter
|
||||||
0, // Priorität
|
0, // Priorität
|
||||||
NULL // Task-Handle (nicht benötigt)
|
NULL // Task-Handle (nicht benötigt)
|
||||||
@@ -283,6 +353,7 @@ bool updateSpoolBambuData(String payload) {
|
|||||||
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
|
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
params->requestType = API_REQUEST_BAMBU_UPDATE;
|
||||||
params->httpType = "PATCH";
|
params->httpType = "PATCH";
|
||||||
params->spoolsUrl = spoolsUrl;
|
params->spoolsUrl = spoolsUrl;
|
||||||
params->updatePayload = updatePayload;
|
params->updatePayload = updatePayload;
|
||||||
@@ -291,7 +362,7 @@ bool updateSpoolBambuData(String payload) {
|
|||||||
BaseType_t result = xTaskCreate(
|
BaseType_t result = xTaskCreate(
|
||||||
sendToApi, // Task-Funktion
|
sendToApi, // Task-Funktion
|
||||||
"SendToApiTask", // Task-Name
|
"SendToApiTask", // Task-Name
|
||||||
4096, // Stackgröße in Bytes
|
6144, // Stackgröße in Bytes
|
||||||
(void*)params, // Parameter
|
(void*)params, // Parameter
|
||||||
0, // Priorität
|
0, // Priorität
|
||||||
NULL // Task-Handle (nicht benötigt)
|
NULL // Task-Handle (nicht benötigt)
|
||||||
@@ -487,6 +558,8 @@ bool checkSpoolmanInstance(const String& url) {
|
|||||||
return strcmp(status, "healthy") == 0;
|
return strcmp(status, "healthy") == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Serial.println("Error contacting spoolman instance! HTTP Code: " + String(httpCode));
|
||||||
}
|
}
|
||||||
http.end();
|
http.end();
|
||||||
return false;
|
return false;
|
||||||
|
@@ -12,6 +12,14 @@ typedef enum {
|
|||||||
API_TRANSMITTING
|
API_TRANSMITTING
|
||||||
} spoolmanApiStateType;
|
} spoolmanApiStateType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
API_REQUEST_OCTO_SPOOL_UPDATE,
|
||||||
|
API_REQUEST_BAMBU_UPDATE,
|
||||||
|
API_REQUEST_SPOOL_TAG_ID_UPDATE,
|
||||||
|
API_REQUEST_SPOOL_WEIGHT_UPDATE,
|
||||||
|
API_REQUEST_SPOOL_LOCATION_UPDATE
|
||||||
|
} SpoolmanApiRequestType;
|
||||||
|
|
||||||
extern volatile spoolmanApiStateType spoolmanApiState;
|
extern volatile spoolmanApiStateType spoolmanApiState;
|
||||||
extern bool spoolman_connected;
|
extern bool spoolman_connected;
|
||||||
extern String spoolmanUrl;
|
extern String spoolmanUrl;
|
||||||
@@ -26,6 +34,7 @@ bool checkSpoolmanExtraFields(); // Neue Funktion zum Überprüfen der Extrafeld
|
|||||||
JsonDocument fetchSingleSpoolInfo(int spoolId); // API-Funktion für die Webseite
|
JsonDocument fetchSingleSpoolInfo(int spoolId); // API-Funktion für die Webseite
|
||||||
bool updateSpoolTagId(String uidString, const char* payload); // Neue Funktion zum Aktualisieren eines Spools
|
bool updateSpoolTagId(String uidString, const char* payload); // Neue Funktion zum Aktualisieren eines Spools
|
||||||
uint8_t updateSpoolWeight(String spoolId, uint16_t weight); // Neue Funktion zum Aktualisieren des Gewichts
|
uint8_t updateSpoolWeight(String spoolId, uint16_t weight); // Neue Funktion zum Aktualisieren des Gewichts
|
||||||
|
uint8_t updateSpoolLocation(String spoolId, String location);
|
||||||
bool initSpoolman(); // Neue Funktion zum Initialisieren von Spoolman
|
bool initSpoolman(); // Neue Funktion zum Initialisieren von Spoolman
|
||||||
bool updateSpoolBambuData(String payload); // Neue Funktion zum Aktualisieren der Bambu-Daten
|
bool updateSpoolBambuData(String payload); // Neue Funktion zum Aktualisieren der Bambu-Daten
|
||||||
bool updateSpoolOcto(int spoolId); // Neue Funktion zum Aktualisieren der Octo-Daten
|
bool updateSpoolOcto(int spoolId); // Neue Funktion zum Aktualisieren der Octo-Daten
|
||||||
|
@@ -674,6 +674,7 @@ bool setupMqtt() {
|
|||||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||||
connected = false;
|
connected = false;
|
||||||
oledShowTopRow();
|
oledShowTopRow();
|
||||||
|
autoSetToBambuSpoolId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!connected) return false;
|
if (!connected) return false;
|
||||||
@@ -687,6 +688,8 @@ bool setupMqtt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void bambu_restart() {
|
void bambu_restart() {
|
||||||
|
Serial.println("Bambu restart");
|
||||||
|
|
||||||
if (BambuMqttTask) {
|
if (BambuMqttTask) {
|
||||||
vTaskDelete(BambuMqttTask);
|
vTaskDelete(BambuMqttTask);
|
||||||
delay(10);
|
delay(10);
|
||||||
|
@@ -180,7 +180,8 @@ void loop() {
|
|||||||
lastWeightReadTime = currentMillis;
|
lastWeightReadTime = currentMillis;
|
||||||
|
|
||||||
// Prüfen ob die Waage korrekt genullt ist
|
// Prüfen ob die Waage korrekt genullt ist
|
||||||
if (autoTare && (weight > 0 && weight < 5) || weight < 0)
|
// Abweichung von 2g ignorieren
|
||||||
|
if (autoTare && (weight > 2 && weight < 7) || weight < -2)
|
||||||
{
|
{
|
||||||
scale_tare_counter++;
|
scale_tare_counter++;
|
||||||
}
|
}
|
||||||
|
18
src/nfc.cpp
18
src/nfc.cpp
@@ -218,11 +218,25 @@ bool decodeNdefAndReturnJson(const byte* encodedMessage) {
|
|||||||
// Sende die aktualisierten AMS-Daten an alle WebSocket-Clients
|
// Sende die aktualisierten AMS-Daten an alle WebSocket-Clients
|
||||||
Serial.println("JSON-Dokument erfolgreich verarbeitet");
|
Serial.println("JSON-Dokument erfolgreich verarbeitet");
|
||||||
Serial.println(doc.as<String>());
|
Serial.println(doc.as<String>());
|
||||||
if (doc["sm_id"] != "")
|
if (doc.containsKey("sm_id") && doc["sm_id"] != "")
|
||||||
{
|
{
|
||||||
Serial.println("SPOOL-ID gefunden: " + doc["sm_id"].as<String>());
|
Serial.println("SPOOL-ID gefunden: " + doc["sm_id"].as<String>());
|
||||||
spoolId = doc["sm_id"].as<String>();
|
spoolId = doc["sm_id"].as<String>();
|
||||||
}
|
}
|
||||||
|
else if(doc.containsKey("location") && doc["location"] != "")
|
||||||
|
{
|
||||||
|
Serial.println("Location Tag found!");
|
||||||
|
String location = doc["location"].as<String>();
|
||||||
|
if(spoolId != ""){
|
||||||
|
updateSpoolLocation(spoolId, location);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.println("Location update tag scanned without scanning spool before!");
|
||||||
|
oledShowMessage("No spool scanned before!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Serial.println("Keine SPOOL-ID gefunden.");
|
Serial.println("Keine SPOOL-ID gefunden.");
|
||||||
|
@@ -74,7 +74,11 @@ void scale_loop(void * parameter) {
|
|||||||
scaleTareRequest = false;
|
scaleTareRequest = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
weight = round(scale.get_units());
|
// Only update weight if median changed more than 1
|
||||||
|
int16_t newWeight = round(scale.get_units());
|
||||||
|
if(abs(weight-newWeight) > 1){
|
||||||
|
weight = newWeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
Reference in New Issue
Block a user