Merge branch 'ver4dev' into ver4dev

This commit is contained in:
Mit4el
2025-03-10 22:40:53 +03:00
committed by GitHub
201 changed files with 21245 additions and 1364 deletions

View File

@@ -5,43 +5,15 @@ void* getAPI_Loging(String subtype, String params);
void* getAPI_LogingDaily(String subtype, String params);
void* getAPI_IoTMath(String subtype, String params);
void* getAPI_owmWeather(String subtype, String params);
void* getAPI_Ping(String subtype, String params);
void* getAPI_Timer(String subtype, String params);
void* getAPI_Variable(String subtype, String params);
void* getAPI_VButton(String subtype, String params);
void* getAPI_A02Distance(String subtype, String params);
void* getAPI_Acs712(String subtype, String params);
void* getAPI_AhtXX(String subtype, String params);
void* getAPI_AnalogAdc(String subtype, String params);
void* getAPI_BL0937(String subtype, String params);
void* getAPI_Bme280(String subtype, String params);
void* getAPI_Bmp280(String subtype, String params);
void* getAPI_Dht1122(String subtype, String params);
void* getAPI_Ds18b20(String subtype, String params);
void* getAPI_Impulse(String subtype, String params);
void* getAPI_MQgas(String subtype, String params);
void* getAPI_Pzem004_v2(String subtype, String params);
void* getAPI_RTC(String subtype, String params);
void* getAPI_S8(String subtype, String params);
void* getAPI_Sht20(String subtype, String params);
void* getAPI_Sht30(String subtype, String params);
void* getAPI_Sonar(String subtype, String params);
void* getAPI_UART(String subtype, String params);
void* getAPI_AnalogBtn(String subtype, String params);
void* getAPI_ButtonIn(String subtype, String params);
void* getAPI_ButtonOut(String subtype, String params);
void* getAPI_Buzzer(String subtype, String params);
void* getAPI_Encoder(String subtype, String params);
void* getAPI_IoTServo(String subtype, String params);
void* getAPI_Mcp23017(String subtype, String params);
void* getAPI_Mp3(String subtype, String params);
void* getAPI_Multitouch(String subtype, String params);
void* getAPI_Pcf8574(String subtype, String params);
void* getAPI_Pwm8266(String subtype, String params);
void* getAPI_TelegramLT(String subtype, String params);
void* getAPI_DwinI(String subtype, String params);
void* getAPI_Lcd2004(String subtype, String params);
void* getAPI_Oled64(String subtype, String params);
void* getAPI(String subtype, String params) {
void* tmpAPI;
@@ -50,42 +22,14 @@ if ((tmpAPI = getAPI_Loging(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_LogingDaily(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_IoTMath(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_owmWeather(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Ping(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Timer(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Variable(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_VButton(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_A02Distance(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Acs712(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_AhtXX(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_AnalogAdc(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_BL0937(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Bme280(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Bmp280(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Dht1122(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Ds18b20(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Impulse(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_MQgas(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Pzem004_v2(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_RTC(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_S8(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Sht20(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Sht30(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Sonar(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_UART(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_AnalogBtn(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_ButtonIn(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_ButtonOut(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Buzzer(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Encoder(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_IoTServo(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Mcp23017(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Mp3(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Multitouch(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Pcf8574(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Pwm8266(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_TelegramLT(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_DwinI(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Lcd2004(subtype, params)) != nullptr) return tmpAPI;
if ((tmpAPI = getAPI_Oled64(subtype, params)) != nullptr) return tmpAPI;
return nullptr;
}

View File

@@ -40,36 +40,12 @@
"btn-uploadUI": "Формирует автоматически графический интерфейс на базе конфигурации и выгружает в экран. Занимает продолжительное время! (в разработке)"
}
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32_4mb": [
"esp32*": [
"plerup/EspSoftwareSerial"
],
"esp32_4mb3f": [
"plerup/EspSoftwareSerial"
],
"esp32cam_4mb": [
"plerup/EspSoftwareSerial"
],
"esp8266_4mb": [
"plerup/EspSoftwareSerial"
],
"esp8266_1mb": [
"plerup/EspSoftwareSerial"
],
"esp8266_1mb_ota": [
"plerup/EspSoftwareSerial"
],
"esp8266_2mb": [
"plerup/EspSoftwareSerial"
],
"esp8266_2mb_ota": [
"plerup/EspSoftwareSerial"
],
"esp8285_1mb": [
"plerup/EspSoftwareSerial"
],
"esp8285_1mb_ota": [
"esp82*": [
"plerup/EspSoftwareSerial"
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
// матрица
#define WIDTH 16 // ширина матрицы
#define HEIGHT 16 // высота матрицы
#define SEGMENTS 1 // диодов в одном "пикселе" (для создания матрицы из кусков ленты)
#define NUM_LEDS WIDTH *HEIGHT *SEGMENTS
// лента
#define LED_COUNT NUM_LEDS // число светодиодов в кольце/ленте
#define LED_DT 13 // пин, куда подключен DIN ленты

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,770 @@
#include "utility.h"
byte hue;
boolean loadingFlag = true;
// **************** НАСТРОЙКИ ЭФФЕКТОВ ****************
// эффект "синусоиды" - ОТКЛЮЧЕН
#define WAVES_AMOUNT 2 // количество синусоид
// эффект "шарики"
#define BALLS_AMOUNT 3 // количество "шариков"
#define CLEAR_PATH 1 // очищать путь
#define BALL_TRACK 1 // (0 / 1) - вкл/выкл следы шариков
#define DRAW_WALLS 0 // режим с рисованием препятствий для шаров (не работает на ESP и STM32)
#define TRACK_STEP 70 // длина хвоста шарика (чем больше цифра, тем хвост короче)
// эффект "квадратик"
#define BALL_SIZE 3 // размер шара
#define RANDOM_COLOR 1 // случайный цвет при отскоке
// эффект "огонь"
#define SPARKLES 1 // вылетающие угольки вкл выкл
#define HUE_ADD 0 // добавка цвета в огонь (от 0 до 230) - меняет весь цвет пламени
// эффект "кометы"
#define TAIL_STEP 100 // длина хвоста кометы
#define SATURATION 150 // насыщенность кометы (от 0 до 255)
#define STAR_DENSE 60 // количество (шанс появления) комет
// эффект "конфетти"
#define DENSE 3 // плотность конфетти
#define BRIGHT_STEP 70 // шаг уменьшения яркости
// эффект "снег"
#define SNOW_DENSE 10 // плотность снегопада
// эффект "Светляки"
#define LIGHTERS_AM 35 // количество светляков
uint32_t globalColor = 0xffffff; // Цвет рисования при запуске белый
unsigned char matrixValue[8][16];
unsigned char line[WIDTH];
int pcnt = 0;
int effectSpeed = _speed; // скрость изменения эффекта
uint8_t USE_SEGMENTS = 1;
uint8_t BorderWidth = 0;
uint8_t dir_mx, seg_num, seg_size, seg_offset;
uint16_t XY(uint8_t, uint8_t); // __attribute__ ((weak));
// эффекты матрицы
// *********** "дыхание" яркостью ***********
boolean brightnessDirection;
byte breathBrightness; // Яркость эффекта "Дыхание"
byte globalBrightness = _brightness;
void brightnessRoutine()
{
if (brightnessDirection)
{
breathBrightness += 2;
if (breathBrightness > globalBrightness - 1)
{
brightnessDirection = false;
}
}
else
{
breathBrightness -= 2;
if (breathBrightness < 1)
{
brightnessDirection = true;
}
}
FastLED.setBrightness(breathBrightness);
}
// *********** снегопад 2.0 ***********
void snowRoutine()
{
// сдвигаем всё вниз
for (byte x = 0; x < WIDTH; x++)
{
for (byte y = 0; y < HEIGHT - 1; y++)
{
drawPixelXY(x, y, getPixColorXY(x, y + 1));
}
}
for (byte x = 0; x < WIDTH; x++)
{
// заполняем случайно верхнюю строку
// а также не даём двум блокам по вертикали вместе быть
if (getPixColorXY(x, HEIGHT - 2) == 0 && (random(0, SNOW_DENSE) == 0))
drawPixelXY(x, HEIGHT - 1, 0xE0FFFF - 0x101010 * random(0, 4));
else
drawPixelXY(x, HEIGHT - 1, 0x000000);
}
}
// ***************************** БЛУДНЫЙ КУБИК *****************************
int coordB[2];
int8_t vectorB[2];
CRGB ballColor;
void ballRoutine()
{
if (loadingFlag)
{
for (byte i = 0; i < 2; i++)
{
coordB[i] = WIDTH / 2 * 10;
vectorB[i] = random(8, 20);
ballColor = CHSV(random(0, 9) * 28, 255, 255);
}
loadingFlag = false;
}
for (byte i = 0; i < 2; i++)
{
coordB[i] += vectorB[i];
if (coordB[i] < 0)
{
coordB[i] = 0;
vectorB[i] = -vectorB[i];
if (RANDOM_COLOR)
ballColor = CHSV(random(0, 9) * 28, 255, 255);
// vectorB[i] += random(0, 6) - 3;
}
}
if (coordB[0] > (WIDTH - BALL_SIZE) * 10)
{
coordB[0] = (WIDTH - BALL_SIZE) * 10;
vectorB[0] = -vectorB[0];
if (RANDOM_COLOR)
ballColor = CHSV(random(0, 9) * 28, 255, 255);
// vectorB[0] += random(0, 6) - 3;
}
if (coordB[1] > (HEIGHT - BALL_SIZE) * 10)
{
coordB[1] = (HEIGHT - BALL_SIZE) * 10;
vectorB[1] = -vectorB[1];
if (RANDOM_COLOR)
ballColor = CHSV(random(0, 9) * 28, 255, 255);
// vectorB[1] += random(0, 6) - 3;
}
FastLED.clear();
for (byte i = 0; i < BALL_SIZE; i++)
for (byte j = 0; j < BALL_SIZE; j++)
leds[getPixelNumber(coordB[0] / 10 + i, coordB[1] / 10 + j)] = ballColor;
}
// *********** радуга заливка ***********
void rainbowRoutine()
{
hue += 3;
for (byte i = 0; i < WIDTH; i++)
{
CHSV thisColor = CHSV((byte)(hue + i * float(255 / WIDTH)), 255, 255);
for (byte j = 0; j < HEIGHT; j++)
drawPixelXY(i, j, thisColor); // leds[getPixelNumber(i, j)] = thisColor;
}
}
// *********** радуга дигональная ***********
void rainbowDiagonalRoutine()
{
hue += 3;
for (byte x = 0; x < WIDTH; x++)
{
for (byte y = 0; y < HEIGHT; y++)
{
CHSV thisColor = CHSV((byte)(hue + (float)(WIDTH / HEIGHT * x + y) * (float)(255 / 100)), 255, 255);
drawPixelXY(x, y, thisColor); // leds[getPixelNumber(i, j)] = thisColor;
}
}
}
// *********** радуга активных светодиодов (рисунка) ***********
void rainbowColorsRoutine()
{
hue++;
for (byte i = 0; i < WIDTH; i++)
{
CHSV thisColor = CHSV((byte)(hue + i * float(255 / WIDTH)), 255, 255);
for (byte j = 0; j < HEIGHT; j++)
if (getPixColor(getPixelNumber(i, j)) > 0)
drawPixelXY(i, j, thisColor);
}
}
// ****************************** ОГОНЬ ******************************
// ********************** огонь **********************
// these values are substracetd from the generated values to give a shape to the animation
const unsigned char valueMask[8][16] PROGMEM = {
{32, 0, 0, 0, 0, 0, 0, 32, 32, 0, 0, 0, 0, 0, 0, 32},
{64, 0, 0, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 64},
{96, 32, 0, 0, 0, 0, 32, 96, 96, 32, 0, 0, 0, 0, 32, 96},
{128, 64, 32, 0, 0, 32, 64, 128, 128, 64, 32, 0, 0, 32, 64, 128},
{160, 96, 64, 32, 32, 64, 96, 160, 160, 96, 64, 32, 32, 64, 96, 160},
{192, 128, 96, 64, 64, 96, 128, 192, 192, 128, 96, 64, 64, 96, 128, 192},
{255, 160, 128, 96, 96, 128, 160, 255, 255, 160, 128, 96, 96, 128, 160, 255},
{255, 192, 160, 128, 128, 160, 192, 255, 255, 192, 160, 128, 128, 160, 192, 255}};
// these are the hues for the fire,
// should be between 0 (red) to about 25 (yellow)
const unsigned char hueMask[8][16] PROGMEM = {
{1, 11, 19, 25, 25, 22, 11, 1, 1, 11, 19, 25, 25, 22, 11, 1},
{1, 8, 13, 19, 25, 19, 8, 1, 1, 8, 13, 19, 25, 19, 8, 1},
{1, 8, 13, 16, 19, 16, 8, 1, 1, 8, 13, 16, 19, 16, 8, 1},
{1, 5, 11, 13, 13, 13, 5, 1, 1, 5, 11, 13, 13, 13, 5, 1},
{1, 5, 11, 11, 11, 11, 5, 1, 1, 5, 11, 11, 11, 11, 5, 1},
{0, 1, 5, 8, 8, 5, 1, 0, 0, 1, 5, 8, 8, 5, 1, 0},
{0, 0, 1, 5, 5, 1, 0, 0, 0, 0, 1, 5, 5, 1, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0}};
// Randomly generate the next line (matrix row)
void generateLine()
{
for (uint8_t x = 0; x < WIDTH; x++)
{
line[x] = random(64, 255);
}
}
// shift all values in the matrix up one row
void shiftUp()
{
for (uint8_t y = HEIGHT - 1; y > 0; y--)
{
for (uint8_t x = 0; x < WIDTH; x++)
{
uint8_t newX = x;
if (x > 15)
newX = x % 16;
if (y > 7)
continue;
matrixValue[y][newX] = matrixValue[y - 1][newX];
}
}
for (uint8_t x = 0; x < WIDTH; x++)
{
uint8_t newX = x;
if (x > 15)
newX = x % 16;
matrixValue[0][newX] = line[newX];
}
}
// draw a frame, interpolating between 2 "key frames"
// @param pcnt percentage of interpolation
void drawFrame(int pcnt)
{
int nextv;
// each row interpolates with the one before it
for (unsigned char y = HEIGHT - 1; y > 0; y--)
{
for (unsigned char x = 0; x < WIDTH; x++)
{
uint8_t newX = x;
if (x > 15)
newX = x % 16;
if (y < 8)
{
nextv =
(((100.0 - pcnt) * matrixValue[y][newX] + pcnt * matrixValue[y - 1][newX]) / 100.0) - pgm_read_byte(&(valueMask[y][newX]));
CRGB color = CHSV(
HUE_ADD + pgm_read_byte(&(hueMask[y][newX])), // H
255, // S
(uint8_t)max(0, nextv) // V
);
leds[getPixelNumber(x, y)] = color;
}
else if (y == 8 && SPARKLES)
{
if (random(0, 20) == 0 && getPixColorXY(x, y - 1) != 0)
drawPixelXY(x, y, getPixColorXY(x, y - 1));
else
drawPixelXY(x, y, 0);
}
else if (SPARKLES)
{
// старая версия для яркости
if (getPixColorXY(x, y - 1) > 0)
drawPixelXY(x, y, getPixColorXY(x, y - 1));
else
drawPixelXY(x, y, 0);
}
}
}
// first row interpolates with the "next" line
for (unsigned char x = 0; x < WIDTH; x++)
{
uint8_t newX = x;
if (x > 15)
newX = x % 16;
CRGB color = CHSV(
HUE_ADD + pgm_read_byte(&(hueMask[0][newX])), // H
255, // S
(uint8_t)(((100.0 - pcnt) * matrixValue[0][newX] + pcnt * line[newX]) / 100.0) // V
);
// leds[getPixelNumber(newX, 0)] = color; // На форуме пишут что это ошибка - вместо newX должно быть x, иначе
leds[getPixelNumber(x, 0)] = color; // на матрицах шире 16 столбцов нижний правый угол неработает
}
}
void fireRoutine()
{
if (loadingFlag)
{
loadingFlag = false;
FastLED.clear();
generateLine();
memset(matrixValue, 0, sizeof(matrixValue));
}
if (pcnt >= 100)
{
shiftUp();
generateLine();
pcnt = 0;
}
drawFrame(pcnt);
pcnt += 30;
}
// **************** МАТРИЦА *****************
void matrixRoutine()
{
if (loadingFlag)
{
loadingFlag = false;
FastLED.clear();
}
for (byte x = 0; x < WIDTH; x++)
{
// заполняем случайно верхнюю строку
uint32_t thisColor = getPixColorXY(x, HEIGHT - 1);
if (thisColor == 0)
drawPixelXY(x, HEIGHT - 1, 0x00FF00 * (random(0, 10) == 0));
else if (thisColor < 0x002000)
drawPixelXY(x, HEIGHT - 1, 0);
else
drawPixelXY(x, HEIGHT - 1, thisColor - 0x002000);
}
// сдвигаем всё вниз
for (byte x = 0; x < WIDTH; x++)
{
for (byte y = 0; y < HEIGHT - 1; y++)
{
drawPixelXY(x, y, getPixColorXY(x, y + 1));
}
}
}
// ********************* ЗВЕЗДОПАД ******************
void fadePixel(byte i, byte j, byte step)
{ // новый фейдер
int pixelNum = getPixelNumber(i, j);
if (getPixColor(pixelNum) == 0)
return;
if (leds[pixelNum].r >= 30 ||
leds[pixelNum].g >= 30 ||
leds[pixelNum].b >= 30)
{
leds[pixelNum].fadeToBlackBy(step);
}
else
{
leds[pixelNum] = 0;
}
}
// функция плавного угасания цвета для всех пикселей
void fader(byte step)
{
for (byte i = 0; i < WIDTH; i++)
{
for (byte j = 0; j < HEIGHT; j++)
{
fadePixel(i, j, step);
}
}
}
void starfallRoutine()
{
// заполняем головами комет левую и верхнюю линию
for (byte i = HEIGHT / 2; i < HEIGHT; i++)
{
if (getPixColorXY(0, i) == 0 && (random(0, STAR_DENSE) == 0) && getPixColorXY(0, i + 1) == 0 && getPixColorXY(0, i - 1) == 0)
leds[getPixelNumber(0, i)] = CHSV(random(0, 200), SATURATION, 255);
}
for (byte i = 0; i < WIDTH / 2; i++)
{
if (getPixColorXY(i, HEIGHT - 1) == 0 && (random(0, STAR_DENSE) == 0) && getPixColorXY(i + 1, HEIGHT - 1) == 0 && getPixColorXY(i - 1, HEIGHT - 1) == 0)
leds[getPixelNumber(i, HEIGHT - 1)] = CHSV(random(0, 200), SATURATION, 255);
}
// сдвигаем по диагонали
for (byte y = 0; y < HEIGHT - 1; y++)
{
for (byte x = WIDTH - 1; x > 0; x--)
{
drawPixelXY(x, y, getPixColorXY(x - 1, y + 1));
}
}
// уменьшаем яркость левой и верхней линии, формируем "хвосты"
for (byte i = HEIGHT / 2; i < HEIGHT; i++)
{
fadePixel(0, i, TAIL_STEP);
}
for (byte i = 0; i < WIDTH / 2; i++)
{
fadePixel(i, HEIGHT - 1, TAIL_STEP);
}
}
// рандомные гаснущие вспышки
void sparklesRoutine()
{
for (byte i = 0; i < DENSE; i++)
{
byte x = random(0, WIDTH);
byte y = random(0, HEIGHT);
if (getPixColorXY(x, y) == 0)
leds[getPixelNumber(x, y)] = CHSV(random(0, 255), 255, 255);
}
fader(BRIGHT_STEP);
}
// ----------------------------- СВЕТЛЯКИ ------------------------------
int lightersPos[2][LIGHTERS_AM];
int8_t lightersSpeed[2][LIGHTERS_AM];
CHSV lightersColor[LIGHTERS_AM];
byte loopCounter;
int angle[LIGHTERS_AM];
int speedV[LIGHTERS_AM];
int8_t angleSpeed[LIGHTERS_AM];
void lightersRoutine()
{
if (loadingFlag)
{
loadingFlag = false;
randomSeed(millis());
for (byte i = 0; i < LIGHTERS_AM; i++)
{
lightersPos[0][i] = random(0, WIDTH * 10);
lightersPos[1][i] = random(0, HEIGHT * 10);
lightersSpeed[0][i] = random(-10, 10);
lightersSpeed[1][i] = random(-10, 10);
lightersColor[i] = CHSV(random(0, 255), 255, 255);
}
}
FastLED.clear();
if (++loopCounter > 20)
loopCounter = 0;
for (byte i = 0; i < map(LIGHTERS_AM, 0, 255, 5, 150); i++)
{
if (loopCounter == 0)
{ // меняем скорость каждые 255 отрисовок
lightersSpeed[0][i] += random(-3, 4);
lightersSpeed[1][i] += random(-3, 4);
lightersSpeed[0][i] = constrain(lightersSpeed[0][i], -20, 20);
lightersSpeed[1][i] = constrain(lightersSpeed[1][i], -20, 20);
}
lightersPos[0][i] += lightersSpeed[0][i];
lightersPos[1][i] += lightersSpeed[1][i];
if (lightersPos[0][i] < 0)
lightersPos[0][i] = (WIDTH - 1) * 10;
if (lightersPos[0][i] >= WIDTH * 10)
lightersPos[0][i] = 0;
if (lightersPos[1][i] < 0)
{
lightersPos[1][i] = 0;
lightersSpeed[1][i] = -lightersSpeed[1][i];
}
if (lightersPos[1][i] >= (HEIGHT - 1) * 10)
{
lightersPos[1][i] = (HEIGHT - 1) * 10;
lightersSpeed[1][i] = -lightersSpeed[1][i];
}
drawPixelXY(lightersPos[0][i] / 10, lightersPos[1][i] / 10, lightersColor[i]);
}
}
// ------------- ПЕЙНТБОЛ -------------
void lightBallsRoutine()
{
if (loadingFlag)
{
loadingFlag = false;
FastLED.clear(); // очистить
dir_mx = WIDTH > HEIGHT ? 0 : 1; // 0 - квадратные сегменты расположены горизонтально, 1 - вертикально
seg_num = dir_mx == 0 ? (WIDTH / HEIGHT) : (HEIGHT / WIDTH); // вычисляем количество сегментов, умещающихся на матрице
seg_size = dir_mx == 0 ? HEIGHT : WIDTH; // Размер квадратного сегмента (высота и ширина равны)
seg_offset = ((dir_mx == 0 ? WIDTH : HEIGHT) - seg_size * seg_num) / (seg_num + 1); // смещение от края матрицы и между сегментами
BorderWidth = 0;
}
// Apply some blurring to whatever's already on the matrix
// Note that we never actually clear the matrix, we just constantly
// blur it repeatedly. Since the blurring is 'lossy', there's
// an automatic trend toward black -- by design.
uint8_t blurAmount = dim8_raw(beatsin8(2, 64, 100));
blur2d(leds, WIDTH, HEIGHT, blurAmount);
// The color of each point shifts over time, each at a different speed.
uint32_t ms = millis();
int16_t idx;
byte cnt = map(effectSpeed, 0, 255, 1, 4);
if (USE_SEGMENTS != 0)
{
// Для неквадратных - вычленяем квадратные сегменты, которые равномерно распределяем по ширине / высоте матрицы
uint8_t i = beatsin8(91, 0, seg_size - BorderWidth - 1);
uint8_t j = beatsin8(109, 0, seg_size - BorderWidth - 1);
uint8_t k = beatsin8(73, 0, seg_size - BorderWidth - 1);
uint8_t m = beatsin8(123, 0, seg_size - BorderWidth - 1);
uint8_t d1 = ms / 29;
uint8_t d2 = ms / 41;
uint8_t d3 = ms / 73;
uint8_t d4 = ms / 97;
for (uint8_t ii = 0; ii < seg_num; ii++)
{
delay(0); // Для предотвращения ESP8266 Watchdog Timer
uint8_t cx = dir_mx == 0 ? (seg_offset * (ii + 1) + seg_size * ii) : 0;
uint8_t cy = dir_mx == 0 ? 0 : (seg_offset * (ii + 1) + seg_size * ii);
uint8_t color_shift = ii * 50;
if (cnt <= 1)
{
idx = XY(i + cx, j + cy);
leds[idx] += CHSV(color_shift + d1, 200U, 255U);
}
if (cnt <= 2)
{
idx = XY(j + cx, k + cy);
leds[idx] += CHSV(color_shift + d2, 200U, 255U);
}
if (cnt <= 3)
{
idx = XY(k + cx, m + cy);
leds[idx] += CHSV(color_shift + d3, 200U, 255U);
}
if (cnt <= 4)
{
idx = XY(m + cx, i + cy);
leds[idx] += CHSV(color_shift + d4, 200U, 255U);
}
// При соединении матрицы из угла вверх или вниз почему-то слева и справа узора остаются полосы, которые
// не гаснут обычным blur - гасим полоски левой и правой стороны дополнительно.
// При соединении из угла влево или вправо или на неквадратных матрицах такого эффекта не наблюдается
for (byte i2 = cy; i2 < cy + seg_size; i2++)
{
fadePixel(cx + BorderWidth, i2, 15);
fadePixel(cx + seg_size - BorderWidth - 1, i2, 15);
}
}
}
else
{
uint8_t i = beatsin8(91, BorderWidth, WIDTH - BorderWidth - 1);
uint8_t j = beatsin8(109, BorderWidth, HEIGHT - BorderWidth - 1);
uint8_t k = beatsin8(73, BorderWidth, WIDTH - BorderWidth - 1);
uint8_t m = beatsin8(123, BorderWidth, HEIGHT - BorderWidth - 1);
if (cnt <= 1)
{
idx = XY(i, j);
leds[idx] += CHSV(ms / 29, 200U, 255U);
}
if (cnt <= 2)
{
idx = XY(k, j);
leds[idx] += CHSV(ms / 41, 200U, 255U);
}
if (cnt <= 3)
{
idx = XY(k, m);
leds[idx] += CHSV(ms / 73, 200U, 255U);
}
if (cnt <= 4)
{
idx = XY(i, m);
leds[idx] += CHSV(ms / 97, 200U, 255U);
}
if (WIDTH == HEIGHT)
{
// При соединении матрицы из угла вверх или вниз почему-то слева и справа узора остаются полосы, которые
// не гаснут обычным blur - гасим полоски левой и правой стороны дополнительно.
// При соединении из угла влево или вправо или на неквадратных матрицах такого эффекта не наблюдается
for (byte i = 0; i < HEIGHT; i++)
{
fadePixel(0, i, 15);
fadePixel(WIDTH - 1, i, 15);
}
}
}
}
// ------------- ВОДОВОРОТ -------------
void swirlRoutine()
{
if (loadingFlag)
{
loadingFlag = false;
FastLED.clear(); // очистить
dir_mx = WIDTH > HEIGHT ? 0 : 1; // 0 - квадратные сегменты расположены горизонтально, 1 - вертикально
seg_num = dir_mx == 0 ? (WIDTH / HEIGHT) : (HEIGHT / WIDTH); // вычисляем количество сегментов, умещающихся на матрице
seg_size = dir_mx == 0 ? HEIGHT : WIDTH; // Размер квадратного сегмента (высота и ширина равны)
seg_offset = ((dir_mx == 0 ? WIDTH : HEIGHT) - seg_size * seg_num) / (seg_num + 1); // смещение от края матрицы и между сегментами
BorderWidth = seg_num == 1 ? 0 : 1;
}
// Apply some blurring to whatever's already on the matrix
// Note that we never actually clear the matrix, we just constantly
// blur it repeatedly. Since the blurring is 'lossy', there's
// an automatic trend toward black -- by design.
uint8_t blurAmount = dim8_raw(beatsin8(2, 64, 100));
blur2d(leds, WIDTH, HEIGHT, blurAmount);
uint32_t ms = millis();
int16_t idx;
if (USE_SEGMENTS != 0)
{
// Use two out-of-sync sine waves
uint8_t i = beatsin8(41, 0, seg_size - BorderWidth - 1);
uint8_t j = beatsin8(27, 0, seg_size - BorderWidth - 1);
// Also calculate some reflections
uint8_t ni = (seg_size - 1) - i;
uint8_t nj = (seg_size - 1) - j;
uint8_t d1 = ms / 11;
uint8_t d2 = ms / 13;
uint8_t d3 = ms / 17;
uint8_t d4 = ms / 29;
uint8_t d5 = ms / 37;
uint8_t d6 = ms / 41;
for (uint8_t ii = 0; ii < seg_num; ii++)
{
delay(0); // Для предотвращения ESP8266 Watchdog Timer
uint8_t cx = dir_mx == 0 ? (seg_offset * (ii + 1) + seg_size * ii) : 0;
uint8_t cy = dir_mx == 0 ? 0 : (seg_offset * (ii + 1) + seg_size * ii);
uint8_t color_shift = ii * 50;
// The color of each point shifts over time, each at a different speed.
idx = XY(i + cx, j + cy);
leds[idx] += CHSV(color_shift + d1, 200, 192);
idx = XY(ni + cx, nj + cy);
leds[idx] += CHSV(color_shift + d2, 200, 192);
idx = XY(i + cx, nj + cy);
leds[idx] += CHSV(color_shift + d3, 200, 192);
idx = XY(ni + cx, j + cy);
leds[idx] += CHSV(color_shift + d4, 200, 192);
idx = XY(j + cx, i + cy);
leds[idx] += CHSV(color_shift + d5, 200, 192);
idx = XY(nj + cx, ni + cy);
leds[idx] += CHSV(color_shift + d6, 200, 192);
// При соединении матрицы из угла вверх или вниз почему-то слева и справа узора остаются полосы, которые
// не гаснут обычным blur - гасим полоски левой и правой стороны дополнительно.
// При соединении из угла влево или вправо или на неквадратных матрицах такого эффекта не наблюдается
for (byte i2 = cy; i2 < cy + seg_size; i2++)
{
fadePixel(cx, i2, 15);
fadePixel(cx + BorderWidth, i2, 15);
fadePixel(cx + seg_size - 1, i2, 15);
fadePixel(cx + seg_size - BorderWidth - 1, i2, 15);
}
}
}
else
{
// Use two out-of-sync sine waves
uint8_t i = beatsin8(41, BorderWidth, WIDTH - BorderWidth - 1);
uint8_t j = beatsin8(27, BorderWidth, HEIGHT - BorderWidth - 1);
// Also calculate some reflections
uint8_t ni = (WIDTH - 1) - i;
uint8_t nj = (HEIGHT - 1) - j;
// The color of each point shifts over time, each at a different speed.
idx = XY(i, j);
leds[idx] += CHSV(ms / 11, 200, 192);
idx = XY(ni, nj);
leds[idx] += CHSV(ms / 13, 200, 192);
idx = XY(i, nj);
leds[idx] += CHSV(ms / 17, 200, 192);
idx = XY(ni, j);
leds[idx] += CHSV(ms / 29, 200, 192);
if (HEIGHT == WIDTH)
{
// для квадратных матриц - 6 точек создают более красивую картину
idx = XY(j, i);
leds[idx] += CHSV(ms / 37, 200, 192);
idx = XY(nj, ni);
leds[idx] += CHSV(ms / 41, 200, 192);
// При соединении матрицы из угла вверх или вниз почему-то слева и справа узора остаются полосы, которые
// не гаснут обычным blur - гасим полоски левой и правой стороны дополнительно.
// При соединении из угла влево или вправо или на неквадратных матрицах такого эффекта не наблюдается
for (byte i = 0; i < HEIGHT; i++)
{
fadePixel(0, i, 15);
fadePixel(WIDTH - 1, i, 15);
}
}
}
}
uint16_t XY(uint8_t x, uint8_t y)
{
return getPixelNumber(x, y);
}
//--------------------крутящаяся радуга матрица------------------
void rainbow_loop_matrix()
{ //-m3-LOOP HSV RAINBOW
idex++;
ihue = ihue + thisstep;
if (idex >= LED_COUNT)
{
idex = 0;
}
if (ihue > 255)
{
ihue = 0;
}
for (byte i = 0; i < WIDTH; i++)
{
CHSV thisColor = CHSV(ihue, thissat, 255);
for (byte j = 0; j < HEIGHT; j++)
drawPixelXY(i, j, thisColor); // leds[getPixelNumber(i, j)] = thisColor;
}
LEDS.show();
if (safeDelay(thisdelay))
return;
}

View File

@@ -0,0 +1,40 @@
{
"menuSection": "screens",
"configItem": [
{
"global": 0,
"name": "GyverLAMP",
"type": "Reading",
"subtype": "GyverLAMP",
"id": "GyverLAMP",
"widget": "range",
"page": "Кнопки",
"descr": "GyverLAMP",
"needSave": 0,
"brightness": "10",
"speed": 30,
"dalay": 10000
}
],
"about": {
"authorName": "",
"authorContact": "",
"authorGit": "",
"exampleURL": "",
"specialThanks": "",
"moduleName": "GyverLAMP",
"moduleVersion": "1",
"moduleDesc": "xxx",
"propInfo": {},
"funcInfo": []
},
"defActive": false,
"usedLibs": {
"esp32*": [
"fastled/FastLED@^3.5.0"
],
"esp82*": [
"fastled/FastLED@^3.5.0"
]
}
}

View File

@@ -0,0 +1,92 @@
#include "config.h"
#define MATRIX_TYPE 0
// #define NUM_LEDS LED_COUNT
// **************** НАСТРОЙКА МАТРИЦЫ ****************
#if (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 0)
#define _WIDTH WIDTH
#define THIS_X x
#define THIS_Y y
#elif (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 1)
#define _WIDTH HEIGHT
#define THIS_X y
#define THIS_Y x
#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 0)
#define _WIDTH WIDTH
#define THIS_X x
#define THIS_Y (HEIGHT - y - 1)
#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 3)
#define _WIDTH HEIGHT
#define THIS_X (HEIGHT - y - 1)
#define THIS_Y x
#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 2)
#define _WIDTH WIDTH
#define THIS_X (WIDTH - x - 1)
#define THIS_Y (HEIGHT - y - 1)
#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 3)
#define _WIDTH HEIGHT
#define THIS_X (HEIGHT - y - 1)
#define THIS_Y (WIDTH - x - 1)
#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 2)
#define _WIDTH WIDTH
#define THIS_X (WIDTH - x - 1)
#define THIS_Y y
#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 1)
#define _WIDTH HEIGHT
#define THIS_X y
#define THIS_Y (WIDTH - x - 1)
#else
#define _WIDTH WIDTH
#define THIS_X x
#define THIS_Y y
#pragma message "Wrong matrix parameters! Set to default"
#endif
// получить номер пикселя в ленте по координатам
uint16_t getPixelNumber(int8_t x, int8_t y)
{
if ((THIS_Y % 2 == 0) || MATRIX_TYPE)
{ // если чётная строка
return (THIS_Y * _WIDTH + THIS_X);
}
else
{ // если нечётная строка
return (THIS_Y * _WIDTH + _WIDTH - THIS_X - 1);
}
}
// функция отрисовки точки по координатам X Y
void drawPixelXY(int8_t x, int8_t y, CRGB color)
{
if (x < 0 || x > WIDTH - 1 || y < 0 || y > HEIGHT - 1)
return;
int thisPixel = getPixelNumber(x, y) * SEGMENTS;
for (byte i = 0; i < SEGMENTS; i++)
{
leds[thisPixel + i] = color;
}
//FastLED.show();
}
// функция получения цвета пикселя по его номеру
uint32_t getPixColor(int thisSegm)
{
int thisPixel = thisSegm * SEGMENTS;
if (thisPixel < 0 || thisPixel > NUM_LEDS - 1)
return 0;
return (((uint32_t)leds[thisPixel].r << 16) | ((long)leds[thisPixel].g << 8) | (long)leds[thisPixel].b);
}
// функция получения цвета пикселя в матрице по его координатам
uint32_t getPixColorXY(int8_t x, int8_t y)
{
return getPixColor(getPixelNumber(x, y));
}

View File

@@ -117,7 +117,7 @@
}
]
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [
"https://github.com/robotclass/RobotClass_LiquidCrystal_I2C",

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,17 @@
/**
* @file NexUpload.h
* The definition of class NexUpload.
*
*
* The definition of class NexUpload.
*
* 1 - Removed all the Arduino code and replaced it by ESP-IDF
* 2 - Removed hard-coded UART configuration, see ESPNexUpload constructor
* 3 - Removed statusMessage and the function _printInfoLine
* 4 - Removed call-back functionality
* 5 - Removed one out of two upload functions
* 6 - BugFix in upload function
* @author Machiel Mastenbroek (machiel.mastenbroek@gmail.com)
* @date 2022/08/14
* @version 0.6.0
*
* 1 - BugFix when display baudrate is diffrent from initial ESP baudrate
* 2 - Improved debug information
* 3 - Make delay commands dependent on the baudrate
@@ -10,12 +19,12 @@
* @date 2019/11/04
* @version 0.5.5
*
* Stability improvement, Nextion display doesnt freeze after the seconds 4096 trance of firmware bytes.
* Now the firmware upload process is stabled without the need of a hard Display power off-on intervention.
* Undocumented features (not mentioned in nextion-hmi-upload-protocol-v1-1 specification) are added.
* This implementation is based in on a reverse engineering with a UART logic analyser between
* Stability improvement, Nextion display doesnt freeze after the seconds 4096 trance of firmware bytes.
* Now the firmware upload process is stabled without the need of a hard Display power off-on intervention.
* Undocumented features (not mentioned in nextion-hmi-upload-protocol-v1-1 specification) are added.
* This implementation is based in on a reverse engineering with a UART logic analyser between
* the Nextion editor v0.58 and a NX4024T032_011R Display.
*
*
* @author Machiel Mastenbroek (machiel.mastenbroek@gmail.com)
* @date 2019/10/24
* @version 0.5.0
@@ -24,7 +33,7 @@
* @author Onno Dirkzwager (onno.dirkzwager@gmail.com)
* @date 2018/12/26
* @version 0.3.0
*
*
* Modified to work with ESP8266 and SoftwareSerial
* @author Ville Vilpas (psoden@gmail.com)
* @date 2018/2/3
@@ -33,44 +42,46 @@
* Original version (a part of https://github.com/itead/ITEADLIB_Arduino_Nextion)
* @author Chen Zengpeng (email:<zengpeng.chen@itead.cc>)
* @date 2016/3/29
* @copyright
* @copyright
* Copyright (C) 2014-2015 ITEAD Intelligent Systems Co., Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef __ESPNEXUPLOAD_H__
#define __ESPNEXUPLOAD_H__
#include <functional>
//#include <iostream>
#include <string.h> /* printf, scanf, NULL */
//#include <inttypes.h>
#include "esp_log.h"
//#include "freertos/FreeRTOS.h"
//#include "freertos/task.h"
//#include "driver/gpio.h"
#include "driver/uart.h"
//#include "hal/uart_types.h"
#include <Arduino.h>
#include <StreamString.h>
#ifdef ESP8266
#include <SoftwareSerial.h>
#else
#include <HardwareSerial.h>
#include <SoftwareSerial.h>
#endif
#define CONFIG_NEX_UART_RECV_BUFFER_SIZE 256
/**
* @addtogroup CoreAPI
* @{
* @addtogroup CoreAPI
* @{
*/
// callback template definition
typedef std::function<void(void)> THandlerFunction;
/**
*
* Provides the API for nextion to upload the ftf file.
@@ -78,81 +89,68 @@ typedef std::function<void(void)> THandlerFunction;
class ESPNexUpload
{
public: /* methods */
// callback template definition
typedef std::function<void(void)> THandlerFunction;
/**
* Constructor.
*
* @param uint32_t upload_baudrate - set upload baudrate.
* Constructor.
*
*/
ESPNexUpload(uint32_t upload_baudrate, int line, int rx, int tx);
ESPNexUpload(uart_port_t uart_num, uint32_t baud_rate, gpio_num_t tx_io_num, gpio_num_t rx_io_num);
/**
* destructor.
*
* destructor.
*
*/
~ESPNexUpload() {}
~ESPNexUpload(){}
/**
* Connect to Nextion over serial
*
* @return true or false.
*/
bool connect();
/**
* prepare upload. Set file size & Connect to Nextion over serial
*
* @return true if success, false for failure.
*/
bool prepareUpload(uint32_t file_size);
bool prepareUpload(uint32_t file_size, bool oldProt);
/**
* set Update Progress Callback. (What to do during update progress)
*
* @return none
*/
void setUpdateProgressCallback(THandlerFunction value);
/**
* start update tft file to nextion.
*
* start update tft file to nextion.
*
* @param const uint8_t *file_buf
* @param size_t buf_size
* @return true if success, false for failure.
*/
bool upload(const uint8_t *file_buf, size_t buf_size);
/**
* start update tft file to nextion.
*
* @param Stream &myFile
* @return true if success, false for failure.
*/
bool upload(Stream &myFile);
/**
* Send reset command to Nextion over serial
*
* @return none.
*/
void softReset(void);
void softReset(void);
/**
* Send reset, end serial, reset _sent_packets & update status message
*
* @return none.
*/
void end(void);
public: /* data */
String statusMessage = "";
void end(void);
private: /* methods */
/*
* get communicate baudrate.
* Semaphore construction to prevent double UART actions
*
*/
void uart_mutex_lock(void) {do {} while (xSemaphoreTake(_upload_uart_lock, portMAX_DELAY) != pdPASS);};
void uart_mutex_unlock(void) {xSemaphoreGive(_upload_uart_lock);};
/*
* get communicate baudrate.
*
* @return communicate baudrate.
*
*/
@@ -162,127 +160,167 @@ private: /* methods */
* search communicate baudrate.
*
* @param baudrate - communicate baudrate.
*
* @return true if success, false for failure.
*
* @return true if success, false for failure.
*/
bool _searchBaudrate(uint32_t baudrate);
bool _searchBaudrate(int baudrate);
/*
* set download baudrate.
*
* @param baudrate - set download baudrate.
*
* @return true if success, false for failure.
*
* @return true if success, false for failure.
*/
bool _setPrepareForFirmwareUpdate(uint32_t upload_baudrate);
/*
* set Nextion running mode.
*
* Undocumented feature of the Nextion protocol.
* It's used by the 'upload to Nextion device' feature of the Nextion Editor V0.58
*
* The nextion display doesn't send any response
*
* Undocumented feature of the Nextion protocol.
* It's used by the 'upload to Nextion device' feature of the Nextion Editor V0.58
*
* The nextion display doesn't send any response
*
*/
void _setRunningMode(void);
/*
* Test UART nextion connection availability
*
* @param input - echo string,
*
* @return true when the 'echo string' that is send is equal to the received string
*
* This test is used by the 'upload to Nextion device' feature of the Nextion Editor V0.58
* Test UART nextion connection availability
*
* @param input - echo string,
*
* @return true when the 'echo string' that is send is equal to the received string
*
* This test is used by the 'upload to Nextion device' feature of the Nextion Editor V0.58
*
*/
bool _echoTest(String input);
bool _echoTest(std::string input);
/*
* This function get the sleep and dim value from the Nextion display.
*
* If sleep = 1 meaning: sleep is enabled
* If sleep = 1 meaning: sleep is enabled
* action : sleep will be disabled
* If dim = 0, meaning: the display backlight is turned off
* action : dim will be set to 100 (percent)
*
* If dim = 0, meaning: the display backlight is turned off
* action : dim will be set to 100 (percent)
*
*/
bool _handlingSleepAndDim(void);
/*
* This function (debug) print the Nextion response to a human readable string
*
* @param esp_request - true: request message from esp to nextion
* false: response message from nextion to esp
*
* @param input - string to print
*
* @param esp_request - true: request message from esp to nextion
* false: response message from nextion to esp
*
* @param input - string to print
*
*/
void _printSerialData(bool esp_request, String input);
/*
* This function print a prefix debug line
*
* @param line: optional debug/ info line
*/
void _printInfoLine(String line = "");
void _printSerialData(bool esp_request, std::string input);
/*
* Send command to Nextion.
*
* @param cmd - the string of command.
* @param tail - end the string with tripple 0xFF byte
* @param null_head - start the string with a single 0x00 byte
* @param tail - end the string with tripple 0xFF byte
* @param null_head - start the string with a single 0x00 byte
*
* @return none.
*/
void sendCommand(const char *cmd, bool tail = true, bool null_head = false);
void sendCommand(const char* cmd, bool tail = true, bool null_head = false);
/*
* Receive string data.
*
* @param buffer - save string data.
* @param timeout - set timeout time.
* Receive string data.
*
* @param buffer - save string data.
* @param timeout - set timeout time.
* @param recv_flag - if recv_flag is true,will braak when receive 0x05.
*
* @return the length of string buffer.
*
*/
uint16_t recvRetString(String &string, uint32_t timeout = 500, bool recv_flag = false);
*/
uint16_t recvRetString(std::string &string, uint32_t timeout = 500,bool recv_flag = false);
/*
*
* This function calculates the transmission time, the transmission time
*
* This function calculates the transmission time, the transmission time
* is based on the length of the message and the baudrate.
*
* @param message - only used to determine the length of the message
*
* @param message - only used to determine the length of the message
*
* @return time in us length of string buffer.
*
*/
uint32_t calculateTransmissionTimeMs(String message);
uint32_t calculateTransmissionTimeMs(std::string message);
/*
* Setup UART for communication with display
*
* @param uart_num - UART number
* @param baud_rate - baud rate speed
* @param tx_io_num - GPIO TX pin
* @param rx_io_num - GPIO RX pin
*
*/
void setBaudrate(uart_port_t uart_num, uint32_t baud_rate, gpio_num_t tx_io_num, gpio_num_t rx_io_num);
void nexSerialBegin(uint32_t upload_baudrate, int line, int rx, int tx);
/*
* Check is UART is avaialble
*/
uint32_t uartAvailable();
private: /* data */
uint32_t _baudrate; /* nextion serail baudrate */
uint32_t _undownloadByte; /* undownload byte of tft file */
uint32_t _upload_baudrate; /* upload baudrate */
uint16_t _sent_packets = 0; /* upload baudrate */
uint8_t _rx;
uint8_t _tx;
uint8_t _line;
THandlerFunction _updateProgressCallback;
/*
* Read one RX byte
*
* @return one received UART byte
*/
uint8_t uartRead();
#ifdef ESP8266
SoftwareSerial* nexSerial;
#else
Stream* nexSerial;
#endif
/*
* Write one TX byte
*
* @param c - one byte
*
*/
void uartWrite(uint8_t c);
/*
* Write char string
*
* @param data - char string of data to send
* @param len - length of the string
*
*/
void uartWriteBuf(const char * data, size_t len);
/*
* Clear TX UART buffer
*/
void uartFlushTxOnly();
private: /* data */
bool _oldProtv11;
uint32_t _baudrate; /* nextion serail baudrate */
uint32_t _undownloadByte; /* undownload byte of tft file */
uart_port_t _upload_uart_num; /* upload uart port number */
uint32_t _upload_baudrate; /* upload baudrate */
gpio_num_t _upload_tx_io_num; /* upload gpio TX */
gpio_num_t _upload_rx_io_num; /* upload gpio RX */
xSemaphoreHandle _upload_uart_lock; /* semaphore to prevent double UART actions */
bool _upload_uart_has_peek; /* UART RX peek flag */
uint8_t _upload_uart_peek_byte; /* UART RX peek byte */
//uint16_t _sent_packets = 0; /* _sent_packets till 4096 bytes */
uint32_t _sent_packets_total = 0; /* total number of uploaded display firmware bytes */
bool _uart_diver_installed; /* flag, if true UART is installed */
std::string str_snprintf(const char *fmt, size_t len, ...);
/// Format the byte array \p data of length \p len in pretty-printed, human-readable hex.
std::string format_hex_pretty(const uint8_t *data, size_t length);
static char format_hex_pretty_char(uint8_t v);
};
/**
* @}
*/
#endif /* #ifndef __ESPNEXUPLOAD_H__ */
#endif /* #ifndef __ESPNEXUPLOAD_H__ */

View File

@@ -13,7 +13,8 @@ private:
int _tx, _rx, _speed, _line;
bool _UpTelegram;
char _inc;
String _inStr = ""; // буфер приема строк в режимах 0, 1, 2
String _inStr = ""; // буфер приема строк в режимах 0, 1, 2
bool _oldProt;
// Выводим русские буквы на экран Nextion (преобразуем в кодировку ISO-8859-5)
String convertRUS(String text)
@@ -52,7 +53,7 @@ private:
}
}
return out;
}
}
public:
Nextion(String parameters) : IoTUart(parameters)
@@ -62,9 +63,11 @@ public:
_host = jsonReadStr(parameters, "host");
jsonRead(parameters, "rx", _rx);
jsonRead(parameters, "tx", _tx);
jsonRead(parameters, "speed", _speed);
jsonRead(parameters, "line", _line);
jsonRead(parameters, "speed", _speed);
jsonRead(parameters, "line", _line);
jsonRead(parameters, "uploadTelegram", _UpTelegram);
if (!jsonRead(parameters, "oldProt_v11", _oldProt))
_oldProt = false;
}
IoTValue execute(String command, std::vector<IoTValue> &param)
@@ -74,15 +77,15 @@ public:
{
updateServer();
}
else if (command == "printFFF")
else if (command == "printFFF")
{
if (param.size() == 2)
//UART.printFFF("auto.val=1",0)
if (param.size() == 2)
// UART.printFFF("auto.val=1",0)
{
String strToUart = "";
strToUart = param[0].valS;
if (param[1].valD)
if (param[1].valD)
uartPrintFFF("\"" + strToUart + "\"");
else
uartPrintFFF(strToUart);
@@ -99,7 +102,7 @@ public:
else
uartPrintFFF(strToUart + param[1].valS);
}
}
}
// отправка кирилических символов на Nextion (русские буквы)
else if (command == "printRusFFF")
{
@@ -123,43 +126,54 @@ public:
else
uartPrintFFF(convertRUS(strToUart + param[1].valS));
}
}// else { // не забываем, что переопределяем execute и нужно проверить что в базовом классе проверяется
// return IoTUart::execute(command, param);
// }
return {};
} // else { // не забываем, что переопределяем execute и нужно проверить что в базовом классе проверяется
// return IoTUart::execute(command, param);
// }
return {};
}
void onModuleOrder(String &key, String &value) {
if (key == "uploadServer") {
void onModuleOrder(String &key, String &value)
{
if (key == "uploadServer")
{
updateServer();
}
}
void uartPrintFFF(const String& msg) {
if (_myUART) {
SerialPrint("I", F("Nextion"), "uartPrintFFF -> "+msg+" +FFFFFF");
void uartPrintFFF(const String &msg)
{
if (_myUART)
{
SerialPrint("I", F("Nextion"), "uartPrintFFF -> " + msg + " +FFFFFF");
_myUART->print(msg);
_myUART->write(0xff);
_myUART->write(0xff);
_myUART->write(0xff);
}
}
//---------------------NEXTION-UART---START------------------------
void uartHandle() {
if (!_myUART) return;
if (_myUART->available()) {
//---------------------NEXTION-UART---START------------------------
void uartHandle()
{
if (!_myUART)
return;
if (_myUART->available())
{
_inc = _myUART->read();
if (_inc == 0xFF) {
if (_inc == 0xFF)
{
_inc = _myUART->read();
_inc = _myUART->read();
_inStr = "";
return;
}
if (_inc == '\r') return;
if (_inc == '\n') {
if (_inStr.indexOf("=") == -1) { // если входящее сообщение не по формату, то работаем как в режиме 0
if (_inc == '\r')
return;
if (_inc == '\n')
{
if (_inStr.indexOf("=") == -1)
{ // если входящее сообщение не по формату, то работаем как в режиме 0
setValue(_inStr);
return;
}
@@ -170,88 +184,69 @@ public:
id.replace(".txt", "_txt");
generateOrder(id, valStr);
_inStr = "";
} else _inStr += _inc;
}
else
_inStr += _inc;
}
}
void onRegEvent(IoTItem* eventItem) {
if (!_myUART || !eventItem) return;
void onRegEvent(IoTItem *eventItem)
{
if (!_myUART || !eventItem)
return;
int indexOf_;
String printStr = "";
printStr += eventItem->getID();
indexOf_ = printStr.indexOf("_");
if (indexOf_ == -1) return; // пропускаем событие, если нет используемого признака типа данных - _txt или _vol
if (printStr.indexOf("_txt") > 0) {
printStr.replace("_txt", ".txt=\"");
printStr += eventItem->getValue();
printStr += "\"";
} else if (printStr.indexOf("_val") > 0) {
printStr += eventItem->getValue();
printStr.replace(".", "");
printStr.replace("_val", ".val=");
} else {
if (indexOf_ == printStr.length()-1) printStr.replace("_", "");
else printStr.replace("_", ".");
printStr += "=";
printStr += eventItem->getValue();
}
printStr += eventItem->getID();
indexOf_ = printStr.indexOf("_");
if (indexOf_ == -1)
return; // пропускаем событие, если нет используемого признака типа данных - _txt или _vol
uartPrintFFF(convertRUS(printStr));
if (printStr.indexOf("_txt") > 0)
{
printStr.replace("_txt", ".txt=\"");
printStr += eventItem->getValue();
printStr += "\"";
}
else if (printStr.indexOf("_val") > 0)
{
printStr += eventItem->getValue();
printStr.replace(".", "");
printStr.replace("_val", ".val=");
}
else
{
if (indexOf_ == printStr.length() - 1)
printStr.replace("_", "");
else
printStr.replace("_", ".");
printStr += "=";
printStr += eventItem->getValue();
}
uartPrintFFF(convertRUS(printStr));
}
//---------------------NEXTION-UART---END------------------------
//---------------------NEXTION-UPDATE---START------------------------
//---------------------NEXTION-UART---END------------------------
//---------------------NEXTION-UPDATE---START------------------------
void updateServer()
{
SerialPrint("I", F("NextionUpdate"), "Update .... ");
SerialPrint("I", F("NextionUpdate"), "Update .... ");
if (!updated)
{
SerialPrint("I", F("NextionUpdate"), "connecting to " + (String)_host);
HTTPClient http;
#if defined ESP8266
WiFiClient client;
if (!http.begin(client, _host, 80, _url))
SerialPrint("I", F("NextionUpdate"), "connection failed ");
#elif defined ESP32
if (!http.begin(String("http://") + _host + _url))
SerialPrint("I", F("NextionUpdate"), "connection failed ");
#endif
SerialPrint("I", F("NextionUpdate"), "Requesting file: " + (String)_url);
int code = http.GET();
// Update the nextion display
if (code == 200)
flashNextion(http);
else
SerialPrint("I", F("NextionUpdate"), "HTTP error: " + (String)http.errorToString(code).c_str());
http.end();
SerialPrint("I", F("NextionUpdate"), "Closing connection ");
}
}
void uploadNextionTlgrm(String &url)
{
if (!_UpTelegram)
return;
if (!updated)
{
SerialPrint("I", F("NextionUpdate"), "connecting to " + url);
SerialPrint("I", F("NextionUpdate"), "connecting to " + (String)_host);
HTTPClient http;
#ifdef ESP8266
SerialPrint("I", F("NextionUpdate"), "Update impossible esp8266: Change boards to esp32 :)");
return;
#else
if (!http.begin(url)) // пингуем файл
#if defined ESP8266
if (!http.begin(_host, 80, _url))
SerialPrint("I", F("NextionUpdate"), "connection failed ");
#elif defined ESP32
if (!http.begin(String("http://") + _host + _url))
SerialPrint("I", F("NextionUpdate"), "connection failed ");
#endif
SerialPrint("I", F("NextionUpdate"), "Requesting file: OK" );
SerialPrint("I", F("NextionUpdate"), "Requesting file: " + (String)_url);
int code = http.GET();
// Update the nextion display
if (code == 200)
@@ -264,40 +259,92 @@ public:
}
}
void uploadNextionTlgrm(String &url)
{
if (!_UpTelegram)
return;
if (!updated)
{
SerialPrint("I", F("NextionUpdate"), "connecting to " + url);
HTTPClient http;
#ifdef ESP8266
SerialPrint("I", F("NextionUpdate"), "Update impossible esp8266: Change boards to esp32 :)");
return;
#else
if (!http.begin(url)) // пингуем файл
SerialPrint("I", F("NextionUpdate"), "connection failed ");
#endif
SerialPrint("I", F("NextionUpdate"), "Requesting file: OK");
int code = http.GET();
// Update the nextion display
if (code == 200)
flashNextion(http);
else
SerialPrint("I", F("NextionUpdate"), "HTTP error: " + (String)http.errorToString(code).c_str());
//http.end();
SerialPrint("I", F("NextionUpdate"), "Closing connection ");
}
}
void flashNextion(HTTPClient &http)
{
int contentLength = http.getSize();
SerialPrint("I", F("NextionUpdate"), "File received. Update Nextion... ");
bool result;
ESPNexUpload nexUp(_speed, _line, _rx, _tx);
nexUp.setUpdateProgressCallback([]()
{ SerialPrint("I", F("NextionUpdate"), "... "); });
ESPNexUpload nexUp(_line, _speed, (gpio_num_t)_tx, (gpio_num_t)_rx);
// nexUp.setUpdateProgressCallback([]()
// { SerialPrint("I", F("NextionUpdate"), "... "); });
result = nexUp.prepareUpload(contentLength);
result = nexUp.prepareUpload(contentLength, _oldProt);
if (!result)
{
SerialPrint("I", F("NextionUpdate"), "Error: " + (String)nexUp.statusMessage);
SerialPrint("I", F("NextionUpdate"), "Error Connect in prepare upload");
}
else
{
updated = true;
SerialPrint("I", F("NextionUpdate"), "Start upload. File size is: " + (String)contentLength);
result = nexUp.upload(*http.getStreamPtr());
if (result)
{
updated = true;
SerialPrint("I", F("NextionUpdate"), "Succesfully updated Nextion! ");
if (tlgrmItem)
tlgrmItem->sendTelegramMsg(false, String("NextionUpdate: Succesfully updated Nextion!"));
}
else
{
SerialPrint("I", F("NextionUpdate"), "Error updating Nextion: " + (String)nexUp.statusMessage);
SerialPrint("I", F("NextionUpdate"), "Error updating Nextion!");
if (tlgrmItem)
tlgrmItem->sendTelegramMsg(false, String("NextionUpdate: Error updating Nextion!"));
}
nexUp.end();
#ifdef ESP8266
_myUART->begin(_speed);
#endif
#ifdef ESP32
if (_line >= 0)
{
//_myUART = new HardwareSerial(_line);
((HardwareSerial *)_myUART)->updateBaudRate(_speed);
}
else
{
//_myUART = new SoftwareSerial(_rx, _tx);
((SoftwareSerial *)_myUART)->begin(_speed);
}
#endif
updated = false;
}
}
//---------------------NEXTION-UPDATE---END------------------------
//---------------------NEXTION-UPDATE---END------------------------
~Nextion(){};
};
void *getAPI_Nextion(String subtype, String param)

View File

@@ -17,7 +17,8 @@
"rx": 16,
"line": 2,
"speed": 9600,
"uploadTelegram": 1
"uploadTelegram": 1,
"oldProt_v11": 0
}
],
"about": {
@@ -28,8 +29,8 @@
"moduleName": "Nextion",
"moduleVersion": "2.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
"esp32_4mb": 152,
"esp8266_4mb": 152
},
"title": "Nextion",
"moduleDesc": "загрузка прошивки в дисплей Nextion. Команда для запуска обновления дисплея: Nextion.Update(); ",
@@ -41,6 +42,7 @@
"host": "Сервер обновления. Можно использовать LiveServer из VisualCode, указывать ip адрес",
"url": "файл прошивки экрана, указывать с расширением, например nextion.tft или iotm/test.tft",
"uploadTelegram": "1 - разрешает прошивать экран через модуль Telegram_v2",
"oldProt_v11": "0 - По умолчанию используется более быстрый протокол версии 1.2 (не официальный), 1 - Использовать старый протокол версии 1.1 для прошивки экрана.",
"btn-uploadServer": "Кнопка загрузки прошивки с сервера LiveServer или другого по ip"
},
"funcInfo": [

View File

@@ -42,8 +42,7 @@ public:
HTTPClient http;
#if defined ESP8266
WiFiClient client;
if (!http.begin(client, _host, 80, _url))
if (!http.begin(_host, 80, _url))
{
// Serial.println("connection failed");
SerialPrint("I", F("NextionUpdate"), "connection failed ");
@@ -119,13 +118,7 @@ public:
int contentLength = http.getSize();
SerialPrint("I", F("NextionUpdate"), "File received. Update Nextion... ");
bool result;
#ifdef ESP8266
ESPNexUpload nextion(115200, -1, _NEXT_RX, _NEXT_TX);
#elif defined(esp32c3m_4mb) || defined(esp32s2_4mb)
ESPNexUpload nextion(115200, 1, _NEXT_RX, _NEXT_TX);
#else
ESPNexUpload nextion(115200, 2, _NEXT_RX, _NEXT_TX);
#endif
ESPNexUpload nextion(115200, _NEXT_RX, _NEXT_TX);
nextion.setUpdateProgressCallback([]()
{ SerialPrint("I", F("NextionUpdate"), "... "); });

View File

@@ -75,7 +75,7 @@
}
]
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [
"https://github.com/stblassitude/Adafruit_SSD1306_Wemos_OLED",

View File

@@ -45,7 +45,7 @@ class Smi2_m : public IoTItem {
// Пакет,SLAVE адрес,функция модбус,адрес регистра,количесво запрашиваемых регистров,локальный адрес регистра.
// Пакет,SLAVE адрес,функция модбус,адрес регистра,данные,локальный адрес регистра.
smi->modbus_construct(&packets[PACKET1], 1, PRESET_MULTIPLE_REGISTERS, 4200, 1, 0);
smi->modbus_configure(&Serial, _baud, SERIAL_8N1, _rx, _tx, _pin, packets, TOTAL_NO_OF_PACKETS, regs);
smi->modbus_configure((HardwareSerial*)&Serial, _baud, SERIAL_8N1, _rx, _tx, _pin, &packets[PACKET1], (uint)TOTAL_NO_OF_PACKETS, &regs[0]);
jsonRead(parameters, "id2show", _show);
}

View File

@@ -50,7 +50,7 @@
}
]
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": []
}

View File

@@ -38,6 +38,7 @@
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -7,12 +7,13 @@ namespace _Broker
{
#define DEF_PORT 1883
bool _global_debug = false;
// MqttBroker broker(1883);
class myPicoMQTT : public PicoMQTT::Server
{
private:
bool _debug;
//bool _debug;
String _user;
String _pass;
@@ -27,15 +28,15 @@ namespace _Broker
_pass = pass;
}
void setDebug(bool debug)
/* void setDebug(bool debug)
{
_debug = debug;
}
*/
protected:
void on_connected(const char *client_id)
{
if (_debug)
if (_Broker::_global_debug)
{
Serial.print("[BrokerMQTT], Client connected: ");
Serial.println(client_id);
@@ -43,7 +44,7 @@ namespace _Broker
}
void on_disconnected(const char *client_id)
{
if (_debug)
if (_Broker::_global_debug)
{
// SerialPrint("i", "BrokerMQTT", "Client disconnected: " + client_id);
@@ -53,7 +54,7 @@ namespace _Broker
}
void on_subscribe(const char *client_id, const char *topic)
{
if (_debug)
if (_Broker::_global_debug)
{
// SerialPrint("i", "BrokerMQTT", "Client " + client_id + ", subscribe: " + topic);
@@ -65,7 +66,7 @@ namespace _Broker
}
void on_unsubscribe(const char *client_id, const char *topic)
{
if (_debug)
if (_Broker::_global_debug)
{
// SerialPrint("i", "BrokerMQTT", "Client " + client_id + ", unsubscribe: " + topic);
@@ -120,6 +121,8 @@ namespace _Broker
clientMqtt->loop();
if (picoMqtt)
picoMqtt->loop();
if (!clientMqtt && !picoMqtt)
vTaskDelete(NULL);
// picoMqtt.loop();
// vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(5));
}
@@ -152,19 +155,6 @@ namespace _Broker
jsonRead(parameters, "srvUser", _srvUser);
jsonRead(parameters, "srvPass", _srvPass);
jsonRead(parameters, "srvPort", _srvPort);
if (_brige)
{
clientMqtt = new PicoMQTT::Client(_server.c_str(), _srvPort, nullptr, _srvUser.c_str(), _srvPass.c_str());
if (_debug)
{
SerialPrint("i", F("BrigeMQTT"), "Bridge mode : ON");
SerialPrint("i", F("BrigeMQTT"), "Bridge server: " + _server);
SerialPrint("i", F("BrigeMQTT"), "Bridge port: " + String(_srvPort));
SerialPrint("i", F("BrigeMQTT"), "Bridge user: " + _srvUser);
SerialPrint("i", F("BrigeMQTT"), "Bridge pass: " + _srvPass);
}
}
}
void doByInterval()
@@ -176,13 +166,33 @@ namespace _Broker
_port = DEF_PORT;
picoMqtt = new myPicoMQTT(_port);
picoMqtt->begin();
picoMqtt->setDebug(_debug);
//picoMqtt->setDebug(_debug);
_global_debug = _debug;
picoMqtt->setAuth(_user, _pass);
if (_brige)
{
clientMqtt = new PicoMQTT::Client(_server.c_str(), _srvPort, chipId.c_str(), _srvUser.c_str(), _srvPass.c_str());
clientMqtt->begin();
if (_debug)
{
SerialPrint("i", F("BrigeMQTT"), "Bridge mode : ON");
SerialPrint("i", F("BrigeMQTT"), "Bridge server: " + _server);
SerialPrint("i", F("BrigeMQTT"), "Bridge port: " + String(_srvPort));
SerialPrint("i", F("BrigeMQTT"), "Bridge user: " + _srvUser);
SerialPrint("i", F("BrigeMQTT"), "Bridge pass: " + _srvPass);
}
}
if (_brige && picoMqtt && clientMqtt)
{
picoMqtt->subscribe("#", [](const char *topic, const char *message)
{ clientMqtt->publish(topic, message);
SerialPrint("i", F("BrigeMQTT"), "client publish, topic: " + String(topic) + " msg: " + String(message) ); });
if (_Broker::_global_debug)
SerialPrint("i", F("BrigeMQTT"), "Client publish, topic: " + String(topic) + " msg: " + String(message) ); });
clientMqtt->subscribe("#", [](const char *topic, const char *message)
{ picoMqtt->publish(topic, message);
if (_Broker::_global_debug)
SerialPrint("i", F("BrigeMQTT"), "Server publish, topic: " + String(topic) + " msg: " + String(message) ); });
}
// picoMqtt.begin();
xTaskCreatePinnedToCore(
@@ -205,9 +215,11 @@ namespace _Broker
~BrokerMQTT()
{
vTaskDelete(brokerTask);
delete picoMqtt;
delete clientMqtt;
//vTaskDelete(brokerTask);
if (picoMqtt)
delete picoMqtt;
if (clientMqtt)
delete clientMqtt;
}
};
}

View File

@@ -46,6 +46,7 @@
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -50,6 +50,7 @@
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -101,7 +101,7 @@
}
]
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [],
"esp82*": []

View File

@@ -0,0 +1,182 @@
#pragma once
#include <Arduino.h>
#define comm_RebootAdapter 2u
#define comm_LockOutReset 3u
struct BoilerInfo
{
uint8_t adapterType; // тип адаптера. 000 - Opentherm 001 - eBus 010 - Navien
uint8_t boilerStatus; // состояние связи с котлом 0 - нет ответа от котла на последнюю команду 1 - есть ответ от котла на последнюю команду
uint8_t rebootStatus; // Код последней перезагрузки адаптера. 0...255 - код
uint8_t adapterHardVer; // Аппаратная версия адаптера. 0...255 - номер версии
uint8_t adapterSoftVer; // u8 Программная версия адаптера. 0...255 - номер версии
uint16_t boilerMemberCode; // 0x0021 R u16 Код производителя котла. Зависит от марки и модели котла. 0...65535 - код производителя
uint16_t boilerModelCode; // 0x0022 R u16 Код модели котла. Зависит от марки и модели котла. 0...65535 - код модели
};
struct BoilerStatus
{
uint8_t burnStatus; // бит 0 - текущее состояние горелки 0 - отключена 1 - включена
uint8_t CHStatus; // бит 1 - текущее состояние отопления 0 - отключено 1 - включено
uint8_t DHWStatus; // бит 2 - текущее состояние ГВС 0 - отключено 1 - включено
};
// Флаги ошибок (только для котлов с интерфейсом OpenTherm)
enum FlagErrorOT //: uitn8_t
{
er_SecviceReq, // 0: Необходимо обслуживание
er_LockOut, // 1: Котел заблокирован
er_LowWater, // 2: Низкое давление в отопительном контуре
er_FlameFault, // 3: Ошибка розжига
er_AirPresFault, // 4: Низкое давление воздуха
er_OverTem // 5: Перегрев теплоносителя в контуре
};
////////////////// Данные регистров второй версии адаптера котла
//////////////////// Регистры для чтения
enum ReadDataEctoControl //: uitn16_t
{
ecR_AdapterInfo = 0x0010, // 0x0010 R bitfields бит 2...0 - тип адаптера. 000 - Opentherm 001 - eBus 010 - Navien
// бит 3 - состояние связи с котлом 0 - нет ответа от котла на последнюю команду 1 - есть ответ от котла на последнюю команду
// u8 Код последней перезагрузки адаптера. 0...255 - код
ecR_AdaperVersion = 0x0011, // 0x0011 R u8 Аппаратная версия адаптера. 0...255 - номер версии
// u8 Программная версия адаптера. 0...255 - номер версии
ecR_Time = 0x0012, // 0x0012 - 0x0013 R u32 Время работы адаптера после перезагрузки 0...4294967295 - время в секундах
ecR_MinSetCH = 0x0014, // 0x0014 R u8 Нижний предел уставки теплоносителя. 0...100 - температура уставки в град. С
ecR_MaxSetCH = 0x0015, // 0x0015 R u8 Верхний предел уставки теплоносителя 0...100 - температура уставки в град. С
ecR_MinSetDHW = 0x0016, // 0x0016 R u8 Нижний предел уставки ГВС 0...100 - температура уставки в град. С
ecR_MaxSetDHW = 0x0017, // 0x0017 R u8 Верхний предел уставки ГВС 0...100 - температура уставки в град. С
ecR_TempCH = 0x0018, // 0x0018 R i16 Текущая температура теплоносителя -100...100 - температура в 0.1 гр. С
ecR_TempDHW = 0x0019, // 0x0019 R u16 Текущая температура ГВС 0...100 - температура в 0.1 гр. С
ecR_Pressure = 0x001A, // 0x001A R u8 Текущее Давление в контуре 0...50 - давление в (0,1бар)
ecR_FlowRate = 0x001B, // 0x001B R u8? Текущий расход ГВС 1...255 - расход в (0,1 л/мин)
ecR_ModLevel = 0x001C, // 0x001C R u8? Текущая модуляция горелки 0xFF - не определено 0...100 - модуляция в (%)
ecR_BoilerStatus = 0x001D, // 0x001D R bitfields бит 0 - текущее состояние горелки 0 - отключена 1 - включена
// бит 1 - текущее состояние отопления 0 - отключено 1 - включено
// бит 2 - текущее состояние ГВС 0 - отключено 1 - включено
ecR_CodeError = 0x001E, // 0x001E R u16 Код ошибки котла (основной). Зависит от марки и модели котла. 0...65535 - код ошибки
ecR_CodeErrorExt = 0x001F, // 0x001F R u16 Код ошибки котла (дополнительный). Зависит от марки и модели котла. 0...65535 - код ошибки
ecR_TempOutside = 0x0020, // 0x0020 R s8 Температура уличного датчика котла (при егоналичии). -65…+100 температура в градусах С
ecR_MemberCode = 0x0021, // 0x0021 R u16 Код производителя котла. Зависит от марки и модели котла. 0...65535 - код производителя
ecR_ModelCode = 0x0022, // 0x0022 R u16 Код модели котла. Зависит от марки и модели котла. 0...65535 - код модели
ecR_FlagErrorOT = 0x0023 // 0x0023 R s8 Флаги ошибок (только для котлов с интерфейсом OpenTherm)
// 0: Необходимо обслуживание
// 1: Котел заблокирован
// 2: Низкое давление в отопительном контуре
// 3: Ошибка розжига
// 4: Низкое давление воздуха
// 5: Перегрев теплоносителя в контуре
};
////////////////////////////// WRITE /////////////
enum WriteDataEctoControl //: uitn16_t
{
ecW_SetTypeConnect = 0x0030, // 0x0030 W u8 Тип внешних подключений (будет сохранено в постоянной памяти адаптера)
// 0 - адаптер подключен к котлу
// 1 - котел подключен к внешнему устройству (панель или перемычка)
ecW_TSetCH = 0x0031, // 0x0031 W int16 Уставка теплоносителя (будет сохранено в постоянной памяти адаптера).
// Будет передана котлу при старте адаптера, пока главным устройством не
// были записаны регистры уставки температуры.
// 0...1000 - температура уставки в десятых долях градуса С (например, для
// установки 45С нужно записать число 450). Во многих котлах необходимо до
// подключения адаптера необходимо поднять температуру теплоносителя
// отопления (нажимая “+” на панели котла) для согласования диапазона
// температуры теплоносителя, в противном случае температура теплоносителя
// может не достигнуть требуемого значения.
ecW_TSetCHFaultConn = 0x0032, // 0x0032 W int16 Уставка теплоносителя в аварийном режиме(будет сохранено в постоянной памяти адаптера).
// Будет передана котлу в случае
// отсутствия связи с главным управляющим устройством.
// 0...1000 - температура уставки в десятых долях град. С (например,
// для установки 45С нужно записать число 450)
ecW_TSetMinCH = 0x0033, // 0x0033 W u8 Нижний предел уставки теплоносителя 0...100 - температура уставки в град. С
// Не все котлы поддерживают этот параметр. Как правило, этот
// предел не должен быть ниже аналогичного предела,
// установленного в настройках котла.
ecW_TSetMaxCH = 0x0034, // 0x0034 W u8 Верхний предел уставки теплоносителя 0...100 - температура уставки в град. С
// Не все котлы поддерживают этот параметр. Как правило, этот
// предел не должен быть выше аналогичного предела,
// установленного в настройках котла.
ecW_TSetMinDHW = 0x0035, // 0x0035 W u8 Нижний предел уставки ГВС 0...100 - температура уставки в град. С
// Не все котлы поддерживают этот параметр. Как правило, этот
// предел не должен быть ниже аналогичного предела,
// установленного в настройках котла.
ecW_TSetMaxDHW = 0x0036, // 0x0036 W u8 Верхний предел уставки ГВС 0...100 - температура уставки в град. С
// Не все котлы поддерживают этот параметр. Как правило, этот
// предел не должен быть выше аналогичного предела,
// установленного в настройках котла.
ecW_TSetDHW = 0x0037, // 0x0037 W u8 Уставка ГВС (EPROM) 0...100 - температура уставки в град. С
// Для большинства котлов эта уставка должна находиться ниже
// предела, установленного в меню самого котла, иначе она может быть
// проигнорирована котлом. Для некоторых котлов стоит устанавливать
// этот параметр равным верхнему пределу уставки ГВС.
ecW_SetMaxModLevel = 0x0038, // 0x0038 W u8 Уставка максимальной модуляции горелки (будет сохранено в постоянной памяти адаптера) 0...100 - уровень модуляции в процентах.
// Данный параметр поддерживается не всеми котлами. Для
// электрических трехфазных котлов возможно задать только 3
// уровня модуляции (в диапазоне 0…100%).
ecW_SetStatusBoiler = 0x0039, // 0x0039 W bitfields бит 0 - режим контура отопления (будет сохранено в постоянной памяти адаптера) 0 - отключен 1 - включен
// бит 1 - режим контура ГВС 0 - отключен 1 - включен
// бит 2 - “второй контур”, используется только некоторыми котлами с
// интерфейсом OpenTherm и может отвечать за активацию бойлера
// косвенного нагрева или встроенной функции ГВС. 0 - отключен 1 - включен
ecW_Command = 0x0080 // 0x0080 W uint16 команда
// 0 - нет команды
// 1 - CH water filling (зарезервировано)
// 2 - перезагрузка адаптера
// 3 - сброс ошибок котла
// 4..65525 - зарезервировано
// При записи любой команды, кроме «нет команды», сразу же меняется
// состояние регистра «Ответ на команду» - становится 2 идет
// обработка команжы
};
///////////////////////////////////////// Регистры состояния:
/*
0x0040…0x006F R i16 состояние данных в регистре ($addr - 0x30)
-2 - ошибка чтения/записи в котел
-1 - регистр не поддерживается
0- для регистра на чтение означает, что данные из котла прочитаны и
валидны. Для регистра на запись означает, что данные успешно приняты
котлом.
1- не инициализирован.
если регистр R: чтение соответствующих данные из котла не
приводилось,
если регистр W: адаптеру не было задано значение для записи
соответствующего значения в котле
*/
/*
0x0081 R int16 Ответ на команду
-32768..-6 - зарезервировано
-5 ошибка выполнения команды
-4 неподдерживаемая котлом команда
-3 не поддерживаемый котлом идентификатор устройства
-2 не поддерживается данный адаптером
-1 не получен ответ за отведенное врекмя
0 команда выполнена успешно
1 не было команды (значение по умолчанию)
2 идет обработка команды (обмен данными)
3..32767 - зарезервировано
Если команда после перезагрузки адаптера не давалась, регистр
читается как 1 - не было команды.
*/

View File

@@ -0,0 +1,817 @@
#include "Global.h"
#include "classes/IoTItem.h"
#include <map>
#include <HardwareSerial.h>
#include "ModbusEC.h"
#include "AdapterCommon.h"
// #include "Stream.h"
#include <vector>
// class ModbusUart;
Stream *_modbusUART = nullptr;
#define UART_LINE 2
uint8_t _DIR_PIN = 0;
// Modbus stuff
// Данные Modbus по умолчанию
#define MODBUS_RX_PIN 18 // Rx pin
#define MODBUS_TX_PIN 19 // Tx pin
#define MODBUS_SERIAL_BAUD 9600 // Baud rate for esp32 and max485 communication
void modbusPreTransmission()
{
// delay(500);
if (_DIR_PIN)
digitalWrite(_DIR_PIN, HIGH);
}
// Pin 4 made low for Modbus receive mode
// Контакт 4 установлен на низком уровне для режима приема Modbus
void modbusPostTransmission()
{
if (_DIR_PIN)
digitalWrite(_DIR_PIN, LOW);
// delay(500);
}
// ModbusMaster node;
// RsEctoControl *rsEC;
class EctoControlAdapter : public IoTItem
{
private:
int _rx = MODBUS_RX_PIN; // адреса прочитаем с веба
int _tx = MODBUS_TX_PIN;
int _baud = MODBUS_SERIAL_BAUD;
String _prot = "SERIAL_8N1";
int protocol = SERIAL_8N1;
uint8_t _addr = 0xF0; // Адрес слейва от 1 до 247
uint8_t _type = 0x14; // Тип устройства: 0x14 адаптер OpenTherm (вторая версия); 0x11 адаптер OpenTherm (первая версия, снята с производства)
bool _debugLevel; // Дебаг
ModbusMaster node;
uint8_t _debug;
// Stream *_modbusUART;
BoilerInfo info;
BoilerStatus status;
uint16_t code;
uint16_t codeExt;
uint8_t flagErr;
float flow;
float maxSetCH;
float maxSetDHW;
float minSetCH;
float minSetDHW;
float modLevel;
float press;
float tCH;
float tDHW;
float tOut;
bool enableCH;
bool enableDHW;
bool enableCH2;
bool _isNetworkActive;
bool _mqttIsConnect;
public:
EctoControlAdapter(String parameters) : IoTItem(parameters)
{
_DIR_PIN = 0;
_addr = jsonReadInt(parameters, "addr"); // адреса slave прочитаем с веба
_rx = jsonReadInt(parameters, "RX"); // прочитаем с веба
_tx = jsonReadInt(parameters, "TX");
_DIR_PIN = jsonReadInt(parameters, "DIR_PIN");
_baud = jsonReadInt(parameters, "baud");
_prot = jsonReadStr(parameters, "protocol");
jsonRead(parameters, "debug", _debugLevel);
if (_prot == "SERIAL_8N1")
{
protocol = SERIAL_8N1;
}
else if (_prot == "SERIAL_8N2")
{
protocol = SERIAL_8N2;
}
// Serial2.begin(baud-rate, protocol, RX pin, TX pin);
_modbusUART = new HardwareSerial(UART_LINE);
if (_debugLevel > 2)
{
SerialPrint("I", "EctoControlAdapter", "baud: " + String(_baud) + ", protocol: " + String(protocol, HEX) + ", RX: " + String(_rx) + ", TX: " + String(_tx));
}
((HardwareSerial *)_modbusUART)->begin(_baud, protocol, _rx, _tx); // выбираем тип протокола, скорость и все пины с веба
((HardwareSerial *)_modbusUART)->setTimeout(200);
//_modbusUART = &serial;
node.begin(_addr, _modbusUART);
node.preTransmission(modbusPreTransmission);
node.postTransmission(modbusPostTransmission);
if (_DIR_PIN)
{
_DIR_PIN = _DIR_PIN;
pinMode(_DIR_PIN, OUTPUT);
digitalWrite(_DIR_PIN, LOW);
}
// 0x14 адаптер OpenTherm (вторая версия)
// 0x15 адаптер eBus
// 0x16 адаптер Navien
if (_addr > 0)
{
uint16_t type;
readFunctionModBus(0x0003, type);
type = type >> 8;
if (0x14 != type && 0x15 != type && 0x16 != type)
{
SerialPrint("E", "EctoControlAdapter", "Не подходящее устройство, type: " + String(type, HEX));
}
}
else if (_addr == 0)
{ // если адреса нет, то шлем широковещательный запрос адреса
uint8_t addr = node.readAddresEctoControl();
SerialPrint("I", "EctoControlAdapter", "readAddresEctoControl, addr: " + String(addr, HEX) + " - Enter to configuration");
}
getModelVersion();
getBoilerInfo();
getBoilerStatus();
}
void doByInterval()
{
// readBoilerInfo();
getBoilerStatus();
getCodeError();
getCodeErrorExt();
if (info.adapterType == 0)
getFlagErrorOT();
// getFlowRate();
// getMaxSetCH();
// getMaxSetDHW();
// getMinSetCH();
// getMinSetDHW();
getModLevel();
getPressure();
getTempCH();
getTempDHW();
getTempOutside();
}
void loop()
{
// для новых версий IoTManager
IoTItem::loop();
}
IoTValue execute(String command, std::vector<IoTValue> &param)
{
if (command == "getModelVersion")
{
getModelVersion();
}
if (command == "getModelVersion")
{
getModelVersion();
}
if (command == "getBoilerInfo")
{
getBoilerInfo();
}
if (command == "getBoilerStatus")
{
getBoilerStatus();
}
if (command == "getCodeError")
{
getCodeError();
}
if (command == "getCodeErrorExt")
{
getCodeErrorExt();
}
if (command == "getFlagErrorOT")
{
getFlagErrorOT();
}
if (command == "getFlowRate")
{
getFlowRate();
}
if (command == "getMaxSetCH")
{
getMaxSetCH();
}
if (command == "getMaxSetDHW")
{
getMaxSetDHW();
}
if (command == "getMinSetCH")
{
getMinSetCH();
}
if (command == "getMinSetDHW")
{
getMinSetDHW();
}
if (command == "getModLevel")
{
getModLevel();
}
if (command == "getPressure")
{
getPressure();
}
if (command == "getTempCH")
{
getTempCH();
}
if (command == "getTempDHW")
{
getTempDHW();
}
if (command == "getTempOutside")
{
getTempOutside();
}
if (command == "setTypeConnect")
{
setTypeConnect(param[0].valD);
}
if (command == "setTCH")
{
setTCH(param[0].valD);
}
if (command == "setTCHFaultConn")
{
setTCHFaultConn(param[0].valD);
}
if (command == "setMinCH")
{
setMinCH(param[0].valD);
}
if (command == "setMaxCH")
{
setMaxCH(param[0].valD);
}
if (command == "setMinDHW")
{
setMinDHW(param[0].valD);
}
if (command == "setMaxDHW")
{
setMaxDHW(param[0].valD);
}
if (command == "setTDHW")
{
setTDHW(param[0].valD);
}
if (command == "setMaxModLevel")
{
setMaxModLevel(param[0].valD);
}
if (command == "setStatusCH")
{
setStatusCH((bool)param[0].valD);
}
if (command == "setStatusDHW")
{
setStatusDHW((bool)param[0].valD);
}
if (command == "setStatusCH2")
{
setStatusCH2((bool)param[0].valD);
}
if (command == "lockOutReset")
{
lockOutReset();
}
if (command == "rebootAdapter")
{
rebootAdapter();
}
return {};
}
void publishData(String widget, String status)
{
IoTItem *tmp = findIoTItem(widget);
if (tmp)
tmp->setValue(status, true);
else
{
if (_debugLevel > 0)
SerialPrint("i", "NEW", widget + " = " + status);
}
}
static void sendTelegramm(String msg)
{
if (tlgrmItem)
tlgrmItem->sendTelegramMsg(false, msg);
}
~EctoControlAdapter() {
};
bool writeFunctionModBus(const uint16_t &reg, uint16_t &data)
{
// set word 0 of TX buffer to least-significant word of counter (bits 15..0)
//node.setTransmitBuffer(1, lowWord(data));
// set word 1 of TX buffer to most-significant word of counter (bits 31..16)
node.setTransmitBuffer(0, data);
// slave: write TX buffer to (2) 16-bit registers starting at register 0
uint8_t result = node.writeMultipleRegisters(reg, 1);
node.clearTransmitBuffer();
if (_debug > 2)
{
SerialPrint("I", "EctoControlAdapter", "writeSingleRegister, addr: " + String((uint8_t)_addr, HEX) + ", reg: 0x" + String(reg, HEX) + ", state: " + String(data) + " = result: " + String(result, HEX));
}
if (result == 0)
return true;
else
return false;
}
bool readFunctionModBus(const uint16_t &reg, uint16_t &reading)
{
// float retValue = 0;
if (_modbusUART)
{
// if (!addr)
// addr = _addr;
node.begin(_addr, _modbusUART);
uint8_t result;
// uint32_t reading;
if (reg == 0x0000)
result = node.readHoldingRegisters(reg, 4);
else
result = node.readHoldingRegisters(reg, 1);
if (_debug > 2)
SerialPrint("I", "EctoControlAdapter", "readHoldingRegisters, addr: " + String(_addr, HEX) + ", reg: 0x" + String(reg, HEX) + " = result: " + String(result, HEX));
// break;
if (result == node.ku8MBSuccess)
{
if (reg == 0x0000)
{
reading = node.getResponseBuffer(0x03);
reading = (uint16_t)((uint8_t)(reading) >> 8);
SerialPrint("I", "EctoControlAdapter", "read info, addr: " + String(_addr, HEX) + ", type: " + String(reading, HEX));
}
else
{
reading = node.getResponseBuffer(0x00);
if (_debug > 2)
SerialPrint("I", "EctoControlAdapter", "Success, Received data, register: " + String(reg) + " = " + String(reading, HEX));
}
node.clearResponseBuffer();
}
else
{
if (_debug > 2)
SerialPrint("E", "EctoControlAdapter", "Failed, Response Code: " + String(result, HEX));
return false;
}
if (reading != 0x7FFF)
return true;
else
return false;
}
return false;
}
bool getModelVersion()
{
uint16_t reqData;
bool ret;
ret = readFunctionModBus(ReadDataEctoControl::ecR_MemberCode, info.boilerMemberCode);
ret = readFunctionModBus(ReadDataEctoControl::ecR_ModelCode, info.boilerModelCode);
ret = readFunctionModBus(ReadDataEctoControl::ecR_AdaperVersion, reqData);
info.adapterHardVer = highByte(reqData);
info.adapterSoftVer = lowByte(reqData);
return ret;
}
bool getBoilerInfo()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_AdapterInfo, reqData);
info.adapterType = highByte(reqData) & 0xF8;
info.boilerStatus = (highByte(reqData) >> 3) & 1u;
info.rebootStatus = lowByte(reqData);
if (ret)
{
publishData("adapterType", String(info.adapterType));
publishData("boilerStatus", String(info.boilerStatus));
publishData("rebootStatus", String(info.rebootStatus));
}
return ret;
}
bool getBoilerStatus()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_BoilerStatus, reqData);
status.burnStatus = (lowByte(reqData) >> 0) & 1u;
status.CHStatus = (lowByte(reqData) >> 1) & 1u;
status.DHWStatus = (lowByte(reqData) >> 2) & 1u;
if (ret)
{
publishData("burnStatus", String(status.burnStatus));
publishData("CHStatus", String(status.CHStatus));
publishData("DHWStatus", String(status.DHWStatus));
}
return ret;
}
bool getCodeError()
{
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_CodeError, code);
if (ret)
{
publishData("codeError", String(code));
if (code)
sendTelegramm("EctoControlAdapter: код ошибки: " + String((int)code));
}
return ret;
}
bool getCodeErrorExt()
{
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_CodeErrorExt, codeExt);
if (ret)
{
publishData("codeErrorExt", String(codeExt));
if (codeExt)
sendTelegramm("EctoControlAdapter: код ошибки: " + String((int)codeExt));
}
return ret;
}
bool getFlagErrorOT()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_FlagErrorOT, reqData);
flagErr = lowByte(reqData);
if (ret)
{
publishData("flagErr", String(flagErr));
switch (flagErr)
{
case 0:
sendTelegramm("EctoControlAdapter: Необходимо обслуживание!");
break;
case 1:
sendTelegramm("EctoControlAdapter: Котёл заблокирован!");
break;
case 2:
sendTelegramm("EctoControlAdapter: Низкое давление в отопительном контуре!");
break;
case 3:
sendTelegramm("EctoControlAdapter: Ошибка розжига!");
break;
case 4:
sendTelegramm("EctoControlAdapter: Низкое давления воздуха!");
break;
case 5:
sendTelegramm("EctoControlAdapter: Перегрев теплоносителя в контуре!");
break;
default:
break;
}
}
return ret;
}
bool getFlowRate()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_FlowRate, reqData);
flow = lowByte(reqData) / 10.f;
if (ret)
publishData("flowRate", String(flow));
return ret;
}
bool getMaxSetCH()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_MaxSetCH, reqData);
maxSetCH = (float)lowByte(reqData);
if (ret)
publishData("maxSetCH", String(maxSetCH));
return ret;
}
bool getMaxSetDHW()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_MaxSetDHW, reqData);
maxSetDHW = (float)lowByte(reqData);
if (ret)
publishData("maxSetDHW", String(maxSetDHW));
return ret;
}
bool getMinSetCH()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_MinSetCH, reqData);
minSetCH = (float)lowByte(reqData);
if (ret)
publishData("minSetCH", String(minSetCH));
return ret;
}
bool getMinSetDHW()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_MinSetDHW, reqData);
minSetDHW = (float)lowByte(reqData);
if (ret)
publishData("minSetDHW", String(minSetDHW));
return ret;
}
bool getModLevel()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_ModLevel, reqData);
modLevel = (float)lowByte(reqData);
if (ret)
publishData("modLevel", String(modLevel));
return ret;
}
bool getPressure()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_Pressure, reqData);
press = lowByte(reqData) / 10.f;
if (ret)
publishData("press", String(press));
return ret;
}
bool getTempCH()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_TempCH, reqData);
tCH = reqData / 10.f;
if (ret)
publishData("tCH", String(tCH));
return ret;
}
bool getTempDHW()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_TempDHW, reqData);
tDHW = reqData / 10.f;
if (ret)
publishData("tDHW", String(tDHW));
return ret;
}
bool getTempOutside()
{
uint16_t reqData;
bool ret = readFunctionModBus(ReadDataEctoControl::ecR_TempOutside, reqData);
tOut = (float)lowByte(reqData);
if (ret)
publishData("tOut", String(tOut));
return ret;
}
bool setTypeConnect(float &data)
{
bool ret = false;
uint16_t data16 = data;
if (writeFunctionModBus(ecW_SetTypeConnect, data16))
{
// TODO запросить результат записи у адаптера
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setTypeConnect");
}
return ret;
}
bool setTCH(float &data)
{
bool ret = false;
uint16_t d16 = data * 10;
if (writeFunctionModBus(ecW_TSetCH, d16))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setTCH");
}
return ret;
}
bool setTCHFaultConn(float &data)
{
bool ret = false;
uint16_t d16 = data * 10;
if (writeFunctionModBus(ecW_TSetCHFaultConn, d16))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setTCHFaultConn");
}
return ret;
}
bool setMinCH(float &data)
{
bool ret = false;
uint16_t data16 = data;
if (writeFunctionModBus(ecW_TSetMinCH, data16))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setMinCH");
}
return ret;
}
bool setMaxCH(float &data)
{
bool ret = false;
uint16_t data16 = data;
if (writeFunctionModBus(ecW_TSetMaxCH, data16))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setMaxCH");
}
return ret;
}
bool setMinDHW(float &data)
{
bool ret = false;
uint16_t data16 = data;
if (writeFunctionModBus(ecW_TSetMinDHW, data16))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setMinDHW");
}
return ret;
}
bool setMaxDHW(float &data)
{
bool ret = false;
uint16_t data16 = data;
if (writeFunctionModBus(ecW_TSetMaxDHW, data16))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setMaxDHW");
}
return ret;
}
bool setTDHW(float &data)
{
bool ret = false;
uint16_t data16 = data;
if (writeFunctionModBus(ecW_TSetDHW, data16))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setTDHW");
}
return ret;
}
bool setMaxModLevel(float &data)
{
bool ret = false;
uint16_t data16 = data;
if (writeFunctionModBus(ecW_SetMaxModLevel, data16))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setMaxModLevel");
}
return ret;
}
bool setStatusCH(bool data)
{
bool ret = false;
enableCH = data;
uint16_t stat = enableCH | (enableDHW << 1) | (enableCH2 << 2);
if (writeFunctionModBus(ecW_SetStatusBoiler, stat))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setStatusCH");
}
return ret;
}
bool setStatusDHW(bool data)
{
bool ret = false;
enableDHW = data;
uint16_t stat = enableCH | (enableDHW << 1) | (enableCH2 << 2);
if (writeFunctionModBus(ecW_SetStatusBoiler, stat))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setStatusDHW");
}
return ret;
}
bool setStatusCH2(bool data)
{
bool ret = false;
enableCH2 = data;
uint16_t stat = enableCH | (enableDHW << 1) | (enableCH2 << 2);
if (writeFunctionModBus(ecW_SetStatusBoiler, stat))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, setStatusCH2");
}
return ret;
}
bool lockOutReset()
{
bool ret = false;
uint16_t d16 = comm_LockOutReset;
if (writeFunctionModBus(ecW_Command, d16))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, lockOutReset");
}
return ret;
}
bool rebootAdapter()
{
bool ret = false;
uint16_t d16 = comm_RebootAdapter;
if (writeFunctionModBus(ecW_Command, d16))
{
ret = true;
}
else
{
if (_debug > 1)
SerialPrint("E", "EctoControlAdapter", "Failed, rebootAdapter");
}
return ret;
}
};
void *getAPI_EctoControlAdapter(String subtype, String param)
{
if (subtype == F("ecAdapter"))
{
return new EctoControlAdapter(param);
}
{
return nullptr;
}
}

