#1 2020-03-29 18:57:39

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

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

Учитывая актуальную задачу «экономии» ресурсов возникла идея считывания показаний счетчика с возможностью в дальнейшем проведения анализа и построения графиков потребелния. Работу электросчетчика можно контролировать с помощью измерителя мощности, например, такого как PZEM-004. Для счетчика воды можно врезать в водопровод дополнительный водомер. Для учета потребления газа это сделать самостоятельно невозможно - поэтому и решено было попробовать создать устройство считывания показаний. Поиск в интернете показал, что задача выполнима, хотя и довольно сложна в виду отсутствия у меня соответствующих в том числе и базовых знаний.
    Как правило в квартирах установлены счетчика старого образца, которое не имеют устройства встроенного модуля считывания данных и не формирует импульсов, из-за этого единственный способ снятия показаний — оптический.
54f229c582f7f6d9d954916f5dab3fd9.th.jpg 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 В.
    Вначале все делалось на «коленках» в виде конструктора из досточек и изоленты. Любое неосторожное задевание стола или неудачный жест рукой приводил к необходимости по новой строить макет. В качестве дисплея был использован счетный механизм от старого счетчика, который в свое время не прошел поверку.
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 позволяет определить границы расположения строки с изображением шкалы счетчика в общем изображении получаемым от камеры. При проведении бинаризации, для определенного на предварительном шаге уровня яркости, строчки изображения которые соответствуют шкале счетчика будут иметь максимальное суммарное значение пикселей, т. к. в этом месте общего изображения будет самое большое количество засвеченных пикселей. Определенные на этом этапе границы отображаются на индикаторе в виде трех горизонтальных линий (верх, середина и низ цифр) для визуального анализа и последующей коррекции при необходимости.
4beea593845e6f4e00d2e6098778bfed.jpg
Для того, чтобы хота как-то можно было избавиться от посторонних засветов линзу были установлены «шторки». В дальнейшем учитывая то, что камера и индикатор расположены неподвижно друг по отношению к другу и после первоначальной установки не перемещаются - на последующих этапах можно применить так называемый программный метод «шторок», с помощью которого ограничивалась область индикатора с точностью до нескольких пикселей и тем самым обрезать засветка его границ. Дополнительную корректировку значений границ цифр шкалы счетчика и метоположения самого индикатора, также можно выполнить через Web интерфейс.
    В процедуре find_max_letter_X осуществляется поиск середины цифр. Уровень бинаризации повышается и по оси Х строится гистограммы суммы пикселей по вертикали. Дополнительно не учитываются еще два пикселя младших пикселя. После нахождения границ первой цифры осуществляется поиск следующей с шагом равным ширине цифры - 26 пикселей. Для каждой найденной цифры строятся вертикальные линии: левый край, середина, правый край показывающие точность автоматического нахождения положения цифры на изображении. Варианты построения гистограмм яркостных уровней по осям Х и Y для каждой цифры показаны на фотографиях.
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 — считается, что цифра распознана правильно и она выводиться на индикатор желтым цветом. Если данные условия не выполняются, то цифра имеет красный цвет. В случае, когда на предыдущем цикле распознавания цифра уже имела такое же значение, то она становиться зеленой.

    Для визуального контроля на дисплей может выводятся изображение шкалы в следующих режимах:
