Criado firmware estatica do sensoriamento

This commit is contained in:
Diego Freitas 2026-03-20 17:08:23 -03:00
parent fc89b3a90e
commit f77d5daf3e
10 changed files with 1380 additions and 39 deletions

View File

@ -174,14 +174,7 @@ namespace AgroBase.Models.Modules
}
}
public SensorModel SensorTemperaturaMotor
{
get
{
var sNTC = Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Sensores?.FirstOrDefault(x => x.Componente == S_Code.sNTC && x.ID.Contains(Mod_ID));
return sNTC;
}
}
public SensorModel SensorTemperaturaMotor => Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Sensores?.FirstOrDefault(x => x.Inicializado && x.Componente == S_Code.sNTC && x.ID.Contains(Mod_ID));
public bool Comandar { get; set; } = false;
public bool AlarmeSensores { get; set; } = false;
@ -511,34 +504,10 @@ namespace AgroBase.Models.Modules
public DateTime UltimoEnvio { get; set; }
public int DelayEnvio { get; set; } = 500;
public SensorServoModel Servo
{
get
{
return Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Servos?
.FirstOrDefault(x => x.ID.Contains(Mod_ID));
}
}
private double VelocidadeAtual =>
FuncoesMatematicas.CalculaVelocidadePercentualMs(Variaveis.OperacaoEmAndamento.Sensoriamento?.Movimentacao?.VelocidadeMedia ?? 0);
private bool StatusControleEmFreio =>
(Variaveis.OperacaoEmAndamento.Controle?.TiposControle?
.FirstOrDefault(x => x.Tipo == T_Code.Mov)?.UltimaDirecao
?? Direcao.Parado) == Direcao.EmFreio;
// leitura atual do ângulo (ou null)
private int AnguloLido
{
get
{
var v = Servo?.ValoresLeituras?
.FirstOrDefault(x => x.funcao == FuncoesPinout.ServoAnguloLeitura)?
.atual?.valor ?? 0;
return (int)v;
}
}
public SensorServoModel Servo => Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Servos?.FirstOrDefault(x => x.Inicializado && x.ID.Contains(Mod_ID));
private double VelocidadeAtual => FuncoesMatematicas.CalculaVelocidadePercentualMs(Variaveis.OperacaoEmAndamento.Sensoriamento?.Movimentacao?.VelocidadeMedia ?? 0);
private bool StatusControleEmFreio => Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos?.FirstOrDefault(x => x.Modulo_ID == Mod_ID)?.MovMotor?.Leitura?.Freado ?? false;
private int? AnguloLido => (int?)Servo?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.ServoAnguloLeitura)?.atual?.valor;
// histerese de velocidade
private bool VelocidadeOkHisterese(int sp, double atual)
@ -637,7 +606,7 @@ namespace AgroBase.Models.Modules
// Regra: envia quando (a) houve transição de modo, (b) alvo mudou significativamente,
// ou (c) não atingiu o alvo atual (com tolerância).
bool alvoMudou = Math.Abs(AnguloControle - anguloAntigo) > 0;
bool atingiu = AtingiuSPComTol(AnguloLido, AnguloControle);
bool atingiu = AnguloLido != null && AtingiuSPComTol(AnguloLido, AnguloControle);
// histerese de velocidade apenas para DEFINIR o alvo (já considerado acima).
// Não use VelocidadeOk para forçar reenvio — isso causa “chatters” de comando.
@ -655,9 +624,9 @@ namespace AgroBase.Models.Modules
private void EnviarComandoModulo()
{
if (EnviarComando && UltimoEnvio.AddMilliseconds(DelayEnvio) < DateTime.Now)
if (EnviarComando && UltimoEnvio.AddMilliseconds(DelayEnvio) < DateTime.Now && Servo != null)
{
GeneralJoystick.EnviarComandoSensoriamento(S_Code.sSRV, Servo.ID, AnguloControle);
GeneralJoystick.EnviarComandoSensoriamento(Servo.Componente, Servo.ID, AnguloControle, Forcar: true);
UltimoEnvio = DateTime.Now;
}
}

View File

@ -41,6 +41,39 @@ public:
}
}
}
template<typename T>
static void ProcessarComponenteCAN(
T& componente,
const std::vector<CanMessagePosicaoDados>& posicoes,
bool todosIDs,
int id_num,
bool enviarStatus,
bool todosDados,
CanMessagePosicaoDados posicao,
std::function<void(const std::vector<uint8_t>&)> enviarDadosFunc
) {
if (todosIDs || componente.ID_Num == id_num) {
componente.RequisitarDados();
if (enviarStatus) {
enviarDadosFunc(componente.MontarMensagemCAN(CanMessagePosicaoDados::Status));
}
if (componente.Iniciado) {
if (todosDados) {
for (auto p : posicoes) {
if (p == CanMessagePosicaoDados::Status) continue;
enviarDadosFunc(componente.MontarMensagemCAN(p));
}
} else {
if (posicao != CanMessagePosicaoDados::Status || !enviarStatus) {
enviarDadosFunc(componente.MontarMensagemCAN(posicao));
}
}
}
}
}
};
#endif

View File

