355 lines
10 KiB
C++
355 lines
10 KiB
C++
#ifndef SensorQualidadeArModel
|
|
#define SensorQualidadeArModel
|
|
|
|
#include "SerialService.h"
|
|
#include "I2CService.h"
|
|
#include "Utils.h"
|
|
#include <vector>
|
|
#include "ScioSense_ENS160.h"
|
|
#include <Adafruit_AHTX0.h>
|
|
|
|
class SensorQualidadeAr {
|
|
public:
|
|
SensorQualidadeAr(String _modID, String _id) {
|
|
Mod_ID = _modID;
|
|
_ID = _id;
|
|
}
|
|
|
|
String Mod_ID = "";
|
|
String _ID;
|
|
int ID_Num;
|
|
byte _EnderecoENS = 0x53;
|
|
byte _EnderecoAHT = 0x38;
|
|
int _CanalMUX;
|
|
|
|
bool Iniciado = false;
|
|
bool EnsIniciado = false;
|
|
bool AhtIniciado = false;
|
|
uint16_t tvoc_ppb;
|
|
uint16_t eco2_ppm;
|
|
uint8_t aqi;
|
|
float temperatura;
|
|
float umidade;
|
|
|
|
void Inicializar() {
|
|
if (Iniciado) {
|
|
MostrarLog("Sensor ja inicializado");
|
|
return;
|
|
}
|
|
|
|
if (!I2CService::SolicitarAcessoI2C(ID_Num, _CanalMUX)) return;
|
|
|
|
bool EnsEncontrado = I2CService::VerificaEnderecoBarramento(_EnderecoENS);
|
|
if (EnsEncontrado) {
|
|
ens160 = new ScioSense_ENS160(ENS160_I2CADDR_1);
|
|
ens160->begin();
|
|
EnsIniciado = ens160->available();
|
|
if (EnsIniciado) {
|
|
MostrarLog("Versao ENS160: " +
|
|
String(ens160->getMajorRev()) + "." +
|
|
String(ens160->getMinorRev()) + "." +
|
|
String(ens160->getBuild()));
|
|
ens160->setMode(ENS160_OPMODE_STD);
|
|
MostrarLog("ENS160 iniciado com sucesso!");
|
|
}
|
|
else {
|
|
MostrarLog("Erro ao iniciar ENS160");
|
|
}
|
|
} else {
|
|
MostrarLog("ENS160 nao iniciado, endereco " + String(_EnderecoENS) + " nao encontrado!");
|
|
}
|
|
|
|
bool AhtEncontrado = I2CService::VerificaEnderecoBarramento(_EnderecoAHT);
|
|
if (AhtEncontrado) {
|
|
AhtIniciado = aht21.begin();
|
|
MostrarLog("AHT21 iniciado com sucesso!");
|
|
} else {
|
|
MostrarLog("AHT21 nao iniciado, endereco " + String(_EnderecoENS) + " nao encontrado!");
|
|
}
|
|
|
|
Iniciado = EnsIniciado || AhtIniciado;
|
|
|
|
I2CService::LiberarAcessoI2C(ID_Num);
|
|
}
|
|
|
|
void Desligar() {
|
|
if (!Iniciado) {
|
|
MostrarLog("Sensor nao esta inicializado");
|
|
return;
|
|
}
|
|
|
|
if (ens160 != nullptr) {
|
|
delete ens160;
|
|
ens160 = nullptr;
|
|
}
|
|
|
|
Iniciado = false;
|
|
EnsIniciado = false;
|
|
AhtIniciado = false;
|
|
|
|
if (I2CService::QuemEstaUsando() == ID_Num) {
|
|
while (I2CService::QuemEstaUsando() == ID_Num) {
|
|
vTaskDelay(10);
|
|
}
|
|
}
|
|
|
|
MostrarLog("Sensor Desligado");
|
|
}
|
|
|
|
void RequisitarDados() {
|
|
if (!Iniciado) return;
|
|
if (EnsIniciado) {
|
|
AferirQualidadeAr();
|
|
}
|
|
if (AhtIniciado) {
|
|
AferirTemperatura();
|
|
}
|
|
}
|
|
|
|
std::vector<uint8_t> MontarMensagemCAN(CanMessagePosicaoDados posicao) {
|
|
std::vector<uint8_t> data;
|
|
data.push_back(static_cast<uint8_t>(posicao));
|
|
data.push_back(ID_Num);
|
|
|
|
switch (posicao) {
|
|
case CanMessagePosicaoDados::Status: {
|
|
data.push_back(Iniciado ? 1 : 0);
|
|
break;
|
|
}
|
|
case CanMessagePosicaoDados::Dados1: {
|
|
data.push_back(tvoc_ppb >> 8); data.push_back(tvoc_ppb & 0xFF);
|
|
data.push_back(eco2_ppm >> 8); data.push_back(eco2_ppm & 0xFF);
|
|
data.push_back(aqi); // 1 byte
|
|
break;
|
|
}
|
|
case CanMessagePosicaoDados::Dados2: {
|
|
uint16_t tmp = temperatura * 100;
|
|
uint16_t umd = umidade * 100;
|
|
data.push_back(tmp >> 8); data.push_back(tmp & 0xFF);
|
|
data.push_back(umd >> 8); data.push_back(umd & 0xFF);
|
|
break;
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
static std::vector<uint8_t> ConfigurarSensor(std::vector<SensorQualidadeAr*>& lista, std::vector<uint8_t>& data, const String& Mod_ID) {
|
|
std::vector<uint8_t> status;
|
|
if (data.size() < 2) return status;
|
|
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[0];
|
|
uint8_t idNum = data[1];
|
|
auto it = std::find_if(lista.begin(), lista.end(), [idNum](SensorQualidadeAr* s) { return s->ID_Num == idNum; });
|
|
bool jaExiste = it != lista.end();
|
|
SensorQualidadeAr* sensor;
|
|
|
|
switch (posicao) {
|
|
case CanMessagePosicaoDados::Config1: {
|
|
if (data.size() < 5) return status;
|
|
bool conectar = data[3] == 1;
|
|
int canalMux = (data[4] == 255) ? -1 : data[4];
|
|
|
|
if (conectar) {
|
|
if (!jaExiste) {
|
|
sensor = new SensorQualidadeAr(Mod_ID, "sQAR_" + String(idNum));
|
|
} else {
|
|
sensor = *it;
|
|
}
|
|
if (!sensor->Iniciado) {
|
|
sensor->ID_Num = idNum;
|
|
sensor->_CanalMUX = canalMux;
|
|
sensor->Inicializar();
|
|
if (!jaExiste) {
|
|
lista.push_back(sensor);
|
|
sensor->MostrarLog("Sensor de qualidade do ar adicionado via CAN: sQAR_" + String(idNum));
|
|
}
|
|
}
|
|
status = sensor->MontarMensagemCAN(CanMessagePosicaoDados::Status);
|
|
} else {
|
|
if (jaExiste) {
|
|
sensor = *it;
|
|
sensor->Desligar();
|
|
status = sensor->MontarMensagemCAN(CanMessagePosicaoDados::Status);
|
|
sensor->MostrarLog("Sensor de qualidade do ar removido via CAN: sQAR_" + String(idNum));
|
|
delete sensor;
|
|
lista.erase(it);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
private:
|
|
|
|
bool DebugMode = true;
|
|
void MostrarLog(String mensagem) {
|
|
if (DebugMode) {
|
|
PrintTela("[QAR " + _ID + "] " + mensagem);
|
|
}
|
|
}
|
|
|
|
ScioSense_ENS160* ens160;
|
|
Adafruit_AHTX0 aht21;
|
|
|
|
bool ENS160_EstaPronto() {
|
|
uint8_t buffer[1];
|
|
if (!I2CService::LeituraSegura(_EnderecoENS, buffer, 1, ENS160_REG_DATA_STATUS)) {
|
|
MostrarLog("[ENS160] Falha na leitura do DATA_STATUS");
|
|
return false;
|
|
}
|
|
|
|
// Bit 1 = NEWDAT (dados prontos)
|
|
return (buffer[0] & ENS160_DATA_STATUS_NEWDAT);
|
|
}
|
|
|
|
TaskHandle_t handleLeitura = nullptr;
|
|
|
|
struct ENS160LeituraPayload {
|
|
SensorQualidadeAr* sensor;
|
|
TaskHandle_t taskParaNotificar;
|
|
};
|
|
|
|
void AferirTemperatura() {
|
|
if (!AhtIniciado) return;
|
|
|
|
if (!I2CService::SolicitarAcessoI2C(ID_Num, _CanalMUX)) return;
|
|
|
|
MostrarLog("Aferindo temperatura...");
|
|
|
|
ENS160LeituraPayload* payload = new ENS160LeituraPayload{
|
|
.sensor = this,
|
|
.taskParaNotificar = xTaskGetCurrentTaskHandle()
|
|
};
|
|
|
|
handleLeitura = nullptr;
|
|
|
|
BaseType_t status = xTaskCreatePinnedToCore(
|
|
TaskLeituraAHT21,
|
|
"LeituraAHT21",
|
|
4096,
|
|
payload,
|
|
1,
|
|
&handleLeitura,
|
|
APP_CPU_NUM
|
|
);
|
|
|
|
if (status != pdPASS) {
|
|
MostrarLog("❌ Falha ao criar task de leitura!");
|
|
I2CService::LiberarAcessoI2C(ID_Num);
|
|
return;
|
|
}
|
|
|
|
BaseType_t sinal = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(200));
|
|
|
|
if (sinal == 0) {
|
|
MostrarLog("⚠️ AHT21 travou no measure, abortando leitura");
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(10)); // dá tempo da task morrer sozinha
|
|
eTaskState estado = eTaskGetState(handleLeitura);
|
|
|
|
if (estado != eDeleted && estado != eInvalid) {
|
|
vTaskDelete(handleLeitura);
|
|
}
|
|
}
|
|
|
|
I2CService::LiberarAcessoI2C(ID_Num);
|
|
handleLeitura = nullptr;
|
|
}
|
|
|
|
void AferirQualidadeAr() {
|
|
if (!EnsIniciado) return;
|
|
|
|
if (!I2CService::SolicitarAcessoI2C(ID_Num, _CanalMUX)) return;
|
|
|
|
MostrarLog("Aferindo qualidade do ar...");
|
|
|
|
ENS160LeituraPayload* payload = new ENS160LeituraPayload{
|
|
.sensor = this,
|
|
.taskParaNotificar = xTaskGetCurrentTaskHandle()
|
|
};
|
|
|
|
handleLeitura = nullptr;
|
|
|
|
BaseType_t status = xTaskCreatePinnedToCore(
|
|
TaskLeituraENS160,
|
|
"LeituraENS160",
|
|
4096,
|
|
payload,
|
|
1,
|
|
&handleLeitura,
|
|
APP_CPU_NUM
|
|
);
|
|
|
|
if (status != pdPASS) {
|
|
MostrarLog("❌ Falha ao criar task de leitura!");
|
|
I2CService::LiberarAcessoI2C(ID_Num);
|
|
return;
|
|
}
|
|
|
|
BaseType_t sinal = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(200));
|
|
|
|
if (sinal == 0) {
|
|
MostrarLog("⚠️ ENS160 travou no measure, abortando leitura");
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(10)); // dá tempo da task morrer sozinha
|
|
eTaskState estado = eTaskGetState(handleLeitura);
|
|
|
|
if (estado != eDeleted && estado != eInvalid) {
|
|
vTaskDelete(handleLeitura);
|
|
}
|
|
}
|
|
|
|
I2CService::LiberarAcessoI2C(ID_Num);
|
|
handleLeitura = nullptr;
|
|
}
|
|
|
|
static void TaskLeituraENS160(void* parametro) {
|
|
ENS160LeituraPayload* payload = static_cast<ENS160LeituraPayload*>(parametro);
|
|
SensorQualidadeAr* sensor = payload->sensor;
|
|
|
|
if (!sensor->ENS160_EstaPronto()) {
|
|
sensor->MostrarLog("❌ ENS160 não está pronto para medir.");
|
|
}
|
|
else {
|
|
sensor->ens160->measure();
|
|
if (sensor->ens160->available()) {
|
|
sensor->tvoc_ppb = sensor->ens160->getTVOC();
|
|
sensor->eco2_ppm = sensor->ens160->geteCO2();
|
|
sensor->aqi = sensor->ens160->getAQI();
|
|
sensor->MostrarLog("TVOC: " + String(sensor->tvoc_ppb) + " ppb, eCO2: " + String(sensor->eco2_ppm) + " ppm, AQI: " + String(sensor->aqi));
|
|
} else {
|
|
sensor->MostrarLog("Dados indisponiveis");
|
|
}
|
|
}
|
|
|
|
if (payload->taskParaNotificar != nullptr) {
|
|
xTaskNotifyGive(payload->taskParaNotificar);
|
|
}
|
|
|
|
delete payload;
|
|
vTaskDelete(nullptr);
|
|
}
|
|
|
|
static void TaskLeituraAHT21(void* parametro) {
|
|
ENS160LeituraPayload* payload = static_cast<ENS160LeituraPayload*>(parametro);
|
|
SensorQualidadeAr* sensor = payload->sensor;
|
|
|
|
sensors_event_t humidity, temp;
|
|
sensor->aht21.getEvent(&humidity, &temp);
|
|
sensor->temperatura = temp.temperature;
|
|
sensor->umidade = humidity.relative_humidity;
|
|
|
|
sensor->MostrarLog("Temperatura: " + String(sensor->temperatura) + ", Umidade: " + String(sensor->umidade));
|
|
|
|
if (payload->taskParaNotificar != nullptr) {
|
|
xTaskNotifyGive(payload->taskParaNotificar);
|
|
}
|
|
|
|
delete payload;
|
|
vTaskDelete(nullptr);
|
|
}
|
|
|
|
};
|
|
|
|
#endif |