mirror of
https://github.com/IoTManagerProject/IoTManager.git
synced 2026-06-10 20:09:19 +03:00
Merge branch 'ver4dev' into ver4dev
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
1309
src/modules/display/GyverLAMP/GyverLAMP.cpp
Normal file
1309
src/modules/display/GyverLAMP/GyverLAMP.cpp
Normal file
File diff suppressed because it is too large
Load Diff
9
src/modules/display/GyverLAMP/config.h
Normal file
9
src/modules/display/GyverLAMP/config.h
Normal 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 ленты
|
||||
1833
src/modules/display/GyverLAMP/effects.h
Normal file
1833
src/modules/display/GyverLAMP/effects.h
Normal file
File diff suppressed because it is too large
Load Diff
770
src/modules/display/GyverLAMP/matrix.h
Normal file
770
src/modules/display/GyverLAMP/matrix.h
Normal 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;
|
||||
}
|
||||
40
src/modules/display/GyverLAMP/modinfo.json
Normal file
40
src/modules/display/GyverLAMP/modinfo.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
92
src/modules/display/GyverLAMP/utility.h
Normal file
92
src/modules/display/GyverLAMP/utility.h
Normal 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));
|
||||
}
|
||||
@@ -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
@@ -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 doesn’t 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 doesn’t 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__ */
|
||||
|
||||
@@ -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> ¶m)
|
||||
@@ -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)
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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"), "... "); });
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [
|
||||
"https://github.com/stblassitude/Adafruit_SSD1306_Wemos_OLED",
|
||||
|
||||
@@ -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, ®s[0]);
|
||||
|
||||
jsonRead(parameters, "id2show", _show);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": []
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
|
||||
182
src/modules/exec/EctoControlAdapter/AdapterCommon.h
Normal file
182
src/modules/exec/EctoControlAdapter/AdapterCommon.h
Normal 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 - не было команды.
|
||||
*/
|
||||
817
src/modules/exec/EctoControlAdapter/EctoControlAdapter.cpp
Normal file
817
src/modules/exec/EctoControlAdapter/EctoControlAdapter.cpp
Normal 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> ¶m)
|
||||
{
|
||||
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 ®, 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 ®, 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;
|
||||
}
|
||||
}
|
||||
543
src/modules/exec/EctoControlAdapter/ModbusEC.cpp
Normal file
543
src/modules/exec/EctoControlAdapter/ModbusEC.cpp
Normal 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;
|
||||
}
|
||||
97
src/modules/exec/EctoControlAdapter/ModbusEC.h
Normal file
97
src/modules/exec/EctoControlAdapter/ModbusEC.h
Normal 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
|
||||
208
src/modules/exec/EctoControlAdapter/modinfo.json
Normal file
208
src/modules/exec/EctoControlAdapter/modinfo.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
88
src/modules/exec/EctoControlAdapter/util/crc16.h
Normal file
88
src/modules/exec/EctoControlAdapter/util/crc16.h
Normal 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_ */
|
||||
64
src/modules/exec/EctoControlAdapter/util/word.h
Normal file
64
src/modules/exec/EctoControlAdapter/util/word.h
Normal 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_ */
|
||||
195
src/modules/exec/IRremote/IRremote.cpp
Normal file
195
src/modules/exec/IRremote/IRremote.cpp
Normal 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> ¶m) {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
109
src/modules/exec/IRremote/modinfo.json
Normal file
109
src/modules/exec/IRremote/modinfo.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [
|
||||
"https://github.com/RoboticsBrno/ServoESP32#v1.0.3"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
},
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32c6_4mb": [
|
||||
"exclude"
|
||||
],
|
||||
"esp32c6_8mb": [
|
||||
"exclude"
|
||||
],
|
||||
"esp32*": []
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"title": "Расширитель портов Pcf8574"
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [
|
||||
"adafruit/Adafruit BusIO @ ^1.13.2"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"freq": "Частота"
|
||||
}
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp82*": []
|
||||
}
|
||||
|
||||
@@ -78,8 +78,6 @@
|
||||
},
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32_4mb": [],
|
||||
"esp32_4mb3f": [],
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
}
|
||||
|
||||
@@ -164,7 +164,6 @@
|
||||
},
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32_4mb3f": [],
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
171
src/modules/exec/Thermostat/GyverPID.h
Normal file
171
src/modules/exec/Thermostat/GyverPID.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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*": []
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"moduleName": "Scenario",
|
||||
"moduleVersion": "1.0",
|
||||
"title": "Сценарии",
|
||||
"moduleDesc": "Сценарии позволяют реализовать индивидуальный алгоритм работы контроллера с учетом происходящих событий. Они представляют из себя описательный язык того, что нужно сделать при наступлении того или иного события, учитывая конкретные условия. \nВ базе языка - выражение вида: “Если условие истина, то выполнить одно действие, а если нет, то иное”. При этом проверка такого выражения будет осуществляться только при наступлении события связанного с элементом конфигурации, который упоминается в этом выражении. \nУсловием или действием может быть любое разрешенное выражение. Они все при выполнении возвращают значение. Выражение может состоять из: идентификаторов элементов конфигурации, чисел (целые, дробные и отрицательные), строк в кавычках, операций сравнения < > <= >= == !=, операций присваивания значений =, математических операций +-*/, логических операций &|, комментариев после символа #, функций (в параметрах которых так же могут быть любые разрешенные выражения), конструкции ветвления IfThenElse, группирующие блоки выражений {}",
|
||||
"moduleDesc": "Сценарии позволяют реализовать индивидуальный алгоритм работы контроллера с учетом происходящих событий. Они представляют из себя описательный язык того, что нужно сделать при наступлении того или иного события, учитывая конкретные условия. \nВ базе языка - выражение вида: “Если условие истина, то выполнить одно действие, а если нет, то иное”. При этом проверка такого выражения будет осуществляться только при наступлении события связанного с элементом конфигурации, который упоминается в этом выражении. \nУсловием или действием может быть любое разрешенное выражение. Они все при выполнении возвращают значение. Выражение может состоять из: идентификаторов элементов конфигурации, чисел (целые, дробные и отрицательные), строк в кавычках, операций сравнения < > <= >= == !=, операций присваивания значений =, математических операций +-*/, логических операций &|, комментариев после символа #, функций (в параметрах которых так же могут быть любые разрешенные выражения), конструкции ветвления IfThenElse, группирующие блоки выражений {}, экранирование символа кавычек и переноса строки",
|
||||
"funcInfo": [
|
||||
{
|
||||
"name": "getIntFromNet",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"title": "A0221AU, A02YYUW Ультразвуковой датчик дальности"
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
},
|
||||
"title": "Acs712 Датчик тока"
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -129,9 +129,10 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
291
src/modules/sensors/BL0942/BL0942.cpp
Normal file
291
src/modules/sensors/BL0942/BL0942.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
116
src/modules/sensors/BL0942/datatypes.h
Normal file
116
src/modules/sensors/BL0942/datatypes.h
Normal 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));
|
||||
108
src/modules/sensors/BL0942/modinfo.json
Normal file
108
src/modules/sensors/BL0942/modinfo.json
Normal 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*": []
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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*": []
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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*": []
|
||||
}
|
||||
}
|
||||
|
||||
399
src/modules/sensors/ModbusRTU/MudbusRTU.cpp
Normal file
399
src/modules/sensors/ModbusRTU/MudbusRTU.cpp
Normal 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 ®, 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> ¶m)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
124
src/modules/sensors/ModbusRTU/modinfo.json
Normal file
124
src/modules/sensors/ModbusRTU/modinfo.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
466
src/modules/sensors/ModbusRTUasync/ModbusRTU.cpp
Normal file
466
src/modules/sensors/ModbusRTUasync/ModbusRTU.cpp
Normal 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> ¶m)
|
||||
{
|
||||
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> ¶m)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
103
src/modules/sensors/ModbusRTUasync/modinfo.json
Normal file
103
src/modules/sensors/ModbusRTUasync/modinfo.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
"Beta": "Beta термистора"
|
||||
}
|
||||
},
|
||||
"defActive": false,
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"descr": "PCF_0",
|
||||
"pin": "0",
|
||||
"mode": "volt",
|
||||
"adc_ref": 5.0,
|
||||
"map": "1,255,1,100",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
@@ -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*": []
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
"int": "Количество секунд между опросами датчика."
|
||||
}
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"int": "Количество секунд между опросами датчика."
|
||||
}
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [
|
||||
"robtillaart/SHT2x@^0.1.1"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"int": "Количество секунд между опросами датчика."
|
||||
}
|
||||
},
|
||||
"defActive": true,
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
259
src/modules/virtual/DiscoveryHA/DiscoveryHA.cpp
Normal file
259
src/modules/virtual/DiscoveryHA/DiscoveryHA.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
40
src/modules/virtual/DiscoveryHA/modinfo.json
Normal file
40
src/modules/virtual/DiscoveryHA/modinfo.json
Normal 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*": []
|
||||
}
|
||||
}
|
||||
295
src/modules/virtual/DiscoveryHomeD/DiscoveryHomeD.cpp
Normal file
295
src/modules/virtual/DiscoveryHomeD/DiscoveryHomeD.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
41
src/modules/virtual/DiscoveryHomeD/modinfo.json
Normal file
41
src/modules/virtual/DiscoveryHomeD/modinfo.json
Normal 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*": []
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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*": []
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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*": []
|
||||
}
|
||||
}
|
||||
339
src/modules/virtual/LogingHourly/LogingHourly.cpp
Normal file
339
src/modules/virtual/LogingHourly/LogingHourly.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
50
src/modules/virtual/LogingHourly/modinfo.json
Normal file
50
src/modules/virtual/LogingHourly/modinfo.json
Normal 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*": []
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
27
src/modules/virtual/UpdateServer/UpdateServer.cpp
Normal file
27
src/modules/virtual/UpdateServer/UpdateServer.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
41
src/modules/virtual/UpdateServer/modinfo.json
Normal file
41
src/modules/virtual/UpdateServer/modinfo.json
Normal 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*": []
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,7 @@
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,7 @@
|
||||
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
"esp82*": [],
|
||||
"bk72*": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
|
||||
if (httpCode > 0)
|
||||
{
|
||||
ret = httpCode;
|
||||
ret = String(httpCode);
|
||||
|
||||
if (httpCode == HTTP_CODE_OK)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user