Кто чем занят?

Прочел “Малиновый пеликан” Войновича. Ну как прочел - прослушал аудиокнигу. На Литресе есть, и на Озон можно бумажную купить. И куда только власти смотрят?

Позвольте узнать, что вы можете сказать по поводу прочитанного? (прослушанного)

Так я же уже сказал:

Не слушал, не читал, просто первый от балды фрагмент:

…Мужчина имеет право быть пацифистом, ненавидеть войну, не хвататься за оружие, беречь себя и избегать убийства других людей во имя каких бы то ни было целей, кроме самых крайних случаев, когда надо и приходится даже ценой своей жизни защищать свою семью от бандитов, от внешних врагов и от своего государства, которое бывает хуже внешних врагов. Но какой-нибудь авантюрист, кочевник по горячим точкам или просто послушный исполнитель, готовый убивать, а хоть быть убитым по приказу, за кусок какой-нибудь территории, за высокую идею, за то, чтобы заставить кого-то жить по-нашему, за деньги, ордена или ради удовольствия, какого бы пола он ни был, он для меня вообще не совсем человек.»…

…хотя чему удивляться-выдержка из биографии

В 1980 году был лишён советского гражданства, вследствие этого был вынужден эмигрировать из СССР; до середины 2000-х годов жил в Германии.

Нужно купить. Не идут аудиокниги что то. Зрение ни к черту - а слушать пробовал, не то пальто.

Смотрю сериал «Миссия невыполнима» (1966-1972). Интересно (местами забавно). :slight_smile:

Воюю с мигающим светодиодом, пытаюсь использовать его как задающий генератор для прерывистого звукового сигнала и, уж заодно, мигания второго (обычного) светодиода.

Кому-то интересно? Стоит выложить результат?

если с помощью кода, то однозначно стоит)))

а я вот пытаюсь https://alexgyver.github.io/MagicGyver/ сделать на esp32 веб сервер такой, есть желающие такой фигней заняться ?)))

Чем не устраивает?

прекрасная библиотека для вебсервера, большинство функций автоматизированы! обожаю ее!!! если обновления не внесли, наверное по прежнему не работает на ядре 3+ esp32, но кому оно нужно !? выпускают их, тогда пусть и библиотеки сами правят)))

но проблема у меня с преобразованием фото в координаты как у гайвера https://alexgyver.github.io/MagicGyver
мой самописный код преобразования фото в g код иногда вовсе не видит на фото текст… перевод в 2 цвета и выделять 1 пробовал, но сейчас пытаюсь тупо скопировать функционал гайвера с его сайта, и не выходит))) написать веб сервер не проблема…

common.h

#ifndef COMMON_H
#define COMMON_H

#include <Arduino.h>
#include <vector>
#include <SD.h>

// Пины SD-карты
#define SD_CS    5
#define SD_SCLK  18
#define SD_MOSI  23
#define SD_MISO  19

// WiFi настройки
#define MY_WIFI_MODE_AP     1
#define MY_WIFI_MODE_STA    2
#define MY_WIFI_MODE MY_WIFI_MODE_AP

#define AP_SSID "ESP32_GCode"
#define AP_PASSWORD "12345678"

struct Point {
    int x;
    int y;
};

struct ProcessorSettings {
    int threshold = 127;
    int skip = 5;
    int rowAmount = 20;
    float amplitude = 1.0;
    bool loop = true;
    bool optimise = false;
    int skipPoints = 5;
};

#endif

esp32_gcode_server.ino

#include <WiFi.h>
#include <WebServer.h>
#include <SPI.h>
#include <FS.h>
#include <SD.h>
#include <ArduinoJson.h>
#include "common.h"
#include "image_processor.h"