View File

@@ -0,0 +1,543 @@
#include "ModbusEC.h"
ModbusMaster::ModbusMaster(void)
{
_idle = 0;
_preTransmission = 0;
_postTransmission = 0;
}
/**
Initialize class object.
Assigns the Modbus slave ID and serial port.
Call once class has been instantiated, typically within setup().
@param slave Modbus slave ID (1..255)
@param &serial reference to serial port object (Serial, Serial1, ... Serial3)
@ingroup setup
*/
void ModbusMaster::begin(uint8_t slave, Stream *serial)
{
// txBuffer = (uint16_t*) calloc(ku8MaxBufferSize, sizeof(uint16_t));
_u8MBSlave = slave;
_serial = serial;
_u8TransmitBufferIndex = 0;
u16TransmitBufferLength = 0;
#if __MODBUSMASTER_DEBUG__
pinMode(__MODBUSMASTER_DEBUG_PIN_A__, OUTPUT);
pinMode(__MODBUSMASTER_DEBUG_PIN_B__, OUTPUT);
#endif
}
void ModbusMaster::beginTransmission(uint16_t u16Address)
{
_u16WriteAddress = u16Address;
_u8TransmitBufferIndex = 0;
u16TransmitBufferLength = 0;
}
// eliminate this function in favor of using existing MB request functions
uint8_t ModbusMaster::requestFrom(uint16_t address, uint16_t quantity)
{
uint8_t read;
// clamp to buffer length
if (quantity > ku8MaxBufferSize)
{
quantity = ku8MaxBufferSize;
}
// set rx buffer iterator vars
_u8ResponseBufferIndex = 0;
_u8ResponseBufferLength = read;
return read;
}
void ModbusMaster::sendBit(bool data)
{
uint8_t txBitIndex = u16TransmitBufferLength % 16;
if ((u16TransmitBufferLength >> 4) < ku8MaxBufferSize)
{
if (0 == txBitIndex)
{
_u16TransmitBuffer[_u8TransmitBufferIndex] = 0;
}
bitWrite(_u16TransmitBuffer[_u8TransmitBufferIndex], txBitIndex, data);
u16TransmitBufferLength++;
_u8TransmitBufferIndex = u16TransmitBufferLength >> 4;
}
}
void ModbusMaster::send(uint16_t data)
{
if (_u8TransmitBufferIndex < ku8MaxBufferSize)
{
_u16TransmitBuffer[_u8TransmitBufferIndex++] = data;
u16TransmitBufferLength = _u8TransmitBufferIndex << 4;
}
}
void ModbusMaster::send(uint32_t data)
{
send(lowWord(data));
send(highWord(data));
}
void ModbusMaster::send(uint8_t data)
{
send(word(data));
}
uint8_t ModbusMaster::available(void)
{
return _u8ResponseBufferLength - _u8ResponseBufferIndex;
}
uint16_t ModbusMaster::receive(void)
{
if (_u8ResponseBufferIndex < _u8ResponseBufferLength)
{
return _u16ResponseBuffer[_u8ResponseBufferIndex++];
}
else
{
return 0xFFFF;
}
}
/**
Set idle time callback function (cooperative multitasking).
This function gets called in the idle time between transmission of data
and response from slave. Do not call functions that read from the serial
buffer that is used by ModbusMaster. Use of i2c/TWI, 1-Wire, other
serial ports, etc. is permitted within callback function.
@see ModbusMaster::ModbusMasterTransaction()
*/
void ModbusMaster::idle(void (*idle)())
{
_idle = idle;
}
/**
Set pre-transmission callback function.
This function gets called just before a Modbus message is sent over serial.
Typical usage of this callback is to enable an RS485 transceiver's
Driver Enable pin, and optionally disable its Receiver Enable pin.
@see ModbusMaster::ModbusMasterTransaction()
@see ModbusMaster::postTransmission()
*/
void ModbusMaster::preTransmission(void (*preTransmission)())
{
_preTransmission = preTransmission;
}
/**
Set post-transmission callback function.
This function gets called after a Modbus message has finished sending
(i.e. after all data has been physically transmitted onto the serial
bus).
Typical usage of this callback is to enable an RS485 transceiver's
Receiver Enable pin, and disable its Driver Enable pin.
@see ModbusMaster::ModbusMasterTransaction()
@see ModbusMaster::preTransmission()
*/
void ModbusMaster::postTransmission(void (*postTransmission)())
{
_postTransmission = postTransmission;
}
/**
Retrieve data from response buffer.
@see ModbusMaster::clearResponseBuffer()
@param u8Index index of response buffer array (0x00..0x3F)
@return value in position u8Index of response buffer (0x0000..0xFFFF)
@ingroup buffer
*/
uint16_t ModbusMaster::getResponseBuffer(uint8_t u8Index)
{
if (u8Index < ku8MaxBufferSize)
{
return _u16ResponseBuffer[u8Index];
}
else
{
return 0xFFFF;
}
}
/**
Clear Modbus response buffer.
@see ModbusMaster::getResponseBuffer(uint8_t u8Index)
@ingroup buffer
*/
void ModbusMaster::clearResponseBuffer()
{
uint8_t i;
for (i = 0; i < ku8MaxBufferSize; i++)
{
_u16ResponseBuffer[i] = 0;
}
}
/**
Place data in transmit buffer.
@see ModbusMaster::clearTransmitBuffer()
@param u8Index index of transmit buffer array (0x00..0x3F)
@param u16Value value to place in position u8Index of transmit buffer (0x0000..0xFFFF)
@return 0 on success; exception number on failure
@ingroup buffer
*/
uint8_t ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value)
{
if (u8Index < ku8MaxBufferSize)
{
_u16TransmitBuffer[u8Index] = u16Value;
return ku8MBSuccess;
}
else
{
return ku8MBIllegalDataAddress;
}
}
/**
Clear Modbus transmit buffer.
@see ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value)
@ingroup buffer
*/
void ModbusMaster::clearTransmitBuffer()
{
uint8_t i;
for (i = 0; i < ku8MaxBufferSize; i++)
{
_u16TransmitBuffer[i] = 0;
}
}
/**
Modbus function 0x03 Read Holding Registers.
This function code is used to read the contents of a contiguous block of
holding registers in a remote device. The request specifies the starting
register address and the number of registers. Registers are addressed
starting at zero.
The register data in the response buffer is packed as one word per
register.
@param u16ReadAddress address of the first holding register (0x0000..0xFFFF)
@param u16ReadQty quantity of holding registers to read (1..125, enforced by remote device)
@return 0 on success; exception number on failure
@ingroup register
*/
uint8_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress,
uint16_t u16ReadQty)
{
_u16ReadAddress = u16ReadAddress;
_u16ReadQty = u16ReadQty;
return ModbusMasterTransaction(ku8MBReadHoldingRegisters);
}
/**
Modbus function 0x06 Write Single Register.
This function code is used to write a single holding register in a
remote device. The request specifies the address of the register to be
written. Registers are addressed starting at zero.
@param u16WriteAddress address of the holding register (0x0000..0xFFFF)
@param u16WriteValue value to be written to holding register (0x0000..0xFFFF)
@return 0 on success; exception number on failure
@ingroup register
*/
uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress,
uint16_t u16WriteValue)
{
_u16WriteAddress = u16WriteAddress;
_u16WriteQty = 0;
_u16TransmitBuffer[0] = u16WriteValue;
return ModbusMasterTransaction(ku8MBWriteSingleRegister);
}
/**
Modbus function 0x10 Write Multiple Registers.
This function code is used to write a block of contiguous registers (1
to 123 registers) in a remote device.
The requested written values are specified in the transmit buffer. Data
is packed as one word per register.
@param u16WriteAddress address of the holding register (0x0000..0xFFFF)
@param u16WriteQty quantity of holding registers to write (1..123, enforced by remote device)
@return 0 on success; exception number on failure
@ingroup register
*/
uint8_t ModbusMaster::writeMultipleRegisters(uint16_t u16WriteAddress,
uint16_t u16WriteQty)
{
_u16WriteAddress = u16WriteAddress;
_u16WriteQty = u16WriteQty;
return ModbusMasterTransaction(ku8MBWriteMultipleRegisters);
}
// new version based on Wire.h
uint8_t ModbusMaster::writeMultipleRegisters()
{
_u16WriteQty = _u8TransmitBufferIndex;
return ModbusMasterTransaction(ku8MBWriteMultipleRegisters);
}
uint8_t ModbusMaster::readAddresEctoControl()
{
_u16ReadAddress = 0x00;
_u16ReadQty = 1;
return ModbusMasterTransaction(ku8MBProgRead46);
}
uint8_t ModbusMaster::writeAddresEctoControl(uint8_t addr)
{
_u16WriteAddress = 0x00;
_u16WriteQty = 1;
_u16TransmitBuffer[0] = (uint16_t)addr;
return ModbusMasterTransaction(ku8MBProgWrite47);
}
/* _____PRIVATE FUNCTIONS____________________________________________________ */
/**
Modbus transaction engine.
Sequence:
- assemble Modbus Request Application Data Unit (ADU),
based on particular function called
- transmit request over selected serial port
- wait for/retrieve response
- evaluate/disassemble response
- return status (success/exception)
@param u8MBFunction Modbus function (0x01..0xFF)
@return 0 on success; exception number on failure
*/
uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
{
uint8_t u8ModbusADU[24];
uint8_t u8ModbusADUSize = 0;
uint8_t i, u8Qty;
uint16_t u16CRC;
uint32_t u32StartTime;
uint8_t u8BytesLeft = 8;
uint8_t u8MBStatus = ku8MBSuccess;
// assemble Modbus Request Application Data Unit
if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47)
{
u8ModbusADU[u8ModbusADUSize++] = 0x00;
u8ModbusADU[u8ModbusADUSize++] = u8MBFunction;
}
else
{
u8ModbusADU[u8ModbusADUSize++] = _u8MBSlave;
u8ModbusADU[u8ModbusADUSize++] = u8MBFunction;
}
switch (u8MBFunction)
{
case ku8MBReadHoldingRegisters:
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16ReadAddress);
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16ReadAddress);
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16ReadQty);
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16ReadQty);
break;
}
switch (u8MBFunction)
{
case ku8MBWriteMultipleRegisters:
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteAddress);
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteAddress);
break;
}
switch (u8MBFunction)
{
case ku8MBWriteMultipleRegisters:
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteQty);
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty);
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty << 1);
for (i = 0; i < lowByte(_u16WriteQty); i++)
{
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[i]);
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[i]);
}
break;
}
// append CRC
u16CRC = 0xFFFF;
for (i = 0; i < u8ModbusADUSize; i++)
{
u16CRC = crc16_update(u16CRC, u8ModbusADU[i]);
}
u8ModbusADU[u8ModbusADUSize++] = lowByte(u16CRC);
u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC);
u8ModbusADU[u8ModbusADUSize] = 0;
// flush receive buffer before transmitting request
while (_serial->read() != -1)
;
// transmit request
if (_preTransmission)
{
_preTransmission();
}
for (i = 0; i < u8ModbusADUSize; i++)
{
_serial->write(u8ModbusADU[i]);
}
u8ModbusADUSize = 0;
_serial->flush(); // flush transmit buffer
if (_postTransmission)
{
_postTransmission();
}
// loop until we run out of time or bytes, or an error occurs
u32StartTime = millis();
while (u8BytesLeft && !u8MBStatus)
{
if (_serial->available())
{
#if __MODBUSMASTER_DEBUG__
digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, true);
#endif
u8ModbusADU[u8ModbusADUSize++] = _serial->read();
u8BytesLeft--;
#if __MODBUSMASTER_DEBUG__
digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, false);
#endif
}
else
{
#if __MODBUSMASTER_DEBUG__
digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, true);
#endif
if (_idle)
{
_idle();
}
#if __MODBUSMASTER_DEBUG__
digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, false);
#endif
}
// evaluate slave ID, function code once enough bytes have been read
if (u8ModbusADUSize == 5)
{
// verify response is for correct Modbus slave
if (u8ModbusADU[0] != _u8MBSlave)
{
// Serial.print(u8ModbusADU[0], HEX);
// Serial.print(" != ");
// Serial.println(_u8MBSlave, HEX);
u8MBStatus = ku8MBInvalidSlaveID;
break;
}
// verify response is for correct Modbus function code (mask exception bit 7)
if ((u8ModbusADU[1] & 0x7F) != u8MBFunction)
{
u8MBStatus = ku8MBInvalidFunction;
break;
}
// check whether Modbus exception occurred; return Modbus Exception Code
if (bitRead(u8ModbusADU[1], 7))
{
u8MBStatus = u8ModbusADU[2];
break;
}
// evaluate returned Modbus function code
switch (u8ModbusADU[1])
{
case ku8MBReadHoldingRegisters:
u8BytesLeft = u8ModbusADU[2];
break;
case ku8MBWriteMultipleRegisters:
u8BytesLeft = 3;
break;
}
}
if ((millis() - u32StartTime) > ku16MBResponseTimeout)
{
u8MBStatus = ku8MBResponseTimedOut;
}
}
// verify response is large enough to inspect further
if (!u8MBStatus && u8ModbusADUSize >= 5)
{
// calculate CRC
u16CRC = 0xFFFF;
for (i = 0; i < (u8ModbusADUSize - 2); i++)
{
u16CRC = crc16_update(u16CRC, u8ModbusADU[i]);
}
// verify CRC
if (!u8MBStatus && (lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2] ||
highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1]))
{
u8MBStatus = ku8MBInvalidCRC;
}
}
// disassemble ADU into words
if (!u8MBStatus)
{
// evaluate returned Modbus function code
switch (u8ModbusADU[1])
{
case ku8MBReadHoldingRegisters:
// load bytes into word; response bytes are ordered H, L, H, L, ...
for (i = 0; i < (u8ModbusADU[2] >> 1); i++)
{
if (i < ku8MaxBufferSize)
{
_u16ResponseBuffer[i] = word(u8ModbusADU[2 * i + 3], u8ModbusADU[2 * i + 4]);
}
_u8ResponseBufferLength = i;
}
break;
case ku8MBProgRead46:
_u16ResponseBuffer[0] = (uint16_t)u8ModbusADU[2];
_u8ResponseBufferLength = 1;
break;
}
}
_u8TransmitBufferIndex = 0;
u16TransmitBufferLength = 0;
_u8ResponseBufferIndex = 0;
return u8MBStatus;
}

