#26 2020-04-21 22:26:41

executer
Учасник
Зареєстрований: 2020-04-19
Повідомлень: 9

Re: Очередная система считывания данных счетчика

Приехал экранчик на ST7789, почти как у вас исходный вариант был. Вы библиотеку Adafruit_ST7735.h использовали? Не осталось бекапа с маленьким экраном подсмотреть?
У Вас экранчик с выведеным пином CS был? У меня - без, я так понимаю он на шине параллельно с SD карточкой сидит и тогда что-то одно получится использовать или подпаиваться надо.

Остання редакція executer (2020-04-22 10:17:53)

Неактивний

#27 2020-04-22 19:59:45

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Очередная система считывания данных счетчика

executer пише:

Приехал экранчик на ST7789, почти как у вас исходный вариант был. Вы библиотеку Adafruit_ST7735.h использовали? Не осталось бекапа с маленьким экраном подсмотреть?
У Вас экранчик с выведеным пином CS был? У меня - без, я так понимаю он на шине параллельно с SD карточкой сидит и тогда что-то одно получится использовать или подпаиваться надо.

У меня был экран ST7735. Я использовал две библиотеки Adafruit_ST7735.h и UTFT.h
Бакапы этих двух библиотек скинул на Google Disk
https://drive.google.com/open?id=1NJuxV … 2Apie_qK7C
Может поможет, но у библиотек разные команды и сами бекапы старые.
Библиотека UTFT более универсальная - подходит для разных дисплеев, на "тяжелая" и мне больше понравилась Adafruit, т.к. она как бы встроена в IDE.

Если судить из этого описания http://arduino.ua/prod466-1-8-128h160-t … reen-modyl то наверное можно и без него.
Я SD карточкой не пользовался, тем более, что карта есть и на плате процессора.

Попробовал записывать данные в GoogleSheets - что-то работает на ESP8266, а что-то на ESP32, но нужно разбираться слета не вышло.

Неактивний

#28 2020-04-22 20:57:37

executer
Учасник
Зареєстрований: 2020-04-19
Повідомлень: 9

Re: Очередная система считывания данных счетчика

Благодарю.
Меня смущало большое количество SPI хостов на ESP32, почему-то решил что для экрана отдельный хост используется, а как указать какой именно бибилиотеке - не понятно, короче перемудрил).

По GoogleSheets можете подсмотреть у меня в предыдущем проекте https://github.com/executer-uno/ESP32_DustTracker - запись пары сотни символов раз в 30 секунд - работает отлично на ESP32. Лимит - миллион ячеек на книгу, кажется. Делал по мануалам от автора библиотеки  https://github.com/electronicsguy/ESP8266/tree/master/HTTPSRedirect, ну только запись в книгу использовал, без календаря и вычитки параметров.

Неактивний

#29 2020-04-25 19:58:19

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Очередная система считывания данных счетчика

executer пише:

По GoogleSheets можете подсмотреть у меня в предыдущем проекте https://github.com/executer-uno/ESP32_DustTracker - запись пары сотни символов раз в 30 секунд - работает отлично на ESP32. Лимит - миллион ячеек на книгу, кажется. Делал по мануалам от автора библиотеки  https://github.com/electronicsguy/ESP8266/tree/master/HTTPSRedirect, ну только запись в книгу использовал, без календаря и вычитки параметров.

Спасибо за ссылку на Ваш предыдущий проект, но как в описании Вы написали
"HTTPS Redirect библиотека Таблицы Google использовал этот протокол для загрузки данных. Самая сложная библиотека как по мне)"

Немного упростил код из других примеров и сделал запись в таблицу значений м3 при изменении. Эти же значения сохраняются в кольцевом буфере.

//запись данных счетчика в таблицу
//https://github.com/mrmdudek/ESP32-send-data-to-google-sheet
//https://github.com/ExploreLab/IoT_ggSpreadSheet 
//https://voltiq.ru/post-data-to-google-sheets-with-esp8266/ - скрипт за основу брал этот
//https://developers.google.com/apps-script/reference/spreadsheet/sheet


#include <WiFiClientSecure.h>
WiFiClientSecure client;
char *server_sheet = "script.google.com";  // Server URL
char *GScriptId = "****************"; //ID 
const int httpsPort = 443;

//---------------------------------------------------- GG_ScriptSheet
void GG_ScriptSheet(float m3_sheet, float minutes_sheet){
  String Data = String("&m3=")+(int)m3_sheet+String("&minutes=") + (int)minutes_sheet; 
  String url32 = String("/macros/s/") + GScriptId + "/exec?"+Data;
  Serial.print("Client.Connecting...");
//  Serial.println(url32);
  if (client.connect(server_sheet, httpsPort)) {
    client.println("GET " + String(url32) + " HTTP/1.1"); //HTTP/1.0 OK sent LINEnotify and GoogleSheet
    client.println("Host: " + String(server_sheet));
    client.println("User-Agent: ESP32_counter");
    client.println("Connection: close\r\n\r\n");
//    Serial.println("Response...OK !!!");  
  }
  else Serial.println("Connection failed!");  
/*  
  while (client.connected()) 
  {
      String line = client.readStringUntil('\n');
      if (line == "\r") 
      {
//        Serial.println("headers received");
        break;
      }
    }
    // if there are incoming bytes available
    // from the server, read them and print them:
    while (client.available()) 
    {
      char c = client.read();
//      Serial.write(c);
  }
*/
  client.stop();
}
//---------------------------------------------------- GG_ScriptSheet

Поставил на прогон - работает.
Таблица состоит из двух листов на первом - последнее время записи, число строк и последнее значение счетчика.
На вторую страницу записываются сами данные виде ID | DataTime | m3*100 | minutes
За трое суток, при неработающем отоплении, у меня в кольцевом буфере сохранилось около 150 значений. Потребление при использовании горячей воды (как правило не более 0,01 м3/мин).

Остання редакція NickVectra (2020-04-30 07:04:45)

Неактивний

#30 2020-04-30 00:47:08

executer
Учасник
Зареєстрований: 2020-04-19
Повідомлень: 9

Re: Очередная система считывания данных счетчика

Шикарно, я думал там сплошная магия в библиотеке ) Гуглотаблицы кажется идеальное хранилище таких данных.

Тем временем промучился неделю с тем чтобы обойтись без экрана и без MMC карточки, голой ESP-CAM. Т.е. чтобы весь интерфейс в браузере был.
Самая сложная часть побеждена - https://github.com/executer-uno/ESP32_CAM_WEB_Photo
Показывает в браузер фоточки из буффера. Проблема была в том что целиком изображение не влазило в буффер web-сервера (использовал ESPAsyncWebServer и поэтому путем проб и ошибок пришлось дойти до chunked запросов, они оказались за пределами моих знаний в С), наконец все поборол и BMP картинка доступна в браузере.

Теперь буду прикручивать обработку картинки из Вашего проекта.

Неактивний

#31 2020-04-30 07:04:04

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Очередная система считывания данных счетчика

executer пише:

Шикарно, я думал там сплошная магия в библиотеке ) Гуглотаблицы кажется идеальное хранилище таких данных.

А я пока немного застрял на сбросе картинки на Google Disk по запросу на основе этого примера https://github.com/executer-uno/esp32cam-gdrive.

Изображение взял такое же как и у меня 320х240 PIXFORMAT_GRAYSCALE. После получения от камеры преобразовываю в JPEG (frame2jpg()).

Это работает, а вот после добавления "рабочего" куска в общий проект нет ответа от сервера Google после отправки туда картинки. 

Фактически срабатывает этот кусочек кода

Serial.println("Waiting for response.");
    long int StartTime=millis();
    while (!client.available()) {
      Serial.print(".");
      delay(100);
      if ((StartTime+waitingTime) < millis()) {
        Serial.println();
        Serial.println("No response.");
        //If you have no response, maybe need a greater value of waitingTime
        break;
      }

waitingTime - 30 секунд, хотя, когда работает полный цикл (считывание, кодировка и  отправка) занимает не более 5 секунд, т.е. не в этом дело  sad.

Так и не смог за несколько дней добиться стабильной работы записи изображения на  Google Disk, поэтому переделал сегодня (1 мая) сохранение не на Google Disk, а на DropBox на основе https://github.com/duthienkt/ESP32DropboxCam

Этот код работает нормально

//https://github.com/duthienkt/ESP32DropboxCam
//http://developer.alexanderklimov.ru/android/dropbox.php

#include "dropbox.h"

// Это ключ приложения, каждое приложение будет иметь свой собственный ключ
#define DROPBOX_ACESS_KEY "*************************"

DropboxMan dropboxMan;

void capture_DropBox(){

  size_t _jpg_buf_len = 0;
  uint8_t * _jpg_buf = NULL;

  fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    return;
  }

  if (fb->format != PIXFORMAT_JPEG) {
    bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
    if (!jpeg_converted) {
      esp_camera_fb_return(fb);
      fb = NULL;
      Serial.println("JPEG compression failed");
      return;
    }
  }
  else {
    _jpg_buf_len = fb->len;
    _jpg_buf = fb->buf;
  }

  String path = "/picture/";
  // Если есть возможность получить время из интернета, используйте его для названия, если нет то время запуска
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    path += String(millis());
  }
  else {
    char buf[50];
    sprintf(buf, "%4d%02d%02d_%02d%02d%02d\0", timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
            timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
    path += buf;
    //        Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
  }
  path += ".jpg";

  dropboxMan.bufferUpload(_jpg_buf, _jpg_buf_len, path, 1); // save to DropBox
  esp_camera_fb_return(fb);
}

Остання редакція NickVectra (2020-05-01 19:38:05)

Неактивний

#32 2020-05-02 07:29:57

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Очередная система считывания данных счетчика

Изображение, которое "видит" камера, сохраненное в DropBox сегодня утром в 7:13
e67be9c8b9a8bb347d789a5e3ceda96d.jpg
Экран дисплея
76674f725e5da0860e3eea6b7aa442a7.jpg

Неактивний

#33 2020-05-07 12:23:06

executer
Учасник
Зареєстрований: 2020-04-19
Повідомлень: 9

Re: Очередная система считывания данных счетчика

NickVectra, добрый день. Перечитал первый пост с описанием, я правильно понял что окно с индикатором счетчика ищется по всему кадру? Единственное требование - оно должно быть более-менее горизонтально расположено на кадре?
Я пока думаю что для понимания шагов обработки и промежуточных результатов нужно сделать подмену TFT библиотеки - заглушку чтобы она генерировала TFT вывод в jpeg картинки для браузера, чтобы в исходном проекте графический вывод не менять сильно.

Неактивний

#34 2020-05-07 13:17:42

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Очередная система считывания данных счетчика

executer пише:

я правильно понял что окно с индикатором счетчика ищется по всему кадру?

На первых этапах при разработке так и было. Потом я ограничил поиск в процедуре find_digits_y
//поиск положения цифр по высоте Y
#define  Y_FIRST_LOOK 10  //ограничим начало поиска 10 пикселями в строке
#define  Y_LAST_LOOK 100 //ограничим конец поиска 100 пикселями в строке

но тестовый счетчик был первоначально "затемнен" черной бумагой (см. фото 2 первого сообщения) и сторонних засветов, которые могли быть восприняты как шкала не было.

Когда стал применять программные "шторки"
#define old_level_Y_up 22 //Положение шторки сверху Y_up
#define old_level_Y_down 48 //Положение шторки снизу X_down

то изображение выше и ниже шторок в программе зачерняется и затемнение счетчика потеряло смысл.

Сейчас, когда эта часть отработана и нет перемещения счетчика, то поиск положения шкалы по оси Y фактически не имеет значения, т.к. шкала уже находится в нужном месте сразу, а я могу подстроить окно из приложения, изменив значение "V2 - смещение по оси Y при суммировании кадров и отображении на дисплее" .
Кроме того идет постоянная автоматическая подстройка положения цифр, о которой я писал выше.

Как мне кажется, для стационарных объектов проще сразу определиться с местом шкалы на изображении и подстраиваться только в небольших пределах, связанных с возможной неточной установкой камеры и индикатора.

executer пише:

Единственное требование - оно должно быть более-менее горизонтально расположено на кадре?

Это требование вызвано тем, что у меня нет процедуры вращения изображения.

executer пише:

Я пока думаю что для понимания шагов обработки и промежуточных результатов нужно сделать подмену TFT библиотеки - заглушку чтобы она генерировала TFT вывод в jpeg картинки для браузера, чтобы в исходном проекте графический вывод не менять сильно.

Мне как-то было проще делать это вначале физически - подклеивая изоленту и картонки smile , а затем выводить изображение именно на индикатор.
Были случаи, когда изображение "терялось" из-за значительного перемещения счетчика и камеры поэтому и ввел режим вывода полного изображения на индикатор в градациях серого (фото 5 первое сообщение).
Я запускал программу без подключенного физически индикатора TFT и ошибок не выдавало.

Остання редакція NickVectra (2020-05-07 13:22:11)

Неактивний

#35 2020-05-11 17:30:46

executer
Учасник
Зареєстрований: 2020-04-19
Повідомлень: 9

Re: Очередная система считывания данных счетчика

Так как у меня в поле зрения будут два счетчика воды то для получения достаточного разрешения переключил камеру на максимальный формат 1600*1200 в JPEG с качеством = 5. Довольно неплохое изображение делает. Так как такой кадр ни в какую память не влезет в распакованном виде - помогла библиотека из списка ардуиновского репозитария "JPEGDecoder". В ней можно получить доступ к каждому запакованному кусочку JPEG и обработать необходимые. Нужный диапазон в итоге сохраняю в буфер с накоплением нескольких кадров, для получения некоего подобия HDR изображения. К сожалению JPEG выдает меньше чем 8 бит на канал, поэтому для получения итогового качества как у GRAYSCALE формата нужно делать несколько снимков для усреднения. У меня уходит 17 секунд на 5 кадров + 1 кадр превью с высоким сжатием.

Дальше этапы обработки отрисовываются в буфере, который потом жмется в JPEG и доступен в браузере:
скриншот
Следующий шаг - собственно распознавание текста )

Остання редакція executer (2020-05-11 17:32:17)

Неактивний

#36 2020-05-11 18:11:09

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Очередная система считывания данных счетчика

Большая работа - выходные проходят не зря.

На картинке там где нули засветы они могут немного затруднять распознавание. Это видно и на найденной ширине цифр - она разная.

Как у Вас выполнена подсветка?
Вы линзу ставили?

Остання редакція NickVectra (2020-05-11 18:11:49)

Неактивний

#37 2020-05-11 18:29:53

executer
Учасник
Зареєстрований: 2020-04-19
Повідомлень: 9

Re: Очередная система считывания данных счетчика

Да уж, погрузился основательно, но удачно все нужные библиотеки и примеры нашлись и особо косяков никаких не вылезло. Повезло.
Камера ESP_CAM32 как есть, без доработок. Подсветка встроенным светодиодом родным. Там где у меня стоят счетчики - других источников света нет, условия стабильные - темнота ). Тестовый стенд - ч.б. распечатка фотки с телефона перед камерой (телефон со вспышкой фотографировал). Не слишком натуралистично, но пока с такими условиями разберусь что к чему, а там буду думать как дальше тестировать. Фото с камеры в тестовых условиях
camera view

Неактивний

#38 2020-05-25 20:08:46

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Очередная система считывания данных счетчика

На работе возникла задача считывания показаний электросчетчика.
Но камера должна стоять не вплотную к счетчику, а на расстоянии.

Вначале хотел повторить эту систему увеличив разрешение камеры и вырезая нужный кусок изображения - практически все собрал и запустил, но потом решил, что нужно двигаться дальше smile

Batu пише:

Думаю здесь вполне можно реализовать распознавание с помощью нейронной сети. Задача обсосаная и решений полно. На одном шрифте обучение должно быть простым.

На основании этого примера https://github.com/surya-veer/RealTime-DigitRecognition на Python OpenCV (настольный компьютер) получается довольно быстро (на мое обучение Python и первые наброски программы ушло неполных 2 дня).

Изображение, которое можно получить практически от любого источника (файл, Web камера, USB камера), преобразуется в градации серого и после фильтра переводится в черно-белое. Найденные контуры позволяют определить положение цифр шкалы, которые вырезаются из общего изображения.

В начале (май - середина июня 2020) распознавание цифр проводилось уже обученной нейронной сетью, предназначенной для рукописных цифрах. Однако анализ показал, что некоторые цифры распознавались не точно.

Поэтому была построена и обучена новая нейронная сеть на изображениях цифр реальной шкалы. Для увеличения ряда обучающей последовательности был использован ImageDataGenerator, который каждую цифру смещал по осям и производил вращение в произвольном порядке. Всего было создано 10000 изображений.
Нейронная сеть обучалась 8 эпох.

Не думаю, что все это можно реализовать на ESP32 Tensofflow lite, хотя заманчиво.
Основные идеи программы приведены ниже.

#!/usr/bin/env python3

import os
from datetime import datetime, timedelta

import numpy as np
import openpyxl
from PIL import Image
import cv2
import sys
import imutils
from keras.models import load_model
import glob
import time

from collections import Counter
import urllib

# поиск и выделенее цифр шкалы


