#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 - - 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 // 20250428 추가 #include // 20250528 추가 #include #include #include #include #include #include #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(""); client.println(""); client.print("FWV="); client.print(F_NUM); client.println("
"); client.print("MAC="); for(uint8_t i=0; i<6; i++) { client.print(mac[i], HEX); if (i < 5) client.print('-'); } client.println("
"); client.print("MID="); client.print(ClientID_NUM); client.println("
"); 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("
"); 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("
"); client.print("232=00"); // unused client.println("
"); 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("
"); } 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("
"); client.println(""); 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