View File

@@ -0,0 +1,97 @@
#ifndef ModbusEC_h
#define ModbusEC_h
#define __MODBUSMASTER_DEBUG__ (0)
#define __MODBUSMASTER_DEBUG_PIN_A__ 4
#define __MODBUSMASTER_DEBUG_PIN_B__ 5
#include "Arduino.h"
#include "util/crc16.h"
#include "util/word.h"
/* _____CLASS DEFINITIONS____________________________________________________ */
/**
Arduino class library for communicating with Modbus slaves over
RS232/485 (via RTU protocol).
*/
class ModbusMaster
{
public:
ModbusMaster();
void begin(uint8_t, Stream *serial);
void idle(void (*)());
void preTransmission(void (*)());
void postTransmission(void (*)());
static const uint8_t ku8MBIllegalFunction = 0x01;
static const uint8_t ku8MBIllegalDataAddress = 0x02;
static const uint8_t ku8MBIllegalDataValue = 0x03;
static const uint8_t ku8MBSlaveDeviceFailure = 0x04;
static const uint8_t ku8MBSuccess = 0x00;
static const uint8_t ku8MBInvalidSlaveID = 0xE0;
static const uint8_t ku8MBInvalidFunction = 0xE1;
static const uint8_t ku8MBResponseTimedOut = 0xE2;
static const uint8_t ku8MBInvalidCRC = 0xE3;
uint16_t getResponseBuffer(uint8_t);
void clearResponseBuffer();
uint8_t setTransmitBuffer(uint8_t, uint16_t);
void clearTransmitBuffer();
void beginTransmission(uint16_t);
uint8_t requestFrom(uint16_t, uint16_t);
void sendBit(bool);
void send(uint8_t);
void send(uint16_t);
void send(uint32_t);
uint8_t available(void);
uint16_t receive(void);
uint8_t readHoldingRegisters(uint16_t, uint16_t);
uint8_t writeMultipleRegisters(uint16_t, uint16_t);
uint8_t writeMultipleRegisters();
uint8_t writeSingleRegister(uint16_t, uint16_t);
uint8_t readAddresEctoControl();
uint8_t writeAddresEctoControl(uint8_t);
private:
Stream *_serial; ///< reference to serial port object
uint8_t _u8MBSlave; ///< Modbus slave (1..255) initialized in begin()
static const uint8_t ku8MaxBufferSize = 20; ///< size of response/transmit buffers
uint16_t _u16ReadAddress; ///< slave register from which to read
uint16_t _u16ReadQty; ///< quantity of words to read
uint16_t _u16ResponseBuffer[ku8MaxBufferSize]; ///< buffer to store Modbus slave response; read via GetResponseBuffer()
uint16_t _u16WriteAddress; ///< slave register to which to write
uint16_t _u16WriteQty; ///< quantity of words to write
uint16_t _u16TransmitBuffer[ku8MaxBufferSize]; ///< buffer containing data to transmit to Modbus slave; set via SetTransmitBuffer()
uint16_t *txBuffer; // from Wire.h -- need to clean this up Rx
uint8_t _u8TransmitBufferIndex;
uint16_t u16TransmitBufferLength;
uint16_t *rxBuffer; // from Wire.h -- need to clean this up Rx
uint8_t _u8ResponseBufferIndex;
uint8_t _u8ResponseBufferLength;
// Modbus function codes for 16 bit access
static const uint8_t ku8MBReadHoldingRegisters = 0x03; ///< Modbus function 0x03 Read Holding Registers
static const uint8_t ku8MBWriteMultipleRegisters = 0x10; ///< Modbus function 0x10 Write Multiple Registers
static const uint8_t ku8MBWriteSingleRegister = 0x06; ///< Modbus function 0x06 Write Single Register
static const uint8_t ku8MBProgRead46 = 0x46; ///< EctoControl function 0x46 Устройство возвращает в ответе свой текущий адрес ADDR
static const uint8_t ku8MBProgWrite47 = 0x47; ///< EctoControl function 0x47 высылается ведущим устройством ведомому с указанием сменить свой имеющийся адрес на заданный
// высылается ведущим устройством единственному устройству на шине с неизвестным адресом
// Modbus timeout [milliseconds]
static const uint16_t ku16MBResponseTimeout = 500; ///< Modbus timeout [milliseconds]
// master function that conducts Modbus transactions
uint8_t ModbusMasterTransaction(uint8_t u8MBFunction);
// idle callback function; gets called during idle time between TX and RX
void (*_idle)();
// preTransmission callback function; gets called before writing a Modbus message
void (*_preTransmission)();
// postTransmission callback function; gets called after a Modbus message has been sent
void (*_postTransmission)();
};
#endif