@ -0,0 +1,129 @@
#include "SerialService.h"
#include "Pinout.h"
#include "Utils.h"
#include "IComponenteCAN.h"
#ifndef ReleModel_v2
#define ReleModel_v2
class Rele_v2 : public ComponenteCAN {
public:
String ID;
uint8_t ID_Num = 0;
PinoModel _pino;
bool Iniciado = false;
Rele_v2() {}
Rele_v2(
const String& id,
uint8_t idNum,
bool atuacaoNivelAlto,
int pino,
TiposBarramentos tipoBarramento
) :
ID(id),
ID_Num(idNum),
_pino(PinoModel(pino, tipoBarramento, _OUTPUT)),
AtuacaoNivelAlto(atuacaoNivelAlto)
{
}
void Inicializar() {
if (Iniciado) {
MostrarLog("Rele ja inicializado");
return;
}
if (_pino.Definido()) {
bool nivelInicial = AtuacaoNivelAlto ? false : true;
_pino.Conectar(nivelInicial);
_Status = Estado::Desligado;
Iniciado = true;
MostrarLog("Rele iniciado");
}
else {
MostrarLog("Rele nao iniciado");
}
}
void Desligar() {
if (!Iniciado) {
MostrarLog("Rele nao esta inicializado");
return;
}
AtualizarStatus(Estado::Desligado);
_pino.Desconectar();
Iniciado = false;
MostrarLog("Rele desligado");
}
void RequisitarDados() {
if (!Iniciado) return;
AtualizarLeitura();
}
void ProcessarComando(std::vector<uint8_t>& data) {
if (!Iniciado) return;
if (data.size() < 3) return;
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[0];
uint8_t idNum = data[1];
if (idNum != ID_Num) return;
switch (posicao) {
case CanMessagePosicaoDados::Command1: {
Estado novoEstado = (Estado)data[2];
AtualizarStatus(novoEstado);
break;
}
}
}
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(static_cast<uint8_t>(_Status));
break;
}
}
return data;
}
private:
bool DebugMode = true;
bool AtuacaoNivelAlto = true;
Estado _Status = Desligado;
void AtualizarStatus(Estado novoEstado) {
bool ligado = (novoEstado == Estado::Ligado);
bool nivelPino = AtuacaoNivelAlto ? ligado : !ligado;
MostrarLog("Atualizando status do rele de " + String(_Status) + " para " + String(novoEstado));
_pino.set(nivelPino);
_Status = novoEstado;
}
void AtualizarLeitura() {
bool nivelPino = _pino.get();
bool ligado = AtuacaoNivelAlto ? nivelPino : !nivelPino;
_Status = ligado ? Estado::Ligado : Estado::Desligado;
}
void MostrarLog(String mensagem) {
if (DebugMode) {
PrintTela("[RLE " + ID + "] " + mensagem);
}
}
};
#endif

View File