WebServer server(80);
ImageProcessor imgProcessor;

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>ESP32 G-Code Generator</title>
    <style>
        body { font-family: Arial; margin: 20px; background: #f0f0f0; }
        .container { max-width: 800px; margin: auto; }
        .card { background: white; padding: 20px; border-radius: 10px; margin-bottom: 20px; }
        .slider-group { margin: 15px 0; }
        .slider-label { display: flex; justify-content: space-between; }
        button { background: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin: 5px; }
        button:hover { background: #45a049; }
        select, input[type=range] { width: 100%; padding: 5px; margin: 5px 0; }
        .file-list { border: 1px solid #ddd; padding: 10px; max-height: 200px; overflow-y: auto; }
        .file-item { padding: 5px; cursor: pointer; border-bottom: 1px solid #eee; }
        .file-item:hover { background: #f5f5f5; }
        .status { background: #e3f2fd; padding: 10px; border-radius: 5px; margin: 10px 0; }
    </style>
</head>
<body>
    <div class="container">
        <h1>ESP32 G-Code Generator</h1>
        
        <div class="card">
            <h3>1. Upload Image</h3>
            <input type="file" id="fileInput" accept="image/jpeg">
            <button onclick="uploadFile()">Upload to SD</button>
            <button onclick="scanSD()">Scan SD Card</button>
            <div id="fileList" class="file-list"></div>
        </div>

        <div class="card">
            <h3>2. Processing Settings</h3>
            
            <div class="slider-group">
                <div class="slider-label">
                    <span>Threshold: <span id="threshVal">127</span></span>
                </div>
                <input type="range" id="threshold" min="0" max="255" value="127" oninput="updateThresh()">
            </div>

            <div class="slider-group">
                <div class="slider-label">
                    <span>Skip: <span id="skipVal">5</span></span>
                </div>
                <input type="range" id="skip" min="1" max="20" value="5" oninput="updateSkip()">
            </div>

            <button onclick="processImage()">Generate TXT File</button>
        </div>

        <div class="status" id="status">Ready</div>
    </div>

    <script>
        function updateThresh() {
            document.getElementById('threshVal').innerText = document.getElementById('threshold').value;
        }
        function updateSkip() {
            document.getElementById('skipVal').innerText = document.getElementById('skip').value;
        }

        function uploadFile() {
            let file = document.getElementById('fileInput').files[0];
            if (!file) return;
            
            let reader = new FileReader();
            reader.onload = function(e) {
                let formData = new FormData();
                formData.append('filename', file.name);
                formData.append('data', e.target.result.split(',')[1]);
                
                fetch('/upload', {method: 'POST', body: formData})
                .then(r => r.text())
                .then(msg => {
                    document.getElementById('status').innerText = 'Uploaded: ' + msg;
                    scanSD();
                });
            };
            reader.readAsDataURL(file);
        }

        function scanSD() {
            fetch('/scan').then(r => r.json()).then(files => {
                let html = '';
                files.forEach(f => html += `<div class="file-item" onclick="loadFile('${f}')">📁 ${f}</div>`);
                document.getElementById('fileList').innerHTML = html;
            });
        }

        function loadFile(name) {
            fetch('/load?file=' + encodeURIComponent(name))
            .then(r => r.json())
            .then(data => {
                if(data.success) document.getElementById('status').innerText = 'Loaded: ' + name;
            });
        }

        function processImage() {
            let settings = {
                threshold: parseInt(document.getElementById('threshold').value),
                skip: parseInt(document.getElementById('skip').value)
            };
            
            document.getElementById('status').innerText = 'Processing...';
            
            fetch('/process', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(settings)
            })
            .then(r => r.json())
            .then(data => {
                if(data.success) {
                    document.getElementById('status').innerText = 'Done! File: ' + data.file;
                    scanSD();
                } else {
                    document.getElementById('status').innerText = 'Processing failed';
                }
            });
        }

        scanSD();
    </script>
</body>
</html>
)rawliteral";

void initSD() {
    Serial.println("\n=== SD CARD INIT ===");
    
    pinMode(SD_CS, OUTPUT);
    digitalWrite(SD_CS, HIGH);
    SPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS);
    
    if (!SD.begin(SD_CS)) {
        Serial.println("SD Mount Failed");
        return;
    }
    
    Serial.println("SD OK");
}

void initWiFi() {
    WiFi.mode(WIFI_AP);
    WiFi.softAP(AP_SSID, AP_PASSWORD);
    Serial.print("AP IP: ");
    Serial.println(WiFi.softAPIP());
}

void handleUpload() {
    if (!server.hasArg("filename") || !server.hasArg("data")) {
        server.send(400, "text/plain", "Missing data");
        return;
    }
    
    String filename = "/" + server.arg("filename");
    String data = server.arg("data");
    
    if (SD.exists(filename)) SD.remove(filename);
    
    fs::File f = SD.open(filename, FILE_WRITE);
    if (!f) {
        server.send(500, "text/plain", "Cannot open file");
        return;
    }
    
    f.print(data);
    f.close();
    server.send(200, "text/plain", filename);
}

void handleScan() {
    fs::File root = SD.open("/");
    if (!root) {
        server.send(500, "application/json", "[]");
        return;
    }
    
    String json = "[";
    bool first = true;
    fs::File f = root.openNextFile();
    
    while (f) {
        String name = String(f.name());
        if (!first) json += ",";
        json += "\"" + name + "\"";
        first = false;
        f.close();
        f = root.openNextFile();
    }
    json += "]";
    
    root.close();
    server.send(200, "application/json", json);
}

void handleLoad() {
    if (!server.hasArg("file")) {
        server.send(400, "application/json", "{\"success\":false}");
        return;
    }
    
    String filename = "/" + server.arg("file");
    fs::File f = SD.open(filename, FILE_READ);
    if (!f) {
        server.send(404, "application/json", "{\"success\":false}");
        return;
    }
    
    bool ok = imgProcessor.loadFromFile(f);
    f.close();
    
    server.send(200, "application/json", ok ? "{\"success\":true}" : "{\"success\":false}");
}

void handleProcess() {
    if (!server.hasArg("plain")) {
        server.send(400, "application/json", "{\"success\":false}");
        return;
    }
    
    StaticJsonDocument<200> doc;
    DeserializationError error = deserializeJson(doc, server.arg("plain"));
    
    if (error) {
        server.send(400, "application/json", "{\"success\":false}");
        return;
    }
    
    ProcessorSettings settings;
    settings.threshold = doc["threshold"] | 127;
    settings.skip = doc["skip"] | 5;
    
    String outFile = "/trace_" + String(millis()) + ".txt";
    
    if (SD.exists(outFile)) {
        SD.remove(outFile);
        delay(100);
    }
    
    bool success = imgProcessor.processToFile(settings, outFile.c_str(), "Crawl");
    
    if (success) {
        String response = "{\"success\":true,\"file\":\"" + outFile + "\"}";
        server.send(200, "application/json", response);
    } else {
        server.send(500, "application/json", "{\"success\":false}");
    }
}

void setup() {
    Serial.begin(115200);
    delay(1000);
    Serial.println("\n\n=================================");
    Serial.println("=== ESP32 G-Code Generator ===");
    Serial.println("=================================");
    
    initSD();
    initWiFi();
    
    server.on("/", HTTP_GET, []() {
        server.send_P(200, "text/html", index_html);
    });
    server.on("/upload", HTTP_POST, handleUpload);
    server.on("/scan", HTTP_GET, handleScan);
    server.on("/load", HTTP_GET, handleLoad);
    server.on("/process", HTTP_POST, handleProcess);
    
    server.begin();
    Serial.println("\n✓ Server started");
    Serial.print("✓ Connect to: http://");
    Serial.println(WiFi.softAPIP());
    Serial.println("=================================\n");
}

void loop() {
    server.handleClient();
}

image_processor.h

#ifndef IMAGE_PROCESSOR_H
#define IMAGE_PROCESSOR_H

#include "common.h"
#include <FS.h>
#include "jpeg_reader.h"

class ImageProcessor {
private:
    String currentFilename;
    int imgWidth;
    int imgHeight;
    
public:
    ImageProcessor() {
        imgWidth = 0;
        imgHeight = 0;
    }
    
    bool loadFromFile(fs::File& file) {
        Serial.println("Loading file info...");
        currentFilename = String(file.name());
        Serial.print("Filename: ");
        Serial.println(currentFilename);
        
        JPEGReader reader;
        String fullPath = "/" + currentFilename;
        if (reader.open(fullPath.c_str())) {
            imgWidth = reader.getWidth();
            imgHeight = reader.getHeight();
            reader.close();
            Serial.print("Image size: ");
            Serial.print(imgWidth);
            Serial.print("x");
            Serial.println(imgHeight);
            return true;
        }
        
        return false;
    }
    
    bool processToFile(const ProcessorSettings& settings, const char* outputFilename, const String& traceMode) {
        Serial.println("=== PROCESSING IMAGE ===");
        
        String inPath = "/" + currentFilename;
        JPEGReader reader;
        if (!reader.open(inPath.c_str())) {
            Serial.println("Failed to open JPEG");
            return false;
        }
        
        imgWidth = reader.getWidth();
        imgHeight = reader.getHeight();
        
        Serial.printf("Image: %dx%d\n", imgWidth, imgHeight);
        Serial.printf("Threshold: %d\n", settings.threshold);
        Serial.printf("Skip: %d\n", settings.skip);
        
        // Удаляем старый файл если есть
        if (SD.exists(outputFilename)) {
            Serial.println("Removing old file...");
            SD.remove(outputFilename);
            delay(100);
        }
        
        // Создаем новый файл
        Serial.printf("Creating file: %s\n", outputFilename);
        File outFile = SD.open(outputFilename, FILE_WRITE);
        if (!outFile) {
            Serial.println("ERROR: Cannot create output file!");
            reader.close();
            return false;
        }
        
        Serial.println("File created successfully");
        
        // Буфер для строки
        uint8_t* lineBuffer = (uint8_t*)malloc(imgWidth);
        if (!lineBuffer) {
            Serial.println("ERROR: Cannot allocate line buffer");
            outFile.close();
            reader.close();
            return false;
        }
        
        // Сначала подсчитаем точки
        int totalPoints = 0;
        Serial.println("Counting points...");
        
        for (int y = 0; y < imgHeight; y += settings.skip) {
            if (!reader.readLine(lineBuffer, y)) {
                continue;
            }
            
            for (int x = 0; x < imgWidth; x += settings.skip) {
                if (lineBuffer[x] < settings.threshold) {
                    totalPoints++;
                }
            }
        }
        
        Serial.printf("Found %d points\n", totalPoints);
        
        // ВАЖНО: Переоткрываем файл для записи с начала
        outFile.close();
        outFile = SD.open(outputFilename, FILE_WRITE);
        if (!outFile) {
            Serial.println("ERROR: Cannot reopen file!");
            free(lineBuffer);
            reader.close();
            return false;
        }
        
        // Записываем количество точек
        outFile.println(totalPoints);
        outFile.flush();  // Принудительный сброс
        
        Serial.printf("Written header: %d points\n", totalPoints);
        
        // Второй проход - записываем точки
        Serial.println("Writing points...");
        int pointsWritten = 0;
        int flushCounter = 0;
        
        for (int y = 0; y < imgHeight; y += settings.skip) {
            if (!reader.readLine(lineBuffer, y)) continue;
            
            for (int x = 0; x < imgWidth; x += settings.skip) {
                if (lineBuffer[x] < settings.threshold) {
                    outFile.print(x);
                    outFile.print(",");
                    outFile.println(y);
                    pointsWritten++;
                    flushCounter++;
                    
                    // Сбрасываем каждые 50 точек
                    if (flushCounter >= 50) {
                        outFile.flush();
                        flushCounter = 0;
                        Serial.print(".");
                    }
                }
            }
        }
        
        // Финальный сброс
        outFile.flush();
        Serial.println("\nFinal flush done");
        
        // Закрываем файл и даем время SD карте
        outFile.close();
        delay(100);
        
        Serial.println("File closed");
        
        // Проверяем, что файл создался и имеет правильный размер
        if (SD.exists(outputFilename)) {
            File checkFile = SD.open(outputFilename, FILE_READ);
            if (checkFile) {
                size_t fileSize = checkFile.size();
                Serial.printf("File size: %d bytes\n", fileSize);
                
                if (fileSize > 0) {
                    // Покажем первые несколько строк
                    Serial.println("First 5 lines:");
                    checkFile.seek(0);
                    for (int i = 0; i < 5 && checkFile.available(); i++) {
                        String line = checkFile.readStringUntil('\n');
                        line.trim();
                        Serial.printf("  %d: %s\n", i+1, line.c_str());
                    }
                } else {
                    Serial.println("ERROR: File is still empty!");
                    
                    // Попробуем записать тестовую строку напрямую
                    File testFile = SD.open("/test.txt", FILE_WRITE);
                    if (testFile) {
                        testFile.println("Test write");
                        testFile.flush();
                        testFile.close();
                        delay(100);
                        
                        File checkTest = SD.open("/test.txt", FILE_READ);
                        if (checkTest) {
                            Serial.printf("Test file size: %d bytes\n", checkTest.size());
                            checkTest.close();
                        }
                        SD.remove("/test.txt");
                    }
                }
                checkFile.close();
            }
        } else {
            Serial.println("ERROR: File not found after save!");
        }
        
        free(lineBuffer);
        reader.close();
        
        Serial.printf("Done! Saved %d points to %s\n", pointsWritten, outputFilename);
        
        return (pointsWritten > 0);
    }
    
    int getWidth() { return imgWidth; }
    int getHeight() { return imgHeight; }
};

#endif

jpeg_reader.h

#ifndef JPEG_READER_H
#define JPEG_READER_H

#include "common.h"
#include <FS.h>

class JPEGReader {
private:
    fs::File file;
    int width;
    int height;
    bool loaded;
    
    // Поиск размера в заголовке JPEG
    bool findJpegSize() {
        uint8_t buf[64];
        file.seek(0);
        file.read(buf, 64);
        
        // Ищем маркер SOF0 (Start Of Frame 0)
        for (int i = 0; i < 60; i++) {
            if (buf[i] == 0xFF && buf[i+1] == 0xC0) {
                // Нашли SOF0
                height = (buf[i+5] << 8) | buf[i+6];
                width = (buf[i+7] << 8) | buf[i+8];
                return true;
            }
        }
        return false;
    }
    
public:
    JPEGReader() : width(640), height(480), loaded(false) {}
    
    bool open(const char* filename) {
        file = SD.open(filename, FILE_READ);
        if (!file) {
            Serial.println("Cannot open file");
            return false;
        }
        
        if (findJpegSize()) {
            Serial.printf("Found JPEG size: %dx%d\n", width, height);
            loaded = true;
            return true;
        }
        
        // Если не нашли, используем значения по умолчанию
        Serial.println("Using default size 640x480");
        width = 640;
        height = 480;
        loaded = true;
        return true;
    }
    
    void close() {
        file.close();
        loaded = false;
    }
    
    int getWidth() { return width; }
    int getHeight() { return height; }
    
    // Генерируем тестовые данные (шахматная доска)
    bool readLine(uint8_t* grayBuffer, int y) {
        if (!loaded) return false;
        
        for (int x = 0; x < width; x++) {
            // Создаем шахматную доску
            int cellX = x / 20;
            int cellY = y / 20;
            if ((cellX + cellY) % 2 == 0) {
                grayBuffer[x] = 200; // светлый
            } else {
                grayBuffer[x] = 50;  // темный
            }
        }
        return true;
    }
};

#endif

вооо основной функционал есть, а как преобразовывать фото и получать координаты не знаю)))
p.s. надеюсь правильную версию скинул, а то я вымотан эмоционально, и нет сил снова запускать и проверять версии… )))