View File

@@ -0,0 +1,208 @@
{
"menuSection": "executive_devices",
"configItem": [
{
"global": 0,
"name": "ectoCtrlAdapter",
"type": "Reading",
"subtype": "ecAdapter",
"id": "ecto",
"widget": "anydataTmp",
"page": "Котёл",
"descr": "Адаптер",
"int": 60,
"addr": 240,
"RX": 18,
"TX": 19,
"DIR_PIN": 4,
"baud": 19200,
"protocol": "SERIAL_8N1",
"debug": 1
}
],
"about": {
"authorName": "Mikhail Bubnov",
"authorContact": "https://t.me/Mit4bmw",
"authorGit": "https://github.com/Mit4el",
"specialThanks": "",
"moduleName": "EctoControlAdapter",
"moduleVersion": "1.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"subTypes": [
"ecAdapter"
],
"title": "EctoControlAdapter",
"moduleDesc": "Управление отопительным котлом через адаптер EctoControl по протоколам OpenTherm, eBUS, Navien. Посредством Modbus RTU. Разъем 4P4C: 1-Желтый(красный)+12V; 2-Белый-GND; 3-Зелёный-A; 4-Коричневый(Синий)-B",
"propInfo": {
"addr": "Адрес slave, что бы узнать адрес - в конфиге адрес 0 и смотреть лог (требуется проверка)",
"int": "Количество секунд между опросами датчика.",
"RX": "Пин RX",
"TX": "Пин TX",
"DIR_PIN": "connect DR, RE pin of MAX485 to gpio, указать 0 если не нужен",
"baud": "скорость Uart",
"protocol": "Протокол Uart: SERIAL_8N1 или SERIAL_8N2",
"debug": "0 - отключить дебаг, 1 - включить вывод дебага, 2 - лог комманд, 3 - вывод modbus"
},
"funcInfo": [
{
"name": "getModelVersion",
"descr": "Запрос модели и версии адаптера и бойлера",
"params": []
},
{
"name": "getBoilerInfo",
"descr": "Запрос состояния связи с котлом, типа адаптера и код перезагрузки адаптера",
"params": []
},
{
"name": "getBoilerStatus",
"descr": "Запрос состояния контуров котла и горелки",
"params": []
},
{
"name": "getCodeError",
"descr": "Код ошибки котла (основной). Зависит от марки и модели котла.",
"params": []
},
{
"name": "getCodeErrorExt",
"descr": "Код ошибки котла (дополнительный). Зависит от марки и модели котла.",
"params": []
},
{
"name": "getFlagErrorOT",
"descr": "Стандартные флаги ошибок котла (только для котлов с интерфейсом OpenTherm)",
"params": []
},
{
"name": "getFlowRate",
"descr": "Текущий расхода ГВС",
"params": []
},
{
"name": "getMaxSetCH",
"descr": "Верхний предел уставки теплоносителя",
"params": []
},
{
"name": "getMaxSetDHW",
"descr": "Верхний предел уставки ГВС",
"params": []
},
{
"name": "getMinSetCH",
"descr": "Нижний предел уставки теплоносителя",
"params": []
},
{
"name": "getMinSetDHW",
"descr": "Нижний предел уставки ГВС",
"params": []
},
{
"name": "getModLevel",
"descr": "Текущая модуляция горелки",
"params": []
},
{
"name": "getPressure",
"descr": "Текущее Давление в контуре",
"params": []
},
{
"name": "getTempCH",
"descr": "Текущая температура теплоносителя",
"params": []
},
{
"name": "getTempDHW",
"descr": "Текущая температура ГВС",
"params": []
},
{
"name": "getTempOutside",
"descr": "Температура уличного датчика котла",
"params": []
},
{
"name": "setTypeConnect",
"descr": "Установить тип внешних подключений (сохраняется в EPROM Адаптера): 0 - адаптер подключен к котлу, 1 - котел подключен к внешнему устройству (панель или перемычка)",
"params": ["Тип подключения"]
},
{
"name": "setTCH",
"descr": "Уставка температуры теплоносителя (сохраняется в EPROM Адаптера)",
"params": ["температура передаётся до десятых градуса"]
},
{
"name": "setTDHW",
"descr": "Уставка температуры ГВС (сохраняется в EPROM Адаптера)",
"params": ["температура передаётся до десятых градуса"]
},
{
"name": "setTCHFaultConn",
"descr": "Уставка теплоносителя в аварийном режиме (сохраняется в EPROM Адаптера). Будет передана котлу в случае отсутствия связи адаптера с управляющим устройством",
"params": ["температура передаётся до десятых градуса"]
},
{
"name": "setMinCH",
"descr": "Задать нижний предел уставки теплоносителя",
"params": ["температура от 0 до 100"]
},
{
"name": "setMaxCH",
"descr": "Задать верхний предел уставки теплоносителя",
"params": ["температура от 0 до 100"]
},
{
"name": "setMinDHW",
"descr": "Задать нижний предел уставки ГВС",
"params": ["температура от 0 до 100"]
},
{
"name": "setMaxDHW",
"descr": "Задать верхний предел уставки ГВС",
"params": ["температура от 0 до 100"]
},
{
"name": "setMaxModLevel",
"descr": "Уставка максимальной модуляции горелки (сохраняется в EPROM Адаптера)",
"params": ["уровень модуляции 0-100%"]
},
{
"name": "setStatusCH",
"descr": "Установить режим (Включить) контура отопления; 0 - отключен, 1 - включен",
"params": ["вкл/откл отопления"]
},
{
"name": "setStatusDHW",
"descr": "Установить режим (Включить) ГВС; 0 - отключен, 1 - включен",
"params": ["вкл/откл ГВС"]
},
{
"name": "setStatusCH2",
"descr": "Установить режим (Включить) второго контура отопления; 0 - отключен, 1 - включен. используется только некоторыми котлами с интерфейсом OpenTherm и может отвечать за активацию бойлера косвенного нагрева или встроенной функции ГВС",
"params": ["вкл/откл второго контура отопления"]
},
{
"name": "lockOutReset",
"descr": "Сброс ошибок котла",
"params": []
},
{
"name": "rebootAdapter",
"descr": "Перезагрузка адаптера",
"params": []
}
]
},
"defActive": false,
"usedLibs": {
"esp32*": [],
"esp32c3m_4mb": ["exclude"]
}
}

View File

@@ -0,0 +1,88 @@
/**
@file
CRC Computations
@defgroup util_crc16 "util/crc16.h": CRC Computations
@code#include "util/crc16.h"@endcode
This header file provides functions for calculating
cyclic redundancy checks (CRC) using common polynomials.
Modified by Doc Walker to be processor-independent (removed inline
assembler to allow it to compile on SAM3X8E processors).
@par References:
Jack Crenshaw's "Implementing CRCs" article in the January 1992 issue of @e
Embedded @e Systems @e Programming. This may be difficult to find, but it
explains CRC's in very clear and concise terms. Well worth the effort to
obtain a copy.
*/
/* Copyright (c) 2002, 2003, 2004 Marek Michalkiewicz
Copyright (c) 2005, 2007 Joerg Wunsch
Copyright (c) 2013 Dave Hylands
Copyright (c) 2013 Frederic Nadeau
Copyright (c) 2015 Doc Walker
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holders nor the names of
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. */
#ifndef _UTIL_CRC16_H_
#define _UTIL_CRC16_H_
/** @ingroup util_crc16
Processor-independent CRC-16 calculation.
Polynomial: x^16 + x^15 + x^2 + 1 (0xA001)<br>
Initial value: 0xFFFF
This CRC is normally used in disk-drive controllers.
@param uint16_t crc (0x0000..0xFFFF)
@param uint8_t a (0x00..0xFF)
@return calculated CRC (0x0000..0xFFFF)
*/
static uint16_t crc16_update(uint16_t crc, uint8_t a)
{
int i;
crc ^= a;
for (i = 0; i < 8; ++i)
{
if (crc & 1)
crc = (crc >> 1) ^ 0xA001;
else
crc = (crc >> 1);
}
return crc;
}
#endif /* _UTIL_CRC16_H_ */

View File

@@ -0,0 +1,64 @@
/**
@file
Utility Functions for Manipulating Words
@defgroup util_word "util/word.h": Utility Functions for Manipulating Words
@code#include "util/word.h"@endcode
This header file provides utility functions for manipulating words.
*/
/*
word.h - Utility Functions for Manipulating Words
This file is part of ModbusMaster.
ModbusMaster is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ModbusMaster is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ModbusMaster. If not, see <http://www.gnu.org/licenses/>.
Written by Doc Walker (Rx)
Copyright © 2009-2015 Doc Walker <4-20ma at wvfans dot net>
*/
#ifndef _UTIL_WORD_H_
#define _UTIL_WORD_H_
/** @ingroup util_word
Return low word of a 32-bit integer.
@param uint32_t ww (0x00000000..0xFFFFFFFF)
@return low word of input (0x0000..0xFFFF)
*/
static inline uint16_t lowWord(uint32_t ww)
{
return (uint16_t) ((ww) & 0xFFFF);
}
/** @ingroup util_word
Return high word of a 32-bit integer.
@param uint32_t ww (0x00000000..0xFFFFFFFF)
@return high word of input (0x0000..0xFFFF)
*/
static inline uint16_t highWord(uint32_t ww)
{
return (uint16_t) ((ww) >> 16);
}
#endif /* _UTIL_WORD_H_ */

View File

@@ -0,0 +1,195 @@
#include "Global.h"
#include "classes/IoTItem.h"
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <IRac.h>
#include <IRutils.h>
IRac* ac;
const uint16_t kIrLed = 4; // The ESP GPIO pin to use that controls the IR LED.
class IRremote : public IoTItem {
private:
String _set_id; // заданная температура
int enable = 1;
float _tmp;
int _prot; // протокол
int _pinTx; // Выход модуля передатчика
public:
IRremote(String parameters): IoTItem(parameters) {
jsonRead(parameters, "pinTx", _pinTx); //передатчик
jsonRead(parameters, "prot", _prot); // используемый протокол
jsonRead(parameters, "set_id", _set_id); // id установленной температуры
if (_pinTx >= 0) {
IoTgpio.pinMode(_pinTx, OUTPUT);
IoTgpio.digitalWrite(_pinTx, false); }
// Set up what we want to send.
// See state_t, opmode_t, fanspeed_t, swingv_t, & swingh_t in IRsend.h for
// all the various options.
ac = new IRac(kIrLed);
ac->next.protocol = (decode_type_t)_prot;
ac->next.model = 1; // Некоторые кондиционеры имеют разные модели. Попробуйте только первое.
ac->next.mode = stdAc::opmode_t::kCool; // Сначала запустите в прохладном режиме.
ac->next.celsius = true; // Используйте градусы Цельсия в качестве единиц измерения температуры. Ложь = Фаренгейт
ac->next.degrees = 20; // 20 degrees.
ac->next.fanspeed = stdAc::fanspeed_t::kMedium; // Запустите вентилятор на средней скорости.
ac->next.swingv = stdAc::swingv_t::kOff; // Не поворачивайте вентилятор вверх или вниз.
ac->next.swingh = stdAc::swingh_t::kOff; // Не поворачивайте вентилятор влево или вправо.
ac->next.light = false; // Выключите все светодиоды/световые приборы/дисплеи, которые сможем.
ac->next.beep = false; // Если есть возможность, выключите все звуковые сигналы кондиционера.
ac->next.econo = false; // Turn off any economy modes if we can.
ac->next.filter = false; // Turn off any Ion/Mold/Health filters if we can.
ac->next.turbo = false; // Don't use any turbo/powerful/etc modes.
ac->next.quiet = false; // Don't use any quiet/silent/etc modes.
ac->next.sleep = -1; // Don't set any sleep time or modes.
ac->next.clean = false; // Turn off any Cleaning options if we can.
ac->next.clock = -1; // Don't set any current time if we can avoid it.
ac->next.power = false; // Initially start with the unit off.
Serial.println("Try to turn on & off every supported A/C type ...");
}
void doByInterval() {}
IoTValue execute(String command, std::vector<IoTValue> &param) {
if (command == "on") {
ac->next.power = true; // Типа команда включить
ac->sendAc(); // Send the message.
SerialPrint("i", F("IRremote"), "Ballu AC on ");
}
if (command == "off") {
ac->next.power = false;
ac->sendAc();
SerialPrint("i", F("IRremote"), "Ballu AC off ");
}
if (command == "cool") {
ac->next.mode = stdAc::opmode_t::kCool;
ac->sendAc();
SerialPrint("i", F("IRremote"), "Ballu AC cool ");
}
if (command == "heat") {
ac->next.mode = stdAc::opmode_t::kHeat;
ac->sendAc();
SerialPrint("i", F("IRremote"), "Ballu AC heat ");
}
if (command == "dry") {
ac->next.mode = stdAc::opmode_t::kDry;
ac->sendAc();
SerialPrint("i", F("IRremote"), "Ballu AC dry ");
}
if (command == "auto") {
ac->next.fanspeed = stdAc::fanspeed_t::kAuto;
ac->sendAc();
SerialPrint("i", F("IRremote"), "Ballu AC speed1 ");
}
if (command == "speedmin") {
ac->next.fanspeed = stdAc::fanspeed_t::kMin;
ac->sendAc();
SerialPrint("i", F("IRremote"), "Ballu AC speed min ");
}
if (command == "speedlow") {
ac->next.fanspeed = stdAc::fanspeed_t::kLow;
ac->sendAc();
SerialPrint("i", F("IRremote"), "Ballu AC speed low ");
}
if (command == "speedmed") {
ac->next.fanspeed = stdAc::fanspeed_t::kMedium; // Надо выбрать под конкретный кондиционер из 6-ти вариантов
ac->sendAc();
SerialPrint("i", F("IRremote"), "Ballu AC speed medium ");
}
if (command == "speedhigh") {
ac->next.fanspeed = stdAc::fanspeed_t::kHigh; // Надо выбрать под конкретный кондиционер из 6-ти вариантов
ac->sendAc();
SerialPrint("i", F("IRremote"), "Ballu AC speed high");
}
if (command == "speedmax") {
ac->next.fanspeed = stdAc::fanspeed_t::kMax; // Надо выбрать под конкретный кондиционер из 6-ти вариантов
ac->sendAc();
SerialPrint("i", F("IRremote"), "Ballu AC speed max");
}
if (command == "speedmh") {
ac->next.fanspeed = stdAc::fanspeed_t::kMediumHigh;
ac->sendAc();
SerialPrint("i", F("IRremote"), "Ballu AC speed max");
}
if (command == "setTemp") {
// заданная температура
IoTItem *tmp = findIoTItem(_set_id);
if (tmp)
{
_tmp = ::atof(tmp->getValue().c_str());
ac->next.degrees = _tmp; // set Temp 17 C - 30 C.
ac->sendAc(); // Send the message.
SerialPrint("i", F("IRremote"), "Ballu AC set temp -> " + String(_tmp) );
}
else
{
// если не заполнены настройки кондиционера
setValue("ошибка настройки кондиционера");
}
}
if (command == "swing") {
ac->next.swingv = stdAc::swingv_t::kMiddle;; // Надо выбрать под конкретный кондиционер из 6-ти вариантов
ac->sendAc(); // Send the message.
SerialPrint("i", F("IRremote"), "Ballu AC swing middle");
}
return {}; // команда поддерживает возвращаемое значения. Т.е. по итогу выполнения команды или общения с внешней системой, можно вернуть значение в сценарий для дальнейшей обработки
}
~IRremote() {};
};
void* getAPI_IRremote(String subtype, String param) {
if (subtype == F("IRremote")) {
return new IRremote(param);
} else {
return nullptr;
}
}

View File

@@ -0,0 +1,109 @@
{
"menuSection": "executive_devices",
"configItem": [{
"global": 0,
"name": "Управление кондиционером",
"type": "Reading",
"subtype": "IRremote",
"id": "ir",
"pinTx": 4,
"set_id": "",
"int": 1,
"prot": 15
}],
"about": {
"authorName": "Serghei Crasnicov",
"authorContact": "https://t.me/Serghei63",
"authorGit": "https://github.com/Serghei63",
"specialThanks": "Mikhail Bubnov https://t.me/Mit4bmw , Олег Астахов https://t.me/Threedreality , Valentin Khandriga @Valiuhaaa",
"moduleName": "IRremote",
"moduleVersion": "2.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"title": "Эмулятор IR пульта",
"moduleDesc": "Позволяет управлять кондиционером и другой техникой по IR каналу",
"propInfo": {
"pinTx": "GPIO номер, к которому подключен инфракрасный приемник.",
"prot": " Цифровой код протокола . UNKNOWN = -1, UNUSED = 0, COOLIX - 15, SAMSUNG_AC - 46 ....",
"set_id": "id установленной температуры"
},
"funcInfo": [
{
"name": "on",
"descr": "Включить конциционер",
"params": []
},
{
"name": "off",
"descr": "Выключить конциционер",
"params": []
},
{
"name": "cool",
"descr": "Режим охлаждения",
"params": []
},
{
"name": "heat",
"descr": "Режим нагрева",
"params": []
},
{
"name": "dry",
"descr": "Режим осушения",
"params": []
},
{
"name": "speedmin",
"descr": "Минимальная скорость вентилятора",
"params": []
},
{
"name": "speedmed",
"descr": "Средняя скорость вентилятора",
"params": []
},
{
"name": "speedmh",
"descr": "Средне - высокая скорость вентилятора",
"params": []
},
{
"name": "speedhigh",
"descr": "Высокая скорость вентилятора",
"params": []
},
{
"name": "speedmax",
"descr": "Максимальная скорость вентилятора",
"params": []
},
{
"name": "setTemp",
"descr": "Установленная температура",
"params": []
},
{
"name": "swing",
"descr": "Управление шторкой вентилятора",
"params": []
}
]
},
"defActive": false,
"usedLibs": {
"esp32*": [
"https://github.com/crankyoldgit/IRremoteESP8266"
],
"esp82*": [
"https://github.com/crankyoldgit/IRremoteESP8266"
]
}
}

View File

@@ -50,7 +50,7 @@
}
]
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [
"https://github.com/RoboticsBrno/ServoESP32#v1.0.3"

View File

@@ -34,7 +34,7 @@
"index": "Значения от 1 до 4, где при выборе 1 будет нумерация pin 100-115, при выборе 2 200-215 и т.д."
}
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [
"adafruit/Adafruit MCP23017 Arduino Library@^2.1.0",

View File

@@ -87,11 +87,17 @@
}
]
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [
"dfrobot/DFRobotDFPlayerMini @ ^1.0.5"
],
"esp32c6_4mb": [
"exclude"
],
"esp32c6_8mb": [
"exclude"
],
"esp82*": [
"dfrobot/DFRobotDFPlayerMini @ ^1.0.5"
]

View File

@@ -49,6 +49,12 @@
},
"defActive": false,
"usedLibs": {
"esp32c6_4mb": [
"exclude"
],
"esp32c6_8mb": [
"exclude"
],
"esp32*": []
}
}

View File

@@ -30,7 +30,7 @@
},
"title": "Расширитель портов Pcf8574"
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [
"adafruit/Adafruit BusIO @ ^1.13.2"

View File

@@ -8,15 +8,15 @@
extern IoTGpio IoTgpio;
class Pwm32 : public IoTItem {
private:
private:
int _pin;
int _freq;
int _freq;
int _apin, _oldValue;
bool _freezVal = true;
int _ledChannel;
int _ledChannel;
int _resolution;
public:
public:
Pwm32(String parameters): IoTItem(parameters) {
_interval = _interval / 1000; // корректируем величину интервала int, теперь он в миллисекундах
@@ -24,10 +24,14 @@ class Pwm32 : public IoTItem {
jsonRead(parameters, "freq", _freq);
jsonRead(parameters, "ledChannel", _ledChannel);
jsonRead(parameters, "PWM_resolution", _resolution);
pinMode(_pin, OUTPUT);
#if defined(esp32c6_4mb) || defined(esp32c6_8mb)
ledcAttachChannel(_pin, _freq, _resolution, _ledChannel);
#else
ledcSetup(_ledChannel, _freq, _resolution);
ledcAttachPin(_pin, _ledChannel);
#endif
ledcWrite(_ledChannel, value.valD);
_resolution = pow(2, _resolution); // переводим биты в значение
@@ -53,7 +57,7 @@ class Pwm32 : public IoTItem {
}
}
}
void setValue(const IoTValue& Value, bool genEvent = true) {
value = Value;
ledcWrite(_ledChannel, value.valD);

View File

@@ -38,7 +38,7 @@
"freq": "Частота"
}
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp82*": []
}

View File

@@ -78,8 +78,6 @@
},
"defActive": false,
"usedLibs": {
"esp32_4mb": [],
"esp32_4mb3f": [],
"esp32*": [],
"esp82*": []
}

View File

@@ -164,7 +164,6 @@
},
"defActive": false,
"usedLibs": {
"esp32_4mb3f": [],
"esp32*": [],
"esp82*": []
}

View File

