SmartFarm/SOURCE/iot_Gateway/iot_gw_v15-2505160002/iot_gw_v15-2505160002.ino
2026-09-04 10:53:44 +09:00

1394 lines
39 KiB
C++

#define F_NUM "2506250001"
/*********************************************
IOTGW-OTA by qst
+ 20250625-버전관리_ 2506250001
+ 지온 센서 값 추가
+ PinChangeInterrupt.h 추가 (펄스 인터럽트 계산용)
- 기본 인터럽트 함수는 사용할수있는 인터럽트 핀이 맞지않아 사용불가
- ModbusRTUMaster.h 라이브러리와 충돌이 생겨서 해당 라이브러리안에 있는 SoftwareSerial 라이브러리 사용을 다 제거하고 사용함
- 펄스값 저장 필요, 노이즈로인한 보드 리셋 대비 - 펄스값이 인입될경우 SD카드에 저장
+ 20250521 - 버그수정
+ github test
+ V15_2505160001
- Ethernet.linkStatus 상태체크 추가
- 와치독 4초확인으로 셋팅 - 20250516
- <avr/wdt.h> - 20250428 와치독 추가
+ V14_2412170001
- mqtt sub (debugging)
- th sensor (debugging)
+ V13_2403132007
- farm sensor board
+ V13_2403122005
* farm only *** +
- ini file
- D8 ~ D15
- D_IN0/D_OUT0 emergency
- ph & nt sensor scan
- cd sensor (4-20mA)
+ V13_2403122004
- modbusRTUmaster z_filter (0,rx,0)
+ V13_2403122003
- NET_VEN control (bd_v2)
- true json topic
- D_OUT control speed doubled
+ V12_3c_2402260000
- mqtt reconnection
+ V12_3c
- OTA by LAN
- th sensor scan debugged (0,rx,0)
- simple task switch
- gw_server(?) removed
+ V10_3 (original)
*********************************************/
#include <avr/wdt.h> // 20250428 추가
#include <PinChangeInterrupt.h> // 20250528 추가
#include <MsTimer2.h>
#include <SD.h>
#include <IniFile.h>
#include <Ethernet.h>
#include <ArduinoOTA.h>
#include <PubSubClient.h>
#include "iotgw_cfg.h"
#include "iotgw_pin_v2.h"
#include "ModbusRTUMaster.h"
ModbusRTUMaster modbus_2(RS485_2, RS485_DE2);
ModbusRTUMaster modbus_3(RS485_3, RS485_DE3);
/*******************************************************/
/* setup variable */
/*******************************************************/
IPAddress subnet(255, 255, 255, 0);
IPAddress myDns (168, 126, 63, 1);
static int mqttPort = 1883;
const char* ota_user = "arduino"; // can not be changed by its bug
const char* ota_pass = "passpass";
/*******************************************************/
/* task table & general variable */
/*******************************************************/
void task_farm_emg();
void task_RS485_2_tx();
void task_RS485_2_rx();
void task_mqtt_dn();
void task_mqtt_up();
void task_http();
void task_io();
void task_ota();
void task_th_tx();
void task_th_rx();
void (*func_task[10])() = {
task_th_tx,
task_io,
task_mqtt_up,
task_th_rx,
0,//task_mqtt_dn,
task_RS485_2_tx,
task_http,
task_ota, // task_farm_emg
task_RS485_2_rx,
0,//task_mqtt_dn,
};
EthernetServer http_server(80);
int8_t task_count = 0;
bool timer1_fire = false;
bool Ethernet_reinitial = false;
uint8_t timeout_cnt = 0;
// --- 전역 변수 ---
const char* countFilename = "status.ini";
char rcvScheduleSeq[7];
char rcvtotalInIntervals[7];
volatile unsigned long pulseCount = 0; // 펄스 카운트 (ISR에서 사용되므로 volatile)
volatile unsigned long last_interrupt_time = 0; // 마지막으로 유효한 펄스가 들어온 시간
const unsigned long debounce_delay = 50; // 바운싱을 무시할 시간 (50ms)
unsigned long totalInIntervals = 0; // 총 펄스 카운트
unsigned long lastCalcTime = 0; // 마지막 카운트 계산 시간
const unsigned long CALC_INTERVAL = 1000; // 카운트 계산 주기 (밀리초)
unsigned long lastSavedPulseCount = 0; // 마지막으로 저장했던 카운트 값
void countPulse() { //테스트로 스위치를 연결했을경우 발생하는 바운싱 문제 해결용.
unsigned long interrupt_time = millis(); // 현재 시간을 측정
// 마지막 유효 펄스로부터 debounce_delay(50ms)보다 긴 시간이 지났는지 확인
if (interrupt_time - last_interrupt_time > debounce_delay) {
// 시간이 충분히 지났다면, 새로운 유효 펄스로 간주하고 카운트
pulseCount++;
// 현재 시간을 마지막 유효 펄스 시간으로 기록
last_interrupt_time = interrupt_time;
}
// 만약 50ms 이내에 또 인터럽트가 발생하면, 이 if문은 무시됩니다. (바운싱 무시)
}
/*
void countPulse() {
pulseCount++;
}
*/
void loadPulseCount();
void savePulseTotal(unsigned long total);
/*******************************************************/
/* setup & loop */
/*******************************************************/
void setup()
{
// 20250428 추가 / 20250516 와치독 제거 - 와치독 실행시 OTA 업데이트 안되는 증상 발현
byte mcusr_mirror = MCUSR; // 리셋 원인 저장
MCUSR = 0; // 레지스터 초기화
wdt_disable(); // 부트할 때 와치독 끄기
pinMode(nLED_USER, OUTPUT);
#ifdef bd_v2
pinMode(NET_VEN, OUTPUT);
digitalWrite(NET_VEN, HIGH);
pinMode(SD_SCSn, OUTPUT);
digitalWrite(SD_SCSn, HIGH);
#endif
pinMode(NET_RSTn, OUTPUT);
digitalWrite(NET_RSTn, HIGH);
pinMode(D_IN0, INPUT_PULLUP);
pinMode(D_IN1, INPUT_PULLUP);
pinMode(D_IN2, INPUT_PULLUP);
pinMode(D_IN3, INPUT_PULLUP);
pinMode(D_IN4, INPUT_PULLUP);
pinMode(D_IN5, INPUT_PULLUP);
pinMode(D_IN6, INPUT_PULLUP);
pinMode(D_IN7, INPUT_PULLUP);
pinMode(D_IN8, INPUT_PULLUP);
pinMode(D_IN9, INPUT_PULLUP);
pinMode(D_IN10, INPUT_PULLUP);
pinMode(D_IN11, INPUT_PULLUP);
pinMode(D_IN12, INPUT_PULLUP);
pinMode(D_IN13, INPUT_PULLUP);
pinMode(D_IN14, INPUT_PULLUP);
pinMode(D_IN15, INPUT_PULLUP);
pinMode(D_OUT0, OUTPUT);
pinMode(D_OUT1, OUTPUT);
pinMode(D_OUT2, OUTPUT);
pinMode(D_OUT3, OUTPUT);
pinMode(D_OUT4, OUTPUT);
pinMode(D_OUT5, OUTPUT);
pinMode(D_OUT6, OUTPUT);
pinMode(D_OUT7, OUTPUT);
#ifdef bd_v2
pinMode(D_OUT8, OUTPUT);
pinMode(D_OUT9, OUTPUT);
pinMode(D_OUT10, OUTPUT);
pinMode(D_OUT11, OUTPUT);
pinMode(D_OUT12, OUTPUT);
pinMode(D_OUT13, OUTPUT);
pinMode(D_OUT14, OUTPUT);
pinMode(D_OUT15, OUTPUT);
#endif
pinMode(RS485_DE2, OUTPUT);
pinMode(RS485_DE3, OUTPUT);
pinMode(PIN_RX0, INPUT_PULLUP);
pinMode(PIN_RX1, INPUT_PULLUP);
pinMode(PIN_RX2, INPUT_PULLUP);
pinMode(PIN_RX3, INPUT_PULLUP);
#ifdef dbg_serial
dbg_serial.begin(115200);
#endif
Serial1.begin(9600);
modbus_2.begin(9600);
modbus_3.begin(9600);
#ifdef bd_v2
pinMode(SD_DETn, INPUT_PULLUP);
if (digitalRead(SD_DETn)) {
dbg_println("sd: none");
}
else {
dbg_println("sd: detected");
}
ini_setup();
#endif
net_setup();
MsTimer2::set(100, timer1);
MsTimer2::start(); // simple task switch
// 리셋 원인 판별 20250428 추가
if (mcusr_mirror & (1 << PORF)) {
if (Serial1) {Serial1.println("mainlog_ Power-On Reset");}
}
else if (mcusr_mirror & (1 << EXTRF)) {
if (Serial1) {Serial1.println("mainlog_ External Reset Pin");}
}
else if (mcusr_mirror & (1 << BORF)) {
if (Serial1) {Serial1.println("mainlog_ Brown-Out Reset");}
}
else if (mcusr_mirror & (1 << WDRF)) {
if (Serial1) {Serial1.println("mainlog_ Watchdog-Reset 4S");}
}
else {
if (Serial1) {Serial1.println("mainlog_ Unknown Reset");}
}
// loadPulseCount(); 실시간 pulse값 전송으로 변경 _ 20250613
// EF (Open Collector NPN) 또는 EV (GND 연결) 타입은 펄스 시 LOW가 되므로 FALLING 엣지 감지
// ---------------------------------------- attachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN), countPulse, FALLING);
// attachInterrupt(digitalPinToInterrupt(D_IN1), countPulse, FALLING); // 인터럽트핀을 사용 안하고있어서 못씀(2, 3, 18, 19, 20, 21번 핀)
attachPCINT(digitalPinToPCINT(D_IN1), countPulse, FALLING); // ModbusRTUMaster.h 와 충돌 - SoftwareSerial 라이브러리 제거 후 사용
lastCalcTime = millis();
wdt_enable(WDTO_4S);
}
#ifdef bd_v2
void ini_setup()
{
const size_t bufferLen = 80;
char buffer[bufferLen];
const char *filename = "cfg_farm.ini";
// SPI.begin();
if (!SD.begin(SD_SCSn)) {
dbg_println("SD.begin() failed");
return;
}
IniFile ini(filename);
if (!ini.open()) {
dbg_println("ini.open() failed");
return;
}
if (!ini.validate(buffer, bufferLen)) {
dbg_print(ini.getFilename());
dbg_println(" not valid: ");
return;
}
if (ini.getMACAddress("network", "mac", buffer, bufferLen, mac)) {
dbg_print("mac=");
for(uint8_t i=0; i<6; i++) {
dbg_print_(mac[i], HEX);
if (i < 5) {
dbg_print(":");
}
}
dbg_println("");
}
if (ini.getIPAddress("network", "ip", buffer, bufferLen, ip)) {
dbg_print("ip=");
dbg_println(ip);
}
if (ini.getIPAddress("network", "gateway", buffer, bufferLen, gateway)) {
dbg_print("gateway=");
dbg_println(gateway);
}
// if (ini.getIPAddress("mqtt", "server", buffer, bufferLen, mqttServer)) {
if (ini.getValue("mqtt", "server", buffer, bufferLen, mqttServer, sizeof(mqttServer))) {
dbg_print("server=");
dbg_println(mqttServer);
}
if (ini.getValue("mqtt", "client", buffer, bufferLen, ClientID_NUM, sizeof(ClientID_NUM))) {
dbg_print("client=");
dbg_println(ClientID_NUM);
}
if (ini.getValue("topic", "head", buffer, bufferLen, topic_head, sizeof(topic_head))) {
dbg_print("head=");
dbg_println(topic_head);
}
if (ini.getValue("topic", "tail", buffer, bufferLen, topic_tail, sizeof(topic_tail))) {
dbg_print("tail=");
dbg_println(topic_tail);
}
SDIloggersensorNames[0] = "DEFAULT";
for(uint8_t i=1; i<=MAX_RS485_2_COUNT; i++) {
String key = "NO"+String(i);
if (ini.getValue("RS485-2", key.c_str(), buffer, sizeof(buffer))) {
SDIloggersensorNames[i] = String(buffer);
} else {
SDIloggersensorNames[i] = "";
}
}
ini.close();
}
#endif
void net_setup()
{
Ethernet_reinitial = true;
digitalWrite(NET_RSTn, LOW);
delay(50); // enough
digitalWrite(NET_RSTn, HIGH);
delay(100); // enough
Ethernet.init(NET_SCSn);
Ethernet.begin(mac, ip, myDns, gateway, subnet);
do {
delay(100); // blink fast if something wrong
digital_toggle(nLED_USER);
} while (Ethernet.hardwareStatus() == EthernetNoHardware);
http_server.begin();
SetMQTTInfo();
// OTA 시작 시 콜백: WDT 비활성화
ArduinoOTA.onStart([]() {
if (Serial1) {Serial1.println("mainlog_ OTA Start - Disabling WDT");}
wdt_disable(); // OTA 중에는 WDT 끔
});
ArduinoOTA.begin(Ethernet.localIP(), &ota_pass[0], &ota_pass[0], InternalStorage);
}
void timer1() { timer1_fire = true; }
void task_ota()
{
ArduinoOTA.poll();
task_farm_emg();
}
// total 값을 INI 파일에 저장
void savePulseTotal(unsigned long total) {
SD.remove(countFilename); // 아래 코드에서 기존 내역을 지우고 새로운 내용을 저장해야 되는데 추가가 되고있어서 지우는 로직 추가
// 파일을 쓰기 모드로 열면 기존 내용은 자동으로 삭제됩니다.
File configFile = SD.open(countFilename, FILE_WRITE);
if (configFile) {
// "TotalCount=값" 형식으로 파일에 데이터를 씁니다.
configFile.print("TotalCount");
configFile.print("=");
configFile.println(total); // println은 줄바꿈 문자를 포함합니다.
configFile.print("rcvScheduleSeq");
configFile.print("=");
configFile.println(rcvScheduleSeq); // println은 줄바꿈 문자를 포함합니다.
configFile.close(); // 파일 쓰기가 끝나면 반드시 닫아주어야 합니다.
if (Serial1) { // 시리얼 모니터에 결과 출력
Serial1.print("mainlog_ savePulseTotal_TotalCount: ");
Serial1.print(total);
Serial1.println(" ");
}
// 시리얼 모니터로 저장된 값 확인
//dbg_print("Pulse count saved: ");
//dbg_println(total);
} else {
//dbg_println("Error opening config file for writing.");
}
}
void loadPulseCount() {
File configFile = SD.open(countFilename, FILE_READ);
if (configFile) {
// 파일 내용이 "TotalCount=12345" 형식이라고 가정
if (configFile.find("TotalCount") && configFile.find("=")) {
// find 함수로 키워드를 찾은 후, parseInt로 숫자만 읽어옴
totalInIntervals = configFile.parseInt();
lastSavedPulseCount = totalInIntervals; // 저장한 값을 기록
if (Serial1) { // 시리얼 모니터에 결과 출력
Serial1.print("mainlog_ loadPulseCount_totalInIntervals: ");
Serial1.print(totalInIntervals);
Serial1.println(" ");
}
}
// "rcvScheduleSeq=값" 형식으로 파일 내용 읽기
if (configFile.find("rcvScheduleSeq") && configFile.find("=")) {
// '=' 다음부터 줄바꿈 문자('\n') 전까지 문자열을 읽어옵니다.
// rcvScheduleSeq 배열의 크기는 6이므로, 최대 5개의 문자와 1개의 NULL 문자를 저장할 수 있습니다.
int bytesRead = configFile.readBytesUntil('\n', rcvScheduleSeq, sizeof(rcvScheduleSeq) - 1);
rcvScheduleSeq[bytesRead] = '\0'; // 읽어온 문자열 끝에 NULL 문자를 추가하여 C-string으로 만듭니다.
// readBytesUntil은 개행 문자를 소비하지만, println으로 저장 시 \r\n이 들어갔다면 \r이 남아있을 수 있습니다.
// 필요하다면 문자열 끝의 \r 제거 로직 추가:
if (bytesRead > 0 && rcvScheduleSeq[bytesRead-1] == '\r') {
rcvScheduleSeq[bytesRead-1] = '\0';
}
if (Serial1) {
Serial1.print("mainlog_ loadPulseCount_rcvScheduleSeq: ");
Serial1.print(rcvScheduleSeq);
Serial1.println(" ");
}
}
configFile.close();
//dbg_print("Loaded value from SD card: ");
//dbg_println(totalInIntervals);
} else {
// 파일이 없는 경우, 처음 시작하는 것으로 간주
//dbg_println("Config file not found. Starting count from 0.");
}
}
void loop()
{
wdt_reset();
unsigned long currentTime = millis();
if (currentTime - lastCalcTime >= CALC_INTERVAL) {
unsigned long currentPulseCountSnapshot;
noInterrupts(); // 인터럽트 비활성화
currentPulseCountSnapshot = pulseCount;
pulseCount = 0; // 다음 계산을 위해 펄스 카운트 리셋
interrupts(); // 인터럽트 다시 활성화
totalInIntervals += currentPulseCountSnapshot; // 총 출수량 업데이트
/* 실시간 pulse값 전송으로 변경 _ 20250613
if (currentPulseCountSnapshot > 0) {
if (lastSavedPulseCount != totalInIntervals) { // 마지막으로 저장했던 값과 현재 값이 다를 경우에만 파일에 저장
savePulseTotal(totalInIntervals);
lastSavedPulseCount = totalInIntervals; // 저장한 값을 기록
}
if (Serial1) { // 시리얼 모니터에 결과 출력
Serial1.print("currentPulseCountSnapshot: ");
Serial1.print(currentPulseCountSnapshot);
Serial1.print(" , ");
Serial1.print("totalInIntervals : ");
Serial1.print(totalInIntervals);
Serial1.println(" ");
}
}
*/
lastCalcTime = currentTime;
}
unsigned long now = millis();
if (timer1_fire) {
timer1_fire = false;
if (func_task[task_count]) func_task[task_count]();
if (++task_count == 10) task_count = 0;
}
task_mqtt_dn();
}
/*******************************************************/
/* i/o & http */
/*******************************************************/
uint8_t DI[16] = {0,};
uint8_t DO[16] = {0,};
uint16_t AI[4] = {0,};
bool flag_emergency = false;
void task_io()
{
DI[0] = !digitalRead(D_IN0);
if (DI[0]) {
if (!flag_emergency) {
PORTA = 0x00; // D_OUT15 ~ 0
PORTC = 0x01; // D_OUT7 ~ 0
flag_emergency = true;
}
}
else {
if (flag_emergency) {
digitalWrite(D_OUT0, LOW);
flag_emergency = false;
}
}
DI[1] = !digitalRead(D_IN1);
DI[2] = !digitalRead(D_IN2);
DI[3] = !digitalRead(D_IN3);
DI[4] = !digitalRead(D_IN4);
DI[5] = !digitalRead(D_IN5);
DI[6] = !digitalRead(D_IN6);
DI[7] = !digitalRead(D_IN7);
DI[8] = !digitalRead(D_IN8);
DI[9] = !digitalRead(D_IN9);
DI[10] = !digitalRead(D_IN10);
DI[11] = !digitalRead(D_IN11);
DI[12] = !digitalRead(D_IN12);
DI[13] = !digitalRead(D_IN13);
DI[14] = !digitalRead(D_IN14);
DI[15] = !digitalRead(D_IN15);
DO[0] = digitalRead(D_OUT0);
DO[1] = digitalRead(D_OUT1);
DO[2] = digitalRead(D_OUT2);
DO[3] = digitalRead(D_OUT3);
DO[4] = digitalRead(D_OUT4);
DO[5] = digitalRead(D_OUT5);
DO[6] = digitalRead(D_OUT6);
DO[7] = digitalRead(D_OUT7);
#ifdef bd_v2
DO[8] = digitalRead(D_OUT8);
DO[9] = digitalRead(D_OUT9);
DO[10] = digitalRead(D_OUT10);
DO[11] = digitalRead(D_OUT11);
DO[12] = digitalRead(D_OUT12);
DO[13] = digitalRead(D_OUT13);
DO[14] = digitalRead(D_OUT14);
DO[15] = digitalRead(D_OUT15);
#endif
AI[0] = analogRead(A_IN0);
AI[1] = analogRead(A_IN1);
AI[2] = analogRead(A_IN2);
AI[3] = analogRead(A_IN3);
}
void task_http()
{
char s0[17];
char tmpS[100];
EthernetClient client = http_server.available();
if (client) {
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so you can send a reply
if (c == '\n' && currentLineIsBlank) {
// send a standard http response header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close"); // the connection will be closed after completion of the response
client.println("Refresh: 5"); // refresh the page automatically every 5 sec
client.println();
client.println("<!DOCTYPE HTML>");
client.println("<html>");
client.print("FWV=");
client.print(F_NUM);
client.println("<br />");
client.print("MAC=");
for(uint8_t i=0; i<6; i++) {
client.print(mac[i], HEX);
if (i < 5) client.print('-');
}
client.println("<br />");
client.print("MID=");
client.print(ClientID_NUM);
client.println("<br />");
uint8_t val1 = (DI[0]<<3)+(DI[1]<<2)+(DI[2]<<1)+DI[3];
uint8_t val2 = (DI[4]<<3)+(DI[5]<<2)+(DI[6]<<1)+DI[7];
uint8_t val3 = (DI[8]<<3)+(DI[9]<<2)+(DI[10]<<1)+DI[11];
uint8_t val4 = (DI[12]<<3)+(DI[13]<<2)+(DI[14]<<1)+DI[15];
client.print("D/I=");
client.print(val1, HEX);
client.print(val2, HEX);
client.print(val3, HEX);
client.print(val4, HEX);
client.println("<br />");
val1 = (DO[0]<<3)+(DO[1]<<2)+(DO[2]<<1)+DO[3];
val2 = (DO[4]<<3)+(DO[5]<<2)+(DO[6]<<1)+DO[7];
client.print("D/O=");
client.print(val1, HEX);
client.print(val2, HEX);
#ifdef bd_v2
val3 = (DO[8]<<3)+(DO[9]<<2)+(DO[10]<<1)+DO[11];
val4 = (DO[12]<<3)+(DO[13]<<2)+(DO[14]<<1)+DO[15];
client.print(val3, HEX);
client.print(val4, HEX);
#endif
client.println("<br />");
client.print("232=00"); // unused
client.println("<br />");
uint8_t state_ph = 0;
for(uint8_t i=1; i<=MAX_RS485_2_COUNT; i++) {
if (RS485_2_dog[i]) {
state_ph = 1;
break;
}
}
for(uint8_t i=1; i<=MAX_RS485_2_COUNT; i++) {
client.print("RS485-2_");
client.print(i);
client.print(" = ");
client.print(SDIloggersensorNames[i]);
client.println("<br />");
}
uint8_t state_th = 0;
for(uint8_t i=1; i<=MAX_TH_COUNT; i++) {
if (th_dog[i]) {
state_th = 1;
break;
}
}
client.print("485=");
client.print(state_ph, HEX);
client.print(state_th, HEX);
client.println("<br />");
client.println("</html>");
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
} else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
}
}
/*******************************************************/
/* th sensor */
/*******************************************************/
#define MAX_TEMP_CNT 10
uint8_t th_addr = 0;
uint16_t sHumid[11] = {0,};
int16_t sTemperature[11] = {0,};
void task_th_tx()
{
if (++th_addr > MAX_TEMP_CNT) th_addr = 1;
modbus_3.begin_readHoldingRegisters(th_addr, 0, 2);
}
void task_th_rx()
{
uint16_t th_val[2];
uint8_t ret = modbus_3.readHoldingRegisters_end(th_addr, th_val, 2);
if (ret) {
sHumid[th_addr] = th_val[0];
sTemperature[th_addr] = th_val[1];
th_dog[th_addr] = 10; // 10 * 5 sec
}
else {
if (th_dog[th_addr]) {
if (--th_dog[th_addr] == 0) { // saturated
sHumid[th_addr] = 0;
sTemperature[th_addr] = 0;
}
}
}
}
uint8_t ph_addr = 0;
uint16_t sPH[6] = {0,}; // PH - QSENTECH
uint16_t sNT[6] = {0,}; // EC - QSENTECH
uint16_t sCD[6] = {0,}; // CO2 - QSENTECH
uint16_t sGT[6] = {0,}; // 지온 - TEROS12
uint16_t sGM[6] = {0,}; // 지습 - TEROS12
uint16_t sGNT[6] = {0,};// EC - TEROS12
uint16_t sWP[6] = {0,}; // WP - 수분장력 - TEROS21
uint16_t sGT2[6] = {0,}; // 지온 - TEROS21
void task_farm_emg()
{
if (flag_emergency) {
digital_toggle(D_OUT0);
}
}
void task_RS485_2_tx()
{
if (++ph_addr > MAX_RS485_2_COUNT) ph_addr = 1;
if (ph_addr == 1) {
RS485_2_TEROS12_Count = 0;
RS485_2_TEROS21_Count = 0;
RS485_2_QSENTECH_Count = 0;
}
if (SDIloggersensorNames[ph_addr] == "TEROS12") {
RS485_2_TEROS12_Count++;
modbus_2.begin_readHoldingRegisters(ph_addr, 0, 3); // 지온,지습,EC
} else
if (SDIloggersensorNames[ph_addr] == "TEROS21") {
RS485_2_TEROS21_Count++;
modbus_2.begin_readHoldingRegisters(ph_addr, 0, 2); // 수분장력,지온
} else
if (SDIloggersensorNames[ph_addr] == "QSENTECH") {
RS485_2_QSENTECH_Count++;
modbus_2.begin_readHoldingRegisters(ph_addr, 0, 3); // ph, ec & co2
} else { // 기본값 QSENTECH
RS485_2_QSENTECH_Count++;
modbus_2.begin_readHoldingRegisters(ph_addr, 0, 3); // ph, ec & co2
}
}
void task_RS485_2_rx()
{
uint16_t RS485_2_val[3];
if (SDIloggersensorNames[ph_addr] == "TEROS12") {
uint8_t ret = modbus_2.readHoldingRegisters_end(ph_addr, RS485_2_val, 3);
if (ret) {
sGT[RS485_2_TEROS12_Count] = RS485_2_val[0] / 10;
sGM[RS485_2_TEROS12_Count] = RS485_2_val[1] / 10;
sGNT[RS485_2_TEROS12_Count] = RS485_2_val[2];
RS485_2_dog[ph_addr] = 10; // 10 * 5 sec
}
else {
if (RS485_2_dog[ph_addr]) {
if (--RS485_2_dog[ph_addr] == 0) { // saturated
sGT[RS485_2_TEROS12_Count] = 0;
sGM[RS485_2_TEROS12_Count] = 0;
sGNT[RS485_2_TEROS12_Count] = 0;
}
}
}
} else
if (SDIloggersensorNames[ph_addr] == "TEROS21") {
uint8_t ret = modbus_2.readHoldingRegisters_end(ph_addr, RS485_2_val, 2);
if (ret) {
sWP[RS485_2_TEROS21_Count] = RS485_2_val[0] / 100;
sGT2[RS485_2_TEROS21_Count] = RS485_2_val[1] ;
RS485_2_dog[ph_addr] = 10; // 10 * 5 sec
}
else {
if (RS485_2_dog[ph_addr]) {
if (--RS485_2_dog[ph_addr] == 0) { // saturated
sWP[RS485_2_TEROS21_Count] = 0;
sGT2[RS485_2_TEROS21_Count] = 0;
}
}
}
} else
if (SDIloggersensorNames[ph_addr] == "QSENTECH") {
uint8_t ret = modbus_2.readHoldingRegisters_end(ph_addr, RS485_2_val, 3);
if (ret) {
sPH[RS485_2_QSENTECH_Count] = RS485_2_val[0];
sNT[RS485_2_QSENTECH_Count] = RS485_2_val[1];
sCD[RS485_2_QSENTECH_Count] = RS485_2_val[2];
RS485_2_dog[ph_addr] = 10; // 10 * 5 sec
}
else {
if (RS485_2_dog[ph_addr]) {
if (--RS485_2_dog[ph_addr] == 0) { // saturated
sPH[RS485_2_QSENTECH_Count] = 0;
sNT[RS485_2_QSENTECH_Count] = 0;
sCD[RS485_2_QSENTECH_Count] = 0;
}
}
}
} else { // 기본값 QSENTECH
uint8_t ret = modbus_2.readHoldingRegisters_end(ph_addr, RS485_2_val, 3);
if (ret) {
sPH[RS485_2_QSENTECH_Count] = RS485_2_val[0];
sNT[RS485_2_QSENTECH_Count] = RS485_2_val[1];
sCD[RS485_2_QSENTECH_Count] = RS485_2_val[2];
RS485_2_dog[ph_addr] = 10; // 10 * 5 sec
}
else {
if (RS485_2_dog[ph_addr]) {
if (--RS485_2_dog[ph_addr] == 0) { // saturated
sPH[RS485_2_QSENTECH_Count] = 0;
sNT[RS485_2_QSENTECH_Count] = 0;
sCD[RS485_2_QSENTECH_Count] = 0;
}
}
}
}
}
/*******************************************************/
/* mqtt up/dn */
/*******************************************************/
EthernetClient ethClient;
PubSubClient mqttClient(mqttServer, mqttPort, callback_, ethClient);
void task_mqtt_dn()
{
{
mqttClient.loop();
}
}
char UP_TOPICNAME[128];
char DN_TOPICNAME[128];
unsigned long lastReconnectAttempt = 0;
void task_mqtt_up()
{
char s0[32];
char tmpS[100];
char sndbuf[100];
char sndPacket[512];
sndPacket[0] = 0;
// Digital Input
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=0; i<16; i++) {
s0[0] = 0;
if (i == 1) {
sprintf(s0, "%d|", totalInIntervals);
}
else {
sprintf(s0, "%d|", DI[i]);
}
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "{\x22\x44I\x22:\x22%s\x22,", tmpS); // "D
strcat(sndPacket, sndbuf);
// Digital Output
sndbuf[0] = 0;
tmpS[0] = 0;
#ifdef bd_v2
for(uint8_t i=0; i<16; i++) {
#else
for(uint8_t i=0; i<8; i++) {
#endif
s0[0] = 0;
sprintf(s0, "%d|", DO[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22\x44O\x22:\x22%s\x22,", tmpS); // "D
strcat(sndPacket, sndbuf);
// Analog Input
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=0; i<4; i++) {
s0[0] = 0;
sprintf(s0, "%d|", AI[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22\x41I\x22:\x22%s\x22,", tmpS); // "A
strcat(sndPacket, sndbuf);
// H Input
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=1; i<=MAX_TH_COUNT; i++) {
s0[0] = 0;
sprintf(s0, "%d|", sHumid[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22HM\x22:\x22%s\x22,", tmpS);
strcat(sndPacket, sndbuf);
// T Input
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=1; i<=MAX_TH_COUNT; i++) {
s0[0] = 0;
sprintf(s0, "%d|", sTemperature[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22TM\x22:\x22%s\x22,", tmpS);
strcat(sndPacket, sndbuf);
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=1; i<=MAX_RS485_2_COUNT; i++) {
s0[0] = 0;
sprintf(s0, "%d|", sCD[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22\x43\x44\x22:\x22%s\x22,", tmpS); // "CD
strcat(sndPacket, sndbuf);
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=1; i<=MAX_RS485_2_COUNT; i++) {
s0[0] = 0;
sprintf(s0, "%d|", sNT[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22NT\x22:\x22%s\x22,", tmpS);
strcat(sndPacket, sndbuf);
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=1; i<=MAX_RS485_2_COUNT; i++) {
s0[0] = 0;
sprintf(s0, "%d|", sPH[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22PH\x22:\x22%s\x22,", tmpS);
strcat(sndPacket, sndbuf);
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=1; i<=MAX_RS485_2_COUNT; i++) {
s0[0] = 0;
sprintf(s0, "%d|", sGT[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22GT\x22:\x22%s\x22,", tmpS);
strcat(sndPacket, sndbuf);
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=1; i<=MAX_RS485_2_COUNT; i++) {
s0[0] = 0;
sprintf(s0, "%d|", sGM[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22GM\x22:\x22%s\x22,", tmpS);
strcat(sndPacket, sndbuf);
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=1; i<=MAX_RS485_2_COUNT; i++) {
s0[0] = 0;
sprintf(s0, "%d|", sGNT[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22GNT\x22:\x22%s\x22,", tmpS);
strcat(sndPacket, sndbuf);
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=1; i<=MAX_RS485_2_COUNT; i++) {
s0[0] = 0;
sprintf(s0, "%d|", sWP[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22WP\x22:\x22%s\x22,", tmpS);
strcat(sndPacket, sndbuf);
sndbuf[0] = 0;
tmpS[0] = 0;
for(uint8_t i=1; i<=MAX_RS485_2_COUNT; i++) {
s0[0] = 0;
sprintf(s0, "%d|", sGT2[i]);
strcat(tmpS, s0);
}
tmpS[strlen(tmpS)-1] = 0;
sprintf(sndbuf, "\x22GT2\x22:\x22%s\x22}", tmpS);
strcat(sndPacket, sndbuf);
/* 스케쥴번호 사용 제거 _ 20250613
sndbuf[0] = 0;
sprintf(sndbuf, "\x22SC\x22:\x22%s\x22}", rcvScheduleSeq);
strcat(sndPacket, sndbuf);
*/
unsigned long now = millis();
if (mqttClient.connected()) {
mqttClient.publish(UP_TOPICNAME, sndPacket);
totalInIntervals = 0;
digital_toggle(nLED_USER);
lastReconnectAttempt = now;
}
else {
if (now - lastReconnectAttempt > 2000) { // 마지막 MQTT publish 와 2초 이상 차이나면 mqtt or LAN 리셋
lastReconnectAttempt = now;
if ((!Ethernet_reinitial) && (Ethernet.linkStatus() != LinkON)) {
// 링크 문제 있을 때만 LAN 리셋
#ifdef bd_v2
digitalWrite(NET_VEN, LOW);
delay(100);
digitalWrite(NET_VEN, HIGH);
#endif
if (Serial1) Serial1.print("mainlog_ Ethernet.linkStatus - FALSE / Millis : ");
if (Serial1) Serial1.println(now);
net_setup();
} else {
// 링크는 OK → MQTT만 재연결 시도
beginConnection();
}
}
}
}
char ClientID[16];
boolean beginConnection()
{
unsigned long now = millis();
if(mqttClient.connect(ClientID, mqtt_user, mqtt_pass))
{
mqttClient.subscribe(DN_TOPICNAME);
if (Serial1) Serial1.print("mainlog_ MQTT Connected & Subscribed re_cnt : ");
if (Serial1) Serial1.print(timeout_cnt);
if (Serial1) Serial1.print(" / Millis : ");
if (Serial1) Serial1.println(now);
if (Serial1) Serial1.println("mainlog_ ---------------------------------------------------------------------------------");
timeout_cnt = 0;
Ethernet_reinitial = false;
return true; // 연결 성공
}
else {
if (timeout_cnt > 1) { //세번 재접속 실패시 리셋 -> 2번 시도 / 로그확인결과 1,2번째 재접속 시도시 붙은 적이 없음 세번까지 재접속할 필요 없음
mqttClient.disconnect();
#ifdef bd_v2
digitalWrite(NET_VEN, LOW);
delay(100);
digitalWrite(NET_VEN, HIGH);
#endif
net_setup();
timeout_cnt = 0;
if (Serial1) Serial1.print("mainlog_Ethernet reinitialized (Millis : ");
if (Serial1) Serial1.print(now);
if (Serial1) Serial1.println(")");
} else {
if (Serial1) Serial1.print("mainlog_ MQTT not Connected re_cnt : ");
if (Serial1) Serial1.print(timeout_cnt);
if (Serial1) Serial1.print(" / Millis : ");
if (Serial1) Serial1.println(now);
timeout_cnt++;
return false; // 연결 실패
}
}
}
char rcvPacket[128];
int rcv_cnt = 0;
int step = 0;
String p_str = "";
char p_val;
void relay_on_00() {digitalWrite(D_OUT0, HIGH);}
void relay_on_01() {digitalWrite(D_OUT1, HIGH);}
void relay_on_02() {digitalWrite(D_OUT2, HIGH);}
void relay_on_03() {digitalWrite(D_OUT3, HIGH);}
void relay_on_04() {digitalWrite(D_OUT4, HIGH);}
void relay_on_05() {digitalWrite(D_OUT5, HIGH);}
void relay_on_06() {digitalWrite(D_OUT6, HIGH);}
void relay_on_07() {digitalWrite(D_OUT7, HIGH);}
void relay_on_08() {digitalWrite(D_OUT8, HIGH);}
void relay_on_09() {digitalWrite(D_OUT9, HIGH);}
void relay_on_10() {digitalWrite(D_OUT10, HIGH);}
void relay_on_11() {digitalWrite(D_OUT11, HIGH);}
void relay_on_12() {digitalWrite(D_OUT12, HIGH);}
void relay_on_13() {digitalWrite(D_OUT13, HIGH);}
void relay_on_14() {digitalWrite(D_OUT14, HIGH);}
void relay_on_15() {digitalWrite(D_OUT15, HIGH);}
void relay_off_00() {digitalWrite(D_OUT0, LOW);}
void relay_off_01() {digitalWrite(D_OUT1, LOW);}
void relay_off_02() {digitalWrite(D_OUT2, LOW);}
void relay_off_03() {digitalWrite(D_OUT3, LOW);}
void relay_off_04() {digitalWrite(D_OUT4, LOW);}
void relay_off_05() {digitalWrite(D_OUT5, LOW);}
void relay_off_06() {digitalWrite(D_OUT6, LOW);}
void relay_off_07() {digitalWrite(D_OUT7, LOW);}
void relay_off_08() {digitalWrite(D_OUT8, LOW);}
void relay_off_09() {digitalWrite(D_OUT9, LOW);}
void relay_off_10() {digitalWrite(D_OUT10, LOW);}
void relay_off_11() {digitalWrite(D_OUT11, LOW);}
void relay_off_12() {digitalWrite(D_OUT12, LOW);}
void relay_off_13() {digitalWrite(D_OUT13, LOW);}
void relay_off_14() {digitalWrite(D_OUT14, LOW);}
void relay_off_15() {digitalWrite(D_OUT15, LOW);}
void (*func_relay_on[16])() = {
relay_on_00,
relay_on_01,
relay_on_02,
relay_on_03,
relay_on_04,
relay_on_05,
relay_on_06,
relay_on_07,
relay_on_08,
relay_on_09,
relay_on_10,
relay_on_11,
relay_on_12,
relay_on_13,
relay_on_14,
relay_on_15,
};
void (*func_relay_off[16])() = {
relay_off_00,
relay_off_01,
relay_off_02,
relay_off_03,
relay_off_04,
relay_off_05,
relay_off_06,
relay_off_07,
relay_off_08,
relay_off_09,
relay_off_10,
relay_off_11,
relay_off_12,
relay_off_13,
relay_off_14,
relay_off_15,
};
#if 1
void relay_on(uint8_t p)
{
if (p_val == '1') func_relay_on[p]();
else if (p_val == '0') func_relay_off[p]();
}
#else
void relay_on(uint8_t p)
{
if (p_val == '1') {
if (p==0) digitalWrite(D_OUT0, HIGH);
else if (p==1) digitalWrite(D_OUT1, HIGH);
else if (p==2) digitalWrite(D_OUT2, HIGH);
else if (p==3) digitalWrite(D_OUT3, HIGH);
else if (p==4) digitalWrite(D_OUT4, HIGH);
else if (p==5) digitalWrite(D_OUT5, HIGH);
else if (p==6) digitalWrite(D_OUT6, HIGH);
else if (p==7) digitalWrite(D_OUT7, HIGH);
#ifdef bd_v2
else if (p==8) digitalWrite(D_OUT8, HIGH);
else if (p==9) digitalWrite(D_OUT9, HIGH);
else if (p==10) digitalWrite(D_OUT10, HIGH);
else if (p==11) digitalWrite(D_OUT11, HIGH);
else if (p==12) digitalWrite(D_OUT12, HIGH);
else if (p==13) digitalWrite(D_OUT13, HIGH);
else if (p==14) digitalWrite(D_OUT14, HIGH);
else if (p==15) digitalWrite(D_OUT15, HIGH);
#endif
}
else if (p_val == '0') {
if (p==0) digitalWrite(D_OUT0, LOW);
else if (p==1) digitalWrite(D_OUT1, LOW);
else if (p==2) digitalWrite(D_OUT2, LOW);
else if (p==3) digitalWrite(D_OUT3, LOW);
else if (p==4) digitalWrite(D_OUT4, LOW);
else if (p==5) digitalWrite(D_OUT5, LOW);
else if (p==6) digitalWrite(D_OUT6, LOW);
else if (p==7) digitalWrite(D_OUT7, LOW);
#ifdef bd_v2
else if (p==8) digitalWrite(D_OUT8, LOW);
else if (p==9) digitalWrite(D_OUT9, LOW);
else if (p==10) digitalWrite(D_OUT10, LOW);
else if (p==11) digitalWrite(D_OUT11, LOW);
else if (p==12) digitalWrite(D_OUT12, LOW);
else if (p==13) digitalWrite(D_OUT13, LOW);
else if (p==14) digitalWrite(D_OUT14, LOW);
else if (p==15) digitalWrite(D_OUT15, LOW);
#endif
}
}
#endif
void callback_(char* topic, byte* payload, unsigned int length)
{
if (flag_emergency) return;
if (length == 29) {
/* 실시간 pulse 값 전송으로 변경 _ 20250613
if (((char)payload[2] == 'S') and
((char)payload[3] == 'C') and
((char)payload[16] == 'P') and
((char)payload[17] == 'S')) {
rcvScheduleSeq[0] = 0;
for (int i = 0; i < 6; i++) {
char c = (char)payload[i+7];
rcvScheduleSeq[i] = c;
}
rcvScheduleSeq[6] = '\0';
rcvtotalInIntervals[0] = 0;
for (int i = 0; i < 6; i++) {
char c = (char)payload[i+21];
rcvtotalInIntervals[i] = c;
}
rcvtotalInIntervals[6] = '\0';
char *endptr; // 오류 검사를 위해 사용 가능
totalInIntervals = strtoul(rcvtotalInIntervals, &endptr, 10);
savePulseTotal(totalInIntervals);
}
*/
}
else {
// handle message arrived
for (int i = 0; i < length; i++) {
char c = (char)payload[i];
switch(step) {
case 1 :
if (c == '}') {
step = 2;
}
else {
if(rcv_cnt > sizeof(rcvPacket)) { // overflow
step = 0;
break;
}
rcvPacket[rcv_cnt] = c;
rcv_cnt++;
break;
}
case 2 :
if (!(rcvPacket[1] == 'D') || !(rcvPacket[2] == 'O')) {
step = 0;
break;
}
#if 1 // issue 241227
if (rcvPacket[4] == ':') {
for(uint8_t p=0; p<DO_NUM; p++) {
p_val = rcvPacket[6+2*p];
relay_on(p);
}
}
else if ((rcvPacket[3] == '_') && (rcvPacket[7] == ':')) {
#if 1 // issue 241227
uint8_t p = 0;
if (rcvPacket[4] == '1') p = 10;
p += (rcvPacket[5] - '0');
if (p <= 15) {
p_val = rcvPacket[9];
relay_on(p);
}
#else
p_str = &rcvPacket[4];
rcvPacket[6] = 0;
p_val = rcvPacket[9];
relay_on(p_str.toInt());
#endif
}
#endif
step = 0;
break;
case 0:
if (c == '{') {
step = 1;
rcv_cnt = 0;
rcvPacket[0] = 0;
}
break;
}
}
}
}
void SetMQTTInfo()
{
mqttClient.setBufferSize(512); // default 256
String _ClientID = "GWID_"+(String)ClientID_NUM;
_ClientID.toCharArray(ClientID, sizeof(ClientID));
String _UP_TOPICNAME = (String)topic_head+"PUB/"+(String)topic_tail+(String)ClientID_NUM;
_UP_TOPICNAME.toCharArray(UP_TOPICNAME, sizeof(UP_TOPICNAME));
String _DN_TOPICNAME = (String)topic_head+"SUB/"+(String)topic_tail+(String)ClientID_NUM;
_DN_TOPICNAME.toCharArray(DN_TOPICNAME, sizeof(DN_TOPICNAME));
}
/*eof*/