def letters_extract(image_pic, image_size_W, image_size_H):
    add_x_y = 10
    min_contourArea = 2000  # минимальне площадь контура, которая отбрасывается
    min_h = 150  # минимлаьная высоты цифры
    max_h = 210  # максимальная высота цифры
    min_w = 60  # миниалная ширина цифры

    # преобразовать в градации серого
    gray = cv2.cvtColor(image_pic, cv2.COLOR_BGR2GRAY)
    # cv2.imshow("gray", gray)

    W = image_pic.shape[1]
    H = image_pic.shape[0]

    # Apply bilateral filter with d = 15,
    # sigmaColor = sigmaSpace = 75.
    gray = cv2.bilateralFilter(gray, 4, 75, 75)

    # cv2.imshow("gray1", gray)
    # поиск значения уровня бинаризации
    ret, thresh = cv2.threshold(
        gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    # cv2.imshow("thresh", thresh)
    # бинаризация изображения
    _, binary = cv2.threshold(gray, ret + 5, 255, cv2.THRESH_BINARY_INV)
    cv2.imshow("binary", binary)

    img_erode = binary.copy()
    # выделенее контуров
    contours, hierarchy = cv2.findContours(
        img_erode, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

    output = image_pic.copy()
    letters = []
    for idx, contour in enumerate(contours):  # индекс и элемент
        (x, y, w, h) = cv2.boundingRect(contour)
        # если площадь меньше отбрасываем
        if cv2.contourArea(contour) < min_contourArea:
            continue
        # строим рамку вокруг контура
        cv2.rectangle(output, (x, y), (x + w, y + h), (255, 0, 255), 1)
        # подписываем высоту, ширину и площадь контура
        cv2.putText(output, str(h)+" "+str(w)+" " + str(cv2.contourArea(contour)), (x+30, y+40), cv2.FONT_HERSHEY_SIMPLEX,
                    0.4, (255, 0, 255), 1, cv2.LINE_AA)

        # отбираем по высоте
        if h < min_h or h > max_h:
            continue

        if hierarchy[0][idx][3] > -1:
            # расширим размер области для вырезания цифры
            if (w < min_w):
                x -= (min_w - w) // 2
                w = min_w

            x -= add_x_y
            y -= add_x_y
            # подписываем высоту и ширину контура
            cv2.putText(output, str(h)+" "+str(w)+" "+str(cv2.contourArea(contour)), (x, y), cv2.FONT_HERSHEY_SIMPLEX,
                        0.5, (0, 255, 0), 1, cv2.LINE_AA)
            w += 2 * add_x_y
            h += 2 * add_x_y
            # строим расширенную рамку вокруг контуров
            cv2.rectangle(output, (x, y), (x + w, y + h), (0, 255, 255), 1)

            # вырезаем область контура - считем это цифой
            letter_crop = gray[y:y + h, x:x + w]
            # cv2.putText(output, str(idx), (x, y), cv2.FONT_HERSHEY_SIMPLEX,
            #             1, (0, 255, 0), 1, cv2.LINE_AA)

            # print(str(len(letters)), letter_crop.shape, idx)
            # изменяем размер
            letter_crop = cv2.resize(letter_crop, (image_size_W, image_size_H))
            letters.append((x, y, h, letter_crop))

    # сортируем значения по X-координате
    letters.sort(key=lambda a: (a[0], a[2]), reverse=False)

    return letters, output


if __name__ == "__main__":
    print("Start loading model...")
    model_32_16 = load_model('my_model.h5')
    print("Model loaded")

    old_time = int(time.time())
    # файл сохранения значений
    WORKBOOK_NAME = 'Counter.xlsx'
    ONE_HOUR = timedelta(hours=1)
    old_shot_hour = datetime.now().hour

    if os.path.isfile(WORKBOOK_NAME):
        wb = openpyxl.load_workbook(WORKBOOK_NAME)
    else:
        wb = openpyxl.Workbook()
    ws = wb.active
    # параметры изображения для распознавания
    image_size_H = 32
    image_size_W = 16
    num_channels = 1

    old_count = 0
    counter_volue = []
    counter_res = 0

    d = 0
    while (1):
        # видео берем от телефона с программой IP WebCam
        cap = cv2.VideoCapture("rtsp://10.0.1.251:8080/h264_ulaw.sdp")
        ret, start_pic = cap.read()
        if ret is not True:
            continue
        # вырезаем область шкалы с запасом
        image_pic = start_pic[100:700, 0:1920]
        H = image_pic.shape[0]
        W = image_pic.shape[1]
        # понизим яркость в районе красных цифр
        image_pic[0:H, W-230:W] = cv2.add(
            image_pic[0:H, W - 230:W], np.array([-40.0]))
        # повысим яркость в районе первой цифры
        image_pic[0:H, 90:300] = cv2.add(
            image_pic[0:H, 90:300], np.array([30.0]))

        # надем все цифры шкалы
        letters, output = letters_extract(
            image_pic, image_size_W, image_size_H)
        i = 0
        count_metter = 0
        while i < len(letters):
            # обработаем найденные цыфры
            pic = letters[i][3]
            pic_data = np.reshape(
                pic, (1, image_size_H, image_size_W, num_channels))
            # опознать цифру
            predictions = model_32_16.predict(pic_data)
            dig = np.argmax(predictions)

            ver = predictions[0][dig]
            y_dig = letters[i][1]
            # если цифры находятся на одной оси Х - то выберем максимальную по высоте
            if i < len(letters) - 1:
                if abs(letters[i][0] - letters[i + 1][0]) < 50:  # x
                    print("цифра {} на одной оси x = {} {}".format(
                        i, letters[i][0], letters[i + 1][0]))
                    if letters[i][2] < letters[i + 1][2]:  # h
                        pic = letters[i + 1][3]
                        pic_data = np.reshape(
                            pic, (1, image_size_H, image_size_W, num_channels))
                        predictions = model_32_16.predict(pic_data)
                        dig = np.argmax(predictions)
                        ver = predictions[0][dig]
                        print("next -------- ", end=" ")
                        print(i, (dig, ver),
                              (letters[i + 1][0], letters[i + 1][2]))
                        y_dig = letters[i + 1][1]

                    else:
                        print("old -------- ", end=" ")
                        print(i, (dig, ver), (letters[i][0], letters[i][2]))

                    i += 1

            # преобразуем значения в число
            count_metter = count_metter * 10 + dig
            # вывод значений опознанных цифр
            cv2.putText(output, str(dig), (letters[i][0]+35, y_dig-20), cv2.FONT_HERSHEY_SIMPLEX,
                        1, (255, 255, 0), 1, cv2.LINE_AA)

            i += 1

        now_time = int(time.time())
        now_date = datetime.now()
        # сохранение показаний в таблице каждый час
        if (now_date.minute == 0) and (now_date.hour != old_shot_hour):
            cv2.imwrite(now_date.strftime("%Y%m%d-%H%M%S.jpg"), start_pic)
            delta = counter_res - old_count
            time_delta = timedelta(seconds=(now_time-old_time))
            ws.append([now_date, old_count, time_delta, delta])
            wb.save(WORKBOOK_NAME)
            old_shot_hour = now_date.hour
        # сохранение показаний в таблице при изменении
        if (now_time - old_time) >= 60:
            if (len(letters) == 7):
                counter_volue.append(count_metter)
                # накопленее значений для усреднения 17 раз
                if len(counter_volue) == 17:
                    c = Counter(counter_volue)
                    counter_res = c.most_common(1)[0][0]
                    counter_res_n = c.most_common(1)[0][1]
                    print("c = {} counter_res = {} counter_res_n = {}".format(
                        c, counter_res, counter_res_n))
                    counter_volue = []

                    if old_count != counter_res:
                        delta = counter_res - old_count
                        time_delta = timedelta(seconds=(now_time-old_time))
                        ws.append([now_date, counter_res, time_delta, delta])
                        ws.column_dimensions['A'].width = 20
                        wb.save(WORKBOOK_NAME)
                        old_time = now_time
                        old_count = counter_res
            else:
                print("recognized " + str(len(letters)), end=" digits ")
            print("data_time = {} counter_res = {} count_metter = {} old_count = {}".format(
                datetime.now(), counter_res, count_metter, old_count))

        # print("-------")
        cv2.imshow("finish", output)
        k = cv2.waitKey(5) & 0xFF
        if k == 27:
            print("CLOSING WEBCAM")
            break

    cap.release()
    cv2.destroyAllWindows()

Эксплуатация программы в течении месяца (середина июня - июль 2020) показала ее полную работоспособность. Может нужно будет перевести потом на Raspberry Pi

Остання редакція NickVectra (2020-07-09 10:54:15)

Неактивний

#39 2021-04-17 21:44:02

Araqel
Учасник
З Армения
Зареєстрований: 2021-04-17
Повідомлень: 12

Re: Очередная система считывания данных счетчика

NickVectra пише:

Учитывая актуальную задачу «экономии» ресурсов возникла идея считывания показаний счетчика с возможностью в дальнейшем проведения анализа и построения графиков потребелния. Работу электросчетчика можно контролировать с помощью измерителя мощности, например, такого как PZEM-004. Для счетчика воды можно врезать в водопровод дополнительный водомер. Для учета потребления газа это сделать самостоятельно невозможно - поэтому и решено было попробовать создать устройство считывания показаний. Поиск в интернете показал, что задача выполнима, хотя и довольно сложна в виду отсутствия у меня соответствующих в том числе и базовых знаний.
    Как правило в квартирах установлены счетчика старого образца, которое не имеют устройства встроенного модуля считывания данных и не формирует импульсов, из-за этого единственный способ снятия показаний — оптический.
https://picua.org/images/2020/03/29/54f229c582f7f6d9d954916f5dab3fd9.th.jpg https://picua.org/images/2020/03/29/abd006fd3b3497abc3586418a9c48889.th.jpg

На первых этапах проработки идеи рассматривался вариант считывания показаний только последней цифры счетчика с помощью фото и светодиода (датчик TCRT5000), например, подсчет белых делений (https://www.ab-log.ru/forum/viewtopic.php?t=8&start=160). На эту идею натолкнула также разработка, в которой предложено считывать с помощью фотоэлемента импульсы светодиода электросчетчика (https://habr.com/ru/post/234679/, https://forum.arduino.ua/viewtopic.php?id=857, https://mysensors.ru/build/pulse_water/). Однако данный метод предполагает установку первоначальных значений и не обеспечивает возврата к текущим показаниям в случае сбоя.
    В результате было принято решение построить систему способную распознать цифры на шкале счетчика. Как известно, сейчас доступен, в том числе и на бесплатной основе, сервис по распознаванию текста на картинках в интернете. Естественно был рассмотрен вариант передачи файла с изображением счетчика и получением результата распознавания. На https://www.youtube.com/watch?v=Upcr_rXnMQY было выложено видео в котором тестировалось Google Vision. В ходе экспериментов несколько фотографий шкалы без предварительной обработки были «скормлены» таким бесплатным сайтам. В большинстве случаем либо они отказывались распознавать цифры и сообщали, что это капча или в виду плохого качества фотографии распознавали их неправильно. Причем текст на лицевой панели самом счетчика распознавался лучше чем цифры шкалы. Можно было бы установить на компьютере одну из бесплатных программ распознавания, однако, как мне показалось такое решение выглядит несколько громоздким. Кроме того в любом случае требовалась предварительная обработка фотография.
    В качестве основного модуля использован ESP32-CAM. Данный модуль обладает достаточными ресурсами, компактен и имеет встроенную камеру. Для вывода служебной и визуальной информации  был вначале использован TFT дисплея 1,8“ работающий по шине SPI. В окончательном варианте был применен 2,0“ TFT дисплей с разрешающей способностью 220х176 пикселей. Данный дисплей позволил вывести больше необходимой визуальной информации и показать всю шкалу счетчика практически полностью.  Все используемые компоненты могут питаться от 5 В, но при этом хорошо стыкуются по 3,3 В.
    Вначале все делалось на «коленках» в виде конструктора из досточек и изоленты. Любое неосторожное задевание стола или неудачный жест рукой приводил к необходимости по новой строить макет. В качестве дисплея был использован счетный механизм от старого счетчика, который в свое время не прошел поверку.
https://picua.org/images/2020/03/29/7c23d9c9f5bff417641da3ff5e1cc748.jpg
Когда надоело мучатся и подошло время натуральных живых испытаний для удобства отладки устройства камера с процессором и дисплей были собраны на единой плате. Возможно из-за того, что встроенная антенна ESP32-CAM была обращена в строну платы связь по WiFi была неустойчивая. Пришлось выпаивать модуль с процессором/камерой и подключать внешнюю антенну. Из аппаратной части также был установлен преобразователь USB-TTL для связи с компьютером и возможностью прошивки модуля. Повышающий преобразователь МТ3806 был использован для формирования напряжения питания светодиодов подсветки в пределах 7-9 В. Включение модулей стандартное и не имеет каких либо особенностей. В библиотеки доработки не вносились.

    Более подробно об алгоритме обработки изображения:
    Камера ESP32-CAM работает в режиме градаций серого с разрешением 320х240 пикселей. Каждый пиксель имеет значение от 0 до 255 и занимает один байт.
    Из-за того, что фокусное расстояние штатного объектива линзы камеры около одного метра перед камерой установлена дополнительная линза 5Х (недорогая линза найденная в ближайшем магазине). Это позволило получить боле-менее не замыленное изображение шкалы счетчика на расстоянии около 10 сантиметров при захвате всей шкалы сразу. Установка линзы с большим увеличением привело бы к тому, что расстояние между камерой и счетчиком сократилось, но вся шкала тогда не помещалась. Эксперименты показали, что подходит любая линза от 4Х до 8Х.
    Для устранения шумов камеры полученные изображения (кадры) в градациях серого усредняются (sum_frames) и интегрированный результат помещается в отдельный буфер с разрешением 320х240 пикселей (camera_capture). С помощью Web интерфейса число кадров суммирования можно выбрать от 1 до 10. По умолчанию установлено 5 кадров.
    В функции find_middle_level_image предварительно автоматически определяется уровень яркости изображения методом Отцу. Данный метод дает более стабильный результат по сравнению со среднеарифметическим и наиболее точно приближен к «идеальному» уровню бинаризации, который обеспечивает более качественное выделение цифр шкалы.
    Процедура find_digits_y позволяет определить границы расположения строки с изображением шкалы счетчика в общем изображении получаемым от камеры. При проведении бинаризации, для определенного на предварительном шаге уровня яркости, строчки изображения которые соответствуют шкале счетчика будут иметь максимальное суммарное значение пикселей, т. к. в этом месте общего изображения будет самое большое количество засвеченных пикселей. Определенные на этом этапе границы отображаются на индикаторе в виде трех горизонтальных линий (верх, середина и низ цифр) для визуального анализа и последующей коррекции при необходимости.
https://picua.org/images/2020/03/29/4beea593845e6f4e00d2e6098778bfed.jpg
Для того, чтобы хота как-то можно было избавиться от посторонних засветов линзу были установлены «шторки». В дальнейшем учитывая то, что камера и индикатор расположены неподвижно друг по отношению к другу и после первоначальной установки не перемещаются - на последующих этапах можно применить так называемый программный метод «шторок», с помощью которого ограничивалась область индикатора с точностью до нескольких пикселей и тем самым обрезать засветка его границ. Дополнительную корректировку значений границ цифр шкалы счетчика и метоположения самого индикатора, также можно выполнить через Web интерфейс.
    В процедуре find_max_letter_X осуществляется поиск середины цифр. Уровень бинаризации повышается и по оси Х строится гистограммы суммы пикселей по вертикали. Дополнительно не учитываются еще два пикселя младших пикселя. После нахождения границ первой цифры осуществляется поиск следующей с шагом равным ширине цифры - 26 пикселей. Для каждой найденной цифры строятся вертикальные линии: левый край, середина, правый край показывающие точность автоматического нахождения положения цифры на изображении. Варианты построения гистограмм яркостных уровней по осям Х и Y для каждой цифры показаны на фотографиях.
https://picua.org/images/2020/03/29/48e31516327611019b8ce0de54ec51e5.jpg
На дисплей также вывена информация об уровне середины цифр, который на показанных фотографиях равен 45 пикселям (Ymid), высота цифр 21 пикселm (Y_d) и средний уровень яркости (mid_lev) изображения 39 единиц. В дальнейшем после отладки данного шага алгоритма от вывода гистограмм на экран дисплея я отказался. В связи стем, что горизонт камеры и счетчика выставлены точно и не перемещаются в дальнейшем уточнении границ расположения шкалы счетчика нет необходимости. Поэтому гистограмма по оси Y для каждой цифры не используется в алгоритме распознавания. Следует обратить внимание, что если при первоначальном нахождении положения окна шкалы счетчика по оси Y пороговая яркость для бинаризации не изменялась по сравнению с определенной по всему изображению, то для определения границ отдельных цифр по оси Х уровень яркости был повышен на 60 единиц. Если этого не делать, то не произойдет четкого разделения яркостной гистограммы на отдельные цифры. Т.е. на каждом из выше приведенных этапов обработки был применен свой уровень яркости при проведении бинаризации.
    Качество получаемого от камеры изображения в том числе зависит и от освещенности. К сожалению, даже применение отельных линеек светодиодов для подсветки шкалы счетчика и закрытие всей конструкции от внешнего света не позволяет добиться идеальной и равномерной яркости для всех цифр одновременно. Поэтому на следующем этапе определив места расположения середины цифры по оси Х в пределах предполагаемой ширины цифры в границах расположения цифр по оси Y производиться расчет среднего уровня яркости для каждой отдельной цифры шкалы счетчика (процедура find_middle_britnes_digital).
    Для поиска совпадения полученного изображения цифр с эталоном в начале был опробован метод свертки https://ru.m.wikipedia.org/wiki/Свёртка … й_анализ)) . Однако этот метод не давал приемлемого результата и предпочтение было отдано подсчету расстояния Хемминга https://ru.wikipedia.org/wiki//Расстояние_Хэмминга.
Для удобства расчета расстояния Хемминга при наложении эталона на изображение осуществляется бинаризация и преобразование изображения в 32 битное число в процедуре convert_to_32. Уровень бинаризации для каждой отдельной цифры выбирается в этом случае индивидуальный (процедура find_middle_britnes_digital).
    Расчет и поиск минимального расстояния Хемминга выполняется в функции image_recognition, которая возвращает значение распознанной цифры. При изменении значения счетчика в процессе своей работы цифры могут занять положение, которое смещено по отношению к середине шкалы счетчика по высоте (промежуточные положения цифр не учитываются). Кроме того, при определении границ каждой цифры возможны отклонения на несколько пикселей как по оси X так и по Y. Поэтому, сравнение эталона с распознаваемой цифрой производиться с учетом возможного смещения на 4 пикселя вверх, 4 пикселя вниз, 1 пиксель право и 1 пиксель влево. Всего возможно 27 вариантов различных смещений. Из всех рассчитанных вариантов сдвигов и всех имеющихся эталонов выбирается тот, который имеет меньшее значение расстояния Хемминга. В качестве порогового, в дальнейшем, применяется значение расстояния Хемминга менее 50 единиц.
    Для последующего усреднения и накопления результатов распознавания цифр в процедуре frequency происходит подсчет числа совпадения распознанной цифры. По результатам выбирается цифра имеющая максимальное значение. Всего таким образом учитывается 10 выборок. В качестве порогового применяется значение равное 7. Таким образом, если из 10 выборок не менее 7 раз распознана одна и та же цифра и при этом расстояние Хеминга было менее порогового уровня 60 — считается, что цифра распознана правильно и она выводиться на индикатор желтым цветом. Если данные условия не выполняются, то цифра имеет красный цвет. В случае, когда на предыдущем цикле распознавания цифра уже имела такое же значение, то она становиться зеленой.

    Для визуального контроля на дисплей может выводятся изображение шкалы в следующих режимах:
- черно-белое изображение, которое прошло бинаризацию согласно описанного выше алгоритма. Данный режим позволяет подкорректировать уровень бинаризации и определить засветы.
- шкала выведенная в градациях серого. Распознавание при этом осуществляется также согласно алгоритма. Применяется для визуального считывания данных шкалы и более точного нахождения границ цифр;
- изображение выводиться в градациях серого на весь экран. Распознавание не осуществляется. Данный режим необходим при первоначальных установках положения камеры и позволяет определить где находиться шкала на изображении.
https://picua.org/images/2020/03/29/d46a3a5446d0acdf252d100018055358.jpg
На 1,8“ дисплее одновременно помещается первых 5 цифр счетчика, которое собственно и нужны при передаче показаний (остальные три цифры после запятой). На 2,0“ помещается все цифры. Через Web интерфейс при необходимости можно осуществить сдвиг выводимой информации для визуального просмотра и анализа.
    На первых этапах разработки программы в качестве эталонного использовался шрифт получаемый от программного знакогенератора. Однако, как выяснилось позже каждый счетчик может иметь свой шрифт на табло. Кроме того, формируемый  шрифт знакогенератором имеет фиксированные размеры 22, 24 и т.д. пикселя по высоте, что не всегда удобно. Кроме того трудно подобрать шрифт, которые будет максимально совпадать со шрифтом шкалы счетчика.
    В данной реализации высота цифр принята 26 пикселя. В качестве эталона использовано полученное от камеры бинаризированное изображение. Для упрощения этой процедуры в программе предусмотрена возможность вывода в порт в шестнадцатеричном формате цифр шкалы. Данные могут быть скопированы сразу в программу для последующего использования как эталонные. Эталонны хранятся в виде массива uint32_t sample[number_of_samples][sample_height] файл sample.h. Массив эталонов может быть расширен различными версиями начертания цифр в том числе и занимающие половинное положение.
    Некоторые этапы разработки приведены на фото ниже
https://picua.org/images/2020/03/29/c307f755191d44535c2bef78c23bb4e0.jpg
На последней фотографии видно, что даже самая последняя цифра успела определиться корректно. На этой же фотографии в нижней строчке выведены значения индивидуальной яркости для каждой цифры (минимальное 62 максимальное 103).

    Оперативное управление осуществляется через Web (файл myserver.h), интерфейс которого показан на рисунке и имеет следующие органы управления:
number_of_sum_frames — число кадров суммирования изображения;
offset_y — смещение по оси  Y при выводе на дисплей и интегрировании кадров. Позволяет подогнать высоту расположения индикатора разных моделей;
level_dispalay_ttf_B_W — дополнительный уровень яркости добавляемый к автоматически определенному при выводе на дисплей в черно-белом режиме;
level_find_digits_y — дополнительный уровень яркости добавляемый к автоматически определенному при нахождении границ цифр по высоте — ось Y;
level_find_max_letter_X — дополнительный уровень яркости добавляемый к автоматически определенному при нахождении границ цифр по ширене — ось Х;
level_convert_to_32  — дополнительный уровень яркости добавляемый к автоматически определенному при бинаризации изображения;
level_Y_up — уровень программной «шторки» выше которой изображение ограничивается — позволяет убрать засветы на индикаторе;
level_Y_down — уровень программной «шторки» ниже которой изображение ограничивается — позволяет убрать засветы на индикаторе;
show_digital — выводит на экран монитора процесс результатов расчета расстояния Хеминга в виде двоичного представления выбранной цифры и эталонов с учетом всех сдвигов. Занимает много времени;
offset_x — смещение по оси Х для вывода на дисплей цифр, которое не помещаются на экране дисплея (6-8).
Gray or BW? - позволяет выбрать режим индикации изображения шкалы в режиме черно-белом, шкала в режиме градаций серого, все изображение в режиме градаций серого, которое помещается на экран;
Show 0/1? - выводит на экран монитора цифры после бинаризации в виде 1 и пробелов или 1 и 0 для возможности визуального анализа работы программы;
Show HEX? -  выводит на экран монитора цифры после бинаризации в шестнадцатеричном виде для использования их в качестве эталонна;
Found digitals :  - результаты текущего распознавания и долгосрочного распознавания. Значение 10 показывает, что цифра ни разу не была определена корректно по указанным выше критериям
    Сам интерфейс написан некорректно и не оптимально, но позволяет оперативно осуществлять предварительную настройку программы.
https://picua.org/images/2020/03/29/d49bdc16580fb085f10000d689c2f990.jpg
Из-за перехода на 2,0“ дисплей пришлось полностью переписать программу под другую библиотеку TFT_22_ILI9225, т. к. в нем используется контроллер ILI9225, а в версии 1,8“ ST7735.
    После «жесткой» сборки всей конструкции, несмотря на то, что алгоритм поиска положения границ цифр по высоте (ось Y), работал исправно, применение программных шторок сразу уменьшает зону поиска до нужных границ. Кроме того, так как фокусное расстояние не изменяется размер цифр также не может поменяться. В процедуре find_digits_y фактически находится середина цифр, а границы определяются как ½ высоты цифр, т. е. +/- 13 пикселей.
https://picua.org/images/2020/03/29/54827a2974acc096751d65ce14da3868.jpg
    Для накопления результатов объема потребленного газа м3 в программе реализован кольцевой буфер размерностью 2048 процедура m3_calculate(). Показания сохраняются каждую минуту. Если изменений в показаниях не произошло или нет правильного результата происходит увеличение текущего времени простоя. Так как буфер кольцевой, то после его заполнения запись начинается сначала. Выводом накопленных результатов занимается процедура print_m3(). При выводе значений из текущего времени получаемого от NMT сервера отнимаются накопленные минуты, что позволяет узнать время начала записи.  Результаты выводятся в виде таблицы порядковый номер, значение m3, время простоя в минутах. За сутки заполняется приблизительно 70-80 ячеек кольцевого буфера. Для корректного сравнения float все значения показаний счетчика были умножены на 100 и тем самым переведены в целое.
    Оригинальным решением подсказанным мне стало фиксация конструкции вот такими  креплениями за трубы счетчика. В качестве корпус решил применить пластиковый пищевой контейнер.
https://picua.org/images/2020/03/29/b311ce2a3cef763b3870e8bdcca6604b.jpg

Итоговый вариант
https://picua.org/images/2020/03/29/a5b17b72a1b5a92376731f212097ad48.jpg
https://picua.org/images/2020/03/29/6a964685f8b2b425dddc9a3ca06f0d25.jpg

Интерфейс управления был переписан под библиотеку Virtuino
https://picua.org/images/2020/03/29/2c3c9d6cd86089ccd804fb73ad46cedc.jpg

Управление аналогично выше описанному Web интерфейсу:
режим дисплея -  позволяет выбрать режим индикации изображения шкалы в режиме черно-белом, шкала в режиме градаций серого, все изображение в режиме градаций серого, которое помещается на экран;
сдвиг для анализа
- смещение по оси  Х при выводе на дисплей и интегрировании кадров.
Позволяет подогнать расположения для индикаторов разных моделей по горизонтали;
- смещение по оси  Y при выводе на дисплей и интегрировании кадров.
Позволяет подогнать высоту расположения индикатора разных моделей;
дисплей — дополнительный уровень яркости добавляемый к автоматически определенному при выводе на дисплей в черно-белом режиме;
яркость Y — дополнительный уровень яркости добавляемый к автоматически определенному при нахождении границ цифр по высоте — ось Y;
яркость X — дополнительный уровень яркости добавляемый к автоматически определенному при нахождении границ цифр по ширене — ось Х;
conv 32   — дополнительный уровень яркости добавляемый к автоматически определенному при бинаризации изображения;
Y_up — уровень программной «шторки» выше которой изображение ограничивается — позволяет убрать засветы на индикаторе;
Y_down — уровень программной «шторки» ниже которой изображение ограничивается — позволяет убрать засветы на индикаторе;
сдвиг для просмотра на дисплее — смещение по оси Х для вывода на дисплей цифр, которое не помещаются на экране дисплея.
HEX -  выводит на экран монитора цифры после бинаризации в шестнадцатеричном виде для использования их в качестве эталонна;
м3 на монитор  - выводит результаты сохраненных данных в кольцевом буфере на монитор.

    Также выводится информация о текущих распознанных цифрах, расстояние Хемминга и число совпадений распознанной цифры за 10 циклов в правой нижней части экрана. Если значения расстояние Хемминга или числа совпадений находится вне пределов, то цифра соответствующая этим значениям будут высвечиваться как символ знака вопроса, красного цвета.
    В верхней части показаны текущие значения шкалы счетчика.
    Слева зеленым мы видим распознанные показания. Именно эти показания и сохраняются в кольцевом буфере и по ним строится график на другой странице приложения Virtuino работающего на смартфоне.

   Спасибо всем, кто помогал мне в реализации данного проекта

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

Код программы представлен ниже

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-cam-video-streaming-web-server-camera-home-assistant/
*********/

#include "esp_camera.h"
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h" //disable brownout problems
#include "soc/rtc_cntl_reg.h"  //disable brownout problems
#include "esp_http_server.h"

#include "TFT_22_ILI9225.h"
#include <SPI.h>
// Include font definition files
// NOTE: These files may not have all characters defined! Check the GFXfont def
// params 3 + 4, e.g. 0x20 = 32 = space to 0x7E = 126 = ~

#include "fonts/FreeSansBold24pt7b.h"

#define TFT_CS         15
#define TFT_RST        -1 //
#define TFT_RS         2 //RS 
#define TFT_SDI 12  // Data out SDA MOSI SDI 
#define TFT_CLK 14  // Clock out CLK

TFT_22_ILI9225 tft = TFT_22_ILI9225(TFT_RST, TFT_RS, TFT_CS, TFT_SDI, TFT_CLK, 0, 200);

#include "virtuino_pins.h"

#include "time.h"
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 7200;
const int   daylightOffset_sec = 3600; //летнее время 3600;

struct tm timeinfo; //структура времени записи кольцевого буфера

#define info_first 2 ////строка вывода результатов ширины и высоты цифр
#define info_result 58 //строка вывода результатов распознавания
#define info_gistorgamm 105 //строка вывода гистограммы
#define info_Hemming 130 //строка вывода информационнии о расстоянии Хемминга
#define info_frequency 145 //строка вывода информационнии о частоте совпадения
#define info_britnes 115 //строка вывода информационнии об уровнях яркости
#define info_time 160 //строка вывода на дисплей м3/мин сек и времени

//++++++++++++++++++++++++++++++++++++++++++ снизить до 50
#define Hemming_level 80 //Значенее расстояния Хемминга которое считается максимально допустимым при распознавании  50

#define width_letter 26 //ширина цифр в пикселях
#define number_letter 8 //число цифр в шкале
#define height_letter 26 //высота по y - совпадает с высотой эталона

#define average_count 10 //количество усреднений результатов распознавания
#define average_count_level average_count-3 //число усреднений, которое принимается за положительное при распознавании

#define F_HEIGHT 176 //высота обработки изображения совпадает с высотой дисплея 2.0
#define F_WIDTH  320 //ширина обработки изображения совпадает с шириной изображения камеры

#include "sample.h" //образцы эталонов

uint16_t Y_first, Y_last; //положение окна расположения цифр в буфере камеры

uint16_t pixel_level = 0; //пороговый уровень пикселей на изображении определеный методом Отцу

uint16_t max_letter_x[number_letter]; //массив середины цифры по оси Х

uint32_t l_32[number_letter][F_HEIGHT]; //массив после перевода распознаваемых цифр в 32 битное число. Запас по высоте равен высоте экрана
uint8_t result[average_count][number_letter]; //накопление результатов распознавания цифр со шкалы

uint16_t *frame_buf; //указатель на буфер для накопления кадров камеры

#define max_shift 9*3 //число вариантов сдвига перемещения эталона  
int shift_XY[max_shift][2] = { //содержит сдвиг по оси X Y
  {0, 0},
  {0, 1},   //up
  {0, 2},   //up
  {0, 3},   //up
  {0, 4},   //up
  {0, -1},  //down
  {0, -2},  //down
  {0, -3},  //down
  {0, -4},  //down
  {1, 0},   //right
  {1, 1},   //right up
  {1, 2},   //right up
  {1, 3},   //right up
  {1, 4},   //right up
  {1, -1},  //right down
  {1, -2},  //right down
  {1, -3},  //right down
  {1, -4},  //right down
  { -1, 0}, //left
  { -1, 1}, //left up
  { -1, 2}, //left up
  { -1, 3}, //left up
  { -1, 4}, //left up
  { -1, -1},//left down
  { -1, -2},//left down
  { -1, -3},//left down
  { -1, -4},//left down
};

struct Hemming_struct { //структура расстояний Хемминга для всех цифр шкалы
  uint8_t result; // опознанная цифра
  uint16_t min_Hemming; // расстояния Хемминга для опознанной цифры
  uint8_t etalon_number; // номер эталона в массиве эталонов
  uint8_t frequency; //число совпадений при опознании
  uint8_t next_result; // значенее следующей цифры
  uint16_t next_min_Hemming; // расстояния Хемминга для следующей цифры
  uint8_t dig_defined; //набор символов, который определяется автоматически после распознавания
  uint8_t britnes_digital; //яркость для каждой буквы
  uint16_t x_width; //определенная ширина цифры
} Hemming[number_letter];

uint8_t frequency[number_of_samples][number_letter]; //подсчет максимального числа совпадений результатов распознавания

camera_fb_t * fb; //для работы камеры указатель на структуру буфер
sensor_t * s; //для работы камеры указаитель на структуру сенсора

// Replace with your network credentials
const char* ssid = "********";
const char* password = "*********";

#define PART_BOUNDARY "123456789000000000000987654321"

// This project was tested with the AI Thinker Model, M5STACK PSRAM Model and M5STACK WITHOUT PSRAM
#define CAMERA_MODEL_AI_THINKER

#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22


static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "n--" PART_BOUNDARY "n";
static const char* _STREAM_PART = "Content-Type: image/jpegnContent-Length: %unn";

httpd_handle_t stream_httpd = NULL;

#include <Ticker.h> //esp32 library that calls functions periodically

Ticker Gas_minute_Ticker; //используется для расчета объма газа каждую минуту

#define size_m3 2048 //размер кольцевого буфера для хранения данных каждую минуту должен быть 256, 512, 1024 ... 

//структура для сохранения информации о расчете объема газа
struct Gas_struct {
  uint32_t m3; //значенее объма газа умноженное на 100
  uint32_t minutes; //разница в минутах между предыдущим и текущим измерением
} Gas[size_m3];

uint16_t position_m3 = 0; //позиция сохранения данных


//---------------------------------------------------- m3_calculate
void m3_calculate() {

  uint32_t k = 1000000; //коэффициент перевода цифр шкалы в число
  uint32_t current_m3 = 0; //текущее значенее
  bool flag = true; //флаг рапознаввания

  uint16_t pos_1 = (position_m3 - 1) & (size_m3 - 1); //позиция в буфере места записи минус 1 - педыдущее значение

  for (uint8_t dig = 0; dig < number_letter - 1; dig++) { //проверка на все кроме последней цифры -1
    if ((Hemming[dig].frequency < average_count_level) || (Hemming[dig].min_Hemming > Hemming_level) || Hemming[dig].dig_defined == 10) {
      //нет правильного результата увеличим пропущенное время если это не начало - предыдущее значение минут равно 0
      if (Gas[pos_1].m3 == 0) Gas[position_m3].minutes++;
      current_m3 = 0; //обнуляем, т.к. нет правильного результата
      flag = false; //сбросим флаг - не распознали
      break; //выйти из цикла
    }
    current_m3 += Hemming[dig].dig_defined * k; //берем опознанное значенее и переводим в число
    k = k / 10; //уменьшаем коэффициент для следующего числа
  }
  Serial.printf("flag=%d current_m3= %d minutes=%d position_m3=%d pos_1=%dn", flag, current_m3, Gas[position_m3].minutes, position_m3, pos_1);

  if (flag) { //распознали сохраняем
    if (((current_m3 - Gas[pos_1].m3 < 0) || (current_m3 - Gas[pos_1].m3 > 6)) && (Gas[pos_1].m3 != 0)) { //ошибка распознавания вне пределов
      //если разница между предыдущем и текущим значением меньше нуля - текущее определили не верно
      //если разница больше 0,06 - ошибка определения - текущее значение определили не верно - за 1 минуту не может быть больше 0,05
      //при начальном заполнении буфера первый раз будет давать ошибку

      V[V_26_error_recognition]++; //увеличим счетчик ошибок неправльно распознанных
      Serial.printf("Значение %d м3 вне пределов на минуте %dn", current_m3, Gas[pos_1].minutes);
    }
    else { //значения совпадают или нормально измененные
      V[V_26_error_recognition] = 0.0; //сбросим счетчик ошибок неверно распознанных
    }

    Serial.printf("Предыдущее %d текущее %d м3 позиция %d минут %dtразница=%dn", Gas[pos_1].m3, current_m3, position_m3, Gas[pos_1].minutes, current_m3 - Gas[pos_1].m3);

    if (Gas[pos_1].m3 == current_m3) {
      //если совпало с предыдущим просто увеличим время простоя на 1 минуту или на время пропущенных минут
      //если ранее были нераспознанные минуты, то прибавим к пропущенному времени
      //      Serial.printf("Совпадение значений current_m3 %.2f м3 увеличим предыдущее время Gas[pos_1].minutes=%d разница %fn",current_m3, Gas[pos_1].minutes,current_m3 - Gas[pos_1].m3);
      Gas[pos_1].minutes += Gas[position_m3].minutes;
      Gas[position_m3].minutes = 1; //возобновим подсчет времени сначала для следующего элемента
      V[V_m3] = current_m3;
      V[V_m3_minutes] = Gas[pos_1].minutes;
    }
    else { //не совпали значения - есть изменения сохраним не обращая внимание на пределы и переход к следующему элементу
      Gas[position_m3].m3 = current_m3;
      V[V_m3] = current_m3;
      V[V_m3_minutes] = Gas[position_m3].minutes;

      position_m3 = (position_m3 + 1) & (size_m3 - 1); //переход к следующему элементу с учетом конца буффера;
    }

    Gas[position_m3].minutes = 1; //подсчет времени сначала для текущего элемента
    Gas[position_m3].m3 = 0;
  } //распознали сохраняем

  pos_1 = (position_m3 - 1) & (size_m3 - 1); //позиция в буфере места записи минус 1 - педыдущее значенее
  uint16_t pos_2 = (position_m3 - 2) & (size_m3 - 1); //позиция в буфере места записи минус 2

  if (Gas[pos_2].minutes != 0) { //если уже сохранено не менее 2-х элементов
    Serial.printf("Gas[pos_2].m3= %d Gas[pos_1].m3= %d minutes=%d difference_m3=%d position-1=%d position-2=%dn",
           Gas[pos_2].m3, Gas[pos_1].m3, Gas[pos_1].minutes, (Gas[pos_1].m3 - Gas[pos_2].m3) / Gas[pos_1].minutes, pos_1, pos_2);
  }
  else 
    Serial.printf("current_m3= %d minutes=%dn", Gas[pos_1].m3, Gas[pos_1].minutes);
}
//---------------------------------------------------- m3_calculate

//---------------------------------------------------- print_m3
void print_m3() {
  //вывести на монитор накопленные результаты m3
  time_t now;
  uint32_t all_minutes = 0; //для подсчета времени с начала отсчета
  
  uint16_t pos_1 = (position_m3 - 1) & (size_m3 - 1); //позиция в буфере места записи минус 1 - педыдущее значенее

  if (Gas[pos_1].minutes == 0) return; //не выводить если начало - нет предыдущего значения
  Serial.printf("itm3*100tminutesttmax number=%dn", position_m3);
  
  for (uint32_t i = 0; i < size_m3; i++) {
    uint16_t pos = (position_m3 + i) & (size_m3 - 1); //позиция в буфере после места записи
    if (Gas[pos].m3 == 0) continue; //если обнаружили конец буфера продолжим
    //     Serial.printf("i=%d pos=%d position_m3+i=%d Gas[pos].minutes=%d Gas[pos].m3=%.2fn",i, pos , position_m3+i,Gas[pos].minutes,Gas[pos].m3);
    all_minutes += Gas[i].minutes;   //подсчет общего времени суммируем все
    Serial.printf("%dt%dt%dn", pos, Gas[pos].m3, Gas[pos].minutes);
  }
  V[V_SH_M3] = 0; //выведем один раз

  if (!getLocalTime(&timeinfo)) { //получим время записи сохранения м3
    Serial.printf("Failed to obtain timen");
  }
  time(&now);
  now -= all_minutes * 60; //отнимим прошедшие минуты о получим начало отсчета
  localtime_r(&now, &timeinfo);
  Serial.printf("nНачало записи %02d.%02d.%4d %02d:%02d:%02dn", timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year + 1900,
         timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
}
//---------------------------------------------------- print_m3


//---------------------------------------------------- printBinary
#include <limits.h>

template<typename T>

void printBinary(T value, String s) {
  for ( size_t mask = 1 << ((sizeof(value) * CHAR_BIT) - 1); mask; mask >>= 1 ) {
    Serial.printf("%c", value & mask ? '1' : ' ');
  }
  Serial.printf("%s", s);
}
//---------------------------------------------------- printBinary


//---------------------------------------------------- britnes_digital
uint16_t find_middle_britnes_digital(uint16_t *fr_buf, bool show) {
  //расчет средней яркости пикселей для каждой цифры после того как определили их место
  for (uint8_t dig = 0; dig < number_letter; dig++) { //поочередно обрабатываем каждое знакоместо отельно
    float britnes  = 0;

    uint16_t w_l = width_letter;
    int x1 = max_letter_x[dig] - width_letter / 2; //для первой цифры размер может быть меньше установленной ширины
    if (x1 < 0) {
      w_l += x1; //x1 имеет отрицательное значенее поэтому суммируем
      if (show) Serial.printf("x1=%d max_letter_x[dig]=%d w_l=%dn", x1, max_letter_x[dig], w_l);
      x1 = 0;
    }
    for (uint16_t y = Y_first; y < Y_last; y++) { //перебор по столбцу в пределах высоты буквы
      for (uint16_t x = x1; x < max_letter_x[dig] + width_letter / 2; x++) { //перебор по строке в пределах ширины одной цифры
        uint32_t i = (y * F_WIDTH + x);
        britnes += fr_buf[i]; //суммируем все значения
      }
    }
    Hemming[dig].britnes_digital = (int)(britnes / (w_l * (Y_last - Y_first))); //для первой цифры ширина может быть меньше
    if (show)
      Serial.printf("dig=%d britnes=%d pixel_level=%dn", dig, (int)(britnes / (width_letter * (Y_last - Y_first))), pixel_level);
  }
}
//---------------------------------------------------- britnes_digital


//---------------------------------------------------- find_middle_level_image
uint16_t find_middle_level_image(uint16_t *fr_buf, bool show) {
  //найти уровень яркости изображения по Отцу

  float av = 0;
  uint16_t min1 = fr_buf[0];
  uint16_t max1 = fr_buf[0];
  uint32_t f_size = F_WIDTH * F_HEIGHT;

  //найти средний уровень пикселей окно табло - засвечено
  // Посчитаем минимальную и максимальную яркость всех пикселей
  for (uint32_t i = 0; i < f_size; i++) {
    av += fr_buf[i];
    if (fr_buf[i] < min1) min1 = fr_buf[i];
    if (fr_buf[i] > max1) max1 = fr_buf[i];
  }
  av = av / f_size;

  // Гистограмма будет ограничена снизу и сверху значениями min и max,
  // поэтому нет смысла создавать гистограмму размером 256 бинов
  int histSize = max1 - min1 + 1;

  int *hist = (int *) heap_caps_calloc(histSize * sizeof(int), 1, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
  if (hist == NULL) {
    Serial.printf("Problem with heap_caps_malloc find_middle_level_yn");
    return 0;
  }

  // Заполним гистограмму нулями
  for (int t = 0; t < histSize; t++)
    hist[t] = 0;

  // И вычислим высоту бинов
  for (int i = 0; i < f_size; i++)
    hist[fr_buf[i] - min1]++;

  // Введем два вспомогательных числа:
  int m = 0; // m - сумма высот всех бинов, домноженных на положение их середины
  int n = 0; // n - сумма высот всех бинов
  for (int t = 0; t <= max1 - min1; t++)
  {
    m += t * hist[t];
    n += hist[t];
  }

  float maxSigma = -1; // Максимальное значение межклассовой дисперсии
  int threshold = 0; // Порог, соответствующий maxSigma

  int alpha1 = 0; // Сумма высот всех бинов для класса 1
  int beta1 = 0; // Сумма высот всех бинов для класса 1, домноженных на положение их середины

  // Переменная alpha2 не нужна, т.к. она равна m - alpha1
  // Переменная beta2 не нужна, т.к. она равна n - alpha1

  // t пробегается по всем возможным значениям порога
  for (int t = 0; t < max1 - min1; t++)
  {
    alpha1 += t * hist[t];
    beta1 += hist[t];

    // Считаем вероятность класса 1.
    float w1 = (float)beta1 / n;
    // Нетрудно догадаться, что w2 тоже не нужна, т.к. она равна 1 - w1

    // a = a1 - a2, где a1, a2 - средние арифметические для классов 1 и 2
    float a = (float)alpha1 / beta1 - (float)(m - alpha1) / (n - beta1);

    // Наконец, считаем sigma
    float sigma = w1 * (1 - w1) * a * a;

    // Если sigma больше текущей максимальной, то обновляем maxSigma и порог
    if (sigma > maxSigma)
    {
      maxSigma = sigma;
      threshold = t;
    }
  }

  // Не забудем, что порог отсчитывался от min, а не от нуля
  threshold += min1;

  heap_caps_free(hist); //освободить буфер

  if (show)
    Serial.printf("min =%d max=%d cреднее по уровню=%.0f threshold= %dn", min1, max1, av, threshold);
  // Все, порог посчитан, возвращаем его наверх :)
  return (uint16_t)(threshold);
}
//---------------------------------------------------- find_middle_level_image


//---------------------------------------------------- find_digits_y
void find_digits_y (uint16_t *fr_buf, uint16_t mid_level, uint8_t add_mid_level, bool show) {
  //fr_buf буфер с изображением формата uint16_t
  //mid_level средний уровень яркости изображения
  //add_mid_level повышение уровня для устранения засветки при анализе
  //show вывести информацию на экран

  //поиск положения цифр по высоте Y
#define  Y_FIRST_LOOK 10  //ограничим начало поиска 10 пикселями в строке
#define  Y_LAST_LOOK 100 //ограничим конец поиска 100 пикселями в строке
  Y_first = 0;
  Y_last = 0;
  float av = 0;
  char buf[50]; //буфер для перевода значений строку и печати на дисплеи

  /*
    //проверка на максимум уровня не должен быть больше 255
    if (mid_level + add_mid_level > 255)
      add_mid_level = (255 - mid_level);
  */
  //поиск среднего уровня
  for (uint8_t y = Y_FIRST_LOOK; y < Y_LAST_LOOK; y++) { //только в пределах экрана по высоте 10-100 строки
    for (uint16_t x = 0; x < tft.maxX(); x++) { //ограничим шириной экрана, а не всем изображением F_WIDTH
      uint32_t i = (y * F_WIDTH + x);
      if (fr_buf[i] > mid_level + add_mid_level) av++;
    }
  }
  av = (uint16_t) (av / (Y_LAST_LOOK - Y_FIRST_LOOK));

  for (uint8_t y = Y_FIRST_LOOK; y < Y_LAST_LOOK; y++) { //только в пределах экрана по высоте 10-100 строки
    float av1 = 0;
    for (uint16_t x = 0; x < tft.maxX(); x++) { //ограничим шириной экрана, а не всем изображением F_WIDTH
      uint32_t i = (y * F_WIDTH + x);
      if (fr_buf[i] > mid_level + add_mid_level) av1++;
    }
    if (av < av1) {
      if (show) {
        Serial.printf("av=%.0f av1=%.0f Y = %dn", av, av1, y);
      }
      if (Y_first == 0) Y_first = y;
      Y_last = y;
    }
  }


  uint8_t Y_mid = Y_first + ((Y_last - Y_first) >> 1);
  if(Y_last - Y_first != height_letter) {
    Y_last  = Y_mid + (height_letter >> 1);
    Y_first = Y_mid - (height_letter >> 1);
  }
  
  tft.drawLine(0, Y_first, tft.maxX() - 1, Y_first, COLOR_YELLOW);
  tft.drawLine(0, Y_last, tft.maxX() - 1, Y_last, COLOR_YELLOW);
  tft.drawLine(0, Y_mid, tft.maxX() - 1, Y_mid, COLOR_CYAN);

  

  /*
    //вывод на дисплей индивидуальных значений яркости
      tft.setFont(Terminal6x8 ); //10 pixel
      tft.fillRectangle (0, info_britnes, tft.maxX(), 20, COLOR_BLACK); //очистить часть экрана

      tft.setColor(COLOR_WHITE);
      tft.setCursor(0, info_britnes);
      for (uint8_t dig = 0; dig < number_letter; dig++) {//вывести значения яркости
        tft.drawTextf("%d ", Hemming[dig].britnes_digital);
      }

  */

  //вывод на дисплей результатов сохраненных значений
  tft.setFont(Terminal6x8); //10 pixel
  tft.fillRectangle (0, info_time - 2, tft.maxX(), info_time + 10, COLOR_BLACK); //очистить часть экрана

  uint8_t pos_1 = (position_m3 - 1) & (size_m3 - 1); //позиция в буфере после места записи -1
  uint8_t pos_2 = (position_m3 - 2) & (size_m3 - 1); //позиция в буфере после места записи -2

  if (Gas[pos_2].minutes != 0) { //если уже сохранено не менее 2-х элементов
    sprintf(buf, "%4d mins %4.2f m3/h", Gas[pos_1].minutes, (Gas[pos_1].m3 - Gas[pos_2].m3) / (Gas[pos_1].minutes * 100.0));
    tft.drawText(0, info_time, buf, COLOR_WHITE);
  }
  else {
    sprintf(buf, "%4d mins %4.2f m3", Gas[pos_1].minutes, Gas[pos_1].m3 / 100.0);
    tft.drawText(0, info_time, buf, COLOR_WHITE);
  }

  sprintf(buf, "%02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);    
  if (!getLocalTime(&timeinfo)) {
    Serial.printf("Failed to obtain timen");
    tft.drawText(tft.maxX() - tft.getTextWidth(buf), info_time, buf, COLOR_RED); //9 * tft.getCharWidth(48)
  }
  else {
    sprintf(buf, "%02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
    tft.drawText(tft.maxX() - tft.getTextWidth(buf), info_time, buf, COLOR_YELLOW);
  }  

  uint16_t x_width_min = F_WIDTH; //ширина экрана
  uint16_t x_width_max = 0; //минимальное значенее

  for (uint8_t dig = 0; dig < number_letter; dig++) {
    //поиск максимальной и минимальной ширины определнной цифры
    if (x_width_min > Hemming[dig].x_width) x_width_min = Hemming[dig].x_width;
    if (x_width_max < Hemming[dig].x_width) x_width_max = Hemming[dig].x_width;
  }

  uint16_t next_x = 0;

  sprintf(buf, "Y_m=%2d", Y_mid);
  if ((Y_last - Y_first) != sample_height)
    tft.drawText(next_x, info_first, buf, COLOR_YELLOW);
  else
    tft.drawText(next_x, info_first, buf, COLOR_GREEN);

  next_x += tft.getTextWidth(buf);

  sprintf(buf, " X_dmax=%d", x_width_max);
  if ((x_width_max > width_letter) || (x_width_max <= x_width_min)) //ширина не должна быть больше ширины буквы
    tft.drawText(next_x, info_first, buf, COLOR_YELLOW);
  else
    tft.drawText(next_x, info_first, buf, COLOR_GREEN);

  next_x += tft.getTextWidth(buf);
  sprintf(buf, " X_dmin=%d", x_width_min);

  if ((x_width_min < 3) || (x_width_min > width_letter)) //ширина не должна быть меньше 3 пикселей
    tft.drawText(next_x, info_first, buf, COLOR_YELLOW);
  else
    tft.drawText(next_x, info_first, buf, COLOR_GREEN);

  if (show) {
    Serial.printf(" Y_first = %d Y_last = %dn", Y_first, Y_last);
  }
}
//---------------------------------------------------- find_digits_y


//---------------------------------------------------- find_max_digital_X
void find_max_digital_X(uint16_t *fr_buf, uint16_t mid_level, uint8_t add_mid_level, bool show) {
  //fr_buf буфер с изображением формата uint16_t
  //mid_level средний уровень изображения
  //add_mid_level повышение следующего уровня для устранения засветки при отображении
  //show вывести информацию на экран

  //поиск границ цифр в найденной строке по X
  uint8_t letter[F_WIDTH]; //массив для поиска максимума по оси Х

  /*
    //проверка на максимум уровня
    if (mid_level + add_mid_level > 255)
      add_mid_level = (255 - mid_level);
  */
  //строим гистограмму подсчет количества единиц по столбцу
  for (uint16_t x = 0; x < F_WIDTH; x++) { //перебор по строке
    letter[x] = 0; //обнуляем начальное значение
    for (uint16_t y = Y_first; y < Y_last + 1; y++) { //ищем только в пределах обнаруженных цифр
      uint16_t i = (y * F_WIDTH + x);
      if (fr_buf[i] > mid_level + add_mid_level) {
        letter[x]++;
      }
    }
    //    if(show) Serial.printf("x=%3d letter=%dn",x,letter[x]);
  }

  if (show) {
    //вывод гистограммы на дисплей с учетом смещения по оси Х
    tft.fillRectangle (0, info_Hemming - 30, tft.maxX(), info_Hemming - 2, COLOR_BLACK); //очистить часть экрана
    for (uint16_t x = V[V_offset_x_digital]; x < F_WIDTH; x++) { //перебор по строке  V[V_offset_x]
      if (letter[x] != 0)
        tft.drawLine(x - V[V_offset_x_digital], info_Hemming - 30, x - V[V_offset_x_digital], 100 + letter[x], COLOR_CYAN); //Y_last + 10 V[V_offset_x]
    }
  }

  //уточним центры цифр по гистограмме
  uint16_t x1 = 0, x2 = 0; //начало и конец цифры
  uint16_t  dig = 0; //номер найденной цифры от 0 до number_letter-1
  for (uint16_t x = 0; x < F_WIDTH; x++) { //перебор по строке
    if (letter[x] > 2) { //если есть пиксели найти начало больше 2 пикселей
      if (x1 != 0) x2 = x; //если было найдено начало установить конец
      else x1 = x; //установить начало
    }
    else { //нет пикселей
      if (x1 != 0 && x2 != 0) { //если были ранее определены пределы показать границы
        if (show) Serial.printf("x1=%4d x2=%4d x_mid=%4d d_x=%d dig=%dn", x1, x2, ((x2 - x1) >> 1) + x1, (x2 - x1), dig);

        if (dig > number_letter - 1) { //усли найдено больше чем цифр в шкале 8 number_letter - 1
          if (show) Serial.printf("Найдено больще цифр чем в шкале по оси Х!!! %dn", dig);
          return;
        }
        max_letter_x[dig] = ((x2 - x1) >> 1) + x1;

        Hemming[dig].x_width = (x2 - x1); //сохраним значенее ширины буквы

        dig++;
        //линия на +/- 5 пикселей от Y_last и Y_first
        tft.drawLine(((x2 - x1) >> 1) + x1 - V[V_offset_x_digital], Y_first - 5, ((x2 - x1) >> 1) + x1 - V[V_offset_x_digital], Y_last + 5, COLOR_BLUE);
        tft.drawLine(x1 - V[V_offset_x_digital], Y_first - 5, x1 - V[V_offset_x_digital], Y_last + 5, COLOR_OLIVE);
        tft.drawLine(x2 - V[V_offset_x_digital], Y_first - 5, x2 - V[V_offset_x_digital], Y_last + 5, COLOR_OLIVE);

        /*
                //построение диаграммы по оси Y между x1 и x2
                for (uint16_t y = Y_first; y < Y_last; y++) {
                  uint8_t y_l = 0;
                  for (uint16_t x = x1; x < x2; x++) {
                    uint16_t i = (y * F_WIDTH + x);
                    if (fr_buf[i] > mid_level + add_mid_level) {
                      y_l++;
                    }
                  }

                  //вывести гистограмму по высоте на экран
                  //          if (SH_0_1 == 1) {
                  //            for (uint8_t i = 0; i < y_l; i++)
                  //              Serial.printf("1");
                  //          }
                  ////          tft.drawFastHLine(x1 - V[V_offset_x_digital], Y_first - 65 + y, y_l, COLOR_YELLOW);
                  //          if (SH_0_1 == 1) Serial.printf("n");
                }
                //        if (SH_0_1 == 1) Serial.printf("n");
                //построение диаграммы по оси Y между x1 и x2
        */
      }

      //обнулим значение для следующей цифры
      x1 = 0;
      x2 = 0;
    }
  }
  if (dig != number_letter)
    if (show) Serial.printf("Не все цифры шкалы найдены по оси Х!!! %dn", dig);
}
//---------------------------------------------------- find_max_digital_X


//---------------------------------------------------- sum_one
uint8_t sum_one(uint32_t d) { //суммирование всех 1 в 32 битном числе
  uint8_t r = 0;
  for (uint8_t i = 0; i < 32; i++) {
    r += d & 0x1;
    d = d >> 1;
  }
  return r;
}
//---------------------------------------------------- sum_one


//---------------------------------------------------- compare
uint8_t compare(uint8_t y, uint8_t samp_dig, uint8_t dig, int X_shift, int Y_shift, bool show) {
  //y положенее по оси Y
  //samp_dig номер эталона
  //dig - какую цифру обрабатываем
  //X_shift сдвиг по оси +/- Х
  //Y_shift сдвиг по оси +/- Y
  //show выводить на экран

  uint32_t samp = sample[samp_dig][y];//центры эталона и сравниваемая цифра совпадают
  //<< 5; нужно подготовить центры середина эталонна полученная из знакогенератора 8, середина цифры от камеры 13

  uint32_t samp1;

  if ((y + Y_shift < F_HEIGHT) || (y + Y_shift > 0)) {
    if (X_shift < 0)
      samp1 = l_32[dig][y + Y_shift] >> abs(X_shift); //образец который сравниваем может быть отрицательное значенее
    else
      samp1 = l_32[dig][y + Y_shift] << X_shift; //образец который сравниваем
  }
  else samp1 = 0;

  if (show)
    printBinary(samp1 ^ samp, "t");
  return sum_one(samp1 ^ samp);
}
//---------------------------------------------------- compare


//---------------------------------------------------- image_recognition
uint8_t image_recognition(uint8_t dig, uint8_t dig_show) {
  //dig - какую цифру обрабатываем
  //dig_show какую цифру отображаем если > 8 = не выводим

  //распознавание цифр
  //сравнить с эталоном - расчет расстояния Хемминга
  uint8_t min_dig_number = number_of_samples; //присвоим максимальное значенее

  //вывод цифры со шкалы в двоичном виде
  if (dig == dig_show) {
    Serial.printf("------------------------------n");
    for (uint8_t y = 0; y < Y_last - Y_first; y++) { //перебор по Y
      printBinary(l_32[dig][y], "n");
    }
    Serial.printf("------------------------------n");
  }

  //вывод цифры со шкалы в HEX формате
  if (V[V_SH_HEX] == 1) {
    Serial.printf("{//%dn", dig);
    for (uint8_t y = 0; y < Y_last - Y_first; y++) { //перебор по Y
      Serial.printf("0x%010lx,t//", l_32[dig][y]);
      printBinary(l_32[dig][y], "n");
    }
    Serial.printf("},n");
  }

  uint32_t min_Hemming[number_of_samples]; //массив минимальных расстояний Хемминга для всех эталонов

  for (uint8_t samp_dig = 0; samp_dig < number_of_samples; samp_dig++) { //перебор по все эталлонам
    uint16_t shift[max_shift]; //хранение результатов расчетов расстояния Хеминга после всех вариантов сдвигов

    for (uint8_t i = 0; i < max_shift; i++)
      shift[i] = 0; //обнулим для накопления результатов расчета расстояния Хеминга

    for (uint8_t y = 0; y < sample_height; y++) { //перебор по Y - высоте эталона и образца
      for (uint8_t i = 0; i < max_shift; i++) //перебор по всем сдвигам
        shift[i] += compare(y, samp_dig, dig, shift_XY[i][0], shift_XY[i][1], dig == dig_show);
      if (dig == dig_show) Serial.printf("n");
    }

    if (dig == dig_show) {
      Serial.printf("Etalon Digit=%d", samp_dig);
      for (uint8_t i = 0; i < max_shift; i++)
        Serial.printf("tshift=%3d %d %dttt", shift[i], shift_XY[i][0], shift_XY[i][1]);
      Serial.printf("n");
    }

    //поиск минималного значения после сдвигов
    min_Hemming[samp_dig] = shift[0];
    for (uint8_t i = 0; i < max_shift; i++) {
      if (min_Hemming[samp_dig] > shift[i])
        min_Hemming[samp_dig] = shift[i];
    }
  }

  uint32_t min_dig = 1024; //будет содержать минимальное значение расстояния Хемминга

  for (uint8_t samp_dig = 0; samp_dig < number_of_samples; samp_dig++) { //перебор по все эталлонам
    if (min_dig >= min_Hemming[samp_dig]) {
      min_dig = min_Hemming[samp_dig];
      min_dig_number = sample_equation[samp_dig]; //получить цифру соответсвия
      Hemming[dig].etalon_number = samp_dig; //номер эталона в массиве
    }
    if (dig == dig_show)
      Serial.printf("Etalon=%dtmin_Hemming=%dn", sample_equation[samp_dig], min_Hemming[samp_dig]);
  }

  if (dig == dig_show)
    Serial.printf("n***** Found Digit=%dtmin_Hemming= %d *****n", min_dig_number, min_dig);

  Hemming[dig].min_Hemming = min_dig;  //сохранить значениее расстояния Хемминга

  //поиск следующего минимума

  min_dig = 1024; //будет содержать минимальное значение расстояния Хемминга
  Hemming[dig].next_result = 10; //всего цифры от 0 до 9

  for (uint8_t samp_dig = 0; samp_dig < number_of_samples; samp_dig++) { //перебор по все эталлонам
    //    Serial.printf("samp_dig=%d min_dig_number=%d min_Hemming=%d min_dig=%dn",samp_dig,min_dig_number,min_Hemming[samp_dig], min_dig);
    if (sample_equation[samp_dig] == min_dig_number) continue; //если уже найденный минимум пропустить
    if (min_dig >= min_Hemming[samp_dig]) {
      min_dig = min_Hemming[samp_dig];
      Hemming[dig].next_result = sample_equation[samp_dig]; //значенее опознанной цифры

    }
  }

  Hemming[dig].next_min_Hemming = min_dig;  //сохранить значение расстояния Хемминга

  return min_dig_number;
}
//---------------------------------------------------- image_recognition


//---------------------------------------------------- convert_to_32
void convert_to_32(uint16_t *fr_buf, uint16_t mid_level, uint8_t add_mid_level, bool show) {

  /*
    //проверка на максимум уровня
    if (mid_level + add_mid_level > 255)
      add_mid_level = (255 - mid_level);
  */

  for (uint8_t dig = 0; dig < number_letter; dig++) { //всего 8 цифр в шкале последовательно обрабатываем каждую
    for (uint16_t y = Y_first; y < Y_last; y++) { //перебор по столбцу
      l_32[dig][y - Y_first] = 0;
      int x1 = max_letter_x[dig] - width_letter / 2; //для первой цифры размер может быть меньше установленной ширины
      if (x1 < 0) x1 = 0;
      for (uint16_t x = x1; x < max_letter_x[dig] + width_letter / 2; x++) { //перебор по строке в пределах одной цифры
        l_32[dig][y - Y_first] = l_32[dig][y - Y_first] << 1; //сдвиг на 1 позицию
        uint32_t i = (y * F_WIDTH + x);

        if (fr_buf[i] > Hemming[dig].britnes_digital + add_mid_level) { //индивидуальный уровень яркости для каждой цифры
          //        if (fr_buf[i] > mid_level + add_mid_level) { //средний уровень
          if (show || V[V_SH_0_1] != 0) Serial.printf("1");
          l_32[dig][y - Y_first]++;
        }
        else {
          if (show) Serial.printf(" ");
          if (V[V_SH_0_1] == 1) Serial.printf(" ");
          if (V[V_SH_0_1] == 2) Serial.printf("0");
        }
      }
      if (show) Serial.printf("|0x%010lxn", l_32[dig][y - Y_first]);
      if (V[V_SH_0_1] != 0) Serial.printf("n");
    }
    if (show) Serial.printf("Letter box middel = %d d_x = %d d_y =%d mid_line_y=%dn", max_letter_x[dig], width_letter, Y_last - Y_first, Y_first + (Y_last - Y_first) / 2);
    if (V[V_SH_0_1] != 0) Serial.printf("n");
  }
}
//---------------------------------------------------- convert_to_32


//---------------------------------------------------- dispalay_ttf_B_W
esp_err_t dispalay_ttf_B_W(uint16_t *fr_buf, uint16_t mid_level, uint8_t add_mid_level) {
  //fr_buf буфер с изображением формата uint16_t
  //X0 начальная кордината вывода по оси Х
  //Y0 начальная кордината вывода по оси Y
  //mid_level средний уровень изображения применятся индивидуальный для каждой цифры
  //add_mid_level повышение следующего уровня для устранения засветки при отображении



  //зарезервировтать паять для буфера дисплея
  uint16_t W = tft.maxX();
  uint16_t H = tft.maxY();

  uint16_t *disp_buf = (uint16_t *)heap_caps_calloc(W * H * 2, 1, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
  if (disp_buf == NULL) {
    Serial.printf("malloc failed f_bn");
    return ESP_FAIL;
  }

  for (int y = 0; y < H; y++) { //выводим только по высоте экрана //tft.maxX() tft.maxY()
    for (int x = 0; x < W; x++) {
      uint8_t dig = 0; //номер цифры табло
      uint32_t i = (y * F_WIDTH + x + V[V_offset_x_digital]);
      uint32_t j = (y * W + x);
      uint16_t color;
      if (s->pixformat == PIXFORMAT_GRAYSCALE) { //если GRAYSCALE преобразовать в RGB565 каждый байт буфера
        color = (((uint16_t)(fr_buf[i]) & 0xF8) << 8) | (((uint16_t)(fr_buf[i]) & 0xFC) << 3) | ((uint16_t)(fr_buf[i]) >> 3);
      }
      else
        color = fr_buf[i];

      if (V[V_GBW] == 0) {
        if (x > max_letter_x[dig] + width_letter / 2) { //если текущее значенее в пределах цифры, то использовать соответствующее значенее яркости
          dig++; //перейти к следующей цифре
          if (dig > number_letter) dig = number_letter - 1; //если больше цифр то принимать яркость последней
        }
        if (fr_buf[i] < Hemming[dig].britnes_digital + add_mid_level) //индивидуальный уровень яркости для каждой цифры
          *(disp_buf + j)  = COLOR_BLACK;
        else
          *(disp_buf + j) = COLOR_WHITE;
      }
      else *(disp_buf + j) = color;
    }

  }
  if (V[V_GBW] == 2) //если выводить полный экран
    tft.drawBitmap(0, 0, disp_buf, W, H); //отобразить на дисплеи
  else 
    tft.drawBitmap(0, 0, disp_buf, W, V[V_level_Y_down] + 10); //отобразить на дисплеи часть изображения с запасом на 10 пикселей

  heap_caps_free(disp_buf); //освободить буфер

  return ESP_OK;
}
//---------------------------------------------------- dispalay_ttf_B_W


//---------------------------------------------------- sum_frames
esp_err_t sum_frames(uint16_t *fr_buf, bool show, uint8_t Y_up, uint8_t Y_down) {
  uint32_t tstart;
  fb = NULL;

  //накопление кадров - проинтегрировать несколько кадров для устранения шумов
  tstart = clock();

  memset(fr_buf, 0, F_WIDTH * F_HEIGHT * 2); //выделить память и очистить размер в байтих

  uint8_t frame_c = V[V_number_of_sum_frames];
  if (s->pixformat != PIXFORMAT_GRAYSCALE)
    frame_c = 1; //если цветное то не суммировать по кадрам

  for (uint8_t frames = 0; frames < frame_c; frames++) { //усредненее по кадрам frame_count
    if (fb) { //освободить буфер
      esp_camera_fb_return(fb);
      fb = NULL;
    }
    fb = esp_camera_fb_get(); //получить данные от камеры
    if (!fb) {
      Serial.printf("Camera capture failed to displayn");
      return ESP_FAIL;
    }

    uint32_t i_max = fb->height * fb->width; //мксимальное значенее массива для данного экрана

    for (uint16_t y = 0; y < F_HEIGHT; y++) { //работаем только с верхней частью кадра
      for (uint16_t x = 0; x < fb->width; x++) {
        if (s->pixformat == PIXFORMAT_GRAYSCALE) {
          uint32_t i = ((y + V[V_offset_y]) * fb->width + x + V[V_offset_x]); //смещенее по оси Y и Х
          uint32_t j = (y * fb->width + x); //GRAYSCALE
          if ((y < Y_up) || (y > Y_down)) {
            fr_buf[j] = 0; //обнулим значения ниже и выше Y - эммитация шторки
            //            Serial.printf("n");
          }
          else {
            if (i < i_max) //размер экрана (176)+смещенее может быть больше размера изображения 240
              fr_buf[j] += fb->buf[i];
            else
              fr_buf[j] = 0;
          }
        }
        else { //если RGB565 берем 8 битный буфер и преобразуем в 16 битный
          uint32_t i = (y * fb->width + x) << 1; //если RGB565
          uint32_t j = (y * fb->width + x); //если RGB565
          //https://github.com/techtoys/SSD2805/blob/master/Microchip/Include/Graphics/gfxcolors.h
          fr_buf[j] += (uint16_t)(fb->buf[i]) << 8 | (uint16_t)(fb->buf[i + 1]); //преобразуем в 16 битное
        }
      }
    }
  } //суммирование по кадрам

  //усредним все пиксели
  for (uint16_t i = 0; i < F_WIDTH * F_HEIGHT; i++) {
    fr_buf[i] = (uint16_t)(fr_buf[i] / frame_c);
  }

  if (fb) { //освободить буфер
    esp_camera_fb_return(fb);
    fb = NULL;
  }
  if (show) {
    Serial.printf("Capture camera time: %u ms of %d framesn", clock() - tstart, frame_c);
  }
  return ESP_OK;
}
//---------------------------------------------------- sum_frames


//---------------------------------------------------- camera_capture
esp_err_t camera_capture(uint16_t *fr_buf, bool show, uint8_t Y_up, uint8_t Y_down) {
  //сумировать кадры
  if (sum_frames(fr_buf, show, Y_up, Y_down) != ESP_OK) return ESP_FAIL;

  uint32_t tstart = clock();
  if (show) {
    dispalay_ttf_B_W(fr_buf, 0, 0);
    Serial.printf("Send buffer time: %u msn", clock() - tstart);
  }
}
//---------------------------------------------------- camera_capture


//---------------------------------------------------- setup
void setup() {

  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector

  Serial.begin(115200);
  Serial.setDebugOutput(false);

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  /*
    config.pixel_format = PIXFORMAT_GRAYSCALE; //PIXFORMAT_JPEG;

    //init with high specs to pre-allocate larger buffers
    if (psramFound()) {
      //    config.frame_size = FRAMESIZE_UXGA;
      config.frame_size = FRAMESIZE_QVGA;
      config.jpeg_quality = 10;
      config.fb_count = 1;
    } else {
      config.frame_size = FRAMESIZE_SCOLOR;
      config.jpeg_quality = 12;
      config.fb_count = 1;
    }

    //  config.frame_size = FRAMESIZE_QVGA;
    // Camera init
  */
  // for display

  config.frame_size = FRAMESIZE_QVGA;
  config.pixel_format = PIXFORMAT_GRAYSCALE; //PIXFORMAT_GRAYSCALE; //PIXFORMAT_RGB565;
  config.fb_count = 2;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    delay(1000);
    ESP.restart();
  }

  //drop down frame size for higher initial frame rate
  s = esp_camera_sensor_get();
  s->set_framesize(s, FRAMESIZE_QVGA);

  /*
    #ifdef  display_2_0
    s->set_hmirror(s, 1);
    s->set_vflip(s, 1);
    #endif
  */
  //  s->set_special_effect(s, 1); //Negative
  //s->set_brightness(s, Value);
  //       s->set_saturation(s, Value);
  //       s->set_contrast(s, Value);

  // Wi-Fi connection
  uint32_t timeout = millis();
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.printf(".");
    if (millis() - timeout > 5000) break;
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("nWiFi connected.nCamera Stream Ready! Go to: http://");
    Serial.printf("%sn", WiFi.localIP().toString().c_str());
  }
  else { //create AP
    WiFi.softAP("ESP32", "87654321");
    Serial.printf("nWiFi %s not found create AP Name - 'ESP32' Password - '87654321'n", ssid);
    Serial.printf("Camera Stream Ready! Go to: http://");
    Serial.printf("%sn", WiFi.softAPIP().toString());
  }

  tft.begin();
  tft.setOrientation(3);
  tft.clear(); //черным

  uint32_t f8  = heap_caps_get_free_size(MALLOC_CAP_8BIT);
  frame_buf = (uint16_t *)heap_caps_calloc(F_WIDTH * F_HEIGHT * 2, 1, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
  if (frame_buf == NULL) {
    Serial.printf("malloc failed frame_bufn");
  }
  else {
    Serial.printf("malloc succeeded frame_bufn");
  }
  Serial.printf("8BIT = %dn", f8 - heap_caps_get_free_size(MALLOC_CAP_8BIT));

  for (uint8_t dig = 0; dig < number_letter; dig++) {
    Hemming[dig].dig_defined = 10; //заносим первоначально максимальное число вне диапазона 0-9
  }
  // Start web server
  //  startCameraServer();

  virtuino.begin(onReceived, onRequested, 512); //Start Virtuino. Set the buffer to 256. With this buffer Virtuino can control about 28 pins (1 command = 9bytes) The T(text) commands with 20 characters need 20+6 bytes
  //virtuino.key="1234";                       //This is the Virtuino password. Only requests the start with this key are accepted from the library
  // avoid special characters like ! $ = @ # % & * on your password. Use only numbers or text characters
  server.begin();
  /*
    for (uint8_t dig = 0; dig < number_letter; dig++) {
      for (uint8_t y = 0; y < height_letter; y++) { //перебор по Y
         printBinary(original[dig][y], "n");
      }
    }
  */



  for (uint16_t i = 0; i < size_m3; i++) { //обнулим буфер сохранения значений
    Gas[i].m3 = 0;
    Gas[i].minutes = 0;
  }
  Gas[0].minutes = 1; //подсчет времени сначала для текущего элемента

  Gas_minute_Ticker.attach(60, m3_calculate); //вызывать расчета объма газа каждую минуту 60

  //init and get the time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

  if (!getLocalTime(&timeinfo)) { //получим время начала записи сохранения м3
    Serial.printf("Failed to obtain timen");
    return;
  }

  EEPROM.begin(16);//Установить размер внутренней памяти для хранения первоначальных значений

  init_V();



  
}
//---------------------------------------------------- setup


//---------------------------------------------------- find_max_number
uint8_t  find_max_number(uint8_t d) {
  //возврат максимального значения из всех найденых
  uint8_t n = 0; //первое значенее 0
  uint8_t m1 = frequency[0][d]; //присваиваем первое значение
  for (uint8_t i = 1; i < number_of_samples; i++) {
    if (m1 < frequency[i][d]) {
      m1 = frequency[i][d];
      n = i;
    }
  }
  return n;
}
//---------------------------------------------------- find_max_number


//---------------------------------------------------- show_result
void show_result(bool show) {
  uint8_t defined;
  uint16_t next_x = 0;
  char buf[10]; //буфер для формирования строки расстояния Хемминга и частоты повторения цифр

  T_0 = ""; //обновим строчку вывода результатов

  int16_t w, h;

  tft.setGFXFont(&FreeSansBold24pt7b); // Set current font
  tft.getGFXTextExtent("0", 0, info_result, &w, &h); // Get string extents
  h += info_result;
  //  Serial.printf("info_result=%d w=%d h=%dn",info_result,w,h);

  //  tft.setFont(Trebuchet_MS16x21); //22 pixel for size 3 Trebuchet_MS16x21

  tft.fillRectangle (0, info_result - 2, tft.maxX(), h + 7, COLOR_BLACK); //очистить часть экрана GFXFont привязан верхней точкой

  //найти максимальную частоту вхождения цифр после опознавания
  for (uint8_t dig = 0; dig < number_letter; dig++) { //number_letter
    defined = find_max_number(dig);
    sprintf(buf, "%d", defined);
    //обновленее первоначально предопределенного набора символов если частота определения символа более 7 и рассояние Хемминга менее Hemming_level
    if ((frequency[defined][dig] > average_count_level) && (Hemming[dig].min_Hemming < Hemming_level)) {
      if (defined != Hemming[dig].dig_defined) { //корректно обнаружили первый раз
        //        Serial.printf("Change defined digital in position=%d from=%d to %dn", dig, Hemming[dig].dig_defined, defined);
        Hemming[dig].dig_defined = defined;
        tft.drawGFXText(next_x, h, buf, COLOR_YELLOW); // Print string
        //        tft.drawText(next_x, info_result,buf,COLOR_YELLOW); //цифра опознана с большой вероятностью
      }
      else tft.drawGFXText(next_x, h, buf, COLOR_GREEN); //цифра распознана корректно уже неоднократно
    }
    else tft.drawGFXText(next_x, h, buf, COLOR_RED);

    T_0 += defined;
    next_x += w; //шаг между цифрами

    if (dig == 4) {
      T_0 += ".";
      tft.drawGFXText(next_x, h, ".", COLOR_GREEN);
      next_x += (w >> 1); //шаг между цифрами
    }


    Hemming[dig].result = defined;
    Hemming[dig].frequency = frequency[defined][dig];

    if ((Hemming[dig].frequency < average_count_level) && (Hemming[dig].min_Hemming > Hemming_level)) {
      Hemming[dig].dig_defined = 10; //не распознали с большой вероятностью
    }

    if (show)
      Serial.printf("found number=%2d frequency=%2d Hemming_min=%4d Hemming_defined =%d position=%2d Hemming_next=%4d next_dig=%2d delta=%3d x_width=%dn",
             Hemming[dig].result, Hemming[dig].frequency, Hemming[dig].min_Hemming, Hemming[dig].dig_defined, Hemming[dig].etalon_number,
             Hemming[dig].next_min_Hemming, Hemming[dig].next_result, Hemming[dig].next_min_Hemming - Hemming[dig].min_Hemming, Hemming[dig].x_width);

  }
  if (show) Serial.printf("n");

  //сохраненее данных для вывода на экран Virtuino
  V[V_D0] =   Hemming[0].dig_defined;
  V[V_D1] =   Hemming[1].dig_defined;
  V[V_D2] =   Hemming[2].dig_defined;
  V[V_D3] =   Hemming[3].dig_defined;
  V[V_D4] =   Hemming[4].dig_defined;
  V[V_D5] =   Hemming[5].dig_defined;
  V[V_D6] =   Hemming[6].dig_defined;
  V[V_D7] =   Hemming[7].dig_defined;

  //вывод растояния Хемминга
  T_1 = "";
  T_2 = "";

  tft.setFont(Terminal6x8); //10 pixel
  tft.fillRectangle (0, info_Hemming - 2, tft.maxX(), info_Hemming + 24, COLOR_BLACK); //очистить часть экрана для расстояния Хеминга и частоты

  for (uint8_t dig = 0; dig < number_letter; dig++) {
    sprintf(buf, "|%3d ", Hemming[dig].min_Hemming);
    next_x = max(tft.getTextWidth(T_1), tft.getTextWidth(T_2));
    if (next_x != 0) next_x -= tft.getTextWidth(" ");

    if (Hemming[dig].min_Hemming < Hemming_level)
      tft.drawText(next_x, info_Hemming, buf, COLOR_GREEN); //печатать каждую цифру со смещенеем на экране дисплея
    else
      tft.drawText(next_x, info_Hemming, buf, COLOR_RED); //печатать каждую цифру со смещенеем на экране дисплея

    //    Serial.printf("%3d %3d '%s'n",next_x,tft.getTextWidth(T_1),T_1.c_str());
    T_1 += buf;

    sprintf(buf, "|%3d ", Hemming[dig].frequency);
    if (Hemming[dig].frequency > average_count_level)
      tft.drawText(next_x, info_frequency, buf, COLOR_GREEN); //печатать каждую цифру со смещенеем на экране дисплея
    else
      tft.drawText(next_x, info_frequency, buf, COLOR_RED); //печатать каждую цифру со смещенеем на экране дисплея

    //    Serial.printf("%3d %3d %3d '%s'n",next_x,tft.getTextWidth(T_2),tft.getTextWidth(T_2)-5,T_2.c_str());
    T_2 += buf;
  }
  T_1 += "|";
  T_2 += "|";
  //  Serial.printf("T_1 = %sn",T_2.c_str());
}
//---------------------------------------------------- show_result

uint32_t free_heap;
bool V_GBW_old = false;

//---------------------------------------------------- loop
void loop() {

  for (uint8_t dig = 0; dig < number_letter; dig++) { //обнулить массив для поиска частоты повторения цифр
    for (uint8_t i = 0; i < number_of_samples; i++) { //перебор по всем значения образцов
      frequency[i][dig] = 0;
    }
  }

  for (uint8_t count = 0; count < average_count; count++) { //повторим результат и найдем опознаные числа

    //обработка запросов web сервера
    virtuinoRun();        // Necessary function to communicate with Virtuino. Client handler

    if (V[V_SH_M3] == 1) print_m3(); //вывести накопленные даные на экран монитора

    if (V[V_GBW] == 2) { //Вывод полного экрана без анализа
      camera_capture(frame_buf, false, 0, F_HEIGHT); //получить кадры с камеры и усреднить их
      dispalay_ttf_B_W(frame_buf, pixel_level, V[V_level_dispalay_ttf_B_W]); //повысим на 5-20 единиц, чтобы убрать засветку
      V_GBW_old = true;
    }
    else {
      if (V_GBW_old) tft.clear(); //очистка после вывода полного экрана без анализа
      V_GBW_old = false;

      if(V[V_GBW] == 1) 
        camera_capture(frame_buf, false, V[V_level_Y_up]-10, V[V_level_Y_down]+10); //получить кадры с камеры и усреднить их
      else 
        camera_capture(frame_buf, false, V[V_level_Y_up], V[V_level_Y_down]); //получить кадры с камеры и усреднить их
      //найти средний уровень пикселей окна табло
      pixel_level = find_middle_level_image(frame_buf, false);

      //отображение на дисплеи
      dispalay_ttf_B_W(frame_buf, pixel_level, V[V_level_dispalay_ttf_B_W]); //повысим на 5-20 единиц, чтобы убрать засветку

      //  free_heap  = heap_caps_get_free_size(MALLOC_CAP_8BIT);
      //поиск положения окна цифр - при найденом уровне по оси y
      find_digits_y(frame_buf, pixel_level, V[V_level_find_digital_Y], false); //уровень повысим на 15 единиц, чтобы убрать засветку
      //  Serial.printf("heap = %dn",free_heap-heap_caps_get_free_size(MALLOC_CAP_8BIT));

      //поиск максимума - предположительно середины цифр
      find_max_digital_X(frame_buf, pixel_level, V[V_level_find_digital_X], false); //уровень повысим на 7 единиц, чтобы убрать засветку

      //найти средний уровень для каждой цифры
      find_middle_britnes_digital(frame_buf, false);

      //преобразование в 32 битное числа
      convert_to_32(frame_buf, pixel_level, V[V_level_convert_to_32], false); //уровень повысим на 20 единиц, чтобы убрать засветку

      //сравнить с эталонном - рассчет расстояния Хемминга
      for (uint8_t dig = 0; dig < number_letter; dig++) { //проврека по всем цифрам шкалы
        result[count][dig] = image_recognition(dig, V[V_show_digital]);
        frequency[result[count][dig]][dig]++; //посчет числа совпадения цифра определенной цифры
      }

      //     if ((int)(millis() - 20000) > 0) //ждать стабилизации камеры 20 секунд
      //       show_result(false); //вывести результат, чтобы результат всегда был на экране

    }
  }

  if (V[V_SH_M3] == 1) print_m3(); //вывести накопленные даные на экран монитора

  if (V[V_GBW] != 2) {
    //     if ((int)(millis() - 20000) > 0) //ждать стабилизации камеры 20 секунд
    show_result(true);
  }
  change_variables(false); //если были изменения коэффициентов записать
}
//---------------------------------------------------- loop

файл sample.h с эталонами

#define sample_height height_letter

#define number_of_samples 41//число эталонов

//массив соответсвий образцов и цифр
uint8_t sample_equation[]  = {  0,0,0,1,1,1,2,2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,6,7,7,7,8,8,8,9,9,9,9,9,0,1,
      0,1,2,4,6,}; //старый счетчик
static uint32_t sample[][sample_height] = {
{//0 (0)
0x0000000000, //                                 
0x0000003c00, //                   1111          
0x000000fe00, //                 1111111         
0x000000ff00, //                 11111111        
0x000000ff80, //                 111111111       
0x000000e780, //                 111  1111       
0x000001e780, //                1111  1111       
0x000001e380, //                1111   111       
0x000001e380, //                1111   111       
0x000001e380, //                1111   111       
0x000001c3c0, //                111    1111      
0x000001c3c0, //                111    1111      
0x000001c3c0, //                111    1111      
0x000001c3c0, //                111    1111      
0x000001e3c0, //                1111   1111      
0x000001e380, //                1111   111       
0x000001e380, //                1111   111       
0x000001e380, //                1111   111       
0x000000e780, //                 111  1111       
0x000000e380, //                 111   111       
0x000000ff80, //                 111111111       
0x000000ff00, //                 11111111        
0x0000003c00, //                   1111          
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//1 (0)
0x0000000000, //                                 
0x0000004200, //                  1    1         
0x000001c380, //                111    111       
0x000001c380, //                111    111       
0x000003c3c0, //               1111    1111      
0x000003c3c0, //               1111    1111      
0x000003c3c0, //               1111    1111      
0x000003c3c0, //               1111    1111      
0x000003c3c0, //               1111    1111      
0x000003c3c0, //               1111    1111      
0x000003c3c0, //               1111    1111      
0x000003c780, //               1111   1111       
0x000003c780, //               1111   1111       
0x000001e780, //                1111  1111       
0x000001ff80, //                1111111111       
0x000001ff80, //                1111111111       
0x000000ff00, //                 11111111        
0x0000007e00, //                  111111         
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000001c00, //                    111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
},
{//2 (0)
0x0000000000, //                                 
0x0000010380, //                1      111       
0x0000010380, //                1      111       
0x0000018380, //                11     111       
0x0000038380, //               111     111       
0x000003c3c0, //               1111    1111      
0x000003c3c0, //               1111    1111      
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000001c380, //                111    111       
0x000001c780, //                111   1111       
0x000001c780, //                111   1111       
0x000001ff80, //                1111111111       
0x000000ff00, //                 11111111        
0x000000ff00, //                 11111111        
0x0000007e00, //                  111111         
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000001800, //                    11           
0x0000003800, //                   111           
0x0000003800, //                   111           
0x0000003800, //                   111           
0x0000003800, //                   111           
0x0000000000, //                                 
},
{//3 (1)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000000000, //                                 
0x0000000000, //                                 
},
{//4 (1)
0x0000007f00, //                  1111111        
0x0000003e00, //                   11111         
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000001000, //                    1            
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000000000, //                                 
},
{//5 (1)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000001800, //                    11           
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//6 (2)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000003c00, //                   1111          
0x0000007f00, //                  1111111        
0x000000ff80, //                 111111111       
0x000001ffc0, //                11111111111      
0x000001e3c0, //                1111   1111      
0x000003c3c0, //               1111    1111      
0x000001c3c0, //                111    1111      
0x00000007c0, //                      11111      
0x0000000780, //                      1111       
0x0000000f80, //                     11111       
0x0000001f00, //                    11111        
0x0000001e00, //                    1111         
0x0000003e00, //                   11111         
0x0000003c00, //                   1111          
0x0000007800, //                  1111           
0x0000007800, //                  1111           
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000ffc0, //                 1111111111      
0x000000ffe0, //                 11111111111     
0x000001ffe0, //                111111111111     
0x0000000000, //                                 
},
{//7 (2)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000003e00, //                   11111         
0x000000ff00, //                 11111111        
0x000001ff80, //                1111111111       
0x000003ffc0, //               111111111111      
0x000007c7e0, //              11111   111111     
0x000007c3e0, //              11111    11111     
0x000007c3e0, //              11111    11111     
0x00000387e0, //               111    111111     
0x0000000fc0, //                     111111      
0x0000000f80, //                     11111       
0x0000001f80, //                    111111       
0x0000003f00, //                   111111        
0x0000007e00, //                  111111         
0x0000007c00, //                  11111          
0x000000fc00, //                 111111          
0x000000f800, //                 11111           
0x000001f800, //                111111           
0x000001f000, //                11111            
0x000001e000, //                1111             
0x000003e000, //               11111             
0x000003ff80, //               11111111111       
0x000003ffe0, //               1111111111111     
0x000003ffe0, //               1111111111111     
0x0000000000, //                                 
},
{//8 (2)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000007800, //                  1111           
0x000001fe00, //                11111111         
0x000003ff00, //               1111111111        
0x000007ff80, //              111111111111       
0x0000078780, //              1111    1111       
0x00000f8780, //             11111    1111       
0x0000070780, //              111     1111       
0x0000020780, //               1      1111       
0x0000000f80, //                     11111       
0x0000001f00, //                    11111        
0x0000003f00, //                   111111        
0x0000007e00, //                  111111         
0x000000fc00, //                 111111          
0x000000fc00, //                 111111          
0x000000f800, //                 11111           
0x000001f000, //                11111            
0x000003f000, //               111111            
0x000003e000, //               11111             
0x000003e000, //               11111             
0x000003e000, //               11111             
0x000007ffc0, //              1111111111111      
0x000007ffc0, //              1111111111111      
},
{//9 (2)
0x0000000000, //                                 
0x0000000c00, //                     11          
0x0000000800, //                     1           
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000007f00, //                  1111111        
0x000000ff80, //                 111111111       
0x000001ff80, //                1111111111       
0x000001c380, //                111    111       
0x000001c380, //                111    111       
0x000003c380, //               1111    111       
0x0000000780, //                      1111       
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000001e00, //                    1111         
0x0000003e00, //                   11111         
0x0000003c00, //                   1111          
0x0000007800, //                  1111           
0x0000007000, //                  111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000e000, //                 111             
0x000001e000, //                1111             
0x000001e000, //                1111             
},
{//10 (3)
0x0000000000, //                                 
0x0000000000, //                                 
0x000003fe00, //               111111111         
0x000003fe00, //               111111111         
0x000003ff00, //               1111111111        
0x0000001f00, //                    11111        
0x0000001f00, //                    11111        
0x0000001e00, //                    1111         
0x0000003e00, //                   11111         
0x0000003c00, //                   1111          
0x0000007c00, //                  11111          
0x000000fc00, //                 111111          
0x000000fe00, //                 1111111         
0x000000ff00, //                 11111111        
0x0000007f80, //                  11111111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x000003ff80, //               11111111111       
0x000003ff00, //               1111111111        
0x000003fe00, //               111111111         
0x0000000000, //                                 
0x0000000000, //                                 
},
{//11 (3)
0x0000000000, //                                 
0x000001fc00, //                1111111          
0x000001fe00, //                11111111         
0x000001fe00, //                11111111         
0x0000001e00, //                    1111         
0x0000001e00, //                    1111         
0x0000001e00, //                    1111         
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000007800, //                  1111           
0x000000f800, //                 11111           
0x000000fc00, //                 111111          
0x000000fe00, //                 1111111         
0x000000ff00, //                 11111111        
0x0000000f00, //                     1111        
0x0000000700, //                      111        
0x0000000700, //                      111        
0x0000000700, //                      111        
0x0000000700, //                      111        
0x0000000700, //                      111        
0x0000000f00, //                     1111        
0x000003ff00, //               1111111111        
0x000003fe00, //               111111111         
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//12 (3)
0x0000000200, //                       1         
0x0000000300, //                       11        
0x0000000380, //                       111       
0x0000000380, //                       111       
0x0000000380, //                       111       
0x0000000380, //                       111       
0x0000000380, //                       111       
0x0000000380, //                       111       
0x0000000780, //                      1111       
0x000003ff00, //               1111111111        
0x000003fe00, //               111111111         
0x000003fc01, //               11111111         1
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000301, //                       11       1
0x0000000700, //                      111        
0x0000000f00, //                     1111        
0x0000001e00, //                    1111         
0x0000001c00, //                    111          
0x0000007800, //                  1111           
0x0000007000, //                  111            
0x0000007000, //                  111            
0x000000e000, //                 111             
0x0000000000, //                                 
},
{//13 (4)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000001c00, //                    111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003800, //                   111           
0x0000007800, //                  1111           
0x0000007800, //                  1111           
0x0000007800, //                  1111           
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000e000, //                 111             
0x000001e600, //                1111  11         
0x000001ef00, //                1111 1111        
0x000001ef00, //                1111 1111        
0x000003ff80, //               11111111111       
0x000003ff80, //               11111111111       
0x000003ff80, //               11111111111       
0x000001ff00, //                111111111        
0x0000000f00, //                     1111        
0x0000000e00, //                     111         
0x0000000e00, //                     111         
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//14 (4)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000007800, //                  1111           
0x0000007800, //                  1111           
0x0000007000, //                  111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000001e200, //                1111   1         
0x000001ef00, //                1111 1111        
0x000001ef00, //                1111 1111        
0x000003ef00, //               11111 1111        
0x000003ff80, //               11111111111       
0x000003ff80, //               11111111111       
0x000003ff80, //               11111111111       
0x000003ff80, //               11111111111       
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//15 (4)
0x0000000000, //                                 
0x0000008000, //                 1               
0x0000008000, //                 1               
0x000000c700, //                 11   111        
0x000001e700, //                1111  111        
0x000001ff80, //                1111111111       
0x000001ff80, //                1111111111       
0x000001ff80, //                1111111111       
0x0000000700, //                      111        
0x0000000700, //                      111        
0x0000000700, //                      111        
0x0000000700, //                      111        
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x000001ff01, //                111111111       1
0x000001ff80, //                1111111111       
0x000001ff00, //                111111111        
0x000001c000, //                111              
0x000001c000, //                111              
0x000001c000, //                111              
0x0000018000, //                11               
0x000001c000, //                111              
0x000001e000, //                1111             
0x0000000000, //                                 
},
{//16 (5)
0x0000000000, //                                 
0x0000010100, //                1       1        
0x0000011100, //                1   1   1        
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000600, //                      11         
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x000001ff00, //                111111111        
0x000001ff00, //                111111111        
0x000001ff00, //                111111111        
0x000001c000, //                111              
0x000001c000, //                111              
0x000001c001, //                111             1
0x000001c000, //                111              
0x000001c000, //                111              
0x000001f800, //                111111           
0x000001fe00, //                11111111         
0x000001ff0f, //                111111111    1111
0x000000070e, //                      111    111 
0x0000000700, //                      111        
0x0000000380, //                       111       
0x0000000000, //                                 
},
{//17 (5)
0x0000000000, //                                 
0x0000000000, //                                 
0x000001ff80, //                1111111111       
0x000001ff80, //                1111111111       
0x000001ff00, //                111111111        
0x000001e000, //                1111             
0x000001c000, //                111              
0x000001c000, //                111              
0x000001e000, //                1111             
0x000001fc00, //                1111111          
0x000001fe00, //                11111111         
0x000001ff00, //                111111111        
0x000000ff80, //                 111111111       
0x0000000780, //                      1111       
0x0000000380, //                       111       
0x0000000380, //                       111       
0x0000000380, //                       111       
0x0000000380, //                       111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x000001ff00, //                111111111        
0x000001fe00, //                11111111         
0x000001fc00, //                1111111          
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//18 (5)
0x0000000000, //                                 
0x0000000000, //                                 
0x000000ff80, //                 111111111       
0x000001ff80, //                1111111111       
0x000001ff00, //                111111111        
0x000001c000, //                111              
0x000001c000, //                111              
0x000001c000, //                111              
0x000001c000, //                111              
0x000001e000, //                1111             
0x000001fc00, //                1111111          
0x000001fe00, //                11111111         
0x000001ff00, //                111111111        
0x000000ff80, //                 111111111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x0000000780, //                      1111       
0x000001ff00, //                111111111        
0x000001ff00, //                111111111        
0x000001fe00, //                11111111         
0x0000000000, //                                 
0x0000000000, //                                 
},
{//19 (6)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000700, //                      111        
0x0000000f00, //                     1111        
0x0000001e00, //                    1111         
0x0000003c00, //                   1111          
0x0000007c00, //                  11111          
0x0000007800, //                  1111           
0x0000007000, //                  111            
0x000000f000, //                 1111            
0x000000fc00, //                 111111          
0x000001fe00, //                11111111         
0x000001ff00, //                111111111        
0x000001ff80, //                1111111111       
0x000001e780, //                1111  1111       
0x000001c380, //                111    111       
0x000001c380, //                111    111       
0x000001c380, //                111    111       
0x000001c380, //                111    111       
0x000001e780, //                1111  1111       
0x000001ff00, //                111111111        
0x000000fe00, //                 1111111         
0x0000007c00, //                  11111          
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//20 (6)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000700, //                      111        
0x0000000f00, //                     1111        
0x0000001e00, //                    1111         
0x0000003c00, //                   1111          
0x0000003800, //                   111           
0x0000007800, //                  1111           
0x0000007000, //                  111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000001fe00, //                11111111         
0x000001ff00, //                111111111        
0x000001ff00, //                111111111        
0x000003f780, //               111111 1111       
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000001e780, //                1111  1111       
0x000001ff00, //                111111111        
0x000000fe00, //                 1111111         
0x0000007c00, //                  11111          
0x0000000000, //                                 
0x0000000000, //                                 
},
{//21 (6)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000700, //                      111        
0x0000000f00, //                     1111        
0x0000001e00, //                    1111         
0x0000003c00, //                   1111          
0x0000003800, //                   111           
0x0000007800, //                  1111           
0x0000007000, //                  111            
0x000000f000, //                 1111            
0x000000f800, //                 11111           
0x000001fe00, //                11111111         
0x000001ff00, //                111111111        
0x000001ff00, //                111111111        
0x000003ff80, //               11111111111       
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000001e780, //                1111  1111       
0x000001ff00, //                111111111        
0x000000fe00, //                 1111111         
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//22 (6)
0x0000000000, //                                 
0x0000004000, //                  1              
0x0000004000, //                  1              
0x0000008000, //                 1               
0x000000f000, //                 1111            
0x000001fe00, //                11111111         
0x000001ff80, //                1111111111       
0x000001e780, //                1111  1111       
0x000001c380, //                111    111       
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000003c380, //               1111    111       
0x000001e780, //                1111  1111       
0x000001ff00, //                111111111        
0x000000fe00, //                 1111111         
0x0000007e01, //                  111111        1
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x000001ff00, //                111111111        
0x000001ff80, //                1111111111       
0x000001ff00, //                111111111        
0x0000000100, //                        1        
0x0000000000, //                                 
},
{//23 (7)
0x0000000000, //                                 
0x000003ff00, //               1111111111        
0x000007ff80, //              111111111111       
0x000007ff80, //              111111111111       
0x000003ff00, //               1111111111        
0x0000001e00, //                    1111         
0x0000001e00, //                    1111         
0x0000003e00, //                   11111         
0x0000003c00, //                   1111          
0x0000003800, //                   111           
0x0000007800, //                  1111           
0x0000007800, //                  1111           
0x000000f800, //                 11111           
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000001e000, //                1111             
0x000001e000, //                1111             
0x000000e000, //                 111             
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//24 (7)
0x0000000000, //                                 
0x000000ff80, //                 111111111       
0x000003ff80, //               11111111111       
0x000003ff80, //               11111111111       
0x0000000f00, //                     1111        
0x0000000e00, //                     111         
0x0000001e00, //                    1111         
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000003800, //                   111           
0x0000003800, //                   111           
0x0000007800, //                  1111           
0x0000007800, //                  1111           
0x0000007000, //                  111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000e000, //                 111             
0x000000e000, //                 111             
0x000000e000, //                 111             
0x000000e000, //                 111             
0x000000e000, //                 111             
0x000000e000, //                 111             
0x0000000000, //                                 
0x0000000000, //                                 
},
{//25 (7)
0x0000000f00, //                     1111        
0x0000000e00, //                     111         
0x0000001e00, //                    1111         
0x0000001c00, //                    111          
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000003800, //                   111           
0x0000007800, //                  1111           
0x0000007800, //                  1111           
0x0000007000, //                  111            
0x0000007000, //                  111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000f000, //                 1111            
0x000000e000, //                 111             
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000007e00, //                  111111         
0x000000ff00, //                 11111111        
0x000001ff00, //                111111111        
0x000001ff00, //                111111111        
},
{//26 (8)
0x0000004000, //                  1              
0x0000004000, //                  1              
0x0000006000, //                  11             
0x0000006000, //                  11             
0x0000006000, //                  11             
0x0000006000, //                  11             
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000007e00, //                  111111         
0x0000007f00, //                  1111111        
0x000000ff80, //                 111111111       
0x000000e780, //                 111  1111       
0x000001e780, //                1111  1111       
0x000001e781, //                1111  1111      1
0x000001e380, //                1111   111       
0x000001e700, //                1111  111        
0x000000ff00, //                 11111111        
0x000000ff00, //                 11111111        
0x000000ff00, //                 11111111        
0x000001ff80, //                1111111111       
0x000001c380, //                111    111       
0x0000018300, //                11     11        
0x0000000000, //                                 
},
{//27 (8)
0x0000000000, //                                 
0x0000007c00, //                  11111          
0x000000fe00, //                 1111111         
0x000001fe00, //                11111111         
0x000001cf00, //                111  1111        
0x000001cf00, //                111  1111        
0x000001cf00, //                111  1111        
0x000001ff00, //                111111111        
0x000001fe00, //                11111111         
0x000001fe00, //                11111111         
0x000001ff00, //                111111111        
0x000001ff00, //                111111111        
0x000001ef80, //                1111 11111       
0x000003c780, //               1111   1111       
0x000003c780, //               1111   1111       
0x000003c780, //               1111   1111       
0x000003c780, //               1111   1111       
0x000003c780, //               1111   1111       
0x000003e780, //               11111  1111       
0x000001ff00, //                111111111        
0x000000ff00, //                 11111111        
0x0000007e00, //                  111111         
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//28 (8)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000001e00, //                    1111         
0x0000003f00, //                   111111        
0x0000007f00, //                  1111111        
0x000000f780, //                 1111 1111       
0x000000e780, //                 111  1111       
0x000000e780, //                 111  1111       
0x000000e780, //                 111  1111       
0x000000ff80, //                 111111111       
0x000000ff00, //                 11111111        
0x000000ff00, //                 11111111        
0x000000ff80, //                 111111111       
0x000000ff80, //                 111111111       
0x000001e7c0, //                1111  11111      
0x000001e3c0, //                1111   1111      
0x000001c3c0, //                111    1111      
0x000001c3c0, //                111    1111      
0x000001c3c0, //                111    1111      
0x000001e3c0, //                1111   1111      
0x000001e780, //                1111  1111       
0x000001ff80, //                1111111111       
0x000000ff00, //                 11111111        
0x0000007e00, //                  111111         
0x0000000000, //                                 
0x0000000000, //                                 
},
{//29 (9)
0x0000000000, //                                 
0x0000010080, //                1        1       
0x0000008080, //                 1       1       
0x0000014080, //                1 1      1       
0x000000e380, //                 111   111       
0x000000ff80, //                 111111111       
0x000000ff00, //                 11111111        
0x0000001e00, //                    1111         
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x000000fc00, //                 111111          
0x000000ff00, //                 11111111        
0x000001ff80, //                1111111111       
0x000001c780, //                111   1111       
0x0000038380, //               111     111       
0x0000038381, //               111     111      1
0x0000038380, //               111     111       
0x0000038380, //               111     111       
0x0000038380, //               111     111       
0x000001cf00, //                111  1111        
0x000001ff00, //                111111111        
0x000000ff00, //                 11111111        
},
{//30 (9)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000007c00, //                  11111          
0x000000ff00, //                 11111111        
0x000001ff00, //                111111111        
0x000001ef80, //                1111 11111       
0x000003c780, //               1111   1111       
0x000003c780, //               1111   1111       
0x000003c780, //               1111   1111       
0x000003c780, //               1111   1111       
0x000003c780, //               1111   1111       
0x000001ff80, //                1111111111       
0x000001ff00, //                111111111        
0x000000ff00, //                 11111111        
0x0000007f00, //                  1111111        
0x0000001e00, //                    1111         
0x0000001e00, //                    1111         
0x0000001e00, //                    1111         
0x0000003c00, //                   1111          
0x0000007c00, //                  11111          
0x0000007800, //                  1111           
0x000000f000, //                 1111            
0x000000e000, //                 111             
0x000001c000, //                111              
0x0000000000, //                                 
0x0000000000, //                                 
},
{//31 (9)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000007e00, //                  111111         
0x0000007f80, //                  11111111       
0x000000ff80, //                 111111111       
0x000001e7c0, //                1111  11111      
0x000001c3c0, //                111    1111      
0x000003c3c0, //               1111    1111      
0x000003c3c0, //               1111    1111      
0x000003c3c0, //               1111    1111      
0x000001e7c0, //                1111  11111      
0x000001ffc0, //                11111111111      
0x000001ff80, //                1111111111       
0x000000ff80, //                 111111111       
0x0000007f80, //                  11111111       
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000001e00, //                    1111         
0x0000001e00, //                    1111         
0x0000003c00, //                   1111          
0x0000003c00, //                   1111          
0x0000007800, //                  1111           
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//32 (9)
0x0000000000, //                                 
0x0000010100, //                1       1        
0x0000010000, //                1                
0x0000018100, //                11      1        
0x0000018380, //                11     111       
0x000001c700, //                111   111        
0x000001ff80, //                1111111111       
0x000001ff0f, //                111111111    1111
0x0000007f00, //                  1111111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000e00, //                     111         
0x0000001e01, //                    1111        1
0x0000003e00, //                   11111         
0x0000003c00, //                   1111          
0x0000007800, //                  1111           
0x000000f000, //                 1111            
0x000001e000, //                1111             
0x000001e001, //                1111            1
0x0000008000, //                 1               
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000007c01, //                  11111         1
0x000001fe00, //                11111111         
},
{//33 (9)
0x0000000000, //                                 
0x0000010000, //                1                
0x0000010100, //                1       1        
0x000001e700, //                1111  111        
0x000000ff00, //                 11111111        
0x0000007f00, //                  1111111        
0x0000000f00, //                     1111        
0x0000000700, //                      111        
0x0000000e00, //                     111         
0x0000001c00, //                    111          
0x0000001c00, //                    111          
0x0000003c00, //                   1111          
0x0000007800, //                  1111           
0x000000f000, //                 1111            
0x000000e000, //                 111             
0x000001c001, //                111             1
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000007c00, //                  11111          
0x000000fe00, //                 1111111         
0x0000018600, //                11    11         
0x0000010100, //                1       1        
0x0000000000, //                                 
},
//старый счетчик
{//34 (0)
0x0000000000, //                                 
0x0000000000, //                                 
0x000000ff00, //                 11111111        
0x000003ff80, //               11111111111       
0x000007ffc0, //              1111111111111      
0x00000781c0, //              1111      111      
0x00000701e0, //              111       1111     
0x00000701e0, //              111       1111     
0x00000f00e0, //             1111        111     
0x00000f00e0, //             1111        111     
0x00000f00e0, //             1111        111     
0x00000f00e0, //             1111        111     
0x00000f00e0, //             1111        111     
0x00000700e0, //              111        111     
0x00000700e0, //              111        111     
0x00000700e0, //              111        111     
0x00000700e0, //              111        111     
0x00000700e0, //              111        111     
0x00000700e0, //              111        111     
0x00000781e0, //              1111      1111     
0x00000781e0, //              1111      1111     
0x000007ffe0, //              11111111111111     
0x000001ffc0, //                11111111111      
0x000001ff00, //                111111111        
0x0000000000, //                                 
0x0000000000, //                                 
},
{//35 (1)
0x0000000000, //                                 
0x0000001e00, //                    1111         
0x0000003f00, //                   111111        
0x0000007f00, //                  1111111        
0x0000007f00, //                  1111111        
0x000000ff00, //                 11111111        
0x000000ff00, //                 11111111        
0x000000ff00, //                 11111111        
0x000000cf00, //                 11  1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000f00, //                     1111        
0x0000000700, //                      111        
0x0000000600, //                      11         
0x0000000000, //                                 
0x0000000000, //                                 
},
{//36 (2)
0x0000000000, //                                 
0x0000000000, //                                 
0x000000fe00, //                 1111111         
0x000001ff80, //                1111111111       
0x000007ffc0, //              1111111111111      
0x00000783c0, //              1111     1111      
0x00000f03c0, //             1111      1111      
0x00000e01c0, //             111        111      
0x00000e01c0, //             111        111      
0x00000e01c0, //             111        111      
0x00000003c0, //                       1111      
0x00000003c0, //                       1111      
0x00000007c0, //                      11111      
0x0000001f80, //                    111111       
0x0000003f00, //                   111111        
0x000000fe00, //                 1111111         
0x000001fc00, //                1111111          
0x000003f000, //               111111            
0x000007e000, //              111111             
0x000007c000, //              11111              
0x00000f8000, //             11111               
0x00000f8000, //             11111               
0x00000fffc0, //             11111111111111      
0x000007ffc0, //              1111111111111      
0x0000000000, //                                 
0x0000000000, //                                 
},
{//37 (4)
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000780, //                      1111       
0x0000000f80, //                     11111       
0x0000001f80, //                    111111       
0x0000001f80, //                    111111       
0x0000001f80, //                    111111       
0x0000003fe0, //                   111111111     
0x0000007fc0, //                  111111111      
0x000000ffe0, //                 11111111111     
0x000001f7c0, //                11111 11111      
0x000001e600, //                1111  11         
0x000003c700, //               1111   111        
0x000007cf00, //              11111  1111        
0x0000078f80, //              1111   11111       
0x00000fffc0, //             11111111111111      
0x00000fffc0, //             11111111111111      
0x00000fffc0, //             11111111111111      
0x00000fffc0, //             11111111111111      
0x00000fff80, //             1111111111111       
0x0000000f80, //                     11111       
0x0000000780, //                      1111       
0x0000000700, //                      111        
0x0000000700, //                      111        
0x0000000000, //                                 
0x0000000000, //                                 
},
{//38 (6)
0x0000000000, //                                 
0x000000ff00, //                 11111111        
0x000001ff80, //                1111111111       
0x000003ffc0, //               111111111111      
0x00000381c0, //               111      111      
0x0000078000, //              1111               
0x0000078000, //              1111               
0x000007fe00, //              1111111111         
0x000007ff80, //              111111111111       
0x000007ffc0, //              1111111111111      
0x000007efc0, //              111111 111111      
0x00000781e0, //              1111      1111     
0x00000781e0, //              1111      1111     
0x00000780e0, //              1111       111     
0x00000780e0, //              1111       111     
0x00000780e0, //              1111       111     
0x00000700e0, //              111        111     
0x00000781e0, //              1111      1111     
0x00000381c0, //               111      111      
0x000003c3c0, //               1111    1111      
0x000001ff00, //                111111111        
0x000000ff00, //                 11111111        
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//39 (6)
0x0000000000, //                                 
0x0000000300, //                       11        
0x0000003c00, //                   1111          
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000003c00, //                   1111          
0x000000ff00, //                 11111111        
0x000001cf80, //                111  11111       
0x0000038180, //               111      11       
0x0000038000, //               111               
0x0000038000, //               111               
0x000003b800, //               111 111           
0x000003ff00, //               1111111111        
0x000003ff80, //               11111111111       
0x000003e780, //               11111  1111       
0x00000381c0, //               111      111      
0x00000381c0, //               111      111      
0x0000030080, //               11        1       
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
{//40 (6)
0x0000000000, //                                 
0x0000001c00, //                    111          
0x000000f000, //                 1111            
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x000000f800, //                 11111           
0x000003fc00, //               11111111          
0x000007be00, //              1111 11111         
0x00000f0600, //             1111     11         
0x00000e0000, //             111                 
0x00000e0000, //             111                 
0x00000fe000, //             1111111             
0x00000ffc00, //             1111111111          
0x00000ffe00, //             11111111111         
0x00000f9e00, //             11111  1111         
0x00000e0700, //             111      111        
0x00000e0700, //             111      111        
0x00000c0700, //             11       111        
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
0x0000000000, //                                 
},
};

Файл virtuino_pins.h

//---VirtuinoCM  Library settings --------------
#include "VirtuinoCM.h"
VirtuinoCM virtuino;
#define V_memory_count 32          // the size of V memory. You can change it to a number <=255)
float V[V_memory_count];           // This array is synchronized with Virtuino V memory. You can change the type to int, long etc.
//---
boolean debug = false;              // set this variable to false on the finale code to decrease the request time.

WiFiServer server(80);                   // Default Virtuino Server port


//Опеределенее ячеек памяти на сервере virtuino

#define V_lastCommTime            0 //V0 время соединения используется для первоначального сброса видимости кнопок в приложении

#define V_number_of_sum_frames    1 //V1 - число кадров суммирования
#define V_offset_y                2 //V2 - смещенее по оси Y при суммировании кадров и отображении на дисплее 75 85 87
#define V_offset_x                3 //V3 - смещенее по оси X при суммировании кадров и отображении на дисплее
#define V_level_dispalay_ttf_B_W  4 //V4 - Доп. уровень бинаризации для дисплея 15
#define V_level_find_digital_Y     5 //V5 - Доп. уровень бинаризации для поиска цифр по y 
#define V_level_find_digital_X    6 //V6 - Доп. уровень бинаризации для поиска цифр по X 

#define V_level_convert_to_32     7 //V7 - Доп. уровень бинаризации при конвертации в 32 бита

#define V_level_Y_up              8 //V8 - Положенее шторки сверху Y_up
#define V_level_Y_down            9 //V9 - Положенее шторки снизу Y_down

#define V_show_digital           10 //V10 - номер цифры какую выводим на экран для сравнения 8 - нет вывода
#define V_offset_x_digital       11 //V11 - смещенее по оси X при отображении на дисплеи для анализа 50 100 150
#define V_GBW                    12 //V12 - 0 = gray 1 - b/w
#define V_SH_0_1                 13 //V13 - вывод на монитор значения гистограммы в двоичном виде
#define V_SH_HEX                 14 //V14 - вывод на монитор в HEX цифр шкалы
#define V_SH_M3                  15 //V15 - вывод на монитор результатов накопленного газа
#define V_m3                     16 //V16 - текущее значенее объема газа умноженное на 100
#define V_m3_minutes             17 //V17 - значенее минут при текущем объеме газа

#define V_D0                     18 //V18 - опознанная цифра 1
#define V_D1                     19 //V19 - опознанная цифра 2
#define V_D2                     20 //V20 - опознанная цифра 3
#define V_D3                     21 //V21 - опознанная цифра 4
#define V_D4                     22 //V22 - опознанная цифра 5
#define V_D5                     23 //V23 - опознанная цифра 6
#define V_D6                     24 //V24 - опознанная цифра 7
#define V_D7                     25 //V25 - опознанная цифра 8

#define V_26_error_recognition   26 //V26 - ошибки распознавания по сравнению с предыдущим
#define V_27                     27 //V27 - 
#define V_28                     28 //V28 - 
#define V_29                     29 //V29 - 
#define V_30                     30 //V30 -
#define V_Total_run              31 //V31 -


String T_0 = "";                    // результаты распознавания
String T_1 = "";                    // расстояние Хемминга
String T_2 = "";                    // частоты повторения цифр

//предопределенные значения
#define old_number_of_sum_frames 5 //число кадров суммирования

#define old_offset_y 83 //смещенее по оси Y при суммировании кадров и отображении на дисплее 54  86 
#define old_offset_x  0 //смещенее по оси X при суммировании кадров и отображении на дисплее

#define old_level_dispalay_ttf_B_W 30 //Доп. уровень бинаризации для дисплея 30
#define old_level_find_digital_Y 30 //Доп. уровень бинаризации для поиска цифр по y 30
#define old_level_find_digital_X 70 //Доп. уровень бинаризации для поиска цифр по X 60 70
#define old_level_convert_to_32 30 //Доп. уровень бинаризации при конвертации в 32 бита 15 73 25 50
#define old_level_Y_up 22 //Положенее шторки сверху Y_up 23
#define old_level_Y_down 48 //Положенее шторки снизу X_down 48


#include <EEPROM.h>

//адреса хранения предопределеных значений в EEPROM
#define offset_y_addr 0 //смещенее по оси Y при суммировании кадров и отображении на дисплее
#define offset_x_addr offset_y_addr + sizeof(byte) //смещенее по оси X при суммировании кадров и отображении на дисплее
#define level_find_digital_Y_addr offset_x_addr + sizeof(byte) //Доп. уровень бинаризации для поиска цифр по y
#define level_find_digital_X_addr level_find_digital_Y_addr + sizeof(byte) //Доп. уровень бинаризации для поиска цифр по X
#define level_convert_to_32_addr level_find_digital_X_addr + sizeof(byte) //Доп. уровень бинаризации при конвертации в 32 бита
#define level_Y_up_addr level_convert_to_32_addr + sizeof(byte)  //Положенее шторки сверху Y_up
#define level_Y_down_addr level_Y_up_addr + sizeof(byte) //Положенее шторки снизу X_down


//================================================================= check_limits
bool check_limits(float &V_test, uint8_t V_max, uint8_t V_min, uint8_t V_set, uint8_t V_MI, uint8_t V_addr)
{
  //Проверка на нахождение в допустимых прелах переменной V_test
  //V_test - значение, который нужно проверить
  //V_max - максимальное значение передела
  //V_min - минимальное значение передал
  //V_set - установить если вне предлов
  //V_MI - ячейка памяти в virtuino
  //V_addr - адрес ячейки для сохранения

//  printf("На входе: V_set %dtV_test %f4tV_old %dn", V_set, V_test, EEPROM.readByte(V_addr));

  if ((uint8_t)(V_test) >= V_max || (uint8_t)(V_test) < V_min || isnan(V_test))
  { //если значение вне пределов или неопределено - nan
    //    Serial.print("Установка первоначального значения: V_set " + (String)V_set
    //                 + "tV_test " + (String)V_test + "tV_MI " + (String)V_MI + "tV_V_addr " + (String)V_addr);
//    Serial.printf("Установка первоначального значения: V_set %ftV_test %ftV_MI %dtV_addr %dn", V_set, V_test, V_MI, V_addr);

    V_test = V_set; //не будет изменения восстанавливаем первоначальное

    V[V_MI] = V_test;

    if (EEPROM.readByte(V_addr) == (uint8_t)(V_test)) //если значение в памяти совпадает не записывать
      return false;

    EEPROM.writeByte(V_addr, (uint8_t)(V_test));     //сохранить в памяти EEPROM
    return true;
  }
  else
  {
    if (EEPROM.readByte(V_addr) != (uint8_t)(V_test)) //если значение в памяти не совпадает записать
    {
      EEPROM.writeByte(V_addr, (uint8_t)(V_test));
      printf("Установить и сохранить значение: V_set %dtV_test %dtV_MI %dtV_addr %dn", V_set, (uint8_t)(V_test), V_MI, V_addr);      
      return true;
    }
    else
      return false;
  }
}
//================================================================= check_limits

//================================================================= change_variables
void change_variables(bool read_from_memory)
//read_from_memory true восстановить из памяти
{
  boolean write_EEPROM_flag = false;
  
  //смещенее по оси Y при суммировании кадров и отображении на дисплее 20 250
  if (read_from_memory) V[V_offset_y] = EEPROM.readByte(offset_y_addr); //восстановить значение из памяти
  write_EEPROM_flag |= check_limits(V[V_offset_y], 250, 20, old_offset_y, V_offset_y, offset_y_addr);

  //смещенее по оси X при суммировании кадров и отображении на дисплее 0 - 50
  if (read_from_memory) V[V_offset_x] = EEPROM.readByte(offset_x_addr); //восстановить значение из памяти
  write_EEPROM_flag |= check_limits(V[V_offset_x], 50, 0, old_offset_x, V_offset_x, offset_x_addr);
  
  //Доп. уровень бинаризации для поиска цифр по y 30
  if (read_from_memory) V[V_level_find_digital_Y] = EEPROM.readByte(level_find_digital_Y_addr); //восстановить значение из памяти
  write_EEPROM_flag |= check_limits(V[V_level_find_digital_Y], 100, 0, old_level_find_digital_Y, V_level_find_digital_Y, level_find_digital_Y_addr);
    
 //Доп. уровень бинаризации для поиска цифр по X 60 70 80
  if (read_from_memory) V[V_level_find_digital_X] = EEPROM.readByte(level_find_digital_X_addr); //восстановить значение из памяти
  write_EEPROM_flag |= check_limits(V[V_level_find_digital_X], 150, 0, old_level_find_digital_X, V_level_find_digital_X, level_find_digital_X_addr);

 //Доп. уровень бинаризации при конвертации в 32 бита 15 73 25 50
  if (read_from_memory) V[V_level_convert_to_32] = EEPROM.readByte(level_convert_to_32_addr); //восстановить значение из памяти
  write_EEPROM_flag |= check_limits(V[V_level_convert_to_32], 150, 0, old_level_convert_to_32, V_level_convert_to_32, level_convert_to_32_addr);
  
 //Положенее шторки сверху Y_up 23
  if (read_from_memory) V[V_level_Y_up] = EEPROM.readByte(level_Y_up_addr); //восстановить значение из памяти
  write_EEPROM_flag |= check_limits(V[V_level_Y_up], 100, 10, old_level_Y_up, V_level_Y_up, level_Y_up_addr);

 //Положенее шторки снизу X_down 44
  if (read_from_memory) V[V_level_Y_down] = EEPROM.readByte(level_Y_down_addr); //восстановить значение из памяти
  write_EEPROM_flag |= check_limits(V[V_level_Y_down], 150, 10, old_level_Y_down, V_level_Y_down, level_Y_down_addr);

  //запись данных в память
  if (write_EEPROM_flag)
  { //записать данные в память
    EEPROM.commit(); //подтвердить запись в память
    printf("Обновлены данные в памятиn");
  }
}
//================================================================= change_variables

//================================================================= init_V
void init_V() {
  boolean write_EEPROM_flag = false;
  
  //инициализация переменных
  change_variables(true);

  V[V_number_of_sum_frames] = old_number_of_sum_frames; //число кадров суммирования
//  V[V_level_dispalay_ttf_B_W] = old_level_dispalay_ttf_B_W; //Доп. уровень бинаризации для дисплея 15
  V[V_level_dispalay_ttf_B_W] = V[V_level_convert_to_32]; //Доп. уровень бинаризации для дисплея совпдает с уровнем уровень бинаризации при конвертации в 32 бита  
    

  V[V_show_digital] = 8;  //V10 - номер цифры какую выводим на экран для сравнения 8 - нет вывода
  V[V_offset_x_digital] = 0; //V11 - смещенее по оси X при отображении на дисплеи для анализа 50 100 150
  V[V_GBW] =  0; //V12 - 0-  - b/w 1 - gray 2 - gray full
  V[V_SH_0_1] = 0; //V13 - вывод на монитор значения гистограммы в двоичном виде
  V[V_SH_HEX] = 0; //V14 - вывод на монитор в HEX цифр шкалы
  V[V_SH_M3] = 0; //V15 - вывод на монитор результатов накопленного газа

  V[V_26_error_recognition] = 0.0; //ошибки распознавания нет

}
//================================================================= init_V

//================================================================= onReceived
/* This function is called every time Virtuino app sends a request to server to change a Pin value
   The 'variableType' can be a character like V, T, O  V=Virtual pin  T=Text Pin    O=PWM Pin
   The 'variableIndex' is the pin number index of Virtuino app
   The 'valueAsText' is the value that has sent from the app   */
void onReceived(char variableType, uint8_t variableIndex, String valueAsText) {
  if (variableType == 'V') {
    float value = valueAsText.toFloat();        // convert the value to float. The valueAsText have to be numerical
    if (variableIndex < V_memory_count) V[variableIndex] = value;          // copy the received value to arduino V memory array
  }
  /*
       else  if (variableType=='T'){
          if (variableIndex==0) T_0=valueAsText;          // Store the text to the text variable T0
          else if (variableIndex==1) T_1=valueAsText;     // Store the text to the text variable T1
         else if (variableIndex==2) T_2=valueAsText;     // Store the text to the text variable T2
         //else if (variableIndex==3) T_3=valueAsText;     // Store the text to the text variable T3

        }
  */
}
//================================================================= onReceived


//================================================================= onRequested
/* This function is called every time Virtuino app requests to read a pin value*/
String onRequested(char variableType, uint8_t variableIndex) {

  char S[30];

  if (variableType == 'V') {
    if (variableIndex < V_memory_count) {
      return String(V[variableIndex]);
      //      sprintf(S, "%.6f", V[variableIndex]); //увеличенее до 6 знаков после запятой
      //      return  S;   // return the value of the arduino V memory array
    }
  }

  else if (variableType == 'T') {
    if (variableIndex == 0) return T_0;
    else if (variableIndex == 1) return T_1;
    else if (variableIndex == 2) return T_2;
    //else if (variableIndex==3) return T_3;
  }

  return "";
}
//================================================================= onRequested






//================================================================= virtuinoRun
void virtuinoRun() {
  WiFiClient client = server.available();
  if (!client) return;
  if (debug) Serial.println("Connected");
  unsigned long timeout = millis() + 3000;
  while (!client.available() && millis() < timeout) delay(1);

  if (millis() > timeout) {
    Serial.println("timeout");
    client.flush();
    client.stop();
    return;
  }

  virtuino.readBuffer = "";  // clear Virtuino input buffer. The inputBuffer stores the incoming characters
  while (client.available() > 0) {
    char c = client.read();         // read the incoming data
    virtuino.readBuffer += c;       // add the incoming character to Virtuino input buffer
    if (debug) Serial.write(c);
  }
  client.flush();
  if (debug) Serial.println("nReceived data: " + virtuino.readBuffer);
  String* response = virtuino.getResponse();   // get the text that has to be sent to Virtuino as reply. The library will check the inptuBuffer and it will create the response text
  if (debug) Serial.println("Response : " + *response);
  client.print(*response);
  client.flush();
  delay(10);
  client.stop();
  //     lastCommTime =  millis();
  V[V_lastCommTime] = round(millis() / 1000.0); //преобразовать в секунды
  if (debug) Serial.println("Disconnected");
}
//================================================================= virtuinoRun






//================================================================= vDelay
void vDelay(int delayInMillis) {

  unsigned long t = millis();

  while ((unsigned long)(millis() - t) < delayInMillis)
    virtuinoRun();
}

//================================================================= vDelay

Пожалуйста, за каждую библиотеку извне дайти ссылку для скачивания. Например для дисплея их просто несчетное количество на Github. Я хотел попробовать проект в деле, но отсутствие библиотек встали непреодолимым препятствием.

Остання редакція Araqel (2021-04-17 21:59:48)

Неактивний

#40 2021-04-17 22:10:54

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Очередная система считывания данных счетчика

Araqel пише:

Пожалуйста, за каждую библиотеку извне дайте ссылку для скачивания. Например для дисплея их просто несчетное количество на Github. Я хотел попробовать проект в деле, но отсутствие библиотек встали непреодолимым препятствием.

У меня система работает и я давно туда не заглядывал - сейчас быстро сложно будет дать ссылки.

Так, например, библиотека дисплея TFT_22_ILI9225
[v1.4.3](https://github.com/Nkawu/TFT_22_ILI9225/releases/tag/v1.4.3) | 2019-11-25 |
но это стандартная библиотека загруженная в Arduino IDE

Сделал архив всех библиотек и скинул на Google Disk
https://drive.google.com/file/d/13AVonp … sp=sharing

Надеюсь это поможет

Остання редакція NickVectra (2021-04-17 22:49:42)

Неактивний

#41 2021-04-17 22:47:55

Araqel
Учасник
З Армения
Зареєстрований: 2021-04-17
Повідомлень: 12

Re: Очередная система считывания данных счетчика

Благодарю за быстрый отзыв!
Вот эти-"fonts/FreeSansBold24pt7b.h"б "time.h", "VirtuinoCM.h", <limits.h> откуда взяты?

Остання редакція Araqel (2021-04-17 22:52:10)

Неактивний

#42 2021-04-17 22:57:49

Araqel
Учасник
З Армения
Зареєстрований: 2021-04-17
Повідомлень: 12

Re: Очередная система считывания данных счетчика

Вы добрейшей души человек. Жму руку.
Вы в начале темы, в прошлом году, писали, что проект находиться в стадии улучщения и доработки. Может есть на него более актуальная, посвяжее версия?

Неактивний

#43 2021-04-17 22:59:44

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Очередная система считывания данных счетчика

Araqel пише:

Вот эти-"fonts/FreeSansBold24pt7b.h"б "time.h", "VirtuinoCM.h" откуда взяты?

FreeSansBold24pt7b.h взято с библиотеки Adafruit_GFX_Library

time.h с библиотеки Time

Библиотека VirtuinoCM.h можно взять с https://virtuino.com/index.php/virtuino/arduino-libraries
или загрузить стандартную библиотеку в Arduino IDE VirtuinoCM

Надеюсь хоть как-то помог big_smile

Неактивний

#44 2021-04-17 23:04:34

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Очередная система считывания данных счетчика

Araqel пише:

Вы в начале темы, в прошлом году, писали, что проект находиться в стадии улучщения и доработки. Может есть на него более актуальная, посвежее версия?

Если я не ошибаюсь то это последняя https://drive.google.com/drive/folders/ … sp=sharing

Еще 9 дней и будет год как работает и сохраняет данные в Google таблице.

Удачи

Остання редакція NickVectra (2021-04-17 23:23:57)

Неактивний

#45 2021-04-17 23:10:49

Araqel
Учасник
З Армения
Зареєстрований: 2021-04-17
Повідомлень: 12

Re: Очередная система считывания данных счетчика

executer пише:

Благодарю.
Меня смущало большое количество SPI хостов на ESP32, почему-то решил что для экрана отдельный хост используется, а как указать какой именно бибилиотеке - не понятно, короче перемудрил).

По GoogleSheets можете подсмотреть у меня в предыдущем проекте https://github.com/executer-uno/ESP32_DustTracker - запись пары сотни символов раз в 30 секунд - работает отлично на ESP32. Лимит - миллион ячеек на книгу, кажется. Делал по мануалам от автора библиотеки  https://github.com/electronicsguy/ESP8266/tree/master/HTTPSRedirect, ну только запись в книгу использовал, без календаря и вычитки параметров.

Оказывается, у Вас большой опыт в интерактивных с Гуглом делах. Вот попытался осуществить https://github.com/gsampallo/esp32cam-gdrive такой проект. Но код в данный репозиторий быль помещен давно и видимо с тех пор в политике Гугл многое изменилось. Ананимно взаимодейстовать с WEB api уже не получается, что пытается делать микроконтроллер, а нужно как минимум вступать на сервер как зарегистрированный пользователь Гугла, если не как хозяин данного Гугл диска. Я даже открыл новый issue, но надежд мало, что хозяин репо что то ответит, который не заглядывал туда 14 месяцев. По вашему мнению какие строки туда должны заноситься, чтобы вход на сервер не был анонимным?

Остання редакція Araqel (2021-04-17 23:21:19)

Неактивний

#46 2021-04-17 23:18:16

Araqel
Учасник
З Армения
Зареєстрований: 2021-04-17
Повідомлень: 12

Re: Очередная система считывания данных счетчика

Araqel пише:
executer пише:

Благодарю.
Меня смущало большое количество SPI хостов на ESP32, почему-то решил что для экрана отдельный хост используется, а как указать какой именно бибилиотеке - не понятно, короче перемудрил).

По GoogleSheets можете подсмотреть у меня в предыдущем проекте https://github.com/executer-uno/ESP32_DustTracker - запись пары сотни символов раз в 30 секунд - работает отлично на ESP32. Лимит - миллион ячеек на книгу, кажется. Делал по мануалам от автора библиотеки  https://github.com/electronicsguy/ESP8266/tree/master/HTTPSRedirect, ну только запись в книгу использовал, без календаря и вычитки параметров.

Оказывается, у Вас большой опыт в интерактивных с Гуглом делах. Вот попытался осуществить https://github.com/gsampallo/esp32cam-gdrive такой проект. Но код в данный репозиторий быль помещен давно и видимо с тех пор в политике Гугл многое изменилось. Ананимно взаимодейстовать с WEB api уже не получается, что пытается делать микроконтроллер, а нужно как мимниму вступать на сервер как зарегистрированный пользователь Гугла, если не как хозяин данного Гугл диска. Я даже открыл новый issue, но надежд мало, что хозяин репо что то ответит, который не заглядывал туда 14 месяцев. По вашему мнению какие строки туда должны заноситься, чтобы вход на сервер не был анонимным?

Я этот код подправил под GSM связь. Вот лог недавольства контроллера надо мною:
20:50:36.000 -> Connecting to APN: internet.beeline.am OK
20:50:40.490 -> Waiting for network...
20:50:40.490 -> GPRS status: connected
20:50:40.490 -> Local IP:10.111.107.23
20:50:46.510 -> Connected to GPRS
20:50:46.784 -> Connect to script.google.com
20:50:49.947 -> Connection successful
20:50:50.832 -> Send a captured image to Google Drive.
20:50:52.940 -> (SSLClient)(SSL_ERROR)(m_update_engine): Error writing to m_client
20:50:52.940 -> (SSLClient)(SSL_ERROR)(m_update_engine): 0
20:50:52.940 -> (SSLClient)(SSL_ERROR)(connected): Not connected because write error is set
20:50:52.974 -> (SSLClient)(SSL_ERROR)(m_print_ssl_error): SSL_CLIENT_WRITE_FAIL
20:50:52.974 -> (SSLClient)(SSL_WARN)(m_run_until): Terminating with write error:
20:50:52.974 -> (SSLClient)(SSL_WARN)(m_run_until): 4
20:50:52.974 -> (SSLClient)(SSL_ERROR)(write): Failed while waiting for the engine to enter BR_SSL_SENDAPP
20:50:52.974 -> (SSLClient)(SSL_ERROR)(write): Cannot operate if the write error is not reset:
20:50:52.974 -> (SSLClient)(SSL_ERROR)(m_print_ssl_error): SSL_CLIENT_WRITE_FAIL
20:50:53.009 -> (SSLClient)(SSL_ERROR)(write): Cannot operate if the write error is not reset:
20:50:53.009 -> (SSLClient)(SSL_ERROR)(m_print_ssl_error): SSL_CLIENT_WRITE_FAIL
20:50:53.009 -> (SSLClient)(SSL_ERROR)(write): Cannot operate if the write error is not reset:

Остання редакція Araqel (2021-04-17 23:21:55)

Неактивний

#47 2021-04-17 23:32:21

Araqel
Учасник
З Армения
Зареєстрований: 2021-04-17
Повідомлень: 12

Re: Очередная система считывания данных счетчика

NickVectra пише:
executer пише:

По GoogleSheets можете подсмотреть у меня в предыдущем проекте https://github.com/executer-uno/ESP32_DustTracker - запись пары сотни символов раз в 30 секунд - работает отлично на ESP32. Лимит - миллион ячеек на книгу, кажется. Делал по мануалам от автора библиотеки  https://github.com/electronicsguy/ESP8266/tree/master/HTTPSRedirect, ну только запись в книгу использовал, без календаря и вычитки параметров.

Спасибо за ссылку на Ваш предыдущий проект, но как в описании Вы написали
"HTTPS Redirect библиотека Таблицы Google использовал этот протокол для загрузки данных. Самая сложная библиотека как по мне)"

Немного упростил код из других примеров и сделал запись в таблицу значений м3 при изменении. Эти же значения сохраняются в кольцевом буфере.

//запись данных счетчика в таблицу
//https://github.com/mrmdudek/ESP32-send-data-to-google-sheet
//https://github.com/ExploreLab/IoT_ggSpreadSheet 
//https://voltiq.ru/post-data-to-google-sheets-with-esp8266/ - скрипт за основу брал этот
//https://developers.google.com/apps-script/reference/spreadsheet/sheet


#include <WiFiClientSecure.h>
WiFiClientSecure client;
char *server_sheet = "script.google.com";  // Server URL
char *GScriptId = "****************"; //ID 
const int httpsPort = 443;

//---------------------------------------------------- GG_ScriptSheet
void GG_ScriptSheet(float m3_sheet, float minutes_sheet){
  String Data = String("&m3=")+(int)m3_sheet+String("&minutes=") + (int)minutes_sheet; 
  String url32 = String("/macros/s/") + GScriptId + "/exec?"+Data;
  Serial.print("Client.Connecting...");
//  Serial.println(url32);
  if (client.connect(server_sheet, httpsPort)) {
    client.println("GET " + String(url32) + " HTTP/1.1"); //HTTP/1.0 OK sent LINEnotify and GoogleSheet
    client.println("Host: " + String(server_sheet));
    client.println("User-Agent: ESP32_counter");
    client.println("Connection: closernrn");
//    Serial.println("Response...OK !!!");  
  }
  else Serial.println("Connection failed!");  
/*  
  while (client.connected()) 
  {
      String line = client.readStringUntil('n');
      if (line == "r") 
      {
//        Serial.println("headers received");
        break;
      }
    }
    // if there are incoming bytes available
    // from the server, read them and print them:
    while (client.available()) 
    {
      char c = client.read();
//      Serial.write(c);
  }
*/
  client.stop();
}
//---------------------------------------------------- GG_ScriptSheet

Поставил на прогон - работает.
Таблица состоит из двух листов на первом - последнее время записи, число строк и последнее значение счетчика.
На вторую страницу записываются сами данные виде ID | DataTime | m3*100 | minutes
За трое суток, при неработающем отоплении, у меня в кольцевом буфере сохранилось около 150 значений. Потребление при использовании горячей воды (как правило не более 0,01 м3/мин).

Вот я вижу, у вас код тоже обходится анонимным входом на Гугл (char *GScriptId = "****************"; //ID что есть адрес скрипта, но оно не предполагает, что Вы авторизованы этим при входе). Это по данным 20-го года. А как обстоит дело в наши дни?

Остання редакція Araqel (2021-04-17 23:36:37)

Неактивний

#48 2021-04-17 23:42:27

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Очередная система считывания данных счетчика

Araqel пише:

Вот я вижу, у вас код тоже обходится анонимным входом на Гугл (char *GScriptId = "****************"; //ID что есть адрес скрипта, но оно не предполагает, что Вы авторизованы этим при входе). Это по данным 20-го года. А как обстоит дело в наши дни?

Я не уверен, что это ответ на Ваш вопрос, но в этом видео на 0:57 выбирается доступ без авторизации
https://www.youtube.com/watch?v=f46VBqWwUuI

Неактивний

#49 2021-04-17 23:53:31

Araqel
Учасник
З Армения
Зареєстрований: 2021-04-17
Повідомлень: 12

Re: Очередная система считывания данных счетчика

Это очень легко проверить. Зайдите на свой https://script.google.com/ и нажмите на Deploy. В взлетающем меню есть только "Вы" и "авторизированный пользователь Гугл". Иными словами микроконтроллр должен авторизоваться на Гугле, чтобы прописать что либо на сервере, хоть послать фото, хоть какие то строчки в ГуглШит.

Неактивний

#50 2021-04-18 00:07:01

Araqel
Учасник
З Армения
Зареєстрований: 2021-04-17
Повідомлень: 12

Re: Очередная система считывания данных счетчика

NickVectra пише:
Araqel пише:

Вот я вижу, у вас код тоже обходится анонимным входом на Гугл (char *GScriptId = "****************"; //ID что есть адрес скрипта, но оно не предполагает, что Вы авторизованы этим при входе). Это по данным 20-го года. А как обстоит дело в наши дни?

Я не уверен, что это ответ на Ваш вопрос, но в этом видео на 0:57 выбирается доступ без авторизации
https://www.youtube.com/watch?v=f46VBqWwUuI

Это старый интерфейс Гугл Скрипта. Тогда действительно можно было подавать анонимный доступ к скрипту. А теперь ни то, что невозможно анонимно обращаться, а интерфейс совсем другой, нежели тот что показан в данном ролике.

Неактивний

Швидке повідомлення

Введіть повідомлення і натисніть Надіслати

Підвал форуму