- черно-белое изображение, которое прошло бинаризацию согласно описанного выше алгоритма. Данный режим позволяет подкорректировать уровень бинаризации и определить засветы.
- шкала выведенная в градациях серого. Распознавание при этом осуществляется также согласно алгоритма. Применяется для визуального считывания данных шкалы и более точного нахождения границ цифр;
- изображение выводиться в градациях серого на весь экран. Распознавание не осуществляется. Данный режим необходим при первоначальных установках положения камеры и позволяет определить где находиться шкала на изображении.
d46a3a5446d0acdf252d100018055358.jpg
На 1,8“ дисплее одновременно помещается первых 5 цифр счетчика, которое собственно и нужны при передаче показаний (остальные три цифры после запятой). На 2,0“ помещается все цифры. Через Web интерфейс при необходимости можно осуществить сдвиг выводимой информации для визуального просмотра и анализа.
    На первых этапах разработки программы в качестве эталонного использовался шрифт получаемый от программного знакогенератора. Однако, как выяснилось позже каждый счетчик может иметь свой шрифт на табло. Кроме того, формируемый  шрифт знакогенератором имеет фиксированные размеры 22, 24 и т.д. пикселя по высоте, что не всегда удобно. Кроме того трудно подобрать шрифт, которые будет максимально совпадать со шрифтом шкалы счетчика.
    В данной реализации высота цифр принята 26 пикселя. В качестве эталона использовано полученное от камеры бинаризированное изображение. Для упрощения этой процедуры в программе предусмотрена возможность вывода в порт в шестнадцатеричном формате цифр шкалы. Данные могут быть скопированы сразу в программу для последующего использования как эталонные. Эталонны хранятся в виде массива uint32_t sample[number_of_samples][sample_height] файл sample.h. Массив эталонов может быть расширен различными версиями начертания цифр в том числе и занимающие половинное положение.
    Некоторые этапы разработки приведены на фото ниже
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 показывает, что цифра ни разу не была определена корректно по указанным выше критериям
    Сам интерфейс написан некорректно и не оптимально, но позволяет оперативно осуществлять предварительную настройку программы.
d49bdc16580fb085f10000d689c2f990.jpg
Из-за перехода на 2,0“ дисплей пришлось полностью переписать программу под другую библиотеку TFT_22_ILI9225, т. к. в нем используется контроллер ILI9225, а в версии 1,8“ ST7735.
    После «жесткой» сборки всей конструкции, несмотря на то, что алгоритм поиска положения границ цифр по высоте (ось Y), работал исправно, применение программных шторок сразу уменьшает зону поиска до нужных границ. Кроме того, так как фокусное расстояние не изменяется размер цифр также не может поменяться. В процедуре find_digits_y фактически находится середина цифр, а границы определяются как ½ высоты цифр, т. е. +/- 13 пикселей.
54827a2974acc096751d65ce14da3868.jpg
    Для накопления результатов объема потребленного газа м3 в программе реализован кольцевой буфер размерностью 2048 процедура m3_calculate(). Показания сохраняются каждую минуту. Если изменений в показаниях не произошло или нет правильного результата происходит увеличение текущего времени простоя. Так как буфер кольцевой, то после его заполнения запись начинается сначала. Выводом накопленных результатов занимается процедура print_m3(). При выводе значений из текущего времени получаемого от NMT сервера отнимаются накопленные минуты, что позволяет узнать время начала записи.  Результаты выводятся в виде таблицы порядковый номер, значение m3, время простоя в минутах. За сутки заполняется приблизительно 70-80 ячеек кольцевого буфера. Для корректного сравнения float все значения показаний счетчика были умножены на 100 и тем самым переведены в целое.
    Оригинальным решением подсказанным мне стало фиксация конструкции вот такими  креплениями за трубы счетчика. В качестве корпус решил применить пластиковый пищевой контейнер.
b311ce2a3cef763b3870e8bdcca6604b.jpg

Итоговый вариант
a5b17b72a1b5a92376731f212097ad48.jpg
6a964685f8b2b425dddc9a3ca06f0d25.jpg