@ -0,0 +1,218 @@
#include "SerialService.h"
#include "I2CService.h"
#include "Utils.h"
#include "IComponenteCAN.h"
#include <Wire.h>
#include <vector>
#include <Adafruit_INA219.h>
#include <Adafruit_INA228.h>
#include <SimpleKalmanFilter.h>
#ifndef SensorCorrenteModel_v2
#define SensorCorrenteModel_v2
class SensorCorrente_v2 : public ComponenteCAN {
public:
String ID;
uint8_t ID_Num = 0;
uint8_t _Endereco = 0;
TiposShuntCorrente _A_Shunt = TiposShuntCorrente::INA228_10A;
int _CanalMUX = -1;
bool isINA219 = false;
bool Iniciado = false;
float busVoltage = 0;
float current = 0;
float power = 0;
float energy = 0;
float charge = 0;
uint16_t codAlarme = 0;
float temperature = 0;
SensorCorrente_v2() {}
SensorCorrente_v2(
const String& id,
uint8_t idNum,
uint8_t endereco,
TiposShuntCorrente tipoShunt,
int canalMux = -1
) :
ID(id),
ID_Num(idNum),
_Endereco(endereco),
_A_Shunt(tipoShunt),
_CanalMUX(canalMux)
{
}
void Inicializar() {
if (Iniciado) {
MostrarLog("Sensor ja inicializado");
return;
}
if (!I2CService::SolicitarAcessoI2C(ID_Num, _CanalMUX)) {
MostrarLog("Falha ao solicitar acesso I2C");
return;
}
isINA219 = (_A_Shunt == TiposShuntCorrente::INA219_3A2);
bool encontrado = I2CService::VerificaEnderecoBarramento(_Endereco);
if (!encontrado) {
Iniciado = false;
MostrarLog("Endereco I2C nao encontrado: " + String(_Endereco));
I2CService::LiberarAcessoI2C(ID_Num);
return;
}
if (isINA219) {
sINA219 = Adafruit_INA219(_Endereco);
Iniciado = sINA219.begin();
if (Iniciado) {
sINA219.setCalibration_32V_2A();
MostrarLog("INA219 iniciado no canal " + String(_CanalMUX));
} else {
MostrarLog("Falha ao iniciar INA219");
}
}
else {
sINA228 = Adafruit_INA228();
Iniciado = sINA228.begin(_Endereco);
if (Iniciado) {
ConfigurarShuntINA228();
sINA228.setAveragingCount(INA228_COUNT_16);
sINA228.setCurrentConversionTime(INA228_TIME_280_us);
MostrarLog("INA228 iniciado no canal " + String(_CanalMUX));
} else {
MostrarLog("Falha ao iniciar INA228");
}
}
I2CService::LiberarAcessoI2C(ID_Num);
}
void Desligar() {
if (!Iniciado) {
MostrarLog("Sensor nao esta inicializado");
return;
}
Iniciado = false;
MostrarLog("Sensor desligado");
}
void RequisitarDados() {
if (!Iniciado) return;
AferirCorrente();
}
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: {
uint16_t volts = (uint16_t)(busVoltage * 100.0f);
uint16_t amps = (uint16_t)(current / 10.0f);
uint16_t watts = (uint16_t)(power / 10.0f);
data.push_back(volts >> 8); data.push_back(volts & 0xFF);
data.push_back(amps >> 8); data.push_back(amps & 0xFF);
data.push_back(watts >> 8); data.push_back(watts & 0xFF);
break;
}
case CanMessagePosicaoDados::Dados2: {
if (isINA219) break;
uint16_t wh = (uint16_t)(energy / 10.0f);
uint16_t temp = (uint16_t)(temperature * 100.0f);
data.push_back(wh >> 8); data.push_back(wh & 0xFF);
data.push_back(temp >> 8); data.push_back(temp & 0xFF);
data.push_back(codAlarme >> 8); data.push_back(codAlarme & 0xFF);
break;
}
}
return data;
}
private:
bool DebugMode = true;
SimpleKalmanFilter filtro = SimpleKalmanFilter(10, 10, 0.05);
Adafruit_INA219 sINA219;
Adafruit_INA228 sINA228;
void MostrarLog(String mensagem) {
if (DebugMode) {
PrintTela("[COR " + ID + "] " + mensagem);
}
}
void ConfigurarShuntINA228() {
if (_A_Shunt == TiposShuntCorrente::INA228_10A) {
sINA228.setShunt(0.015, 10.0);
}
else if (_A_Shunt == TiposShuntCorrente::INA228_50A) {
sINA228.setShunt(0.0015, 50.0);
}
else if (_A_Shunt == TiposShuntCorrente::INA228_100A) {
sINA228.setShunt(0.00075, 100.0);
}
}
void AferirCorrente() {
if (!Iniciado) return;
if (!I2CService::SolicitarAcessoI2C(ID_Num, _CanalMUX)) return;
if (isINA219) {
busVoltage = sINA219.getBusVoltage_V();
float leituraCorrente = sINA219.getCurrent_mA();
current = filtro.updateEstimate(leituraCorrente);
power = sINA219.getPower_mW();
}
else {
busVoltage = sINA228.getBusVoltage_V();
float leituraCorrente = sINA228.getCurrent_mA();
current = filtro.updateEstimate(leituraCorrente);
power = sINA228.getPower_mW();
temperature = sINA228.readDieTemp();
energy = sINA228.readEnergy();
charge = sINA228.readCharge();
codAlarme = (uint16_t)LerRegistradorINA(0x0B, 2);
}
I2CService::LiberarAcessoI2C(ID_Num);
}
uint32_t LerRegistradorINA(uint8_t reg, uint8_t bytes) {
uint8_t buffer[4] = {0};
if (bytes > 4) return 0;
if (!I2CService::LeituraSegura(_Endereco, buffer, bytes, reg)) {
MostrarLog("Falha na leitura do registrador 0x" + String(reg, HEX));
return 0;
}
uint32_t valor = 0;
for (uint8_t i = 0; i < bytes; i++) {
valor = (valor << 8) | buffer[i];
}
return valor;
}
};
#endif

View File

@ -0,0 +1,136 @@
#include "SerialService.h"
#include "I2CService.h"
#include "Utils.h"
#include "IComponenteCAN.h"
#include <Wire.h>
#ifndef SensorMagneticoModel_v2
#define SensorMagneticoModel_v2
class SensorMagnetico_v2 : public ComponenteCAN {
public:
String ID;
uint8_t ID_Num = 0;
uint8_t _Endereco = 0x36;
int _CanalMUX = -1;
bool Iniciado = false;
uint16_t raw = 0;
float angle = 0;
SensorMagnetico_v2() {}
SensorMagnetico_v2(
const String& id,
uint8_t idNum,
uint8_t endereco = 0x36,
int canalMux = -1
) :
ID(id),
ID_Num(idNum),
_Endereco(endereco),
_CanalMUX(canalMux)
{
}
void Inicializar() {
if (Iniciado) {
MostrarLog("Sensor ja inicializado");
return;
}
if (!I2CService::SolicitarAcessoI2C(ID_Num, _CanalMUX)) {
MostrarLog("Falha ao solicitar acesso I2C");
return;
}
bool encontrado = I2CService::VerificaEnderecoBarramento(_Endereco);
Iniciado = encontrado;
if (!Iniciado) {
MostrarLog("Sensor nao iniciado, endereco " + String(_Endereco) + " nao encontrado");
} else {
MostrarLog("Sensor iniciado");
}
I2CService::LiberarAcessoI2C(ID_Num);
}
void Desligar() {
if (!Iniciado) {
MostrarLog("Sensor nao esta inicializado");
return;
}
Iniciado = false;
MostrarLog("Sensor desligado");
}
void RequisitarDados() {
if (!Iniciado) return;
AferirAngulo();
}
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: {
uint16_t ang100 = (uint16_t)round(angle * 100.0f);
data.push_back(raw >> 8);
data.push_back(raw & 0xFF);
data.push_back(ang100 >> 8);
data.push_back(ang100 & 0xFF);
break;
}
}
return data;
}
private:
bool DebugMode = true;
static const uint8_t REG_RAW_ANGLE = 0x0C;
void MostrarLog(String mensagem) {
if (DebugMode) {
PrintTela("[MAG " + ID + "] " + mensagem);
}
}
void AferirAngulo() {
if (!Iniciado) return;
if (!I2CService::SolicitarAcessoI2C(ID_Num, _CanalMUX)) return;
raw = LerRawAngle();
angle = RawToDegrees(raw);
I2CService::LiberarAcessoI2C(ID_Num);
}
uint16_t LerRawAngle() {
uint8_t buffer[2] = {0};
if (!I2CService::LeituraSegura(_Endereco, buffer, 2, REG_RAW_ANGLE)) {
MostrarLog("Falha na leitura do RAW_ANGLE");
return 0;
}
uint16_t valor = ((uint16_t)buffer[0] << 8) | buffer[1];
valor &= 0x0FFF;
return valor;
}
float RawToDegrees(uint16_t valorRaw) {
return (valorRaw * 360.0f) / 4096.0f;
}
};
#endif