@@ -19,21 +19,21 @@ class Telegram : public IoTItem {
msg = deleteBeforeDelimiter(msg, "_");
generateOrder(selectToMarker(msg, "_"), selectToMarkerLast(msg, "_"));
_myBot.sendMessage(_chatID, "order done");
SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + ", msg: " + String(msg));
SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToStringIoTM(_chatID) + ", msg: " + String(msg));
} else if (msg.indexOf("get") != -1) {
msg = deleteBeforeDelimiter(msg, "_");
IoTItem* item = findIoTItem(msg);
if (item) {
_myBot.sendMessage(_chatID, item->getValue());
SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + ", msg: " + String(msg));
SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToStringIoTM(_chatID) + ", msg: " + String(msg));
}
} else if (msg.indexOf("all") != -1) {
String list = returnListOfParams();
_myBot.sendMessage(_chatID, list);
SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + "\n" + list);
SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToStringIoTM(_chatID) + "\n" + list);
} else if (msg.indexOf("help") != -1) {
_myBot.sendMessage(_chatID, "ID: " + chipId);
_myBot.sendMessage(_chatID, "chatID: " + uint64ToString(_chatID));
_myBot.sendMessage(_chatID, "chatID: " + uint64ToStringIoTM(_chatID));
_myBot.sendMessage(_chatID, F("Wrong order, use /all to get all values, /get_id to get value, or /set_id_value to set value"));
} else {
setValue(msg);
@@ -70,7 +70,7 @@ class Telegram : public IoTItem {
if (_receiveMsg) {
TBMessage msg;
if (_myBot.getNewMessage(msg)) {
SerialPrint("->", F("Telegram"), "chat ID: " + uint64ToString(msg.sender.id) + ", msg: " + msg.text);
SerialPrint("->", F("Telegram"), "chat ID: " + uint64ToStringIoTM(msg.sender.id) + ", msg: " + msg.text);
if (_autos) {
_chatID = msg.sender.id;
}
@@ -100,12 +100,12 @@ class Telegram : public IoTItem {
void sendTelegramMsg(bool often, String msg) {
if (often) {
_myBot.sendMessage(_chatID, msg);
SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + ", msg: " + msg);
SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToStringIoTM(_chatID) + ", msg: " + msg);
} else {
if (_prevMsg != msg) {
_prevMsg = msg;
_myBot.sendMessage(_chatID, msg);
SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + ", msg: " + msg);
SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToStringIoTM(_chatID) + ", msg: " + msg);
}
}
}

View File

@@ -41,7 +41,7 @@ class TelegramLT : public IoTItem {
if (param.size() == 1) {
String strTmp;
if (param[0].isDecimal && param[0].valS == "")
strTmp = param[0].valD;
strTmp = String(param[0].valD);
else
strTmp = param[0].valS;

View File

@@ -52,6 +52,7 @@
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -11,6 +11,7 @@
#ifdef ESP8266
#define FB_DYNAMIC
#endif
#include <FastBot.h>
#include <map>
@@ -94,8 +95,8 @@ public:
_myBot->tick();
if (fl_rollback)
{
_myBot->tickManual(); // Чтобы отметить сообщение прочитанным
#ifdef ESP32
_myBot->tickManual(); // Чтобы отметить сообщение прочитанным
if (Update.rollBack())
{
SerialPrint("I", F("Update"), F("Откат OTA успешно выполнен"));
@@ -107,6 +108,9 @@ public:
SerialPrint("E", F("Update"), F("Откат OTA не выполнен!"));
_myBot->sendMessage("Откат OTA не выполнен!", _chatID);
}
#else
SerialPrint("I", F("Update"), F("Откат OTA только в ESP32"));
_myBot->sendMessage("Откат OTA поддерживается только в ESP32", _chatID);
#endif
}
// была попытка OTA обновления. Обновляемся после ответа серверу!
@@ -686,7 +690,7 @@ public:
{
_myBot->sendMessage("ID: " + chipId, _chatID);
_myBot->sendMessage("chatID: " + _chatID, _chatID);
_myBot->sendMessage("Command: /help - this text \n /all - inline menu get all values \n /allMenu - bottom menu get all values \n /menu - bottom USER menu from scenario \n /get_id - get value by ID \n /set_id_value - set value in ID \n /file_name_type - take file from esp \n /file_type - support file type \n /reboot - reboot esp \n\n send file and write download - \"download\" file to esp \n\n send *.tft file - flash Nextion \n\n send firmware.bin or littltfs.bin - firmware ESP ", _chatID);
_myBot->sendMessage("Command: /help - this text \n /all - inline menu get all values \n /allMenu - bottom menu get all values \n /menu - bottom USER menu from scenario \n /get_id - get value by ID \n /set_id_value - set value in ID \n /file_/path/name_type - take file from esp \n /file_type - support file type \n /reboot - reboot esp \n\n send file and write download - \"download\" file to esp \n\n send *.tft file - flash Nextion \n\n send firmware.bin or littltfs.bin - firmware ESP ", _chatID);
}
}
else if (msg.text.indexOf("/reboot") != -1)

View File

@@ -0,0 +1,171 @@
/*
GyverPID - библиотека PID регулятора для Arduino
Документация: https://alexgyver.ru/gyverpid/
GitHub: https://github.com/GyverLibs/GyverPID
Возможности:
- Время одного расчёта около 70 мкс
- Режим работы по величине или по её изменению (для интегрирующих процессов)
- Возвращает результат по встроенному таймеру или в ручном режиме
- Встроенные калибровщики коэффициентов
- Режим работы по ошибке и по ошибке измерения
- Встроенные оптимизаторы интегральной суммы
AlexGyver, alex@alexgyver.ru
https://alexgyver.ru/
MIT License
Версии:
v1.1 - убраны дефайны
v1.2 - возвращены дефайны
v1.3 - вычисления ускорены, библиотека облегчена
v2.0 - логика работы чуть переосмыслена, код улучшен, упрощён и облегчён
v2.1 - integral вынесен в public
v2.2 - оптимизация вычислений
v2.3 - добавлен режим PID_INTEGRAL_WINDOW
v2.4 - реализация внесена в класс
v3.0
- Добавлен режим оптимизации интегральной составляющей (см. доку)
- Добавлены автоматические калибровщики коэффициентов (см. примеры и доку)
v3.1 - исправлен режиме ON_RATE, добавлено автоограничение инт. суммы
v3.2 - чуть оптимизации, добавлена getResultNow
v3.3 - в тюнерах можно передать другой обработчик класса Stream для отладки
*/
#ifndef GyverPID_h
#define GyverPID_h
#include <Arduino.h>
#if defined(PID_INTEGER) // расчёты с целыми числами
typedef int datatype;
#else // расчёты с float числами
typedef float datatype;
#endif
#define NORMAL 0
#define REVERSE 1
#define ON_ERROR 0
#define ON_RATE 1
class GyverPID
{
public:
// ==== datatype это float или int, в зависимости от выбранного (см. пример integer_calc) ====
GyverPID() {}
// kp, ki, kd, dt
GyverPID(float new_kp, float new_ki, float new_kd, int new_dt = 60)
{
setDt(new_dt);
Kp = new_kp;
Ki = new_ki;
Kd = new_kd;
prevInput = 0;
integral = 0;
output = 0;
}
// направление регулирования: NORMAL (0) или REVERSE (1)
void setDirection(boolean direction)
{
_direction = direction;
}
// режим: работа по входной ошибке ON_ERROR (0) или по изменению ON_RATE (1)
void setMode(boolean mode)
{
_mode = mode;
}
// лимит выходной величины (например для ШИМ ставим 0-255)
void setLimits(int min_output, int max_output)
{
_minOut = min_output;
_maxOut = max_output;
}
// установка времени дискретизации (для getResultTimer)
void setDt(int new_dt)
{
_dt_s = new_dt;
_dt = new_dt * 1000;
}
datatype setpoint = 0; // заданная величина, которую должен поддерживать регулятор
datatype input = 0; // сигнал с датчика (например температура, которую мы регулируем)
datatype output = 0; // выход с регулятора на управляющее устройство (например величина ШИМ или угол поворота серво)
float Kp = 0.0; // коэффициент P
float Ki = 0.0; // коэффициент I
float Kd = 0.0; // коэффициент D
float integral = 0.0; // интегральная сумма
// возвращает новое значение при вызове (если используем свой таймер с периодом dt!)
datatype getResult()
{
datatype error = setpoint - input; // ошибка регулирования
datatype delta_input = prevInput - input; // изменение входного сигнала за dt
prevInput = input; // запомнили предыдущее
if (_direction)
{ // смена направления
error = -error;
delta_input = -delta_input;
}
output = _mode ? 0 : (error * Kp); // пропорциональая составляющая
output += delta_input * Kd / _dt_s; // дифференциальная составляющая
#if (PID_INTEGRAL_WINDOW > 0)
// ЭКСПЕРИМЕНТАЛЬНЫЙ РЕЖИМ ИНТЕГРАЛЬНОГО ОКНА
if (++t >= PID_INTEGRAL_WINDOW)
t = 0; // перемотка t
integral -= errors[t]; // вычитаем старое
errors[t] = error * Ki * _dt_s; // запоминаем в массив
integral += errors[t]; // прибавляем новое
#else
integral += error * Ki * _dt_s; // обычное суммирование инт. суммы
#endif
#ifdef PID_OPTIMIZED_I
// ЭКСПЕРИМЕНТАЛЬНЫЙ РЕЖИМ ОГРАНИЧЕНИЯ ИНТЕГРАЛЬНОЙ СУММЫ
output = constrain(output, _minOut, _maxOut);
if (Ki != 0)
integral = constrain(integral, (_minOut - output) / (Ki * _dt_s), (_maxOut - output) / (Ki * _dt_s));
#endif
if (_mode)
integral += delta_input * Kp; // режим пропорционально скорости
integral = constrain(integral, _minOut, _maxOut); // ограничиваем инт. сумму
output += integral; // интегральная составляющая
output = constrain(output, _minOut, _maxOut); // ограничиваем выход
return output;
}
// возвращает новое значение не ранее, чем через dt миллисекунд (встроенный таймер с периодом dt)
datatype getResultTimer()
{
if (millis() - pidTimer >= _dt)
{
pidTimer = millis();
getResult();
}
return output;
}
// посчитает выход по реальному прошедшему времени между вызовами функции
datatype getResultNow()
{
setDt(millis() - pidTimer);
pidTimer = millis();
return getResult();
}
private:
int16_t _dt = 100; // время итерации в мс
float _dt_s = 0.1; // время итерации в с
boolean _mode = 0, _direction = 0;
int _minOut = 0, _maxOut = 255;
datatype prevInput = 0;
uint32_t pidTimer = 0;
#if (PID_INTEGRAL_WINDOW > 0)
datatype errors[PID_INTEGRAL_WINDOW];
int t = 0;
#endif
};
#endif

View File

@@ -1,5 +1,6 @@
#include "Global.h"
#include "classes/IoTItem.h"
#include "GyverPID.h"
extern IoTGpio IoTgpio;
@@ -15,6 +16,7 @@ private:
float sp, pv, pv2;
String interim;
int enable = 1;
int _direction = 0;
public:
ThermostatGIST(String parameters) : IoTItem(parameters)
@@ -24,6 +26,7 @@ public:
jsonRead(parameters, "term_rezerv_id", _term_rezerv_id);
jsonRead(parameters, "gist", _gist);
jsonRead(parameters, "rele", _rele);
jsonRead(parameters, "direction", _direction);
}
void doByInterval()
@@ -55,13 +58,31 @@ public:
{
tmp = findIoTItem(_rele);
if (tmp)
tmp->setValue("0", true);
{
if (_direction)
{
tmp->setValue("0", true);
}
else
{
tmp->setValue("1", true);
}
}
}
if (pv <= sp - _gist && enable)
{
tmp = findIoTItem(_rele);
if (tmp)
tmp->setValue("1", true);
{
if (_direction)
{
tmp->setValue("1", true);
}
else
{
tmp->setValue("0", true);
}
}
}
}
else
@@ -86,13 +107,31 @@ public:
{
tmp = findIoTItem(_rele);
if (tmp)
tmp->setValue("0", true);
{
if (_direction)
{
tmp->setValue("0", true);
}
else
{
tmp->setValue("1", true);
}
}
}
if (pv2 <= sp - _gist && enable)
{
tmp = findIoTItem(_rele);
if (tmp)
tmp->setValue("1", true);
{
if (_direction)
{
tmp->setValue("1", true);
}
else
{
tmp->setValue("0", true);
}
}
}
}
else
@@ -144,23 +183,40 @@ public:
return {};
}
~ThermostatGIST(){};
~ThermostatGIST() {};
};
GyverPID *regulator = nullptr;
GyverPID *instanceregulator(float _KP, float _KI, float _KD, int interval, boolean setDirection, int setLimitsMIN, int setLimitsMAX)
{
if (!regulator)
{ // Если библиотека ранее инициализировалась, то просто вернем указатель
// Инициализируем библиотеку
regulator = new GyverPID(_KP, _KI, _KD, interval); // коэф. П, коэф. И, коэф. Д, период дискретизации dt (с)
regulator->setDirection(setDirection); // направление регулирования (NORMAL/REVERSE). ПО УМОЛЧАНИЮ СТОИТ NORMAL
regulator->setLimits(setLimitsMIN, setLimitsMAX); // пределы. ПО УМОЛЧАНИЮ СТОЯТ 0 И 100
SerialPrint("i", F("ThermostatPID"), " _KP:" + String(_KP) + " _KI:" + String(_KI) + " _KD:" + String(_KD) + " interval:" + String(interval) + " _setLimitsMIN:" + String(setLimitsMIN) + " _setLimitsMAX:" + String(setLimitsMAX) + " Direction:" + String(setDirection));
// GyverPID regulator(_KP, _KI, _KD, interval);
}
return regulator;
}
class ThermostatPID : public IoTItem
{
private:
String _set_id; // заданная температура
String _term_id; // термометр
String _term_rezerv_id; // резервный термометр
float _int, _KP, _KI, _KD, sp, pv,
String _set_id; // заданная температура
String _term_id; // термометр
boolean _setDirection;
float _int, _KP, _KI, _KD,
sp, pv,
pv_last = 0, // предыдущая температура
ierr = 0, // интегральная погрешность
dt = 0; // время между измерениями
String _rele; // реле
String interim;
int enable = 1;
long interval;
int interval, _setLimitsMIN, _setLimitsMAX;
IoTItem *tmp;
int releState = 0;
@@ -169,20 +225,29 @@ public:
{
jsonRead(parameters, "set_id", _set_id);
jsonRead(parameters, "term_id", _term_id);
jsonRead(parameters, "term_rezerv_id", _term_rezerv_id);
jsonRead(parameters, "int", _int);
jsonRead(parameters, "KP", _KP);
jsonRead(parameters, "KI", _KI);
jsonRead(parameters, "KD", _KD);
jsonRead(parameters, F("int"), interval);
interval = interval * 1000; // интервал проверки в сек
jsonRead(parameters, "rele", _rele);
// GyverPID
jsonRead(parameters, "setDirection", _setDirection);
jsonRead(parameters, "setLimitsMIN", _setLimitsMIN);
jsonRead(parameters, "setLimitsMAX", _setLimitsMAX);
// в процессе работы можно менять коэффициенты
// instanceregulator(_KP, _KI, _KD, interval)->Kp = _KP;
// instanceregulator(_KP, _KI, _KD, interval)->Ki = _KI;
// instanceregulator(_KP, _KI, _KD, interval)->Kd = _KD;
}
protected:
//===============================================================
// Вычисляем температуру контура отпления, коэффициенты ПИД регулятора
//===============================================================
/*
float pid(float sp, float pv, float pv_last, float &ierr, float dt)
{
float Kc = _KP; // K / %Heater 5
@@ -217,11 +282,10 @@ protected:
// выход регулятора, он же уставка для ID-1 (температура теплоносителя контура СО котла)
op = constrain(op, oplo, ophi);
}
ierr = I;
return op;
}
*/
void
doByInterval()
{
@@ -239,32 +303,24 @@ protected:
interim = tmp->getValue();
pv = ::atof(interim.c_str());
}
if (pv < -40 && pv > 120 && !pv) // Решаем что ошибка датчика
if (enable)
{
if (_term_rezerv_id != "")
{
tmp = findIoTItem(_term_rezerv_id); // используем резервный
if (tmp)
{
interim = tmp->getValue();
pv = ::atof(interim.c_str());
if (pv < -40 && pv > 120 && !pv)
pv = 0;
}
else
pv = 0;
}
else
pv = 0;
}
if (sp && pv)
{
// value.valD = pid(sp, pv, pv_last, ierr, _int);
// value.valS = (String)(int)value.valD;
regEvent(pid(sp, pv, pv_last, ierr, _int), "ThermostatPID", false, true);
// regEvent(pid(sp, pv, pv_last, ierr, _int), "ThermostatPID", false, true);
// instanceregulator(_KP, _KI, _KD, interval,_setDirection,_setLimitsMIN,_setLimitsMAX)->setDirection(_setDirection); // направление регулирования (NORMAL/REVERSE). ПО УМОЛЧАНИЮ СТОИТ NORMAL
// instanceregulator(_KP, _KI, _KD, interval,_setDirection,_setLimitsMIN,_setLimitsMAX)->setLimits(_setLimitsMIN, _setLimitsMAX); // пределы. ПО УМОЛЧАНИЮ СТОЯТ 0 И 100
// instanceregulator(_KP, _KI, _KD, interval)->setMode(1);
instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX)->setpoint = sp;
instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX)->input = pv;
value.valD = instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX)->getResult();
SerialPrint("i", F("ThermostatPID"), " _KP:" + String(_KP) + " _KI:" + String(_KI) + " _KD:" + String(_KD) + " interval:" + String(interval) + " _setLimitsMIN:" + String(_setLimitsMIN) + " _setLimitsMAX:" + String(_setLimitsMAX) + " Direction:" + String(_setDirection));
SerialPrint("i", F("ThermostatPID"), "setpoint: " + String(sp) + " input: " + String(pv));
regEvent(value.valD, "ThermostatPID", false, true);
}
else
regEvent(0, "ThermostatPID", false, true);
{
value.valD = 0;
regEvent(value.valD, "ThermostatPID", false, true);
}
pv_last = pv;
}
@@ -280,14 +336,14 @@ protected:
currentMillis = millis();
difference = currentMillis - prevMillis;
if (_rele != "" && enable && value.valD * interval / 100000 > difference / 1000 && releState == 0)
if (_rele != "" && enable && value.valD * interval / 100 > difference / 1000 && releState == 0)
{
releState = 1;
tmp = findIoTItem(_rele);
if (tmp)
tmp->setValue("1", true);
}
if (_rele != "" && enable && value.valD * interval / 100000 < difference / 1000 && releState == 1)
if (_rele != "" && enable && value.valD * interval / 100 < difference / 1000 && releState == 1)
{
releState = 0;
tmp = findIoTItem(_rele);
@@ -295,7 +351,7 @@ protected:
tmp->setValue("0", true);
}
if (difference >= interval)
if (difference >= interval * 1000)
{
prevMillis = millis();
this->doByInterval();
@@ -311,6 +367,32 @@ protected:
if (param.size())
{
enable = param[0].valD;
if (enable == 0)
{
delete regulator;
regulator = nullptr;
// instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX);
}
}
}
if (command == "setLimitsMIN")
{
if (param.size())
{
_setLimitsMIN = param[0].valD;
// delete regulator;
// regulator = nullptr;
// instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX);
}
}
if (command == "setLimitsMAX")
{
if (param.size())
{
_setLimitsMAX = param[0].valD;
// delete regulator;
// regulator = nullptr;
// instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX);
}
}
if (command == "KP")
@@ -318,6 +400,9 @@ protected:
if (param.size())
{
_KP = param[0].valD;
delete regulator;
regulator = nullptr;
instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX);
}
}
if (command == "KI")
@@ -325,6 +410,9 @@ protected:
if (param.size())
{
_KI = param[0].valD;
delete regulator;
regulator = nullptr;
instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX);
}
}
if (command == "KD")
@@ -332,12 +420,30 @@ protected:
if (param.size())
{
_KD = param[0].valD;
delete regulator;
regulator = nullptr;
instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX);
}
}
if (command == "setDirection")
{
if (param.size())
{
_setDirection = param[0].valD;
delete regulator;
regulator = nullptr;
instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX);
}
}
}
return {};
}
~ThermostatPID(){};
~ThermostatPID()
{
delete regulator;
regulator = nullptr;
};
};
class ThermostatETK : public IoTItem
@@ -395,7 +501,7 @@ protected:
}
}
~ThermostatETK(){};
~ThermostatETK() {};
};
class ThermostatETK2 : public IoTItem
@@ -471,7 +577,7 @@ protected:
}
}
~ThermostatETK2(){};
~ThermostatETK2() {};
};
void *getAPI_Thermostat(String subtype, String param)
@@ -495,4 +601,4 @@ void *getAPI_Thermostat(String subtype, String param)
//}
return nullptr;
}
}

View File

@@ -16,8 +16,9 @@
"set_id": "",
"term_id": "",
"term_rezerv_id": "",
"gist": 0.3,
"rele": ""
"gist": 0.1,
"rele": "",
"direction": 0
},
{
"global": 0,
@@ -31,14 +32,16 @@
"descr": "термостат",
"int": 60,
"round": 1,
"map": "1,100,1,100",
"map": "1024,1024,1,100",
"set_id": "",
"term_id": "",
"term_rezerv_id": "",
"rele": "",
"KP": 5.0,
"KI": 50,
"KD": 1.0
"setDirection": 0,
"setLimitsMIN": 0,
"setLimitsMAX": 100,
"KP": 10,
"KI": 0.02,
"KD": 8
},
{
"global": 0,
@@ -75,11 +78,11 @@
],
"about": {
"authorName": "AVAKS",
"authorContact": "https://t.me/@avaks_dev",
"authorContact": "https://t.me/@avaks",
"authorGit": "https://github.com/avaksru",
"specialThanks": "@Serghei63 за работу PID с обычным реле, Serg помощь в тестировании и устранении ошибок",
"moduleName": "Thermostat",
"moduleVersion": "1",
"moduleVersion": "3",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
@@ -129,10 +132,24 @@
"params": [
"thermostat.KD(1) - задает значение коэффициента"
]
},
{
"name": "setLimitsMIN / setLimitsMAX",
"descr": " лимит выходной величины (например для ШИМ ставим 0-255).",
"params": [
"thermostat.setLimitsMIN(1) - задает минимальное значение PID"
]
},
{
"name": "setDirection",
"descr": "направление регулирования: NORMAL (0) или REVERSE (1).",
"params": [
"thermostat.setDirection(1) - задает реверсное регулирование"
]
}
]
},
"defActive": false,
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []

View File

@@ -7,7 +7,7 @@
"moduleName": "Scenario",
"moduleVersion": "1.0",
"title": "Сценарии",
"moduleDesc": "Сценарии позволяют реализовать индивидуальный алгоритм работы контроллера с учетом происходящих событий. Они представляют из себя описательный язык того, что нужно сделать при наступлении того или иного события, учитывая конкретные условия. \nВ базе языка - выражение вида: “Если условие истина, то выполнить одно действие, а если нет, то иное”. При этом проверка такого выражения будет осуществляться только при наступлении события связанного с элементом конфигурации, который упоминается в этом выражении. \nУсловием или действием может быть любое разрешенное выражение. Они все при выполнении возвращают значение. Выражение может состоять из: идентификаторов элементов конфигурации, чисел (целые, дробные и отрицательные), строк в кавычках, операций сравнения < > <= >= == !=, операций присваивания значений =, математических операций +-*/, логических операций &|, комментариев после символа #, функций (в параметрах которых так же могут быть любые разрешенные выражения), конструкции ветвления IfThenElse, группирующие блоки выражений {}",
"moduleDesc": "Сценарии позволяют реализовать индивидуальный алгоритм работы контроллера с учетом происходящих событий. Они представляют из себя описательный язык того, что нужно сделать при наступлении того или иного события, учитывая конкретные условия. \nВ базе языка - выражение вида: “Если условие истина, то выполнить одно действие, а если нет, то иное”. При этом проверка такого выражения будет осуществляться только при наступлении события связанного с элементом конфигурации, который упоминается в этом выражении. \nУсловием или действием может быть любое разрешенное выражение. Они все при выполнении возвращают значение. Выражение может состоять из: идентификаторов элементов конфигурации, чисел (целые, дробные и отрицательные), строк в кавычках, операций сравнения < > <= >= == !=, операций присваивания значений =, математических операций +-*/, логических операций &|, комментариев после символа #, функций (в параметрах которых так же могут быть любые разрешенные выражения), конструкции ветвления IfThenElse, группирующие блоки выражений {}, экранирование символа кавычек и переноса строки",
"funcInfo": [
{
"name": "getIntFromNet",

View File

@@ -35,7 +35,7 @@
},
"title": "A0221AU, A02YYUW Ультразвуковой датчик дальности"
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [],
"esp82*": []

View File

@@ -39,7 +39,7 @@
},
"title": "Acs712 Датчик тока"
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [],
"esp82*": []

View File