Интерфейс управления был переписан под библиотеку Virtuino
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/jpeg\nContent-Length: %u\n\n";

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=%d\n", 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 вне пределов на минуте %d\n", current_m3, Gas[pos_1].minutes);
    }
    else { //значения совпадают или нормально измененные
      V[V_26_error_recognition] = 0.0; //сбросим счетчик ошибок неверно распознанных
    }

    Serial.printf("Предыдущее %d текущее %d м3 позиция %d минут %d\tразница=%d\n", 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 разница %f\n",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=%d\n",
           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=%d\n", 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("i\tm3*100\tminutes\t\tmax number=%d\n", 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=%.2f\n",i, pos , position_m3+i,Gas[pos].minutes,Gas[pos].m3);
    all_minutes += Gas[i].minutes;   //подсчет общего времени суммируем все
    Serial.printf("%d\t%d\t%d\n", pos, Gas[pos].m3, Gas[pos].minutes);
  }
  V[V_SH_M3] = 0; //выведем один раз

  if (!getLocalTime(&timeinfo)) { //получим время записи сохранения м3
    Serial.printf("Failed to obtain time\n");
  }
  time(&now);
  now -= all_minutes * 60; //отнимим прошедшие минуты о получим начало отсчета
  localtime_r(&now, &timeinfo);
  Serial.printf("\nНачало записи %02d.%02d.%4d %02d:%02d:%02d\n", 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=%d\n", 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=%d\n", 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_y\n");
    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= %d\n", 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 = %d\n", 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\0", 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\0", Gas[pos_1].minutes, Gas[pos_1].m3 / 100.0);
    tft.drawText(0, info_time, buf, COLOR_WHITE);
  }

  sprintf(buf, "%02d:%02d:%02d\0", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);    
  if (!getLocalTime(&timeinfo)) {
    Serial.printf("Failed to obtain time\n");
    tft.drawText(tft.maxX() - tft.getTextWidth(buf), info_time, buf, COLOR_RED); //9 * tft.getCharWidth(48)
  }
  else {
    sprintf(buf, "%02d:%02d:%02d\0", 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 = %d\n", 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=%d\n",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=%d\n", x1, x2, ((x2 - x1) >> 1) + x1, (x2 - x1), dig);

        if (dig > number_letter - 1) { //усли найдено больше чем цифр в шкале 8 number_letter - 1
          if (show) Serial.printf("Найдено больще цифр чем в шкале по оси Х!!! %d\n", 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("Не все цифры шкалы найдены по оси Х!!! %d\n", 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("{//%d\n", 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 %d\t\t\t", 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=%d\tmin_Hemming=%d\n", sample_equation[samp_dig], min_Hemming[samp_dig]);
  }

  if (dig == dig_show)
    Serial.printf("\n***** Found Digit=%d\tmin_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=%d\n",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%010lx\n", 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=%d\n", 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_b\n");
    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 display\n");
      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 frames\n", 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 ms\n", 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("%s\n", 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("%s\n", 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_buf\n");
  }
  else {
    Serial.printf("malloc succeeded frame_buf\n");
  }
  Serial.printf("8BIT = %d\n", 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 time\n");
    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=%d\n",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\0", 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 %d\n", 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=%d\n",
             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 \0", 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 \0", 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 = %s\n",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 = %d\n",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 %d\tV_test %f4\tV_old %d\n", 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 %f\tV_test %f\tV_MI %d\tV_addr %d\n", 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 %d\tV_test %d\tV_MI %d\tV_addr %d\n", 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

Остання редакція NickVectra (2020-03-30 09:15:25)

Неактивний

#2 2020-03-29 22:24:38

Lic Control
Учасник
Зареєстрований: 2016-01-23
Повідомлень: 124

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

Круто!
Молодец. Огромная работа. Ждём продолжения.
А какие потребители газа висят на этом счётчика?

Неактивний

#3 2020-03-29 22:52:04

Жорж
Учасник
Зареєстрований: 2019-02-23
Повідомлень: 14

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

в черговий раз: браво )))
і спасибі за купу цікавого матеріалу і посилань в статті

Неактивний

#4 2020-03-29 23:19:01

Вячеслав Азаров
Учасник
Зареєстрований: 2017-05-25
Повідомлень: 1,732

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

Мне также понравилость такое решение распознавания цифр. Но думаю, что алгоритм ещё потребует доработки. Он может не сгодиться для счетчиков с другими шрифтами. А в общем, впечатляет. Класс!

Неактивний

#5 2020-03-30 03:34:50

Batu
Учасник
З Харьков
Зареєстрований: 2017-11-08
Повідомлень: 301

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

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

Неактивний

#6 2020-03-30 10:03:37

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

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

Lic Control пише:

А какие потребители газа висят на этом счётчика?

Это квартира. Из потребителей двухконтурный газовый котел 24 кВт и газовая плита.

Вячеслав Азаров пише:

Но думаю, что алгоритм ещё потребует доработки. Он может не сгодиться для счетчиков с другими шрифтами.

Набор эталонов создается очень легко. При нажатии на кнопку HEX на монитор выводятся наборы из текущих цифр, которые нужно просто вставить в программу. При изменении показаний и появлении новых цифр - расширяем набор эталонов и т. д. Есть отдельная программка, которая проверяет наборы и дает готовый текст программы.
У меня распознавание тестировалось для двух счетчиков тех, которые были под рукой.

Полностью согласен, что алгоритм требует доработки. Если кто-то возьмет за основу идею может совместно и доработаем  smile

Batu пише:

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

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

Всем спасибо

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

Неактивний

#7 2020-03-30 10:45:43

Вячеслав Азаров
Учасник
Зареєстрований: 2017-05-25
Повідомлень: 1,732

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

NickVectra пише:

Полностью согласен, что алгоритм требует доработки. Если кто-то возьмет за основу идею может совместно и доработаем  smile

Сейчас все блымки и врашалки считают. Можно и так, если сделать надёжный счётчик. Мне один тальянец предлагал на 8-ми разрядном микроконроллере распознавание цифр сделать; шутник. Хотя, думаю возможно, с внешней памятью. Распознавание цифр, это хороший и возможно более правильный вариант. Если оптимизировать прибор - может получится продукт. Но всё-таки это еще дороговато, для масового применения.

Неактивний

#8 2020-03-30 13:56:21

Batu
Учасник
З Харьков
Зареєстрований: 2017-11-08
Повідомлень: 301

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

NickVectra пише:
Batu пише:

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

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

Всем спасибо

здесь видео доступно.
https://youtu.be/IHZwWFHWa-w?t=850
Более подробно и есть еще видео
https://vas3k.ru/blog/machine_learning/
А что за процессор?

Остання редакція Batu (2020-03-30 13:56:49)

Неактивний

#9 2020-03-30 13:59:28

Mishka
Учасник
Зареєстрований: 2019-11-18
Повідомлень: 93

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

Вячеслав Азаров пише:
NickVectra пише:

Полностью согласен, что алгоритм требует доработки. Если кто-то возьмет за основу идею может совместно и доработаем  smile

Мне один тальянец предлагал на 8-ми разрядном микроконроллере распознавание цифр сделать; шутник. Хотя, думаю возможно, с внешней памятью.

"На показанных фотографиях [ширина цифр] равна 45 пикселям (Ymid), высота цифр 21 пикселям (Y_d)", и вы можете добавить порядка двух внутренних уровней по 16-20 узлов, а так же иметь один 10 разрядный уровень на выходе. Итого 45*21+2*20+10 = 995 байтов займет нейронная сеть. Плюс несколько десятков байтов на другие структуры. Само собой, веса тренированной сети прописаны в ПЗУ.

Суммирование операция простая, скорость тут тоже не требуется. Даже если цифра будет проворачиваться в момент получения фотографии и ее нельзя будет распознать, то через 5 секунд это уже станет возможным. С дополнительным контролем результата (число должно все время увеличиваться, цифры в старших разрядах не должны хаотично менять свои значения) можно избежать случайных ошибок. Кроме того, можно даже вести карту вероятностей появления каждой цифры - счетчик ведь работает предсказуемым образом. Она тоже займет порядка 10*N байтов, где N - количество разрядов на счетчике. Зато вероятность ошибки может быть получится свести чуть ли не до нуля.

Остання редакція Mishka (2020-03-30 14:10:27)

Неактивний

#10 2020-03-30 14:19:30

Вячеслав Азаров
Учасник
Зареєстрований: 2017-05-25
Повідомлень: 1,732

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

Mishka пише:
Вячеслав Азаров пише:
NickVectra пише:

Полностью согласен, что алгоритм требует доработки. Если кто-то возьмет за основу идею может совместно и доработаем  smile

Мне один тальянец предлагал на 8-ми разрядном микроконроллере распознавание цифр сделать; шутник. Хотя, думаю возможно, с внешней памятью.

"На показанных фотографиях [ширина цифр] равна 45 пикселям (Ymid), высота цифр 21 пикселям (Y_d)", и вы можете добавить порядка двух внутренних уровней по 16-20 узлов, а так же иметь один 10 разрядный уровень на выходе. Итого 45*21+2*20+10 = 995 байтов займет нейронная сеть. Плюс несколько десятков байтов на другие структуры.

Так кто вам мешает? Покажите класс. smile Восьмиразрядные микроконтроллеры даже интерфейса, к этим видеосенсорам, не имеют. Нужно будет делать самостоятельно. Минимальная скорость чтения которых 5 Мбайт/с, Объем данных сами вычислите. Эти сенсоры не имеют внутреннй памяти изображения, это ПЗС и выборочно читать отдельные области картинки невозможно.

Неактивний

#11 2020-03-30 15:38:57

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

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

Batu пише:

А что за процессор?

Процессор ESP32-CAM

Mishka пише:

"На показанных фотографиях [ширина цифр] равна 45 пикселям (Ymid), высота цифр 21 пикселям (Y_d)", и вы можете добавить порядка двух внутренних уровней по 16-20 узлов, а так же иметь один 10 разрядный уровень на выходе. Итого 45*21+2*20+10 = 995 байтов займет нейронная сеть. Плюс несколько десятков байтов на другие структуры. Само собой, веса тренированной сети прописаны в ПЗУ.

Суммирование операция простая, скорость тут тоже не требуется. Даже если цифра будет проворачиваться в момент получения фотографии и ее нельзя будет распознать, то через 5 секунд это уже станет возможным. С дополнительным контролем результата (число должно все время увеличиваться, цифры в старших разрядах не должны хаотично менять свои значения) можно избежать случайных ошибок. Кроме того, можно даже вести карту вероятностей появления каждой цифры - счетчик ведь работает предсказуемым образом. Она тоже займет порядка 10*N байтов, где N - количество разрядов на счетчике. Зато вероятность ошибки может быть получится свести чуть ли не до нуля.

Ymid - это середина цифры по оси Y
Высота (height_letter) и ширина цифры (width_letter) принята по 26 пикселей.
Ширина цифры после определения занимает от 16 до 3 пикселей в зависимости от того какая цифра.
В высоте также участвует возможный сдвиг цифры, когда она занимает промежуточное положение.
У меня так просто, к сожалению, не получилось разобраться в нейронной сети даже самой простейшей.

Неактивний

#12 2020-03-30 17:50:24

Batu
Учасник
З Харьков
Зареєстрований: 2017-11-08
Повідомлень: 301

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

Процессор ESP32-CAM вполне серьезный проц

Неактивний

#13 2020-03-30 20:00:12

Mishka
Учасник
Зареєстрований: 2019-11-18
Повідомлень: 93

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

Вячеслав Азаров пише:

Так кто вам мешает? Покажите класс. smile Восьмиразрядные микроконтроллеры даже интерфейса, к этим видеосенсорам, не имеют. Нужно будет делать самостоятельно. Минимальная скорость чтения которых 5 Мбайт/с, Объем данных сами вычислите. Эти сенсоры не имеют внутреннй памяти изображения, это ПЗС и выборочно читать отдельные области картинки невозможно.

Мне ничего не мешает. Но я ведь и не должен? smile

В ESP32-CAM, похоже, стоит камера OV2640. У нее есть возможность отображать только интересующий регион (region of interest - ROI). Интерфейс с кадровым буфером делать тоже не нужно.

В принципе вполне возможно читать и распознавать цифра за цифрой все значения счетчика. Похоже, что на Uno с ее Mega2560 это сделать достаточно комфортно. Но теоретически можно попробовать уложиться и в 2Кб ОЗУ, переместив предварительно обученную сеть в ПЗУ.

Конечно же, я совсем не имею ввиду, что нужно заменить ESP32-CAM на 8-битный контроллер. Но и утверждать, что Ваш знакомый просто неудачно пошутил, тоже не могу.

Неактивний

#14 2020-03-30 20:46:45

Mishka
Учасник
Зареєстрований: 2019-11-18
Повідомлень: 93

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

NickVectra пише:

Ymid - это середина цифры по оси Y
Высота (height_letter) и ширина цифры (width_letter) принята по 26 пикселей.
Ширина цифры после определения занимает от 16 до 3 пикселей в зависимости от того какая цифра.
В высоте также участвует возможный сдвиг цифры, когда она занимает промежуточное положение.
У меня так просто, к сожалению, не получилось разобраться в нейронной сети даже самой простейшей.

Ага, вижу, извините за невнимательность. Размер получается еще меньше!

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

Неактивний

#15 2020-03-30 21:02:36

Вячеслав Азаров
Учасник
Зареєстрований: 2017-05-25
Повідомлень: 1,732

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

Mishka пише:
Вячеслав Азаров пише:

Так кто вам мешает? Покажите класс. smile Восьмиразрядные микроконтроллеры даже интерфейса, к этим видеосенсорам, не имеют. Нужно будет делать самостоятельно. Минимальная скорость чтения которых 5 Мбайт/с, Объем данных сами вычислите. Эти сенсоры не имеют внутреннй памяти изображения, это ПЗС и выборочно читать отдельные области картинки невозможно.

Мне ничего не мешает. Но я ведь и не должен? smile

В ESP32-CAM, похоже, стоит камера OV2640. У нее есть возможность отображать только интересующий регион (region of interest - ROI). Интерфейс с кадровым буфером делать тоже не нужно.

В принципе вполне возможно читать и распознавать цифра за цифрой все значения счетчика. Похоже, что на Uno с ее Mega2560 это сделать достаточно комфортно. Но теоретически можно попробовать уложиться и в 2Кб ОЗУ, переместив предварительно обученную сеть в ПЗУ.

Конечно же, я совсем не имею ввиду, что нужно заменить ESP32-CAM на 8-битный контроллер. Но и утверждать, что Ваш знакомый просто неудачно пошутил, тоже не могу.

Не должен, конечно. Советовать и критиковать всегда проще чем самому сделать. Да вы поробуйте ещё подключите её к АТMega2560. Про возможность читать интересующий регион, с этих сенсоров, я не знал. Это упрощает дело, но не решает проблему чтения и синхронизации сенсора. В ESP32-CAM 4 Мв SPI PSRAM + столько же внутренней. Пошутил он, видимо, зная мой интерес к 8-ми разрядкам. А Ардукам имеет и видеобуфер и видеопроцессор. Только Ардуино УНО ему ещё не хватает. smile Речь шла о 8-ми разрядном микроконтроллере а не о GPGA Lattis!

Неактивний

#16 2020-03-30 23:47:26

Watchdog
Гість

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

Да ESP32-CAM, как специально, для этого сделана. И все на одном чипе, не считая памяти. Из чего можно такое собрать, за $5?
https://aliexpress.ru/item/32963016501.html

#17 2020-03-31 07:18:28

Batu
Учасник
З Харьков
Зареєстрований: 2017-11-08
Повідомлень: 301

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

А я вот думаю, не легче ли было сделать счетчики самому.. И нафига это распознавание

Неактивний

#18 2020-03-31 09:13:17

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

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

Watchdog пише:

Да ESP32-CAM, как специально, для этого сделана. И все на одном чипе, не считая памяти. Из чего можно такое собрать, за $5?
https://aliexpress.ru/item/32963016501.html

На этой плате уже есть внешняя PSRAM 4 MB, которая используется, в том числе, для организации 2-х кадровых буферов.
Еще можно отказаться от дисплея и установленного на постоянно преобразователя USB-TTL.

Batu пише:

А я вот думаю, не легче ли было сделать счетчики самому.. И нафига это распознавание

Это газ и кто разрешит его установить?
Насколько я читал, все даже современные счетчики (кроме электрических и тепловых) считают импульсы.

Batu пише:

И нафига это распознавание

А просто интересная задача - что делать когда карантин big_smile

Остання редакція NickVectra (2020-03-31 09:20:36)

Неактивний

#19 2020-03-31 10:06:52

г0сть
Гість

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

NickVectra пише:

Это газ и кто разрешит его установить?

А кто разрешит навешивать на счетчик эту приблуду?

#20 2020-03-31 12:41:02

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

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

г0сть пише:
NickVectra пише:

Это газ и кто разрешит его установить?

А кто разрешит навешивать на счетчик эту приблуду?

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

Вот есть электронный за 5,6 тыс. грн. с радио-модулем для передачи данных  https://www.samgas.com.ua/sites/default/files/rse_instr_user_ru.pdf
но тогда будут вопрос по расшифровке отсылаемых данных.

Неактивний

#21 2020-03-31 21:19:00

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

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

Сегодня вечером добавил модуль HC-05 Bluetooth (был именно такой). Использую только для передачи данных от устройства на телефон или компьютер вместо того, что бы тянуть кабель.
С левой стороны "коробочки" есть два разъема: питание и вход/выход для программирования через USB-TTL преобразователь - он же Serial. Модуль HC-05  включил параллельно USB-TTL преобразователю на скорости 115200.
Теперь все, что передается обычно на Serial также теперь поступает по каналу Bluetooth.
В программе ничего менять не нужно.

Еще хочу сделать меленький резервный источник на одном 18650.
Сейчас на прогоне стоят два варианта: на модуле J5019 и на модуле TP4056+MT3608.
При нагрузке 0,5 А  ни один из модулей не показывает полного заряда аккумулятора, хотя на нем 4,2 В.
От источника идет потребление по 0,7 А. Ну и как известно эти модули греются при зарядке.
При работе устройство потребляет 0,2-0,4 А. Поэтому  сопротивление R3 модуля TP4056 заменил на 1,5 кОм уменьшив ток зарядки и модуль будет соответственно меньше греться.

Почему-то несмотря на то, что "коробка" закреплена и не движется по отношению к счетчику иногда происходит смещение изображения по оси Y. Причем это смещение может быть как вверх так и вниз. Объяснить это я не могу. Поэтому добавил еще автоматическую подстройку по оси Y. Каждые 10 измерений происходит смещение по оси Y на +/- 1 или 0 пикселей. Выбирается то значение смещения, которое дает минимальное значение суммы расстояний  Хемминга для 7 цифр шкалы (без самой последней). На следующем шаге процесс повторяется.

Добавил возможность программного сброса с помощью команды ESP.restart() из приложения.
Сброс дисплея пришлось также сделать программным подключив контакт RST к GPIO13 заменив в скетче строчку
#define TFT_RST        13 // -1

Остання редакція NickVectra (2020-04-08 21:27:04)

Неактивний

#22 2020-04-19 23:50:30

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

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

Пытаюсь повторить проект с выдачей показаний в GoogleSheets табличку. С выдачей проблем нет, а для распознавания показаний Ваш проект идеально подходит.

Я так понял в актуальной версии отказались от Web интерфейса (нет файла myserver.h) в пользу Virtuino.
Не поделитесь проектом для Virtuino и пару слов как его развернуть на телефоне у себя? Не то с ходу там многовато опций непонятных.

Не пользуетесь git-hub?

Неактивний

#23 2020-04-20 10:18:46

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

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

executer пише:

Не поделитесь проектом для Virtuino и пару слов как его развернуть на телефоне у себя?

Скинул последние версии файлов на goodle диск

https://drive.google.com/drive/folders/ … sp=sharing

Из Play Маркета устанавливаете Virtuino (именное Virtuino).
Файл gas1-0.vrt копируете в папку virtuino/projects. Загружаете и запускаете Virtuino.
Устанавливаете правильный адреса сервера - тот, который выводится на экран монитора при запуске скетча (у меня это):
"Camera Stream Ready! Go to: http://172.20.0.35"
Выбирайте настройка сервера
0ac61575817203f85f9881947bae2d6f.jpg
Далее gas counter
e8191cc829a4cf8c01c190ab889c6dd8.jpg
Смените адрес сервера на Ваш.
При необходимости работать не только внутри локальной домашней сети, но и извне можно добавить второй сервер, например, no-ip.
Для этого нужно, чтобы соответствующий адрес и порт были видны извне - т.е. открыть этот порт в роутере.

Второй сервер NodeMCU termo у Вас думаю должен быть просто отключен - это из моего предыдущего проекта по управлению котлом я беру данные для построения графика режима работы котла (страница 1 в приложении Virtuino).

У меня версия Virtuino платная поэтому нет ограничений на количество виджетов. Если загрузите мой gas1-0.vrt, то работать должен, но редактировать, наверное не даст.
В бесплатной версии можно добавлять, кажется, до 10 виджетов, думаю этого достаточно для тестирования.

Значение виджета - это элемент массива V[] в скетче описание которого в файле virtuino_pins.h.

Если в приложении Virtuino Вы строите график, например м3, то потом накопленные значения можно сохранить в формате Excel.
Кроме того, как я писал выше после добавления модуля с Bluetooth накопленные данные можно легко вывести на экран монитора и также сохранить в в формате Excel.даже без физического подключения.

Добавил сбор статистики по использованию эталонов. Стало интересно все ли используются.
Данные выводится на экран монитора при нажатии клавиши "м3 на монитор".

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

Смещение изображения, о котором я писал в предыдущем сообщении происходит если используется два буфера для камеры config.fb_count = 2. т.к. скорости достаточно, то использую config.fb_count = 1.

Готов ответить на Ваши вопросы.

Буду признателен Вам за совместную доработку.

Остання редакція NickVectra (2020-04-20 10:26:55)

Неактивний

#24 2020-04-20 10:54:23

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

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

Большое спасибо за оперативный ответ, выходной пройдет не зря )
Жаль дисплея под рукой нет, заказал только.

У меня задача - распознать показания двух водомеров в поле зрения ESP-CAM и выдать показания в табличку GoogleSheets, экран в общем не нужен, но на этапе отладки видимо без него никак.

Неактивний

#25 2020-04-20 14:47:22

Watchdog
Гість

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

Batu пише:

А я вот думаю, не легче ли было сделать счетчики самому.. И нафига это распознавание

Не легче. Счётчики это метрология, а это уже не кухонное дело. Точность, надёжность, стабильность, эталоны, сертификаты и т.п. А с видеокамеры, взятки гладки. Да и видеосенсор стоит сейчас $1-2. Пришёл час исскуственного ока. smile

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

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

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