View File

@ -0,0 +1,151 @@
#include "SerialService.h"
#include "Pinout.h"
#include "Utils.h"
#include "IComponenteCAN.h"
#include <SimpleKalmanFilter.h>
#ifndef SensorTemperaturaNtcModel
#define SensorTemperaturaNtcModel
class SensorTemperaturaNtc : public ComponenteCAN {
public:
String ID;
uint8_t ID_Num = 0;
PinoModel _pino;
int leitura = 0;
double temperatura = -1;
double tensao = 0;
bool Iniciado = false;
bool Checando = false;
float OffsetTemperatura = 0.0f;
SensorTemperaturaNtc() {}
SensorTemperaturaNtc(
const String& id,
uint8_t idNum,
int pino,
TiposBarramentos tipoBarramento,
int canalMux = -1,
float offsetTemperatura = 0.0f
) :
ID(id),
ID_Num(idNum),
_pino(PinoModel(pino, tipoBarramento, _INPUT, ANALOGICO, idNum, canalMux)),
OffsetTemperatura(offsetTemperatura)
{
}
void Inicializar() {
if (Iniciado) {
MostrarLog("Sensor ja inicializado");
return;
}
leitura = 0;
temperatura = -1;
tensao = 0;
_pino.Conectar();
Iniciado = _pino.Definido();
MostrarLog(Iniciado ? "Sensor iniciado" : "Sensor nao iniciado");
}
void Desligar() {
if (!Iniciado) {
MostrarLog("Sensor nao esta inicializado");
return;
}
_pino.Desconectar();
Iniciado = false;
MostrarLog("Sensor desligado");
}
void RequisitarDados() {
if (!Iniciado) return;
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: {
int16_t leituraInt = static_cast<int16_t>(round(temperatura * 100.0f));
data.push_back((uint8_t)(leitura >> 8));
data.push_back((uint8_t)(leitura & 0xFF));
data.push_back((uint8_t)(leituraInt >> 8));
data.push_back((uint8_t)(leituraInt & 0xFF));
break;
}
}
return data;
}
private:
bool DebugMode = true;
// SimpleKalmanFilter filtro = SimpleKalmanFilter(5, 15, 0.01);
void MostrarLog(String mensagem) {
if (DebugMode) {
PrintTela("[TMP " + ID + "] " + mensagem);
}
}
void AferirTemperatura() {
leitura = _pino.get(1);
// leitura = filtro.updateEstimate(leitura);
temperatura = CalcularTemperatura((float)leitura, OffsetTemperatura);
MostrarLog("Leitura: " + String(leitura) +
", Tensao: " + String(tensao) +
", Temperatura: " + String(temperatura));
}
float CalcularTemperatura(float leituraAdc, float offset = 0.0f) {
const float Vs = 5.0f;
const float Beta = 3950.0f;
const float To = 298.15f;
const float Ro = 10000.0f;
const float adcMax = 4095.0f;
static constexpr float divisor = 1.0f;
static constexpr float vOffset = (2.516f / 3.036f);
if (leituraAdc < 200.0f) return -1.0f;
float Vadc = leituraAdc / adcMax * Vs;
Vadc = Vadc * divisor * vOffset;
tensao = Vadc;
if (Vadc <= 0.001f) return -100.0f;
if (Vadc >= Vs) return NAN;
float Rt = Ro * (Vadc / (Vs - Vadc));
float T = 1.0f / (1.0f / To + log(Rt / Ro) / Beta);
float Tc = T - 273.15f + offset;
return roundf(Tc * 100.0f) / 100.0f;
}
};
#endif

View File

