new Modules from Chat

This commit is contained in:
Mit4el
2025-07-25 00:23:37 +03:00
parent acf7732c3c
commit 18012bd57a
24 changed files with 6283 additions and 1279 deletions

View File

@@ -212,6 +212,10 @@
"path": "src/modules/virtual/Ping",
"active": true
},
{
"path": "src/modules/virtual/SolarCalc",
"active": false
},
{
"path": "src/modules/virtual/Timer",
"active": true
@@ -374,6 +378,10 @@
"path": "src/modules/sensors/MQgas",
"active": false
},
{
"path": "src/modules/sensors/NoiseAdc",
"active": false
},
{
"path": "src/modules/sensors/Ntc",
"active": true
@@ -382,6 +390,10 @@
"path": "src/modules/sensors/Pcf8591",
"active": false
},
{
"path": "src/modules/sensors/Presence",
"active": false
},
{
"path": "src/modules/sensors/Pzem004t",
"active": false
@@ -422,6 +434,10 @@
"path": "src/modules/sensors/Sht30",
"active": false
},
{
"path": "src/modules/sensors/SoftRTC",
"active": false
},
{
"path": "src/modules/sensors/Sonar",
"active": false
@@ -548,6 +564,10 @@
"path": "src/modules/exec/Thermostat",
"active": true
},
{
"path": "src/modules/exec/WakeOnLanModule",
"active": false
},
{
"path": "src/modules/sensors/Ds2423",
"active": false
@@ -566,6 +586,10 @@
"path": "src/modules/display/Lcd2004",
"active": false
},
{
"path": "src/modules/display/LedFX",
"active": false
},
{
"path": "src/modules/display/Nextion",
"active": false
@@ -590,6 +614,10 @@
"path": "src/modules/display/TM16XX",
"active": false
},
{
"path": "src/modules/display/U8g2lib",
"active": false
},
{
"path": "src/modules/display/Ws2812b",
"active": false

View File

@@ -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

View File

@@ -1,4 +1,3 @@
#include "ModbusEC.h"
#define COUNT_BIT_AVAIL 5
@@ -11,19 +10,8 @@ ModbusMaster::ModbusMaster(void)
_postTransmission = 0;
}
/**
Initialize class object.
Assigns the Modbus slave ID and serial port.
Call once class has been instantiated, typically within setup().
@param slave Modbus slave ID (1..255)
@param &serial reference to serial port object (Serial, Serial1, ... Serial3)
@ingroup setup
*/
void ModbusMaster::begin(uint8_t slave, Stream *serial)
{
// txBuffer = (uint16_t*) calloc(ku8MaxBufferSize, sizeof(uint16_t));
_u8MBSlave = slave;
_serial = serial;
_u8TransmitBufferIndex = 0;
@@ -42,16 +30,13 @@ void ModbusMaster::beginTransmission(uint16_t u16Address)
u16TransmitBufferLength = 0;
}
// eliminate this function in favor of using existing MB request functions
uint8_t ModbusMaster::requestFrom(uint16_t address, uint16_t quantity)
{
uint8_t read;
// clamp to buffer length
uint8_t read = 0; // Èíèöèàëèçèðîâàíî çíà÷åíèå ïî óìîë÷àíèþ
if (quantity > ku8MaxBufferSize)
{
quantity = ku8MaxBufferSize;
}
// set rx buffer iterator vars
_u8ResponseBufferIndex = 0;
_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)())
{
_idle = idle;
}
/**
Set pre-transmission callback function.
This function gets called just before a Modbus message is sent over serial.
Typical usage of this callback is to enable an RS485 transceiver's
Driver Enable pin, and optionally disable its Receiver Enable pin.
@see ModbusMaster::ModbusMasterTransaction()
@see ModbusMaster::postTransmission()
*/
void ModbusMaster::preTransmission(void (*preTransmission)())
{
_preTransmission = preTransmission;
}
/**
Set post-transmission callback function.
This function gets called after a Modbus message has finished sending
(i.e. after all data has been physically transmitted onto the serial
bus).
Typical usage of this callback is to enable an RS485 transceiver's
Receiver Enable pin, and disable its Driver Enable pin.
@see ModbusMaster::ModbusMasterTransaction()
@see ModbusMaster::preTransmission()
*/
void ModbusMaster::postTransmission(void (*postTransmission)())
{
_postTransmission = postTransmission;
}
/**
Retrieve data from response buffer.
@see ModbusMaster::clearResponseBuffer()
@param u8Index index of response buffer array (0x00..0x3F)
@return value in position u8Index of response buffer (0x0000..0xFFFF)
@ingroup buffer
*/
uint16_t ModbusMaster::getResponseBuffer(uint8_t u8Index)
{
if (u8Index < ku8MaxBufferSize)
@@ -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()
{
uint8_t i;
for (i = 0; i < ku8MaxBufferSize; i++)
{
_u16ResponseBuffer[i] = 0;
}
}
/**
Place data in transmit buffer.
@see ModbusMaster::clearTransmitBuffer()
@param u8Index index of transmit buffer array (0x00..0x3F)
@param u16Value value to place in position u8Index of transmit buffer (0x0000..0xFFFF)
@return 0 on success; exception number on failure
@ingroup buffer
*/
uint8_t ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value)
{
if (u8Index < ku8MaxBufferSize)
@@ -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()
{
uint8_t i;
for (i = 0; i < ku8MaxBufferSize; i++)
{
_u16TransmitBuffer[i] = 0;
}
}
/**
Modbus function 0x03 Read Holding Registers.
This function code is used to read the contents of a contiguous block of
holding registers in a remote device. The request specifies the starting
register address and the number of registers. Registers are addressed
starting at zero.
The register data in the response buffer is packed as one word per
register.
@param u16ReadAddress address of the first holding register (0x0000..0xFFFF)
@param u16ReadQty quantity of holding registers to read (1..125, enforced by remote device)
@return 0 on success; exception number on failure
@ingroup register
*/
uint8_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress,
uint16_t u16ReadQty)
{
@@ -256,18 +161,6 @@ uint8_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress,
return ModbusMasterTransaction(ku8MBReadHoldingRegisters);
}
/**
Modbus function 0x06 Write Single Register.
This function code is used to write a single holding register in a
remote device. The request specifies the address of the register to be
written. Registers are addressed starting at zero.
@param u16WriteAddress address of the holding register (0x0000..0xFFFF)
@param u16WriteValue value to be written to holding register (0x0000..0xFFFF)
@return 0 on success; exception number on failure
@ingroup register
*/
uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress,
uint16_t u16WriteValue)
{
@@ -277,20 +170,6 @@ uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress,
return ModbusMasterTransaction(ku8MBWriteSingleRegister);
}
/**
Modbus function 0x10 Write Multiple Registers.
This function code is used to write a block of contiguous registers (1
to 123 registers) in a remote device.
The requested written values are specified in the transmit buffer. Data
is packed as one word per register.
@param u16WriteAddress address of the holding register (0x0000..0xFFFF)
@param u16WriteQty quantity of holding registers to write (1..123, enforced by remote device)
@return 0 on success; exception number on failure
@ingroup register
*/
uint8_t ModbusMaster::writeMultipleRegisters(uint16_t u16WriteAddress,
uint16_t u16WriteQty)
{
@@ -299,7 +178,6 @@ uint8_t ModbusMaster::writeMultipleRegisters(uint16_t u16WriteAddress,
return ModbusMasterTransaction(ku8MBWriteMultipleRegisters);
}
// new version based on Wire.h
uint8_t ModbusMaster::writeMultipleRegisters()
{
_u16WriteQty = _u8TransmitBufferIndex;
@@ -313,6 +191,7 @@ uint8_t ModbusMaster::readAddresEctoControl()
ModbusMasterTransaction(ku8MBProgRead46);
return getResponseBuffer(0x00);
}
uint8_t ModbusMaster::writeAddresEctoControl(uint8_t addr)
{
_u16WriteAddress = 0x00;
@@ -321,20 +200,6 @@ uint8_t ModbusMaster::writeAddresEctoControl(uint8_t addr)
return ModbusMasterTransaction(ku8MBProgWrite47);
}
/* _____PRIVATE FUNCTIONS____________________________________________________ */
/**
Modbus transaction engine.
Sequence:
- assemble Modbus Request Application Data Unit (ADU),
based on particular function called
- transmit request over selected serial port
- wait for/retrieve response
- evaluate/disassemble response
- return status (success/exception)
@param u8MBFunction Modbus function (0x01..0xFF)
@return 0 on success; exception number on failure
*/
uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
{
uint8_t u8ModbusADU[24];
@@ -345,7 +210,7 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
uint8_t u8BytesLeft = 8;
uint8_t u8MBStatus = ku8MBSuccess;
// assemble Modbus Request Application Data Unit
// Assemble Modbus Request Application Data Unit
if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47)
{
u8ModbusADU[u8ModbusADUSize++] = 0x00;
@@ -382,7 +247,8 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty);
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++] = lowByte(_u16TransmitBuffer[i]);
@@ -390,47 +256,31 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
break;
}
// append CRC
// Append CRC
u16CRC = 0xFFFF;
for (i = 0; i < u8ModbusADUSize; 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++] = highByte(u16CRC);
// }
u8ModbusADU[u8ModbusADUSize] = 0;
// flush receive buffer before transmitting request
while (_serial->read() != -1)
;
// Flush receive buffer before transmitting request
while (_serial->read() != -1);
// transmit request
if (_preTransmission)
{
_preTransmission();
}
// Transmit request
if (_preTransmission) _preTransmission();
for (i = 0; i < u8ModbusADUSize; i++)
{
_serial->write(u8ModbusADU[i]);
}
u8ModbusADUSize = 0;
_serial->flush(); // flush transmit buffer
if (_postTransmission)
{
_postTransmission();
}
_serial->flush();
if (_postTransmission) _postTransmission();
// loop until we run out of time or bytes, or an error occurs
// Loop until timeout or response complete
u32StartTime = millis();
while (u8BytesLeft && !u8MBStatus)
{
@@ -441,7 +291,6 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
#endif
uint8_t req = _serial->read();
u8ModbusADU[u8ModbusADUSize++] = req;
Serial.print(req, HEX);
u8BytesLeft--;
#if __MODBUSMASTER_DEBUG__
digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, false);
@@ -452,132 +301,110 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
#if __MODBUSMASTER_DEBUG__
digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, true);
#endif
if (_idle)
{
_idle();
}
if (_idle) _idle();
#if __MODBUSMASTER_DEBUG__
digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, false);
#endif
}
// evaluate slave ID, function code once enough bytes have been read
uint8_t count;
if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47)
count = COUNT_BIT_AVAIL_46F;
else
count = COUNT_BIT_AVAIL;
// Evaluate response after enough bytes received
uint8_t count = (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47)
? COUNT_BIT_AVAIL_46F : COUNT_BIT_AVAIL;
if (u8ModbusADUSize == count)
{
if (u8MBFunction != ku8MBProgRead46 && u8MBFunction != ku8MBProgWrite47)
{
// verify response is for correct Modbus slave
if (u8ModbusADU[0] != _u8MBSlave)
{
// Serial.print(u8ModbusADU[0], HEX);
// Serial.print(" != ");
// Serial.println(_u8MBSlave, HEX);
u8MBStatus = ku8MBInvalidSlaveID;
break;
}
// verify response is for correct Modbus function code (mask exception bit 7)
if ((u8ModbusADU[1] & 0x7F) != u8MBFunction)
{
u8MBStatus = ku8MBInvalidFunction;
break;
}
}
// check whether Modbus exception occurred; return Modbus Exception Code
if (bitRead(u8ModbusADU[1], 7))
{
u8MBStatus = u8ModbusADU[2];
break;
}
// evaluate returned Modbus function code
// Determine remaining bytes based on function
switch (u8ModbusADU[1])
{
case ku8MBReadHoldingRegisters:
u8BytesLeft = u8ModbusADU[2];
break;
case ku8MBWriteMultipleRegisters:
u8BytesLeft = 3;
break;
case ku8MBProgRead46:
u8BytesLeft = 1;
break;
case ku8MBProgWrite47:
u8BytesLeft = 1;
break;
default:
case ku8MBReadHoldingRegisters:
u8BytesLeft = u8ModbusADU[2];
break;
case ku8MBWriteMultipleRegisters:
u8BytesLeft = 3;
break;
case ku8MBProgRead46:
case ku8MBProgWrite47:
u8BytesLeft = 1;
break;
default: ; // Ïóñòîé îïåðàòîð äëÿ êîððåêòíîãî ñèíòàêñèñà
}
}
if ((millis() - u32StartTime) > ku16MBResponseTimeout)
{
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
if (!u8MBStatus && u8ModbusADUSize >= COUNT_BIT_AVAIL)
u16CRC = 0xFFFF;
for (i = 0; i < (u8ModbusADUSize - 2); i++)
{
// calculate CRC
u16CRC = 0xFFFF;
for (i = 0; i < (u8ModbusADUSize - 2); i++)
{
u16CRC = crc16_update(u16CRC, u8ModbusADU[i]);
}
u16CRC = crc16_update(u16CRC, u8ModbusADU[i]);
}
// verify CRC
if (!u8MBStatus && (lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2] ||
highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1]))
{
u8MBStatus = ku8MBInvalidCRC;
}
// Èñïðàâëåíî: äîáàâëåíû ñêîáêè äëÿ ãðóïïèðîâêè óñëîâèé
if (!u8MBStatus &&
((lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2]) ||
(highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1])))
{
u8MBStatus = ku8MBInvalidCRC;
}
}
// disassemble ADU into words
// Parse response data
if (!u8MBStatus)
{
// evaluate returned Modbus function code
switch (u8ModbusADU[1])
{
case ku8MBReadHoldingRegisters:
// load bytes into word; response bytes are ordered H, L, H, L, ...
for (i = 0; i < (u8ModbusADU[2] >> 1); i++)
{
if (i < ku8MaxBufferSize)
case ku8MBReadHoldingRegisters:
for (i = 0; i < (u8ModbusADU[2] >> 1); i++)
{
_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;
}
_u8ResponseBufferLength = i;
}
break;
case ku8MBProgRead46:
Serial.print("ku8MBProgRead46");
for (i = 0; i < (u8ModbusADUSize); i++)
{
Serial.println(u8ModbusADU[i], HEX);
}
_u16ResponseBuffer[0] = (uint16_t)u8ModbusADU[2];
_u8ResponseBufferLength = 1;
break;
break;
case ku8MBProgRead46:
_u16ResponseBuffer[0] = (uint16_t)u8ModbusADU[2];
_u8ResponseBufferLength = 1;
break;
}
}
// Reset buffers
_u8TransmitBufferIndex = 0;
u16TransmitBufferLength = 0;
_u8ResponseBufferIndex = 0;
return u8MBStatus;
}
}