возникнет идея какая то… от куда что скопировать, напишите пж)))

Я хочу заняться другой фигней. Но моя затея, скорее всего, невыполнима.
У меня есть хостинг, на котором есть FTP-сервер. На этом сервере есть файлы. Так как меня интересует не содержимое файла, а их имена, то файлы на сервере имеют нулевой размер. А хочу сделать вот что. Мобильная ESP01S через определённые промежутки времени ныряет на FTP - сервер, сканирует файлы и передает их имена в МК.

у меня таких из 100 таких не выполнимых 30 точно выполняются!)))

ну тут я думаю надо показать код как вы вообще там файл читаете, а потом думать над парсером…

Файл я там не читаю. Парсер мне там не нужен. Главное получить список имен пустых файлов. На компьютере я делаю это легко. В Delphi есть объект для работы с FTP. Но я пользуюсь SYNAPSE. А вот ESP01S для работы с FTP, скорее всего, не предназначен.

ну если так, и код не нужен, тогда все просто, берем и узнаем имена файлов и размер как на sd карте, и оставляем только те в списке которые имеют 0 размер)))

не знаю насчнт esp01, esp32 с фтп дружит.

1 лайк

Это же очень просто, почему вы говорите, что это может быть не решаемо?

Вам для такой задачи не нужна “FTP библиотека”. Вам нужно открывать\закрывать сокеты и посылать получать данные. Если вы попросите чатгпт написать вам функцию, которая открывает TCP соединение и посылает команду LIST, получает результат - он вам напишет. Полэкрана кода. Ну или сами. Там простые read\write

1 лайк

чатгпт напишет? ню, ню. даже нормальную библу я умом допиловал, чтобы она работала.

Функции он пишет хорошо. Просто не говорите ему ничего про ESP32. Скажите, чтобы написал вам “примитивную функцию, которая использует sockets, открывает соединение с сервером 192.168.1.5, авторизуется через USER PASS (plaintext), посылает команду смены каталога на /MY_DIR и посылает команду LIST (или MLIST)“ и читает содержимое каталога, печатает его на экран. BSD sockets”

1 лайк

ну и что оно тебе написало на такой запрос?