@ -0,0 +1,128 @@
#include "SerialService.h"
#include "Pinout.h"
#include "Utils.h"
#include "IComponenteCAN.h"
#include <ESP32Servo.h>
#ifndef ServoAgroModel_v2
#define ServoAgroModel_v2
class ServoAgro_v2 : public ComponenteCAN {
public:
String ID;
uint8_t ID_Num = 0;
bool Incremental = false;
bool Iniciado = false;
PinoModel _pino;
ServoAgro_v2() {}
ServoAgro_v2(
const String& id,
uint8_t idNum,
bool incremental,
int pino,
TiposBarramentos tipoBarramento
) :
ID(id),
ID_Num(idNum),
Incremental(incremental),
_pino(PinoModel(pino, tipoBarramento, _OUTPUT))
{
}
void Inicializar() {
if (Iniciado) {
MostrarLog("Servo ja inicializado");
return;
}
if (_pino.Definido()) {
_Servo.attach(_pino.num);
AtualizarAngulo(Incremental ? 0 : 180);
Iniciado = true;
MostrarLog("Servo iniciado");
}
else {
MostrarLog("Servo nao iniciado");
}
}
void Desligar() {
if (!Iniciado) {
MostrarLog("Servo nao esta inicializado");
return;
}
if (_pino.Definido()) {
_Servo.detach();
}
MostrarLog("Servo Desligado");
Iniciado = false;
}
void RequisitarDados() {
if (!Iniciado) return;
}
void ProcessarComando(std::vector<uint8_t>& data) {
if (!Iniciado) return;
if (data.size() < 3) return;
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[0];
uint8_t idNum = data[1];
if (idNum != ID_Num) return;
switch (posicao) {
case CanMessagePosicaoDados::Command1: {
int novoAngulo = (int)data[2];
AtualizarAngulo(novoAngulo);
break;
}
}
}
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(static_cast<uint8_t>(_Angulo));
break;
}
}
return data;
}
private:
bool DebugMode = true;
Servo _Servo;
int _Angulo = 0;
void AtualizarAngulo(int novoAngulo) {
novoAngulo = constrain(novoAngulo, 0, 180);
MostrarLog("Angulo atualizado de " + String(_Angulo) + " para " + String(novoAngulo));
_Angulo = novoAngulo;
_Servo.write(_Angulo);
}
void MostrarLog(String mensagem) {
if (DebugMode) {
PrintTela("[SRV " + ID + "] " + mensagem);
}
}
};
#endif

View File

@ -0,0 +1,153 @@
#include "SerialService.h"
#include "Pinout.h"
#include "Utils.h"
#include "IComponenteCAN.h"
#ifndef SinaleiroModel
#define SinaleiroModel
class Sinaleiro_v2 : public ComponenteCAN {
public:
String ID;
uint8_t ID_Num = 0;
PinoModel _pino;
bool Iniciado = false;
int _StatusID = 0;
StatusLED _Status = Apagado;
StatusLED _StatusLeitura = Apagado;
int _Qtd = 0;
int _TempoMs = 0;
int _PausaMs = 0;
// controle interno (não era exposto antes)
bool _estadoAtual = false;
int _piscadasAtual = 0;
bool _emPausa = false;
unsigned long _lastMillis = 0;
Sinaleiro_v2() {}
Sinaleiro_v2(
const String& id,
uint8_t idNum,
int pino,
TiposBarramentos tipoBarramento
) :
ID(id),
ID_Num(idNum),
_pino(PinoModel(pino, tipoBarramento, _OUTPUT))
{}
void Inicializar() {
if (Iniciado) return;
if (_pino.Definido()) {
_pino.Conectar(false);
Iniciado = true;
}
}
void AtualizarEstado(unsigned long agora) {
if (!Iniciado) return;
if (_Status != StatusLED::Piscando) {
_pino.set(_Status == StatusLED::Aceso);
_StatusLeitura = _Status;
return;
}
// modo piscando
if (_piscadasAtual < _Qtd) {
if (!_emPausa && (agora - _lastMillis >= _TempoMs)) {
_estadoAtual = !_estadoAtual;
_pino.set(_estadoAtual);
_lastMillis = agora;
if (!_estadoAtual) {
_piscadasAtual++;
}
}
}
else {
// pausa
if (!_emPausa) {
_pino.set(false);
_emPausa = true;
_lastMillis = agora;
}
if (agora - _lastMillis >= _PausaMs) {
_emPausa = false;
_piscadasAtual = 0;
_lastMillis = agora;
}
}
_StatusLeitura = StatusLED::Piscando;
}
void ProcessarComando(std::vector<uint8_t>& data) {
if (!Iniciado) return;
if (data.size() < 7) return;
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[0];
uint8_t idNum = data[1];
if (idNum != ID_Num) return;
if (posicao == CanMessagePosicaoDados::Command1) {
_StatusID = data[2];
_Status = (StatusLED)data[3];
_Qtd = data[4];
_TempoMs = (uint16_t)data[5] * 10;
_PausaMs = (uint16_t)data[6] * 10;
// reset ciclo
_piscadasAtual = 0;
_emPausa = false;
_lastMillis = millis();
}
}
void RequisitarDados() {}
std::vector<uint8_t> MontarMensagemCAN(CanMessagePosicaoDados posicao) {
std::vector<uint8_t> data;
data.push_back((uint8_t)posicao);
data.push_back(ID_Num);
switch (posicao) {
case Status:
data.push_back(Iniciado ? 1 : 0);
break;
case Dados1:
data.push_back(_StatusID);
data.push_back(_StatusLeitura);
break;
}
return data;
}
};
class LedManager {
public:
std::vector<Sinaleiro_v2*> leds;
void Adicionar(Sinaleiro_v2* led) {
leds.push_back(led);
}
void Atualizar() {
unsigned long agora = millis();
for (auto* led : leds) {
if (led != nullptr && led->Iniciado) {
led->AtualizarEstado(agora);
}
}
}
};
#endif