View 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);

View File

@@ -17,16 +17,17 @@
"DIR_PIN": 4,
"baud": 19200,
"protocol": "SERIAL_8N1",
"debug": 1
"debug": 1,
"UARTLine": 1
}
],
"about": {
"authorName": "Mikhail Bubnov",
"authorContact": "https://t.me/Mit4bmw",
"authorGit": "https://github.com/Mit4el",
"specialThanks": "",
"specialThanks": "SeregaKi",
"moduleName": "EctoControlAdapter",
"moduleVersion": "1.0",
"moduleVersion": "2.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
@@ -37,80 +38,100 @@
"title": "EctoControlAdapter",
"moduleDesc": "Управление отопительным котлом через адаптер EctoControl по протоколам OpenTherm, eBUS, Navien. Посредством Modbus RTU. Разъем 4P4C: 1-Желтый(красный)+12V; 2-Белый-GND; 3-Зелёный-A; 4-Коричневый(Синий)-B",
"propInfo": {
"addr": "Адрес slave, что бы узнать адрес - в конфиге адрес 0 и смотреть лог (требуется проверка)",
"addr": "Адрес slave, что бы узнать адрес - в конфиге адрес 0 и смотреть лог",
"int": "Количество секунд между опросами датчика.",
"RX": "Пин RX",
"TX": "Пин TX",
"DIR_PIN": "connect DR, RE pin of MAX485 to gpio, указать 0 если не нужен",
"baud": "скорость Uart",
"protocol": "Протокол Uart: SERIAL_8N1 или SERIAL_8N2",
"debug": "0 - отключить дебаг, 1 - включить вывод дебага, 2 - лог комманд, 3 - вывод modbus"
"debug": "0 - отключить дебаг, 1 - включить вывод дебага, 2 - лог комманд, 3 - вывод modbus",
"UARTLine": "Номер UART порта (0, 1 или 2), по умолчанию 1"
},
"funcInfo": [
{
"name": "getModelVersion",
"descr": "Запрос модели и версии адаптера и бойлера",
"params": []
"name": "setHeating",
"descr": "Включение/отключение контура отопления",
"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",
"descr": "Запрос состояния связи с котлом, типа адаптера и код перезагрузки адаптера",
"descr": "Запрос информации о котле и адаптере",
"params": []
},
{
"name": "getBoilerStatus",
"descr": "Запрос состояния контуров котла и горелки",
"name": "getWrittenBoilerStatus",
"descr": "Запрос записанного статуса котла",
"params": []
},
{
"name": "getCodeError",
"descr": "Код ошибки котла (основной). Зависит от марки и модели котла.",
"name": "getSettings",
"descr": "Запрос текущих настроек адаптера",
"params": []
},
{
"name": "getCodeErrorExt",
"descr": "Код ошибки котла (дополнительный). Зависит от марки и модели котла.",
"params": []
"name": "setTCH",
"descr": "Установка температуры отопления",
"params": ["температура (например 55.5)"]
},
{
"name": "getFlagErrorOT",
"descr": "Стандартные флаги ошибок котла (только для котлов с интерфейсом OpenTherm)",
"params": []
"name": "setTDHW",
"descr": "Установка температуры ГВС",
"params": ["температура (целое число)"]
},
{
"name": "getFlowRate",
"descr": "Текущий расхода ГВС",
"params": []
"name": "setSetTypeConnect",
"descr": "Установка типа подключения",
"params": ["0 - адаптер к котлу, 1 - котел к внешнему устройству"]
},
{
"name": "getMaxSetCH",
"descr": "Верхний предел уставки теплоносителя",
"params": []
"name": "setTSetCHFaultConn",
"descr": "Установка температуры отопления при аварии связи",
"params": ["температура (например 55.5)"]
},
{
"name": "getMaxSetDHW",
"descr": "Верхний предел уставки ГВС",
"params": []
"name": "setTSetMinCH",
"descr": "Установка минимальной температуры отопления",
"params": ["температура (например 35.0)"]
},
{
"name": "getMinSetCH",
"descr": "Нижний предел уставки теплоносителя",
"params": []
"name": "setTSetMaxCH",
"descr": "Установка максимальной температуры отопления",
"params": ["температура (например 85.0)"]
},
{
"name": "getMinSetDHW",
"descr": "Нижний предел уставки ГВС",
"params": []
"name": "setTSetMinDHW",
"descr": "Установка минимальной температуры ГВС",
"params": ["температура (целое число)"]
},
{
"name": "getModLevel",
"descr": "Текущая модуляция горелки",
"params": []
"name": "setTSetMaxDHW",
"descr": "Установка максимальной температуры ГВС",
"params": ["температура (целое число)"]
},
{
"name": "getPressure",
"descr": "Текущее Давление в контуре",
"params": []
"name": "setSetMaxModLevel",
"descr": "Установка максимального уровня модуляции",
"params": ["уровень (0-100)"]
},
{
"name": "getTempCH",
@@ -122,81 +143,80 @@
"descr": "Текущая температура ГВС",
"params": []
},
{
"name": "getPressure",
"descr": "Давление в контуре",
"params": []
},
{
"name": "getFlowRate",
"descr": "Расход ГВС",
"params": []
},
{
"name": "getModLevel",
"descr": "Уровень модуляции горелки",
"params": []
},
{
"name": "getBoilerStatusRaw",
"descr": "Сырое значение регистра статуса",
"params": []
},
{
"name": "getTempOutside",
"descr": "Температура уличного датчика котла",
"descr": "Температура уличного датчика",
"params": []
},
{
"name": "setTypeConnect",
"descr": "Установить тип внешних подключений (сохраняется в EPROM Адаптера): 0 - адаптер подключен к котлу, 1 - котел подключен к внешнему устройству (панель или перемычка)",
"params": ["Тип подключения"]
},
{
"name": "setTCH",
"descr": "Уставка температуры теплоносителя (сохраняется в EPROM Адаптера)",
"params": ["температура передаётся до десятых градуса"]
},
{
"name": "setTDHW",
"descr": "Уставка температуры ГВС (сохраняется в EPROM Адаптера)",
"params": ["температура передаётся до десятых градуса"]
},
{
"name": "setTCHFaultConn",
"descr": "Уставка теплоносителя в аварийном режиме (сохраняется в EPROM Адаптера). Будет передана котлу в случае отсутствия связи адаптера с управляющим устройством",
"params": ["температура передаётся до десятых градуса"]
},
{
"name": "setMinCH",
"descr": "Задать нижний предел уставки теплоносителя",
"params": ["температура от 0 до 100"]
},
{
"name": "setMaxCH",
"descr": "Задать верхний предел уставки теплоносителя",
"params": ["температура от 0 до 100"]
},
{
"name": "setMinDHW",
"descr": "Задать нижний предел уставки ГВС",
"params": ["температура от 0 до 100"]
},
{
"name": "setMaxDHW",
"descr": "Задать верхний предел уставки ГВС",
"params": ["температура от 0 до 100"]
},
{
"name": "setMaxModLevel",
"descr": "Уставка максимальной модуляции горелки (сохраняется в EPROM Адаптера)",
"params": ["уровень модуляции 0-100%"]
},
{
"name": "setStatusCH",
"descr": "Установить режим (Включить) контура отопления; 0 - отключен, 1 - включен",
"params": ["вкл/откл отопления"]
},
{
"name": "setStatusDHW",
"descr": "Установить режим (Включить) ГВС; 0 - отключен, 1 - включен",
"params": ["вкл/откл ГВС"]
},
{
"name": "setStatusCH2",
"descr": "Установить режим (Включить) второго контура отопления; 0 - отключен, 1 - включен. используется только некоторыми котлами с интерфейсом OpenTherm и может отвечать за активацию бойлера косвенного нагрева или встроенной функции ГВС",
"params": ["вкл/откл второго контура отопления"]
},
{
"name": "lockOutReset",
"descr": "Сброс ошибок котла",
"name": "getFlagErrorOT",
"descr": "Флаги ошибок OpenTherm",
"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": "Перезагрузка адаптера",
"params": []
},
{
"name": "resetErrors",
"descr": "Сброс ошибок котла",
"params": []
}
]
},

View 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> &param)
{
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;
}
}

View 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
}

View 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"]
}
}

View 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> &param)
{
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;
}
}

View 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

View 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*": []
}
}

View 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);
// Приводим к диапазону 0360
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> &param)
{
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;
}
}

View 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()
}

View 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*": []
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View 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": "Без виджета"
}
]

View 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;
// Переходим из юлианского в григоріанский:
// * для 19002099 разница = 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> &param)
{
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;
}

View 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()
}

View 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*": []
}
}

View 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> &param)
{
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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

View 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

View 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"]
}
}