mirror of
https://github.com/IoTManagerProject/IoTManager.git
synced 2026-03-30 20:09:14 +03:00
new Modules from Chat
This commit is contained in:
@@ -212,6 +212,10 @@
|
|||||||
"path": "src/modules/virtual/Ping",
|
"path": "src/modules/virtual/Ping",
|
||||||
"active": true
|
"active": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "src/modules/virtual/SolarCalc",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "src/modules/virtual/Timer",
|
"path": "src/modules/virtual/Timer",
|
||||||
"active": true
|
"active": true
|
||||||
@@ -374,6 +378,10 @@
|
|||||||
"path": "src/modules/sensors/MQgas",
|
"path": "src/modules/sensors/MQgas",
|
||||||
"active": false
|
"active": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "src/modules/sensors/NoiseAdc",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "src/modules/sensors/Ntc",
|
"path": "src/modules/sensors/Ntc",
|
||||||
"active": true
|
"active": true
|
||||||
@@ -382,6 +390,10 @@
|
|||||||
"path": "src/modules/sensors/Pcf8591",
|
"path": "src/modules/sensors/Pcf8591",
|
||||||
"active": false
|
"active": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "src/modules/sensors/Presence",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "src/modules/sensors/Pzem004t",
|
"path": "src/modules/sensors/Pzem004t",
|
||||||
"active": false
|
"active": false
|
||||||
@@ -422,6 +434,10 @@
|
|||||||
"path": "src/modules/sensors/Sht30",
|
"path": "src/modules/sensors/Sht30",
|
||||||
"active": false
|
"active": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "src/modules/sensors/SoftRTC",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "src/modules/sensors/Sonar",
|
"path": "src/modules/sensors/Sonar",
|
||||||
"active": false
|
"active": false
|
||||||
@@ -548,6 +564,10 @@
|
|||||||
"path": "src/modules/exec/Thermostat",
|
"path": "src/modules/exec/Thermostat",
|
||||||
"active": true
|
"active": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "src/modules/exec/WakeOnLanModule",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "src/modules/sensors/Ds2423",
|
"path": "src/modules/sensors/Ds2423",
|
||||||
"active": false
|
"active": false
|
||||||
@@ -566,6 +586,10 @@
|
|||||||
"path": "src/modules/display/Lcd2004",
|
"path": "src/modules/display/Lcd2004",
|
||||||
"active": false
|
"active": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "src/modules/display/LedFX",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "src/modules/display/Nextion",
|
"path": "src/modules/display/Nextion",
|
||||||
"active": false
|
"active": false
|
||||||
@@ -590,6 +614,10 @@
|
|||||||
"path": "src/modules/display/TM16XX",
|
"path": "src/modules/display/TM16XX",
|
||||||
"active": false
|
"active": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "src/modules/display/U8g2lib",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "src/modules/display/Ws2812b",
|
"path": "src/modules/display/Ws2812b",
|
||||||
"active": false
|
"active": false
|
||||||
|
|||||||
@@ -1,182 +0,0 @@
|
|||||||
#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 - не было команды.
|
|
||||||
*/
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
#include "ModbusEC.h"
|
#include "ModbusEC.h"
|
||||||
|
|
||||||
#define COUNT_BIT_AVAIL 5
|
#define COUNT_BIT_AVAIL 5
|
||||||
@@ -11,19 +10,8 @@ ModbusMaster::ModbusMaster(void)
|
|||||||
_postTransmission = 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)
|
void ModbusMaster::begin(uint8_t slave, Stream *serial)
|
||||||
{
|
{
|
||||||
// txBuffer = (uint16_t*) calloc(ku8MaxBufferSize, sizeof(uint16_t));
|
|
||||||
_u8MBSlave = slave;
|
_u8MBSlave = slave;
|
||||||
_serial = serial;
|
_serial = serial;
|
||||||
_u8TransmitBufferIndex = 0;
|
_u8TransmitBufferIndex = 0;
|
||||||
@@ -42,16 +30,13 @@ void ModbusMaster::beginTransmission(uint16_t u16Address)
|
|||||||
u16TransmitBufferLength = 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 ModbusMaster::requestFrom(uint16_t address, uint16_t quantity)
|
||||||
{
|
{
|
||||||
uint8_t read;
|
uint8_t read = 0; // Èíèöèàëèçèðîâàíî çíà÷åíèå ïî óìîë÷àíèþ
|
||||||
// clamp to buffer length
|
|
||||||
if (quantity > ku8MaxBufferSize)
|
if (quantity > ku8MaxBufferSize)
|
||||||
{
|
{
|
||||||
quantity = ku8MaxBufferSize;
|
quantity = ku8MaxBufferSize;
|
||||||
}
|
}
|
||||||
// set rx buffer iterator vars
|
|
||||||
_u8ResponseBufferIndex = 0;
|
_u8ResponseBufferIndex = 0;
|
||||||
_u8ResponseBufferLength = read;
|
_u8ResponseBufferLength = read;
|
||||||
|
|
||||||
@@ -110,62 +95,21 @@ uint16_t ModbusMaster::receive(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
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)())
|
void ModbusMaster::idle(void (*idle)())
|
||||||
{
|
{
|
||||||
_idle = 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)())
|
void ModbusMaster::preTransmission(void (*preTransmission)())
|
||||||
{
|
{
|
||||||
_preTransmission = 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)())
|
void ModbusMaster::postTransmission(void (*postTransmission)())
|
||||||
{
|
{
|
||||||
_postTransmission = 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)
|
uint16_t ModbusMaster::getResponseBuffer(uint8_t u8Index)
|
||||||
{
|
{
|
||||||
if (u8Index < ku8MaxBufferSize)
|
if (u8Index < ku8MaxBufferSize)
|
||||||
@@ -178,31 +122,15 @@ uint16_t ModbusMaster::getResponseBuffer(uint8_t u8Index)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
Clear Modbus response buffer.
|
|
||||||
|
|
||||||
@see ModbusMaster::getResponseBuffer(uint8_t u8Index)
|
|
||||||
@ingroup buffer
|
|
||||||
*/
|
|
||||||
void ModbusMaster::clearResponseBuffer()
|
void ModbusMaster::clearResponseBuffer()
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|
||||||
for (i = 0; i < ku8MaxBufferSize; i++)
|
for (i = 0; i < ku8MaxBufferSize; i++)
|
||||||
{
|
{
|
||||||
_u16ResponseBuffer[i] = 0;
|
_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)
|
uint8_t ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value)
|
||||||
{
|
{
|
||||||
if (u8Index < ku8MaxBufferSize)
|
if (u8Index < ku8MaxBufferSize)
|
||||||
@@ -216,38 +144,15 @@ uint8_t ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
Clear Modbus transmit buffer.
|
|
||||||
|
|
||||||
@see ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value)
|
|
||||||
@ingroup buffer
|
|
||||||
*/
|
|
||||||
void ModbusMaster::clearTransmitBuffer()
|
void ModbusMaster::clearTransmitBuffer()
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|
||||||
for (i = 0; i < ku8MaxBufferSize; i++)
|
for (i = 0; i < ku8MaxBufferSize; i++)
|
||||||
{
|
{
|
||||||
_u16TransmitBuffer[i] = 0;
|
_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,
|
uint8_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress,
|
||||||
uint16_t u16ReadQty)
|
uint16_t u16ReadQty)
|
||||||
{
|
{
|
||||||
@@ -256,18 +161,6 @@ uint8_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress,
|
|||||||
return ModbusMasterTransaction(ku8MBReadHoldingRegisters);
|
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,
|
uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress,
|
||||||
uint16_t u16WriteValue)
|
uint16_t u16WriteValue)
|
||||||
{
|
{
|
||||||
@@ -277,20 +170,6 @@ uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress,
|
|||||||
return ModbusMasterTransaction(ku8MBWriteSingleRegister);
|
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,
|
uint8_t ModbusMaster::writeMultipleRegisters(uint16_t u16WriteAddress,
|
||||||
uint16_t u16WriteQty)
|
uint16_t u16WriteQty)
|
||||||
{
|
{
|
||||||
@@ -299,7 +178,6 @@ uint8_t ModbusMaster::writeMultipleRegisters(uint16_t u16WriteAddress,
|
|||||||
return ModbusMasterTransaction(ku8MBWriteMultipleRegisters);
|
return ModbusMasterTransaction(ku8MBWriteMultipleRegisters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// new version based on Wire.h
|
|
||||||
uint8_t ModbusMaster::writeMultipleRegisters()
|
uint8_t ModbusMaster::writeMultipleRegisters()
|
||||||
{
|
{
|
||||||
_u16WriteQty = _u8TransmitBufferIndex;
|
_u16WriteQty = _u8TransmitBufferIndex;
|
||||||
@@ -313,6 +191,7 @@ uint8_t ModbusMaster::readAddresEctoControl()
|
|||||||
ModbusMasterTransaction(ku8MBProgRead46);
|
ModbusMasterTransaction(ku8MBProgRead46);
|
||||||
return getResponseBuffer(0x00);
|
return getResponseBuffer(0x00);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t ModbusMaster::writeAddresEctoControl(uint8_t addr)
|
uint8_t ModbusMaster::writeAddresEctoControl(uint8_t addr)
|
||||||
{
|
{
|
||||||
_u16WriteAddress = 0x00;
|
_u16WriteAddress = 0x00;
|
||||||
@@ -321,20 +200,6 @@ uint8_t ModbusMaster::writeAddresEctoControl(uint8_t addr)
|
|||||||
return ModbusMasterTransaction(ku8MBProgWrite47);
|
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 ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
|
||||||
{
|
{
|
||||||
uint8_t u8ModbusADU[24];
|
uint8_t u8ModbusADU[24];
|
||||||
@@ -345,7 +210,7 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
|
|||||||
uint8_t u8BytesLeft = 8;
|
uint8_t u8BytesLeft = 8;
|
||||||
uint8_t u8MBStatus = ku8MBSuccess;
|
uint8_t u8MBStatus = ku8MBSuccess;
|
||||||
|
|
||||||
// assemble Modbus Request Application Data Unit
|
// Assemble Modbus Request Application Data Unit
|
||||||
if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47)
|
if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47)
|
||||||
{
|
{
|
||||||
u8ModbusADU[u8ModbusADUSize++] = 0x00;
|
u8ModbusADU[u8ModbusADUSize++] = 0x00;
|
||||||
@@ -382,7 +247,8 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
|
|||||||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty);
|
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty);
|
||||||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty << 1);
|
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty << 1);
|
||||||
|
|
||||||
for (i = 0; i < lowByte(_u16WriteQty); i++)
|
// Èñïðàâëåíî: èñïîëüçóåì _u16WriteQty âìåñòî lowByte()
|
||||||
|
for (i = 0; i < _u16WriteQty; i++)
|
||||||
{
|
{
|
||||||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[i]);
|
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[i]);
|
||||||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[i]);
|
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[i]);
|
||||||
@@ -390,47 +256,31 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// append CRC
|
// Append CRC
|
||||||
u16CRC = 0xFFFF;
|
u16CRC = 0xFFFF;
|
||||||
for (i = 0; i < u8ModbusADUSize; i++)
|
for (i = 0; i < u8ModbusADUSize; i++)
|
||||||
{
|
{
|
||||||
u16CRC = crc16_update(u16CRC, u8ModbusADU[i]);
|
u16CRC = crc16_update(u16CRC, u8ModbusADU[i]);
|
||||||
}
|
}
|
||||||
// if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47)
|
|
||||||
// {
|
|
||||||
// u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC);
|
|
||||||
// u8ModbusADU[u8ModbusADUSize++] = lowByte(u16CRC);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
u8ModbusADU[u8ModbusADUSize++] = lowByte(u16CRC);
|
u8ModbusADU[u8ModbusADUSize++] = lowByte(u16CRC);
|
||||||
u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC);
|
u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC);
|
||||||
// }
|
|
||||||
|
|
||||||
u8ModbusADU[u8ModbusADUSize] = 0;
|
u8ModbusADU[u8ModbusADUSize] = 0;
|
||||||
|
|
||||||
// flush receive buffer before transmitting request
|
// Flush receive buffer before transmitting request
|
||||||
while (_serial->read() != -1)
|
while (_serial->read() != -1);
|
||||||
;
|
|
||||||
|
|
||||||
// transmit request
|
// Transmit request
|
||||||
if (_preTransmission)
|
if (_preTransmission) _preTransmission();
|
||||||
{
|
|
||||||
_preTransmission();
|
|
||||||
}
|
|
||||||
for (i = 0; i < u8ModbusADUSize; i++)
|
for (i = 0; i < u8ModbusADUSize; i++)
|
||||||
{
|
{
|
||||||
_serial->write(u8ModbusADU[i]);
|
_serial->write(u8ModbusADU[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
u8ModbusADUSize = 0;
|
u8ModbusADUSize = 0;
|
||||||
_serial->flush(); // flush transmit buffer
|
_serial->flush();
|
||||||
if (_postTransmission)
|
if (_postTransmission) _postTransmission();
|
||||||
{
|
|
||||||
_postTransmission();
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop until we run out of time or bytes, or an error occurs
|
// Loop until timeout or response complete
|
||||||
u32StartTime = millis();
|
u32StartTime = millis();
|
||||||
while (u8BytesLeft && !u8MBStatus)
|
while (u8BytesLeft && !u8MBStatus)
|
||||||
{
|
{
|
||||||
@@ -441,7 +291,6 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
|
|||||||
#endif
|
#endif
|
||||||
uint8_t req = _serial->read();
|
uint8_t req = _serial->read();
|
||||||
u8ModbusADU[u8ModbusADUSize++] = req;
|
u8ModbusADU[u8ModbusADUSize++] = req;
|
||||||
Serial.print(req, HEX);
|
|
||||||
u8BytesLeft--;
|
u8BytesLeft--;
|
||||||
#if __MODBUSMASTER_DEBUG__
|
#if __MODBUSMASTER_DEBUG__
|
||||||
digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, false);
|
digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, false);
|
||||||
@@ -452,132 +301,110 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
|
|||||||
#if __MODBUSMASTER_DEBUG__
|
#if __MODBUSMASTER_DEBUG__
|
||||||
digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, true);
|
digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, true);
|
||||||
#endif
|
#endif
|
||||||
if (_idle)
|
if (_idle) _idle();
|
||||||
{
|
|
||||||
_idle();
|
|
||||||
}
|
|
||||||
#if __MODBUSMASTER_DEBUG__
|
#if __MODBUSMASTER_DEBUG__
|
||||||
digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, false);
|
digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, false);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluate slave ID, function code once enough bytes have been read
|
// Evaluate response after enough bytes received
|
||||||
uint8_t count;
|
uint8_t count = (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47)
|
||||||
if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47)
|
? COUNT_BIT_AVAIL_46F : COUNT_BIT_AVAIL;
|
||||||
count = COUNT_BIT_AVAIL_46F;
|
|
||||||
else
|
|
||||||
count = COUNT_BIT_AVAIL;
|
|
||||||
|
|
||||||
if (u8ModbusADUSize == count)
|
if (u8ModbusADUSize == count)
|
||||||
{
|
{
|
||||||
if (u8MBFunction != ku8MBProgRead46 && u8MBFunction != ku8MBProgWrite47)
|
if (u8MBFunction != ku8MBProgRead46 && u8MBFunction != ku8MBProgWrite47)
|
||||||
{
|
{
|
||||||
// verify response is for correct Modbus slave
|
|
||||||
if (u8ModbusADU[0] != _u8MBSlave)
|
if (u8ModbusADU[0] != _u8MBSlave)
|
||||||
{
|
{
|
||||||
// Serial.print(u8ModbusADU[0], HEX);
|
|
||||||
// Serial.print(" != ");
|
|
||||||
// Serial.println(_u8MBSlave, HEX);
|
|
||||||
|
|
||||||
u8MBStatus = ku8MBInvalidSlaveID;
|
u8MBStatus = ku8MBInvalidSlaveID;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify response is for correct Modbus function code (mask exception bit 7)
|
|
||||||
if ((u8ModbusADU[1] & 0x7F) != u8MBFunction)
|
if ((u8ModbusADU[1] & 0x7F) != u8MBFunction)
|
||||||
{
|
{
|
||||||
u8MBStatus = ku8MBInvalidFunction;
|
u8MBStatus = ku8MBInvalidFunction;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check whether Modbus exception occurred; return Modbus Exception Code
|
|
||||||
if (bitRead(u8ModbusADU[1], 7))
|
if (bitRead(u8ModbusADU[1], 7))
|
||||||
{
|
{
|
||||||
u8MBStatus = u8ModbusADU[2];
|
u8MBStatus = u8ModbusADU[2];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluate returned Modbus function code
|
// Determine remaining bytes based on function
|
||||||
switch (u8ModbusADU[1])
|
switch (u8ModbusADU[1])
|
||||||
{
|
{
|
||||||
case ku8MBReadHoldingRegisters:
|
case ku8MBReadHoldingRegisters:
|
||||||
u8BytesLeft = u8ModbusADU[2];
|
u8BytesLeft = u8ModbusADU[2];
|
||||||
break;
|
break;
|
||||||
|
case ku8MBWriteMultipleRegisters:
|
||||||
case ku8MBWriteMultipleRegisters:
|
u8BytesLeft = 3;
|
||||||
u8BytesLeft = 3;
|
break;
|
||||||
break;
|
case ku8MBProgRead46:
|
||||||
|
case ku8MBProgWrite47:
|
||||||
case ku8MBProgRead46:
|
u8BytesLeft = 1;
|
||||||
u8BytesLeft = 1;
|
break;
|
||||||
break;
|
default: ; // Ïóñòîé îïåðàòîð äëÿ êîððåêòíîãî ñèíòàêñèñà
|
||||||
|
|
||||||
case ku8MBProgWrite47:
|
|
||||||
u8BytesLeft = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((millis() - u32StartTime) > ku16MBResponseTimeout)
|
if ((millis() - u32StartTime) > ku16MBResponseTimeout)
|
||||||
{
|
{
|
||||||
u8MBStatus = ku8MBResponseTimedOut;
|
u8MBStatus = ku8MBResponseTimedOut;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (u8MBFunction != ku8MBProgRead46 && u8MBFunction != ku8MBProgWrite47)
|
// Verify CRC for standard functions
|
||||||
|
if (!u8MBStatus &&
|
||||||
|
u8MBFunction != ku8MBProgRead46 &&
|
||||||
|
u8MBFunction != ku8MBProgWrite47 &&
|
||||||
|
u8ModbusADUSize >= COUNT_BIT_AVAIL)
|
||||||
{
|
{
|
||||||
// verify response is large enough to inspect further
|
u16CRC = 0xFFFF;
|
||||||
if (!u8MBStatus && u8ModbusADUSize >= COUNT_BIT_AVAIL)
|
for (i = 0; i < (u8ModbusADUSize - 2); i++)
|
||||||
{
|
{
|
||||||
// calculate CRC
|
u16CRC = crc16_update(u16CRC, u8ModbusADU[i]);
|
||||||
u16CRC = 0xFFFF;
|
}
|
||||||
for (i = 0; i < (u8ModbusADUSize - 2); i++)
|
|
||||||
{
|
|
||||||
u16CRC = crc16_update(u16CRC, u8ModbusADU[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify CRC
|
// Èñïðàâëåíî: äîáàâëåíû ñêîáêè äëÿ ãðóïïèðîâêè óñëîâèé
|
||||||
if (!u8MBStatus && (lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2] ||
|
if (!u8MBStatus &&
|
||||||
highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1]))
|
((lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2]) ||
|
||||||
{
|
(highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1])))
|
||||||
u8MBStatus = ku8MBInvalidCRC;
|
{
|
||||||
}
|
u8MBStatus = ku8MBInvalidCRC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// disassemble ADU into words
|
|
||||||
|
// Parse response data
|
||||||
if (!u8MBStatus)
|
if (!u8MBStatus)
|
||||||
{
|
{
|
||||||
// evaluate returned Modbus function code
|
|
||||||
switch (u8ModbusADU[1])
|
switch (u8ModbusADU[1])
|
||||||
{
|
{
|
||||||
case ku8MBReadHoldingRegisters:
|
case ku8MBReadHoldingRegisters:
|
||||||
// load bytes into word; response bytes are ordered H, L, H, L, ...
|
for (i = 0; i < (u8ModbusADU[2] >> 1); i++)
|
||||||
for (i = 0; i < (u8ModbusADU[2] >> 1); i++)
|
|
||||||
{
|
|
||||||
if (i < ku8MaxBufferSize)
|
|
||||||
{
|
{
|
||||||
_u16ResponseBuffer[i] = word(u8ModbusADU[2 * i + 3], u8ModbusADU[2 * i + 4]);
|
if (i < ku8MaxBufferSize)
|
||||||
|
{
|
||||||
|
_u16ResponseBuffer[i] = word(u8ModbusADU[2 * i + 3], u8ModbusADU[2 * i + 4]);
|
||||||
|
}
|
||||||
|
_u8ResponseBufferLength = i;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
_u8ResponseBufferLength = i;
|
|
||||||
}
|
case ku8MBProgRead46:
|
||||||
break;
|
_u16ResponseBuffer[0] = (uint16_t)u8ModbusADU[2];
|
||||||
case ku8MBProgRead46:
|
_u8ResponseBufferLength = 1;
|
||||||
Serial.print("ku8MBProgRead46");
|
break;
|
||||||
for (i = 0; i < (u8ModbusADUSize); i++)
|
|
||||||
{
|
|
||||||
Serial.println(u8ModbusADU[i], HEX);
|
|
||||||
}
|
|
||||||
|
|
||||||
_u16ResponseBuffer[0] = (uint16_t)u8ModbusADU[2];
|
|
||||||
_u8ResponseBufferLength = 1;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset buffers
|
||||||
_u8TransmitBufferIndex = 0;
|
_u8TransmitBufferIndex = 0;
|
||||||
u16TransmitBufferLength = 0;
|
u16TransmitBufferLength = 0;
|
||||||
_u8ResponseBufferIndex = 0;
|
_u8ResponseBufferIndex = 0;
|
||||||
|
|
||||||
return u8MBStatus;
|
return u8MBStatus;
|
||||||
}
|
}
|
||||||
459
src/modules/exec/EctoControlAdapter/export - Копия.json
Normal file
459
src/modules/exec/EctoControlAdapter/export - Копия.json
Normal file
@@ -0,0 +1,459 @@
|
|||||||
|
{
|
||||||
|
"mark": "iotm",
|
||||||
|
"config": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "boilerConnected",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Статус котла",
|
||||||
|
"descr": "✔ Связь с котлом",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "burner",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Статус котла",
|
||||||
|
"descr": "🔥 Горелка",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "heating",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Статус котла",
|
||||||
|
"descr": "☢ Контур отопления",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "dhw",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Статус котла",
|
||||||
|
"descr": "🛁 Контур ГВС",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": "1",
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "tempCH",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Котёл",
|
||||||
|
"descr": "🌡️ Теплоносителя",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": "1",
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "tempDHW",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Котёл",
|
||||||
|
"descr": "🌡️ ГВС",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "VButton",
|
||||||
|
"id": "setStatusCH",
|
||||||
|
"needSave": "0",
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "Управление",
|
||||||
|
"descr": "☢ Отопление (вкл/выкл)",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "VButton",
|
||||||
|
"id": "setStatusDHW",
|
||||||
|
"needSave": "0",
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "Управление",
|
||||||
|
"descr": "🛁 Гвс (вкл/выкл)",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "setTCH",
|
||||||
|
"needSave": "0",
|
||||||
|
"widget": "inputDgt",
|
||||||
|
"page": "Управление",
|
||||||
|
"descr": "🌡️ Уставка отопления",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": "1",
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Ds18b20",
|
||||||
|
"id": "temp",
|
||||||
|
"widget": "anydataTmp",
|
||||||
|
"page": "Термостат",
|
||||||
|
"descr": "🌡️ В помещении",
|
||||||
|
"int": "120",
|
||||||
|
"pin": "15",
|
||||||
|
"index": "0",
|
||||||
|
"addr": "28163C0D000000CF",
|
||||||
|
"round": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "SetTempId",
|
||||||
|
"needSave": "1",
|
||||||
|
"widget": "inputDgt",
|
||||||
|
"page": "Термостат",
|
||||||
|
"descr": "🌡️ Желаемая",
|
||||||
|
"int": "0",
|
||||||
|
"val": "23",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "setDHW",
|
||||||
|
"needSave": "1",
|
||||||
|
"widget": "inputDgt",
|
||||||
|
"page": "Управление",
|
||||||
|
"descr": "🌡️ Уставка ГВС",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "pressure",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Статус котла",
|
||||||
|
"descr": "💧 Давление",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "codeError",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Котёл",
|
||||||
|
"descr": "🚨 Ошибка котла",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "lasttemp",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "nil",
|
||||||
|
"page": "Ввод",
|
||||||
|
"descr": "Введите число",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "VButton",
|
||||||
|
"id": "auto",
|
||||||
|
"needSave": "1",
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "Термостат",
|
||||||
|
"descr": "Термостат (вкл/выкл",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0",
|
||||||
|
"moduleName": "VButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "tempOT",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "nil",
|
||||||
|
"page": "Ввод",
|
||||||
|
"descr": "Введите число",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Writing",
|
||||||
|
"subtype": "TelegramLT",
|
||||||
|
"id": "tg",
|
||||||
|
"widget": "",
|
||||||
|
"page": "",
|
||||||
|
"descr": "",
|
||||||
|
"token": "",
|
||||||
|
"chatID": "",
|
||||||
|
"moduleName": "TelegramLT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": "1",
|
||||||
|
"needSave": "0",
|
||||||
|
"type": "Writing",
|
||||||
|
"subtype": "ThermostatPID",
|
||||||
|
"id": "Thermo96",
|
||||||
|
"widget": "anydataTmp",
|
||||||
|
"page": "Термостат",
|
||||||
|
"descr": "🌡️ Расчетная",
|
||||||
|
"int": "10",
|
||||||
|
"round": 1,
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"set_id": "SetTempId",
|
||||||
|
"term_id": "temp",
|
||||||
|
"rele": "",
|
||||||
|
"setDirection": 0,
|
||||||
|
"setLimitsMIN": "20",
|
||||||
|
"setLimitsMAX": "60",
|
||||||
|
"KP": 10,
|
||||||
|
"KI": "0.01",
|
||||||
|
"KD": "8",
|
||||||
|
"moduleName": "Thermostat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "UpdateServer",
|
||||||
|
"id": "UpdateServer38",
|
||||||
|
"widget": "",
|
||||||
|
"page": "",
|
||||||
|
"descr": "",
|
||||||
|
"btn-startUpdateAll": "http://192.168.0.84:5500/iotm/esp32c6_8mb/400/",
|
||||||
|
"btn-startUpdateFS": "",
|
||||||
|
"btn-startUpdateFW": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "tSetCH",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Сохраненные значения",
|
||||||
|
"descr": "🌡️ Отопления",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "tSetDHW",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Сохраненные значения",
|
||||||
|
"descr": "🌡️ ГВС",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "writtenHeatingEnabled",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Сохраненные значения",
|
||||||
|
"descr": "Отопление (вкл/выкл)",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "writtenDHWEnabled",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Сохраненные значения",
|
||||||
|
"descr": "ГВС (вкл/выкл)",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "setMaxModLevel",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Сохраненные значения",
|
||||||
|
"descr": "Модуляция",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "ecAdapter",
|
||||||
|
"id": "ecto",
|
||||||
|
"widget": "nil",
|
||||||
|
"page": "Котёл",
|
||||||
|
"descr": "Адаптер",
|
||||||
|
"int": "20",
|
||||||
|
"addr": "23",
|
||||||
|
"RX": "11",
|
||||||
|
"TX": "10",
|
||||||
|
"DIR_PIN": "0",
|
||||||
|
"baud": 19200,
|
||||||
|
"protocol": "SERIAL_8N1",
|
||||||
|
"debug": "0",
|
||||||
|
"UARTLine": "1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario=>if onStart then {
|
||||||
|
tg.sendOftenMsg("E-bus http://" + getIP());
|
||||||
|
if auto then {Thermo96.enable(1);} else {Thermo96.enable(0);}
|
||||||
|
}
|
||||||
|
|
||||||
|
#включение/отключение отопления
|
||||||
|
if setStatusCH == 1 then ecto.setHeating(1);
|
||||||
|
if setStatusCH == 0 then ecto.setHeating(0);
|
||||||
|
|
||||||
|
#включение/отключение ГВС
|
||||||
|
if setStatusDHW == 1 then ecto.setDHW(1);
|
||||||
|
if setStatusDHW == 0 then ecto.setDHW(0);
|
||||||
|
|
||||||
|
#setStatusCH2
|
||||||
|
if setStatusCH2 == 1 then ecto.setSecondaryCircuit(1);
|
||||||
|
if setStatusCH2 == 0 then ecto.setSecondaryCircuit(0);
|
||||||
|
|
||||||
|
#Установка 🌡️ГВС
|
||||||
|
if setDHW then ecto.setTDHW (setDHW);
|
||||||
|
#Установка 🌡️ теплоносителя
|
||||||
|
if setTCH then ecto.setTCH (setTCH);
|
||||||
|
|
||||||
|
#Включение термостата пид
|
||||||
|
if auto == 1 then Thermo96.enable(1);
|
||||||
|
|
||||||
|
#Вылючение термостата пмд
|
||||||
|
if auto == 0 then {
|
||||||
|
Thermo96.enable(0);
|
||||||
|
tempOT = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
if Thermo96 > 38 then tempOT = Thermo96 else tempOT = 20;
|
||||||
|
|
||||||
|
#Сравнение температуры для предотвращения повторных данных
|
||||||
|
if tempOT != lasttemp then lasttemp = tempOT;
|
||||||
|
if lasttemp then ecto.setTCH (lasttemp);
|
||||||
|
|
||||||
|
#считывание записанных в адаптер значений
|
||||||
|
if writtenHeatingEnabled then setStatusCH := writtenHeatingEnabled;
|
||||||
|
if writtenDHWEnabled then setStatusDHW := writtenDHWEnabled;
|
||||||
|
if tSetDHW then setDHW := tSetDHW;
|
||||||
|
if tSetCH then setTCH := tSetCH;
|
||||||
|
|
||||||
|
#уведомления в телеграм
|
||||||
|
if codeError > 1 then tg.sendMsg("🚨 Ошибка котла F" + codeError);
|
||||||
|
if boilerConnected == 0 then tg.sendMsg("🚨 Отсутствует связь с котлом") else tg.sendMsg("Cвязь с котлом восстановлена") ;
|
||||||
|
if pressure < 0.9 then tg.sendMsg("💧 Низкое давление теплоносителя" + pressure);
|
||||||
@@ -17,16 +17,17 @@
|
|||||||
"DIR_PIN": 4,
|
"DIR_PIN": 4,
|
||||||
"baud": 19200,
|
"baud": 19200,
|
||||||
"protocol": "SERIAL_8N1",
|
"protocol": "SERIAL_8N1",
|
||||||
"debug": 1
|
"debug": 1,
|
||||||
|
"UARTLine": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"about": {
|
"about": {
|
||||||
"authorName": "Mikhail Bubnov",
|
"authorName": "Mikhail Bubnov",
|
||||||
"authorContact": "https://t.me/Mit4bmw",
|
"authorContact": "https://t.me/Mit4bmw",
|
||||||
"authorGit": "https://github.com/Mit4el",
|
"authorGit": "https://github.com/Mit4el",
|
||||||
"specialThanks": "",
|
"specialThanks": "SeregaKi",
|
||||||
"moduleName": "EctoControlAdapter",
|
"moduleName": "EctoControlAdapter",
|
||||||
"moduleVersion": "1.0",
|
"moduleVersion": "2.0",
|
||||||
"usedRam": {
|
"usedRam": {
|
||||||
"esp32_4mb": 15,
|
"esp32_4mb": 15,
|
||||||
"esp8266_4mb": 15
|
"esp8266_4mb": 15
|
||||||
@@ -37,80 +38,100 @@
|
|||||||
"title": "EctoControlAdapter",
|
"title": "EctoControlAdapter",
|
||||||
"moduleDesc": "Управление отопительным котлом через адаптер EctoControl по протоколам OpenTherm, eBUS, Navien. Посредством Modbus RTU. Разъем 4P4C: 1-Желтый(красный)+12V; 2-Белый-GND; 3-Зелёный-A; 4-Коричневый(Синий)-B",
|
"moduleDesc": "Управление отопительным котлом через адаптер EctoControl по протоколам OpenTherm, eBUS, Navien. Посредством Modbus RTU. Разъем 4P4C: 1-Желтый(красный)+12V; 2-Белый-GND; 3-Зелёный-A; 4-Коричневый(Синий)-B",
|
||||||
"propInfo": {
|
"propInfo": {
|
||||||
"addr": "Адрес slave, что бы узнать адрес - в конфиге адрес 0 и смотреть лог (требуется проверка)",
|
"addr": "Адрес slave, что бы узнать адрес - в конфиге адрес 0 и смотреть лог",
|
||||||
"int": "Количество секунд между опросами датчика.",
|
"int": "Количество секунд между опросами датчика.",
|
||||||
"RX": "Пин RX",
|
"RX": "Пин RX",
|
||||||
"TX": "Пин TX",
|
"TX": "Пин TX",
|
||||||
"DIR_PIN": "connect DR, RE pin of MAX485 to gpio, указать 0 если не нужен",
|
"DIR_PIN": "connect DR, RE pin of MAX485 to gpio, указать 0 если не нужен",
|
||||||
"baud": "скорость Uart",
|
"baud": "скорость Uart",
|
||||||
"protocol": "Протокол Uart: SERIAL_8N1 или SERIAL_8N2",
|
"protocol": "Протокол Uart: SERIAL_8N1 или SERIAL_8N2",
|
||||||
"debug": "0 - отключить дебаг, 1 - включить вывод дебага, 2 - лог комманд, 3 - вывод modbus"
|
"debug": "0 - отключить дебаг, 1 - включить вывод дебага, 2 - лог комманд, 3 - вывод modbus",
|
||||||
|
"UARTLine": "Номер UART порта (0, 1 или 2), по умолчанию 1"
|
||||||
},
|
},
|
||||||
"funcInfo": [
|
"funcInfo": [
|
||||||
{
|
{
|
||||||
"name": "getModelVersion",
|
"name": "setHeating",
|
||||||
"descr": "Запрос модели и версии адаптера и бойлера",
|
"descr": "Включение/отключение контура отопления",
|
||||||
"params": []
|
"params": ["1 - включить, 0 - отключить"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setDHW",
|
||||||
|
"descr": "Включение/отключение ГВС",
|
||||||
|
"params": ["1 - включить, 0 - отключить"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setSecondaryCircuit",
|
||||||
|
"descr": "Включение/отключение вторичного контура",
|
||||||
|
"params": ["1 - включить, 0 - отключить"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setBoilerStatus",
|
||||||
|
"descr": "Комбинированная установка статусов котла",
|
||||||
|
"params": [
|
||||||
|
"отопление (1/0)",
|
||||||
|
"ГВС (1/0)",
|
||||||
|
"вторичный контур (1/0)"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getBoilerInfo",
|
"name": "getBoilerInfo",
|
||||||
"descr": "Запрос состояния связи с котлом, типа адаптера и код перезагрузки адаптера",
|
"descr": "Запрос информации о котле и адаптере",
|
||||||
"params": []
|
"params": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getBoilerStatus",
|
"name": "getWrittenBoilerStatus",
|
||||||
"descr": "Запрос состояния контуров котла и горелки",
|
"descr": "Запрос записанного статуса котла",
|
||||||
"params": []
|
"params": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getCodeError",
|
"name": "getSettings",
|
||||||
"descr": "Код ошибки котла (основной). Зависит от марки и модели котла.",
|
"descr": "Запрос текущих настроек адаптера",
|
||||||
"params": []
|
"params": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getCodeErrorExt",
|
"name": "setTCH",
|
||||||
"descr": "Код ошибки котла (дополнительный). Зависит от марки и модели котла.",
|
"descr": "Установка температуры отопления",
|
||||||
"params": []
|
"params": ["температура (например 55.5)"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getFlagErrorOT",
|
"name": "setTDHW",
|
||||||
"descr": "Стандартные флаги ошибок котла (только для котлов с интерфейсом OpenTherm)",
|
"descr": "Установка температуры ГВС",
|
||||||
"params": []
|
"params": ["температура (целое число)"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getFlowRate",
|
"name": "setSetTypeConnect",
|
||||||
"descr": "Текущий расхода ГВС",
|
"descr": "Установка типа подключения",
|
||||||
"params": []
|
"params": ["0 - адаптер к котлу, 1 - котел к внешнему устройству"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getMaxSetCH",
|
"name": "setTSetCHFaultConn",
|
||||||
"descr": "Верхний предел уставки теплоносителя",
|
"descr": "Установка температуры отопления при аварии связи",
|
||||||
"params": []
|
"params": ["температура (например 55.5)"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getMaxSetDHW",
|
"name": "setTSetMinCH",
|
||||||
"descr": "Верхний предел уставки ГВС",
|
"descr": "Установка минимальной температуры отопления",
|
||||||
"params": []
|
"params": ["температура (например 35.0)"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getMinSetCH",
|
"name": "setTSetMaxCH",
|
||||||
"descr": "Нижний предел уставки теплоносителя",
|
"descr": "Установка максимальной температуры отопления",
|
||||||
"params": []
|
"params": ["температура (например 85.0)"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getMinSetDHW",
|
"name": "setTSetMinDHW",
|
||||||
"descr": "Нижний предел уставки ГВС",
|
"descr": "Установка минимальной температуры ГВС",
|
||||||
"params": []
|
"params": ["температура (целое число)"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getModLevel",
|
"name": "setTSetMaxDHW",
|
||||||
"descr": "Текущая модуляция горелки",
|
"descr": "Установка максимальной температуры ГВС",
|
||||||
"params": []
|
"params": ["температура (целое число)"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getPressure",
|
"name": "setSetMaxModLevel",
|
||||||
"descr": "Текущее Давление в контуре",
|
"descr": "Установка максимального уровня модуляции",
|
||||||
"params": []
|
"params": ["уровень (0-100)"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getTempCH",
|
"name": "getTempCH",
|
||||||
@@ -122,81 +143,80 @@
|
|||||||
"descr": "Текущая температура ГВС",
|
"descr": "Текущая температура ГВС",
|
||||||
"params": []
|
"params": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getPressure",
|
||||||
|
"descr": "Давление в контуре",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getFlowRate",
|
||||||
|
"descr": "Расход ГВС",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getModLevel",
|
||||||
|
"descr": "Уровень модуляции горелки",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getBoilerStatusRaw",
|
||||||
|
"descr": "Сырое значение регистра статуса",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getTempOutside",
|
"name": "getTempOutside",
|
||||||
"descr": "Температура уличного датчика котла",
|
"descr": "Температура уличного датчика",
|
||||||
"params": []
|
"params": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "setTypeConnect",
|
"name": "getFlagErrorOT",
|
||||||
"descr": "Установить тип внешних подключений (сохраняется в EPROM Адаптера): 0 - адаптер подключен к котлу, 1 - котел подключен к внешнему устройству (панель или перемычка)",
|
"descr": "Флаги ошибок OpenTherm",
|
||||||
"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": []
|
"params": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "rebootAdapter",
|
"name": "getMinSetCH",
|
||||||
|
"descr": "Минимальная уставка теплоносителя",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getMaxSetCH",
|
||||||
|
"descr": "Максимальная уставка теплоносителя",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getMinSetDHW",
|
||||||
|
"descr": "Минимальная уставка ГВС",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getMaxSetDHW",
|
||||||
|
"descr": "Максимальная уставка ГВС",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getAdapterInfo",
|
||||||
|
"descr": "Информация об адаптере (регистр 0x0010)",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getAdapterVersion",
|
||||||
|
"descr": "Версия адаптера (регистр 0x0011)",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getUptime",
|
||||||
|
"descr": "Время работы адаптера (регистр 0x0012)",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reboot",
|
||||||
"descr": "Перезагрузка адаптера",
|
"descr": "Перезагрузка адаптера",
|
||||||
"params": []
|
"params": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "resetErrors",
|
||||||
|
"descr": "Сброс ошибок котла",
|
||||||
|
"params": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
162
src/modules/exec/WakeOnLanModule/WakeOnLanModule.cpp
Normal file
162
src/modules/exec/WakeOnLanModule/WakeOnLanModule.cpp
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
// Licensed under the Cooperative Non-Violent Public License (CNPL)
|
||||||
|
// See: https://github.com/CHE77/IoTManager-Modules/blob/main/LICENSE
|
||||||
|
|
||||||
|
#include "Global.h"
|
||||||
|
#include "classes/IoTItem.h"
|
||||||
|
|
||||||
|
#include <WiFiUdp.h>
|
||||||
|
#include <WakeOnLan.h>
|
||||||
|
|
||||||
|
WiFiUDP UDP; // Создаем объект WiFiUDP
|
||||||
|
WakeOnLan WOL(UDP); // Используем библиотеку WakeOnLan
|
||||||
|
|
||||||
|
class WakeOnLanModule : public IoTItem
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
String _macAddress; // MAC-адрес для пробуждения
|
||||||
|
String _SecureOn = "";
|
||||||
|
int _port = 9;
|
||||||
|
int _repeats = 3;
|
||||||
|
bool isInitiated = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
WakeOnLanModule(String parameters) : IoTItem(parameters)
|
||||||
|
{
|
||||||
|
jsonRead(parameters, "MAC", _macAddress); // Чтение MAC-адреса из параметров
|
||||||
|
_macAddress.replace("\"", "");
|
||||||
|
if (!isValidMacAddress(_macAddress))
|
||||||
|
{
|
||||||
|
SerialPrint("E", "WakeOnLan", "Settings > MAC = " + _macAddress + " is not valid", _id);
|
||||||
|
_macAddress = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonRead(parameters, "SecureOn", _SecureOn); // Чтение MAC-адреса из параметров
|
||||||
|
_SecureOn.replace("\"", "");
|
||||||
|
if (!_SecureOn.isEmpty() && !isValidMacAddress(_SecureOn))
|
||||||
|
{
|
||||||
|
SerialPrint("E", "WakeOnLan", "Settings > SecureOn = " + _SecureOn + " is not valid", _id);
|
||||||
|
_SecureOn = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonRead(parameters, "port", _port); // Чтение MAC-адреса из параметров
|
||||||
|
|
||||||
|
jsonRead(parameters, "repeats", _repeats); // Чтение MAC-адреса из параметров
|
||||||
|
|
||||||
|
WOL.setRepeat(_repeats, 100); // Repeat the packet three times with 100ms delay between
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
if (isNetworkActive())
|
||||||
|
{ // Рассчитываем broadcast-адрес
|
||||||
|
IPAddress broadcastAddress = WOL.calculateBroadcastAddress(WiFi.localIP(), WiFi.subnetMask());
|
||||||
|
WOL.setBroadcastAddress(broadcastAddress); // Устанавливаем broadcast-адрес
|
||||||
|
isInitiated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValidMacAddress(String mac)
|
||||||
|
{
|
||||||
|
// Удаляем двоеточия, если есть
|
||||||
|
mac.replace(":", "");
|
||||||
|
mac.toUpperCase();
|
||||||
|
|
||||||
|
// Должно быть ровно 12 символов (6 байт в hex)
|
||||||
|
if (mac.length() != 12)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Проверка, что все символы — это HEX (0-9, A-F)
|
||||||
|
for (int i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
char c = mac.charAt(i);
|
||||||
|
if (!isHexadecimalDigit(c))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setValue(const IoTValue &Value, bool genEvent = true)
|
||||||
|
{
|
||||||
|
value = Value;
|
||||||
|
|
||||||
|
if (value.valD == 1 && isNetworkActive())
|
||||||
|
{
|
||||||
|
if (!isInitiated) init();
|
||||||
|
|
||||||
|
if (!_macAddress.isEmpty() && !_SecureOn.isEmpty())
|
||||||
|
{
|
||||||
|
WOL.sendSecureMagicPacket(_macAddress, _SecureOn, _port);
|
||||||
|
SerialPrint("I", "WakeOnLan", "setValue, _SecureOn = " + _SecureOn, _id);
|
||||||
|
}
|
||||||
|
else if (!_macAddress.isEmpty())
|
||||||
|
{
|
||||||
|
WOL.sendMagicPacket(_macAddress, _port); // Отправка Magic Packet
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", "WakeOnLan", "Settings > MAC and/or SecureOn - not set or not valid", _id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
regEvent((String)(int)value.valD, "WakeOnLan", false, genEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
||||||
|
{
|
||||||
|
if (!isInitiated) init();
|
||||||
|
|
||||||
|
if (command == "mac")
|
||||||
|
{
|
||||||
|
String macTarget = "";
|
||||||
|
|
||||||
|
if (param.size() == 1 && isValidMacAddress(param[0].valS))
|
||||||
|
{
|
||||||
|
macTarget = param[0].valS;
|
||||||
|
WOL.sendMagicPacket(macTarget); // Отправка Magic Packet
|
||||||
|
}
|
||||||
|
else if (param.size() == 2 && isValidMacAddress(param[0].valS))
|
||||||
|
{
|
||||||
|
macTarget = param[0].valS;
|
||||||
|
int portNum = param[1].valD;
|
||||||
|
WOL.sendMagicPacket(macTarget, portNum); // Отправка Magic Packet
|
||||||
|
}
|
||||||
|
else if (param.size() == 3 && isValidMacAddress(param[0].valS) && isValidMacAddress(param[1].valS))
|
||||||
|
{
|
||||||
|
macTarget = param[0].valS;
|
||||||
|
String secureOn = param[1].valS;
|
||||||
|
int portNum = param[2].valD;
|
||||||
|
WOL.sendSecureMagicPacket(macTarget, secureOn, portNum);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", "WakeOnLan", "MAC and/or SecureOn - not valid", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
SerialPrint("I", "WakeOnLan", "execute, Magic Packet sent to " + macTarget, _id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}; // команда поддерживает возвращаемое значения. Т.е. по итогу выполнения команды или общения с внешней системой, можно вернуть значение в сценарий для дальнейшей обработки
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
String getValue() {
|
||||||
|
return (String)(int)value.valD;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
void doByInterval() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
void *getAPI_WakeOnLanModule(String subtype, String param)
|
||||||
|
{
|
||||||
|
if (subtype == F("WakeOnLanModule"))
|
||||||
|
{
|
||||||
|
return new WakeOnLanModule(param); // Используем новое имя класса
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
295
src/modules/exec/WakeOnLanModule/export.json
Normal file
295
src/modules/exec/WakeOnLanModule/export.json
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
{
|
||||||
|
"mark": "iotm",
|
||||||
|
"config": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "ipNet",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Network",
|
||||||
|
"descr": "IP",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Writing",
|
||||||
|
"subtype": "Cron",
|
||||||
|
"id": "cronWiFi",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Timers",
|
||||||
|
"descr": " cronWiFi",
|
||||||
|
"int": 1,
|
||||||
|
"val": "0 * * * * *",
|
||||||
|
"formatNextAlarm": "%H:%M:%S",
|
||||||
|
"needSave": 0,
|
||||||
|
"moduleName": "Cron"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "VButton",
|
||||||
|
"id": "wifiConn",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "Network",
|
||||||
|
"descr": " Wi-Fi Connection",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0",
|
||||||
|
"moduleName": "VButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "noWiFiCounter",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Network",
|
||||||
|
"descr": " noWiFi Counter",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "maxNoWiFi",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "inputDgt",
|
||||||
|
"page": "Network",
|
||||||
|
"descr": " maxNoWiFi to reboot",
|
||||||
|
"int": "0",
|
||||||
|
"val": "100",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Writing",
|
||||||
|
"subtype": "Cron",
|
||||||
|
"id": "cronPing",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Timers",
|
||||||
|
"descr": " cronPing",
|
||||||
|
"int": 1,
|
||||||
|
"val": "2 * * * * *",
|
||||||
|
"formatNextAlarm": "%H:%M:%S",
|
||||||
|
"needSave": 0,
|
||||||
|
"moduleName": "Cron"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Ping",
|
||||||
|
"id": "pingN",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "nil",
|
||||||
|
"page": "Network",
|
||||||
|
"descr": "Internet",
|
||||||
|
"ip": "8.8.8.8",
|
||||||
|
"timeout": 5,
|
||||||
|
"interval": "1",
|
||||||
|
"data_size": 0,
|
||||||
|
"count": 0,
|
||||||
|
"tos": 0,
|
||||||
|
"moduleName": "Ping"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "noInternetCounter",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Network",
|
||||||
|
"descr": "noInternet Counter",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "VButton",
|
||||||
|
"id": "internetConn",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "Network",
|
||||||
|
"descr": "Internet Connection",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0",
|
||||||
|
"moduleName": "VButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "routerFlag0",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "nil",
|
||||||
|
"page": "nil",
|
||||||
|
"descr": "routerFlag0",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "routerFlag1",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "nil",
|
||||||
|
"page": "nil",
|
||||||
|
"descr": "routerFlag1",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "routerFlag3",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "nil",
|
||||||
|
"page": "nil",
|
||||||
|
"descr": "routerFlag3",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "VButton",
|
||||||
|
"id": "vbtnWOLbook",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "WakeOnLAN",
|
||||||
|
"descr": "Wake Notebook scenario",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0",
|
||||||
|
"moduleName": "VButton",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "WakeOnLanModule",
|
||||||
|
"id": "wakeLanBook",
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "WakeOnLAN",
|
||||||
|
"descr": "Wake Notebook w.settings",
|
||||||
|
"MAC": "\"A0:AD:A8:A2:AF:E2\"",
|
||||||
|
"SecureOn": "",
|
||||||
|
"port": 9,
|
||||||
|
"repeats": 3,
|
||||||
|
"moduleName": "WakeOnLanModule",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "macInput",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "inputTxt",
|
||||||
|
"page": "WakeOnLAN",
|
||||||
|
"descr": "MAC Input",
|
||||||
|
"int": "0",
|
||||||
|
"val": "A0:AD:A8:A2:AF:E2",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "bootDateNet",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Time",
|
||||||
|
"descr": " Boot Date",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario=>if onStart then {
|
||||||
|
ipNet = getIP()
|
||||||
|
pingN.ping("8.8.8.8")
|
||||||
|
bootDateNet=getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#book
|
||||||
|
if vbtnWOLbook == 1 then {
|
||||||
|
wakeLanBook.mac(macInput)
|
||||||
|
vbtnWOLbook = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
######################
|
||||||
|
if cronWiFi then ipNet = getIP()
|
||||||
|
|
||||||
|
if ipNet == "(IP unset)" | ipNet == "192.168.4.1" then {
|
||||||
|
wifiConn = 0
|
||||||
|
noWiFiCounter = noWiFiCounter +1
|
||||||
|
}else {
|
||||||
|
wifiConn = 1
|
||||||
|
noWiFiCounter = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if noWiFiCounter > maxNoWiFi then reboot()
|
||||||
|
|
||||||
|
###################
|
||||||
|
if cronPing then {
|
||||||
|
pingN.ping("8.8.8.8")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pingN == 0 then {
|
||||||
|
internetConn = 0
|
||||||
|
noInternetCounter = noInternetCounter +1
|
||||||
|
}else {
|
||||||
|
internetConn = 1
|
||||||
|
#noInternetCounter = 0
|
||||||
|
noInternetCounter = noInternetCounter +1
|
||||||
|
}
|
||||||
48
src/modules/exec/WakeOnLanModule/modinfo.json
Normal file
48
src/modules/exec/WakeOnLanModule/modinfo.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"menuSection": "executive_devices",
|
||||||
|
"configItem": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"name": "WakeOnLan",
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "WakeOnLanModule",
|
||||||
|
"id": "wakeLan",
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "Кнопки",
|
||||||
|
"descr": "Wake PC",
|
||||||
|
"MAC": "A8:A3:A5:A5:52:42",
|
||||||
|
"SecureOn": "",
|
||||||
|
"port": 9,
|
||||||
|
"repeats": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"about": {
|
||||||
|
"authorName": "Alex",
|
||||||
|
"authorContact": "https://t.me/cmche",
|
||||||
|
"authorGit": "https://github.com/CHE77/IoTManager-Modules",
|
||||||
|
"specialThanks": "",
|
||||||
|
"moduleName": "WakeOnLanModule",
|
||||||
|
"moduleVersion": "1.0",
|
||||||
|
"usedRam": {
|
||||||
|
"esp32_4mb": 15,
|
||||||
|
"esp8266_4mb": 15
|
||||||
|
},
|
||||||
|
"title": "WakeOnLan",
|
||||||
|
"moduleDesc": "Пробуждение компютера по сети",
|
||||||
|
"license": "Cooperative Non-Violent Public License (CNPL)",
|
||||||
|
"propInfo": {
|
||||||
|
"MAC": "MAC адрес сетевой карты",
|
||||||
|
"SecureOn": "Секретный ключ в таком же виде как и MAC адрес. (необязательно)",
|
||||||
|
"port": "порт - по умолчнияю - 9",
|
||||||
|
"repeats": "Количество повторов команд пробужния - для надежности"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defActive": false,
|
||||||
|
"usedLibs": {
|
||||||
|
"esp32*": ["a7md0/WakeOnLan @ ^1.1.7"],
|
||||||
|
"esp82*": ["a7md0/WakeOnLan @ ^1.1.7"],
|
||||||
|
"bk72*": ["a7md0/WakeOnLan @ ^1.1.7"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
359
src/modules/sensors/NoiseAdc/NoiseAdc.cpp
Normal file
359
src/modules/sensors/NoiseAdc/NoiseAdc.cpp
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
// Licensed under the Cooperative Non-Violent Public License (CNPL)
|
||||||
|
// See: https://github.com/CHE77/IoTManager-Modules/blob/main/LICENSE
|
||||||
|
|
||||||
|
#include "Global.h"
|
||||||
|
#include "classes/IoTItem.h"
|
||||||
|
|
||||||
|
extern IoTGpio IoTgpio;
|
||||||
|
|
||||||
|
// Это файл сенсора, в нем осуществляется чтение сенсора.
|
||||||
|
// для добавления сенсора вам нужно скопировать этот файл и заменить в нем текст NoiseAdc на название вашего сенсора
|
||||||
|
// Название должно быть уникальным, коротким и отражать суть сенсора.
|
||||||
|
|
||||||
|
// ребенок - родитель
|
||||||
|
class NoiseAdc : public IoTItem
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
//=======================================================================================================
|
||||||
|
// Секция переменных.
|
||||||
|
// Это секция где Вы можете объявлять переменные и объекты arduino библиотек, что бы
|
||||||
|
// впоследствии использовать их в loop и setup
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
float adcRange = 1023.0;
|
||||||
|
const int maxSamples = 100;
|
||||||
|
int samples[100];
|
||||||
|
float deltas[100]; // отклонения от среднего
|
||||||
|
|
||||||
|
#else
|
||||||
|
float adcRange = 4095.0;
|
||||||
|
const int maxSamples = 200;
|
||||||
|
int samples[200];
|
||||||
|
float deltas[200]; // отклонения от среднего
|
||||||
|
#endif
|
||||||
|
|
||||||
|
unsigned int _pin;
|
||||||
|
unsigned int _steps;
|
||||||
|
unsigned int _period = 0;
|
||||||
|
unsigned long _lastSoundingMillis = 0;
|
||||||
|
|
||||||
|
int sampleIndex = 0;
|
||||||
|
|
||||||
|
float _referenceVoltage = 0.02; // калибровочный "тихий" уровень
|
||||||
|
float vcc = 3.3;
|
||||||
|
|
||||||
|
String _parameter = "";
|
||||||
|
|
||||||
|
float mean = 0;
|
||||||
|
float minVal = 0;
|
||||||
|
float maxVal = 0;
|
||||||
|
float rms = 0;
|
||||||
|
float peak = 0;
|
||||||
|
float median = 0;
|
||||||
|
float minValMean = 0;
|
||||||
|
float maxValMean = 0;
|
||||||
|
float peakToPeak = 0;
|
||||||
|
float peakVoltage = 0;
|
||||||
|
float db = 0;
|
||||||
|
|
||||||
|
bool _debug = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
//=======================================================================================================
|
||||||
|
// setup()
|
||||||
|
// это аналог setup из arduino. Здесь вы можете выполнять методы инициализации сенсора.
|
||||||
|
// Такие как ...begin и подставлять в них параметры полученные из web интерфейса.
|
||||||
|
// Все параметры хранятся в перемененной parameters, вы можете прочитать любой параметр используя jsonRead функции:
|
||||||
|
// jsonReadStr, jsonReadBool, jsonReadInt
|
||||||
|
NoiseAdc(String parameters) : IoTItem(parameters)
|
||||||
|
{
|
||||||
|
_pin = jsonReadInt(parameters, "pin");
|
||||||
|
_steps = jsonReadInt(parameters, "steps");
|
||||||
|
if (_steps > maxSamples)
|
||||||
|
_steps = maxSamples;
|
||||||
|
|
||||||
|
if (_steps < 10)
|
||||||
|
_steps = 10;
|
||||||
|
_period = _interval / _steps;
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("i", F("NoiseAdc"), "_period = " + String(_period));
|
||||||
|
|
||||||
|
jsonRead(parameters, F("refVoltage"), _referenceVoltage, false);
|
||||||
|
if (_referenceVoltage == 0)
|
||||||
|
_referenceVoltage = 0.01;
|
||||||
|
|
||||||
|
_parameter = jsonReadStr(parameters, "parameter");
|
||||||
|
jsonRead(parameters, F("debug"), _debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
//=======================================================================================================
|
||||||
|
|
||||||
|
void doByInterval()
|
||||||
|
{
|
||||||
|
float result = analyzeSamples(_parameter);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
value.valD = result;
|
||||||
|
regEvent(value.valD, "NoiseAdc", _debug, true); // обязательный вызов хотяб один
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//=======================================================================================================
|
||||||
|
// loop()
|
||||||
|
// полный аналог loop() из arduino. Нужно помнить, что все модули имеют равный поочередный доступ к центральному loop(), поэтому, необходимо следить
|
||||||
|
// за задержками в алгоритме и не создавать пауз. Кроме того, данная версия перегружает родительскую, поэтому doByInterval() отключается, если
|
||||||
|
// не повторить механизм расчета интервалов.
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (millis() > _lastSoundingMillis + _period && sampleIndex < maxSamples)
|
||||||
|
{
|
||||||
|
_lastSoundingMillis = millis();
|
||||||
|
samples[sampleIndex++] = IoTgpio.analogRead(_pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
IoTItem::loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onModuleOrder(String &key, String &value)
|
||||||
|
{
|
||||||
|
if (key == "setRefVoltage")
|
||||||
|
{
|
||||||
|
_referenceVoltage = analyzeSamples("peakVoltage");
|
||||||
|
SerialPrint("i", F("NoiseAdc"), "User run calibration referenceVoltage " + String(_referenceVoltage));
|
||||||
|
// TODO wtitejson to config.json?????
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
||||||
|
{
|
||||||
|
if (command == "parameter")
|
||||||
|
{
|
||||||
|
if (param.size() == 1 && !param[0].isDecimal)
|
||||||
|
{
|
||||||
|
|
||||||
|
String parameter = param[0].valS.c_str();
|
||||||
|
|
||||||
|
float output = 0;
|
||||||
|
|
||||||
|
if (parameter == "mean")
|
||||||
|
{
|
||||||
|
output = mean;
|
||||||
|
}
|
||||||
|
else if (parameter == "minVal")
|
||||||
|
{
|
||||||
|
output = minVal;
|
||||||
|
}
|
||||||
|
else if (parameter == "maxVal")
|
||||||
|
{
|
||||||
|
output = maxVal;
|
||||||
|
}
|
||||||
|
else if (parameter == "RMS")
|
||||||
|
{
|
||||||
|
output = rms;
|
||||||
|
}
|
||||||
|
else if (parameter == "median")
|
||||||
|
{
|
||||||
|
output = median;
|
||||||
|
}
|
||||||
|
else if (parameter == "minValMean")
|
||||||
|
{
|
||||||
|
output = minValMean;
|
||||||
|
}
|
||||||
|
else if (parameter == "maxValMean")
|
||||||
|
{
|
||||||
|
output = maxValMean;
|
||||||
|
}
|
||||||
|
else if (parameter == "peak")
|
||||||
|
{
|
||||||
|
output = peak;
|
||||||
|
}
|
||||||
|
else if (parameter == "peakToPeak")
|
||||||
|
{
|
||||||
|
output = peakToPeak;
|
||||||
|
}
|
||||||
|
else if (parameter == "peakVoltage")
|
||||||
|
{
|
||||||
|
output = peakVoltage;
|
||||||
|
}
|
||||||
|
else if (parameter == "db")
|
||||||
|
{
|
||||||
|
output = db;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("i", F("NoiseAdc"), "execute parameter " + String(parameter) + " = " + String(output));
|
||||||
|
|
||||||
|
if (output)
|
||||||
|
{
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = output;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}; // команда поддерживает возвращаемое значения. Т.е. по итогу выполнения команды или общения с внешней системой, можно вернуть значение в сценарий для дальнейшей обработки
|
||||||
|
}
|
||||||
|
|
||||||
|
float analyzeSamples(String parameter)
|
||||||
|
{
|
||||||
|
if (sampleIndex < 5)
|
||||||
|
{
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("i", F("NoiseAdc"), "Нет данных для анализа");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
minVal = samples[0];
|
||||||
|
maxVal = samples[0];
|
||||||
|
float sum = 0;
|
||||||
|
for (int i = 0; i < sampleIndex; i++)
|
||||||
|
{
|
||||||
|
if (samples[i] < minVal)
|
||||||
|
minVal = samples[i];
|
||||||
|
if (samples[i] > maxVal)
|
||||||
|
maxVal = samples[i];
|
||||||
|
sum += samples[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
mean = sum / sampleIndex;
|
||||||
|
|
||||||
|
for (int i = 0; i < sampleIndex; i++)
|
||||||
|
{
|
||||||
|
deltas[i] = samples[i] - mean;
|
||||||
|
}
|
||||||
|
|
||||||
|
rms = 0;
|
||||||
|
|
||||||
|
minValMean = deltas[0];
|
||||||
|
maxValMean = deltas[0];
|
||||||
|
|
||||||
|
peak = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < sampleIndex; i++)
|
||||||
|
{
|
||||||
|
rms += deltas[i] * deltas[i];
|
||||||
|
|
||||||
|
if (deltas[i] < minValMean)
|
||||||
|
minValMean = deltas[i];
|
||||||
|
if (deltas[i] > maxValMean)
|
||||||
|
maxValMean = deltas[i];
|
||||||
|
|
||||||
|
if (abs(deltas[i] > peak))
|
||||||
|
peak = abs(deltas[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
rms = sqrt(rms / sampleIndex);
|
||||||
|
|
||||||
|
peakToPeak = maxValMean - minValMean;
|
||||||
|
|
||||||
|
float sorted[maxSamples];
|
||||||
|
memcpy(sorted, deltas, sizeof(float) * sampleIndex);
|
||||||
|
std::sort(sorted, sorted + sampleIndex);
|
||||||
|
median = (sampleIndex % 2 == 0) ? (sorted[sampleIndex / 2 - 1] + sorted[sampleIndex / 2]) / 2.0 : sorted[sampleIndex / 2];
|
||||||
|
|
||||||
|
peakVoltage = peakToPeak * vcc / adcRange;
|
||||||
|
db = 20.0 * log10(peakVoltage / _referenceVoltage);
|
||||||
|
if (peakVoltage < _referenceVoltage)
|
||||||
|
db = 0;
|
||||||
|
if (_debug)
|
||||||
|
{
|
||||||
|
Serial.println("== Анализ завершен ==");
|
||||||
|
Serial.print("Сэмплов: ");
|
||||||
|
Serial.println(sampleIndex);
|
||||||
|
Serial.print("Среднее (DC): ");
|
||||||
|
Serial.println(mean, 2);
|
||||||
|
Serial.print("Мин/Макс (Aбс): ");
|
||||||
|
Serial.print(minVal, 2);
|
||||||
|
Serial.print(" / ");
|
||||||
|
Serial.println(maxVal, 2);
|
||||||
|
Serial.print("RMS (AC): ");
|
||||||
|
Serial.println(rms, 2);
|
||||||
|
Serial.print("Медиана (AC): ");
|
||||||
|
Serial.println(median, 2);
|
||||||
|
Serial.print("Мин/Макс (AC): ");
|
||||||
|
Serial.print(minValMean, 2);
|
||||||
|
Serial.print(" / ");
|
||||||
|
Serial.println(maxValMean, 2);
|
||||||
|
Serial.print("Абсолютный пик (AC): ");
|
||||||
|
Serial.println(peak, 2);
|
||||||
|
Serial.print("Размах (AC): ");
|
||||||
|
Serial.println(peakToPeak, 2);
|
||||||
|
Serial.print("Размах в вольтах: ");
|
||||||
|
Serial.println(peakVoltage, 4);
|
||||||
|
Serial.print("Уровень звука ≈ dB SPL: ");
|
||||||
|
Serial.println(db, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sampleIndex = 0;
|
||||||
|
|
||||||
|
if (parameter == "mean")
|
||||||
|
{
|
||||||
|
return mean;
|
||||||
|
}
|
||||||
|
else if (parameter == "minVal")
|
||||||
|
{
|
||||||
|
return minVal;
|
||||||
|
}
|
||||||
|
else if (parameter == "maxVal")
|
||||||
|
{
|
||||||
|
return maxVal;
|
||||||
|
}
|
||||||
|
else if (parameter == "RMS")
|
||||||
|
{
|
||||||
|
return rms;
|
||||||
|
}
|
||||||
|
else if (parameter == "median")
|
||||||
|
{
|
||||||
|
return median;
|
||||||
|
}
|
||||||
|
else if (parameter == "minValMean")
|
||||||
|
{
|
||||||
|
return minValMean;
|
||||||
|
}
|
||||||
|
else if (parameter == "maxValMean")
|
||||||
|
{
|
||||||
|
return maxValMean;
|
||||||
|
}
|
||||||
|
else if (parameter == "peak")
|
||||||
|
{
|
||||||
|
return peak;
|
||||||
|
}
|
||||||
|
else if (parameter == "peakToPeak")
|
||||||
|
{
|
||||||
|
return peakToPeak;
|
||||||
|
}
|
||||||
|
else if (parameter == "peakVoltage")
|
||||||
|
{
|
||||||
|
return peakVoltage;
|
||||||
|
}
|
||||||
|
else if (parameter == "db")
|
||||||
|
{
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~NoiseAdc() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
void *getAPI_NoiseAdc(String subtype, String param)
|
||||||
|
{
|
||||||
|
if (subtype == F("NoiseAdc"))
|
||||||
|
{
|
||||||
|
return new NoiseAdc(param);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/modules/sensors/NoiseAdc/export.json
Normal file
89
src/modules/sensors/NoiseAdc/export.json
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"mark": "iotm",
|
||||||
|
"config": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Writing",
|
||||||
|
"subtype": "TelegramLT",
|
||||||
|
"id": "tg0",
|
||||||
|
"widget": "",
|
||||||
|
"page": "",
|
||||||
|
"descr": "",
|
||||||
|
"token": "1262564256457:AAGQJ6LvZ-dOGFvaerhbserhdtbzgdndaY",
|
||||||
|
"chatID": "49999999"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "valueTreashhold",
|
||||||
|
"needSave": "1",
|
||||||
|
"widget": "inputDgt",
|
||||||
|
"page": "Сенсоры",
|
||||||
|
"descr": "ADC Threshold",
|
||||||
|
"int": "0",
|
||||||
|
"val": "4095",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "VButton",
|
||||||
|
"id": "vbtnListen",
|
||||||
|
"needSave": "1",
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "Сенсоры",
|
||||||
|
"descr": "Tg Alerts",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0",
|
||||||
|
"moduleName": "VButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Writing",
|
||||||
|
"subtype": "Loging",
|
||||||
|
"id": "logNoise",
|
||||||
|
"widget": "chart2",
|
||||||
|
"page": "Графики",
|
||||||
|
"descr": "Noise",
|
||||||
|
"int": 0,
|
||||||
|
"daysSave": 5,
|
||||||
|
"daysShow": 0,
|
||||||
|
"points": 300,
|
||||||
|
"moduleName": "Loging"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "NoiseAdc",
|
||||||
|
"id": "noiseADC",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Сенсоры",
|
||||||
|
"descr": "NoiseAdc",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"pin": "0",
|
||||||
|
"int": 10,
|
||||||
|
"avgSteps": "500",
|
||||||
|
"parameter": "peakToPeak",
|
||||||
|
"debug": 1,
|
||||||
|
"refVoltage": "0.02",
|
||||||
|
"btn-setRefVoltage": "nil",
|
||||||
|
"moduleName": "NoiseAdc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario=>if onStart then tg0.sendMsg("ESP Noise reboot. IP " + getIP() )
|
||||||
|
|
||||||
|
if noiseADC > valueTreashhold & vbtnListen then {tg0.sendMsg("Noise level = " + noiseADC)
|
||||||
|
}
|
||||||
|
|
||||||
|
if noiseADC then = noiseADC
|
||||||
|
|
||||||
|
|
||||||
70
src/modules/sensors/NoiseAdc/modinfo.json
Normal file
70
src/modules/sensors/NoiseAdc/modinfo.json
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"menuSection": "sensors",
|
||||||
|
"configItem": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"name": "Анализ шума ADC",
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "NoiseAdc",
|
||||||
|
"id": "noise",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Сенсоры",
|
||||||
|
"descr": "NoiseAdc",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"pin": 0,
|
||||||
|
"int": 10,
|
||||||
|
"steps": 100,
|
||||||
|
"parameter": "peakToPeak",
|
||||||
|
"debug": 1,
|
||||||
|
"refVoltage": 0.02,
|
||||||
|
"btn-setRefVoltage": "nil"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"about": {
|
||||||
|
"authorName": "Alex",
|
||||||
|
"authorContact": "https://t.me/cmche",
|
||||||
|
"authorGit": "https://github.com/CHE77/IoTManager-Modules",
|
||||||
|
"exampleURL": "https://iotmanager.org/wiki",
|
||||||
|
"specialThanks": "",
|
||||||
|
"moduleName": "NoiseAdc",
|
||||||
|
"moduleVersion": "1.0",
|
||||||
|
"usedRam": {
|
||||||
|
"esp32_4mb": 10,
|
||||||
|
"esp8266_4mb": 3
|
||||||
|
},
|
||||||
|
"title": "Анализ шума ADC",
|
||||||
|
"moduleDesc": "Позволяет получить различные характеристики сигнала на аналоговом GPIO. В каждый промежуток времени int делает avgSteps измерений, орпеделяет характоистики ряда и выводит один параметров.",
|
||||||
|
"license": "Cooperative Non-Violent Public License (CNPL)",
|
||||||
|
"propInfo": {
|
||||||
|
"pin": "Аналоговый GPIO номер, к которому подключен датчик.",
|
||||||
|
"int": "Длительность опроса датчика и вывод показаний",
|
||||||
|
"steps": "Количество считываний в период опроса. Минимальное - 10. Максимальное - 200(esp8266), 1000(esp32). По факту оно будет несколько меньше.",
|
||||||
|
"parameter": {
|
||||||
|
"mean": "Среднее",
|
||||||
|
"minVal": "Минимальное значение ADC",
|
||||||
|
"maxVal": "Максимальное значение ADC",
|
||||||
|
"RMS": "Среднеквадратичное отклонение (от среднего)",
|
||||||
|
"median": "Медиана (от среднего)",
|
||||||
|
"minValMean": "Минимальное значение (от среднего)",
|
||||||
|
"maxValMean": "Максимальное значение (от среднего)",
|
||||||
|
"peak": "Абсолютный пик (от среднего)",
|
||||||
|
"peakToPeak": "Размах сигнала относительно среднего (AC) в отсчётах ADC",
|
||||||
|
"peakVoltage": "Максимальная амплитуда колебаний сигнала",
|
||||||
|
"db": "Приближённая оценка уровня звука в децибелах "
|
||||||
|
},
|
||||||
|
|
||||||
|
"debug": "Вывод отладочной информации",
|
||||||
|
"refVoltage": "Базовый уровень для сигнала (в тишине) в Вольтах",
|
||||||
|
"btn-setRefVoltage": "Замер базового уровня в тишине и вывод его в сериал"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defActive": false,
|
||||||
|
"usedLibs": {
|
||||||
|
"esp32*": [],
|
||||||
|
"esp82*": [],
|
||||||
|
"bk72*": []
|
||||||
|
}
|
||||||
|
}
|
||||||
579
src/modules/sensors/Presence/Presence.cpp
Normal file
579
src/modules/sensors/Presence/Presence.cpp
Normal file
@@ -0,0 +1,579 @@
|
|||||||
|
// Licensed under the Cooperative Non-Violent Public License (CNPL)
|
||||||
|
// See: https://github.com/CHE77/IoTManager-Modules/blob/main/LICENSE
|
||||||
|
|
||||||
|
|
||||||
|
#define MQTT_MAX_PACKET_SIZE 512 // или 1024
|
||||||
|
#include "Global.h"
|
||||||
|
#include "classes/IoTItem.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#define EARTH_RADIUS_KM 6371.0 // Радиус Земли в километрах
|
||||||
|
|
||||||
|
#include "NTP.h"
|
||||||
|
#include <PubSubClient.h> // чтобы знать тип
|
||||||
|
extern PubSubClient mqtt; // объявляем глобальный объект
|
||||||
|
|
||||||
|
class Presence : public IoTItem
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
String _MAC;
|
||||||
|
String _parameter;
|
||||||
|
IoTItem *tmp;
|
||||||
|
int _minutesPassed = 0;
|
||||||
|
String json = "{}";
|
||||||
|
int orange = 0;
|
||||||
|
int red = 0;
|
||||||
|
int offline = 0;
|
||||||
|
bool dataFromNode = false;
|
||||||
|
String _topic = "";
|
||||||
|
bool _isJson;
|
||||||
|
bool _ticker = true;
|
||||||
|
bool _debug = false;
|
||||||
|
bool sendOk = false;
|
||||||
|
float _lat_A = 0;
|
||||||
|
float _lon_A = 0;
|
||||||
|
|
||||||
|
|
||||||
|
struct PresenceData
|
||||||
|
{
|
||||||
|
String chargingState;
|
||||||
|
String plugState;
|
||||||
|
String connectedWifi;
|
||||||
|
String geoLocation;
|
||||||
|
float lat = 0.0;
|
||||||
|
float lon = 0.0;
|
||||||
|
unsigned long geoTimestamp = 0;
|
||||||
|
String geoTime;
|
||||||
|
String deviceName;
|
||||||
|
int batteryLevel = -1;
|
||||||
|
unsigned long currentTimestamp = 0;
|
||||||
|
String currentTime;
|
||||||
|
unsigned long nextScheduledTimestamp = 0;
|
||||||
|
String nextScheduledTime;
|
||||||
|
unsigned long nextAlarmclockTimestamp = 0;
|
||||||
|
String nextAlarmclockTime;
|
||||||
|
std::vector<String> conditionContent;
|
||||||
|
String conditionContentString;
|
||||||
|
};
|
||||||
|
PresenceData pdata;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Presence(String parameters) : IoTItem(parameters)
|
||||||
|
{
|
||||||
|
_parameter = jsonReadStr(parameters, "parameter");
|
||||||
|
jsonRead(parameters, F("orange"), orange);
|
||||||
|
jsonRead(parameters, F("red"), red);
|
||||||
|
jsonRead(parameters, F("offline"), offline);
|
||||||
|
_topic = jsonReadStr(parameters, "topic");
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("i", "Presence topic : ", _topic);
|
||||||
|
jsonRead(parameters, F("isJson"), _isJson);
|
||||||
|
// jsonRead(parameters, "addPrefix", _addPrefix);
|
||||||
|
jsonRead(parameters, F("Lat. A"), _lat_A);
|
||||||
|
jsonRead(parameters, F("Long. A"), _lon_A);
|
||||||
|
jsonRead(parameters, F("ticker"), _ticker);
|
||||||
|
jsonRead(parameters, F("debug"), _debug);
|
||||||
|
dataFromNode = false;
|
||||||
|
if (mqttIsConnect())
|
||||||
|
{
|
||||||
|
mqtt.setBufferSize(512);
|
||||||
|
sendOk = true;
|
||||||
|
mqttSubscribeExternal(_topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *TimeToString(unsigned long t)
|
||||||
|
{
|
||||||
|
static char str[12];
|
||||||
|
long h = t / 3600;
|
||||||
|
t = t % 3600;
|
||||||
|
int m = t / 60;
|
||||||
|
int s = t % 60;
|
||||||
|
sprintf(str, "%02ld:%02d:%02d", h, m, s);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
double toRadians(double degrees)
|
||||||
|
{
|
||||||
|
return degrees * M_PI / 180.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double toDegrees(double radians)
|
||||||
|
{
|
||||||
|
return radians * 180.0 / M_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Возвращает пеленг в градусах: от 0 до 360
|
||||||
|
double calculateInitialBearing(double lat1, double lon1, double lat2, double lon2)
|
||||||
|
{
|
||||||
|
lat1 = toRadians(lat1);
|
||||||
|
lon1 = toRadians(lon1);
|
||||||
|
lat2 = toRadians(lat2);
|
||||||
|
lon2 = toRadians(lon2);
|
||||||
|
|
||||||
|
double deltaLon = lon2 - lon1;
|
||||||
|
|
||||||
|
double y = sin(deltaLon) * cos(lat2);
|
||||||
|
double x = cos(lat1) * sin(lat2) -
|
||||||
|
sin(lat1) * cos(lat2) * cos(deltaLon);
|
||||||
|
|
||||||
|
double bearing = atan2(y, x);
|
||||||
|
bearing = toDegrees(bearing);
|
||||||
|
|
||||||
|
// Приводим к диапазону 0–360
|
||||||
|
return fmod((bearing + 360.0), 360.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// lat и lon — в градусах
|
||||||
|
double haversineDistance(double lat1, double lon1, double lat2, double lon2)
|
||||||
|
{
|
||||||
|
double dLat = toRadians(lat2 - lat1);
|
||||||
|
double dLon = toRadians(lon2 - lon1);
|
||||||
|
|
||||||
|
lat1 = toRadians(lat1);
|
||||||
|
lat2 = toRadians(lat2);
|
||||||
|
|
||||||
|
double a = sin(dLat / 2) * sin(dLat / 2) +
|
||||||
|
cos(lat1) * cos(lat2) *
|
||||||
|
sin(dLon / 2) * sin(dLon / 2);
|
||||||
|
|
||||||
|
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
||||||
|
|
||||||
|
return EARTH_RADIUS_KM * c * 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMqttRecive(String &topic, String &msg)
|
||||||
|
{
|
||||||
|
Serial.printf("[MQTT] Topic: %s\nPayload size: %d bytes\n", topic.c_str(), msg.length());
|
||||||
|
msg.trim(); // Убираем пробелы и переносы строк
|
||||||
|
|
||||||
|
if (msg.indexOf("HELLO") == -1)
|
||||||
|
{
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("i", "Presence HELLO", " _1d: " + _id + " topic: " + topic + " msg: " + msg);
|
||||||
|
String dev = selectToMarkerLast(topic, "/");
|
||||||
|
dev.toUpperCase();
|
||||||
|
dev.replace(":", "");
|
||||||
|
if (_topic != topic)
|
||||||
|
{
|
||||||
|
if (_debug)
|
||||||
|
{
|
||||||
|
SerialPrint("i", "Presence", topic + " not equal: " + _topic + " msg: " + msg);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isJson)
|
||||||
|
{
|
||||||
|
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
||||||
|
DeserializationError err = deserializeJson(doc, msg);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("Presence"), err.f_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject obj = doc.as<JsonObject>();
|
||||||
|
|
||||||
|
if (obj.containsKey("chargingState"))
|
||||||
|
{
|
||||||
|
pdata.chargingState = obj["chargingState"].as<String>();
|
||||||
|
}
|
||||||
|
if (obj.containsKey("plugState"))
|
||||||
|
{
|
||||||
|
pdata.plugState = obj["plugState"].as<String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.containsKey("connectedWifi"))
|
||||||
|
pdata.connectedWifi = obj["connectedWifi"].as<String>();
|
||||||
|
if (obj.containsKey("geoLocation"))
|
||||||
|
{
|
||||||
|
pdata.geoLocation = obj["geoLocation"].as<String>();
|
||||||
|
// Разбор геолокации после получения
|
||||||
|
parseGeo(pdata.geoLocation);
|
||||||
|
pdata.geoTime = getDateTimeDotFormatedFromUnix(pdata.geoTimestamp);
|
||||||
|
SerialPrint("i", "Presence", "GeoTime : " + pdata.geoTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.containsKey("deviceName"))
|
||||||
|
pdata.deviceName = obj["deviceName"].as<String>();
|
||||||
|
if (obj.containsKey("batteryLevel"))
|
||||||
|
pdata.batteryLevel = obj["batteryLevel"].as<int>();
|
||||||
|
if (obj.containsKey("currentTimestamp"))
|
||||||
|
{
|
||||||
|
pdata.currentTimestamp = obj["currentTimestamp"].as<unsigned long>();
|
||||||
|
pdata.currentTime = getDateTimeDotFormatedFromUnix(pdata.currentTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.containsKey("nextScheduledTimestamp"))
|
||||||
|
{
|
||||||
|
pdata.nextScheduledTimestamp = obj["nextScheduledTimestamp"].as<unsigned long>();
|
||||||
|
pdata.nextScheduledTime = getDateTimeDotFormatedFromUnix(pdata.nextScheduledTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.containsKey("nextAlarmclockTimestamp"))
|
||||||
|
{
|
||||||
|
pdata.nextAlarmclockTimestamp = obj["nextAlarmclockTimestamp"].as<unsigned long>();
|
||||||
|
pdata.nextAlarmclockTime = getDateTimeDotFormatedFromUnix(pdata.nextAlarmclockTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.containsKey("conditionContent"))
|
||||||
|
{
|
||||||
|
JsonArray arr = obj["conditionContent"].as<JsonArray>();
|
||||||
|
pdata.conditionContent.clear();
|
||||||
|
for (String s : arr)
|
||||||
|
{
|
||||||
|
pdata.conditionContent.push_back(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
String conditionStr;
|
||||||
|
for (size_t i = 0; i < pdata.conditionContent.size(); ++i)
|
||||||
|
{
|
||||||
|
conditionStr += pdata.conditionContent[i];
|
||||||
|
if (i < pdata.conditionContent.size() - 1)
|
||||||
|
conditionStr += ", ";
|
||||||
|
}
|
||||||
|
pdata.conditionContentString = conditionStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataFromNode = true;
|
||||||
|
_minutesPassed = 0;
|
||||||
|
|
||||||
|
String sensorVal;
|
||||||
|
|
||||||
|
if (_parameter == "latitude")
|
||||||
|
{
|
||||||
|
value.isDecimal = true;
|
||||||
|
value.valD = pdata.lat;
|
||||||
|
}
|
||||||
|
else if (_parameter == "longitude")
|
||||||
|
{
|
||||||
|
value.isDecimal = true;
|
||||||
|
value.valD = pdata.lon;
|
||||||
|
}
|
||||||
|
else if (_parameter == "azimuth")
|
||||||
|
{
|
||||||
|
value.isDecimal = true;
|
||||||
|
value.valD = calculateInitialBearing(_lat_A, _lon_A, pdata.lat, pdata.lon);
|
||||||
|
}
|
||||||
|
else if (_parameter == "distance")
|
||||||
|
{
|
||||||
|
value.isDecimal = true;
|
||||||
|
value.valD = haversineDistance(_lat_A, _lon_A, pdata.lat, pdata.lon);
|
||||||
|
}
|
||||||
|
else if (_parameter == "batteryLevel")
|
||||||
|
{
|
||||||
|
value.isDecimal = true;
|
||||||
|
value.valD = pdata.batteryLevel;
|
||||||
|
}
|
||||||
|
else if (_parameter == "geoTime")
|
||||||
|
{
|
||||||
|
value.isDecimal = false;
|
||||||
|
value.valS = pdata.geoTime;
|
||||||
|
}
|
||||||
|
else if (_parameter == "geoTimestamp")
|
||||||
|
{
|
||||||
|
value.isDecimal = false;
|
||||||
|
value.valS = pdata.geoTimestamp;
|
||||||
|
}
|
||||||
|
else if (_parameter == "currentTime")
|
||||||
|
{
|
||||||
|
value.isDecimal = false;
|
||||||
|
value.valS = pdata.currentTime;
|
||||||
|
}
|
||||||
|
else if (_parameter == "nextScheduledTime")
|
||||||
|
{
|
||||||
|
value.isDecimal = false;
|
||||||
|
value.valS = pdata.nextScheduledTime;
|
||||||
|
}
|
||||||
|
else if (_parameter == "nextAlarmclockTime")
|
||||||
|
{
|
||||||
|
value.isDecimal = false;
|
||||||
|
value.valS = pdata.nextAlarmclockTime;
|
||||||
|
}
|
||||||
|
else if (_parameter == "conditionContent")
|
||||||
|
{
|
||||||
|
value.isDecimal = false;
|
||||||
|
value.valS = pdata.conditionContentString;
|
||||||
|
}
|
||||||
|
else if (obj.containsKey(_parameter) && obj[_parameter].is<const char *>())
|
||||||
|
{
|
||||||
|
sensorVal = obj[_parameter].as<const char *>();
|
||||||
|
value.isDecimal = false;
|
||||||
|
value.valS = sensorVal;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value.isDecimal = false;
|
||||||
|
value.valS = "parameter mismatch";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.isDecimal)
|
||||||
|
{
|
||||||
|
regEvent(value.valD, F("Presence"), _debug, _ticker);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
regEvent(value.valS, F("Presence"), _debug, _ticker);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_debug)
|
||||||
|
{
|
||||||
|
SerialPrint("i", "Presence", "Received MAC: " + dev + " val=" + msg);
|
||||||
|
}
|
||||||
|
dataFromNode = true;
|
||||||
|
_minutesPassed = 0;
|
||||||
|
setValue(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
||||||
|
{
|
||||||
|
IoTValue valTmp;
|
||||||
|
|
||||||
|
if (command == "latitude")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = pdata.lat;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "longitude")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = pdata.lon;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "azimuth")
|
||||||
|
{
|
||||||
|
if (param.size() == 2 && param[0].isDecimal && param[1].isDecimal)
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = calculateInitialBearing(param[0].valD, param[1].valD, pdata.lat, pdata.lon);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = "wrong parameters";
|
||||||
|
}
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "distance")
|
||||||
|
{
|
||||||
|
if (param.size() == 2 && param[1].isDecimal && param[1].isDecimal)
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = haversineDistance(param[0].valD, param[1].valD, pdata.lat, pdata.lon);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = "wrong parameters";
|
||||||
|
}
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "batteryLevel")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = pdata.batteryLevel;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "geoTime")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.geoTime;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "geoTimestamp")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.geoTimestamp;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "currentTime")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.currentTime;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "currentTimestamp")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.currentTimestamp;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "nextScheduledTime")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.nextScheduledTime;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "nextScheduledTimestamp")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.nextScheduledTimestamp;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "nextAlarmclockTime")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.nextAlarmclockTime;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "nextAlarmclockTimestamp")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.nextAlarmclockTimestamp;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "conditionContent")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.conditionContentString;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "chargingState")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.chargingState;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "plugState")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.plugState;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "connectedWifi")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.connectedWifi;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "deviceName")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.deviceName;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "geoLocation")
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = pdata.geoLocation;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = "wrong command";
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
String getMqttExterSub()
|
||||||
|
{
|
||||||
|
return _topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
void doByInterval()
|
||||||
|
{
|
||||||
|
_minutesPassed++;
|
||||||
|
setNewWidgetAttributes();
|
||||||
|
if (mqttIsConnect() && !sendOk)
|
||||||
|
{
|
||||||
|
mqtt.setBufferSize(512); // ← ДО подписи
|
||||||
|
sendOk = true;
|
||||||
|
mqttSubscribeExternal(_topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void onMqttWsAppConnectEvent()
|
||||||
|
{
|
||||||
|
setNewWidgetAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNewWidgetAttributes()
|
||||||
|
{
|
||||||
|
|
||||||
|
jsonWriteStr(json, F("info"), prettyMinutsTimeout(_minutesPassed));
|
||||||
|
if (dataFromNode)
|
||||||
|
{
|
||||||
|
if (orange != 0 && red != 0 && offline != 0)
|
||||||
|
{
|
||||||
|
if (_minutesPassed < orange)
|
||||||
|
{
|
||||||
|
jsonWriteStr(json, F("color"), "");
|
||||||
|
}
|
||||||
|
if (_minutesPassed >= orange && _minutesPassed < red)
|
||||||
|
{
|
||||||
|
jsonWriteStr(json, F("color"), F("orange")); // сделаем виджет оранжевым
|
||||||
|
}
|
||||||
|
if (_minutesPassed >= red && _minutesPassed < offline)
|
||||||
|
{
|
||||||
|
jsonWriteStr(json, F("color"), F("red")); // сделаем виджет красным
|
||||||
|
}
|
||||||
|
if (_minutesPassed >= offline)
|
||||||
|
{
|
||||||
|
jsonWriteStr(json, F("info"), F("offline"));
|
||||||
|
SerialPrint("i", "Presence", _id + " - offline");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jsonWriteStr(json, F("info"), F("awaiting"));
|
||||||
|
}
|
||||||
|
// SerialPrint("i", "JSON", json);
|
||||||
|
sendSubWidgetsValues(_id, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseGeo(const String &geo)
|
||||||
|
{
|
||||||
|
if (!geo.startsWith("geo:"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int commaIndex = geo.indexOf(',', 4);
|
||||||
|
int semicolonIndex = geo.indexOf(';', commaIndex);
|
||||||
|
|
||||||
|
if (commaIndex == -1 || semicolonIndex == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String latStr = geo.substring(4, commaIndex);
|
||||||
|
String lonStr = geo.substring(commaIndex + 1, semicolonIndex);
|
||||||
|
|
||||||
|
// timestamp
|
||||||
|
String tsTag = "timestamp=";
|
||||||
|
int tsIndex = geo.indexOf(tsTag);
|
||||||
|
String tsStr = (tsIndex > 0) ? geo.substring(tsIndex + tsTag.length()) : "";
|
||||||
|
|
||||||
|
pdata.lat = latStr.toFloat();
|
||||||
|
pdata.lon = lonStr.toFloat();
|
||||||
|
pdata.geoTimestamp = tsStr.toInt();
|
||||||
|
|
||||||
|
if (_debug)
|
||||||
|
{
|
||||||
|
SerialPrint("i", "Presence", "Lat: " + String(pdata.lat, 6));
|
||||||
|
SerialPrint("i", "Presence", "Lon: " + String(pdata.lon, 6));
|
||||||
|
SerialPrint("i", "Presence", "Geo timestamp: " + String(pdata.geoTimestamp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~Presence() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
void *getAPI_Presence(String subtype, String param)
|
||||||
|
{
|
||||||
|
if (subtype == F("Presence"))
|
||||||
|
{
|
||||||
|
return new Presence(param);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
372
src/modules/sensors/Presence/export.json
Normal file
372
src/modules/sensors/Presence/export.json
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
{
|
||||||
|
"mark": "iotm",
|
||||||
|
"config": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutDistance",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataKm",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "Distance",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": "0.001",
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutAzimuth",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "Azimuth",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Presence",
|
||||||
|
"id": "presence",
|
||||||
|
"widget": "anydataM",
|
||||||
|
"page": "Presence",
|
||||||
|
"descr": "Distance",
|
||||||
|
"Lat. A": "47.0159",
|
||||||
|
"Long. A": "28.8448",
|
||||||
|
"parameter": "distance",
|
||||||
|
"topic": "/myPhone/status",
|
||||||
|
"isJson": 1,
|
||||||
|
"round": "0",
|
||||||
|
"orange": 60,
|
||||||
|
"red": 120,
|
||||||
|
"offline": 180,
|
||||||
|
"int": 15,
|
||||||
|
"ticker": 1,
|
||||||
|
"debug": 1,
|
||||||
|
"moduleName": "Presence"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutLat",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "Latitude",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutLong",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "Longitude",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutbatteryLevel",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataHum",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "batteryLevel",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutgeoTime",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "geoTime",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutgeoTimestamp",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "geoTimestamp",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutcurrentTime",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "currentTime",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutcurrentTimestamp",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "currentTimestamp",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutnextScheduledTime",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "nextScheduledTime",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutnextScheduledTimestamp",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "nextScheduledTimestamp",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutnextAlarmclockTime",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "nextAlarmclockTime",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutnextAlarmclockTimestamp",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "nextAlarmclockTimestamp",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutchargingState",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "chargingState",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutplugState",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "plugState",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutconnectedWifi",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "connectedWifi",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutdeviceName",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "deviceName",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutgeoLocation",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "geoLocation",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutconditionContent",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Output",
|
||||||
|
"descr": "conditionContent",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario=> if presence then {
|
||||||
|
voutDistance = presence.distance(47.0159,28.8448)
|
||||||
|
voutAzimuth = presence.azimuth(47.0159,28.8448)
|
||||||
|
voutLat = presence.latitude()
|
||||||
|
voutLong = presence.longitude()
|
||||||
|
voutbatteryLevel = presence.batteryLevel()
|
||||||
|
voutgeoTime = presence.geoTime()
|
||||||
|
voutgeoTimestamp = presence.geoTimestamp()
|
||||||
|
voutcurrentTime = presence.currentTime()
|
||||||
|
voutcurrentTimestamp = presence.currentTimestamp()
|
||||||
|
voutnextScheduledTime = presence.nextScheduledTime()
|
||||||
|
voutnextScheduledTimestamp = presence.nextScheduledTimestamp()
|
||||||
|
voutnextAlarmclockTime = presence.nextAlarmclockTime()
|
||||||
|
voutnextAlarmclockTimestamp = presence.nextAlarmclockTimestamp()
|
||||||
|
voutchargingState = presence.chargingState()
|
||||||
|
voutplugState = presence.plugState()
|
||||||
|
voutconnectedWifi = presence.connectedWifi()
|
||||||
|
voutdeviceName = presence.deviceName()
|
||||||
|
voutgeoLocation = presence.geoLocation()
|
||||||
|
voutconditionContent = presence.conditionContent()
|
||||||
|
}
|
||||||
199
src/modules/sensors/Presence/modinfo.json
Normal file
199
src/modules/sensors/Presence/modinfo.json
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
{
|
||||||
|
"menuSection": "sensors",
|
||||||
|
"configItem": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"name": "MQTT Presence Subscriber",
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Presence",
|
||||||
|
"id": "presence",
|
||||||
|
"widget": "anydataM",
|
||||||
|
"page": "Presence",
|
||||||
|
"descr": "Дистанция",
|
||||||
|
"Lat. A": "47.0159",
|
||||||
|
"Long. A": "28.8448",
|
||||||
|
"parameter": "distance",
|
||||||
|
"topic": "/myPhone/status",
|
||||||
|
"isJson": 1,
|
||||||
|
"round": "0",
|
||||||
|
"orange": 60,
|
||||||
|
"red": 120,
|
||||||
|
"offline": 180,
|
||||||
|
"int": 15,
|
||||||
|
"ticker": 1,
|
||||||
|
"debug": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"about": {
|
||||||
|
"authorName": "Alex",
|
||||||
|
"authorContact": "https://t.me/cmche",
|
||||||
|
"authorGit": "https://github.com/CHE77/IoTManager-Modules",
|
||||||
|
"exampleURL": "https://iotmanager.org/wiki",
|
||||||
|
"specialThanks": "",
|
||||||
|
"moduleName": "Presence",
|
||||||
|
"moduleVersion": "1.0",
|
||||||
|
"usedRam": {
|
||||||
|
"esp32_4mb": 15,
|
||||||
|
"esp8266_4mb": 15
|
||||||
|
},
|
||||||
|
"title": "MQTT Presence Subscriber",
|
||||||
|
"moduleDesc": "Модуль получения и обработки данных из Presence Publisher app - https://f-droid.org/packages/org.ostrya.presencepublisher/ https://play.google.com/store/apps/details?id=org.ostrya.presencepublisher Получает геопозицию телефона, считает пеленг и дистанцию.",
|
||||||
|
"license": "Cooperative Non-Violent Public License (CNPL)",
|
||||||
|
"propInfo": {
|
||||||
|
"Lat. A": "Широта точки отсчета (локации), в градусах",
|
||||||
|
"Long. A": "Долгота точки отсчета (локации), в градусах",
|
||||||
|
"parameter": "Параметр/ключ для получения данных из json и его производных. Совпадают с методами для сценария",
|
||||||
|
"topic": "Топик на который подписывется модуль. Должен совпадать с топиком в приложении и оканчиваться на status",
|
||||||
|
"isJson": "1 - ожидаем в топике json. Другие форматы пока не подерживаются. В приложении выберите тоже json",
|
||||||
|
"round": "Округление после запятой.",
|
||||||
|
"orange": "Количество минут после которого окрасить виджет в оранжевый цвет",
|
||||||
|
"red": "количество минут после которого окрасить виджет в красный цвет",
|
||||||
|
"offline": "Количество минут после которого отобразить что устройство offline, если все три orange red и offline поставить в ноль - то функция окраски выключится",
|
||||||
|
"int": "Интервал для изменения цвета",
|
||||||
|
"ticker": "Генерировать(1) или нет(0) события при каждом тике часов (каждые int секунд).",
|
||||||
|
"debug": "1 - выводить дополнительный лог в сериал"
|
||||||
|
},
|
||||||
|
"retInfo": " - Согласно выбраного параметра",
|
||||||
|
"funcInfo": [
|
||||||
|
{
|
||||||
|
"name": "latitude",
|
||||||
|
"descr": "Получить широту позиции устройства (и далее) с приложением Presence Publisher",
|
||||||
|
"params": [
|
||||||
|
"presence.latitude()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "longitude",
|
||||||
|
"descr": "Получить долготу позиции устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.longitude()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "distance",
|
||||||
|
"descr": "Получить дистанцию до устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.distance()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "azimuth",
|
||||||
|
"descr": "Получить пеленг на устройтво",
|
||||||
|
"params": [
|
||||||
|
"presence.azimuth()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "batteryLevel",
|
||||||
|
"descr": "Получить уровень заряда батареи устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.batteryLevel()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "geoTime",
|
||||||
|
"descr": "Получить время определения геопозии устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.geoTime()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "geoTimestamp",
|
||||||
|
"descr": "Получить UnixTime определения геопозии устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.geoTimestamp()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currentTime",
|
||||||
|
"descr": "Получить время получения геопозиции от устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.currentTime()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currentTimestamp",
|
||||||
|
"descr": "Получить UnixTime получения геопозиции от устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.currentTimestamp()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nextScheduledTime",
|
||||||
|
"descr": "Получить время следующего получения геопозиции от устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.nextScheduledTime()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nextScheduledTimestamp",
|
||||||
|
"descr": "Получить UnixTime следующего получения геопозиции от устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.nextScheduledTimestamp()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nextAlarmclockTime",
|
||||||
|
"descr": "Получить время следующего будильника на устройтве",
|
||||||
|
"params": [
|
||||||
|
"presence.nextAlarmclockTime()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nextAlarmclockTimestamp",
|
||||||
|
"descr": "Получить UnixTime следующего будильника на устройтве",
|
||||||
|
"params": [
|
||||||
|
"presence.nextAlarmclockTimestamp()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chargingState",
|
||||||
|
"descr": "Получить статус зарядки устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.chargingState()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "plugState",
|
||||||
|
"descr": "Получить тип зарядки устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.plugState()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "connectedWifi",
|
||||||
|
"descr": "Получить Wi-Fi точку доступа к которой подключено устройство",
|
||||||
|
"params": [
|
||||||
|
"presence.connectedWifi()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "deviceName",
|
||||||
|
"descr": "Получить имя устройства",
|
||||||
|
"params": [
|
||||||
|
"presence.deviceName()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "geoLocation",
|
||||||
|
"descr": "Получить строку с широтой, долготой и верменем определения геопозиции",
|
||||||
|
"params": [
|
||||||
|
"presence.geoLocation()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "conditionContent",
|
||||||
|
"descr": "Получить дополнительное условие отправки данных (геопозиции и др)",
|
||||||
|
"params": [
|
||||||
|
"presence.conditionContent()"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"defActive": false,
|
||||||
|
"usedLibs": {
|
||||||
|
"esp32*": [],
|
||||||
|
"esp82*": [],
|
||||||
|
"bk72*": []
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/modules/sensors/Presence/preview.png
Normal file
BIN
src/modules/sensors/Presence/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
383
src/modules/sensors/Presence/widgets.json
Normal file
383
src/modules/sensors/Presence/widgets.json
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "anydataRed",
|
||||||
|
"label": "Сообщение1",
|
||||||
|
"widget": "anydata",
|
||||||
|
"icon": "body",
|
||||||
|
"color": "red",
|
||||||
|
"descrColor": "red"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataDgr",
|
||||||
|
"label": "Сообщение2",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "",
|
||||||
|
"color": "red",
|
||||||
|
"icon": "walk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataDef",
|
||||||
|
"label": "Текст",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "",
|
||||||
|
"icon": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataVlt",
|
||||||
|
"label": "Вольты",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "V",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataAmp",
|
||||||
|
"label": "Амперы",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "A",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataWt",
|
||||||
|
"label": "Ватты",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "Wt",
|
||||||
|
"icon": "speedometer",
|
||||||
|
"color": [
|
||||||
|
{
|
||||||
|
"level": 0,
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 200,
|
||||||
|
"value": "#009933"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 2000,
|
||||||
|
"value": "#FF9900"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 4000,
|
||||||
|
"value": "red"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataWth",
|
||||||
|
"label": "Энергия",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "kWh",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataHtz",
|
||||||
|
"label": "Герцы",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "Hz",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataTmp",
|
||||||
|
"label": "Температура",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "°С",
|
||||||
|
"icon": "thermometer",
|
||||||
|
"font": "OCR A Std",
|
||||||
|
"color": [
|
||||||
|
{
|
||||||
|
"level": -20,
|
||||||
|
"value": "#0000CC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": -10,
|
||||||
|
"value": "#0000CC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 0,
|
||||||
|
"value": "#0000CC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 12,
|
||||||
|
"value": "#3366FF"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 16,
|
||||||
|
"value": "#33CCFF"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 18,
|
||||||
|
"value": "#009933"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 30,
|
||||||
|
"value": "#FF9900"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": 40,
|
||||||
|
"value": "red"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataMm",
|
||||||
|
"label": "Давление",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "mm",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataHum",
|
||||||
|
"label": "Влажность",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "%",
|
||||||
|
"icon": "water",
|
||||||
|
"color": "#88AADF"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataTm",
|
||||||
|
"label": "Время",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "button",
|
||||||
|
"label": "Кнопка",
|
||||||
|
"widget": "btn",
|
||||||
|
"size": "large",
|
||||||
|
"color": "green",
|
||||||
|
"send": "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "toggle",
|
||||||
|
"label": "Переключатель",
|
||||||
|
"widget": "toggle",
|
||||||
|
"icon": "",
|
||||||
|
"iconOff": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chart1",
|
||||||
|
"label": "График без точек",
|
||||||
|
"widget": "chart",
|
||||||
|
"dateFormat": "HH:mm",
|
||||||
|
"maxCount": 86400,
|
||||||
|
"pointRadius": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chart2",
|
||||||
|
"label": "График с точками",
|
||||||
|
"widget": "chart",
|
||||||
|
"maxCount": 86400,
|
||||||
|
"dateFormat": "HH:mm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chart3",
|
||||||
|
"label": "График Дневной",
|
||||||
|
"widget": "chart",
|
||||||
|
"dateFormat": "DD.MM.YYYY",
|
||||||
|
"maxCount": 86400,
|
||||||
|
"type": "bar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chart4",
|
||||||
|
"label": "График Часовой",
|
||||||
|
"widget": "chart",
|
||||||
|
"dateFormat": "HH:mm",
|
||||||
|
"maxCount": 3600,
|
||||||
|
"type": "bar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chart5",
|
||||||
|
"label": "График двойной",
|
||||||
|
"widget": "chart",
|
||||||
|
"series": [
|
||||||
|
"Температура, С",
|
||||||
|
"Влажность, %"
|
||||||
|
],
|
||||||
|
"dateFormat": "HH:mm",
|
||||||
|
"maxCount": 86400,
|
||||||
|
"pointRadius": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chart6",
|
||||||
|
"label": "График тройной",
|
||||||
|
"widget": "chart",
|
||||||
|
"series": [
|
||||||
|
"Температура, С",
|
||||||
|
"Влажность, %",
|
||||||
|
"Давление, кПа"
|
||||||
|
],
|
||||||
|
"dateFormat": "HH:mm",
|
||||||
|
"maxCount": 86400,
|
||||||
|
"pointRadius": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fillgauge",
|
||||||
|
"label": "Бочка",
|
||||||
|
"widget": "fillgauge",
|
||||||
|
"circleColor": "#00FFFF",
|
||||||
|
"textColor": "#FFFFFF",
|
||||||
|
"waveTextColor": "#000000",
|
||||||
|
"waveColor": "#00FFFF"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inputDate",
|
||||||
|
"label": "Ввод даты",
|
||||||
|
"widget": "input",
|
||||||
|
"size": "small",
|
||||||
|
"color": "orange",
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inputDgt",
|
||||||
|
"label": "Ввод числа",
|
||||||
|
"widget": "input",
|
||||||
|
"color": "blue",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inputTxt",
|
||||||
|
"label": "Ввод текста",
|
||||||
|
"widget": "input",
|
||||||
|
"size": "small",
|
||||||
|
"color": "orange",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inputTm",
|
||||||
|
"label": "Ввод времени",
|
||||||
|
"widget": "input",
|
||||||
|
"color": "blue",
|
||||||
|
"type": "time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "progressLine",
|
||||||
|
"label": "Статус линия",
|
||||||
|
"widget": "progress-line",
|
||||||
|
"icon": "sunny",
|
||||||
|
"max": "100",
|
||||||
|
"stroke": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "progressRound",
|
||||||
|
"label": "Статус круг",
|
||||||
|
"widget": "progress-round",
|
||||||
|
"max": "100",
|
||||||
|
"stroke": "20",
|
||||||
|
"color": "#45ccce",
|
||||||
|
"background": "#777",
|
||||||
|
"semicircle": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "range",
|
||||||
|
"label": "Ползунок",
|
||||||
|
"widget": "range",
|
||||||
|
"descrColor": "red",
|
||||||
|
"after": "%",
|
||||||
|
"k": 0.0977,
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"debounce": 500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rangeServo",
|
||||||
|
"label": "Ползунок (Servo)",
|
||||||
|
"widget": "range",
|
||||||
|
"descrColor": "red",
|
||||||
|
"after": "°",
|
||||||
|
"k": 1,
|
||||||
|
"min": 0,
|
||||||
|
"max": 180,
|
||||||
|
"debounce": 500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "select",
|
||||||
|
"label": "Выпадающий",
|
||||||
|
"widget": "select",
|
||||||
|
"options": [
|
||||||
|
"Выключен",
|
||||||
|
"Включен"
|
||||||
|
],
|
||||||
|
"status": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataPpm",
|
||||||
|
"label": "PPM",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "ppm",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydatamAmp",
|
||||||
|
"label": "миллиАмперы",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "mAmp",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydatamVlt",
|
||||||
|
"label": "миллиВольты",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "mVlt",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydatamWt",
|
||||||
|
"label": "миллиВатты",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "mWt",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataKm",
|
||||||
|
"label": "Километры",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "km",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataM",
|
||||||
|
"label": "Метры",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "m",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataCm",
|
||||||
|
"label": "Сантиметры",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "cm",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataLiter",
|
||||||
|
"label": "Литры",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "ltr",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataSpeed",
|
||||||
|
"label": "мерты в секунду",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "m/s",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataСorner",
|
||||||
|
"label": "угол градусов",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "°",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anydataBar",
|
||||||
|
"label": "давление Bar",
|
||||||
|
"widget": "anydata",
|
||||||
|
"after": "Kg/cm²",
|
||||||
|
"icon": "speedometer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nil",
|
||||||
|
"label": "Без виджета"
|
||||||
|
}
|
||||||
|
]
|
||||||
718
src/modules/sensors/SoftRTC/SoftRTC.cpp
Normal file
718
src/modules/sensors/SoftRTC/SoftRTC.cpp
Normal file
@@ -0,0 +1,718 @@
|
|||||||
|
// Licensed under the Cooperative Non-Violent Public License (CNPL)
|
||||||
|
// See: https://github.com/CHE77/IoTManager-Modules/blob/main/LICENSE
|
||||||
|
|
||||||
|
#include "Global.h"
|
||||||
|
#include "classes/IoTItem.h"
|
||||||
|
#include "NTP.h"
|
||||||
|
#include "WsServer.h"
|
||||||
|
|
||||||
|
// Подключаем нужные заголовочные файлы в зависимости от платформы
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include <TZ.h>
|
||||||
|
#include <coredecls.h> // Для settimeofday_cb() в ESP8266
|
||||||
|
#else
|
||||||
|
#include <esp_sntp.h> // Для ESP32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SoftRTC_SYNC_STATUS_NOT_SET = 0, // Время не установлено
|
||||||
|
// SoftRTC_SYNC_STATUS_COULD_NOT_SET, // Время не может быть установлено
|
||||||
|
SoftRTC_SYNC_STATUS_BEFORE_SoftRTC, // Время было установлено до SoftRTC
|
||||||
|
SoftRTC_SYNC_STATUS_RESTORED, // Время восстановлено из памяти
|
||||||
|
SoftRTC_SYNC_STATUS_MANUAL, // Время установлено вручную
|
||||||
|
SoftRTC_SYNC_STATUS_FROM_BROWSER_OR_NTP,
|
||||||
|
SoftRTC_SYNC_STATUS_FROM_BROWSER, // Время (скоере всего) установлено с Браузера
|
||||||
|
SoftRTC_SYNC_STATUS_NTP_JUST, // Время только что синхронизировано через NTP
|
||||||
|
SoftRTC_SYNC_STATUS_NTP // Время синхронизировано через NTP
|
||||||
|
|
||||||
|
} SoftRTC_sync_status_t;
|
||||||
|
|
||||||
|
// Переменная для отслеживания источника времени
|
||||||
|
volatile int syncStatus = SoftRTC_SYNC_STATUS_NOT_SET;
|
||||||
|
|
||||||
|
// Функция-коллбэк для NTP (разные сигнатуры для разных платформ)
|
||||||
|
#ifdef ESP8266
|
||||||
|
/*
|
||||||
|
void timeSyncCallback()
|
||||||
|
{
|
||||||
|
syncStatus = SoftRTC_SYNC_STATUS_NTP_JUST;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
#else
|
||||||
|
void timeSyncCallback(struct timeval *tv)
|
||||||
|
{
|
||||||
|
(void)tv; // Неиспользуемый параметр
|
||||||
|
syncStatus = SoftRTC_SYNC_STATUS_NTP_JUST;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
time_t getUnixTimeFromYMDHMS(int year, int month, int day, int hour, int minute, int second)
|
||||||
|
{
|
||||||
|
struct tm t;
|
||||||
|
t.tm_year = year - 1900;
|
||||||
|
t.tm_mon = month - 1;
|
||||||
|
t.tm_mday = day;
|
||||||
|
t.tm_hour = hour;
|
||||||
|
t.tm_min = minute;
|
||||||
|
t.tm_sec = second;
|
||||||
|
t.tm_isdst = -1;
|
||||||
|
return mktime(&t);
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t getUnixTimeFromString(String datetime)
|
||||||
|
{
|
||||||
|
struct tm t = {0};
|
||||||
|
int d, m, y, h, min, s;
|
||||||
|
|
||||||
|
const char *dt = datetime.c_str();
|
||||||
|
|
||||||
|
if (sscanf(dt, "%d%*c%d%*c%d %d:%d:%d", &d, &m, &y, &h, &min, &s) == 6)
|
||||||
|
{
|
||||||
|
if (y < 100)
|
||||||
|
y += (y >= 69) ? 1900 : 2000;
|
||||||
|
if (d > 31)
|
||||||
|
{
|
||||||
|
int temp = d;
|
||||||
|
d = y;
|
||||||
|
y = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
t.tm_mday = d;
|
||||||
|
t.tm_mon = m - 1;
|
||||||
|
t.tm_year = y - 1900;
|
||||||
|
t.tm_hour = h;
|
||||||
|
t.tm_min = min;
|
||||||
|
t.tm_sec = s;
|
||||||
|
t.tm_isdst = -1;
|
||||||
|
|
||||||
|
return mktime(&t);
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatUnixTime(time_t unixTime)
|
||||||
|
{
|
||||||
|
struct tm *timeInfo;
|
||||||
|
timeInfo = localtime(&unixTime);
|
||||||
|
char formattedTime[32];
|
||||||
|
// char formattedTime[20];
|
||||||
|
int year = timeInfo->tm_year + 1900;
|
||||||
|
int shortYear = year % 100;
|
||||||
|
|
||||||
|
snprintf(formattedTime, sizeof(formattedTime), "%02d.%02d.%02d %02d:%02d:%02d",
|
||||||
|
timeInfo->tm_mday, timeInfo->tm_mon + 1, shortYear,
|
||||||
|
timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
|
||||||
|
|
||||||
|
return String(formattedTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDSTfunc(time_t utc)
|
||||||
|
{
|
||||||
|
struct tm *timeinfo = gmtime(&utc); // UTC time → broken-down time
|
||||||
|
int year = timeinfo->tm_year + 1900;
|
||||||
|
|
||||||
|
// Найдём последнюю дату воскресенья марта и октября
|
||||||
|
struct tm lastMarchSunday = {0};
|
||||||
|
lastMarchSunday.tm_year = year - 1900;
|
||||||
|
lastMarchSunday.tm_mon = 2; // Март (0-based)
|
||||||
|
lastMarchSunday.tm_mday = 31;
|
||||||
|
lastMarchSunday.tm_hour = 3; // Переход в 03:00 UTC
|
||||||
|
|
||||||
|
mktime(&lastMarchSunday); // нормализация даты
|
||||||
|
|
||||||
|
// Ищем последнее воскресенье марта
|
||||||
|
while (lastMarchSunday.tm_wday != 0)
|
||||||
|
{ // 0 = воскресенье
|
||||||
|
lastMarchSunday.tm_mday--;
|
||||||
|
mktime(&lastMarchSunday);
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t dstStart = mktime(&lastMarchSunday); // начало летнего времени
|
||||||
|
|
||||||
|
struct tm lastOctoberSunday = {0};
|
||||||
|
lastOctoberSunday.tm_year = year - 1900;
|
||||||
|
lastOctoberSunday.tm_mon = 9; // Октябрь
|
||||||
|
lastOctoberSunday.tm_mday = 31;
|
||||||
|
lastOctoberSunday.tm_hour = 4; // Переход в 04:00 UTC
|
||||||
|
|
||||||
|
mktime(&lastOctoberSunday);
|
||||||
|
|
||||||
|
while (lastOctoberSunday.tm_wday != 0)
|
||||||
|
{
|
||||||
|
lastOctoberSunday.tm_mday--;
|
||||||
|
mktime(&lastOctoberSunday);
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t dstEnd = mktime(&lastOctoberSunday); // конец летнего времени
|
||||||
|
|
||||||
|
return utc >= dstStart && utc < dstEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getWeekOfYear(time_t unixTime)
|
||||||
|
{
|
||||||
|
struct tm *timeInfo = localtime(&unixTime);
|
||||||
|
char buffer[3]; // 2 цифры + \0
|
||||||
|
strftime(buffer, sizeof(buffer), "%W", timeInfo); // %W — номер недели (понедельник — первый день недели)
|
||||||
|
return atoi(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getDayOfWeekNumber(time_t unixTime)
|
||||||
|
{
|
||||||
|
struct tm *timeInfo = localtime(&unixTime);
|
||||||
|
int wday = timeInfo->tm_wday;
|
||||||
|
return (wday == 0) ? 7 : wday; // воскресенье (0) → 7
|
||||||
|
}
|
||||||
|
|
||||||
|
int getDaysInMonth(time_t unixTime)
|
||||||
|
{
|
||||||
|
struct tm t = *localtime(&unixTime);
|
||||||
|
|
||||||
|
t.tm_mday = 1; // Первый день текущего месяца
|
||||||
|
t.tm_mon += 1; // Следующий месяц
|
||||||
|
t.tm_hour = 0; t.tm_min = 0; t.tm_sec = 0;
|
||||||
|
|
||||||
|
if (t.tm_mon > 11) { // Декабрь → январь
|
||||||
|
t.tm_mon = 0;
|
||||||
|
t.tm_year += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t firstOfNextMonth = mktime(&t);
|
||||||
|
t.tm_mon -= 1; // Возврат к текущему
|
||||||
|
t.tm_mday = 1;
|
||||||
|
time_t firstOfThisMonth = mktime(&t);
|
||||||
|
|
||||||
|
int days = (firstOfNextMonth - firstOfThisMonth) / (60 * 60 * 24);
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Возвращает день и месяц Пасхи в Григорианском календаре
|
||||||
|
void getOrthodoxEasterDate(int year, int &day, int &month)
|
||||||
|
{
|
||||||
|
// Meeus (Julian) algorithm
|
||||||
|
int a = year % 4;
|
||||||
|
int b = year % 7;
|
||||||
|
int c = year % 19;
|
||||||
|
int d = (19 * c + 15) % 30;
|
||||||
|
int e = (2 * a + 4 * b - d + 34) % 7;
|
||||||
|
|
||||||
|
int julianMonth = (d + e + 114) / 31; // 3 = март, 4 = апрель
|
||||||
|
int julianDay = ((d + e + 114) % 31) + 1;
|
||||||
|
|
||||||
|
// Переходим из юлианского в григоріанский:
|
||||||
|
// * для 1900‑2099 разница = 13 дней
|
||||||
|
struct tm t {};
|
||||||
|
t.tm_year = year - 1900;
|
||||||
|
t.tm_mon = julianMonth - 1;
|
||||||
|
t.tm_mday = julianDay + 13; // 13‑дневная разница
|
||||||
|
mktime(&t); // нормализует переход между месяцами
|
||||||
|
|
||||||
|
day = t.tm_mday;
|
||||||
|
month = t.tm_mon + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SoftRTC : public IoTItem
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
bool _ticker = false;
|
||||||
|
bool _debug = false;
|
||||||
|
long interval = 60;
|
||||||
|
int _winterTime = 2;
|
||||||
|
int _summerTime = 3;
|
||||||
|
time_t recovered_unixTime = 0;
|
||||||
|
time_t lastNTPtimeCorrection = 0;
|
||||||
|
time_t lastUnixTimeMillis = 0;
|
||||||
|
time_t lastUnixTime = 0;
|
||||||
|
// time_t _timeZoneSeconds;
|
||||||
|
int _timezone = 0;
|
||||||
|
int lastSyncStatus = 0;
|
||||||
|
time_t before = 0;
|
||||||
|
unsigned long startMillis = 0; // Сохраняем текущее время в миллисекундах
|
||||||
|
|
||||||
|
public:
|
||||||
|
SoftRTC(String parameters) : IoTItem(parameters)
|
||||||
|
{
|
||||||
|
_timezone = jsonReadInt(settingsFlashJson, F("timezone"));
|
||||||
|
// Устанавливаем коллбэк в зависимости от платформы
|
||||||
|
#ifdef ESP8266
|
||||||
|
// settimeofday_cb(timeSyncCallback);
|
||||||
|
#else
|
||||||
|
sntp_set_time_sync_notification_cb(timeSyncCallback);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_needSave = true;
|
||||||
|
_round = 0;
|
||||||
|
jsonRead(parameters, F("ticker"), _ticker);
|
||||||
|
jsonRead(parameters, F("int"), interval);
|
||||||
|
jsonRead(parameters, F("winterTime"), _winterTime);
|
||||||
|
jsonRead(parameters, F("summerTime"), _summerTime);
|
||||||
|
jsonRead(parameters, F("debug"), _debug);
|
||||||
|
|
||||||
|
before = time(nullptr);
|
||||||
|
startMillis = millis(); // Сохраняем текущее время в миллисекундах
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
if (syncStatus == SoftRTC_SYNC_STATUS_NTP_JUST)
|
||||||
|
{
|
||||||
|
time_t ut = getSystemTime();
|
||||||
|
String localDateTime = formatUnixTime(ut + _timezone * 60 * 60);
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "✅ Время синхронизировано через NTP! " + (String)ut + " LT: " + localDateTime);
|
||||||
|
|
||||||
|
lastNTPtimeCorrection = getSystemTime() - (lastUnixTime + (millis() - lastUnixTimeMillis) / 1000);
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "lastNTPtimeCorrection: " + (String)lastNTPtimeCorrection + " сек.");
|
||||||
|
syncStatus = SoftRTC_SYNC_STATUS_NTP;
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = (String)ut;
|
||||||
|
regEvent(valTmp.valS, F("SoftRTC"), _debug, true);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (syncStatus < 4) // если вреямя обновлено из надежного источника то уже не остлеживаем его изменение
|
||||||
|
{
|
||||||
|
|
||||||
|
time_t after = time(nullptr);
|
||||||
|
startMillis = millis() - startMillis;
|
||||||
|
|
||||||
|
if ((abs(after - before) - int((startMillis + 500) / 1000)) > 2) // проверка на изменение вермени
|
||||||
|
{
|
||||||
|
int delta = abs(after - before - int((startMillis + 500) / 1000));
|
||||||
|
|
||||||
|
if (_debug){
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Замечено, что Время измененно!");
|
||||||
|
SerialPrint("I", F("SoftRTC"), "syncStatus = " + String(syncStatus));
|
||||||
|
SerialPrint("I", F("SoftRTC"), "lastSyncStatus = " + String(lastSyncStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (syncStatus == lastSyncStatus)
|
||||||
|
{
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Замечено, что Время измененно без изменения статуса");
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
syncStatus = SoftRTC_SYNC_STATUS_FROM_BROWSER_OR_NTP;
|
||||||
|
lastSyncStatus = SoftRTC_SYNC_STATUS_FROM_BROWSER_OR_NTP;
|
||||||
|
|
||||||
|
time_t ut = getSystemTime();
|
||||||
|
String localDateTime = formatUnixTime(ut + _timezone * 60 * 60);
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "✅ Время синхронизировано через NTP или Browser! " + (String)ut + " LT: " + localDateTime);
|
||||||
|
|
||||||
|
lastNTPtimeCorrection = getSystemTime() - (lastUnixTime + (millis() - lastUnixTimeMillis) / 1000);
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "lastNTPtimeCorrection: " + (String)lastNTPtimeCorrection + " сек.");
|
||||||
|
|
||||||
|
#else
|
||||||
|
syncStatus = SoftRTC_SYNC_STATUS_FROM_BROWSER;
|
||||||
|
lastSyncStatus = SoftRTC_SYNC_STATUS_FROM_BROWSER;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lastSyncStatus = syncStatus;
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Замечено, что Время измененно c изменением статуса");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
before = time(nullptr);
|
||||||
|
startMillis = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
IoTItem::loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void doByInterval()
|
||||||
|
{
|
||||||
|
time_t unixTime = 0;
|
||||||
|
|
||||||
|
if (syncStatus == SoftRTC_SYNC_STATUS_RESTORED)
|
||||||
|
{
|
||||||
|
unixTime = getSystemTime();
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Время восстановлено из памяти: " + (String)unixTime);
|
||||||
|
if (isNetworkActive())
|
||||||
|
{
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Пробуем синхронизировать через NTP");
|
||||||
|
synchTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (syncStatus == SoftRTC_SYNC_STATUS_MANUAL)
|
||||||
|
{
|
||||||
|
unixTime = getSystemTime();
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Время установлено вручную: " + (String)unixTime);
|
||||||
|
if (isNetworkActive())
|
||||||
|
{
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Пробуем синхронизировать через NTP");
|
||||||
|
synchTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef ESP8266
|
||||||
|
else if (syncStatus == SoftRTC_SYNC_STATUS_FROM_BROWSER_OR_NTP)
|
||||||
|
{
|
||||||
|
unixTime = getSystemTime();
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Время синхронизировано через WEB или NTP: " + (String)unixTime);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
else if (syncStatus == SoftRTC_SYNC_STATUS_FROM_BROWSER)
|
||||||
|
{
|
||||||
|
unixTime = getSystemTime();
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Время синхронизировано через Браузер: " + (String)unixTime);
|
||||||
|
}
|
||||||
|
else if (syncStatus == SoftRTC_SYNC_STATUS_NTP || syncStatus == SoftRTC_SYNC_STATUS_NTP_JUST)
|
||||||
|
{
|
||||||
|
unixTime = getSystemTime();
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Время синхронизировано через NTP: " + (String)unixTime);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
else if (time(nullptr) > 100000)
|
||||||
|
{
|
||||||
|
syncStatus = SoftRTC_SYNC_STATUS_BEFORE_SoftRTC;
|
||||||
|
unixTime = getSystemTime();
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Время синхронизировано через NTP до загрузки SoftRTC: " + (String)unixTime);
|
||||||
|
}
|
||||||
|
else if (syncStatus == SoftRTC_SYNC_STATUS_NOT_SET)
|
||||||
|
{
|
||||||
|
valuesFlashJson = readFile(F("values.json"), 4096);
|
||||||
|
valuesFlashJson.replace("\r\n", "");
|
||||||
|
String valAsStr = "";
|
||||||
|
if (jsonRead(valuesFlashJson, _id, valAsStr, _debug))
|
||||||
|
{
|
||||||
|
recovered_unixTime = valAsStr.toInt();
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Время из энергонезависимой памяти: " + (String)recovered_unixTime);
|
||||||
|
|
||||||
|
time_t nextSecond = millis() / 1000 + 1;
|
||||||
|
while (millis() / 1000 < nextSecond)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
unixTime = recovered_unixTime + millis() / 1000 + interval / 2;
|
||||||
|
|
||||||
|
struct timeval now = {.tv_sec = unixTime, .tv_usec = 0};
|
||||||
|
settimeofday(&now, NULL);
|
||||||
|
|
||||||
|
syncStatus = SoftRTC_SYNC_STATUS_RESTORED;
|
||||||
|
|
||||||
|
String localDateTime = formatUnixTime(unixTime + _timezone * 60 * 60);
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Установлено восстановленное время: " + (String)unixTime + " LT: " + localDateTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// syncStatus = SoftRTC_SYNC_STATUS_COULD_NOT_SET;
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Время не может быть восстановлено из памяти");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Время не установлено");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unixTime)
|
||||||
|
{
|
||||||
|
lastUnixTime = unixTime;
|
||||||
|
lastUnixTimeMillis = millis();
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "Сохраняем время: " + (String)unixTime);
|
||||||
|
value.isDecimal = false;
|
||||||
|
value.valS = (String)unixTime;
|
||||||
|
regEvent(value.valS, F("SoftRTC"), _debug, _ticker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onModuleOrder(String &key, String &value)
|
||||||
|
{
|
||||||
|
if (key == "setUTime")
|
||||||
|
{
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("i", F("SoftRTC"), "Устанавливаем время: " + value);
|
||||||
|
char *stopstring;
|
||||||
|
time_t ut = strtoul(value.c_str(), &stopstring, 10);
|
||||||
|
|
||||||
|
struct timeval now = {.tv_sec = ut, .tv_usec = 0};
|
||||||
|
settimeofday(&now, NULL);
|
||||||
|
|
||||||
|
syncStatus = SoftRTC_SYNC_STATUS_MANUAL;
|
||||||
|
|
||||||
|
String localDateTime = formatUnixTime(getSystemTime() + _timezone * 60 * 60);
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "LT: " + localDateTime);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = (String)ut;
|
||||||
|
regEvent(valTmp.valS, F("SoftRTC"), _debug, _ticker);
|
||||||
|
}
|
||||||
|
else if (key == "setSysTime")
|
||||||
|
{
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("i", F("SoftRTC"), "Устанавливаем системное время: " + value);
|
||||||
|
|
||||||
|
time_t ut = getUnixTimeFromString(value) - _timezone * 60 * 60;
|
||||||
|
|
||||||
|
struct timeval now = {.tv_sec = ut, .tv_usec = 0};
|
||||||
|
settimeofday(&now, NULL);
|
||||||
|
|
||||||
|
String localDateTime = formatUnixTime(getSystemTime() + _timezone * 60 * 60);
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), (String)ut + " LT: " + localDateTime);
|
||||||
|
|
||||||
|
syncStatus = SoftRTC_SYNC_STATUS_MANUAL;
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = (String)ut;
|
||||||
|
regEvent(valTmp.valS, F("SoftRTC"), _debug, _ticker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
||||||
|
{
|
||||||
|
if (command == "checkForSummer")
|
||||||
|
{
|
||||||
|
unixTime = getSystemTime();
|
||||||
|
bool isDST = isDSTfunc(unixTime);
|
||||||
|
|
||||||
|
int newTimeZone = isDST ? _summerTime : _winterTime;
|
||||||
|
|
||||||
|
if (newTimeZone != _timezone)
|
||||||
|
{
|
||||||
|
jsonWriteInt_(settingsFlashJson, F("timezone"), isDST ? _summerTime : _winterTime);
|
||||||
|
syncSettingsFlashJson();
|
||||||
|
_timezone = newTimeZone;
|
||||||
|
|
||||||
|
if (newTimeZone == _summerTime)
|
||||||
|
{
|
||||||
|
SerialPrint("i", F("SoftRTC"), "Перешли на летнее время");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("i", F("SoftRTC"), "Перешли на зименее время");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
else if (command == "getTime")
|
||||||
|
{
|
||||||
|
String localDateTime = formatUnixTime(getSystemTime() + _timezone * 60 * 60);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = localDateTime;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "getUTShort")
|
||||||
|
{
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
unixTimeShort = getSystemTime() - START_DATETIME;
|
||||||
|
valTmp.valD = unixTimeShort;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "setUnixTime")
|
||||||
|
{
|
||||||
|
if (param.size() == 1)
|
||||||
|
{
|
||||||
|
time_t ut = strtoul(param[0].valS.c_str(), nullptr, 10);
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("i", F("SoftRTC"), "Устанавливаем время UT: " + (String)ut);
|
||||||
|
|
||||||
|
struct timeval now = {.tv_sec = ut, .tv_usec = 0};
|
||||||
|
settimeofday(&now, NULL);
|
||||||
|
|
||||||
|
String localDateTime = formatUnixTime(ut + _timezone * 60 * 60);
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "LT: " + localDateTime);
|
||||||
|
syncStatus = SoftRTC_SYNC_STATUS_MANUAL;
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = (String)ut;
|
||||||
|
regEvent(valTmp.valS, F("SoftRTC"), _debug, _ticker);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (command == "setTimeFromYMDHMS")
|
||||||
|
{
|
||||||
|
if (param.size() == 6)
|
||||||
|
{
|
||||||
|
time_t ut = getUnixTimeFromYMDHMS(param[0].valD, param[1].valD, param[2].valD,
|
||||||
|
param[3].valD, param[4].valD, param[5].valD);
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("i", F("SoftRTC"), "Устанавливаем время YMDHMS: " + (String)ut);
|
||||||
|
|
||||||
|
struct timeval now = {.tv_sec = ut, .tv_usec = 0};
|
||||||
|
settimeofday(&now, NULL);
|
||||||
|
|
||||||
|
String localDateTime = formatUnixTime(ut + _timezone * 60 * 60);
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("I", F("SoftRTC"), "LT: " + localDateTime);
|
||||||
|
syncStatus = SoftRTC_SYNC_STATUS_MANUAL;
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = (String)ut;
|
||||||
|
regEvent(valTmp.valS, F("SoftRTC"), _debug, _ticker);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (command == "lastNTPtimeCorrection")
|
||||||
|
{
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = lastNTPtimeCorrection;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "getWeekNumber")
|
||||||
|
{
|
||||||
|
time_t ut = getSystemTime() + _timezone * 60 * 60; // учитываем локальное смещение
|
||||||
|
int week = getWeekOfYear(ut);
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = week;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "getDayOfWeek")
|
||||||
|
{
|
||||||
|
time_t ut = getSystemTime() + _timezone * 60 * 60; // учёт смещения
|
||||||
|
int dayNum = getDayOfWeekNumber(ut);
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = dayNum;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "getYear")
|
||||||
|
{
|
||||||
|
time_t ut = getSystemTime() + _timezone * 60 * 60; // локальное смещение
|
||||||
|
struct tm *timeInfo = localtime(&ut);
|
||||||
|
int year = timeInfo->tm_year + 1900; // полный (4‑значный) год
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true; // возвращаем число
|
||||||
|
valTmp.valD = year;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "getDayOfYear")
|
||||||
|
{
|
||||||
|
time_t ut = getSystemTime() + _timezone * 60 * 60;
|
||||||
|
struct tm *timeInfo = localtime(&ut);
|
||||||
|
int dayOfYear = timeInfo->tm_yday + 1; // tm_yday начинается с 0
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = dayOfYear;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "getDaysInMonth")
|
||||||
|
{
|
||||||
|
time_t ut = getSystemTime() + _timezone * 60 * 60;
|
||||||
|
int days = getDaysInMonth(ut);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = days;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "getOrthodoxEaster")
|
||||||
|
{
|
||||||
|
time_t ut = getSystemTime() + _timezone * 60 * 60;
|
||||||
|
struct tm *t = localtime(&ut);
|
||||||
|
int day, month;
|
||||||
|
getOrthodoxEasterDate(t->tm_year + 1900, day, month);
|
||||||
|
|
||||||
|
char buf[12];
|
||||||
|
snprintf(buf, sizeof(buf), "%02d.%02d.%d", day, month, t->tm_year + 1900);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = String(buf);
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "isOrthodoxEaster")
|
||||||
|
{
|
||||||
|
time_t ut = getSystemTime() + _timezone * 60 * 60;
|
||||||
|
struct tm *now = localtime(&ut);
|
||||||
|
|
||||||
|
int dEaster, mEaster;
|
||||||
|
getOrthodoxEasterDate(now->tm_year + 1900, dEaster, mEaster);
|
||||||
|
|
||||||
|
bool todayIsEaster = (now->tm_mday == dEaster) && (now->tm_mon + 1 == mEaster);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = todayIsEaster ? 1 : 0;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
~SoftRTC() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SoftRTCsyncStatus : public IoTItem
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
int lastSyncStatus = -1;
|
||||||
|
bool _debug = false;
|
||||||
|
bool _ticker = true;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SoftRTCsyncStatus(String parameters) : IoTItem(parameters)
|
||||||
|
{
|
||||||
|
_round = 0;
|
||||||
|
jsonRead(parameters, F("ticker"), _ticker);
|
||||||
|
jsonRead(parameters, F("debug"), _debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if (syncStatus != lastSyncStatus)
|
||||||
|
{
|
||||||
|
lastSyncStatus = syncStatus;
|
||||||
|
if (_debug)
|
||||||
|
SerialPrint("i", F("SoftRTC"), "Статус синхронизации: " + (String)lastSyncStatus);
|
||||||
|
value.isDecimal = true;
|
||||||
|
value.valD = lastSyncStatus;
|
||||||
|
regEvent(value.valD, F("SoftRTCsyncStatus"), _debug, _ticker);
|
||||||
|
}
|
||||||
|
IoTItem::loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
~SoftRTCsyncStatus() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
void *getAPI_SoftRTC(String subtype, String param)
|
||||||
|
{
|
||||||
|
if (subtype == F("SoftRTC"))
|
||||||
|
{
|
||||||
|
return new SoftRTC(param);
|
||||||
|
}
|
||||||
|
else if (subtype == F("SoftRTCsyncStatus"))
|
||||||
|
{
|
||||||
|
return new SoftRTCsyncStatus(param);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
264
src/modules/sensors/SoftRTC/export .json
Normal file
264
src/modules/sensors/SoftRTC/export .json
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
{
|
||||||
|
"mark": "iotm",
|
||||||
|
"config": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutTime",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Time",
|
||||||
|
"descr": "Local Time",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Writing",
|
||||||
|
"subtype": "Timer",
|
||||||
|
"id": "timer1",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Timers",
|
||||||
|
"descr": "Timer",
|
||||||
|
"int": 1,
|
||||||
|
"countDown": "15",
|
||||||
|
"ticker": "0",
|
||||||
|
"repeat": 1,
|
||||||
|
"needSave": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutNTPtimeCorrection",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Time",
|
||||||
|
"descr": "Last NTP Time Correction",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutSyncStatus",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Time",
|
||||||
|
"descr": "SoftRTC Sync Status",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "voutTimeSoft",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Time",
|
||||||
|
"descr": "Local Time Soft",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "SoftRTCsyncStatus",
|
||||||
|
"id": "softRTCsyncStatus",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Time",
|
||||||
|
"descr": "SoftRTC Sync Status",
|
||||||
|
"ticker": "1",
|
||||||
|
"int": 10,
|
||||||
|
"round": 0,
|
||||||
|
"debug": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "SoftRTC",
|
||||||
|
"id": "softRTC",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Timers",
|
||||||
|
"descr": "UT softRTC",
|
||||||
|
"winterTime": 2,
|
||||||
|
"summerTime": 3,
|
||||||
|
"ticker": 0,
|
||||||
|
"int": 10,
|
||||||
|
"debug": 1,
|
||||||
|
"btn-setUTime": "0",
|
||||||
|
"btn-setSysTime": "d-m-y h:m:s",
|
||||||
|
"moduleName": "SoftRTC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "dayOfWeek",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Days",
|
||||||
|
"descr": "Day of Week",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "weekNumber",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Days",
|
||||||
|
"descr": " Week Number",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "dayOfWeekT",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Days",
|
||||||
|
"descr": "Day of Week",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "getDayOfYear",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Days",
|
||||||
|
"descr": "Day of Year",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "getDaysInMonth",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Days",
|
||||||
|
"descr": "Days in Month",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "getOrthodoxEaster",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Days",
|
||||||
|
"descr": "Orthodox Easter",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "VButton",
|
||||||
|
"id": "isOrthodoxEasterB",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "Days",
|
||||||
|
"descr": "is Orthodox Easter",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0",
|
||||||
|
"moduleName": "VButton"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario=>
|
||||||
|
|
||||||
|
|
||||||
|
if timer1 == 0 then {
|
||||||
|
voutTime = getTime();# не сработает если время не обновлено
|
||||||
|
voutTimeSoft = softRTC.getTime();
|
||||||
|
dayOfWeek = softRTC.getDayOfWeek()
|
||||||
|
voutNTPtimeCorrection = softRTC.lastNTPtimeCorrection()
|
||||||
|
weekNumber = softRTC.getWeekNumber()
|
||||||
|
getDayOfYear = softRTC.getDayOfYear()
|
||||||
|
getDaysInMonth = softRTC.getDaysInMonth()
|
||||||
|
getOrthodoxEaster = softRTC.getOrthodoxEaster()
|
||||||
|
isOrthodoxEasterB = softRTC.isOrthodoxEaster()
|
||||||
|
}
|
||||||
|
|
||||||
|
if dayOfWeek == 1 then dayOfWeekT = "Monday"
|
||||||
|
if dayOfWeek == 2 then dayOfWeekT = "Tuesday"
|
||||||
|
if dayOfWeek == 3 then dayOfWeekT = "Wednesday"
|
||||||
|
if dayOfWeek == 4 then dayOfWeekT = "Thursday"
|
||||||
|
if dayOfWeek == 5 then dayOfWeekT = "Friday"
|
||||||
|
if dayOfWeek == 6 then dayOfWeekT = "Saturday"
|
||||||
|
if dayOfWeek == 7 then dayOfWeekT = "Sunday"
|
||||||
|
|
||||||
|
if softRTCsyncStatus == 0 then voutSyncStatus = "Not set";
|
||||||
|
if softRTCsyncStatus == 1 then voutSyncStatus = "Set before SoftRTC";
|
||||||
|
if softRTCsyncStatus == 2 then voutSyncStatus = "Restored";
|
||||||
|
if softRTCsyncStatus == 3 then voutSyncStatus = "Set manually";
|
||||||
|
if softRTCsyncStatus == 4 then voutSyncStatus = "Set with NTP or Browser";
|
||||||
|
if softRTCsyncStatus == 5 then voutSyncStatus = "Set with Browser";
|
||||||
|
if softRTCsyncStatus == 6 then voutSyncStatus = "Just set with NTP";
|
||||||
|
if softRTCsyncStatus == 7 then voutSyncStatus = "Set with NTP";
|
||||||
|
|
||||||
|
if softRTC then {voutNTPtimeCorrection = softRTC.lastNTPtimeCorrection()
|
||||||
|
}
|
||||||
|
|
||||||
161
src/modules/sensors/SoftRTC/modinfo.json
Normal file
161
src/modules/sensors/SoftRTC/modinfo.json
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
{
|
||||||
|
"menuSection": "sensors",
|
||||||
|
"configItem": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"name": "Часы реального времени (программные)",
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "SoftRTC",
|
||||||
|
"id": "softRTC",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Таймеры",
|
||||||
|
"descr": "UT softRTC",
|
||||||
|
"winterTime": 2,
|
||||||
|
"summerTime": 3,
|
||||||
|
"ticker": 0,
|
||||||
|
"int": 10,
|
||||||
|
"debug": 1,
|
||||||
|
"btn-setUTime": "0",
|
||||||
|
"btn-setSysTime": "d-m-y h:m:s"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"name": "Часы реального времени (программные) Статус",
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "SoftRTCsyncStatus",
|
||||||
|
"id": "softRTCsyncStatus",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Таймеры",
|
||||||
|
"descr": "softRTC Sync Status",
|
||||||
|
"ticker": 1,
|
||||||
|
"debug": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"about": {
|
||||||
|
"authorName": "Alex K",
|
||||||
|
"authorContact": "https://t.me/cmche",
|
||||||
|
"authorGit": "https://github.com/CHE77/IoTManager-Modules",
|
||||||
|
"exampleURL": "https://iotmanager.org/wiki",
|
||||||
|
"specialThanks": "",
|
||||||
|
"moduleName": "SoftRTC",
|
||||||
|
"moduleVersion": "1.0",
|
||||||
|
"usedRam": {
|
||||||
|
"esp32_4mb": 15,
|
||||||
|
"esp8266_4mb": 15
|
||||||
|
},
|
||||||
|
"title": "Часы реального времени программные",
|
||||||
|
"moduleDesc": "Позволяет хранить время в энергонезависимой памяти.",
|
||||||
|
"license": "Cooperative Non-Violent Public License (CNPL)",
|
||||||
|
"propInfo": {
|
||||||
|
"ticker": "Генерировать(1) или нет(0) события при каждом тике часов (каждые int секунд).",
|
||||||
|
"debug": "Вывод отладочной информации",
|
||||||
|
"int": "Количество секунд между сохраниеями времени. Желательно чётное.",
|
||||||
|
"btn-setUTime": "Кнопка установки времени на основе указанного unixtime",
|
||||||
|
"btn-setSysTime": "Кнопка установки времени на основе указанного в формате d-m-y h:m:s или y-m-d h:m:s"
|
||||||
|
},
|
||||||
|
"retInfo": "Содержит сохраненное Время в энергонезависимую память",
|
||||||
|
"funcInfo": [
|
||||||
|
{
|
||||||
|
"name": "getTime",
|
||||||
|
"descr": "Получить строковое значение времени по указанному формату.",
|
||||||
|
"params": [
|
||||||
|
"SoftRTC.getTime()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getUTShort",
|
||||||
|
"descr": "Получить строковое значение времени по указанному формату.",
|
||||||
|
"params": [
|
||||||
|
"SoftRTC.getUTShort()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setUnixTime",
|
||||||
|
"descr": "Установить время через сценарий в формате юникстайм",
|
||||||
|
"params": [
|
||||||
|
"SoftRTC.setUnixTime(1743087041) - параметр в виде числа или строки"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setTimeFromYMDHMS",
|
||||||
|
"descr": "Установить время через сценарий в формате YYYY,MM,DD,HH,MM,SS",
|
||||||
|
"params": [
|
||||||
|
"softRTC.setTimeFromYMDHMS(2025,01,01,12,30,30) "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lastNTPtimeCorrection",
|
||||||
|
"descr": "Вывести поправку после синхронизации по NTP",
|
||||||
|
"params": [
|
||||||
|
" softRTC.lastNTPtimeCorrection() "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getWeekNumber",
|
||||||
|
"descr": "Вывести номер недели в году",
|
||||||
|
"params": [
|
||||||
|
" softRTC.getWeekNumber() "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getDayOfWeek",
|
||||||
|
"descr": "Вывести день недели. Воскресенье (0) → 7",
|
||||||
|
"params": [
|
||||||
|
" softRTC.getDayOfWeek() "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getYear",
|
||||||
|
"descr": "Вывести год",
|
||||||
|
"params": [
|
||||||
|
" softRTC.getYear() "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getDayOfYear",
|
||||||
|
"descr": "Вывести день в году",
|
||||||
|
"params": [
|
||||||
|
" softRTC.getDayOfYear() "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getOrthodoxEaster",
|
||||||
|
"descr": "Вывести дату Пасхи в текущем году",
|
||||||
|
"params": [
|
||||||
|
" softRTC.getOrthodoxEaster() "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isOrthodoxEaster",
|
||||||
|
"descr": "уже Пасха?",
|
||||||
|
"params": [
|
||||||
|
" softRTC.isOrthodoxEaster() "
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
,
|
||||||
|
"title2": "Часы реального времени программные Статус",
|
||||||
|
"moduleDesc2": "Позволяет получать статус синхроницации времени",
|
||||||
|
"propInfo2": {
|
||||||
|
"ticker": "Генерировать(1) или нет(0) события при каждом изменении статуса.",
|
||||||
|
"debug": "Вывод отладочной информации"
|
||||||
|
},
|
||||||
|
"retInfo2": "Содержит статус синхроницации времени",
|
||||||
|
"Status": {
|
||||||
|
"0": "Время не установлено",
|
||||||
|
"1": "Время было установлено до SoftRTC",
|
||||||
|
"2": "Время восстановлено из памяти",
|
||||||
|
"3": "Время установлено вручную",
|
||||||
|
"4": "Время синхронизировано через NTP или через Браузер",
|
||||||
|
"5": "Время синхронизировано через Браузер",
|
||||||
|
"6": "Время только что синхронизировано через NTP",
|
||||||
|
"7": "Время синхронизировано через NTP"
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
"defActive": false,
|
||||||
|
"usedLibs": {
|
||||||
|
"esp32*": [],
|
||||||
|
"esp82*": []
|
||||||
|
}
|
||||||
|
}
|
||||||
488
src/modules/virtual/SolarCalc/SolarCalc.cpp
Normal file
488
src/modules/virtual/SolarCalc/SolarCalc.cpp
Normal file
@@ -0,0 +1,488 @@
|
|||||||
|
// Licensed under the Cooperative Non-Violent Public License (CNPL)
|
||||||
|
// See: https://github.com/CHE77/IoTManager-Modules/blob/main/LICENSE
|
||||||
|
|
||||||
|
#include "Global.h"
|
||||||
|
#include "classes/IoTItem.h"
|
||||||
|
// #include <ctime>
|
||||||
|
#include <SolarCalculator.h>
|
||||||
|
#include "NTP.h"
|
||||||
|
|
||||||
|
class SolarCalculator : public IoTItem
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
float _lat = 0;
|
||||||
|
float _long = 0;
|
||||||
|
|
||||||
|
int year = 0;
|
||||||
|
int month = 0;
|
||||||
|
int day = 0;
|
||||||
|
int hour = 0;
|
||||||
|
int minute = 0;
|
||||||
|
int second = 0;
|
||||||
|
|
||||||
|
unsigned long _sunriseTime = 0;
|
||||||
|
unsigned long _transitTime = 0;
|
||||||
|
unsigned long _sunsetTime = 0;
|
||||||
|
|
||||||
|
String _parameter = "";
|
||||||
|
bool _ticker = false;
|
||||||
|
bool _debug = false;
|
||||||
|
|
||||||
|
// Rounded HH:mm format
|
||||||
|
char *hoursToString(double h, char *str)
|
||||||
|
{
|
||||||
|
int m = int(round(h * 60));
|
||||||
|
int hr = (m / 60) % 24;
|
||||||
|
int mn = m % 60;
|
||||||
|
|
||||||
|
str[0] = (hr / 10) % 10 + '0';
|
||||||
|
str[1] = (hr % 10) + '0';
|
||||||
|
str[2] = ':';
|
||||||
|
str[3] = (mn / 10) % 10 + '0';
|
||||||
|
str[4] = (mn % 10) + '0';
|
||||||
|
str[5] = '\0';
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
SolarCalculator(String parameters) : IoTItem(parameters)
|
||||||
|
{
|
||||||
|
jsonRead(parameters, "lat", _lat);
|
||||||
|
jsonRead(parameters, "lon", _long);
|
||||||
|
_parameter = jsonReadStr(parameters, "parameter");
|
||||||
|
jsonRead(parameters, F("ticker"), _ticker);
|
||||||
|
jsonRead(parameters, F("debug"), _debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getTime()
|
||||||
|
{
|
||||||
|
unixTime = getSystemTime();
|
||||||
|
breakEpochToTime(unixTime, _time_utc);
|
||||||
|
|
||||||
|
day = _time_utc.day_of_month;
|
||||||
|
month = _time_utc.month;
|
||||||
|
year = _time_utc.year + 2000; // 0-25
|
||||||
|
|
||||||
|
hour = _time_utc.hour;
|
||||||
|
minute = _time_utc.minute;
|
||||||
|
second = _time_utc.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sunArcFromTransit(double longitude, double &sunArc)
|
||||||
|
{
|
||||||
|
JulianDay jd(year, month, day, hour, minute, second);
|
||||||
|
double T = calcJulianCent(jd);
|
||||||
|
|
||||||
|
// Получаем экваториальные координаты Солнца
|
||||||
|
double ra, dec;
|
||||||
|
calcSolarCoordinates(T, ra, dec); // ra в градусах, dec тоже
|
||||||
|
|
||||||
|
double GMST = calcGrMeanSiderealTime(jd);
|
||||||
|
double ghaSun = wrapTo360(GMST - ra);
|
||||||
|
|
||||||
|
// LHA = GHA + долгота наблюдателя (восточная долгота положительная)
|
||||||
|
double lha = wrapTo180(ghaSun + longitude);
|
||||||
|
|
||||||
|
// Угол от транзита: 0° — транзит, 90° — 6 часов позже и т.д.
|
||||||
|
sunArc = lha;
|
||||||
|
}
|
||||||
|
|
||||||
|
void doByInterval()
|
||||||
|
{
|
||||||
|
if (isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
JulianDay jd(year, month, day, hour, minute, second);
|
||||||
|
|
||||||
|
double azimuth, elevation;
|
||||||
|
calcHorizontalCoordinates(jd, _lat, _long, azimuth, elevation);
|
||||||
|
|
||||||
|
value.isDecimal = true;
|
||||||
|
if (_parameter == "azimuth")
|
||||||
|
{
|
||||||
|
value.valD = azimuth;
|
||||||
|
}
|
||||||
|
else if (_parameter == "elevation")
|
||||||
|
{
|
||||||
|
value.valD = elevation;
|
||||||
|
}
|
||||||
|
else if (_parameter == "sunArcFromTransit")
|
||||||
|
{
|
||||||
|
double sunArc;
|
||||||
|
sunArcFromTransit(_long, sunArc);
|
||||||
|
value.valD = sunArc;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), _parameter + " is not correct parameter!!!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
regEvent(value.valD, F("SoftRTC"), _debug, _ticker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
||||||
|
{
|
||||||
|
if (command == "sunrise")
|
||||||
|
{
|
||||||
|
if (param.size() == 0 && isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
}
|
||||||
|
else if (param.size() == 3 && param[0].isDecimal && param[1].isDecimal && param[2].isDecimal)
|
||||||
|
{
|
||||||
|
year = param[2].valD;
|
||||||
|
month = param[1].valD;
|
||||||
|
day = param[0].valD;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), "Wrong parameter!s or time is not synched!!", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
double transit, sunrise, sunset;
|
||||||
|
calcSunriseSunset(year, month, day, _lat, _long, transit, sunrise, sunset);
|
||||||
|
|
||||||
|
int utc_offset = jsonReadInt(settingsFlashJson, F("timezone"));
|
||||||
|
|
||||||
|
char str[6];
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = hoursToString(sunrise + utc_offset, str);
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "transit")
|
||||||
|
{
|
||||||
|
if (param.size() == 0 && isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
}
|
||||||
|
else if (param.size() == 3 && param[0].isDecimal && param[1].isDecimal && param[2].isDecimal)
|
||||||
|
{
|
||||||
|
year = param[2].valD;
|
||||||
|
month = param[1].valD;
|
||||||
|
day = param[0].valD;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), "Wrong parameter!s or time is not synched!!", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
double transit, sunrise, sunset;
|
||||||
|
calcSunriseSunset(year, month, day, _lat, _long, transit, sunrise, sunset);
|
||||||
|
|
||||||
|
int utc_offset = jsonReadInt(settingsFlashJson, F("timezone"));
|
||||||
|
|
||||||
|
char str[6];
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = hoursToString(transit + utc_offset, str);
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "sunset")
|
||||||
|
{
|
||||||
|
|
||||||
|
if (param.size() == 0 && isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
}
|
||||||
|
else if (param.size() == 3 && param[0].isDecimal && param[1].isDecimal && param[2].isDecimal)
|
||||||
|
{
|
||||||
|
day = param[0].valD;
|
||||||
|
month = param[1].valD;
|
||||||
|
year = param[2].valD;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), "Wrong parameter!s or time is not synched!!", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
double transit, sunrise, sunset;
|
||||||
|
calcSunriseSunset(year, month, day, _lat, _long, transit, sunrise, sunset);
|
||||||
|
|
||||||
|
int utc_offset = jsonReadInt(settingsFlashJson, F("timezone"));
|
||||||
|
|
||||||
|
char str[6];
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = false;
|
||||||
|
valTmp.valS = hoursToString(sunset + utc_offset, str);
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "azimuth")
|
||||||
|
{
|
||||||
|
if (param.size() == 0 && isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
}
|
||||||
|
else if (param.size() > 3)
|
||||||
|
{
|
||||||
|
day = param[0].valD;
|
||||||
|
month = param[1].valD;
|
||||||
|
year = param[2].valD;
|
||||||
|
hour = param[3].valD;
|
||||||
|
minute = (param.size() > 4) ? param[4].valD : 0;
|
||||||
|
second = (param.size() > 5) ? param[5].valD : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), "Wrong parameter!s or time is not synched!!", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
JulianDay jd(year, month, day, hour, minute, second);
|
||||||
|
double azimuth, elevation;
|
||||||
|
calcHorizontalCoordinates(jd, _lat, _long, azimuth, elevation);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = azimuth;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "elevation")
|
||||||
|
{
|
||||||
|
if (param.size() == 0 && isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
}
|
||||||
|
else if (param.size() > 3)
|
||||||
|
{
|
||||||
|
day = param[0].valD;
|
||||||
|
month = param[1].valD;
|
||||||
|
year = param[2].valD;
|
||||||
|
hour = param[3].valD;
|
||||||
|
minute = (param.size() > 4) ? param[4].valD : 0;
|
||||||
|
second = (param.size() > 5) ? param[5].valD : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), "Wrong parameter!s or time is not synched!!", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
JulianDay jd(year, month, day, hour, minute, second);
|
||||||
|
double azimuth, elevation;
|
||||||
|
calcHorizontalCoordinates(jd, _lat, _long, azimuth, elevation);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = elevation;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "sunArcFromTransit")
|
||||||
|
{
|
||||||
|
if (param.size() == 0 && isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
}
|
||||||
|
else if (param.size() > 3)
|
||||||
|
{
|
||||||
|
day = param[0].valD;
|
||||||
|
month = param[1].valD;
|
||||||
|
year = param[2].valD;
|
||||||
|
hour = param[3].valD;
|
||||||
|
minute = (param.size() > 4) ? param[4].valD : 0;
|
||||||
|
second = (param.size() > 5) ? param[5].valD : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), "Wrong parameter!s or time is not synched!!", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
double sunArc;
|
||||||
|
sunArcFromTransit(_long, sunArc);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = sunArc;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "jd")
|
||||||
|
{
|
||||||
|
// float hours = 0;
|
||||||
|
if (param.size() == 0 && isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
}
|
||||||
|
else if (param.size() > 3)
|
||||||
|
{
|
||||||
|
day = param[0].valD;
|
||||||
|
month = param[1].valD;
|
||||||
|
year = param[2].valD;
|
||||||
|
hour = param[3].valD;
|
||||||
|
minute = (param.size() > 4) ? param[4].valD : 0;
|
||||||
|
second = (param.size() > 5) ? param[5].valD : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), "Wrong parameters or time is not synched!!", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
JulianDay jd(year, month, day, hour, minute, second);
|
||||||
|
float jdJD = jd.JD;
|
||||||
|
float jdm = jd.m;
|
||||||
|
float jdSum = jdJD + jdm;
|
||||||
|
//SerialPrint("I", F("SolarCalc"), "jdJD = " + String(jdJD));
|
||||||
|
//SerialPrint("I", F("SolarCalc"), "jdm = " + String(jdm));
|
||||||
|
//SerialPrint("I", F("SolarCalc"), "jdSum = " + String(jdSum));
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = jdSum;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "GMST")
|
||||||
|
{
|
||||||
|
if (param.size() == 0 && isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
}
|
||||||
|
else if (param.size() > 3)
|
||||||
|
{
|
||||||
|
day = param[0].valD;
|
||||||
|
month = param[1].valD;
|
||||||
|
year = param[2].valD;
|
||||||
|
hour = param[3].valD;
|
||||||
|
minute = (param.size() > 4) ? param[4].valD : 0;
|
||||||
|
second = (param.size() > 5) ? param[5].valD : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), "Wrong parameters or time is not synched!!", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
JulianDay jd(year, month, day, hour, minute, second);
|
||||||
|
double GMST = calcGrMeanSiderealTime(jd);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = GMST;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "LST")
|
||||||
|
{
|
||||||
|
if (param.size() == 0 && isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
}
|
||||||
|
else if (param.size() > 3)
|
||||||
|
{
|
||||||
|
day = param[0].valD;
|
||||||
|
month = param[1].valD;
|
||||||
|
year = param[2].valD;
|
||||||
|
hour = param[3].valD;
|
||||||
|
minute = (param.size() > 4) ? param[4].valD : 0;
|
||||||
|
second = (param.size() > 5) ? param[5].valD : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), "Wrong parameters or time is not synched!!", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
JulianDay jd(year, month, day, hour, minute, second);
|
||||||
|
double GMST = calcGrMeanSiderealTime(jd);
|
||||||
|
double LST = wrapTo360(GMST + _long);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = LST;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "ra")
|
||||||
|
{
|
||||||
|
if (param.size() == 0 && isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
}
|
||||||
|
else if (param.size() > 3)
|
||||||
|
{
|
||||||
|
day = param[0].valD;
|
||||||
|
month = param[1].valD;
|
||||||
|
year = param[2].valD;
|
||||||
|
hour = param[3].valD;
|
||||||
|
minute = (param.size() > 4) ? param[4].valD : 0;
|
||||||
|
second = (param.size() > 5) ? param[5].valD : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), "Wrong parameters or time is not synched!!", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
JulianDay jd(year, month, day, hour, minute, second);
|
||||||
|
double T = calcJulianCent(jd);
|
||||||
|
double ra, dec;
|
||||||
|
calcSolarCoordinates(T, ra, dec);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = ra;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "dec")
|
||||||
|
{
|
||||||
|
if (param.size() == 0 && isTimeSynch)
|
||||||
|
{
|
||||||
|
getTime();
|
||||||
|
}
|
||||||
|
else if (param.size() > 3)
|
||||||
|
{
|
||||||
|
day = param[0].valD;
|
||||||
|
month = param[1].valD;
|
||||||
|
year = param[2].valD;
|
||||||
|
hour = param[3].valD;
|
||||||
|
minute = (param.size() > 4) ? param[4].valD : 0;
|
||||||
|
second = (param.size() > 5) ? param[5].valD : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerialPrint("E", F("SolarCalculator"), "Wrong parameters or time is not synched!!", _id);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
JulianDay jd(year, month, day, hour, minute, second);
|
||||||
|
double T = calcJulianCent(jd);
|
||||||
|
double ra, dec;
|
||||||
|
calcSolarCoordinates(T, ra, dec);
|
||||||
|
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = dec;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "getHour" && param.size() == 1)
|
||||||
|
{ // получаем час из какого-то элемента и переводим его в UTC
|
||||||
|
|
||||||
|
int h = selectToMarker(param[0].valS, ":").toInt();
|
||||||
|
int utc_offset = jsonReadInt(settingsFlashJson, F("timezone"));
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = h - utc_offset;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else if (command == "getMinute" && param.size() == 1)
|
||||||
|
{ // получаем минуты из какого-то элемента и переводим его в UTC
|
||||||
|
int min = selectToMarkerLast(param[0].valS, ":").toInt();
|
||||||
|
IoTValue valTmp;
|
||||||
|
valTmp.isDecimal = true;
|
||||||
|
valTmp.valD = min;
|
||||||
|
return valTmp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SerialPrint("E", F("SolarCalculator"), F("Unknown command or wrong parameters."));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void *getAPI_SolarCalculator(String subtype, String param)
|
||||||
|
{
|
||||||
|
if (subtype == F("SolarCalculator"))
|
||||||
|
{
|
||||||
|
return new SolarCalculator(param);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
BIN
src/modules/virtual/SolarCalc/coordinates.jpg
Normal file
BIN
src/modules/virtual/SolarCalc/coordinates.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 220 KiB |
455
src/modules/virtual/SolarCalc/export.json
Normal file
455
src/modules/virtual/SolarCalc/export.json
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
{
|
||||||
|
"mark": "iotm",
|
||||||
|
"config": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "VButton",
|
||||||
|
"id": "CalculateBtn",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "Sun",
|
||||||
|
"descr": "Calculate",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "RiseTimeToday",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataTm",
|
||||||
|
"page": "Sun rise",
|
||||||
|
"descr": " sunrise today",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "azimuth",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "horizontal coordinates",
|
||||||
|
"descr": " azimuth",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "elevationTransit",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Sun transit",
|
||||||
|
"descr": "elevation at Transit",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "elevation",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "horizontal coordinates",
|
||||||
|
"descr": " elevation",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "SetTimeToday",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataTm",
|
||||||
|
"page": "Sun set",
|
||||||
|
"descr": " sunset today",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "TransitTimeToday",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataTm",
|
||||||
|
"page": "Sun transit",
|
||||||
|
"descr": " transit today",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "hourRiseToday",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Sun rise",
|
||||||
|
"descr": "Sun rise UTC hours",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "minuteRiseToday",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Sun rise",
|
||||||
|
"descr": "Sun rise UTC minutes",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "1024,1024,1,100",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": 0,
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "azimuthRise",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Sun rise",
|
||||||
|
"descr": "azimuth at sunrise",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "azimuthTransit",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Sun transit",
|
||||||
|
"descr": "azimuth at transit",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "azimuthSet",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Sun set",
|
||||||
|
"descr": "azimuth at sunset",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "VButton",
|
||||||
|
"id": "isDayTime",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "toggle",
|
||||||
|
"page": "Sun",
|
||||||
|
"descr": "is Day Time",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0",
|
||||||
|
"moduleName": "VButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "SolarCalculator",
|
||||||
|
"id": "Sun",
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "by Interval",
|
||||||
|
"descr": "Azimuth",
|
||||||
|
"lat": "50.118",
|
||||||
|
"lon": "-005.478",
|
||||||
|
"parameter": "azimuth",
|
||||||
|
"int": "60",
|
||||||
|
"ticker": "1",
|
||||||
|
"debug": 1,
|
||||||
|
"moduleName": "SolarCalculator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "sunArcFromTransit",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Sun transit",
|
||||||
|
"descr": "Sun Arc From Transit",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "jd",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Raw astro data",
|
||||||
|
"descr": "jd",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "2",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "GMST",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Raw astro data",
|
||||||
|
"descr": " GMST",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "LST",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Raw astro data",
|
||||||
|
"descr": " LST",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "ra",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "equatorial coordinates",
|
||||||
|
"descr": "ra",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "dec",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "equatorial coordinates",
|
||||||
|
"descr": "dec",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "GHA",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Raw astro data",
|
||||||
|
"descr": " GHA",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "Variable",
|
||||||
|
"id": "LHA",
|
||||||
|
"needSave": 0,
|
||||||
|
"widget": "anydataСorner",
|
||||||
|
"page": "Raw astro data",
|
||||||
|
"descr": "LHA",
|
||||||
|
"int": "0",
|
||||||
|
"val": "0.0",
|
||||||
|
"map": "",
|
||||||
|
"plus": 0,
|
||||||
|
"multiply": 1,
|
||||||
|
"round": "1",
|
||||||
|
"moduleName": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Writing",
|
||||||
|
"subtype": "Timer",
|
||||||
|
"id": "timerMinute",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Timers",
|
||||||
|
"descr": "Timer 1 minite",
|
||||||
|
"int": 1,
|
||||||
|
"countDown": "60",
|
||||||
|
"ticker": "0",
|
||||||
|
"repeat": 1,
|
||||||
|
"needSave": 0,
|
||||||
|
"moduleName": "Timer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Writing",
|
||||||
|
"subtype": "Timer",
|
||||||
|
"id": "timerDay",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Timers",
|
||||||
|
"descr": "Timer 1 Day",
|
||||||
|
"int": 1,
|
||||||
|
"countDown": "86400",
|
||||||
|
"ticker": "0",
|
||||||
|
"repeat": "1",
|
||||||
|
"needSave": 0,
|
||||||
|
"moduleName": "Timer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"type": "Writing",
|
||||||
|
"subtype": "Cron",
|
||||||
|
"id": "cronMidnight",
|
||||||
|
"widget": "anydataDef",
|
||||||
|
"page": "Timers",
|
||||||
|
"descr": "Cron midnight",
|
||||||
|
"int": 1,
|
||||||
|
"val": "1 0 0 * * *",
|
||||||
|
"formatNextAlarm": "%H:%M:%S",
|
||||||
|
"needSave": 0,
|
||||||
|
"moduleName": "Cron"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario=>if timerDay == 0 | CalculateBtn == 1 then {
|
||||||
|
RiseTimeToday = Sun.sunrise()
|
||||||
|
TransitTimeToday = Sun.transit()
|
||||||
|
SetTimeToday = Sun.sunset()
|
||||||
|
|
||||||
|
hourRiseToday = Sun.getHour(RiseTimeToday)
|
||||||
|
minuteRiseToday = Sun.getMinute(RiseTimeToday)
|
||||||
|
|
||||||
|
if RiseTimeToday then azimuthRise = Sun.azimuth(getDay(),getMonth(),2025, Sun.getHour(RiseTimeToday) ,Sun.getMinute(RiseTimeToday))
|
||||||
|
|
||||||
|
if TransitTimeToday then {azimuthTransit = Sun.azimuth(getDay(),getMonth(),2025, Sun.getHour(TransitTimeToday),Sun.getMinute(TransitTimeToday))
|
||||||
|
|
||||||
|
elevationTransit = Sun.elevation(getDay(),getMonth(),2025, Sun.getHour(TransitTimeToday) ,Sun.getMinute(TransitTimeToday))
|
||||||
|
}
|
||||||
|
|
||||||
|
if SetTimeToday then azimuthSet = Sun.azimuth(getDay(),getMonth(),2025, Sun.getHour(SetTimeToday) ,Sun.getMinute(SetTimeToday))
|
||||||
|
|
||||||
|
CalculateBtn = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if timerMinute == 0 | CalculateBtn == 1 then {
|
||||||
|
|
||||||
|
jd = Sun.jd()
|
||||||
|
GMST = Sun.GMST()
|
||||||
|
LST = Sun.LST()
|
||||||
|
ra = Sun.ra()
|
||||||
|
dec = Sun.dec()
|
||||||
|
GHA = GMST - ra
|
||||||
|
LHA = GHA - 28.8448
|
||||||
|
|
||||||
|
azimuth = Sun.azimuth()
|
||||||
|
elevation = Sun.elevation()
|
||||||
|
sunArcFromTransit = Sun.sunArcFromTransit()
|
||||||
|
|
||||||
|
if elevation > 0 then isDayTime = 1 else isDayTime = 0
|
||||||
|
|
||||||
|
CalculateBtn = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if cronMidnight then CalculateBtn = 1
|
||||||
|
if onStart then CalculateBtn = 1
|
||||||
167
src/modules/virtual/SolarCalc/modinfo.json
Normal file
167
src/modules/virtual/SolarCalc/modinfo.json
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
{
|
||||||
|
"menuSection": "virtual_elments",
|
||||||
|
"configItem": [
|
||||||
|
{
|
||||||
|
"global": 0,
|
||||||
|
"name": "Solar Calculator",
|
||||||
|
"type": "Reading",
|
||||||
|
"subtype": "SolarCalculator",
|
||||||
|
"id": "Sun",
|
||||||
|
"widget": "anydataCorner",
|
||||||
|
"page": "Математика",
|
||||||
|
"descr": "Azimuth",
|
||||||
|
"lat": 47.0159,
|
||||||
|
"lon": 28.8448,
|
||||||
|
"parameter": "azimuth",
|
||||||
|
"round": 1,
|
||||||
|
"int": 60,
|
||||||
|
"ticker": 1,
|
||||||
|
"debug": 0
|
||||||
|
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"about": {
|
||||||
|
"authorName": "Alex",
|
||||||
|
"authorContact": "https://t.me/cmche",
|
||||||
|
"authorGit": "https://github.com/CHE77/IoTManager-Modules",
|
||||||
|
"exampleURL": "https://iotmanager.org/wiki",
|
||||||
|
"specialThanks": "",
|
||||||
|
"moduleName": "SolarCalculator",
|
||||||
|
"moduleVersion": "1.0",
|
||||||
|
"usedRam": {
|
||||||
|
"esp32_4mb": 15,
|
||||||
|
"esp8266_4mb": 15
|
||||||
|
},
|
||||||
|
"title": "Модуль SolarCalc",
|
||||||
|
"moduleDesc": "Модуль для расчета положения Солнца в различных системах координат. Требуют обновленного времени: через интернет, RTC, SoftRTC",
|
||||||
|
"license": "Cooperative Non-Violent Public License (CNPL)",
|
||||||
|
"propInfo": {
|
||||||
|
},
|
||||||
|
"funcInfo": [
|
||||||
|
{
|
||||||
|
"name": "sunrise",
|
||||||
|
"descr": "Расчет времени восхода",
|
||||||
|
"params": [
|
||||||
|
"Sun.sunrise() - на текущую дату",
|
||||||
|
"Sun.sunrise(день, месяц, год) - на заданую дату"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "transit",
|
||||||
|
"descr": "Расчет времени транзита",
|
||||||
|
"params": [
|
||||||
|
"Sun.transit() - на текущую дату",
|
||||||
|
"Sun.transit(день, месяц, год) - на заданую дату"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sunset",
|
||||||
|
"descr": "Расчет времени захода",
|
||||||
|
"params": [
|
||||||
|
"Sun.sunset() - на текущую дату",
|
||||||
|
"Sun.sunset(день, месяц, год) - на заданую дату"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "azimuth",
|
||||||
|
"descr": "Расчет азимута",
|
||||||
|
"params": [
|
||||||
|
"Sun.azimuth() - на текущий момент времени",
|
||||||
|
"Sun.azimuth(день, месяц, год, час, минут, секунд) - на заданое веремя",
|
||||||
|
"Sun.azimuth(день, месяц, год, час, минут) - на заданое веремя",
|
||||||
|
"Sun.azimuth(день, месяц, год, час) - на заданое веремя"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "elevation",
|
||||||
|
"descr": "Расчет высоты (над горизонтом)",
|
||||||
|
"params": [
|
||||||
|
"Sun.elevation() - на текущий момент времени",
|
||||||
|
"Sun.elevation(день, месяц, год, час, минут, секунд) - на заданое веремя",
|
||||||
|
"Sun.elevation(день, месяц, год, час, минут) - на заданое веремя",
|
||||||
|
"Sun.elevation(день, месяц, год, час) - на заданое веремя"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sunArcFromTransit",
|
||||||
|
"descr": "Расчет дуги эклиптики от зенита",
|
||||||
|
"params": [
|
||||||
|
"Sun.sunArcFromTransit() - на текущий момент времени",
|
||||||
|
"Sun.sunArcFromTransit(день, месяц, год, час, минут, секунд) - на заданое веремя",
|
||||||
|
"Sun.sunArcFromTransit(день, месяц, год, час, минут) - на заданое веремя",
|
||||||
|
"Sun.sunArcFromTransit(день, месяц, год, час) - на заданое веремя"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "jd",
|
||||||
|
"descr": "Расчет Юлинаской даты",
|
||||||
|
"params": [
|
||||||
|
"Sun.jd() - на текущий момент времени",
|
||||||
|
"Sun.jd(день, месяц, год, час, минут, секунд) - на заданое веремя",
|
||||||
|
"Sun.jd(день, месяц, год, час, минут) - на заданое веремя",
|
||||||
|
"Sun.jd(день, месяц, год, час) - на заданое веремя"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GMST",
|
||||||
|
"descr": "Расчет среднего звездного времени на меридиане Гринвича",
|
||||||
|
"params": [
|
||||||
|
"Sun.GMST() - на текущий момент времени",
|
||||||
|
"Sun.GMST(день, месяц, год, час, минут, секунд) - на заданое веремя",
|
||||||
|
"Sun.GMST(день, месяц, год, час, минут) - на заданое веремя",
|
||||||
|
"Sun.GMST(день, месяц, год, час) - на заданое веремя"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LST",
|
||||||
|
"descr": "Расчет Местного звездного времени",
|
||||||
|
"params": [
|
||||||
|
"Sun.LST() - на текущий момент времени",
|
||||||
|
"Sun.LST(день, месяц, год, час, минут, секунд) - на заданое веремя",
|
||||||
|
"Sun.LST(день, месяц, год, час, минут) - на заданое веремя",
|
||||||
|
"Sun.LST(день, месяц, год, час) - на заданое веремя"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ra",
|
||||||
|
"descr": "Расчет Прямого Восхождения",
|
||||||
|
"params": [
|
||||||
|
"Sun.ra() - на текущий момент времени",
|
||||||
|
"Sun.ra(день, месяц, год, час, минут, секунд) - на заданое веремя",
|
||||||
|
"Sun.ra(день, месяц, год, час, минут) - на заданое веремя",
|
||||||
|
"Sun.ra(день, месяц, год, час) - на заданое веремя"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dec",
|
||||||
|
"descr": "Расчет Склонения",
|
||||||
|
"params": [
|
||||||
|
"Sun.dec() - на текущий момент времени",
|
||||||
|
"Sun.dec(день, месяц, год, час, минут, секунд) - на заданое веремя",
|
||||||
|
"Sun.dec(день, месяц, год, час, минут) - на заданое веремя",
|
||||||
|
"Sun.dec(день, месяц, год, час) - на заданое веремя"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getHour",
|
||||||
|
"descr": "Получаем час из какого-то элемента и переводим его в UTC",
|
||||||
|
"params": [
|
||||||
|
"Sun.getHour(HH:mm)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getMinute",
|
||||||
|
"descr": "Получаем минуты из какого-то элемента",
|
||||||
|
"params": [
|
||||||
|
"Sun.getMinute(HH:mm)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"defActive": false,
|
||||||
|
"usedLibs": {
|
||||||
|
"esp32*": ["jpb10/SolarCalculator@^2.0.1"],
|
||||||
|
"esp82*": ["jpb10/SolarCalculator@^2.0.1"],
|
||||||
|
"bk72*": ["jpb10/SolarCalculator@^2.0.1"]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user