View File

@ -0,0 +1,8 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
"version": "0.2.0",
"configurations": [
]
}

View File

@ -0,0 +1,416 @@
// Dispositivo: Sensoriamento
// Versão Firmware: 22
// Ultima atualização: 20/03/2026
// Atualização: Firmware mock
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\SerialService.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\CanService.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\EepromService.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\I2CService.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\Utils.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\Pinout.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\ServoAgroModel_v2.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\ReleModel_v2.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\SinaleiroModel_v2.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\SensorTemperaturaNtcModel.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\SensorCorrenteModel_v2.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\SensorMagneticoModel_v2.h"
// Configurações do CAN
uint8_t CanID = 0x11;
CanService canService(CanID);
//LoRaService loraService(&canService);
#define D_Code Sen
int VERSION = 22;
String Mod_ID = "Sen";
#define _pinoLED RGB_BUILTIN
bool Conectado = false;
bool Conectar = false;
int _TaxaAmostragem = 1000;
volatile bool RequisitarDados = false;
int latenciaLoop = 0;
int qtdEnviosStatus = 0;
int maxEnviosStatus = 5;
#pragma region SINALEIROS
Sinaleiro_v2 sLED_Manual("LEDMN", 21, 37, TiposBarramentos::CONTROLADOR);
Sinaleiro_v2 sLED_Automatico("LEDAT", 22, 35, TiposBarramentos::CONTROLADOR);
Sinaleiro_v2 sLED_Operacao("LEDOP", 23, 36, TiposBarramentos::CONTROLADOR);
Sinaleiro_v2* ObterSinaleiroPorId(uint8_t idNum) {
switch (idNum) {
case 21: return &sLED_Manual;
case 22: return &sLED_Automatico;
case 23: return &sLED_Operacao;
default: return nullptr;
}
}
LedManager ledManager;
void LedTask(void* pv) {
while (true) {
ledManager.Atualizar();
vTaskDelay(pdMS_TO_TICKS(10)); // 100 Hz
}
}
#pragma endregion
#pragma region SERVOS
ServoAgro_v2 sSRV_FreioET("FROET", 41, true, 45, TiposBarramentos::CONTROLADOR);
ServoAgro_v2 sSRV_FreioDT("FRODT", 43, false, 47, TiposBarramentos::CONTROLADOR);
ServoAgro_v2* ObterServoPorId(uint8_t idNum) {
switch (idNum) {
case 41: return &sSRV_FreioET;
case 43: return &sSRV_FreioDT;
default: return nullptr;
}
}
#pragma endregion
#pragma region RELES
Rele_v2 sRLE_CoolerIn("RLCIN", 81, true, 39, TiposBarramentos::CONTROLADOR);
Rele_v2 sRLE_CoolerOut("RLCOUT", 82, true, 40, TiposBarramentos::CONTROLADOR);
Rele_v2* ObterRelePorId(uint8_t idNum) {
switch (idNum) {
case 81: return &sRLE_CoolerIn;
case 82: return &sRLE_CoolerOut;
default: return nullptr;
}
}
#pragma endregion
#pragma region TEMPERATURA NTC
SensorTemperaturaNtc sNTC_MotorET("TMVET", 1, 0, TiposBarramentos::EXPANSOR_ADS, -1, 0.0f);
SensorTemperaturaNtc sNTC_MotorDT("TMVDT", 3, 1, TiposBarramentos::EXPANSOR_ADS, -1, 0.0f);
SensorTemperaturaNtc sNTC_MotorEF("TMVEF", 2, 2, TiposBarramentos::EXPANSOR_ADS, -1, 0.0f);
SensorTemperaturaNtc sNTC_MotorDF("TMVDF", 4, 3, TiposBarramentos::EXPANSOR_ADS, -1, 0.0f);
SensorTemperaturaNtc* ObterTempNtcPorId(uint8_t idNum) {
switch (idNum) {
case 1: return &sNTC_MotorET;
case 2: return &sNTC_MotorEF;
case 3: return &sNTC_MotorDT;
case 4: return &sNTC_MotorDF;
default: return nullptr;
}
}
#pragma endregion
#pragma region CORRENTE
SensorCorrente_v2 sCOR_3V0 ("COR_AB3V", 11, 0x40, TiposShuntCorrente::INA219_3A2, 5);
SensorCorrente_v2 sCOR_5V0 ("COR_AB5V", 12, 0x40, TiposShuntCorrente::INA219_3A2, 4);
SensorCorrente_v2 sCOR_7V2 ("COR_AB7V2", 13, 0x40, TiposShuntCorrente::INA219_3A2, 2);
SensorCorrente_v2 sCOR_12V ("COR_AB12V", 14, 0x40, TiposShuntCorrente::INA228_50A, 3);
SensorCorrente_v2 sCOR_19V ("COR_AB19V", 15, 0x40, TiposShuntCorrente::INA228_10A, 1);
SensorCorrente_v2 sCOR_24V ("COR_AB24V", 16, 0x40, TiposShuntCorrente::INA228_50A, 6);
SensorCorrente_v2 sCOR_36V ("COR_AB36V", 17, 0x40, TiposShuntCorrente::INA228_100A, 7);
SensorCorrente_v2* ObterSensorCorrentePorId(uint8_t idNum) {
switch (idNum) {
case 11: return &sCOR_3V0;
case 12: return &sCOR_5V0;
case 13: return &sCOR_7V2;
case 14: return &sCOR_12V;
case 15: return &sCOR_19V;
case 16: return &sCOR_24V;
case 17: return &sCOR_36V;
default: return nullptr;
}
}
#pragma endregion
#pragma region MAGNETICOS
SensorMagnetico_v2 sMAG_MotorET("MAG_ET", 91, 0x36, 1);
SensorMagnetico_v2 sMAG_MotorDT("MAG_DT", 92, 0x36, 2);
SensorMagnetico_v2 sMAG_MotorEF("MAG_EF", 93, 0x36, 3);
SensorMagnetico_v2 sMAG_MotorDF("MAG_DF", 94, 0x36, 4);
SensorMagnetico_v2* ObterSensorMagneticoPorId(uint8_t idNum) {
switch (idNum) {
case 91: return &sMAG_MotorET;
case 92: return &sMAG_MotorDT;
case 93: return &sMAG_MotorEF;
case 94: return &sMAG_MotorDF;
default: return nullptr;
}
}
#pragma endregion
void MostrarLog(String mensagem) {
bool _debugMode = false;
if (_debugMode) {
PrintTela(String("[SEN] ") + mensagem);
}
}
void inicializarDependenciasI2C() {
I2CService::IniciarI2C();
I2CService::IniciarMUX();
I2CService::IniciarMCP();
}
void setup() {
delay(2000);
inicializarDependenciasI2C();
sSRV_FreioET.Inicializar();
sSRV_FreioDT.Inicializar();
sRLE_CoolerIn.Inicializar();
sRLE_CoolerOut.Inicializar();
sLED_Manual.Inicializar();
sLED_Automatico.Inicializar();
sLED_Operacao.Inicializar();
sNTC_MotorET.Inicializar();
sNTC_MotorDT.Inicializar();
sNTC_MotorEF.Inicializar();
sNTC_MotorDF.Inicializar();
sCOR_3V0.Inicializar();
sCOR_5V0.Inicializar();
sCOR_7V2.Inicializar();
sCOR_12V.Inicializar();
sCOR_19V.Inicializar();
sCOR_24V.Inicializar();
sCOR_36V.Inicializar();
sMAG_MotorET.Inicializar();
sMAG_MotorDT.Inicializar();
sMAG_MotorEF.Inicializar();
sMAG_MotorDF.Inicializar();
ledManager.Adicionar(&sLED_Manual);
ledManager.Adicionar(&sLED_Automatico);
ledManager.Adicionar(&sLED_Operacao);
xTaskCreatePinnedToCore(LedTask, "LedTask", 3000, NULL, 1, NULL, tskNO_AFFINITY);
// Registrar callback para processar mensagens CAN recebidas
canService.setReceiveCallback([](int packetSize, int senderId, CanMessagePosicaoDados posicao, byte* data, int dataLength) {
std::vector<uint8_t> fullData;
fullData.push_back(static_cast<uint8_t>(posicao)); // Primeiro byte é sempre a posicao
for (int i = 0; i < dataLength; i++) {
fullData.push_back(data[i]);
}
ProcessarFrameCAN(posicao, fullData);
});
canService.begin();
}
void loop() {
if (!RequisitarDados) {
enviarLogsPeriodicamente();
}
delay(1);
}
// Função para enviar logs do sensor periodicamente
unsigned long ultimoEnvioLogs = 0;
const unsigned long intervaloEnvioLogs = 1000; // 1 segundo
void enviarLogsPeriodicamente() {
unsigned long tempoAtual = millis();
// Verifica se o intervalo de 5 segundos já passou
if (tempoAtual - ultimoEnvioLogs >= intervaloEnvioLogs) {
ultimoEnvioLogs = tempoAtual; // Atualiza o tempo do último envio
enviarDadosSensores(canService.ID_Num_sTOD, CanMessagePosicaoDados::DadosAll);
}
}
void enviarDadosSensores(uint8_t id_num, CanMessagePosicaoDados posicao) {
if (Conectado || posicao == CanMessagePosicaoDados::Status) {
unsigned long startLoop = millis();
bool enviarStatus = qtdEnviosStatus >= maxEnviosStatus && posicao != CanMessagePosicaoDados::Status;
bool todosDados = posicao == CanMessagePosicaoDados::DadosAll;
bool todosIDs = id_num == canService.ID_Num_sTOD;
if (id_num == canService.ID_Num_sMOD) {
EnviarDadosCAN(canService.MontarFrameReqStatusMod(D_Code, Conectado, VERSION));
return;
}
MostrarLog("Atualizando dados dos sensores...");
if (todosIDs && enviarStatus) {
EnviarDadosCAN(canService.MontarFrameReqStatusMod(D_Code, Conectado, VERSION));
}
ComponenteCAN::ProcessarComponenteCAN(sSRV_FreioET, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sSRV_FreioDT, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sRLE_CoolerIn, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sRLE_CoolerOut, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sLED_Manual, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sLED_Automatico, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sLED_Operacao, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sNTC_MotorET, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sNTC_MotorDT, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sNTC_MotorEF, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sNTC_MotorDF, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sCOR_3V0, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sCOR_5V0, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sCOR_7V2, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sCOR_12V, {CanMessagePosicaoDados::Dados1, CanMessagePosicaoDados::Dados2}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sCOR_19V, {CanMessagePosicaoDados::Dados1, CanMessagePosicaoDados::Dados2}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sCOR_24V, {CanMessagePosicaoDados::Dados1, CanMessagePosicaoDados::Dados2}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sCOR_36V, {CanMessagePosicaoDados::Dados1, CanMessagePosicaoDados::Dados2}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sMAG_MotorET, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sMAG_MotorDT, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sMAG_MotorEF, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
ComponenteCAN::ProcessarComponenteCAN(sMAG_MotorDF, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN);
if (enviarStatus) {
qtdEnviosStatus = 0;
}
else {
qtdEnviosStatus++;
}
latenciaLoop = millis() - startLoop;
if (posicao == CanMessagePosicaoDados::DadosAll) {
EnviarDadosCAN(canService.MontarFrameReqDadosFim(latenciaLoop));
}
MostrarLog("Ciclo concluido, latencia de loop: " + String(latenciaLoop));
}
}
void EnviarDadosCAN(std::vector<uint8_t> data) {
if (data.size() > 2) {
canService.adicionarMensagemFila(data);
}
}
void ProcessarFrameCAN(CanMessagePosicaoDados posicao, std::vector<uint8_t> data) {
if (data.size() < 1) return;
F_Code funcao = canService.FuncaoPorPosicao(posicao);
switch (funcao) {
case F_Code::ReqTx: ProcessarReq(data); break;
case F_Code::CmdTx: ProcessarCmd(data); break;
case F_Code::CfgTx: ProcessarCfg(data); break;
}
}
void ProcessarReq(std::vector<uint8_t> data) {
if (data.size() < 2) return;
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[0];
uint8_t id = data[1];
enviarDadosSensores(id, posicao);
}
void ProcessarCfg(std::vector<uint8_t> data) {
if (data.size() < 4) return;
std::vector<uint8_t> _chkRx;
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[0];
uint8_t id = data[1];
S_Code componente = DetectarComponente(id);
if (componente == S_Code::sMOD) {
switch (posicao) {
case CanMessagePosicaoDados::Config1: {
if (data.size() < 8) return;
bool conectar = data[2] == 1;
int taxa = data[3] * 10;
bool requisitar = data[4] == 1;
//loraService.AddressBase = data[5];
int pinoSDA = data[6];
int pinoSCL = data[7];
_TaxaAmostragem = taxa;
RequisitarDados = requisitar;
if (pinoSDA != PINO_INVALIDO && pinoSCL != PINO_INVALIDO) {
if (I2CService::DefinirPinos(pinoSDA, pinoSCL) || !I2CService::I2CIniciado) {
inicializarDependenciasI2C();
}
}
vTaskDelay(pdMS_TO_TICKS(100));
Conectado = conectar;
_chkRx = canService.MontarFrameReqStatusMod(D_Code, Conectado, VERSION);
MostrarLog("Configuracao do modulo concluida");
break;
}
}
}
EnviarDadosCAN(_chkRx);
}
void ProcessarCmd(std::vector<uint8_t> data) {
if (data.size() < 2) return;
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[0];
uint8_t id = data[1];
S_Code componente = DetectarComponente(id);
switch (componente) {
case sMOD: {
if (data.size() < 3) return;
switch (posicao) {
case CanMessagePosicaoDados::Command1: {
Estado estado = (Estado)data[2];
if (estado == Estado::Reset) {
delay(100);
esp_restart();
}
break;
}
}
break;
}
case sSRV: {
ServoAgro_v2* servo = ObterServoPorId(id);
if (servo == nullptr)
return;
servo->ProcessarComando(data);
break;
}
case sRLE: {
Rele_v2* rele = ObterRelePorId(id);
if (rele == nullptr)
return;
rele->ProcessarComando(data);
break;
}
case sLED: {
Sinaleiro_v2* sinaleiro = ObterSinaleiroPorId(id);
if (sinaleiro == nullptr)
return;
sinaleiro->ProcessarComando(data);
break;
}
default:
break;
}
}
S_Code DetectarComponente(uint8_t idNum) {
if (idNum == canService.ID_Num_sMOD) return S_Code::sMOD;
if (idNum == canService.ID_Num_sTOD) return S_Code::sTOD;
if (idNum == canService.ID_Num_sLRA) return S_Code::sLRA;
if (idNum >= 01 && idNum <= 10) return S_Code::sNTC;
if (idNum >= 11 && idNum <= 20) return S_Code::sCOR;
if (idNum >= 21 && idNum <= 30) return S_Code::sLED;
if (idNum >= 41 && idNum <= 50) return S_Code::sSRV;
if (idNum >= 81 && idNum <= 90) return S_Code::sRLE;
if (idNum >= 91 && idNum <= 100) return S_Code::sMAG;
return S_Code::sVZO;
}