@@ -7,33 +7,41 @@ extern IoTGpio IoTgpio;
// для добавления сенсора вам нужно скопировать этот файл и заменить в нем текст AnalogAdc на название вашего сенсора
// Название должно быть уникальным, коротким и отражать суть сенсора.
//ребенок - родитель
class AnalogAdc : public IoTItem {
private:
// ребенок - родитель
class AnalogAdc : public IoTItem
{
private:
//=======================================================================================================
// Секция переменных.
// Это секция где Вы можете объявлять переменные и объекты arduino библиотек, что бы
// впоследствии использовать их в loop и setup
unsigned int _pin;
unsigned int _avgSteps, _avgCount;
unsigned long _avgSumm;
float adCresult;
unsigned int _avgSteps;
unsigned int realSteps;
float _adcAverage = 0;
unsigned int _period = 0;
unsigned long _lastSoundingMillis = 0;
public:
public:
//=======================================================================================================
// setup()
// это аналог setup из arduino. Здесь вы можете выполнять методы инициализации сенсора.
// Такие как ...begin и подставлять в них параметры полученные из web интерфейса.
// Все параметры хранятся в перемененной parameters, вы можете прочитать любой параметр используя jsonRead функции:
// jsonReadStr, jsonReadBool, jsonReadInt
AnalogAdc(String parameters) : IoTItem(parameters) {
AnalogAdc(String parameters) : IoTItem(parameters)
{
_pin = jsonReadInt(parameters, "pin");
_avgSteps = jsonReadInt(parameters, "avgSteps");
if (!_avgSteps) {
if (!_avgSteps)
{
jsonRead(parameters, F("int"), _interval, false);
}
_avgSumm = 0;
_avgCount = 0;
else
{
_period = _interval / _avgSteps;
}
// Serial.println("_period = " + String(_period));
}
//=======================================================================================================
@@ -44,10 +52,19 @@ class AnalogAdc : public IoTItem {
// если у сенсора несколько величин то делайте несколько regEvent
// не используйте delay - помните, что данный loop общий для всех модулей. Если у вас планируется длительная операция, постарайтесь разбить ее на порции
// и выполнить за несколько тактов
void doByInterval() {
if (_avgSteps <= 1) value.valD = IoTgpio.analogRead(_pin);
value.valD = adCresult;///
regEvent(value.valD, "AnalogAdc"); //обязательный вызов хотяб один
void doByInterval()
{
if (_avgSteps <= 1)
value.valD = IoTgpio.analogRead(_pin);
else
{
value.valD = _adcAverage / (float)realSteps;
//Serial.print("value= " + String(value.valD) + " \t");
//Serial.print("adcAverage = " + String(_adcAverage) + " \t");
//Serial.println("realSteps = " + String(realSteps));
realSteps = 0;
}
regEvent(value.valD, "AnalogAdc"); // обязательный вызов хотяб один
}
//=======================================================================================================
@@ -55,17 +72,20 @@ class AnalogAdc : public IoTItem {
// полный аналог loop() из arduino. Нужно помнить, что все модули имеют равный поочередный доступ к центральному loop(), поэтому, необходимо следить
// за задержками в алгоритме и не создавать пауз. Кроме того, данная версия перегружает родительскую, поэтому doByInterval() отключается, если
// не повторить механизм расчета интервалов.
void loop() {
if (_avgSteps > 1) {
if (_avgCount > _avgSteps) {
// value.valD = _avgSumm / (_avgSteps + 1);
adCresult = _avgSumm / (_avgSteps + 1);
_avgSumm = 0;
_avgCount = 0;
}
void loop()
{
if (_avgSteps > 1)
{
_avgSumm = _avgSumm + IoTgpio.analogRead(_pin);
_avgCount++;
if (millis() > _lastSoundingMillis + _period)
{
realSteps++;
int sounding = IoTgpio.analogRead(_pin);
_adcAverage = _adcAverage + sounding; // IoTgpio.analogRead(_pin);
_lastSoundingMillis = millis();
//Serial.print("adc= " + String(sounding) + " \t");
//Serial.println("_adcAverage = " + String(_adcAverage));
}
}
IoTItem::loop();
}
@@ -76,10 +96,14 @@ class AnalogAdc : public IoTItem {
// после замены названия сенсора, на функцию можно не обращать внимания
// если сенсор предполагает использование общего объекта библиотеки для нескольких экземпляров сенсора, то в данной функции необходимо предусмотреть
// создание и контроль соответствующих глобальных переменных
void* getAPI_AnalogAdc(String subtype, String param) {
if (subtype == F("AnalogAdc")) {
void *getAPI_AnalogAdc(String subtype, String param)
{
if (subtype == F("AnalogAdc"))
{
return new AnalogAdc(param);
} else {
}
else
{
return nullptr;
}
}

View File

@@ -42,6 +42,7 @@
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -138,12 +138,19 @@ public:
~BL0937wh(){};
};
#if defined LIBRETINY
void /* ICACHE_RAM_ATTR */ bl0937_cf1_interrupt()
#else
void ICACHE_RAM_ATTR bl0937_cf1_interrupt()
#endif
{
bl0937->cf1_interrupt();
}
#if defined LIBRETINY
void /* ICACHE_RAM_ATTR */ bl0937_cf_interrupt()
#else
void ICACHE_RAM_ATTR bl0937_cf_interrupt()
#endif
{
bl0937->cf_interrupt();
}

View File

@@ -177,16 +177,21 @@ void BL0937::setResistors(double current, double voltage_upstream, double voltag
_calculateDefaultMultipliers();
}
}
#if defined LIBRETINY
void /* ICACHE_RAM_ATTR */ BL0937::cf_interrupt() {
#else
void ICACHE_RAM_ATTR BL0937::cf_interrupt() {
#endif
unsigned long now = micros();
_power_pulse_width = now - _last_cf_interrupt;
_last_cf_interrupt = now;
_pulse_count++;
}
#if defined LIBRETINY
void /* ICACHE_RAM_ATTR */ BL0937::cf1_interrupt() {
#else
void ICACHE_RAM_ATTR BL0937::cf1_interrupt() {
#endif
unsigned long now = micros();
if ((now - _first_cf1_interrupt) > _pulse_timeout) {

View File

@@ -129,9 +129,10 @@
}
]
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -0,0 +1,291 @@
#include "Global.h"
#include "classes/IoTUart.h"
#include "datatypes.h"
//namespace bl0942
//{
class BL0942cmd;
BL0942cmd *BL0942 = nullptr;
DataPacket buffer;
uint8_t inpos = 0xFF;
uint8_t checksum;
uint8_t pubPhase = 0xFF;
bool needUpdate = false;
static const float BL0942_PREF = 596; // taken from tasmota
static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218
static const float BL0942_IREF = 251213.46469622; // 305978/1.218
static const float BL0942_EREF = 3304.61127328; // Measured
static const char *const TAG = "bl0942";
static const uint8_t BL0942_READ_COMMAND = 0x58;
static const uint8_t BL0942_FULL_PACKET = 0xAA;
static const uint8_t BL0942_PACKET_HEADER = 0x55;
static const uint8_t BL0942_WRITE_COMMAND = 0xA8;
static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10;
static const uint8_t BL0942_REG_MODE = 0x18;
static const uint8_t BL0942_REG_SOFT_RESET = 0x19;
static const uint8_t BL0942_REG_USR_WRPROT = 0x1A;
static const uint8_t BL0942_REG_TPS_CTRL = 0x1B;
// TODO: Confirm insialisation works as intended
const uint8_t BL0942_INIT[5][6] = {
// Reset to default
{BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
// Enable User Operation Write
{BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
{BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37},
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
{BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
// 0x181C = Half cycle, Fast RMS threshold 6172
{BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
class BL0942cmd : public IoTUart
{
private:
float i_rms, watt, v_rms, frequency, total_energy_consumption = 0;
// Divide by this to turn into Watt
float power_reference_ = BL0942_PREF;
// Divide by this to turn into Volt
float voltage_reference_ = BL0942_UREF;
// Divide by this to turn into Ampere
float current_reference_ = BL0942_IREF;
// Divide by this to turn into kWh
float energy_reference_ = BL0942_EREF;
public:
BL0942cmd(String parameters) : IoTUart(parameters)
{
/*
jsonRead(parameters, "R_current", CURRENT_RESISTOR);
jsonRead(parameters, "R_upstream", VOLTAGE_RESISTOR_UPSTREAM);
jsonRead(parameters, "R_downstream", VOLTAGE_RESISTOR_DOWNSTREAM);
jsonRead(parameters, "CF_GPIO", BL0942_CF_GPIO);
jsonRead(parameters, "CF1_GPIO", BL0942_CF1_GPIO);
jsonRead(parameters, "SEL_GPIO", BL0942_SEL_GPIO_INV);
jsonRead(parameters, "kfV", _kfV);
jsonRead(parameters, "kfA", _kfA);
jsonRead(parameters, "kfW", _kfW);
*/
for (auto *i : BL0942_INIT)
{
_myUART->write(i, 6);
delay(1);
}
_myUART->flush();
BL0942 = this;
}
void loop()
{
if (_myUART->available())
{
while (_myUART->available())
{
uint8_t in;
_myUART->readBytes(&in, 1);
if (inpos < sizeof(buffer) - 1)
{ // читаем тело пакета
((uint8_t *)(&buffer))[inpos] = in;
inpos++;
checksum += in;
}
else if (inpos < sizeof(buffer))
{ // получили контрольную сумму
inpos++;
checksum ^= 0xFF;
if (in != checksum)
{
//ESP_LOGE(TAG, "BL0942 invalid checksum! 0x%02X != 0x%02X", checksum, in);
SerialPrint("E", "BL0942cmd", "Invalid checksum!", _id);
}
else
{
pubPhase = 0;
}
}
else
{
if (in == BL0942_PACKET_HEADER)
{ // стартовый хидер
((uint8_t *)(&buffer))[0] = BL0942_PACKET_HEADER;
inpos = 1; // начало сохранения буфера
checksum = BL0942_READ_COMMAND + BL0942_PACKET_HEADER; // начальные данные рассчета кс
pubPhase = 3;
}
else
{
//ESP_LOGE(TAG, "Invalid data. Header mismatch: %d", in);
SerialPrint("E", "BL0942cmd", "Invalid data. Header mismatch", _id);
}
}
}
}
else if (pubPhase < 3)
{
if (pubPhase == 0)
{
i_rms = (uint24_t)buffer.i_rms / current_reference_;
watt = (int24_t)buffer.watt / power_reference_;
pubPhase = 1;
}
else if (pubPhase == 1)
{
v_rms = (uint24_t)buffer.v_rms / voltage_reference_;
frequency = 1000000.0f / buffer.frequency;
pubPhase = 2;
}
else if (pubPhase == 2)
{
uint32_t cf_cnt = (uint24_t)buffer.cf_cnt;
total_energy_consumption = cf_cnt / energy_reference_;
pubPhase = 3;
}
}
IoTItem::loop();
}
void doByInterval()
{
_myUART->write(BL0942_READ_COMMAND);
_myUART->write(BL0942_FULL_PACKET);
}
float getEnergy() { return total_energy_consumption; }
float getPower() { return watt; }
float getCurrent() { return i_rms; }
float getVoltage() { return v_rms; }
~BL0942cmd(){
};
};
class BL0942v : public IoTItem
{
private:
public:
BL0942v(String parameters) : IoTItem(parameters)
{
}
void doByInterval()
{
if (BL0942)
regEvent(BL0942->getVoltage(), "BL0942 V");
else
{
regEvent(NAN, "BL0942v");
SerialPrint("E", "BL0942cmd", "initialization error", _id);
}
}
~BL0942v(){};
};
class BL0942a : public IoTItem
{
private:
public:
BL0942a(String parameters) : IoTItem(parameters)
{
}
void doByInterval()
{
if (BL0942)
regEvent(BL0942->getCurrent(), "BL0942 A");
else
{
regEvent(NAN, "BL0942a");
SerialPrint("E", "BL0942cmd", "initialization error", _id);
}
}
~BL0942a(){};
};
class BL0942w : public IoTItem
{
private:
public:
BL0942w(String parameters) : IoTItem(parameters)
{
}
void doByInterval()
{
if (BL0942)
regEvent(BL0942->getPower(), "BL0942 W");
else
{
regEvent(NAN, "BL0942w");
SerialPrint("E", "BL0942cmd", "initialization error", _id);
}
}
~BL0942w(){};
};
class BL0942wh : public IoTItem
{
private:
public:
BL0942wh(String parameters) : IoTItem(parameters)
{
}
void doByInterval()
{
if (BL0942)
regEvent(BL0942->getEnergy() / 3600.0 / 1000.0, "BL0942 Wh");
else
{
regEvent(NAN, "BL0942wh");
SerialPrint("E", "BL0942cmd", "initialization error", _id);
}
}
~BL0942wh(){};
};
//} // namespace bl0942
void *getAPI_BL0942(String subtype, String param)
{
if (subtype == F("BL0942v"))
{
return new BL0942v(param);
}
else if (subtype == F("BL0942a"))
{
return new BL0942a(param);
}
else if (subtype == F("BL0942w"))
{
return new BL0942w(param);
}
else if (subtype == F("BL0942wh"))
{
return new BL0942wh(param);
}
else if (subtype == F("BL0942cmd"))
{
return new BL0942cmd(param);
}
else
{
return nullptr;
}
}

View File

@@ -0,0 +1,116 @@
#pragma once
#include <cstdint>
//#include "helpers.h"
namespace internal {
// Various functions can be constexpr in C++14, but not in C++11 (because their body isn't just a return statement).
// Define a substitute constexpr keyword for those functions, until we can drop C++11 support.
#if __cplusplus >= 201402L
#define constexpr14 constexpr
#else
#define constexpr14 inline // constexpr implies inline
#endif
// std::byteswap from C++23
template<typename T> constexpr14 T byteswap(T n) {
T m;
for (size_t i = 0; i < sizeof(T); i++)
reinterpret_cast<uint8_t *>(&m)[i] = reinterpret_cast<uint8_t *>(&n)[sizeof(T) - 1 - i];
return m;
}
template<> constexpr14 uint8_t byteswap(uint8_t n) { return n; }
template<> constexpr14 uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); }
template<> constexpr14 uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); }
template<> constexpr14 uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); }
template<> constexpr14 int8_t byteswap(int8_t n) { return n; }
template<> constexpr14 int16_t byteswap(int16_t n) { return __builtin_bswap16(n); }
template<> constexpr14 int32_t byteswap(int32_t n) { return __builtin_bswap32(n); }
template<> constexpr14 int64_t byteswap(int64_t n) { return __builtin_bswap64(n); }
/// Convert a value between host byte order and big endian (most significant byte first) order.
template<typename T> constexpr14 T convert_big_endian(T val) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return byteswap(val);
#else
return val;
#endif
}
/// Convert a value between host byte order and little endian (least significant byte first) order.
template<typename T> constexpr14 T convert_little_endian(T val) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return val;
#else
return byteswap(val);
#endif
}
/// Wrapper class for memory using big endian data layout, transparently converting it to native order.
template<typename T> class BigEndianLayout {
public:
constexpr14 operator T() { return convert_big_endian(val_); }
private:
T val_;
} __attribute__((packed));
/// Wrapper class for memory using big endian data layout, transparently converting it to native order.
template<typename T> class LittleEndianLayout {
public:
constexpr14 operator T() { return convert_little_endian(val_); }
private:
T val_;
} __attribute__((packed));
} // namespace internal
/// 24-bit unsigned integer type, transparently converting to 32-bit.
struct uint24_t { // NOLINT(readability-identifier-naming)
operator uint32_t() { return val; }
uint32_t val : 24;
} __attribute__((packed));
/// 24-bit signed integer type, transparently converting to 32-bit.
struct int24_t { // NOLINT(readability-identifier-naming)
operator int32_t() { return val; }
int32_t val : 24;
} __attribute__((packed));
// Integer types in big or little endian data layout.
using uint64_be_t = internal::BigEndianLayout<uint64_t>;
using uint32_be_t = internal::BigEndianLayout<uint32_t>;
using uint24_be_t = internal::BigEndianLayout<uint24_t>;
using uint16_be_t = internal::BigEndianLayout<uint16_t>;
using int64_be_t = internal::BigEndianLayout<int64_t>;
using int32_be_t = internal::BigEndianLayout<int32_t>;
using int24_be_t = internal::BigEndianLayout<int24_t>;
using int16_be_t = internal::BigEndianLayout<int16_t>;
using uint64_le_t = internal::LittleEndianLayout<uint64_t>;
using uint32_le_t = internal::LittleEndianLayout<uint32_t>;
using uint24_le_t = internal::LittleEndianLayout<uint24_t>;
using uint16_le_t = internal::LittleEndianLayout<uint16_t>;
using int64_le_t = internal::LittleEndianLayout<int64_t>;
using int32_le_t = internal::LittleEndianLayout<int32_t>;
using int24_le_t = internal::LittleEndianLayout<int24_t>;
using int16_le_t = internal::LittleEndianLayout<int16_t>;
struct DataPacket {
uint8_t frame_header;
uint24_le_t i_rms;
uint24_le_t v_rms;
uint24_le_t i_fast_rms;
int24_le_t watt;
uint24_le_t cf_cnt;
uint16_le_t frequency;
uint8_t reserved1;
uint8_t status;
uint8_t reserved2;
uint8_t reserved3;
uint8_t checksum;
} __attribute__((packed));

View File

@@ -0,0 +1,108 @@
{
"menuSection": "sensors",
"configItem": [
{
"global": 0,
"name": "BL0942 Напряжение",
"type": "Reading",
"subtype": "BL0942v",
"id": "bl_v",
"widget": "anydataVlt",
"page": "BL0942",
"descr": "Напряжение",
"int": 15,
"round": 1
},
{
"global": 0,
"name": "BL0942 Сила тока",
"type": "Reading",
"subtype": "BL0942a",
"id": "bl_a",
"widget": "anydataAmp",
"page": "BL0942",
"descr": "Сила тока",
"int": 15,
"round": 1
},
{
"global": 0,
"name": "BL0942 Мощность",
"type": "Reading",
"subtype": "BL0942w",
"id": "bl_w",
"widget": "anydataWt",
"page": "BL0942",
"descr": "Мощность",
"int": 15,
"round": 1
},
{
"global": 0,
"name": "BL0942 Энергия",
"type": "Reading",
"subtype": "BL0942wh",
"id": "bl_wh",
"widget": "anydataWth",
"page": "BL0942",
"descr": "Энергия",
"int": 15,
"round": 1
},
{
"global": 0,
"name": "BL0942 настройка",
"type": "Reading",
"subtype": "BL0942cmd",
"id": "bl_set",
"widget": "nil",
"page": "",
"descr": "",
"int": "5",
"tx": 17,
"rx": 16,
"line": 2,
"speed": 9600
}
],
"about": {
"authorName": "Bubnov Mikhail",
"authorContact": "https://t.me/Mit4bmw",
"authorGit": "https://github.com/Mit4el",
"specialThanks": "",
"moduleName": "BL0942",
"moduleVersion": "1.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"title": "Счетчик электроэнергии BL0942",
"moduleDesc": "Считает потраченную электроэнергию, измеряет напряжение, силу тока и прочие параметры.",
"propInfo": {
"int": "Количество секунд между опросами датчика. В bl_set интервал между попытками калибровки (т.к. нужны сначала данные от датчика)",
"btn-reset": "Энергия BL0942 будет сброшена к нулю.",
"R_current": "Резистор подключенный последовательно к основной линии",
"R_upstream": "это 5 резисторов по 470 Ком в делителе напряжения, который питает вывод V2P",
"R_downstream": "это резистор емкостью 1 Ком в делителе напряжения, который питает вывод V2P",
"CF_GPIO": "пин CF",
"CF1_GPIO": "пин CF1",
"SEL_GPIO": "пин SEL",
"kfV": "Коэффициент корректировки напряжение, указать после калибровки",
"kfA": "Коэффициент корректировки тока, указать после калибровки",
"kfW": "Коэффициент корректировки мощности, указать после калибровки"
},
"funcInfo": [
{
"name": "calibration",
"descr": "Расчет коэффициентов калибровки. Вызывать от имени BL0942 настройка. bl_set.calibration(220, 16, 3.5). Полученный коэффициенты искать в логе и ввести в конфигурацию kfV, kfA и kfW",
"params": ["Напряжение, Ток, Мощность"]
}
]
},
"defActive": false,
"usedLibs": {
"esp32*": [],
"esp82*": [],
"bk72*": []
}
}

View File

@@ -178,7 +178,11 @@ void scanEndedCB(NimBLEScanResults results)
// pBLEScan->clearResults();
}
class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks
//#if defined (esp32c6_4mb) || defined (esp32c6_8mb)
//class BleScan : public IoTItem, NimBLEScanCallbacks
//#else
class BleScan : public IoTItem, NimBLEScanCallbacks //BLEAdvertisedDeviceCallbacks //NimBLEScanCallbacks
//#endif
{
private:
// описание параметров передаваемых из настроек датчика из веба
@@ -201,7 +205,7 @@ public:
return spr;
}
void onResult(BLEAdvertisedDevice *advertisedDevice)
void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override
{
JsonObject BLEdata = doc.to<JsonObject>();
String mac_adress_ = advertisedDevice->getAddress().toString().c_str();
@@ -214,11 +218,20 @@ public:
}
if (advertisedDevice->haveManufacturerData())
{
#if defined (esp32c6_4mb) || defined (esp32c6_8mb)
char *manufacturerdata = BLEUtils::buildHexData(NULL, (uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length());
#else
std::string manufacturerdata = NimBLEUtils::dataToHexString((uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length());
#endif
BLEdata["manufacturerdata"] = manufacturerdata;
#if defined (esp32c6_4mb) || defined (esp32c6_8mb)
free(manufacturerdata);
#endif
}
if (advertisedDevice->haveRSSI())
//#if !defined (esp32c6_4mb) && !defined (esp32c6_8mb) //&& !defined (esp32_4mb3f)
// if (advertisedDevice->haveRSSI())
//#endif
BLEdata["rssi"] = (int)advertisedDevice->getRSSI();
if (advertisedDevice->haveTXPower())
BLEdata["txpower"] = (int8_t)advertisedDevice->getTXPower();
@@ -243,7 +256,6 @@ public:
mac_address = BLEdata["id"].as<const char *>();
}
mac_address.replace(":", "");
if (_debug < 2)
{
BLEdata.remove("manufacturerdata");
@@ -292,12 +304,17 @@ public:
BleScan(String parameters) : IoTItem(parameters)
{
_scanDuration = jsonReadInt(parameters, "scanDuration");
_scanDuration = _scanDuration * 1000;
_filter = jsonReadStr(parameters, "filter");
jsonRead(parameters, "debug", _debug);
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); // create new scan
pBLEScan->setAdvertisedDeviceCallbacks(this);
//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f)
pBLEScan->setScanCallbacks(this);
//#else
// pBLEScan->setAdvertisedDeviceCallbacks(this);
//#endif
pBLEScan->setActiveScan(false); // active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value
@@ -312,7 +329,11 @@ public:
if (_scanDuration > 0)
{
SerialPrint("i", F("BLE"), "Start Scanning...");
pBLEScan->start(_scanDuration, scanEndedCB, false);
//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f)
pBLEScan->start(_scanDuration, false);
//#else
// pBLEScan->start(_scanDuration, scanEndedCB, false);
//#endif
}
}
}

View File

@@ -64,8 +64,14 @@
},
"defActive": false,
"usedLibs": {
"esp32c6_4mb": [
"https://github.com/Mit4el/NimBLE-Arduino.git#c6-build"
],
"esp32c6_8mb": [
"https://github.com/Mit4el/NimBLE-Arduino.git#c6-build"
],
"esp32*": [
"https://github.com/Mit4el/NimBLE-Arduino.git"
"https://github.com/Mit4el/NimBLE-Arduino.git#release/2.2"
],
"esp32s2_4mb": [
"exclude"

View File

@@ -178,7 +178,11 @@ void scanEndedCB(NimBLEScanResults results)
// pBLEScan->clearResults();
}
class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks
//#if defined (esp32c6_4mb) || defined (esp32c6_8mb)
//class BleScan : public IoTItem, NimBLEScanCallbacks
//#else
class BleScan : public IoTItem, NimBLEScanCallbacks//BLEAdvertisedDeviceCallbacks //NimBLEScanCallbacks
//#endif
{
private:
// описание параметров передаваемых из настроек датчика из веба
@@ -201,24 +205,33 @@ public:
return spr;
}
void onResult(BLEAdvertisedDevice *advertisedDevice)
void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override
{
JsonObject BLEdata = doc.to<JsonObject>();
String mac_adress_ = advertisedDevice->getAddress().toString().c_str();
mac_adress_.toUpperCase();
BLEdata["id"] = (char *)mac_adress_.c_str();
//SerialPrint("i", F("BLE"), "FOUND "+ mac_adress_);
if (advertisedDevice->haveName())
{
BLEdata["name"] = (char *)advertisedDevice->getName().c_str();
}
if (advertisedDevice->haveManufacturerData())
{
#if defined (esp32c6_4mb) || defined (esp32c6_8mb)
char *manufacturerdata = BLEUtils::buildHexData(NULL, (uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length());
#else
std::string manufacturerdata = NimBLEUtils::dataToHexString((uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length());
#endif
BLEdata["manufacturerdata"] = manufacturerdata;
#if defined (esp32c6_4mb) || defined (esp32c6_8mb)
free(manufacturerdata);
#endif
}
if (advertisedDevice->haveRSSI())
//#if !defined (esp32c6_4mb) && !defined (esp32c6_8mb) //&& !defined (esp32_4mb3f)
// if (advertisedDevice->haveRSSI())
//#endif
BLEdata["rssi"] = (int)advertisedDevice->getRSSI();
if (advertisedDevice->haveTXPower())
BLEdata["txpower"] = (int8_t)advertisedDevice->getTXPower();
@@ -291,12 +304,17 @@ public:
BleScan(String parameters) : IoTItem(parameters)
{
_scanDuration = jsonReadInt(parameters, "scanDuration");
_scanDuration = _scanDuration * 1000;
_filter = jsonReadStr(parameters, "filter");
jsonRead(parameters, "debug", _debug);
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); // create new scan
pBLEScan->setAdvertisedDeviceCallbacks(this);
//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f)
pBLEScan->setScanCallbacks(this);
//#else
// pBLEScan->setAdvertisedDeviceCallbacks(this);
//#endif
pBLEScan->setActiveScan(false); // active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value
@@ -311,7 +329,11 @@ public:
if (_scanDuration > 0)
{
SerialPrint("i", F("BLE"), "Start Scanning...");
pBLEScan->start(_scanDuration, scanEndedCB, false);
//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f)
pBLEScan->start(_scanDuration, false);
//#else
// pBLEScan->start(_scanDuration, scanEndedCB, false);
//#endif
}
}
}

View File

@@ -64,8 +64,14 @@
},
"defActive": false,
"usedLibs": {
"esp32c6_4mb": [
"https://github.com/Mit4el/NimBLE-Arduino.git#c6-build"
],
"esp32c6_8mb": [
"https://github.com/Mit4el/NimBLE-Arduino.git#c6-build"
],
"esp32*": [
"https://github.com/Mit4el/NimBLE-Arduino.git"
"https://github.com/Mit4el/NimBLE-Arduino.git#release/2.2"
],
"esp32s2_4mb": [
"exclude"

View File

@@ -178,7 +178,11 @@ void scanEndedCB(NimBLEScanResults results)
// pBLEScan->clearResults();
}
class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks
//#if defined (esp32c6_4mb) || defined (esp32c6_8mb)
//class BleScan : public IoTItem, NimBLEScanCallbacks
//#else
class BleScan : public IoTItem, NimBLEScanCallbacks //BLEAdvertisedDeviceCallbacks //NimBLEScanCallbacks
//#endif
{
private:
// описание параметров передаваемых из настроек датчика из веба
@@ -201,7 +205,7 @@ public:
return spr;
}
void onResult(BLEAdvertisedDevice *advertisedDevice)
void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override
{
JsonObject BLEdata = doc.to<JsonObject>();
String mac_adress_ = advertisedDevice->getAddress().toString().c_str();
@@ -214,11 +218,20 @@ public:
}
if (advertisedDevice->haveManufacturerData())
{
#if defined (esp32c6_4mb) || defined (esp32c6_8mb)
char *manufacturerdata = BLEUtils::buildHexData(NULL, (uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length());
#else
std::string manufacturerdata = NimBLEUtils::dataToHexString((uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length());
#endif
BLEdata["manufacturerdata"] = manufacturerdata;
#if defined (esp32c6_4mb) || defined (esp32c6_8mb)
free(manufacturerdata);
#endif
}
if (advertisedDevice->haveRSSI())
//#if !defined (esp32c6_4mb) && !defined (esp32c6_8mb) //&& !defined (esp32_4mb3f)
// if (advertisedDevice->haveRSSI())
//#endif
BLEdata["rssi"] = (int)advertisedDevice->getRSSI();
if (advertisedDevice->haveTXPower())
BLEdata["txpower"] = (int8_t)advertisedDevice->getTXPower();
@@ -254,7 +267,6 @@ public:
BLEdata.remove("track");
BLEdata.remove("id");
}
// дописываем время прихода пакета данных
BLEdata["last"] = millis();
if (_debug)
@@ -292,12 +304,17 @@ public:
BleScan(String parameters) : IoTItem(parameters)
{
_scanDuration = jsonReadInt(parameters, "scanDuration");
_scanDuration = _scanDuration * 1000;
_filter = jsonReadStr(parameters, "filter");
jsonRead(parameters, "debug", _debug);
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); // create new scan
pBLEScan->setAdvertisedDeviceCallbacks(this);
//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f)
pBLEScan->setScanCallbacks(this);
//#else
// pBLEScan->setAdvertisedDeviceCallbacks(this);
//#endif
pBLEScan->setActiveScan(false); // active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value
@@ -312,7 +329,11 @@ public:
if (_scanDuration > 0)
{
SerialPrint("i", F("BLE"), "Start Scanning...");
pBLEScan->start(_scanDuration, scanEndedCB, false);
//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f)
pBLEScan->start(_scanDuration, false);
//#else
// pBLEScan->start(_scanDuration, scanEndedCB, false);
//#endif
}
}
}

View File

@@ -64,8 +64,14 @@
},
"defActive": false,
"usedLibs": {
"esp32c6_4mb": [
"https://github.com/Mit4el/NimBLE-Arduino.git#c6-build"
],
"esp32c6_8mb": [
"https://github.com/Mit4el/NimBLE-Arduino.git#c6-build"
],
"esp32*": [
"https://github.com/Mit4el/NimBLE-Arduino.git"
"https://github.com/Mit4el/NimBLE-Arduino.git#release/2.2"
],
"esp32s2_4mb": [
"exclude"

View File

@@ -42,6 +42,12 @@
"esp32*": [
"https://github.com/milesburton/Arduino-Temperature-Control-Library"
],
"esp32c6_4mb": [
"https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg"
],
"esp32c6_8mb": [
"https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg"
],
"esp82*": [
"https://github.com/milesburton/Arduino-Temperature-Control-Library"
]

View File

@@ -67,17 +67,6 @@
"defActive": false,
"usedLibs": {
"esp32*": [],
"esp82*": [],
"esp32_4mb": [],
"esp32_4mb3f": [],
"esp32cam_4mb": [],
"esp32c3m_4mb": [],
"esp8266_4mb": [],
"esp8266_1mb": [],
"esp8266_1mb_ota": [],
"esp8285_1mb": [],
"esp8285_1mb_ota": [],
"esp8266_2mb": [],
"esp8266_2mb_ota": []
"esp82*": []
}
}

View File

@@ -75,7 +75,8 @@ public:
for (JsonPair kv : jsonObject)
{
String key = kv.key().c_str();
String val = kv.value();
String val = kv.value().as<String>();
val.trim();
if (_debug)
{
SerialPrint("i", "ExternalMQTT", "Received MAC: " + dev + " key=" + key + " val=" + val);

View File

@@ -252,20 +252,9 @@
]
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32_4mb": [],
"esp32_4mb3f": [],
"esp32cam_4mb": [],
"esp32_16mb": [],
"esp32s2_4mb": [],
"esp8266_4mb": [],
"esp8266_16mb": [],
"esp8266_1mb": [],
"esp8266_1mb_ota": [],
"esp8285_1mb": [],
"esp8285_1mb_ota": [],
"esp8266_2mb": [],
"esp8266_2mb_ota": []
"esp32*": [],
"esp82*": []
}
}

View File

@@ -0,0 +1,399 @@
#include "Global.h"
#include "classes/IoTItem.h"
#include <map>
#include <HardwareSerial.h>
// https://github.com/4-20ma/ModbusMaster
#include <ModbusMaster.h>
// class ModbusUart;
Stream *_modbusUART = nullptr;
int _DIR_PIN = 0;
#define UART_LINE 2
// Modbus stuff
// Данные Modbus по умолчанию
#define MODBUS_RX_PIN 18 // Rx pin
#define MODBUS_TX_PIN 19 // Tx pin
#define MODBUS_SERIAL_BAUD 9600 // Baud rate for esp32 and max485 communication
void modbusPreTransmission()
{
// delay(500);
if (_DIR_PIN)
digitalWrite(_DIR_PIN, HIGH);
}
// Pin 4 made low for Modbus receive mode
// Контакт 4 установлен на низком уровне для режима приема Modbus
void modbusPostTransmission()
{
if (_DIR_PIN)
digitalWrite(_DIR_PIN, LOW);
// delay(500);
}
float readFunctionModBus(const uint8_t &func, const uint8_t &addr, const uint16_t &reg, const uint8_t &count = 1, const bool &isFloat = 0)
{
float retValue = 0;
if (_modbusUART)
{
node.begin(addr, (Stream &)*_modbusUART);
uint8_t result;
// uint16_t data[2] = {0, 0};
uint32_t reading;
switch (func)
{
// Modbus function 0x01 Read Coils
// Функция Modbus 0x01 Чтение Катушек
case 1: // 0x01
count = count > 16 ? 16 : count;
count = count < 1 ? 1 : count;
result = node.readCoils(reg, count);
if (_debug)
{
SerialPrint("I", "ModbusNode", "readCoils, addr: " + String(addr, HEX) + ", reg: " + String(reg, HEX) + " = result: " + String(result, HEX));
}
break;
// Modbus function 0x02 Read Discrete Inputs
// Функция Modbus 0x02 Чтение дискретных входов
case 2: // 0x02
count = count > 16 ? 16 : count;
count = count < 1 ? 1 : count;
result = node.readDiscreteInputs(reg, count);
if (_debug)
{
SerialPrint("I", "ModbusNode", "readDiscreteInputs, addr: " + String(addr, HEX) + ", reg: " + String(reg, HEX) + " = result: " + String(result, HEX));
}
break;
// Modbus function 0x03 Read Holding Registers
case 3: // 0x03
count = count > 2 ? 2 : count;
count = count < 1 ? 1 : count;
result = node.readHoldingRegisters(reg, count);
if (_debug)
{
SerialPrint("I", "ModbusNode", "readHoldingRegisters, addr: " + String(addr, HEX) + ", reg: " + String(reg, HEX) + " = result: " + String(result, HEX));
}
break;
// Modbus function 0x04 Read Input Registers
// Функция Modbus 0x04 Чтение входных регистров
case 4: // 0x04
count = count > 2 ? 2 : count;
count = count < 1 ? 1 : count;
result = node.readInputRegisters(reg, count);
if (_debug)
{
SerialPrint("I", "ModbusNode", "readInputRegisters, addr: " + String(addr, HEX) ", reg: " + String(reg, HEX) + " = result: " + String(result, HEX));
}
break;
default:
SerialPrint("I", "ModbusNode", "Unknown function or not supported: " + String(func, HEX));
break;
}
if (result == node.ku8MBSuccess)
{
if ((func == 3 || func == 4) && countR == 2)
{
reading = node.getResponseBuffer(0x00) |
node.getResponseBuffer(0x01) << 16;
}
else
{
reading = node.getResponseBuffer(0x00);
}
node.clearResponseBuffer();
if (_debug)
{
SerialPrint("I", "ModbusMaster", "Success, Received data, register: " + String(reg) + " = " + String(reading, HEX));
}
if (isFloat)
{
retValue = *(float *)&reading;
}
retValue = reading;
}
else
{
if (_debug)
{
SerialPrint("I", "ModbusMaster", "Failed, Response Code: " + String(result, HEX));
}
}
}
return retValue;
}
ModbusMaster node;
class ModbusNode : public IoTItem
{
private:
// Initialize the ModbusMaster object as node
// Инициализируем объект ModbusMaster как узел
uint8_t _addr = 0; // Адрес слейва от 1 до 247
String _regStr = ""; // Адрес регистра который будем дергать ( по коду от 0х0000 до 0х????)
String _funcStr = ""; // Функция ModBUS
uint8_t _func;
uint16_t _reg = 0;
uint8_t _countReg = 1;
bool _isFloat = 0;
bool _debug; // Дебаг
public:
ModbusNode(String parameters) : IoTItem(parameters)
{
_addr = jsonReadInt(parameters, "addr"); // адреса slave прочитаем с веба
jsonRead(parameters, "reg", _regStr); // адреса регистров прочитаем с веба
jsonRead(parameters, "func", _funcStr); // Функция ModBUS
_countReg = jsonReadInt(parameters, "count");
jsonRead(parameters, "float", _isFloat);
jsonRead(parameters, "debug", _debug);
_func = hexStringToUint8(_funcStr);
_reg = hexStringToUint16(_regStr);
}
void doByInterval()
{
regEvent(readFunctionModBus(_func, _addr, _reg, _countReg, _isFloat), "register");
}
~ModbusNode(){};
};
class ModbusMaster : public IoTItem
{
private:
int _rx = MODBUS_RX_PIN; // адреса прочитаем с веба
int _tx = MODBUS_TX_PIN;
int _baud = MODBUS_SERIAL_BAUD;
String _prot = "SERIAL_8N1";
int protocol = SERIAL_8N1;
int _addr = 0; // Адрес слейва от 1 до 247 ( вроде )
String _regStr = ""; // Адрес регистра который будем дергать ( по коду от 0х0000 до 0х????)
uint16_t _reg = 0;
bool _debug; // Дебаг
public:
ModbusMaster(String parameters) : IoTItem(parameters)
{
_rx = jsonReadInt(parameters, "RX"); // прочитаем с веба
_tx = jsonReadInt(parameters, "TX");
_DIR_PIN = jsonReadInt(parameters, "DIR_PIN");
_baud = jsonReadInt(parameters, "baud");
_prot = jsonReadStr(parameters, "protocol");
jsonRead(parameters, "debug", _debug);
if (_prot == "SERIAL_8N1")
{
protocol = SERIAL_8N1;
}
else if (_prot == "SERIAL_8N2")
{
protocol = SERIAL_8N2;
}
pinMode(_DIR_PIN, OUTPUT);
digitalWrite(_DIR_PIN, LOW);
// Serial2.begin(baud-rate, protocol, RX pin, TX pin);
_modbusUART = new HardwareSerial(UART_LINE);
if (_debug)
{
SerialPrint("I", "ModbusMaster", "baud: " + String(_baud) + ", protocol: " + String(protocol, HEX) + ", RX: " + String(_rx) + ", TX: " + String(_tx));
}
((HardwareSerial *)_modbusUART)->begin(_baud, protocol, _rx, _tx); // выбираем тип протокола, скорость и все пины с веба
((HardwareSerial *)_modbusUART)->setTimeout(200);
node.preTransmission(modbusPreTransmission);
node.postTransmission(modbusPostTransmission);
}
// Комманды из сценария
IoTValue execute(String command, std::vector<IoTValue> &param)
{
IoTValue val;
uint8_t result;
uint32_t reading;
uint16_t _reg = 0;
uint8_t count = 1;
bool isFloat = 0;
if (command == "readInputRegisters") // vout = mb.readInputRegisters(1, "0х0000", 1, 0) - "Адрес","Регистр","Кличество регистров","1-float, 0-long"
{
if (param.size())
{
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
count = (uint8_t)param[2].valD;
count = count > 2 ? 2 : count;
count = count < 1 ? 1 : count;
isFloat = (bool)param[3].valD;
val.valD = readFunctionModBus(0x04, _addr, _reg, count, isFloat);
}
return val;
}
else if (command == "readHoldingRegisters") // vout = mb.readHoldingRegisters(1, "0х0000", 2, 1) - "Адрес","Регистр","Кличество регистров","1-float, 0-long"
{
if (param.size())
{
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
count = (uint8_t)param[2].valD;
count = count > 2 ? 2 : count;
count = count < 1 ? 1 : count;
isFloat = (bool)param[3].valD;
val.valD = readFunctionModBus(0x03, _addr, _reg, count, isFloat);
}
return val;
}
else if (command == "readCoils") // vout = mb.readCoils(1, \"0х0000\", 1) - "Адрес","Регистр","Кличество бит"
{
if (param.size())
{
count = (uint8_t)param[2].valD;
count = count > 16 ? 16 : count;
count = count < 1 ? 1 : count;
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
node.begin(_addr, (Stream &)*_modbusUART);
val.valD = readFunctionModBus(0x01, _addr, _reg, count);
}
return val;
}
else if (command == "readDiscreteInputs") // vout = mb.readDiscreteInputs(1, \"0х0000\", 1) - "Адрес","Регистр","Кличество бит"
{
if (param.size())
{
count = (uint8_t)param[2].valD;
count = count > 16 ? 16 : count;
count = count < 1 ? 1 : count;
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
node.begin(_addr, (Stream &)*_modbusUART);
val.valD = readFunctionModBus(0x02, _addr, _reg, count);
}
return val;
}
else if (command == "writeSingleRegister") // vout = mb.writeSingleRegister(1,"0x0003", 1) - addr, register, state
{
if (param.size())
{
node.begin((uint8_t)param[0].valD, (Stream &)*_modbusUART);
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
// bool state = param[2].valD;
uint16_t state = param[2].valD;
result = node.writeSingleRegister(_reg, state);
if (_debug)
{
SerialPrint("I", "ModbusMaster", "writeSingleRegister, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state) + " = result: " + String(result, HEX));
}
}
// Что можно вернуть в ответ на запись ???
return {};
}
else if (command == "writeSingleCoil") // vout = mb.writeSingleCoil(1,"0x0003", 1) - addr, register, state
{
if (param.size())
{
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
node.begin(_addr, (Stream &)*_modbusUART);
bool state = param[2].valD;
result = node.writeSingleCoil(_reg, state);
if (_debug)
{
SerialPrint("I", "ModbusMaster", "writeSingleCoil, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state) + " = result: " + String(result, HEX));
}
}
// Что можно вернуть в ответ на запись койлов???
return {};
}
else if (command == "writeMultipleCoils") // Пример: mb.writeMultipleCoils(1, \"0х0000\", 4, 3) - будут записаны в четыре бита 0011
{
if (param.size())
{
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
count = (uint8_t)param[2].valD;
count = count > 16 ? 16 : count;
count = count < 1 ? 1 : count;
node.begin(_addr, (Stream &)*_modbusUART);
uint16_t state = param[3].valD;
node.setTransmitBuffer(0, state);
result = node.writeMultipleRegisters(_reg, count);
node.clearTransmitBuffer();
if (_debug)
{
SerialPrint("I", "ModbusMaster", "writeSingleCoil, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state) + " = result: " + String(result, HEX));
}
}
return {};
}
// На данный момент записывает 2(два) регистра!!!!! Подходит для записи float?? Функция 0х10 протокола.
else if (command == "writeMultipleRegisters") // mb.writeMultipleRegisters(1, \"0х0000\", 1234.987)
{
if (param.size())
{
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
node.begin(_addr, (Stream &)*_modbusUART);
float state = param[2].valD;
node.setTransmitBuffer(0, lowWord(state));
node.setTransmitBuffer(1, highWord(state));
result = node.writeMultipleRegisters(_reg, 2);
node.clearTransmitBuffer();
if (_debug)
{
SerialPrint("I", "ModbusMaster", "writeMultipleRegisters, addr: " + String((uint8_t)_addr, HEX) + ", reg: " + String(_reg, HEX) + ", state: " + String(state) + " (" + String(state, HEX) + ")");
}
}
return {};
}
return val;
}
~ModbusMaster()
{
delete _modbusUART;
_modbusUART = nullptr;
};
};
void *getAPI_ModbusRTU(String subtype, String param)
{
if (subtype == F("mbNode"))
{
return new ModbusNode(param);
}
else if (subtype == F("mbMaster"))
{
return new ModbusMaster(param);
}
{
return nullptr;
}
}

View File

@@ -0,0 +1,124 @@
{
"menuSection": "sensors",
"configItem": [
{
"global": 0,
"name": "ModbusNode",
"type": "Reading",
"subtype": "mbNode",
"id": "mbNode",
"widget": "anydataTmp",
"page": "Modbus",
"descr": "Данные modbus",
"int": 5,
"func": "0x03",
"addr": 1,
"reg": "0x0000",
"count": 1,
"float": 1,
"round": 0,
"debug": 1
},
{
"global": 0,
"name": "ModbusMaster",
"type": "Reading",
"subtype": "mbMaster",
"id": "mb",
"widget": "anydataTmp",
"page": "Modbus",
"descr": "Настройки modbus",
"int": 5,
"RX": 18,
"TX": 19,
"DIR_PIN": 4,
"baud": 9600,
"protocol": "SERIAL_8N2",
"debug": 1
}
],
"about": {
"authorName": "Serghei Crasnicov",
"authorContact": "https://t.me/Serghei63",
"authorGit": "https://github.com/Serghei63",
"specialThanks": "Mit4bmw",
"moduleName": "ModbusRTU",
"moduleVersion": "2.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"subTypes": [
"mbNode",
"mbMaster"
],
"title": "ModbusMaster",
"moduleDesc": "Позволяет управлять оборудованием по протоколу modbus",
"propInfo": {
"func": "Функция modbus",
"addr": "Адрес slav",
"reg": "Адрес регистра",
"count": "Количество регистров. В ModbusNode 16-разядные регистры можно считать не более 2 в ноде. Для битовых данных не более 16 бит в ноде. Значение поместится в value Ноды",
"float": "Тип считываемых данных, если count=2, то данные могут быть long (указать 0) или float (указать 1). Числа Long не рекомендуются (на больших числах не будет точности), так как будет ограничено точность представление в IotManager идет во float",
"int": "Количество секунд между опросами датчика.",
"RX": "Пин RX",
"TX": "Пин TX",
"DIR_PIN": "connect DR, RE pin of MAX485 to gpio, указать 0 если не нужен",
"baud": "скорость Uart",
"protocol": "Протокол Uart: SERIAL_8N1 или SERIAL_8N2",
"debug": " 1 - включить вывод дебага , 0 - отключить дебаг"
},
"funcInfo": [
{
"name": "readCoils",
"descr": "Чтение койла (битового поля). Функция 0х01 протокола. Читает не более 16 бит за раз. Пример: mb.readCoils(1, \"0х0000\", 1)",
"params": ["Адрес","Регистр","Кличество бит"]
},
{
"name": "readDiscreteInputs",
"descr": "Чтение дискретного выхода (битового поля). Функция 0х02 протокола. Читает не более 16 бит за раз. Пример: mb.readDiscreteInputs(1, \"0х0000\", 8)",
"params": ["Адрес","Регистр","Кличество бит"]
},
{
"name": "readHoldingRegisters",
"descr": "Запрос данных регистра. Функция 0х03 протокола. Читает не более двух регистров за раз. Пример: mb.readHoldingRegisters(1, \"0х0000\", 1, 0)",
"params": ["Адрес","Регистр","Кличество регистров","1-float, 0-long"]
},
{
"name": "readInputRegisters",
"descr": "Запрос данных регистра. Функция 0х04 протокола. Читает не более двух регистров за раз. Пример: mb.readInputRegisters(1, \"0х0000\", 1, 0)",
"params": ["Адрес","Регистр","Кличество регистров","1-float, 0-long"]
},
{
"name": "writeSingleCoils",
"descr": "Запись в койл (битовое поле) одного бита. Функция 0х05 протокола. Пример: mb.writeSingleCoils(1, \"0х0000\", 1)",
"params": ["Адрес","Регистр","Данные"]
},
{
"name": "writeSingleRegister",
"descr": "Запись данных в один регистр. Функция 0х06 протокола. Пример: mb.writeSingleRegister(1, \"0х0000\", 128)",
"params": ["Адрес","Регистр","Данные"]
},
{
"name": "writeMultipleCoils",
"descr": "Запись данных в несколько койлов до 16 (число от 0 до 65535). Функция 0x0F протокола. Пример: mb.writeMultipleCoils(1, \"0х0000\", 4, 3) - будут записаны в четыре бита 0011",
"params": ["Адрес","Регистр","Кличество койлов (бит)","Данные"]
},
{
"name": "writeMultipleRegisters",
"descr": "Запись данных в несколько регистров. На данный момент записывает 2(два) регистра!!!!! Подходит для записи float?? Функция 0х10 протокола. Пример: mb.writeMultipleRegisters(1, \"0х0000\", 1234.987)",
"params": ["Адрес","Регистр","Данные"]
}
]
},
"defActive": false,
"usedLibs": {
"esp32*": [
"https://github.com/4-20ma/ModbusMaster"
],
"esp82*": [
"https://github.com/4-20ma/ModbusMaster"
]
}
}

View File

@@ -0,0 +1,466 @@
#include "Global.h"
#include "classes/IoTItem.h"
#include <map>
#include <HardwareSerial.h>
#include "Logging.h"
#include "ModbusClientRTU.h"
#include "CoilData.h"
// class ModbusUart;
Stream *_modbusUART = nullptr;
// Данные Modbus по умолчанию
int8_t MODBUS_DIR_PIN = 0;
#define MODBUS_UART_LINE 2
#define MODBUS_RX_PIN 18 // Rx pin
#define MODBUS_TX_PIN 19 // Tx pin
#define MODBUS_SERIAL_BAUD 9600 // Baud rate for esp32 and max485 communication
// bool modBus_data_ready = false;
uint32_t modBus_Token_count = 0; // Счетчик токенов для Нод, 0 - всегду у главного класса, дальше по порядку
class ModbusNode;
std::map<uint32_t, ModbusNode *> MBNoneMap;
ModbusClientRTU *MB = nullptr;
ModbusClientRTU *instanceModBus(int8_t _DR)
{
if (!MB)
{ // Если библиотека ранее инициализировалась, т о просто вернем указатель
// Инициализируем библиотеку
if (_DR)
MB = new ModbusClientRTU(_DR);
else
MB = new ModbusClientRTU();
}
return MB;
}
// ModbusClientRTU MB(_DIR_PIN);
// ModbusClientRTU MB();
class ModbusNode : public IoTItem
{
private:
// Initialize the ModbusMaster object as node
// Инициализируем объект ModbusMaster как узел
uint8_t _addr = 0; // Адрес слейва от 1 до 247
String _regStr = ""; // Адрес регистра который будем дергать ( по коду от 0х0000 до 0х????)
String _funcStr = ""; // Функция ModBUS
uint8_t _func;
uint16_t _reg = 0;
uint8_t _countReg = 1;
uint32_t _token = 0;
bool _isFloat = 0;
CoilData _respCoil;
public:
ModbusNode(String parameters) : IoTItem(parameters)
{
_addr = jsonReadInt(parameters, "addr"); // адреса slave прочитаем с веба
jsonRead(parameters, "reg", _regStr); // адреса регистров прочитаем с веба
jsonRead(parameters, "func", _funcStr); // Функция ModBUS
jsonRead(parameters, "isFloat", _isFloat);
_countReg = jsonReadInt(parameters, "count");
_func = hexStringToUint8(_funcStr);
_reg = hexStringToUint16(_regStr);
modBus_Token_count++;
_token = modBus_Token_count;
MBNoneMap[_token] = this;
Serial.printf("Добавлен нода/токен: %s - %d\n", getID(), _token);
}
void doByInterval()
{
if (!MB)
{
Serial.printf("ModbusNode: ModbusClientAsync is NULL\n");
return;
}
if (_func == 0x04) // vout = mb.readInputRegisters(1, "0х0000", 1, 0) - "Адрес","Регистр","Кличество регистров"
{
// val.valD = readFunctionModBus(0x04, _addr, _reg, count, isFloat);
Serial.printf("sending request with token %d\n", _token);
Error err;
err = MB->addRequest(_token, _addr, READ_INPUT_REGISTER, _reg, _countReg);
if (err != SUCCESS)
{
ModbusError e(err);
Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e);
}
}
else if (_func == 0x03) // vout = mb.readHoldingRegisters(1, "0х0000", 2, 1) - "Адрес","Регистр","Кличество регистров"
{
Serial.printf("sending request with token %d\n", _token);
Error err;
err = MB->addRequest(_token, _addr, READ_HOLD_REGISTER, _reg, _countReg);
if (err != SUCCESS)
{
ModbusError e(err);
Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e);
}
}
else if (_func == 0x01) // vout = mb.readCoils(1, \"0х0000\", 1) - "Адрес","Регистр","Кличество бит"
{
Serial.printf("sending request with token %d\n", _token);
Error err;
err = MB->addRequest(_token, _addr, READ_COIL, _reg, _countReg);
if (err != SUCCESS)
{
ModbusError e(err);
Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e);
}
}
else if (_func == 0x02) // vout = mb.readDiscreteInputs(1, \"0х0000\", 1) - "Адрес","Регистр","Кличество бит"
{
Serial.printf("sending request with token %d\n", _token);
Error err;
err = MB->addRequest(_token, _addr, READ_DISCR_INPUT, _reg, _countReg);
if (err != SUCCESS)
{
ModbusError e(err);
Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e);
}
}
}
void parseMB(ModbusMessage response)
{
if (MB)
{
if (_func == 0x02 || _func == 0x01) // coil
{
uint16_t val;
// response.get(3, val);
// regEvent((float)val, "ModbusNode");
CoilData cd(_countReg);
cd.set(0, _countReg, (uint8_t *)response.data() + 3);
_respCoil = cd;
cd.print("Received : ", Serial);
val = cd[0];
regEvent(val, "ModbusNode");
}
else
{
if (_countReg == 2 && _isFloat)
{
float val;
response.get(3, val);
regEvent(val, "ModbusNode");
}
else
{
if (_countReg == 2)
{
uint16_t val1, val2;
response.get(3, val1);
response.get(5, val2);
Serial.printf("COUNT 2: %02X - %02X\n", (int)val1, (int)val2);
long val = val1 | val2 << 16;
regEvent((float)val, "ModbusNode");
}
else
{
uint16_t val;
response.get(3, val);
regEvent((float)val, "ModbusNode");
}
}
}
}
}
// Комманды из сценария
IoTValue execute(String command, std::vector<IoTValue> &param)
{
IoTValue val;
// uint8_t result;
// uint32_t reading;
uint16_t _index = 0;
if (command == "getBits")
{
if (param.size())
{
if (_respCoil.size() > _index)
{
_index = param[0].valD;
val.valD = _respCoil[_index];
return val;
}
}
}
return {};
}
~ModbusNode()
{
//MBNoneMap.erase(_token);
};
};
// Define an onData handler function to receive the regular responses
// Arguments are received response message and the request's token
void handleModBusData(ModbusMessage response, uint32_t token)
{
printf("Response --- Token:%d FC:%02X Server:%d Length:%d\n",
token,
response.getFunctionCode(),
response.getServerID(),
response.size());
HEXDUMP_N("Data dump", response.data(), response.size());
// // First value is on pos 3, after server ID, function code and length byte
// uint16_t offs = 3;
// // The device has values all as IEEE754 float32 in two consecutive registers
// offs = response.get(offs, values[i]);
// uint16_t val;
// response.get(3, val);
if (MBNoneMap[token])
{
MBNoneMap[token]->parseMB(response);
}
else
{
Serial.printf("Токен/Нода не найден: %d\n", token);
}
// modBus_data_ready = true;
}
// Define an onError handler function to receive error responses
// Arguments are the error code returned and a user-supplied token to identify the causing request
void handleModBusError(Error error, uint32_t token)
{
// ModbusError wraps the error code and provides a readable error message for it
ModbusError me(error);
// LOG_E("Error response: %02X - %s\n", (int)me, (const char *)me);
Serial.printf("Error response: %02X - %s\n", (int)me, (const char *)me);
}
class ModbusClientAsync : public IoTItem
{
private:
int8_t _rx = MODBUS_RX_PIN; // адреса прочитаем с веба
int8_t _tx = MODBUS_TX_PIN;
int _baud = MODBUS_SERIAL_BAUD;
String _prot = "SERIAL_8N1";
int protocol = SERIAL_8N1;
int _addr = 0; // Адрес слейва от 1 до 247 ( вроде )
String _regStr = ""; // Адрес регистра который будем дергать ( по коду от 0х0000 до 0х????)
uint16_t _reg = 0;
bool _debug; // Дебаг
uint32_t _token = 0; // Токен у главного класса весгда 0
public:
ModbusClientAsync(String parameters) : IoTItem(parameters)
{
_rx = (int8_t)jsonReadInt(parameters, "RX"); // прочитаем с веба
_tx = (int8_t)jsonReadInt(parameters, "TX");
MODBUS_DIR_PIN = (int8_t)jsonReadInt(parameters, "DIR_PIN");
_baud = jsonReadInt(parameters, "baud");
_prot = jsonReadStr(parameters, "protocol");
jsonRead(parameters, "debug", _debug);
if (_prot == "SERIAL_8N1")
{
protocol = SERIAL_8N1;
}
else if (_prot == "SERIAL_8N2")
{
protocol = SERIAL_8N2;
}
pinMode(MODBUS_DIR_PIN, OUTPUT);
digitalWrite(MODBUS_DIR_PIN, LOW);
// Serial2.begin(baud-rate, protocol, RX pin, TX pin);
instanceModBus(MODBUS_DIR_PIN);
_modbusUART = new HardwareSerial(MODBUS_UART_LINE);
if (_debug)
{
SerialPrint("I", "ModbusClientAsync", "baud: " + String(_baud) + ", protocol: " + String(protocol, HEX) + ", RX: " + String(_rx) + ", TX: " + String(_tx));
}
RTUutils::prepareHardwareSerial((HardwareSerial &)*_modbusUART);
// Serial2.begin(BAUDRATE, SERIAL_8N1, RXPIN, TXPIN);
((HardwareSerial *)_modbusUART)->begin(_baud, protocol, _rx, _tx); // выбираем тип протокола, скорость и все пины с веба
((HardwareSerial *)_modbusUART)->setTimeout(200);
// Set up ModbusRTU client.
// - provide onData handler function
MB->onDataHandler(&handleModBusData);
// - provide onError handler function
MB->onErrorHandler(&handleModBusError);
// Set message timeout to 2000ms
MB->setTimeout(2000);
// Start ModbusRTU background task
MB->begin((HardwareSerial &)*_modbusUART);
// MBNoneMap[_token] = this;
}
// Комманды из сценария
IoTValue execute(String command, std::vector<IoTValue> &param)
{
IoTValue val;
// uint8_t result;
// uint32_t reading;
uint16_t _reg = 0;
uint8_t count = 1;
if (command == "writeSingleRegister") // vout = mb.writeSingleRegister(1,"0x0003", 1) - addr, register, state
{
if (param.size())
{
// node.begin((uint8_t)param[0].valD, (Stream &)*_modbusUART);
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
// bool state = param[2].valD;
uint16_t state = param[2].valD;
// result = node.writeSingleRegister(_reg, state);
if (_debug)
{
SerialPrint("I", "ModbusClientAsync", "writeSingleRegister, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state));
}
// We will first set the register to a known state, read the register,
// then write to it and finally read it again to verify the change
// Set defined conditions first - write 0x1234 to the register
// The Token value is used in handleData to avoid the output for this first preparation request!
// uint32_t Token = 1111;
Serial.printf("sending request with token %d\n", _token);
Error err;
err = MB->addRequest(_token, _addr, WRITE_HOLD_REGISTER, _reg, state);
if (err != SUCCESS)
{
ModbusError e(err);
Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e);
}
}
// Что можно вернуть в ответ на запись ???
return {};
}
else if (command == "writeSingleCoil") // vout = mb.writeSingleCoil(1,"0x0003", 1) - addr, register, state
{
if (param.size())
{
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
// node.begin(_addr, (Stream &)*_modbusUART);
bool state = param[2].valD;
// result = node.writeSingleCoil(_reg, state);
if (_debug)
{
SerialPrint("I", "ModbusClientAsync", "writeSingleCoil, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state));
}
// next set a single coil at 8
Serial.printf("sending request with token %d\n", _token);
Error err;
ModbusMessage msg;
if (state)
{
msg.setMessage(_addr, WRITE_COIL, _reg, 0xFF00);
err = MB->addRequest(msg, _token);
}
else
{
// msg.setMessage(_addr, WRITE_COIL, _reg, 0x0000);
err = MB->addRequest(_token, _addr, WRITE_COIL, _reg, 0);
}
if (err != SUCCESS)
{
ModbusError e(err);
Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e);
}
}
// Что можно вернуть в ответ на запись койлов???
return {};
}
else if (command == "writeMultipleCoils") // Пример: mb.writeMultipleCoils(1, \"0х0000\", 4, 3) - будут записаны в четыре бита 0011
{
if (param.size())
{
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
count = (uint8_t)param[2].valD;
count = count > 16 ? 16 : count;
count = count < 1 ? 1 : count;
// node.begin(_addr, (Stream &)*_modbusUART);
uint16_t state = param[3].valD;
// node.setTransmitBuffer(0, state);
// result = node.writeMultipleRegisters(_reg, count);
// node.clearTransmitBuffer();
Serial.printf("NOT SUPPORTED!\n");
if (_debug)
{
SerialPrint("I", "ModbusClientAsync", "writeSingleCoil, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state));
}
CoilData cd(12);
// Finally set a a bunch of coils starting at 20
cd = "011010010110";
Serial.printf("sending request with token %d\n", _token);
Error err;
err = MB->addRequest(_token, _addr, WRITE_MULT_COILS, _reg, cd.coils(), cd.size(), cd.data());
if (err != SUCCESS)
{
ModbusError e(err);
Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e);
}
}
return {};
}
// На данный момент записывает 2(два) регистра!!!!! Подходит для записи float?? Функция 0х10 протокола.
else if (command == "writeMultipleRegisters") // mb.writeMultipleRegisters(1, \"0х0000\", 1234.987)
{
if (param.size())
{
_addr = param[0].valD;
_reg = hexStringToUint16(param[1].valS);
// node.begin(_addr, (Stream &)*_modbusUART);
float state = param[2].valD;
// node.setTransmitBuffer(0, lowWord(state));
// node.setTransmitBuffer(1, highWord(state));
// result = node.writeMultipleRegisters(_reg, 2);
// node.clearTransmitBuffer();
Serial.printf("NOT SUPPORTED!\n");
if (_debug)
{
SerialPrint("I", "ModbusClientAsync", "writeMultipleRegisters, addr: " + String((uint8_t)_addr, HEX) + ", reg: " + String(_reg, HEX) + ", state: " + String(state) + " (" + String(state, HEX) + ")");
}
}
return {};
}
return val;
}
~ModbusClientAsync()
{
delete _modbusUART;
_modbusUART = nullptr;
MBNoneMap.clear();
};
};
void *getAPI_ModbusRTUasync(String subtype, String param)
{
if (subtype == F("mbNode"))
{
return new ModbusNode(param);
}
else if (subtype == F("mbClient"))
{
return new ModbusClientAsync(param);
}
{
return nullptr;
}
}

View File

@@ -0,0 +1,103 @@
{
"menuSection": "sensors",
"configItem": [
{
"global": 0,
"name": "ModbusNode",
"type": "Reading",
"subtype": "mbNode",
"id": "mbNode",
"widget": "anydataTmp",
"page": "Modbus",
"descr": "Чтение данных modbus",
"int": 5,
"func": "0x03",
"addr": 1,
"reg": "0x0000",
"count": 1,
"isFloat": 0,
"round": 0
},
{
"global": 0,
"name": "ModbusAsunc",
"type": "Reading",
"subtype": "mbClient",
"id": "mb",
"widget": "anydataTmp",
"page": "Modbus",
"descr": "Настройки и запись modbus",
"int": 5,
"RX": 18,
"TX": 19,
"DIR_PIN": 4,
"baud": 9600,
"protocol": "SERIAL_8N2",
"debug": 1
}
],
"about": {
"authorName": "Bubnov Mikhail",
"authorContact": "https://t.me/Mit4bmw",
"authorGit": "https://github.com/Mit4el",
"specialThanks": "Serghei Crasnicov",
"moduleName": "ModbusRTUasync",
"moduleVersion": "1.1",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"subTypes": [
"mbClient"
],
"title": "ModbusAsync",
"moduleDesc": "Позволяет управлять оборудованием по протоколу modbus. Модуль ModbusAsync обязательный для настройки. Запись через функции сценария в ModbusAsync. Для чтение регистров добавлять модули ModbusNode.",
"propInfo": {
"int": "Количество секунд между опросами датчика.",
"RX": "Пин RX",
"TX": "Пин TX",
"DIR_PIN": "connect DR, RE pin of MAX485 to gpio, указать 0 если не нужен",
"baud": "скорость Uart",
"protocol": "Протокол Uart: SERIAL_8N1 или SERIAL_8N2",
"debug": " 1 - включить вывод дебага , 0 - отключить дебаг",
"func": "Функция чтения modbus (0x01, 0x02, 0x03, 0x04)",
"addr": "Адрес slave",
"reg": "Адрес регистра",
"count": "Количество регистров. В ModbusNode 16-разядные регистры можно считать не более 2 в ноде. Для битовых данных не более 16 бит в ноде. Значение поместится в value Ноды",
"isFloat": "Тип считываемых данных, если count=2, то данные могут быть long (указать 0) или float (указать 1). Числа Long не рекомендуются (на больших числах не будет точности), так как будет ограничено точность представление в IotManager идет во float"
},
"funcInfo": [
{
"name": "writeSingleCoils",
"descr": "Запись в койл (битовое поле) одного бита, вызывать из ModbusAsunc. Функция 0х05 протокола. Пример: mb.writeSingleCoils(1, \"0х0000\", 1)",
"params": ["Адрес","Регистр","Данные"]
},
{
"name": "writeSingleRegister",
"descr": "Запись данных в один регистр, вызывать из ModbusAsunc. Функция 0х06 протокола. Пример: mb.writeSingleRegister(1, \"0х0000\", 128)",
"params": ["Адрес","Регистр","Данные"]
},
{
"name": "writeMultipleCoils",
"descr": "В разработке! Запись данных в несколько койлов до 16 (число от 0 до 65535), вызывать из ModbusAsunc. Функция 0x0F протокола. Пример: mb.writeMultipleCoils(1, \"0х0000\", 4, 3) - будут записаны в четыре бита 0011",
"params": ["Адрес","Регистр","Кличество койлов (бит)","Данные"]
},
{
"name": "writeMultipleRegisters",
"descr": "В разработке! Запись данных в несколько регистров, вызывать из ModbusAsunc. Записывает 2(два) регистра!!!!! Подходит для записи float?? Функция 0х10 протокола. Пример: mb.writeMultipleRegisters(1, \"0х0000\", 1234.987)",
"params": ["Адрес","Регистр","Данные"]
},
{
"name": "getBits",
"descr": "Получить из ModbusNode бит по его номеру, если ранее считали из слейва F0x01 или F0x02 более одного бита. Вернёт -1 если данных нет. Пример: mbNode.getCoil(0) получит первый из считанных бит",
"params": ["индекс бита"]
}
]
},
"defActive": false,
"usedLibs": {
"esp32*": [
"https://github.com/eModBus/eModBus"
]
}
}

View File

@@ -48,7 +48,7 @@
"Beta": "Beta термистора"
}
},
"defActive": false,
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []

View File

@@ -5,10 +5,13 @@
#include <Adafruit_PCF8591.h>
// Make sure that this is set to the value in volts of VCC
#define ADC_REFERENCE_VOLTAGE 3.3
//#define ADC_REFERENCE_VOLTAGE 3.3
// Make sure that this is set to the value in volts of VCC
//#define ADC_REFERENCE_VOLTAGE 5.0
class Pcf8591 : public IoTItem {
int _pin;
float _adc_ref;
bool _isRaw;
bool _isInited = false;
Adafruit_PCF8591 pcf = Adafruit_PCF8591();
@@ -19,6 +22,9 @@ class Pcf8591 : public IoTItem {
jsonRead(parameters, "pin", tmp);
_pin = tmp.toInt();
jsonRead(parameters, "adc_ref", tmp);
_adc_ref = tmp.toFloat();
jsonRead(parameters, "mode", tmp);
_isRaw = tmp == "raw";
@@ -48,7 +54,8 @@ class Pcf8591 : public IoTItem {
if (_isRaw)
value.valD = pcf.analogRead(_pin); // Чтение АЦП нулевого канала (Вольты)
else
value.valD = (int_to_volts(pcf.analogRead(_pin), 8, ADC_REFERENCE_VOLTAGE));
// value.valD = (int_to_volts(pcf.analogRead(_pin), 8, ADC_REFERENCE_VOLTAGE));
value.valD = (int_to_volts(pcf.analogRead(_pin), 8, _adc_ref));
regEvent(value.valD, "PCF8591");
}

View File

@@ -12,6 +12,7 @@
"descr": "PCF_0",
"pin": "0",
"mode": "volt",
"adc_ref": 5.0,
"map": "1,255,1,100",
"plus": 0,
"multiply": 1,

View File

@@ -142,18 +142,9 @@
"btn-reset": "pzem будет сброшен к нулю. Смотрите в логе результат: [i] Pzem reset done"
}
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32_4mb": [],
"esp32_4mb3f": [],
"esp32cam_4mb": [],
"esp32c3m_4mb": [],
"esp8266_4mb": [],
"esp8266_1mb": [],
"esp8266_1mb_ota": [],
"esp8285_1mb": [],
"esp8285_1mb_ota": [],
"esp8266_2mb": [],
"esp8266_2mb_ota": []
"esp32*": [],
"esp82*": []
}
}

View File

@@ -34,7 +34,7 @@
"int": "Количество секунд между опросами датчика."
}
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [],
"esp82*": []

View File

@@ -47,7 +47,7 @@
"int": "Количество секунд между опросами датчика."
}
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [
"robtillaart/SHT2x@^0.1.1"

View File

@@ -13,19 +13,33 @@
#include "Wire.h"
#include <WEMOS_SHT3X.h>
SHT3X sht30(0x45);
SHT3X sht30(0x44);
class Sht30t : public IoTItem {
private:
uint8_t _addr = 0;
public:
Sht30t(String parameters): IoTItem(parameters) { }
Sht30t(String parameters): IoTItem(parameters) {
{
String sAddr;
jsonRead(parameters, "addr", sAddr);
if (sAddr == "")
scanI2C();
else
_addr = hexStringToUint8(sAddr);
}
}
void doByInterval() {
if(sht30.get()==0){
value.valD = sht30.cTemp;
SerialPrint("E", "Sensor Sht30t", "OK");
SerialPrint("i", "Sensor Sht30t", "OK");
if (value.valD < -46.85F) regEvent(value.valD, "Sht30t"); // TODO: найти способ понимания ошибки получения данных
if (value.valD > -46.85F) regEvent(value.valD, "Sht30t"); // TODO: найти способ понимания ошибки получения данных
else SerialPrint("E", "Sensor Sht30t", "Error", _id);
}
}
@@ -33,14 +47,27 @@ class Sht30t : public IoTItem {
};
class Sht30h : public IoTItem {
private:
uint8_t _addr = 0;
public:
Sht30h(String parameters): IoTItem(parameters) { }
Sht30h(String parameters): IoTItem(parameters) {
{
String sAddr;
jsonRead(parameters, "addr", sAddr);
if (sAddr == "")
scanI2C();
else
_addr = hexStringToUint8(sAddr);
}
}
void doByInterval() {
if(sht30.get()==0){
value.valD = sht30.humidity;
SerialPrint("E", "Sensor Sht30h", "OK");
SerialPrint("i", "Sensor Sht30h", "OK");
if (value.valD != -6) regEvent(value.valD, "Sht30h"); // TODO: найти способ понимания ошибки получения данных
else SerialPrint("E", "Sensor Sht30h", "Error", _id);
}

View File

@@ -10,6 +10,7 @@
"widget": "anydataTmp",
"page": "Сенсоры",
"descr": "SHT30 Температура",
"addr": "",
"int": 15,
"round": 1
},
@@ -22,6 +23,7 @@
"widget": "anydataHum",
"page": "Сенсоры",
"descr": "SHT30 Влажность",
"addr": "",
"int": 15,
"round": 1
}
@@ -47,7 +49,7 @@
"int": "Количество секунд между опросами датчика."
}
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [
"WEMOS SHT3x@1.0.0"

View File

@@ -35,7 +35,7 @@
"int": "Количество секунд между опросами датчика."
}
},
"defActive": true,
"defActive": false,
"usedLibs": {
"esp32*": [],
"esp82*": []

View File

@@ -50,6 +50,7 @@
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -0,0 +1,259 @@
#include "Global.h"
#include "classes/IoTDiscovery.h"
class DiscoveryHA : public IoTDiscovery
{
private:
String _topic = "";
bool sendOk = false;
// bool topicOk = false;
bool HA = false;
public:
DiscoveryHA(String parameters) : IoTDiscovery(parameters)
{
_topic = jsonReadStr(parameters, "topic");
if (_topic && _topic != "" && _topic != "null")
{
HA = true;
HATopic = _topic;
}
if (mqttIsConnect() && HA)
{
#if defined ESP32
// пре реконнекте вызывается отправка всех виджетов из файла layout.json в HA
// на ESP8266 мало оперативки и это можно делать только до момента конфигурации
// mqttReconnect();
#endif
// sendOk = true;
// mqttSubscribeExternal(_topic);
}
}
/*
void onMqttRecive(String &topic, String &msg)
{
if (!HA)
return;
if (msg.indexOf("HELLO") == -1)
{
String dev = selectToMarkerLast(topic, "/");
dev.toUpperCase();
dev.replace(":", "");
if (_topic != topic)
{
// SerialPrint("i", "ExternalMQTT", _id + " not equal: " + topic + " msg: " + msg);
return;
}
// обработка топика, на который подписались
}
} */
void doByInterval()
{
/* // периодически проверяем связь с MQTT брокером и если она появилась, то подписываемся на нужный топик
if (mqttIsConnect() && !sendOk && &&topicOk)
{
sendOk = true;
getlayoutHA();
publishRetain(mqttRootDevice + "/state", "{\"status\":\"online\"}");
//mqttSubscribeExternal(_topic);
}
// если нет коннектас брокером, то сбрасываем флаг подписки, что бы при реконекте заново подписаться
if (!mqttIsConnect())
sendOk = false; */
}
/* String getMqttExterSub()
{
return _topic;
} */
void mqttSubscribeDiscovery()
{
if (HA)
{
getlayoutHA();
publishRetain(mqttRootDevice + "/state", "{\"status\":\"online\"}");
}
}
void getlayoutHA()
{
if (HA)
{
auto file = seekFile("layout.json");
if (!file)
{
SerialPrint("E", F("MQTT"), F("no file layout.json"));
return;
}
size_t size = file.size();
DynamicJsonDocument doc(size * 2);
DeserializationError error = deserializeJson(doc, file);
if (error)
{
SerialPrint("E", F("MQTT"), error.f_str());
jsonWriteInt(errorsHeapJson, F("jse3"), 1); // Ошибка чтения json файла с виджетами при отправки в mqtt
}
int i = 0;
// String HATopic = jsonReadStr(settingsFlashJson, F("HomeAssistant"));
JsonArray arr = doc.as<JsonArray>();
for (JsonVariant value : arr)
{
String dev = selectToMarkerLast(value["topic"].as<String>(), "/");
dev.replace(":", "");
String HAjson = "";
HAjson = "{\"availability\":[{\"topic\": \"" + mqttRootDevice + "/state\",\"value_template\": \"{{ value_json.status }}\"}],\"availability_mode\": \"any\",";
HAjson = HAjson + " \"device\": {\"identifiers\": [\"" + mqttRootDevice + value["page"].as<String>() + "\"],";
HAjson = HAjson + " \"name\": \" " + value["page"].as<String>() + "\"},";
HAjson = HAjson + " \"name\": \"" + value["descr"].as<String>() + "\",";
HAjson = HAjson + " \"state_topic\": \"" + value["topic"].as<String>() + "/status\",";
HAjson = HAjson + " \"icon\": \"hass:none\",";
// сенсоры
if (value["name"].as<String>() == "anydataTmp")
{
HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\",";
HAjson = HAjson + " \"unit_of_measurement\": \"°C\"";
}
else if (value["name"].as<String>() == "anydataHum")
{
HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\",";
HAjson = HAjson + " \"unit_of_measurement\": \"%\"";
}
else if (value["name"].as<String>() == "anydataMm")
{
HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\",";
HAjson = HAjson + " \"unit_of_measurement\": \"mm\"";
}
else if (value["name"].as<String>() == "anydataBar")
{
HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\",";
HAjson = HAjson + " \"unit_of_measurement\": \"Bar\"";
}
else if (value["name"].as<String>() == "anydataPpm")
{
HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\",";
HAjson = HAjson + " \"unit_of_measurement\": \"ppm\"";
}
// ввод числаФВ
else if (value["name"].as<String>() == "inputDgt")
{
HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\",";
HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as<String>() + "/control\",";
HAjson = HAjson + " \"mode\": \"box\",";
HAjson = HAjson + " \"min\": " + -1000000 + ",";
HAjson = HAjson + " \"max\": " + 1000000 + "";
}
// ввод текста inputTxt
else if (value["name"].as<String>() == "inputTxt")
{
HAjson = HAjson + " \"value_template\": \"{{ value_json.status | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\",";
HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as<String>() + "/control\"";
}
// переключатель
else if (value["name"].as<String>() == "toggle")
{
HAjson = HAjson + " \"value_template\": \"{{ value_json.status | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\",";
HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as<String>() + "/control\",";
HAjson = HAjson + " \"device_class\": \"switch\",";
HAjson = HAjson + " \"payload_off\": " + 0 + ",";
HAjson = HAjson + " \"payload_on\": " + 1 + ",";
HAjson = HAjson + " \"state_off\": " + 0 + ",";
HAjson = HAjson + " \"state_on\": " + 1 + "";
}
else
{
HAjson = HAjson + " \"value_template\": \"{{ value_json.status | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\"";
}
HAjson = HAjson + " }";
// "has_entity_name" : false,
// SerialPrint("E", F("MQTT"), HAjson);
// текст
if (value["widget"].as<String>() == "anydata")
{
if (!publishRetain(HATopic + "/sensor/" + chipId + "/" + dev + "/config", HAjson))
{
SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant"));
}
}
// ввод числа
if (value["name"].as<String>() == "inputDgt")
{
if (!publishRetain(HATopic + "/number/" + chipId + "/" + dev + "/config", HAjson))
{
SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant"));
}
}
// ввод текста inputTxt
if (value["name"].as<String>() == "inputTxt")
{
if (!publishRetain(HATopic + "/text/" + chipId + "/" + dev + "/config", HAjson))
{
SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant"));
}
}
// переключатель
if (value["name"].as<String>() == "toggle")
{
if (!publishRetain(HATopic + "/switch/" + chipId + "/" + dev + "/config", HAjson))
{
SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant"));
}
}
i++;
}
file.close();
publishRetain(mqttRootDevice + "/state", "{\"status\":\"online\"}");
for (std::list<IoTItem *>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it)
{
if ((*it)->iAmLocal)
{
publishStatusMqtt((*it)->getID(), (*it)->getValue());
(*it)->onMqttWsAppConnectEvent();
}
}
}
}
IoTDiscovery *getHADiscovery()
{
if (HA)
return this;
else
return nullptr;
}
~DiscoveryHA(){};
};
void *getAPI_DiscoveryHA(String subtype, String param)
{
if (subtype == F("DiscoveryHA"))
{
return new DiscoveryHA(param);
}
else
{
return nullptr;
}
}

View File

@@ -0,0 +1,40 @@
{
"menuSection": "virtual_elments",
"configItem": [
{
"global": 0,
"name": "Discovery of HA",
"type": "Reading",
"subtype": "DiscoveryHA",
"id": "ha",
"widget": "",
"page": "",
"descr": "",
"topic": "homeassistant"
}
],
"about": {
"authorName": "Bubnov Mikhail",
"authorContact": "https://t.me/Mit4bmw",
"authorGit": "https://github.com/Mit4el",
"specialThanks": "@AVAKS",
"moduleName": "DiscoveryHA",
"moduleVersion": "1.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"title": "DiscoveryHA",
"moduleDesc": "Модуль проброса данных в MQTT для HomeAssistant",
"propInfo": {
"topic":"Топик HomeAssistant"
}
},
"defActive": false,
"usedLibs": {
"esp32*": [],
"esp82*": [],
"bk72*": []
}
}

View File

@@ -0,0 +1,295 @@
#include "Global.h"
#include "classes/IoTDiscovery.h"
// #include "MqttDiscovery.h"
class DiscoveryHomeD : public IoTDiscovery
{
private:
String _topic = "";
bool sendOk = false;
// bool topicOk = false;
bool HOMEd = false;
int _names = 0;
String esp_id = chipId;
public:
DiscoveryHomeD(String parameters) : IoTDiscovery(parameters)
{
_topic = jsonReadStr(parameters, "topic");
_names = jsonReadInt(parameters, "names");
if (_topic && _topic != "" && _topic != "null")
{
HOMEd = true;
HOMEdTopic = _topic;
}
if (_names)
{
esp_id = jsonReadStr(settingsFlashJson, F("name"));
jsonWriteInt(settingsFlashJson, F("HOMEd_names"), 1);
}
else
{
jsonWriteInt(settingsFlashJson, F("HOMEd_names"), 0);
}
if (mqttIsConnect() && HOMEd)
{
mqttReconnect();
// sendOk = true;
// mqttSubscribeExternal(_topic);
}
}
void onMqttRecive(String &topic, String &payloadStr)
{
if (!HOMEd)
return;
if (payloadStr.indexOf("HELLO") == -1)
{
/* String dev = selectToMarkerLast(topic, "/");
dev.toUpperCase();
dev.replace(":", "");
if (_topic != topic)
{
// SerialPrint("i", "ExternalMQTT", _id + " not equal: " + topic + " msg: " + msg);
return;
} */
// обработка топика, на который подписались
if (topic.indexOf(F("/td/custom")) != -1)
{
// обрабатываем команды из HOMEd
StaticJsonDocument<200> doc;
deserializeJson(doc, payloadStr);
for (JsonPair kvp : doc.as<JsonObject>())
{
String key = kvp.key().c_str();
String value = kvp.value().as<const char *>();
if (key.indexOf(F("status_")) != -1)
{
key.replace("status_", "");
if (value == "on")
{
generateOrder(key, "1");
}
else if (value == "off")
{
generateOrder(key, "0");
}
else if (value == "toggle")
{
String val = (String)(1 - getItemValue(key).toInt());
generateOrder(key, val);
}
}
else
{
if (!value)
{
float val = kvp.value();
generateOrder(key, (String)(val));
}
else
{
generateOrder(key, value);
}
}
}
SerialPrint("i", F("=>MQTT"), "Msg from HOMEd: " + payloadStr);
}
}
}
void doByInterval()
{
/* // периодически проверяем связь с MQTT брокером и если она появилась, то подписываемся на нужный топик
if (mqttIsConnect() && !sendOk && topicOk)
{
sendOk = true;
publishRetain(_topic + "/device/custom/" + esp_id, "{\"status\":\"online\"}");
String HOMEdsubscribeTopic = _topic + "/td/custom/" + esp_id;
// mqtt.subscribe(HOMEdsubscribeTopic.c_str());
mqttSubscribeExternal(HOMEdsubscribeTopic);
}
// если нет коннектас брокером, то сбрасываем флаг подписки, что бы при реконекте заново подписаться
if (!mqttIsConnect())
sendOk = false; */
}
void publishStatusHOMEd(const String &topic, const String &data)
{
String path_h = HOMEdTopic + "/fd/custom/" + esp_id;
String json_h = "{}";
if (topic != "onStart")
{
if (data.toInt() == 1)
{
jsonWriteStr(json_h, "status_" + topic, "on");
}
else if (data.toInt() == 0)
{
jsonWriteStr(json_h, "status_" + topic, "off");
}
if (data.toFloat())
{
jsonWriteFloat(json_h, topic, data.toFloat());
}
else
{
jsonWriteStr(json_h, topic, data);
}
mqtt.publish(path_h.c_str(), json_h.c_str(), false);
}
}
void mqttSubscribeDiscovery()
{
if (HOMEd)
{
deleteFromHOMEd();
getlayoutHOMEd();
publishRetain(HOMEdTopic + "/device/custom/" + esp_id, "{\"status\":\"online\"}");
String HOMEdsubscribeTopic = HOMEdTopic + "/td/custom/" + esp_id;
mqtt.subscribe(HOMEdsubscribeTopic.c_str());
}
}
void getlayoutHOMEd()
{
if (HOMEd)
{
String devName = jsonReadStr(settingsFlashJson, F("name"));
auto file = seekFile("layout.json");
if (!file)
{
SerialPrint("E", F("MQTT"), F("no file layout.json"));
return;
}
size_t size = file.size();
DynamicJsonDocument doc(size * 2);
DeserializationError error = deserializeJson(doc, file);
if (error)
{
SerialPrint("E", F("MQTT"), error.f_str());
jsonWriteInt(errorsHeapJson, F("jse3"), 1); // Ошибка чтения json файла с виджетами при отправки в mqtt
}
int i = 0;
// String path = jsonReadStr(settingsFlashJson, F("HOMEd"));
JsonArray arr = doc.as<JsonArray>();
String HOMEdJSON = "";
HOMEdJSON = "{\"action\":\"updateDevice\",";
HOMEdJSON = HOMEdJSON + "\"device\":\"" + chipId + "\",";
HOMEdJSON = HOMEdJSON + "\"data\":{";
HOMEdJSON = HOMEdJSON + "\"active\": true,";
HOMEdJSON = HOMEdJSON + "\"cloud\": false,";
HOMEdJSON = HOMEdJSON + "\"discovery\": false,";
HOMEdJSON = HOMEdJSON + "\"id\":\"" + chipId + "\",";
HOMEdJSON = HOMEdJSON + "\"name\":\"" + devName + "\",";
HOMEdJSON = HOMEdJSON + "\"real\":true,";
HOMEdJSON = HOMEdJSON + "\"exposes\": [";
String options = "";
for (JsonVariant value : arr)
{
String name = value["descr"];
String device = selectToMarkerLast(value["topic"].as<String>(), "/");
// String id = ChipId + "-" + device;
String expose = value["name"];
if (value["name"].as<String>() == "toggle")
{
HOMEdJSON = HOMEdJSON + "\"switch_" + device + "\",";
}
else
{
HOMEdJSON = HOMEdJSON + "\"" + device + "\",";
}
if (value["name"].as<String>() == "anydataTmp")
{
// HOMEdJSON = HOMEdJSON + "\"temperature_" + device + "\",";
options = options + "\"" + device + "\":{\"type\": \"sensor\", \"class\": \"temperature\", \"state\": \"measurement\", \"unit\": \"°C\", \"round\": 1},";
}
if (value["name"].as<String>() == "anydataHum")
{
// HOMEdJSON = HOMEdJSON + "\"humidity_" + device + "\",";
options = options + "\"" + device + "\":{\"type\": \"sensor\", \"class\": \"humidity\", \"state\": \"measurement\", \"unit\": \"%\", \"round\": 1},";
}
if (value["name"].as<String>() == "inputDgt")
{
options = options + "\"" + device + "\":{\"type\": \"number\", \"min\": -10000, \"max\": 100000, \"step\": 0.1},";
}
i++;
}
options = options.substring(0, options.length() - 1);
HOMEdJSON = HOMEdJSON.substring(0, HOMEdJSON.length() - 1);
HOMEdJSON = HOMEdJSON + "],";
HOMEdJSON = HOMEdJSON + " \"options\": {" + options + "}";
HOMEdJSON = HOMEdJSON + "}}";
String topic = (HOMEdTopic + "/command/custom").c_str();
if (!publish(topic, HOMEdJSON))
{
SerialPrint("E", F("MQTT"), F("Failed publish data to HOMEd"));
}
file.close();
publishRetain(HOMEdTopic + "/device/custom/" + esp_id, "{\"status\":\"online\"}");
for (std::list<IoTItem *>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it)
{
if ((*it)->iAmLocal)
{
publishStatusMqtt((*it)->getID(), (*it)->getValue());
(*it)->onMqttWsAppConnectEvent();
}
}
}
}
void deleteFromHOMEd()
{
if (HOMEd)
{
for (std::list<IoTItem *>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it)
{
if (*it)
{
String id_widget = (*it)->getID().c_str();
String HOMEdjson = "";
HOMEdjson = "{\"action\":\"removeDevice\",";
HOMEdjson = HOMEdjson + "\"device\":\"";
HOMEdjson = HOMEdjson + chipId;
HOMEdjson = HOMEdjson + "\"}";
String topic = (HOMEdTopic + "/command/custom").c_str();
if (!publish(topic, HOMEdjson))
{
SerialPrint("E", F("MQTT"), F("Failed remove from HOMEd"));
}
}
}
}
}
IoTDiscovery *getHOMEdDiscovery()
{
if (HOMEd)
return this;
else
return nullptr;
}
~DiscoveryHomeD(){};
};
void *getAPI_DiscoveryHomeD(String subtype, String param)
{
if (subtype == F("DiscoveryHomeD"))
{
return new DiscoveryHomeD(param);
}
else
{
return nullptr;
}
}

View File

@@ -0,0 +1,41 @@
{
"menuSection": "virtual_elments",
"configItem": [
{
"global": 0,
"name": "Discovery of HomeD",
"type": "Reading",
"subtype": "DiscoveryHomeD",
"id": "homed",
"widget": "",
"page": "",
"descr": "",
"topic": "homed",
"names":1
}
],
"about": {
"authorName": "Bubnov Mikhail",
"authorContact": "https://t.me/Mit4bmw",
"authorGit": "https://github.com/Mit4el",
"specialThanks": "@AVAKS",
"moduleName": "DiscoveryHomeD",
"moduleVersion": "1.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"title": "DiscoveryHomeD",
"moduleDesc": "Модуль проброса данных в MQTT для HOMEd-Custom",
"propInfo": {
"topic":"Топик службы HOMEd-Custom, например /myRoom/homed"
}
},
"defActive": false,
"usedLibs": {
"esp32*": [],
"esp82*": [],
"bk72*": []
}
}

View File

@@ -5,8 +5,9 @@
void *getAPI_Date(String params);
class Loging : public IoTItem {
private:
class Loging : public IoTItem
{
private:
String logid;
String id;
String tmpValue;
@@ -14,6 +15,8 @@ class Loging : public IoTItem {
int _publishType = -2;
int _wsNum = -1;
int days = 1;
int daysShow = 1;
int points;
// int keepdays;
@@ -25,45 +28,58 @@ class Loging : public IoTItem {
// long interval;
public:
Loging(String parameters) : IoTItem(parameters) {
public:
Loging(String parameters) : IoTItem(parameters)
{
jsonRead(parameters, F("logid"), logid);
jsonRead(parameters, F("id"), id);
jsonRead(parameters, F("points"), points);
if (points > 300) {
if (points > 300)
{
points = 300;
SerialPrint("E", F("Loging"), "'" + id + "' user set more points than allowed, value reset to 300");
}
long interval;
jsonRead(parameters, F("int"), interval); // в минутах
jsonRead(parameters, F("int"), interval); // в минутах
setInterval(interval * 60);
// jsonRead(parameters, F("keepdays"), keepdays, false);
jsonRead(parameters, F("daysSave"), days);
days = days * 86400;
jsonRead(parameters, F("daysShow"), daysShow);
daysShow = daysShow * 86400;
// создадим экземпляр класса даты
dateIoTItem = (IoTItem *)getAPI_Date("{\"id\": \"" + id + "-date\",\"int\":\"20\",\"subtype\":\"date\"}");
IoTItems.push_back(dateIoTItem);
SerialPrint("I", F("Loging"), "created date instance " + id);
}
void doByInterval() {
void doByInterval()
{
// если объект логгирования не был создан
if (!isItemExist(logid)) {
if (!isItemExist(logid))
{
SerialPrint("E", F("Loging"), "'" + id + "' loging object not exist, return");
return;
}
String value = getItemValue(logid);
// если значение логгирования пустое
if (value == "") {
if (value == "")
{
SerialPrint("E", F("Loging"), "'" + id + "' loging value is empty, return");
return;
}
// если время не было получено из интернета
if (!isTimeSynch) {
if (!isTimeSynch)
{
SerialPrint("E", F("Loging"), "'" + id + "' Сant loging - time not synchronized, return");
return;
}
if (hasDayChanged())
deleteOldFile();
regEvent(value, F("Loging"));
@@ -76,13 +92,17 @@ class Loging : public IoTItem {
String filePath = readDataDB(id);
// если данные о файле отсутствуют, создадим новый
if (filePath == "failed" || filePath == "") {
if (filePath == "failed" || filePath == "")
{
SerialPrint("E", F("Loging"), "'" + id + "' file path not found, start create new file");
createNewFileWithData(logData);
return;
} else {
}
else
{
// если файл все же есть но был создан не сегодня, то создаем сегодняшний
if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) {
if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath)))
{
SerialPrint("E", F("Loging"), "'" + id + "' file too old, start create new file");
createNewFileWithData(logData);
return;
@@ -95,29 +115,38 @@ class Loging : public IoTItem {
SerialPrint("i", F("Loging"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size));
// если количество строк до заданной величины и дата не менялась
if (lines <= points && !hasDayChanged()) {
if (lines <= points && !hasDayChanged())
{
// просто добавим в существующий файл новые данные
addNewDataToExistingFile(filePath, logData);
// если больше или поменялась дата то создадим следующий файл
} else {
}
else
{
createNewFileWithData(logData);
}
// запускаем процедуру удаления старых файлов если память переполняется
deleteLastFile();
}
void SetDoByInterval(String valse) {
void SetDoByInterval(String valse)
{
String value = valse;
// если значение логгирования пустое
if (value == "") {
if (value == "")
{
SerialPrint("E", F("LogingEvent"), "'" + id + "' loging value is empty, return");
return;
}
// если время не было получено из интернета
if (!isTimeSynch) {
if (!isTimeSynch)
{
SerialPrint("E", F("LogingEvent"), "'" + id + "' Сant loging - time not synchronized, return");
return;
}
if (hasDayChanged())
deleteOldFile();
regEvent(value, F("LogingEvent"));
String logData;
jsonWriteInt(logData, "x", unixTime, false);
@@ -126,13 +155,17 @@ class Loging : public IoTItem {
String filePath = readDataDB(id);
// если данные о файле отсутствуют, создадим новый
if (filePath == "failed" || filePath == "") {
if (filePath == "failed" || filePath == "")
{
SerialPrint("E", F("LogingEvent"), "'" + id + "' file path not found, start create new file");
createNewFileWithData(logData);
return;
} else {
}
else
{
// если файл все же есть но был создан не сегодня, то создаем сегодняшний
if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) {
if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath)))
{
SerialPrint("E", F("LogingEvent"), "'" + id + "' file too old, start create new file");
createNewFileWithData(logData);
return;
@@ -145,53 +178,73 @@ class Loging : public IoTItem {
SerialPrint("i", F("LogingEvent"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size));
// если количество строк до заданной величины и дата не менялась
if (lines <= points && !hasDayChanged()) {
if (lines <= points && !hasDayChanged())
{
// просто добавим в существующий файл новые данные
addNewDataToExistingFile(filePath, logData);
// если больше или поменялась дата то создадим следующий файл
} else {
}
else
{
createNewFileWithData(logData);
}
// запускаем процедуру удаления старых файлов если память переполняется
deleteLastFile();
}
void createNewFileWithData(String &logData) {
void createNewFileWithData(String &logData)
{
logData = logData + ",";
String path = "/lg/" + id + "/" + String(unixTimeShort) + ".txt"; // создадим путь вида /lg/id/133256622333.txt
String path = "/lg/" + id + "/" + String(unixTimeShort) + ".txt"; // создадим путь вида /lg/id/133256622333.txt
// создадим пустой файл
if (writeEmptyFile(path) != "success") {
if (writeEmptyFile(path) != "success")
{
SerialPrint("E", F("Loging"), "'" + id + "' file writing error, return");
return;
}
// запишем в него данные
if (addFile(path, logData) != "success") {
if (addFile(path, logData) != "success")
{
SerialPrint("E", F("Loging"), "'" + id + "' data writing error, return");
return;
}
// запишем путь к нему в базу данных
if (saveDataDB(id, path) != "success") {
if (saveDataDB(id, path) != "success")
{
SerialPrint("E", F("Loging"), "'" + id + "' db file writing error, return");
return;
}
#ifdef LIBRETINY
SerialPrint("i", F("Loging"), "'" + id + "' file created http://" + ipToString(WiFi.localIP()) + path);
#else
SerialPrint("i", F("Loging"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path);
#endif
}
void addNewDataToExistingFile(String &path, String &logData) {
void addNewDataToExistingFile(String &path, String &logData)
{
logData = logData + ",";
if (addFile(path, logData) != "success") {
if (addFile(path, logData) != "success")
{
SerialPrint("i", F("Loging"), "'" + id + "' file writing error, return");
return;
};
#ifdef LIBRETINY
SerialPrint("i", F("Loging"), "'" + id + "' loging in file http://" + ipToString(WiFi.localIP()) + path);
#else
SerialPrint("i", F("Loging"), "'" + id + "' loging in file http://" + WiFi.localIP().toString() + path);
#endif
}
// данная функция уже перенесена в ядро и будет удалена в последствии
bool hasDayChanged() {
bool hasDayChanged()
{
bool changed = false;
String currentDate = getTodayDateDotFormated();
if (!firstTimeInit) {
if (prevDate != currentDate) {
if (!firstTimeInit)
{
if (prevDate != currentDate)
{
changed = true;
SerialPrint("i", F("NTP"), F("Change day event"));
#if defined(ESP8266)
@@ -206,7 +259,8 @@ class Loging : public IoTItem {
return changed;
}
void publishValue() {
void publishValue()
{
String dir = "/lg/" + id;
filesList = getFilesList(dir);
@@ -216,75 +270,111 @@ class Loging : public IoTItem {
bool noData = true;
while (filesList.length()) {
while (filesList.length())
{
String path = selectToMarker(filesList, ";");
path = "/lg/" + id + path;
path = dir + path;
f++;
unsigned long fileUnixTimeLocal = getFileUnixLocalTime(path);
unsigned long reqUnixTime = strDateToUnix(getItemValue(id + "-date"));
if (fileUnixTimeLocal > reqUnixTime && fileUnixTimeLocal < reqUnixTime + 86400) {
if (fileUnixTimeLocal > reqUnixTime - daysShow && fileUnixTimeLocal < reqUnixTime + 86400)
{
noData = false;
String json = getAdditionalJson();
if (_publishType == TO_MQTT) {
if (_publishType == TO_MQTT)
{
publishChartFileToMqtt(path, id, calculateMaxCount());
} else if (_publishType == TO_WS) {
}
else if (_publishType == TO_WS)
{
sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE);
} else if (_publishType == TO_MQTT_WS) {
}
else if (_publishType == TO_MQTT_WS)
{
sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE);
publishChartFileToMqtt(path, id, calculateMaxCount());
}
SerialPrint("i", F("Loging"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", sent");
} else {
}
else
{
SerialPrint("i", F("Loging"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", skipped");
}
/*
//------------ delete old files ----------------
String pathTodel = path;
pathTodel.replace("/", "");
pathTodel.replace(".txt", "");
int pathTodel_ = pathTodel.toInt();
if (pathTodel_ < unixTimeShort - days)
{
removeFile(path);
SerialPrint("i", "Loging!!!!!!", path + " => old files been clean");
}
if (pathTodel_ < unixTimeShort - 5184000)
{
removeFile(path);
SerialPrint("i", "Loging!!!!!!", path + " => > 2 month files been clean");
}
//------------ delete old files ----------------
*/
filesList = deleteBeforeDelimiter(filesList, ";");
}
// если данных нет отправляем пустой грфик
if (noData) {
if (noData)
{
clearValue();
}
}
String getAdditionalJson() {
String getAdditionalJson()
{
String topic = mqttRootDevice + "/" + id;
String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\"}";
return json;
}
void publishChartToWsSinglePoint(String value) {
void publishChartToWsSinglePoint(String value)
{
String topic = mqttRootDevice + "/" + id;
String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\",\"status\":[{\"x\":" + String(unixTime) + ",\"y1\":" + value + "}]}";
sendStringToWs("chartb", json, -1);
}
void clearValue() {
void clearValue()
{
String topic = mqttRootDevice + "/" + id;
String json = "{\"maxCount\":0,\"topic\":\"" + topic + "\",\"status\":[]}";
sendStringToWs("chartb", json, -1);
}
void clearHistory() {
void clearHistory()
{
String dir = "/lg/" + id;
cleanDirectory(dir);
}
void deleteLastFile() {
void deleteLastFile()
{
IoTFSInfo tmp = getFSInfo();
SerialPrint("i", "Loging", String(tmp.freePer) + " % free flash remaining");
if (tmp.freePer <= 50.00) {
if (tmp.freePer <= 10.00)
{
String dir = "/lg/" + id;
filesList = getFilesList(dir);
int i = 0;
while (filesList.length()) {
while (filesList.length())
{
String path = selectToMarker(filesList, ";");
path = dir + path;
i++;
if (i == 1) {
if (i == 1)
{
removeFile(path);
SerialPrint("!", "Loging", String(i) + ") " + path + " => oldest files been deleted");
return;
@@ -295,7 +385,37 @@ class Loging : public IoTItem {
}
}
void setPublishDestination(int publishType, int wsNum) {
void deleteOldFile()
{
String dir = "/lg/" + id;
filesList = getFilesList(dir);
int i = 0;
while (filesList.length())
{
String path = selectToMarker(filesList, ";");
String pathTodel = path;
pathTodel.replace("/", "");
pathTodel.replace(".txt", "");
int pathTodel_ = pathTodel.toInt();
path = dir + path;
i++;
if (pathTodel_ < unixTimeShort - days)
{
removeFile(path);
SerialPrint("i", "Loging!!!!!!", String(i) + ") " + path + " => old files been clean");
}
if (pathTodel_ < unixTimeShort - 5184000)
{
removeFile(path);
SerialPrint("i", "Loging!!!!!!", String(i) + ") " + path + " => > 2 month files been clean");
}
filesList = deleteBeforeDelimiter(filesList, ";");
}
}
void setPublishDestination(int publishType, int wsNum)
{
_publishType = publishType;
_wsNum = wsNum;
}
@@ -315,11 +435,13 @@ class Loging : public IoTItem {
// }
// }
void regEvent(const String &value, const String &consoleInfo, bool error = false, bool genEvent = true) {
void regEvent(const String &value, const String &consoleInfo, bool error = false, bool genEvent = true)
{
String userDate = getItemValue(id + "-date");
String currentDate = getTodayDateDotFormated();
// отправляем в график данные только когда выбран сегодняшний день
if (userDate == currentDate) {
if (userDate == currentDate)
{
// generateEvent(_id, value);
// publishStatusMqtt(_id, value);
@@ -333,7 +455,8 @@ class Loging : public IoTItem {
// путь вида: /lg/log/1231231.txt
unsigned long getFileUnixLocalTime(String path) { return gmtTimeToLocal(selectToMarkerLast(deleteToMarkerLast(path, "."), "/").toInt() + START_DATETIME); }
void setValue(const IoTValue &Value, bool genEvent = true) {
void setValue(const IoTValue &Value, bool genEvent = true)
{
value = Value;
this->SetDoByInterval(String(value.valD));
SerialPrint("i", "Loging", "setValue:" + String(value.valD));
@@ -341,37 +464,48 @@ class Loging : public IoTItem {
}
};
void *getAPI_Loging(String subtype, String param) {
if (subtype == F("Loging")) {
void *getAPI_Loging(String subtype, String param)
{
if (subtype == F("Loging"))
{
return new Loging(param);
} else {
}
else
{
return nullptr;
}
}
class Date : public IoTItem {
private:
class Date : public IoTItem
{
private:
bool firstTime = true;
public:
public:
String id;
Date(String parameters) : IoTItem(parameters) {
Date(String parameters) : IoTItem(parameters)
{
jsonRead(parameters, F("id"), id);
value.isDecimal = false;
}
void setValue(const String &valStr, bool genEvent = true) {
void setValue(const String &valStr, bool genEvent = true)
{
value.valS = valStr;
setValue(value, genEvent);
}
void setValue(const IoTValue &Value, bool genEvent = true) {
void setValue(const IoTValue &Value, bool genEvent = true)
{
value = Value;
regEvent(value.valS, "", false, genEvent);
// отправка данных при изменении даты
for (std::list<IoTItem *>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) {
if ((*it)->getSubtype() == "Loging") {
if ((*it)->getID() == selectToMarker(id, "-")) {
for (std::list<IoTItem *>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it)
{
if ((*it)->getSubtype() == "Loging")
{
if ((*it)->getID() == selectToMarker(id, "-"))
{
(*it)->setPublishDestination(TO_MQTT_WS, -1);
(*it)->publishValue();
}
@@ -379,14 +513,18 @@ class Date : public IoTItem {
}
}
void setTodayDate() {
void setTodayDate()
{
setValue(getTodayDateDotFormated());
SerialPrint("E", F("Loging"), "today date set " + getTodayDateDotFormated());
SerialPrint("i", F("Loging"), "today date set " + getTodayDateDotFormated());
}
void doByInterval() {
if (isTimeSynch) {
if (firstTime) {
void doByInterval()
{
if (isTimeSynch)
{
if (firstTime)
{
setTodayDate();
firstTime = false;
}

View File

@@ -1,56 +1,63 @@
{
"menuSection": "virtual_elments",
"configItem": [
{
"global": 0,
"name": "График",
"type": "Writing",
"subtype": "Loging",
"id": "log",
"widget": "chart2",
"page": "Графики",
"descr": "Температура",
"num": 1,
"int": 5,
"logid": "t",
"points": 300
},
{
"global": 0,
"name": "График по событию",
"type": "Writing",
"subtype": "Loging",
"id": "log",
"widget": "chart2",
"page": "Графики",
"descr": "Температура",
"int": 0,
"num": 1,
"points": 300
}
],
"about": {
"authorName": "Dmitry Borisenko",
"authorContact": "https://t.me/Dmitry_Borisenko",
"authorGit": "https://github.com/DmitryBorisenko33",
"specialThanks": "@itsid1 @Valiuhaaa Serg",
"moduleName": "Loging",
"moduleVersion": "3.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"title": "Логирование в график",
"moduleDesc": "Расширение позволяющее логировать любую величину в график. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp. В окне ввода даты можно выбирать день, историю которого вы хотите посмотреть. Старые файлы будут удаляться автоматически после того как объем оставшейся flesh памяти устройства будет менее 20 процентов",
"propInfo": {
"int": "Интервал логирования в мнутах, рекомендуется для esp8266 использоать интервал не менее 5-ти минут",
"logid": "ID величины которую будем логировать",
"points": "Максимальное количество точек в одном файле, может быть не более 300. Не рекомендуется менять этот параметр"
}
"menuSection": "virtual_elments",
"configItem": [
{
"global": 0,
"name": "График",
"type": "Writing",
"subtype": "Loging",
"id": "log",
"widget": "chart2",
"page": "Графики",
"descr": "Температура",
"num": 1,
"int": 5,
"logid": "t",
"daysSave": 5,
"daysShow": 0,
"points": 300
},
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
{
"global": 0,
"name": "График по событию",
"type": "Writing",
"subtype": "Loging",
"id": "log",
"widget": "chart2",
"page": "Графики",
"descr": "Температура",
"int": 0,
"num": 1,
"daysSave": 5,
"daysShow": 0,
"points": 300
}
],
"about": {
"authorName": "Dmitry Borisenko",
"authorContact": "https://t.me/Dmitry_Borisenko",
"authorGit": "https://github.com/DmitryBorisenko33",
"specialThanks": "@itsid1 @Valiuhaaa Serg",
"moduleName": "Loging",
"moduleVersion": "4.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"title": "Логирование в график",
"moduleDesc": "Расширение позволяющее логировать любую величину в график. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp. В окне ввода даты можно выбирать день, историю которого вы хотите посмотреть. Старые файлы будут удаляться автоматически после того как объем оставшейся flesh памяти устройства будет менее 20 процентов",
"propInfo": {
"int": "Интервал логирования в мнутах, рекомендуется для esp8266 использоать интервал не менее 5-ти минут",
"logid": "ID величины которую будем логировать",
"points": "Максимальное количество точек в одном файле, может быть не более 300. Не рекомендуется менять этот параметр",
"daysSave": "Количество дней за которое надо хранить график",
"daysShow": "Какое количество дней отображать на графике, в дополнение к текущему. 0 - за текущие сутки с ноля часов; 1 - вчерашние сутки и сегодняшние, и т.д."
}
},
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": [],
"bk72*": []
}
}

View File

@@ -139,7 +139,11 @@ class LogingDaily : public IoTItem {
SerialPrint("E", F("LogingDaily"), "'" + id + "' db file writing error, return");
return;
}
#ifdef LIBRETINY
SerialPrint("i", F("LogingDaily"), "'" + id + "' file created http://" + ipToString(WiFi.localIP()) + path);
#else
SerialPrint("i", F("LogingDaily"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path);
#endif
}
void addNewDataToExistingFile(String &path, String &logData) {
@@ -148,7 +152,11 @@ class LogingDaily : public IoTItem {
SerialPrint("i", F("LogingDaily"), "'" + id + "' file writing error, return");
return;
};
#ifdef LIBRETINY
SerialPrint("i", F("LogingDaily"), "'" + id + "' LogingDaily in file http://" + ipToString(WiFi.localIP()) + path);
#else
SerialPrint("i", F("LogingDaily"), "'" + id + "' LogingDaily in file http://" + WiFi.localIP().toString() + path);
#endif
}
bool hasDayChanged() {

View File

@@ -1,49 +1,50 @@
{
"menuSection": "virtual_elments",
"configItem": [
{
"global": 0,
"name": "График дневного расхода",
"type": "Writing",
"subtype": "LogingDaily",
"id": "log",
"widget": "chart3",
"page": "Графики",
"descr": "Температура",
"num": 1,
"int": 1,
"logid": "t",
"points": 200,
"telegram": 0,
"test": 0,
"btn-defvalue": 0,
"btn-reset": "nil"
}
],
"about": {
"authorName": "Dmitry Borisenko",
"authorContact": "https://t.me/Dmitry_Borisenko",
"authorGit": "https://github.com/DmitryBorisenko33",
"specialThanks": "@itsid1 @Valiuhaaa Serg",
"moduleName": "LogingDaily",
"moduleVersion": "3.1",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"title": "График дневного расхода",
"moduleDesc": "Расширение позволяющее логировать накопительные величины и видеть их дневное изменение. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp",
"propInfo": {
"int": "Интервал логирования в мнутах, частота проверки смены суток в минутах. Не рекомендуется менять",
"logid": "ID накопительной величины которую будем логировать",
"points": "Максимальное количество точек",
"telegram": "График будет отправлять в телеграм репорт с расходами каждый день",
"test": "Параметр необходим для разработчиков. Режим тестирования. График будет обновляться не раз в сутки, а кадый заданный в int интервал."
}
},
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
"menuSection": "virtual_elments",
"configItem": [
{
"global": 0,
"name": "График дневного расхода",
"type": "Writing",
"subtype": "LogingDaily",
"id": "log",
"widget": "chart3",
"page": "Графики",
"descr": "Температура",
"num": 1,
"int": 1,
"logid": "t",
"points": 365,
"telegram": 0,
"test": 0,
"btn-defvalue": 0,
"btn-reset": "nil"
}
],
"about": {
"authorName": "Dmitry Borisenko",
"authorContact": "https://t.me/Dmitry_Borisenko",
"authorGit": "https://github.com/DmitryBorisenko33",
"specialThanks": "@itsid1 @Valiuhaaa Serg",
"moduleName": "LogingDaily",
"moduleVersion": "3.1",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"title": "График дневного расхода",
"moduleDesc": "Расширение позволяющее логировать накопительные величины и видеть их дневное изменение. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp",
"propInfo": {
"int": "Интервал логирования в мнутах, частота проверки смены суток в минутах. Не рекомендуется менять",
"logid": "ID накопительной величины которую будем логировать",
"points": "Максимальное количество точек",
"telegram": "График будет отправлять в телеграм репорт с расходами каждый день",
"test": "Параметр необходим для разработчиков. Режим тестирования. График будет обновляться не раз в сутки, а кадый заданный в int интервал."
}
},
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": [],
"bk72*": []
}
}

View File

@@ -0,0 +1,339 @@
#include "Global.h"
#include "classes/IoTItem.h"
#include "ESPConfiguration.h"
#include "NTP.h"
class LogingHourly : public IoTItem
{
private:
String logid;
String id;
String filesList = "";
String descr;
int _publishType = -2;
int _wsNum = -1;
int points;
int testMode;
int telegram;
IoTItem *dateIoTItem;
// String prevDate = "";
String prevHourly = "";
bool firstTimeInit = true;
// long interval;
public:
LogingHourly(String parameters) : IoTItem(parameters)
{
jsonRead(parameters, F("logid"), logid);
jsonRead(parameters, F("id"), id);
jsonRead(parameters, F("points"), points);
jsonRead(parameters, F("test"), testMode);
jsonRead(parameters, F("telegram"), telegram);
jsonRead(parameters, F("descr"), descr);
long interval;
jsonRead(parameters, F("int"), interval);
interval = interval * 1000 * 60; // приводим к милисекундам
}
void doByInterval()
{
if (hasHourlyChanged() || testMode == 1)
{
execute();
}
}
void execute()
{
// если объект логгирования не был создан
if (!isItemExist(logid))
{
SerialPrint("E", F("LogingHourly"), "'" + id + "' LogingHourly object not exist, return");
return;
}
String value = getItemValue(logid);
// если значение логгирования пустое
if (value == "")
{
SerialPrint("E", F("LogingHourly"), "'" + id + "' LogingHourly value is empty, return");
return;
}
// если время не было получено из интернета
if (!isTimeSynch)
{
SerialPrint("E", F("LogingHourly"), "'" + id + "' Cant LogingHourly - time not synchronized, return");
return;
}
String logData;
float currentValue = value.toFloat();
// прочитаем предудущее значение
float prevValue = readDataDB(id + "-v").toFloat();
// сохраним в базу данных текущее значение, понадобится в следующие час
saveDataDB(id + "-v", value);
float difference = currentValue - prevValue;
if (telegram == 1)
{
String msg = descr + ": total " + String(currentValue) + ", consumed " + String(difference);
for (std::list<IoTItem *>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it)
{
if ((*it)->getSubtype() == "TelegramLT" || "Telegram")
{
(*it)->sendTelegramMsg(false, msg);
}
}
}
// jsonWriteInt(logData, "x", unixTime - 120);
jsonWriteInt(logData, "x", unixTime - 120);
jsonWriteFloat(logData, "y1", difference);
// прочитаем путь к файлу последнего сохранения
String filePath = readDataDB(id);
// если данные о файле отсутствуют, создадим новый
if (filePath == "failed" || filePath == "")
{
SerialPrint("E", F("LogingHourly"), "'" + id + "' file path not found, start create new file");
createNewFileWithData(logData);
return;
}
// считаем количество строк и определяем размер файла
size_t size = 0;
int lines = countJsonObj(filePath, size);
SerialPrint("i", F("LogingHourly"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size));
// если количество строк до заданной величины и час и дата не менялась
// if (lines <= points && !hasHourlyChanged()) {
if (lines <= points)
{
// просто добавим в существующий файл новые данные
addNewDataToExistingFile(filePath, logData);
}
else
{
String file = readFile(filePath, 2000);
file = deleteBeforeDelimiter(file, "},");
writeFile(filePath, file);
addNewDataToExistingFile(filePath, logData);
}
}
void createNewFileWithData(String &logData)
{
logData = logData + ",";
String path = "/lgh/" + id + "/" + id + ".txt"; // создадим путь вида /lgd/id/id.txt
// создадим пустой файл
if (writeEmptyFile(path) != "success")
{
SerialPrint("E", F("LogingHourly"), "'" + id + "' file writing error, return");
return;
}
// запишем в него данные
if (addFile(path, logData) != "success")
{
SerialPrint("E", F("LogingHourly"), "'" + id + "' data writing error, return");
return;
}
// запишем путь к нему в базу данных
if (saveDataDB(id, path) != "success")
{
SerialPrint("E", F("LogingHourly"), "'" + id + "' db file writing error, return");
return;
}
SerialPrint("i", F("LogingHourly"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path);
}
void addNewDataToExistingFile(String &path, String &logData)
{
logData = logData + ",";
if (addFile(path, logData) != "success")
{
SerialPrint("i", F("LogingHourly"), "'" + id + "' file writing error, return");
return;
};
SerialPrint("i", F("LogingHourly"), "'" + id + "' LogingHourly in file http://" + WiFi.localIP().toString() + path);
}
const String getTimeLocal_hh()
{
char buf[32];
sprintf(buf, "%02d", _time_local.hour);
return String(buf);
}
bool hasHourlyChanged()
{
bool changed = false;
String currentHourly = getTimeLocal_hh();
if (!firstTimeInit)
{
if (prevHourly != currentHourly)
{
changed = true;
SerialPrint("i", F("NTP"), F("Change hourly event"));
#if defined(ESP8266)
FileFS.gc();
#endif
#if defined(ESP32)
#endif
}
}
if (isTimeSynch)
firstTimeInit = false;
prevHourly = currentHourly;
return changed;
}
bool hasDayChanged()
{
bool changed = false;
String currentDate = getTodayDateDotFormated();
if (!firstTimeInit)
{
if (prevDate != currentDate)
{
changed = true;
SerialPrint("i", F("NTP"), F("Change day event"));
#if defined(ESP8266)
FileFS.gc();
#endif
#if defined(ESP32)
#endif
}
}
if (isTimeSynch)
firstTimeInit = false;
prevDate = currentDate;
return changed;
}
void publishValue()
{
String dir = "/lgh/" + id;
filesList = getFilesList(dir);
SerialPrint("i", F("LogingHourly"), "file list: " + filesList);
int f = 0;
while (filesList.length())
{
String path = selectToMarker(filesList, ";");
path = "/lgh/" + id + path;
f++;
String json = getAdditionalJson();
if (_publishType == TO_MQTT)
{
publishChartFileToMqtt(path, id, calculateMaxCount());
}
else if (_publishType == TO_WS)
{
sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE);
}
else if (_publishType == TO_MQTT_WS)
{
publishChartFileToMqtt(path, id, calculateMaxCount());
sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE);
}
SerialPrint("i", F("LogingHourly"), String(f) + ") " + path + ", sent");
filesList = deleteBeforeDelimiter(filesList, ";");
}
}
String getAdditionalJson()
{
String topic = mqttRootDevice + "/" + id;
String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\"}";
return json;
}
void clearHistory()
{
String dir = "/lgh/" + id;
cleanDirectory(dir);
}
// void publishChartToWsSinglePoint(String value) {
// String topic = mqttRootDevice + "/" + id;
// String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\",\"status\":[{\"x\":" + String(unixTime) + ",\"y1\":" + value + "}]}";
// String pk = "/string/chart.json|" + json;
// standWebSocket.broadcastTXT(pk);
// }
void setPublishDestination(int publishType, int wsNum = -1)
{
_publishType = publishType;
_wsNum = wsNum;
}
String getValue()
{
return "";
}
/*
void loop() {
if (enableDoByInt) {
currentMillis = millis();
difference = currentMillis - prevMillis;
if (difference >= interval) {
prevMillis = millis();
this->doByInterval();
}
}
}
*/
// просто максимальное количество точек
int calculateMaxCount()
{
// return 1440;//1440
return 3600; // 1440
}
void onModuleOrder(String &key, String &value)
{
if (key == "defvalue")
{
saveDataDB(id + "-v", value);
SerialPrint("i", F("LogingHourly"), "User set default value: " + value);
}
else if (key == "reset")
{
clearHistory();
SerialPrint("i", F("LogingHourly"), F("User clean chart history"));
}
}
};
void *getAPI_LogingHourly(String subtype, String param)
{
if (subtype == F("LogingHourly"))
{
return new LogingHourly(param);
}
else
{
return nullptr;
}
}

View File

@@ -0,0 +1,50 @@
{
"menuSection": "virtual_elments",
"configItem": [
{
"global": 0,
"name": "График часового расхода",
"type": "Writing",
"subtype": "LogingHourly",
"id": "logh",
"widget": "chart3",
"page": "Графики",
"descr": "Расход в час",
"num": 1,
"int": 1,
"logid": "",
"points": 24,
"telegram": 0,
"test": 0,
"btn-defvalue": 0,
"btn-reset": "nil"
}
],
"about": {
"authorName": "AVAKS",
"authorContact": "https://t.me/avaks",
"authorGit": "https://github.com/avaksru",
"specialThanks": "@itsid1 @Valiuhaaa Serg @Serghei63",
"moduleName": "LogingHourly",
"moduleVersion": "2",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"title": "График часового расхода",
"moduleDesc": "Расширение позволяющее логировать накопительные величины и видеть их часовое изменение. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp",
"propInfo": {
"int": "Интервал логирования в мнутах, частота проверки смены часа в минутах. Не рекомендуется менять",
"logid": "ID накопительной величины которую будем логировать",
"points": "Максимальное количество точек",
"telegram": "График будет отправлять в телеграм репорт с расходами каждый час",
"test": "Параметр необходим для разработчиков. Режим тестирования. График будет обновляться не раз в час, а кадый заданный в int интервал."
}
},
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": [],
"bk72*": []
}
}

View File

@@ -56,6 +56,7 @@
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -74,6 +74,7 @@
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -0,0 +1,27 @@
#include "Global.h"
#include "classes/IoTItem.h"
class UpdateServer : public IoTItem {
public:
UpdateServer(String parameters) : IoTItem(parameters) {}
void onModuleOrder(String &key, String &value) {
if (key == "startUpdateAll") {
upgrade_firmware(3, value);
} else if (key == "startUpdateFS") {
upgrade_firmware(2, value);
} else if (key == "startUpdateFW") {
upgrade_firmware(1, value);
}
}
~UpdateServer() {};
};
void* getAPI_UpdateServer(String subtype, String param) {
if (subtype == F("UpdateServer")) {
return new UpdateServer(param);
} else {
return nullptr;
}
}

View File

@@ -0,0 +1,41 @@
{
"menuSection": "virtual_elments",
"configItem": [
{
"global": 0,
"name": "Свой сервер обновлений",
"type": "Reading",
"subtype": "UpdateServer",
"id": "UpdateServer",
"widget": "",
"page": "",
"descr": "",
"btn-startUpdateAll": "http://192.168.11.112/iotm/esp8266_4mb/400",
"btn-startUpdateFS": "http://192.168.11.112/iotm/esp8266_4mb/400",
"btn-startUpdateFW": "http://192.168.11.112/iotm/esp8266_4mb/400"
}
],
"about": {
"authorName": "Ilya Belyakov",
"authorContact": "https://t.me/Biveraxe",
"authorGit": "https://github.com/biveraxe",
"exampleURL": "https://iotmanager.org/",
"specialThanks": "",
"moduleName": "UpdateServer",
"moduleVersion": "1.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"title": "Свой сервер обновлений",
"moduleDesc": "Модуль для получения прошивки из своего сервера обновлений.",
"propInfo": {
"btn-startUpdate": "Кнопка запуска процесса обновления из указанного URL"
}
},
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
}
}

View File

@@ -36,6 +36,7 @@
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -96,6 +96,7 @@
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -88,6 +88,7 @@
"usedLibs": {
"esp32*": [],
"esp82*": []
"esp82*": [],
"bk72*": []
}
}

View File

@@ -77,7 +77,7 @@ public:
if (httpCode > 0)
{
ret = httpCode;
ret = String(httpCode);
if (httpCode == HTTP_CODE_OK)
{