This commit is contained in:
thek4n 2026-06-13 16:25:28 +03:00
parent 7851217140
commit ff6e6ba5f1
3 changed files with 599 additions and 105 deletions

View File

@ -1,112 +1,604 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Logic Analyzer</title>
</head>
<body>
<main>
<canvas id="binaryChart" width="500" height="250"></canvas>
</main>
<script>
function drawAllWaveforms(channels_total, samples_total, samples) {
const canvas = document.getElementById('binaryChart');
const ctx = canvas.getContext('2d');
<html lang="en">
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, user-scalable=no'>
<title>Logic Analyzer - Ring Buffer Waveform</title>
<style>
* {
box-sizing: border-box;
user-select: none;
}
body {
background: #0a0f1e;
font-family: 'Segoe UI', 'Roboto', 'Consolas', monospace;
margin: 0;
min-height: 100dvh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
main {
background: #11161f;
border-radius: 28px;
box-shadow: 0 20px 35px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05);
padding: 20px 24px 24px 24px;
backdrop-filter: blur(2px);
}
.canvas-container {
background: #010101;
border-radius: 16px;
padding: 8px;
box-shadow: inset 0 0 8px #00000055, 0 8px 20px rgba(0,0,0,0.3);
}
canvas {
display: block;
width: 100%;
height: auto;
background: #0b0e14;
border-radius: 12px;
cursor: crosshair;
}
.info-panel {
margin-top: 20px;
display: flex;
flex-wrap: wrap;
gap: 18px;
justify-content: space-between;
align-items: flex-start;
}
.status-card {
background: #1e2533;
padding: 8px 18px;
border-radius: 60px;
font-size: 0.85rem;
font-weight: 500;
color: #b9e6ff;
letter-spacing: 0.5px;
box-shadow: inset 0 1px 0 #2f3a4a, 0 4px 8px #00000030;
}
.ring-stats {
background: #151e2a;
border-radius: 20px;
padding: 6px 18px;
font-family: monospace;
font-weight: bold;
color: #7ee0ff;
font-size: 0.9rem;
}
.legend {
display: flex;
flex-wrap: wrap;
gap: 16px;
background: #0f141fcc;
backdrop-filter: blur(4px);
border-radius: 24px;
padding: 8px 20px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.75rem;
font-weight: 600;
color: #cdddf7;
}
.legend-color {
width: 18px;
height: 12px;
border-radius: 4px;
}
.sample-badge {
background: #00000066;
border-radius: 14px;
padding: 4px 12px;
font-family: monospace;
font-size: 0.8rem;
color: #9cf2ff;
}
button {
background: #2a3b4e;
border: none;
border-radius: 40px;
padding: 6px 14px;
color: white;
font-weight: bold;
font-size: 0.7rem;
cursor: pointer;
transition: 0.2s;
box-shadow: 0 2px 5px black;
}
button:active {
transform: scale(0.96);
}
@media (max-width: 700px) {
main { padding: 14px; }
.legend-item { font-size: 0.65rem; }
}
</style>
</head>
<body>
<main>
<div class="canvas-container">
<canvas id="logicCanvas" width="1000" height="500" style="width:100%; height:auto; max-width:1000px; aspect-ratio:1000/500"></canvas>
</div>
<div class="info-panel">
<div class="status-card" id="wsStatus">⚡ Ожидание подключения</div>
<div class="ring-stats" id="ringStats">🔔 буфер: 0 / 0 семплов</div>
<div class="legend" id="channelLegend"></div>
<div class="sample-badge" id="sampleCounter">📊 приём: --</div>
<button id="clearBtn" title="Сбросить буфер (очистить историю)">⟳ Очистить</button>
</div>
</main>
<script>
(function(){
// ---------- КОНФИГУРАЦИЯ ----------
const MAX_RING_SIZE = 2048; // максимальная длина кольцевого буфера (глубина истории)
let ringBuffer = new Array(MAX_RING_SIZE).fill(0); // храним 8-битные sample (целые 0..255)
let ringHead = 0; // следующая позиция для записи
let ringCount = 0; // сколько реально семплов в буфере (0..MAX_RING_SIZE)
// Параметры отображения (каналы: 0...7)
const TOTAL_CHANNELS = 8;
const CHANNEL_NAMES = ['CH0', 'CH1', 'CH2', 'CH3', 'CH4', 'CH5', 'CH6', 'CH7'];
// Цветовая палитра (яркие, различимые на тёмном фоне)
const CHANNEL_COLORS = [
'#FFB86C', '#50FA7B', '#FF5555', '#8BE9FD',
'#F1FA8C', '#BD93F9', '#FF79C6', '#6BE0D9'
];
let canvas = document.getElementById('logicCanvas');
let ctx = canvas.getContext('2d');
let animationFrameId = null;
let lastRenderedCount = -1; // для оптимизации, перерисовка только при изменении
// WebSocket
let ws = null;
let connectionAttempts = 0;
// Элементы UI
const wsStatusDiv = document.getElementById('wsStatus');
const ringStatsSpan = document.getElementById('ringStats');
const sampleCounterSpan = document.getElementById('sampleCounter');
const clearBtn = document.getElementById('clearBtn');
const legendContainer = document.getElementById('channelLegend');
// ---- Построение легенды каналов ----
function buildLegend() {
legendContainer.innerHTML = '';
for (let ch = 0; ch < TOTAL_CHANNELS; ch++) {
const item = document.createElement('div');
item.className = 'legend-item';
const colorBox = document.createElement('div');
colorBox.className = 'legend-color';
colorBox.style.backgroundColor = CHANNEL_COLORS[ch % CHANNEL_COLORS.length];
const label = document.createElement('span');
label.innerText = `${CHANNEL_NAMES[ch]}`;
item.appendChild(colorBox);
item.appendChild(label);
legendContainer.appendChild(item);
}
}
// ---- Управление кольцевым буфером ----
function pushSample(sampleValue) {
// sampleValue: число 0..255 (8 бит)
if (typeof sampleValue !== 'number' || isNaN(sampleValue)) return;
let val = Math.min(255, Math.max(0, Math.floor(sampleValue)));
ringBuffer[ringHead] = val;
ringHead = (ringHead + 1) % MAX_RING_SIZE;
if (ringCount < MAX_RING_SIZE) {
ringCount++;
}
// триггерим перерисовку при новом данных (асинхронно)
scheduleRender();
}
// Очистка буфера (сброс истории)
function clearRingBuffer() {
ringHead = 0;
ringCount = 0;
// затирать массив необязательно, но для эстетики заполним нулями
for (let i = 0; i < MAX_RING_SIZE; i++) ringBuffer[i] = 0;
scheduleRender();
updateStatsUI();
}
// Получить массив семплов в порядке от старых к новым (для отрисовки)
function getOrderedSamples() {
if (ringCount === 0) return [];
const result = new Array(ringCount);
const startIdx = (ringHead - ringCount + MAX_RING_SIZE) % MAX_RING_SIZE;
for (let i = 0; i < ringCount; i++) {
result[i] = ringBuffer[(startIdx + i) % MAX_RING_SIZE];
}
return result;
}
// ---- ОТРИСОВКА ВСЕХ ВОЛНОВЫХ ФОРМ (логический анализатор) ----
// рисуем все 8 каналов, каждый бит - цифровой сигнал (0/1)
function drawWaveforms() {
if (!canvas || !ctx) return;
const samplesArray = getOrderedSamples();
const sampleCount = samplesArray.length;
if (sampleCount === 0) {
// пустой буфер: рисуем тёмный фон и надпись
ctx.clearRect(0, 0, canvas.width, canvas.height);
const data = [];
for (let i = 0; i < samples_total; i++) {
data.push((samples[i] >> 1) & 1);
ctx.fillStyle = '#0b0e14';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#6c7a8e';
ctx.font = '14px "Segoe UI", monospace';
ctx.textAlign = 'center';
ctx.fillText('⏳ Ожидание данных...', canvas.width/2, canvas.height/2);
ctx.textAlign = 'left';
return;
}
// Настройки отступов
const leftMargin = 50;
const rightMargin = 30;
const topMargin = 30;
const bottomMargin = 40;
const graphWidth = canvas.width - leftMargin - rightMargin; // 500 - 50 - 30 = 420
const graphHeight = canvas.height - topMargin - bottomMargin; // 250 - 30 - 40 = 180
// Настройки отрисовки
const w = canvas.width;
const h = canvas.height;
const channelHeight = h / TOTAL_CHANNELS; // высота на один канал
const paddingTop = 4;
const effectiveChannelH = channelHeight - 2; // небольшой отступ между каналами
// Шаг между точками
const stepX = graphWidth / (samples_total - 1); // 420 / 9 = 46.67px
ctx.clearRect(0, 0, w, h);
// фон сетки (очень тёмный)
ctx.fillStyle = '#0a0d14';
ctx.fillRect(0, 0, w, h);
// Отрисовка осей
// Рисуем горизонтальные разделители и подписи каналов
for (let ch = 0; ch < TOTAL_CHANNELS; ch++) {
const yTop = ch * channelHeight;
const yMid = yTop + channelHeight/2;
// линия разделителя (тонкая)
ctx.beginPath();
// Ось Y (вертикальная)
ctx.moveTo(leftMargin, topMargin);
ctx.lineTo(leftMargin, canvas.height - bottomMargin);
// Ось X (горизонтальная)
ctx.lineTo(canvas.width - rightMargin, canvas.height - bottomMargin);
ctx.strokeStyle = '#2a3343';
ctx.lineWidth = 0.8;
ctx.setLineDash([4, 6]);
ctx.moveTo(0, yTop + channelHeight);
ctx.lineTo(w, yTop + channelHeight);
ctx.stroke();
ctx.setLineDash([]);
// подпись канала слева
ctx.fillStyle = CHANNEL_COLORS[ch % CHANNEL_COLORS.length];
ctx.font = 'bold 12px "JetBrains Mono", monospace';
ctx.shadowBlur = 0;
ctx.fillText(CHANNEL_NAMES[ch], 8, yMid + 4);
// фоновая область канала (лёгкая подсветка)
ctx.fillStyle = '#10141e';
ctx.fillRect(0, yTop + 1, w, channelHeight - 1);
}
// Подписи на оси Y (0 и 1)
ctx.font = "12px Arial";
ctx.fillStyle = "black";
ctx.fillText("1", leftMargin - 20, topMargin + 3);
ctx.fillText("0", leftMargin - 20, canvas.height - bottomMargin + 3);
// --- Рисуем сигналы для каждого канала ---
// Используем линейную интерполяцию/ступенчатый вид: рисуем линии по точкам (x0,y0) -> (x1, y1)
// Горизонтальная шкала: ширина одного семпла = w / sampleCount
const stepX = w / sampleCount;
if (stepX < 0.5) {
// при огромном количестве точек можно упростить, но всё равно корректно отрисуем линии, даже если шаг меньше пикселя
// сохраняем точность - canvas сгладит
}
// Подпись оси Y
ctx.save();
ctx.translate(20, canvas.height / 2);
ctx.rotate(-Math.PI / 2);
ctx.fillText("Значение", -20, 0);
ctx.restore();
for (let ch = 0; ch < TOTAL_CHANNELS; ch++) {
const yBase = ch * channelHeight;
const yLow = yBase + channelHeight - 6; // уровень логического 0 (нижняя часть)
const yHigh = yBase + 6; // уровень логической 1 (верх)
// более чёткие границы: для наглядности опускаем 0 почти к низу, 1 к верху канала
const zeroLevel = yBase + channelHeight - 4;
const oneLevel = yBase + 6;
// Рисуем ступенчатый график (бинарный)
ctx.beginPath();
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.lineWidth = 2.2;
ctx.strokeStyle = CHANNEL_COLORS[ch % CHANNEL_COLORS.length];
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
for (let i = 0; i < samples_total; i++) {
const x = leftMargin + i * stepX;
// Преобразуем значение (0 или 1) в Y-координату
// 1 -> верх (topMargin), 0 -> низ (canvas.height - bottomMargin)
const y = data[i] === 1 ? topMargin : canvas.height - bottomMargin;
let prevX = 0;
// берем первый sample, получаем бит для канала ch
let prevBit = (samplesArray[0] >> ch) & 1;
let prevY = prevBit === 1 ? oneLevel : zeroLevel;
ctx.moveTo(0, prevY);
if (i === 0) {
ctx.moveTo(x, y);
for (let i = 1; i < sampleCount; i++) {
const sampleVal = samplesArray[i];
const bit = (sampleVal >> ch) & 1;
const currentX = i * stepX;
const currentY = bit === 1 ? oneLevel : zeroLevel;
// если значение бита изменилось — рисуем вертикальный переход
if (bit !== prevBit) {
// сначала линия до той же X, но с предыдущим Y (горизонтальная)
ctx.lineTo(currentX, prevY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(currentX, prevY);
ctx.lineTo(currentX, currentY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(currentX, currentY);
prevBit = bit;
prevY = currentY;
} else {
// Рисуем горизонтальную линию от предыдущей точки
const prevX = leftMargin + (i - 1) * stepX;
const prevY = data[i-1] === 1 ? topMargin : canvas.height - bottomMargin;
ctx.lineTo(prevX + stepX, prevY); // Горизонтальная линия
ctx.lineTo(x, y); // Вертикальный скачок
// горизонтальный сегмент
ctx.lineTo(currentX, currentY);
}
}
// финальный штрих до конца
const lastX = w;
ctx.lineTo(lastX, prevY);
ctx.stroke();
// небольшая заливка для области нуля/единицы (дополнительно полупрозрачная тень)
ctx.save();
ctx.globalAlpha = 0.12;
ctx.fillStyle = CHANNEL_COLORS[ch % CHANNEL_COLORS.length];
for (let seg = 0; seg < sampleCount-1; seg++) {
const bitSeg = (samplesArray[seg] >> ch) & 1;
if (bitSeg === 1) {
const segX1 = seg * stepX;
const segX2 = (seg+1) * stepX;
ctx.fillRect(segX1, yBase+2, segX2-segX1, channelHeight-4);
}
}
ctx.restore();
}
// сетка вертикальных тактов (опционально каждые 8/16/32 семпла)
ctx.beginPath();
ctx.strokeStyle = '#2e3b4e';
ctx.lineWidth = 0.6;
ctx.setLineDash([5, 8]);
const gridSpacing = Math.max(16, Math.floor(sampleCount / 20));
for (let i = 0; i <= sampleCount; i += gridSpacing) {
const xPos = i * stepX;
if (xPos <= w) {
ctx.moveTo(xPos, 0);
ctx.lineTo(xPos, h);
}
}
ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = "black";
ctx.font = "bold 12px Arial";
ctx.fillText("Время (такты)", canvas.width/2 - 30, canvas.height - 10);
// отрисовка текста с числом семплов поверх (информативно)
ctx.font = '10px "Fira Code", monospace';
ctx.fillStyle = '#b9d9ff';
ctx.shadowBlur = 0;
ctx.fillText(`${sampleCount} выборок`, w - 70, 18);
}
function connectWebSocket() {
ws = new WebSocket('ws://' + window.location.hostname + ':81');
let renderScheduled = false;
function scheduleRender() {
if (renderScheduled) return;
renderScheduled = true;
requestAnimationFrame(() => {
drawWaveforms();
updateStatsUI(); // обновляем счетчики буфера и приёма
renderScheduled = false;
});
}
ws.onopen = function() {
console.log('WebSocket connected');
updateStatus('Подключено');
// обновление UI статистики (размер буфера, кол-во семплов)
function updateStatsUI() {
if (ringStatsSpan) {
ringStatsSpan.innerHTML = `💾 кольц. буфер: ${ringCount} / ${MAX_RING_SIZE} семпл`;
}
if (sampleCounterSpan) {
sampleCounterSpan.innerHTML = `📈 последний пакет: ${ringCount > 0 ? ringCount : '—'}`;
}
}
// обновление статуса WebSocket
function updateStatus(message, isConnected = false) {
if (wsStatusDiv) {
wsStatusDiv.innerHTML = `🔌 ${message}`;
if (isConnected) {
wsStatusDiv.style.background = '#1a4731';
wsStatusDiv.style.color = '#b4ffcf';
} else {
wsStatusDiv.style.background = '#3d2a2a';
wsStatusDiv.style.color = '#ffbc9a';
}
}
}
// ---- ОБРАБОТКА ВХОДЯЩИХ ДАННЫХ (WebSocket) ----
// Формат ожидаемого JSON:
// { type: 'data', channels_total: 8, samples_total: N, samples: [12, 200, ...] }
// или массив байт. samples - массив целых 0..255
function processSamplesFromMessage(dataMsg) {
let samplesArray = null;
// если пришёл массив samples
if (dataMsg.samples && Array.isArray(dataMsg.samples)) {
samplesArray = dataMsg.samples;
}
// поддержка альтернативного поля data
else if (dataMsg.data && Array.isArray(dataMsg.data)) {
samplesArray = dataMsg.data;
}
// также если в поле samples_total подсказка
if (samplesArray && samplesArray.length > 0) {
// фильтруем и добавляем каждый sample в кольцевой буфер
for (let i = 0; i < samplesArray.length; i++) {
let smp = Number(samplesArray[i]);
if (isNaN(smp)) continue;
pushSample(smp);
}
// также можно отобразить быстрый счётчик
if (sampleCounterSpan) {
sampleCounterSpan.innerHTML = `📊 +${samplesArray.length} семпл(ов) | всего ${ringCount}`;
}
} else {
// нет данных, но возможно samples_total = 0
console.warn("Нет массива samples в сообщении", dataMsg);
}
}
// ---- WEBSOCKET подключение (адаптация под ring buffer) ----
function connectWebSocket() {
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
try { ws.close(); } catch(e) {}
}
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `ws://${window.location.hostname}:81`;
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('[LOGIC] WebSocket connected');
updateStatus('Подключено к анализатору', true);
connectionAttempts = 0;
};
ws.onmessage = function(event) {
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'data') {
drawAllWaveforms(data.channels_total, data.samples_total, data.samples);
} else if (data.type === 'status') {
updateStatus(data.message);
// стандартное сообщение от логического анализатора
// ожидаем data.samples — массив байт
if (data.samples) {
processSamplesFromMessage(data);
}
// поддержка также прямого массива samples_total и samples
else if (data.samples_total !== undefined && data.samples) {
processSamplesFromMessage(data);
}
else {
// возможно сообщение просто содержит samples массив? проверяем корневой массив
if (Array.isArray(data)) {
for (let val of data) pushSample(val);
} else if (data.data && Array.isArray(data.data)) {
processSamplesFromMessage(data);
} else {
// неизвестный формат, но пробуем извлечь samples поле рекурсивно
if (data.payload && data.payload.samples) processSamplesFromMessage(data.payload);
}
}
}
else if (data.type === 'status') {
updateStatus(data.message || data.status, true);
}
else {
// попробуем универсально: если есть свойство samples использовать
if (data.samples) processSamplesFromMessage(data);
else if (Array.isArray(data)) {
data.forEach(v => pushSample(v));
}
}
} catch (err) {
console.error("JSON parse error or invalid data:", err, event.data);
// возможно raw binary данные? по тексту не ожидается, но игнорируем
}
};
ws.onclose = function() {
console.log('WebSocket disconnected');
updateStatus('Отключено. Переподключение...');
setTimeout(connectWebSocket, 1000);
ws.onclose = (ev) => {
console.warn('WebSocket closed, reconnecting...', ev.code);
updateStatus('Отключено. Переподключение через 1с...', false);
setTimeout(() => {
connectWebSocket();
}, 1000);
};
ws.onerror = (err) => {
console.error('WebSocket error', err);
updateStatus('Ошибка соединения', false);
};
}
window.addEventListener('DOMContentLoaded', connectWebSocket);
</script>
</body>
// ---- ИНИЦИАЛИЗАЦИЯ И АДАПТАЦИЯ РАЗМЕРА CANVAS ----
function resizeCanvasAndRedraw() {
const container = canvas.parentElement;
const maxWidth = Math.min(1200, window.innerWidth - 60);
// устанавливаем размер canvas в пикселях для чёткой графики
const targetWidth = Math.min(1000, maxWidth);
const targetHeight = 500;
canvas.width = targetWidth;
canvas.height = targetHeight;
canvas.style.width = `${targetWidth}px`;
canvas.style.height = `${targetHeight}px`;
scheduleRender();
}
// очистка буфера по кнопке
function handleClear() {
clearRingBuffer();
updateStatus('Буфер сброшен', !!ws && ws.readyState === WebSocket.OPEN);
scheduleRender();
}
// Имитация демо-данных, если сервер не отвечает (для теста интерфейса кольцевого буфера)
// но только если нет подключения в течение 4 секунд - демонстрация работы (добавим генератор)
let demoInterval = null;
function startDemoDataIfNeeded() {
// Проверяем каждые 5 секунд: если нет ws или не открыт и буфер пуст, то генерируем демо
if (demoInterval) clearInterval(demoInterval);
demoInterval = setInterval(() => {
if (!ws || ws.readyState !== WebSocket.OPEN) {
// генерируем несколько тестовых семплов для наглядности, но только если буфер не слишком большой
if (ringCount < 300) {
const demoCount = 4;
for (let i = 0; i < demoCount; i++) {
// Создаём осмысленную последовательность: счётчик, меандр на разных каналах
const timeSeed = Date.now() + i;
let sampleVal = 0;
// CH0: квадратная волна (каждые 8 семплов)
if ((ringCount + i) % 16 < 8) sampleVal |= (1 << 0);
// CH1: меандр с другой частотой
if ((ringCount + i) % 24 < 12) sampleVal |= (1 << 1);
// CH2: случайный импульс
if (Math.sin((ringCount + i) * 0.3) > 0) sampleVal |= (1 << 2);
// CH3: активный каждые 3 семпла
if ((ringCount + i) % 6 < 3) sampleVal |= (1 << 3);
// CH4: половина
if ((ringCount + i) % 10 > 4) sampleVal |= (1 << 4);
// CH5: шумоподобный
if (Math.floor(Math.random() * 2)) sampleVal |= (1 << 5);
// CH6, CH7 (доп)
if ((ringCount + i) % 7 < 3) sampleVal |= (1 << 6);
if ((ringCount + i) % 5 === 0) sampleVal |= (1 << 7);
pushSample(sampleVal);
}
}
}
}, 360);
}
// --- Отслеживание изменения размера окна ---
window.addEventListener('resize', () => {
resizeCanvasAndRedraw();
});
// Инициализация UI
function init() {
buildLegend();
resizeCanvasAndRedraw();
clearRingBuffer(); // чистый старт
connectWebSocket();
clearBtn.addEventListener('click', handleClear);
// небольшая анимация рендера (периодическая перерисовка для обновления UI даже без данных)
function periodicRender() {
drawWaveforms();
requestAnimationFrame(periodicRender);
}
// Запускаем непрерывный рендер (эффективно, не нагружает)
periodicRender();
}
init();
})();
</script>
</body>
</html>

View File

@ -128,14 +128,14 @@ void appendSamples() {
lastTime = now;
if (g_samples_idx == SAMPLES_BUFFER_CAP - 1) return;
if (g_samples_idx == SAMPLES_BUFFER_CAP) return;
SAMPLES[g_samples_idx] = takeSample();
g_samples_idx++;
}
void websocketSendSamples() {
if (g_samples_idx != SAMPLES_BUFFER_CAP - 1) return;
if (g_samples_idx < SAMPLES_BUFFER_CAP) return;
static unsigned long lastTime = 0;
unsigned long now = millis();

View File

@ -1,19 +1,21 @@
#!/usr/bin/env -S just --justfile
BOARD := env("BOARD", "esp8266:esp8266:nodemcuv2")
PORT := env("PORT", "/dev/ttyUBS0")
alias compile := build
build:
#!/bin/sh
xxd -i assets/root.html > firmware/logic_analyzer/root_html.h
cd firmware/logic_analyzer
arduino-cli compile --fqbn esp8266:esp8266:nodemcuv2
arduino-cli compile --fqbn "{{BOARD}}"
alias flash := upload
[working-directory: 'firmware/logic_analyzer']
upload:
arduino-cli upload --fqbn esp8266:esp8266:nodemcuv2 --port /dev/ttyUSB0
arduino-cli upload --fqbn "{{BOARD}}" --port "{{PORT}}"
monitor:
arduino-cli monitor --port /dev/ttyUSB0
arduino-cli monitor --port "{{PORT}}" --config 115200