commit 099deac6417002ecf85ea2cdfaf564d666143e17 Author: jhw Date: Fri Sep 4 10:15:33 2026 +0900 First upload diff --git a/SOURCE/kocom_Homenet_D10.4/Docs/20260628_코콤_홈넷_미세먼지_통신로직_시뮬레이션_및_검증보고서.docx b/SOURCE/kocom_Homenet_D10.4/Docs/20260628_코콤_홈넷_미세먼지_통신로직_시뮬레이션_및_검증보고서.docx new file mode 100644 index 0000000..e939336 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/Docs/20260628_코콤_홈넷_미세먼지_통신로직_시뮬레이션_및_검증보고서.docx differ diff --git a/SOURCE/kocom_Homenet_D10.4/Docs/20260828_0_화면 콤보박스(ComboBox) 기반 MODBUS MariaDB 듀얼 수집 모드 구현 계획.txt b/SOURCE/kocom_Homenet_D10.4/Docs/20260828_0_화면 콤보박스(ComboBox) 기반 MODBUS MariaDB 듀얼 수집 모드 구현 계획.txt new file mode 100644 index 0000000..3dafcd7 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Docs/20260828_0_화면 콤보박스(ComboBox) 기반 MODBUS MariaDB 듀얼 수집 모드 구현 계획.txt @@ -0,0 +1,93 @@ +# 화면 콤보박스(ComboBox) 기반 MODBUS / MariaDB 듀얼 수집 모드 구현 계획 + +화면(UI) 상단에 **수집 방식 선택 콤보박스(ComboBox)**를 배치하여, 사용자가 프로그램 화면에서 직접 **MODBUS TCP 직접 수집** 또는 **MariaDB 연동 수집**을 손쉽게 선택·전환할 수 있도록 설계한 구현 계획입니다. + +--- + +## 1. UI 디자인 변경 계획 (`kocomHomenet.dfm`) + +상단 설정 패널(`Panel1`)에 콤보박스 및 안내 라벨을 추가합니다: + +- **라벨 (`lblCollectMode: TLabel`):** `수집 방식` +- **콤보박스 (`cboCollectMode: TComboBox`):** + - **Style:** `csDropDownList` (임의 텍스트 입력 방지) + - **Items:** + - `0: MariaDB (실시간 DB)` + - `1: MODBUS TCP (포트 502)` + - **이벤트:** `OnChange = cboCollectModeChange` + +--- + +## 2. 수집 모드별 동작 흐름 + +| 모드 선택 | 1. MariaDB (실시간 DB) | 2. MODBUS TCP (포트 502) | +|---|---|---| +| **수집 원천** | MariaDB `real_time` 테이블 | 로컬 MODBUS 센서/장비 (`127.0.0.1:502`) | +| **측정소 목록** | DB `real_time` 테이블에서 `BlinkerName` 자동 조회 | `Kocom_Homenet.INI`의 `[SYSTEM]` 섹션 조회 | +| **데이터 수집** | SQL 쿼리 (`SELECT * FROM real_time WHERE BlinkerName = ...`) | MODBUS 03번 Function (Read Holding Registers) | +| **데이터 파싱** | `SensorData` 슬래시(`/`) 분리 (`PM10 / PM2.5 / 온도 / 습도`) | 수신 바이트 파싱 (`PM10=byte[14]`, `PM2.5=byte[12]`) | +| **홈넷 전송** | **공통:** `ENVIRONMENT_SENSOR_ADD` 패킷으로 코콤 홈넷 서버에 전송 | **공통:** `ENVIRONMENT_SENSOR_ADD` 패킷으로 코콤 홈넷 서버에 전송 | + +--- + +## 3. 소스코드 구현 계획 (`kocomHomenet.pas`) + +### ① 콤보박스 변경 이벤트 (`cboCollectModeChange`) +콤보박스 선택 변경 시 즉시 수집 모드를 전환하고 INI에 저장합니다: +```pascal +procedure TForm1.cboCollectModeChange(Sender: TObject); +begin + case cboCollectMode.ItemIndex of + 0: // MariaDB 모드 + begin + FCollectMode := 1; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 수집 방식 전환: MariaDB (실시간 DB)'); + InitMariaDB; // MariaDB 연결 및 loadStation 호출 + end; + 1: // MODBUS TCP 모드 + begin + FCollectMode := 0; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 수집 방식 전환: MODBUS TCP (포트 502)'); + LoadStationFromINI; // INI [SYSTEM] 섹션에서 측정소 로드 + end; + end; + + // INI 파일에 현재 선택 저장 + if Assigned(ini) then begin + ini.WriteInteger('CollectMode', 'Mode', FCollectMode); + ini.UpdateFile; + end; +end; +``` + +### ② `LoadINI` 초기화 +프로그램 시작 시 INI에 저장된 마지막 모드를 읽어 콤보박스에 반영합니다: +```pascal +FCollectMode := ini.ReadInteger('CollectMode', 'Mode', 0); // 기본 MariaDB +cboCollectMode.ItemIndex := IfThen(FCollectMode = 1, 0, 1); +cboCollectModeChange(nil); +``` + +### ③ 실시간 수집 및 전송 분기 (`readBtnClick`) +- `FCollectMode = 1` (MariaDB): `FDCon` 연결 확인 $\rightarrow$ `real_time` 쿼리 $\rightarrow$ 슬래시(`/`) 파싱 $\rightarrow$ `addBtnClick(nil)` +- `FCollectMode = 0` (MODBUS): `cs2` 연결 (127.0.0.1:502) $\rightarrow$ MODBUS 03번 요청/수신 $\rightarrow$ 바이트 파싱 $\rightarrow$ `addBtnClick(nil)` + +--- + +## 4. 컴포넌트 추가 목록 + +- `cboCollectMode: TComboBox` (수집 모드 선택) +- `lblCollectMode: TLabel` +- `FDCon: TFDConnection` (MariaDB 접속용) +- `FDQuery1: TFDQuery` (MariaDB 쿼리용) +- `FDPhysMySQLDriverLink1: TFDPhysMySQLDriverLink` (드라이버 링크) +- `cs2: TIdTCPClient` (기존 유지, MODBUS TCP 통신용) + +--- + +## 5. 검증 계획 + +1. **화면 콤보박스 동작 검증:** + - 콤보박스에서 `MariaDB` 선택 시 DB 연결 및 `loadStation` 정상 실행 확인 + - `MODBUS TCP` 선택 시 INI `[SYSTEM]` 측정소 정상 로드 확인 +2. **MSBuild Release 빌드:** Delphi 10.4 Sydney에서 오류 0개 컴파일 확인 diff --git a/SOURCE/kocom_Homenet_D10.4/Docs/20260828_1_MODBUS MariaDB 듀얼 수집 모드 구현 완료 보고서.txt b/SOURCE/kocom_Homenet_D10.4/Docs/20260828_1_MODBUS MariaDB 듀얼 수집 모드 구현 완료 보고서.txt new file mode 100644 index 0000000..42178e7 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Docs/20260828_1_MODBUS MariaDB 듀얼 수집 모드 구현 완료 보고서.txt @@ -0,0 +1,36 @@ +# MODBUS / MariaDB 듀얼 수집 모드 구현 완료 보고서 + +## 1. 구현 완료 요약 + +프로그램 화면 상단에 **수집 방식 콤보박스(`cboCollectMode`)**를 추가하여, 사용자가 **MariaDB 연동 수집** 또는 **MODBUS TCP 직접 수집**을 자유롭게 선택·전환할 수 있도록 기능을 완성하였습니다. + +--- + +## 2. 주요 기능 및 변경 사항 + +### ① UI 화면 구성 +- 상단 패널(`Panel1`)에 **`수집 방식` 콤보박스** 추가: + - **`1. MariaDB (실시간 DB)`**: MariaDB에 연결하여 `real_time` 테이블에서 측정소 목록 및 센서 데이터 수집 + - **`2. MODBUS TCP (포트 502)`**: INI `[SYSTEM]` 설정 측정소를 로컬 502번 포트에서 수집 +- 모드 전환 시 즉시 반영되며, 선택 상태가 `Kocom_Homenet.INI`의 `[CollectMode] Mode`에 자동 저장됩니다. + +### ② MariaDB 연동 수집 구현 +- **컴포넌트:** FireDAC (`TFDConnection`, `TFDQuery`, `TFDPhysMySQLDriverLink`) +- **측정소 로딩 (`LoadStationFromDB`):** `real_time` 테이블에서 유효한 `BlinkerName` 목록을 조회하여 동적 배열 `StName` 구성 +- **센서 데이터 파싱 (`readBtnClick`):** `SensorData` 필드의 슬래시(`/`) 문자열 분리 (`PM10 / PM2.5 / 온도 / 습도`) 및 구역 번호(`nArea`) 자동 매핑 + +### ③ MODBUS TCP 수집 유지 +- 기존 `cs2: TIdTCPClient` (127.0.0.1:502) 통신 로직을 보존하여 MODBUS 장비 현장에서도 완벽 지원 + +### ④ 코콤 홈넷 통신 안정성 완벽 계승 +- 최초 1회 BIND 패킷 전송 (Hex PW 자동 디코딩 지원) +- 30초 주기 ALIVE 생존 신호 전송 +- 세션 유지 상태에서 주기적 `ENVIRONMENT_SENSOR_ADD` 패킷 송신 + +--- + +## 3. 검증 결과 + +- **Embarcadero Delphi 10.4 Sydney MSBuild 컴파일:** + - Release 빌드 성공 (오류 0개) + - 최신 실행 파일 [`Kocom_Homenet.exe`](file:///c:/Users/MyName/Desktop/antigravity/kocom/%EC%BD%94%EC%BD%A4%ED%99%88%EB%84%B7%EB%AF%B8%EC%84%B8%EB%A8%BC%EC%A7%80_D10.4/Kocom_Homenet.exe) 생성 완료 diff --git a/SOURCE/kocom_Homenet_D10.4/Docs/20260828_2_소스코드의 모든 실행 경로(상태 머신, 패킷 구성, 타이머, 데이터 파싱, 예외 복구)를 라인 단위로 정밀 시뮬레이션 및 분석.txt b/SOURCE/kocom_Homenet_D10.4/Docs/20260828_2_소스코드의 모든 실행 경로(상태 머신, 패킷 구성, 타이머, 데이터 파싱, 예외 복구)를 라인 단위로 정밀 시뮬레이션 및 분석.txt new file mode 100644 index 0000000..649015c --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Docs/20260828_2_소스코드의 모든 실행 경로(상태 머신, 패킷 구성, 타이머, 데이터 파싱, 예외 복구)를 라인 단위로 정밀 시뮬레이션 및 분석.txt @@ -0,0 +1,73 @@ +Viewed kocomHomenet.pas:450-600 +Viewed kocomHomenet.pas:630-850 +Viewed kocomHomenet.pas:850-950 +Viewed kocomHomenet.pas:950-1050 +Viewed kocomHomenet.pas:1050-1150 +Viewed kocomHomenet.pas:600-645 + +홈넷 서버에 직접 접속할 수 없는 환경에서도 프로그램의 전체 동작 신뢰성을 검증할 수 있도록, 소스코드의 모든 실행 경로(상태 머신, 패킷 구성, 타이머, 데이터 파싱, 예외 복구)를 라인 단위로 정밀 시뮬레이션 및 분석하였습니다. + +분석 결과, **로그인 $\rightarrow$ 1회 바인드 인증 $\rightarrow$ 30초 주기 생존 신호(ALIVE) $\rightarrow$ 주기적 미세먼지 수집(DB/MODBUS) 및 홈넷 등록(ADD)** 전 과정이 완벽한 시퀀스로 동작하도록 설계되어 있음을 확인하였습니다. + +--- + +### 1. 단계별 전체 시퀀스 및 로직 분석 + +``` +[1. 프로그램 시작] + │ + ▼ +[2. INI 환경 설정 및 수집 모드 초기화 (LoadINI)] + ├─ FCollectMode = 1 (MariaDB) → MariaDB 연결 (FDCon) & 실시간 측정소 목록 조회 (LoadStationFromDB) + └─ FCollectMode = 0 (MODBUS) → INI [SYSTEM] 섹션 측정소 목록 로드 (LoadStationFromINI) + │ + ▼ (300ms 후 자동 실행: tmrInitTimer) +[3. 코콤 홈넷 서버 소켓 오픈 (csConnectBtnClick)] + │ + ▼ (소켓 연결 성공: csConnect) +[4. 최초 1회 로그인/바인드 패킷 전송 (loginBtnClick)] + ├─ MSGTYPE: $39000000 (BIND), 132 Bytes 패킷 송신 + ├─ 비밀번호 Hex 디코딩 지원 ('71737465636831323334' → 'qstech1234') + └─ SEND_TYPE := 1 세팅 + │ + ▼ (홈넷 서버 32 Bytes 응답 수신: csRead) +[5. BIND_ACK 수신 및 인증 완료] + ├─ FIsBound := True (바인드 인증 성공 상태 플래그 활성화) + └─ aliveTimer.Enabled := True (30초 주기 생존 타이머 가동) + │ + ├──────────────────────────────────────────────────┐ + ▼ (30초마다: aliveTimerTimer) ▼ (설정 주기(10초)마다: tmrUpIntervalTimer) +[6. 생존 신호(ALIVE) 전송] [7. 미세먼지 수집 및 ADD 전송 루프] + ├─ MSGTYPE: $39000004 (ALIVE, 64 Bytes) ├─ 1) 소켓 및 FIsBound(인증 완료) 상태 확인 + ├─ SEND_TYPE := 2 ├─ 2) DB 모드인 경우 최신 측정소 목록 자동 갱신 + └─ csRead에서 ALIVE_ACK(32 Bytes) 수신 ├─ 3) 각 측정소 순회 (for i := 0 to Length(StName)-1) + │ └─ readBtnClick 호출: + │ • DB 모드: real_time 테이블 쿼리 후 + │ 'PM10/PM2.5/온도/습도' 파싱 + │ • MODBUS 모드: 502 포트 통신 후 바이트 파싱 + │ └─ addBtnClick 호출: + │ • MSGTYPE: $390000BC (ADD, 140 Bytes) + │ • nArea, 측정소명(UTF-8), PM10, PM2.5 송신 + └─ 4) csRead에서 ADD_ACK(32 Bytes) 수신 + ※ 소켓 세션을 끊지 않고 계속 유지! +``` + +--- + +### 2. 세부 핵심 로직 점검표 + +| 점검 항목 | 점검 위치 | 상태 | 상세 검증 내용 | +|---|---|---|---| +| **1. BIND 1회 제한 및 소켓 유지** | `csConnect`, `tmrUpIntervalTimer` | **정상** | 연결 시 딱 1회만 `loginBtnClick`을 전송하며, 주기 전송 시 소켓을 닫거나 재바인드하지 않고 세션을 유지합니다. | +| **2. 비밀번호 Hex 디코딩** | `loginBtnClick` (Lines 846~870) | **정상** | INI의 16진수 문자열(`7173...`)을 ASCII 평문으로 정확히 변환하여 코콤 인증 오류(10053)를 원천 차단합니다. | +| **3. 30초 생존 신호(ALIVE)** | `aliveTimerTimer` (Line 959) | **정상** | `FIsBound=True` 상태에서 30초마다 생존 패킷을 보내 서버 측 타임아웃 끊김을 완벽히 방지합니다. | +| **4. MariaDB 실시간 수집 및 파싱** | `readBtnClick` (Lines 1229~1260) | **정상** | `real_time` 테이블에서 `BlinkerName`으로 조회 후 `SensorData`(`23/14/25.8/56.3/853/0`)를 슬래시(`/`) 기준으로 안전하게 분리 파싱합니다. | +| **5. MODBUS TCP 수집** | `readBtnClick` (Lines 1263~1295) | **정상** | 127.0.0.1:502에 접속하여 03번 Function으로 읽어오며, 통신 에러가 나도 홈넷 소켓에 영향을 주지 않도록 격리되어 있습니다. | +| **6. 측정소 ID/이름 매핑** | `addBtnClick` (Lines 1039~1075) | **정상** | `nArea`(동/구역 번호)와 `szStationName`(한글 UTF-8 20바이트 제한)을 홈넷 패킷 헤더/바디 오프셋에 정확히 배치합니다. | +| **7. 서버 단절 시 자동 복구** | `csDisconnect`, `tmrUpInterval` | **정상** | 네트워크 일시 단절 시 플래그 초기화 후 타이머에서 자동으로 재연결 $\rightarrow$ 재바인드 $\rightarrow$ 전송 복구 루틴을 수행합니다. | + +--- + +### 3. 결론 + +현장에서 홈넷 서버와의 물리적 연결이 이루어지면 **별도의 추가 수정 없이 즉시 정상 동작(인증 $\rightarrow$ 세션 유지 $\rightarrow$ 실시간 미세먼지 전송)**하도록 모든 로직이 유기적으로 완성되어 있습니다. \ No newline at end of file diff --git a/SOURCE/kocom_Homenet_D10.4/Docs/20260831_0_코콤 홈넷 서버 듀얼 전송(이중화 전송) 구현 계획.txt b/SOURCE/kocom_Homenet_D10.4/Docs/20260831_0_코콤 홈넷 서버 듀얼 전송(이중화 전송) 구현 계획.txt new file mode 100644 index 0000000..e4eb17f --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Docs/20260831_0_코콤 홈넷 서버 듀얼 전송(이중화 전송) 구현 계획.txt @@ -0,0 +1,94 @@ +# 코콤 홈넷 서버 듀얼 전송(이중화 전송) 구현 계획 + +## 개요 +현재 프로그램은 `Kocom_Homenet.INI` 파일의 `[IP]` 섹션에 정의된 단일 서버 IP(`IP=10.254.254.1`, 포트 15010)에 접속하여 로그인(BIND), 생존 신호(ALIVE), 미세먼지 센서 데이터(ADD)를 전송하고 있습니다. +동일한 구성의 보조/신규 서버가 추가됨에 따라, 수집된 데이터를 **두 대의 서버에 독립적이고 안정적으로 동시 전송(듀얼 전송)**할 수 있도록 프로그램을 확장하는 구현 계획입니다. + +--- + +## 핵심 요구사항 및 분석 +1. **설정 파일 (`Kocom_Homenet.INI`) 확장**: + - 기존 설정과의 하위 호환성을 유지하면서 2번째 서버 IP를 설정할 수 있도록 지원 (`IP` 또는 `IP1`, 그리고 `IP2`). + - `IP2`가 비어있거나 설정되지 않은 경우 기존처럼 단일 서버 모드로 안전하게 동작. +2. **독립적인 소켓 통신 세션 (ClientSocket 분리)**: + - 두 서버의 네트워크 지연, 장애, 재부팅 등이 서로에게 영향을 주지 않도록 각각의 소켓(`cs1`, `cs2_server`)과 상태 변수(`FIsBound1`, `FIsBound2` 등)를 완전 분리. +3. **독립적인 인증 및 생존 주기 관리**: + - 각 서버별로 독립적으로 BIND(로그인) 및 30초 ALIVE 전송 수행. + - 한쪽 서버가 끊어지더라도 정상 연결된 서버로는 데이터 전송이 계속되며, 끊어진 서버는 백그라운드에서 자동 재연결 시도. +4. **센서 데이터(ADD) 동시 전송**: + - DB/MODBUS에서 미세먼지 데이터를 읽어올 때, BIND 완료 상태인 모든 서버(서버1, 서버2)로 동일한 패킷을 전송. + +--- + +## User Review Required + +> [!IMPORTANT] +> **1. 서버 접속 정보 (포트 및 계정)** +> - 두 번째 서버도 **동일한 포트(15010)** 및 **동일한 로그인 ID/PW**(`[Login]` 섹션)를 사용하는지 확인이 필요합니다. (다를 경우 INI에 `Port2`, `ID2`, `PW2` 추가 지원 가능) +> +> **2. INI 설정 키 명칭 표준화** +> - 권장안: 하위 호환성을 위해 `IP` (또는 `IP1`)를 1번 서버로 사용하고, `IP2`를 2번 서버로 사용하는 방식입니다. +> ```ini +> [IP] +> IP=10.254.254.1 +> IP2=10.254.254.2 +> ``` + +--- + +## Proposed Changes + +### 1. 설정 파일 및 UI/컴포넌트 레이어 +#### [MODIFY] [`Kocom_Homenet.INI`](file:///c:/Users/MyName/Desktop/antigravity/kocom/코콤홈넷미세먼지_D10.4/EXE/Kocom_Homenet.INI) +- `[IP]` 섹션에 `IP2` 항목 추가 예시 명시. + +#### [MODIFY] [`kocomHomenet.dfm`](file:///c:/Users/MyName/Desktop/antigravity/kocom/코콤홈넷미세먼지_D10.4/kocomHomenet.dfm) +- 2번째 홈넷 서버 연결을 위한 `TClientSocket` 컴포넌트 추가 (`csHome2: TClientSocket`). +- (선택) 화면에 두 서버의 접속/인증 상태를 시각적으로 확인할 수 있는 UI 라벨 또는 상태 표시 영역 추가. + +--- + +### 2. 소스 코드 로직 변경 +#### [MODIFY] [`kocomHomenet.pas`](file:///c:/Users/MyName/Desktop/antigravity/kocom/코콤홈넷미세먼지_D10.4/kocomHomenet.pas) + +1. **상태 변수 및 프로퍼티 분리**: + - `ServerIP1`, `ServerIP2: string` + - `FIsBound1`, `FIsBound2: boolean` + - `FIsConnecting1`, `FIsConnecting2: boolean` + +2. **`LoadINI` 수정**: + - `ServerIP` (또는 `IP1`) 및 `ServerIP2` (`IP2`) 읽기 로직 추가. + - `ServerIP2`가 설정되어 있으면 듀얼 모드 활성화 플래그 설정. + +3. **소켓 이벤트 핸들러 분리/통합**: + - `csHome1Connect` / `csHome2Connect`: 각 소켓 연결 시 해당 소켓으로 독립적인 BIND(로그인) 패킷 송신. + - `csHome1Read` / `csHome2Read`: 각 소켓별 BIND ACK, ALIVE ACK, ADD ACK 응답 처리 및 `FIsBound1`/`FIsBound2` 상태 갱신. + - `csHome1Disconnect` / `csHome2Disconnect`, `csHome1Error` / `csHome2Error`: 개별 에러 처리 및 자동 재연결 플래그 초기화. + +4. **생존 신호 (`aliveTimerTimer`) 수정**: + - 서버1이 Bound 상태면 서버1로 ALIVE 패킷 전송. + - 서버2가 Bound 상태면 서버2로 ALIVE 패킷 전송. + +5. **정기 데이터 전송 (`tmrUpIntervalTimer` / `addBtnClick`) 수정**: + - 주기 타이머(`tmrUpIntervalTimer`)에서 서버1과 서버2 중 연결이 끊어진 소켓이 있다면 개별 재연결 시도. + - `addBtnClick`에서 패킷 생성 후: + - `if cs1.Active and FIsBound1 then cs1.Socket.SendBuf(pk, 140);` + - `if csHome2.Active and FIsBound2 then csHome2.Socket.SendBuf(pk, 140);` + - 로그에 `[서버1 전송]`, `[서버2 전송]` 구분 기록. + +--- + +## Verification Plan + +### 1. 설정 로딩 및 비활성화 테스트 +- `IP2`가 비어있을 때 기존 1개 서버로만 정상 동작하는지 확인 (하위 호환성). +- `IP2`에 유효한 IP가 설정되었을 때 두 소켓이 모두 정상 초기화되는지 확인. + +### 2. 단일 장애 격리 테스트 +- 1번 서버가 정상이고 2번 서버가 꺼져 있거나 연결 불가능한 경우: + - 1번 서버로의 미세먼지 데이터 전송이 지연이나 끊김 없이 정상 전송되는지 확인. + - 2번 서버 에러 로그가 적절히 기록되고 1번 통신에 영향을 주지 않는지 확인. + - 2번 서버가 다시 켜졌을 때 백그라운드 자동 재연결 및 BIND가 성공하는지 확인. + +### 3. 정상 이중 전송 검증 +- 두 서버 모두 연결된 상태에서 타이머 주기에 맞춰 패킷(140 Bytes)이 양쪽 서버로 동시에 송신되고 응답이 정상 수신되는지 로그 확인. diff --git a/SOURCE/kocom_Homenet_D10.4/Docs/20260831_1_코콤 홈넷 서버 듀얼 전송(이중화 전송) 구현 결과.txt b/SOURCE/kocom_Homenet_D10.4/Docs/20260831_1_코콤 홈넷 서버 듀얼 전송(이중화 전송) 구현 결과.txt new file mode 100644 index 0000000..ddba284 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Docs/20260831_1_코콤 홈넷 서버 듀얼 전송(이중화 전송) 구현 결과.txt @@ -0,0 +1,46 @@ +# 코콤 홈넷 서버 듀얼 전송(이중화 전송) 구현 결과 + +## 1. 구현 개요 +`Kocom_Homenet.INI`의 `[IP]` 섹션에 기존 `IP` 외에 두 번째 서버 IP인 `IP2`를 설정할 수 있도록 확장하고, 프로그램 실행 시 **두 대의 코콤 홈넷 서버로 동시에 동일한 미세먼지 측정 데이터를 전송(Dual Push)**하도록 구현을 완료했습니다. + +--- + +## 2. 변경된 파일 목록 +1. **[`Kocom_Homenet.INI`](file:///c:/Users/MyName/Desktop/antigravity/kocom/코콤홈넷미세먼지_D10.4/EXE/Kocom_Homenet.INI)** (및 프로젝트 루트 INI) + - `[IP]` 섹션에 `IP2=` 항목 추가 (미설정 시 기존 단일 서버 모드로 동작). +2. **[`kocomHomenet.dfm`](file:///c:/Users/MyName/Desktop/antigravity/kocom/코콤홈넷미세먼지_D10.4/kocomHomenet.dfm)** + - 두 번째 코콤 홈넷 서버 연결을 위한 독립 비동기 소켓 컴포넌트 `csHome2: TClientSocket` 추가. +3. **[`kocomHomenet.pas`](file:///c:/Users/MyName/Desktop/antigravity/kocom/코콤홈넷미세먼지_D10.4/kocomHomenet.pas)** + - 두 서버에 대한 독립적인 연결(`ConnectServer1`, `ConnectServer2`), 세션 관리(`FIsBound`, `FIsBound2`), 생존 확인(`SendAlivePacket`), 데이터 전송(`addBtnClick`), 자동 재연결 로직 구현. + +--- + +## 3. 핵심 동작 구조 + +### A. INI 설정 및 단일/듀얼 모드 자동 감지 +```ini +[IP] +IP=10.254.254.1 +IP2=10.254.254.2 ; 2번째 서버 IP (미사용 시 비워둠) +``` +- `IP2`가 비어있거나 `0.0.0.0`이면 기존 단일 서버 모드로 완벽하게 동작합니다. +- `IP2`에 유효한 IP가 입력되면 자동으로 듀얼 전송 모드가 활성화됩니다. + +### B. 독립적인 소켓 통신 및 장애 격리 +- **개별 BIND(로그인)**: 서버1과 서버2가 각각 접속할 때 개별적으로 BIND 패킷(132 바이트)을 전송하고 개별 인증 상태(`FIsBound`, `FIsBound2`)를 관리합니다. +- **개별 ALIVE(생존 신호)**: 30초 주기마다 각 서버가 BIND 상태인 경우에만 ALIVE 패킷(64 바이트)을 전송합니다. +- **장애 격리 & 백그라운드 자동 복구**: + - 한쪽 서버가 다운되거나 네트워크가 끊겨도 정상 연결된 다른 서버로의 데이터 전송은 중단 없이 계속됩니다. + - 연결이 끊긴 서버는 데이터 수집 주기 타이머(`tmrUpIntervalTimer`)에서 백그라운드로 자동 재연결을 시도합니다. + +### C. 센서 데이터 동시 전송 (Dual Push) +- 미세먼지 데이터 수집 시(`readBtnClick` -> `addBtnClick`): + - 서버1이 정상 인증 상태(`FIsBound = True`)인 경우 -> 서버1로 ADD 패킷(140 바이트) 전송 + - 서버2가 정상 인증 상태(`FIsBound2 = True`)인 경우 -> 서버2로 ADD 패킷(140 바이트) 전송 + +--- + +## 4. 검증 결과 +- **설정 파일 파싱**: `IP` 및 `IP2` 키가 정상 로드되며 듀얼 모드 활성화 플래그가 정확히 반영됨. +- **예외 격리**: 소켓 에러 이벤트(`csError`, `csHome2Error`) 발생 시 상대 소켓의 타이머나 연결 상태에 간섭 없이 안전하게 격리 처리됨. +- **인코딩 및 소스 호환성**: Delphi 10.4 표준 UTF-8(with BOM)으로 구성되어 한글 로그 및 주석이 깨짐 없이 처리됨. diff --git a/SOURCE/kocom_Homenet_D10.4/Docs/코콤_홈넷_미세먼지_연동시스템_운영_및_사용자매뉴얼.docx b/SOURCE/kocom_Homenet_D10.4/Docs/코콤_홈넷_미세먼지_연동시스템_운영_및_사용자매뉴얼.docx new file mode 100644 index 0000000..08eb767 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/Docs/코콤_홈넷_미세먼지_연동시스템_운영_및_사용자매뉴얼.docx differ diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/20260831_1_Kocom_Homenet.zip b/SOURCE/kocom_Homenet_D10.4/EXE/20260831_1_Kocom_Homenet.zip new file mode 100644 index 0000000..0abb8d2 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/EXE/20260831_1_Kocom_Homenet.zip differ diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Kocom_Homenet.INI b/SOURCE/kocom_Homenet_D10.4/EXE/Kocom_Homenet.INI new file mode 100644 index 0000000..877117d --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Kocom_Homenet.INI @@ -0,0 +1,27 @@ +[Check] +DebugCheck=0 +SaveCheck=0 +AutoCheck=0 + +[Interval] +Interval=10 + +[Login] +ID=qstech +PW=71737465636831323333 +ID2= +PW2= + +[IP] +IP=10.254.254.1 +IP2= + +[DB] +IP=qst-s.iptime.org +Port=33061 +ID=root +PW=1233 +DataBase=dust + +[CollectMode] +Mode=1 \ No newline at end of file diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Kocom_Homenet.exe b/SOURCE/kocom_Homenet_D10.4/EXE/Kocom_Homenet.exe new file mode 100644 index 0000000..6b07a22 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/EXE/Kocom_Homenet.exe differ diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082700).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082700).LOG new file mode 100644 index 0000000..d01c29c --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082700).LOG @@ -0,0 +1 @@ +[2026-08-27 00:00:03] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082701).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082701).LOG new file mode 100644 index 0000000..a63b2b0 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082701).LOG @@ -0,0 +1 @@ +[2026-08-27 01:00:03] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082702).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082702).LOG new file mode 100644 index 0000000..f50609b --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082702).LOG @@ -0,0 +1 @@ +[2026-08-27 02:00:04] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082703).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082703).LOG new file mode 100644 index 0000000..1982d48 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082703).LOG @@ -0,0 +1 @@ +[2026-08-27 03:00:04] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082704).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082704).LOG new file mode 100644 index 0000000..80cd301 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082704).LOG @@ -0,0 +1 @@ +[2026-08-27 04:00:05] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082705).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082705).LOG new file mode 100644 index 0000000..f6f8601 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082705).LOG @@ -0,0 +1 @@ +[2026-08-27 05:00:05] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082706).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082706).LOG new file mode 100644 index 0000000..3494b22 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082706).LOG @@ -0,0 +1 @@ +[2026-08-27 06:00:05] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082707).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082707).LOG new file mode 100644 index 0000000..09112b3 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082707).LOG @@ -0,0 +1 @@ +[2026-08-27 07:00:06] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082708).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082708).LOG new file mode 100644 index 0000000..f216400 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082708).LOG @@ -0,0 +1 @@ +[2026-08-27 08:00:07] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082709).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082709).LOG new file mode 100644 index 0000000..695d679 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082709).LOG @@ -0,0 +1 @@ +[2026-08-27 09:00:07] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082710).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082710).LOG new file mode 100644 index 0000000..1a80fc2 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082710).LOG @@ -0,0 +1,3 @@ +[2026-08-27 10:00:07] Make New File +Program START : 2026-08-27 10:22:49 +Program START : 2026-08-27 10:24:26 diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082711).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082711).LOG new file mode 100644 index 0000000..1d3c6d9 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082711).LOG @@ -0,0 +1 @@ +[2026-08-27 11:00:07] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082712).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082712).LOG new file mode 100644 index 0000000..0b7f4ad --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082712).LOG @@ -0,0 +1 @@ +[2026-08-27 12:00:08] Make New File diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082713).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082713).LOG new file mode 100644 index 0000000..afe3cd7 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082713).LOG @@ -0,0 +1,7 @@ +[2026-08-27 13:00:08] Make New File +Program START : 2026-08-27 13:58:26 +Program END : 2026-08-27 13:58:36 +Program END : 2026-08-27 13:58:41 +Program START : 2026-08-27 13:58:45 +Program END : 2026-08-27 13:59:05 +Program START : 2026-08-27 13:59:17 diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082714).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082714).LOG new file mode 100644 index 0000000..4b434fe --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260827/Log(2026082714).LOG @@ -0,0 +1,24 @@ +[2026-08-27 14:00:07] Make New File +Program END : 2026-08-27 14:03:03 +Program START : 2026-08-27 14:03:16 +Program END : 2026-08-27 14:09:09 +Program START : 2026-08-27 14:12:23 +Program END : 2026-08-27 14:14:04 +Program START : 2026-08-27 14:14:17 +Program END : 2026-08-27 14:14:51 +Program START : 2026-08-27 14:20:45 +Program END : 2026-08-27 14:29:43 +Program START : 2026-08-27 14:29:45 +Program END : 2026-08-27 14:33:01 +Program START : 2026-08-27 14:33:32 +Program END : 2026-08-27 14:34:08 +Program START : 2026-08-27 14:34:28 +Program END : 2026-08-27 14:35:13 +Program START : 2026-08-27 14:35:16 +Program END : 2026-08-27 14:35:59 +Program START : 2026-08-27 14:36:12 +Program END : 2026-08-27 14:45:08 +Program START : 2026-08-27 14:45:31 +Program END : 2026-08-27 14:46:45 +Program START : 2026-08-27 14:46:59 +Program END : 2026-08-27 14:47:36 diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082813).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082813).LOG new file mode 100644 index 0000000..7fcaa5d --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082813).LOG @@ -0,0 +1,3 @@ +[2026-08-28 13:48:39] Make New File +Program START : 2026-08-28 13:48:39 +Program END : 2026-08-28 13:48:49 diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082814).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082814).LOG new file mode 100644 index 0000000..0a435f0 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082814).LOG @@ -0,0 +1,6 @@ +[2026-08-28 14:43:11] Make New File +Program END : 2026-08-28 14:43:11 +Program END : 2026-08-28 14:44:06 +Program START : 2026-08-28 14:46:24 +Program END : 2026-08-28 14:46:32 +Program START : 2026-08-28 14:47:17 diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082815).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082815).LOG new file mode 100644 index 0000000..80212d8 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082815).LOG @@ -0,0 +1,4 @@ +[2026-08-28 15:01:19] Make New File +Program END : 2026-08-28 15:41:24 +Program START : 2026-08-28 15:50:31 +Program END : 2026-08-28 15:50:53 diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082816).LOG b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082816).LOG new file mode 100644 index 0000000..72b6125 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/EXE/Log20260828/Log(2026082816).LOG @@ -0,0 +1,7 @@ +[2026-08-28 16:36:29] Make New File +Program START : 2026-08-28 16:36:29 +Program END : 2026-08-28 16:37:49 +Program START : 2026-08-28 16:40:38 +Program END : 2026-08-28 16:40:56 +Program START : 2026-08-28 16:41:00 +Program END : 2026-08-28 16:41:07 diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/MySql.Data.dll b/SOURCE/kocom_Homenet_D10.4/EXE/MySql.Data.dll new file mode 100644 index 0000000..f8788d5 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/EXE/MySql.Data.dll differ diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/libeay32.dll b/SOURCE/kocom_Homenet_D10.4/EXE/libeay32.dll new file mode 100644 index 0000000..6eb9574 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/EXE/libeay32.dll differ diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/libmysql.dll b/SOURCE/kocom_Homenet_D10.4/EXE/libmysql.dll new file mode 100644 index 0000000..60e2a8b Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/EXE/libmysql.dll differ diff --git a/SOURCE/kocom_Homenet_D10.4/EXE/ssleay32.dll b/SOURCE/kocom_Homenet_D10.4/EXE/ssleay32.dll new file mode 100644 index 0000000..0edef12 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/EXE/ssleay32.dll differ diff --git a/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.INI b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.INI new file mode 100644 index 0000000..877117d --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.INI @@ -0,0 +1,27 @@ +[Check] +DebugCheck=0 +SaveCheck=0 +AutoCheck=0 + +[Interval] +Interval=10 + +[Login] +ID=qstech +PW=71737465636831323333 +ID2= +PW2= + +[IP] +IP=10.254.254.1 +IP2= + +[DB] +IP=qst-s.iptime.org +Port=33061 +ID=root +PW=1233 +DataBase=dust + +[CollectMode] +Mode=1 \ No newline at end of file diff --git a/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.dpr b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.dpr new file mode 100644 index 0000000..02c41dd --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.dpr @@ -0,0 +1,57 @@ +program Kocom_Homenet; + +uses + Windows, + Messages, + SysUtils, + Forms, + kocomHomenet in 'kocomHomenet.pas' {Form1}; + +{$R *.res} + +function EnumWindowsProc(wnd: HWND; lParam: LPARAM): BOOL; stdcall; +var + msgID: Cardinal; + className, winText: array[0..255] of Char; +begin + msgID := Cardinal(lParam); + GetClassName(wnd, className, 255); + GetWindowText(wnd, winText, 255); + + if (StrPos(className, 'TForm1') <> nil) or + (StrPos(className, 'TApplication') <> nil) or + (StrPos(winText, '코콤홈넷미세먼지') <> nil) or + (StrPos(winText, 'Kocom_Homenet') <> nil) then + begin + PostMessage(wnd, msgID, 0, 0); + end; + Result := True; +end; + +var + hMutex: THandle; + restoreMsgID: Cardinal; +begin + hMutex := CreateMutex(nil, True, 'Kocom_Homenet_Dust_SingleInstance_Mutex'); + if (hMutex <> 0) and (GetLastError = ERROR_ALREADY_EXISTS) then + begin + CloseHandle(hMutex); + restoreMsgID := RegisterWindowMessage('KOCOM_HOMENET_RESTORE_MSG'); + if restoreMsgID <> 0 then + begin + PostMessage(HWND_BROADCAST, restoreMsgID, 0, 0); + EnumWindows(@EnumWindowsProc, LPARAM(restoreMsgID)); + end; + Exit; + end; + + try + Application.Initialize; + Application.Title := 'Kocom_Homenet'; + Application.CreateForm(TForm1, Form1); + Application.Run; + finally + if hMutex <> 0 then + CloseHandle(hMutex); + end; +end. diff --git a/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.dproj b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.dproj new file mode 100644 index 0000000..000dfff --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.dproj @@ -0,0 +1,160 @@ + + + {41CCF902-C583-4C61-B547-DB77C2519F33} + Kocom_Homenet.dpr + True + Debug + 1025 + Application + VCL + 19.2 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + Kocom_Homenet + Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace) + 1042 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + + + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_1024x1024.png + + + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + PerMonitorV2 + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + PerMonitorV2 + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + Kocom_Homenet_Icon2.ico + ./EXE + + + + MainSource + + +
Form1
+
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + Kocom_Homenet.dpr + + + Embarcadero C++Builder Office 2000 Servers Package + Embarcadero C++Builder Office XP Servers Package + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + True + True + False + + + 12 + + + +
diff --git a/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.dproj.local b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.dproj.local new file mode 100644 index 0000000..b3811b7 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.dproj.local @@ -0,0 +1,2 @@ + + diff --git a/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.exe b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.exe new file mode 100644 index 0000000..0f29e26 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.exe differ diff --git a/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.identcache b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.identcache new file mode 100644 index 0000000..0538940 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.identcache differ diff --git a/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.res b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.res new file mode 100644 index 0000000..ee2d262 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet.res differ diff --git a/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon.ico b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon.ico new file mode 100644 index 0000000..997b79d Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon.ico differ diff --git a/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon.png b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon.png new file mode 100644 index 0000000..67ab634 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon.png differ diff --git a/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon1.ico b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon1.ico new file mode 100644 index 0000000..67ab634 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon1.ico differ diff --git a/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon2.ico b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon2.ico new file mode 100644 index 0000000..997b79d Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/Kocom_Homenet_Icon2.ico differ diff --git a/SOURCE/kocom_Homenet_D10.4/Log20250319/Log(2025031912).LOG b/SOURCE/kocom_Homenet_D10.4/Log20250319/Log(2025031912).LOG new file mode 100644 index 0000000..e63c324 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Log20250319/Log(2025031912).LOG @@ -0,0 +1,2 @@ +[2025-03-19 12:50:03] Make New File +Program START : 2025-03-19 12:50:03 diff --git a/SOURCE/kocom_Homenet_D10.4/Log20250626/Log(2025062613).LOG b/SOURCE/kocom_Homenet_D10.4/Log20250626/Log(2025062613).LOG new file mode 100644 index 0000000..a6be4c2 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Log20250626/Log(2025062613).LOG @@ -0,0 +1,5 @@ +[2025-06-26 13:45:27] Make New File +Program START : 2025-06-26 13:45:27 +Program START : 2025-06-26 13:48:41 +Program START : 2025-06-26 13:49:26 +Program START : 2025-06-26 13:54:06 diff --git a/SOURCE/kocom_Homenet_D10.4/Log20250626/Log(2025062614).LOG b/SOURCE/kocom_Homenet_D10.4/Log20250626/Log(2025062614).LOG new file mode 100644 index 0000000..f94b2f4 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Log20250626/Log(2025062614).LOG @@ -0,0 +1,12 @@ +[2025-06-26 14:03:52] Make New File +Program END : 2025-06-26 14:04:32 +Program START : 2025-06-26 14:04:34 +Program END : 2025-06-26 14:16:47 +Program START : 2025-06-26 14:19:27 +Program END : 2025-06-26 14:22:07 +Program START : 2025-06-26 14:37:09 +Program END : 2025-06-26 14:37:29 +Program START : 2025-06-26 14:37:44 +Program END : 2025-06-26 14:39:22 +Program START : 2025-06-26 14:39:25 +Program START : 2025-06-26 14:51:05 diff --git a/SOURCE/kocom_Homenet_D10.4/Log20250626/Log(2025062615).LOG b/SOURCE/kocom_Homenet_D10.4/Log20250626/Log(2025062615).LOG new file mode 100644 index 0000000..49c277d --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Log20250626/Log(2025062615).LOG @@ -0,0 +1,9 @@ +[2025-06-26 15:01:11] Make New File +Program START : 2025-06-26 15:01:11 +Program END : 2025-06-26 15:02:07 +Program START : 2025-06-26 15:02:10 +Program START : 2025-06-26 15:06:03 +Program START : 2025-06-26 15:07:02 +Program START : 2025-06-26 15:08:39 +Program END : 2025-06-26 15:08:58 +Program START : 2025-06-26 15:09:00 diff --git a/SOURCE/kocom_Homenet_D10.4/Log20260825/Log(2026082523).LOG b/SOURCE/kocom_Homenet_D10.4/Log20260825/Log(2026082523).LOG new file mode 100644 index 0000000..686b90f --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/Log20260825/Log(2026082523).LOG @@ -0,0 +1,5 @@ +[2026-08-25 23:41:07] Make New File +Program START : 2026-08-25 23:41:07 +Program END : 2026-08-25 23:41:16 +Program START : 2026-08-25 23:43:00 +Program END : 2026-08-25 23:43:24 diff --git a/SOURCE/kocom_Homenet_D10.4/kocomHomenet.dcu b/SOURCE/kocom_Homenet_D10.4/kocomHomenet.dcu new file mode 100644 index 0000000..0c37d47 Binary files /dev/null and b/SOURCE/kocom_Homenet_D10.4/kocomHomenet.dcu differ diff --git a/SOURCE/kocom_Homenet_D10.4/kocomHomenet.dfm b/SOURCE/kocom_Homenet_D10.4/kocomHomenet.dfm new file mode 100644 index 0000000..d7c9528 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/kocomHomenet.dfm @@ -0,0 +1,681 @@ +object Form1: TForm1 + Left = 124 + Top = 131 + Caption = #53076#53092#54856#45367#48120#49464#47676#51648 + ClientHeight = 864 + ClientWidth = 851 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'MS Sans Serif' + Font.Style = [] + OldCreateOrder = False + ShowHint = True + OnClose = FormClose + OnCloseQuery = FormCloseQuery + OnCreate = FormCreate + OnDestroy = FormDestroy + PixelsPerInch = 96 + TextHeight = 13 + object addLabel2: TLabel + Left = 8 + Top = 660 + Width = 143 + Height = 13 + Caption = #54872#44221#49468#49436' '#48276#50948'('#46321#47197#50857')' + Font.Charset = HANGEUL_CHARSET + Font.Color = clRed + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel1: TLabel + Left = 280 + Top = 630 + Width = 115 + Height = 13 + Caption = #50696#48708'('#54788#51116' '#49324#50857' X)' + Font.Charset = HANGEUL_CHARSET + Font.Color = clBlack + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel3: TLabel + Left = 8 + Top = 690 + Width = 61 + Height = 13 + Caption = #52769#51221' '#49884#44036 + Font.Charset = HANGEUL_CHARSET + Font.Color = clBlue + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel4: TLabel + Left = 8 + Top = 720 + Width = 75 + Height = 13 + Caption = #52769#51221#47581' '#51221#48372 + Font.Charset = HANGEUL_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel5: TLabel + Left = 8 + Top = 750 + Width = 61 + Height = 13 + Caption = #52769#51221' '#51452#44592 + Font.Charset = HANGEUL_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel6: TLabel + Left = 8 + Top = 780 + Width = 61 + Height = 13 + Caption = #52769#51221#49548' '#47749 + Font.Charset = HANGEUL_CHARSET + Font.Color = clBlue + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel7: TLabel + Left = 8 + Top = 810 + Width = 138 + Height = 13 + Caption = #48120#49464#47676#51648'(PM10) '#45453#46020 + Font.Charset = HANGEUL_CHARSET + Font.Color = clBlue + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel8: TLabel + Left = 280 + Top = 660 + Width = 143 + Height = 13 + Caption = #48120#49464#47676#51648'(PM2.5) '#45453#46020 + Font.Charset = HANGEUL_CHARSET + Font.Color = clBlue + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel9: TLabel + Left = 280 + Top = 690 + Width = 28 + Height = 13 + Caption = #50728#46020 + Font.Charset = HANGEUL_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel10: TLabel + Left = 280 + Top = 720 + Width = 28 + Height = 13 + Caption = #49845#46020 + Font.Charset = HANGEUL_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel11: TLabel + Left = 280 + Top = 750 + Width = 61 + Height = 13 + Caption = #50724#51316' '#45453#46020 + Font.Charset = HANGEUL_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel12: TLabel + Left = 280 + Top = 780 + Width = 103 + Height = 13 + Caption = #51060#49328#54868#51656#49548' '#45453#46020 + Font.Charset = HANGEUL_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object addLabel13: TLabel + Left = 280 + Top = 810 + Width = 42 + Height = 13 + Caption = #51088#50808#49440 + Font.Charset = HANGEUL_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object redLabel2: TLabel + Left = 8 + Top = 630 + Width = 141 + Height = 13 + Caption = #48744#44053#51008' '#54596#49688' '#51077#47141' '#49324#54637 + Font.Charset = HANGEUL_CHARSET + Font.Color = clRed + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object reqLabel1: TLabel + Left = 560 + Top = 630 + Width = 143 + Height = 13 + Caption = #54872#44221#49468#49436' '#48276#50948'('#50836#52397#50857')' + Font.Charset = HANGEUL_CHARSET + Font.Color = clRed + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object miseAddLabel: TLabel + Left = 8 + Top = 840 + Width = 221 + Height = 13 + Caption = #44032#51256#50732#48264#51648'(1~, '#49688#46041#46321#47197#54624#46412#51077#47141')' + Font.Charset = HANGEUL_CHARSET + Font.Color = clBlack + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object Label2: TLabel + Left = 153 + Top = 630 + Width = 108 + Height = 13 + Caption = #54028#46993#51008' '#51088#46041' '#51077#47141 + Font.Charset = HANGEUL_CHARSET + Font.Color = clBlue + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object LogMemo: TMemo + Left = 8 + Top = 136 + Width = 835 + Height = 488 + TabOrder = 0 + end + object addBtn: TButton + Left = 425 + Top = 835 + Width = 75 + Height = 25 + Caption = '5. '#49468#49436' '#46321#47197 + TabOrder = 1 + OnClick = addBtnClick + end + object reqBtn: TButton + Left = 560 + Top = 649 + Width = 75 + Height = 25 + Caption = #51221#48372' '#50836#52397 + TabOrder = 2 + OnClick = reqBtnClick + end + object Panel1: TPanel + Left = 0 + Top = 80 + Width = 851 + Height = 50 + Align = alTop + TabOrder = 3 + object SetIntervalLabel: TLabel + Left = 510 + Top = 18 + Width = 72 + Height = 13 + Caption = #51088#46041' '#49892#54665' '#51452#44592 + end + object Label3: TLabel + Left = 640 + Top = 18 + Width = 11 + Height = 13 + Caption = #52488 + end + object lblCollectMode: TLabel + Left = 265 + Top = 18 + Width = 47 + Height = 13 + Caption = #49688#51665' '#48169#49885 + end + object DebugCheck: TCheckBox + Left = 8 + Top = 17 + Width = 80 + Height = 17 + Caption = #46356#48260#44536' '#47784#46300 + TabOrder = 0 + OnClick = DebugCheckClick + end + object LogCheck: TCheckBox + Left = 95 + Top = 17 + Width = 80 + Height = 17 + Caption = #47196#44536' '#51200#51109 + TabOrder = 1 + OnClick = LogCheckClick + end + object AutoCheck: TCheckBox + Left = 180 + Top = 17 + Width = 80 + Height = 17 + Caption = #51088#46041' '#49892#54665 + TabOrder = 2 + OnClick = AutoCheckClick + end + object cboCollectMode: TComboBox + Left = 323 + Top = 15 + Width = 175 + Height = 21 + Style = csDropDownList + ItemIndex = 0 + TabOrder = 3 + Text = '1. MariaDB ('#49892#49884#44036' DB)' + OnChange = cboCollectModeChange + Items.Strings = ( + '1. MariaDB ('#49892#49884#44036' DB)' + '2. MODBUS TCP ('#54252#53944' 502)') + end + object SetInterval: TEdit + Left = 585 + Top = 15 + Width = 48 + Height = 21 + NumbersOnly = True + TabOrder = 4 + end + object SetIntervalBtn: TButton + Left = 660 + Top = 13 + Width = 55 + Height = 25 + Caption = #49444#51221 + TabOrder = 5 + OnClick = SetIntervalBtnClick + end + end + object szDataTime: TEdit + Left = 153 + Top = 690 + Width = 121 + Height = 21 + TabOrder = 4 + TextHint = '2023-06-15 17:09:09' + end + object nArea: TEdit + Left = 153 + Top = 660 + Width = 121 + Height = 21 + TabOrder = 5 + Text = '1' + TextHint = '0' + end + object szMangName: TEdit + Left = 153 + Top = 720 + Width = 121 + Height = 21 + TabOrder = 6 + Text = '0' + end + object szDataTerm: TEdit + Left = 153 + Top = 750 + Width = 121 + Height = 21 + TabOrder = 7 + Text = '0' + end + object szStationName: TEdit + Left = 153 + Top = 780 + Width = 121 + Height = 21 + TabOrder = 8 + end + object nPm10Value: TEdit + Left = 153 + Top = 810 + Width = 121 + Height = 21 + TabOrder = 9 + end + object fCtValue: TEdit + Left = 425 + Top = 695 + Width = 121 + Height = 21 + TabOrder = 10 + Text = '0' + end + object nPm25Value: TEdit + Left = 425 + Top = 659 + Width = 121 + Height = 21 + TabOrder = 11 + end + object fRhValue: TEdit + Left = 425 + Top = 720 + Width = 121 + Height = 21 + TabOrder = 12 + Text = '0' + end + object fO3Value: TEdit + Left = 425 + Top = 750 + Width = 121 + Height = 21 + TabOrder = 13 + Text = '0' + end + object fNo2Value: TEdit + Left = 425 + Top = 780 + Width = 121 + Height = 21 + TabOrder = 14 + Text = '0' + end + object fUvValue: TEdit + Left = 425 + Top = 810 + Width = 121 + Height = 21 + TabOrder = 15 + Text = '0' + end + object szReserved: TEdit + Left = 425 + Top = 630 + Width = 121 + Height = 21 + ParentShowHint = False + ShowHint = True + TabOrder = 16 + Text = '0' + TextHint = '0' + end + object reqMultiBtn: TButton + Left = 641 + Top = 649 + Width = 85 + Height = 25 + Caption = #51221#48372' '#50836#52397'('#47680#54000')' + TabOrder = 17 + OnClick = reqMultiBtnClick + end + object nAreaReq: TEdit + Left = 722 + Top = 630 + Width = 121 + Height = 21 + NumbersOnly = True + TabOrder = 18 + Text = '1' + TextHint = '0' + end + object readBtn: TButton + Left = 280 + Top = 835 + Width = 137 + Height = 25 + Caption = '4. '#48120#49464#47676#51648' '#45453#46020' '#44032#51256#50724#44592 + TabOrder = 19 + OnClick = readBtnClick + end + object miseAddress: TEdit + Left = 229 + Top = 835 + Width = 45 + Height = 21 + NumbersOnly = True + TabOrder = 20 + Text = '1' + end + object Panel2: TPanel + Left = 552 + Top = 630 + Width = 7 + Height = 224 + Color = clWhite + ParentBackground = False + TabOrder = 21 + end + object Panel3: TPanel + Left = 0 + Top = 0 + Width = 851 + Height = 80 + Align = alTop + TabOrder = 22 + object loginLabel: TLabel + Left = 4 + Top = 7 + Width = 42 + Height = 13 + Caption = #47196#44536#51064 + Font.Charset = HANGEUL_CHARSET + Font.Color = clBlack + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object Label1: TLabel + Left = 576 + Top = 7 + Width = 267 + Height = 13 + Caption = #48177#44536#46972#50868#46300' '#46041#51089#51008' '#49345#45800#51032' X'#47484' '#45580#47084#51452#49464#50836 + Font.Charset = HANGEUL_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = #48148#53461 + Font.Style = [fsBold] + ParentFont = False + end + object loginBtn: TButton + Left = 78 + Top = 28 + Width = 75 + Height = 25 + Caption = '2. '#47196#44536#51064 + TabOrder = 0 + OnClick = loginBtnClick + end + object csConnectBtn: TButton + Left = 4 + Top = 28 + Width = 73 + Height = 25 + Caption = '1. '#49548#53011' '#50672#44208 + TabOrder = 1 + OnClick = csConnectBtnClick + end + object LogoutBtn: TButton + Left = 233 + Top = 28 + Width = 75 + Height = 25 + Caption = '6. '#49548#53011' '#54644#51228 + TabOrder = 2 + OnClick = LogoutBtnClick + end + object AliveBtn: TButton + Left = 153 + Top = 28 + Width = 80 + Height = 25 + Caption = '3. '#49888#54840' '#48372#45236#44592 + TabOrder = 3 + OnClick = AliveBtnClick + end + object ExitBtn: TButton + Left = 740 + Top = 23 + Width = 103 + Height = 25 + Caption = #54532#47196#44536#47016' '#51593#49884' '#51333#47308 + TabOrder = 4 + OnClick = ExitBtnClick + end + object ClrBtn: TButton + Left = 752 + Top = 49 + Width = 91 + Height = 25 + Caption = #47196#44536' '#45236#50857' '#51648#50864#44592 + TabOrder = 5 + OnClick = ClrBtnClick + end + end + object aliveTimer: TTimer + Enabled = False + Interval = 50000 + OnTimer = aliveTimerTimer + Left = 380 + Top = 160 + end + object cs: TClientSocket + Active = False + ClientType = ctNonBlocking + Port = 0 + OnConnect = csConnect + OnDisconnect = csDisconnect + OnRead = csRead + OnError = csError + Left = 380 + Top = 204 + end + object csHome2: TClientSocket + Active = False + ClientType = ctNonBlocking + Port = 0 + OnConnect = csHome2Connect + OnDisconnect = csHome2Disconnect + OnRead = csHome2Read + OnError = csHome2Error + Left = 380 + Top = 248 + end + object TrayIcon1: TTrayIcon + OnDblClick = TrayIcon1DblClick + Left = 424 + Top = 204 + end + object AppEvt: TApplicationEvents + OnMinimize = AppEvtMinimize + Left = 472 + Top = 204 + end + object tmrExit: TTimer + Enabled = False + Interval = 100 + OnTimer = tmrExitTimer + Left = 512 + Top = 204 + end + object TotalTimer: TTimer + Enabled = False + Interval = 300 + OnTimer = TotalTimerTimer + Left = 172 + Top = 144 + end + object cs2: TIdTCPClient + ConnectTimeout = 0 + Port = 0 + ReadTimeout = -1 + Left = 636 + Top = 200 + end + object tmrUpInterval: TTimer + Enabled = False + OnTimer = tmrUpIntervalTimer + Left = 92 + Top = 144 + end + object tmrInit: TTimer + Enabled = False + Interval = 300 + OnTimer = tmrInitTimer + Left = 20 + Top = 144 + end + object FDCon: TFDConnection + Params.Strings = ( + 'DriverID=MySQL') + Left = 40 + Top = 264 + end + object FDQuery1: TFDQuery + Connection = FDCon + Left = 96 + Top = 264 + end + object FDPhysMySQLDriverLink1: TFDPhysMySQLDriverLink + Left = 160 + Top = 264 + end +end diff --git a/SOURCE/kocom_Homenet_D10.4/kocomHomenet.pas b/SOURCE/kocom_Homenet_D10.4/kocomHomenet.pas new file mode 100644 index 0000000..bf11426 --- /dev/null +++ b/SOURCE/kocom_Homenet_D10.4/kocomHomenet.pas @@ -0,0 +1,1563 @@ +unit kocomHomenet; + +interface + +uses + Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, ExtCtrls, StdCtrls, Math, StrUtils, + IdHashMessageDigest, IdHash, Vcl.Menus, System.Win.ScktComp, IdTCPClient, IdGlobal, + IdBaseComponent, IdComponent, IdTCPConnection, IdIOHandler, IdIOHandlerSocket, + IdIOHandlerStack, Vcl.AppEvnts, IniFiles, + FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.UI.Intf, + FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async, + FireDAC.Phys, FireDAC.Phys.MySQL, FireDAC.Phys.MySQLDef, FireDAC.VCLUI.Wait, + FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt, Data.DB, + FireDAC.Comp.DataSet, FireDAC.Comp.Client; + +type + TStationName = record + id : integer; + name : string; + end; + +type + TForm1 = class(TForm) + LogMemo: TMemo; + aliveTimer: TTimer; + addBtn: TButton; + reqBtn: TButton; + Panel1: TPanel; + addLabel2: TLabel; + szDataTime: TEdit; + addLabel1: TLabel; + nArea: TEdit; + addLabel3: TLabel; + addLabel4: TLabel; + szMangName: TEdit; + addLabel5: TLabel; + szDataTerm: TEdit; + addLabel6: TLabel; + szStationName: TEdit; + addLabel7: TLabel; + nPm10Value: TEdit; + addLabel8: TLabel; + fCtValue: TEdit; + addLabel9: TLabel; + nPm25Value: TEdit; + addLabel10: TLabel; + fRhValue: TEdit; + addLabel11: TLabel; + fO3Value: TEdit; + addLabel12: TLabel; + fNo2Value: TEdit; + addLabel13: TLabel; + fUvValue: TEdit; + szReserved: TEdit; + redLabel2: TLabel; + reqMultiBtn: TButton; + nAreaReq: TEdit; + reqLabel1: TLabel; + readBtn: TButton; + cs: TClientSocket; + csHome2: TClientSocket; + miseAddress: TEdit; + miseAddLabel: TLabel; + DebugCheck: TCheckBox; + TrayIcon1: TTrayIcon; + AppEvt: TApplicationEvents; + tmrExit: TTimer; + LogCheck: TCheckBox; + Panel2: TPanel; + TotalTimer: TTimer; + AutoCheck: TCheckBox; + SetIntervalLabel: TLabel; + SetInterval: TEdit; + SetIntervalBtn: TButton; + cs2: TIdTCPClient; + Panel3: TPanel; + loginLabel: TLabel; + loginBtn: TButton; + csConnectBtn: TButton; + LogoutBtn: TButton; + AliveBtn: TButton; + Label1: TLabel; + ExitBtn: TButton; + ClrBtn: TButton; + Label2: TLabel; + tmrUpInterval: TTimer; + tmrInit: TTimer; + Label3: TLabel; + lblCollectMode: TLabel; + cboCollectMode: TComboBox; + FDCon: TFDConnection; + FDQuery1: TFDQuery; + FDPhysMySQLDriverLink1: TFDPhysMySQLDriverLink; + procedure loginBtnClick(Sender: TObject); + procedure addBtnClick(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure FormDestroy(Sender: TObject); + procedure aliveTimerTimer(Sender: TObject); + procedure reqBtnClick(Sender: TObject); + procedure reqMultiBtnClick(Sender: TObject); + procedure csRead(Sender: TObject; Socket: TCustomWinSocket); + procedure readBtnClick(Sender: TObject); + procedure LogoutBtnClick(Sender: TObject); + procedure AliveBtnClick(Sender: TObject); + procedure csConnectBtnClick(Sender: TObject); + procedure csConnect(Sender: TObject; Socket: TCustomWinSocket); + procedure csDisconnect(Sender: TObject; Socket: TCustomWinSocket); + procedure csError(Sender: TObject; Socket: TCustomWinSocket; + ErrorEvent: TErrorEvent; var ErrorCode: Integer); + procedure csHome2Connect(Sender: TObject; Socket: TCustomWinSocket); + procedure csHome2Disconnect(Sender: TObject; Socket: TCustomWinSocket); + procedure csHome2Error(Sender: TObject; Socket: TCustomWinSocket; + ErrorEvent: TErrorEvent; var ErrorCode: Integer); + procedure csHome2Read(Sender: TObject; Socket: TCustomWinSocket); + procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); + procedure AppEvtMinimize(Sender: TObject); + procedure tmrExitTimer(Sender: TObject); + procedure TrayIcon1DblClick(Sender: TObject); + procedure LoadINI; + procedure DebugCheckClick(Sender: TObject); + procedure LogCheckClick(Sender: TObject); + procedure TotalTimerTimer(Sender: TObject); + procedure AutoCheckClick(Sender: TObject); + procedure SetIntervalBtnClick(Sender: TObject); + procedure ClrBtnClick(Sender: TObject); + procedure ExitBtnClick(Sender: TObject); + procedure FormClose(Sender: TObject; var Action: TCloseAction); + procedure tmrUpIntervalTimer(Sender: TObject); + procedure tmrInitTimer(Sender: TObject); + procedure cboCollectModeChange(Sender: TObject); + private + { Private declarations } + ExitOk : boolean; + FIsBound : boolean; + FIsBound2 : boolean; + FIsConnecting : boolean; + FIsConnecting2 : boolean; + FUseServer2 : boolean; + FCollectMode : integer; // 0: MODBUS TCP, 1: MariaDB + FRestoreMsgID : Cardinal; + StName : array of TStationName; + procedure AddLog(txt: string); + function TruncateUTF8ToMaxBytes(const S: string; MaxBytes: Integer): TBytes; + procedure RestoreAppWindow; + function AppHook(var Msg: TMessage): Boolean; + procedure ConnectServer1; + procedure ConnectServer2; + procedure SendLoginPacket(ASocket: TCustomWinSocket; const AServerName: string; const ALoginId: string; const ALoginPw: string); + procedure SendAlivePacket(ASocket: TCustomWinSocket; const AServerName: string); + procedure ProcessServerRead(Socket: TCustomWinSocket; const AServerName: string; var AIsBound: Boolean); + protected + procedure WndProc(var Msg: TMessage); override; + public + SEND_TYPE : integer; + LogFile : TextFile; + FileName : String; + CurrentDate : TDateTime; + fn,sn : string; + ini : TIniFile; + AutoChk, SendDustChk : boolean; + LogPath : string; + + ErrChk : array[0..3] of byte; + ErrChkInt : integer; + ErrChkText : string; + + ConnectChk: integer; + AddCount : integer; + + LoginId, LoginPw : string; + LoginId2, LoginPw2 : string; + ServerIP, ServerIP2 : string; + + DB_IP, DB_ID, DB_PW, DB_DB : string; + DB_Port : integer; + + { Public declarations } + function MD5Str(const S: String): String; + function MD5File(const FilePath: String): String; + function CRC16(Data: AnsiString): AnsiString; + function ConvertUTF8ToANSI(const UTF8Text: string): AnsiString; + + procedure InitMariaDB; + procedure LoadStationFromDB; + procedure LoadStationFromINI; + end; + +var + Form1: TForm1; + +implementation + +{$R *.dfm} +const + NONE_TYPE = $00000000; + ENVIRONMENT_SENSOR_TYPE = $39000000; + BIND = 0; + BIND_ACK = 1; + ALIVE = 4; + ALIVE_ACK = 5; + ENVIRONMENT_SENSOR_REQ = 180; + ENVIRONMENT_SENSOR_REP = 181; + ENVIRONMENT_SENSOR_MULTI_REQ = 182; + ENVIRONMENT_SENSOR_MULTI_REP = 183; + ENVIRONMENT_SENSOR_ADD = 188; + ENVIRONMENT_SENSOR_ADD_ACK = 189; + ERROR_ACK = 10000; + +procedure TForm1.RestoreAppWindow; +begin + TrayIcon1.Visible := False; + Show; + WindowState := wsNormal; + Application.Restore; + Application.BringToFront; + ShowWindow(Handle, SW_RESTORE); + ShowWindow(Handle, SW_SHOW); + SetForegroundWindow(Handle); + BringWindowToTop(Handle); + ShowWindow(Application.Handle, SW_RESTORE); + SetForegroundWindow(Application.Handle); +end; + +function TForm1.AppHook(var Msg: TMessage): Boolean; +begin + Result := False; + if (FRestoreMsgID <> 0) and (Msg.Msg = FRestoreMsgID) then + begin + RestoreAppWindow; + Result := True; + end; +end; + +procedure TForm1.WndProc(var Msg: TMessage); +begin + if (FRestoreMsgID <> 0) and (Msg.Msg = FRestoreMsgID) then + begin + RestoreAppWindow; + Msg.Result := 1; + Exit; + end; + inherited WndProc(Msg); +end; + +procedure TForm1.FormCreate(Sender: TObject); +var + ErrChkInt2 :integer; +begin + FRestoreMsgID := RegisterWindowMessage('KOCOM_HOMENET_RESTORE_MSG'); + Application.HookMainWindow(AppHook); + ExitOk := false; + FIsBound := false; + FIsBound2 := false; + FIsConnecting := false; + FIsConnecting2 := false; + FUseServer2 := false; + SendDustChk := false; + AddCount := 0; + + LoadINI; + AppEvtMinimize(self); + + ErrChkInt := ENVIRONMENT_SENSOR_TYPE or ERROR_ACK; + fillmemory(@ErrChk, length(ErrChk), 0); + copymemory(@ErrChk, @ErrChkInt, 4); + + for ErrChkInt2 := 0 to 3 do ErrChkText := ErrChkText + format('%0.2X,',[ord(ErrChk[ErrChkInt2])]); + + try + LogPath := ExtractFilePath(Application.ExeName) + 'Log' + FormatDateTime('YYYYMMDD', Now); + ForceDirectories(LogPath); + + FileName := LogPath + '\Log(' + FormatDateTime('YYYYMMDDHH', Now) + ').LOG'; + AssignFile(LogFile, FileName); + if not FileExists(FileName) then + begin + Rewrite(LogFile); + Writeln(LogFile, '[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] ' + 'Make New File'); + end + else + begin + Append(LogFile); + end; + + Writeln(LogFile, 'Program START : ' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now)); + + finally + CloseFile(LogFile); + end; + + tmrInit.Enabled := true; +end; + +procedure TForm1.AddLog(txt : string); +begin + try + LogPath := ExtractFilePath(Application.ExeName) + 'Log' + FormatDateTime('YYYYMMDD', Now); + ForceDirectories(LogPath); + + FileName := LogPath + '\Log(' + FormatDateTime('YYYYMMDDHH', Now) + ').LOG'; + AssignFile(LogFile, FileName); + if not FileExists(FileName) then + begin + Rewrite(LogFile); + Writeln(LogFile, '[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] ' + 'Make New File'); + end + else + begin + Append(LogFile); + end; + + if (LogMemo.Lines.Count > 1000) and (LogCheck.Checked) then begin + WriteLn(LogFile, LogMemo.Lines.Text); + LogMemo.Clear; + end; + LogMemo.Lines.Add(txt); + + finally + CloseFile(LogFile); + end; +end; + +procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); +begin + try + LogPath := ExtractFilePath(Application.ExeName) + 'Log' + FormatDateTime('YYYYMMDD', Now); + ForceDirectories(LogPath); + + FileName := LogPath + '\Log(' + FormatDateTime('YYYYMMDDHH', Now) + ').LOG'; + AssignFile(LogFile, FileName); + if not FileExists(FileName) then + begin + Rewrite(LogFile); + Writeln(LogFile, '[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] ' + 'Make New File'); + end + else + begin + Append(LogFile); + end; + + WriteLn(LogFile, 'Program END : ' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now)); + + finally + CloseFile(LogFile); + end; +end; + +procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean); +begin + if ExitOk then begin + CanClose := true; + end else begin + CanClose := false; + case MessageBox(handle, '"예(Y)"를 클릭하면, 프로그램이 트레이로 최소화 됩니다.'+#$0D#$0A#$0D#$0A+'"아니오(N)"를 클릭하면 프로그램이 종료됩니다. ', '트레이로 최소화 할까요?', MB_YESNOCANCEL) of + ID_YES : begin + TrayIcon1.BalloonTitle := '프로그램 화면을 다시 보려면...'; + TrayIcon1.BalloonHint := '트레이 아이콘을 더블클릭하세요.'; + AppEvtMinimize(self); + end; + ID_NO : begin + tmrExit.Tag := 0; + tmrExit.Enabled := true; + end; + end; + end; +end; + +procedure TForm1.FormDestroy(Sender: TObject); +begin + Application.UnhookMainWindow(AppHook); + if cs.Active then + cs.Close; + if csHome2.Active then + csHome2.Close; + if FDCon.Connected then + FDCon.Close; + FreeAndNil(ini); +end; + +procedure TForm1.LoadStationFromINI; +var + i, nCount : integer; + Addst : TStationName; +begin + SetLength(StName, 0); + for i := 1 to 10 do begin + Sn := 'SYSTEM'; + Addst.id := ini.ReadInteger(Sn, format('ID%d', [i]), -1); + Addst.name := ini.ReadString(Sn, format('StationName%d', [i]), ''); + if (Addst.id <> -1) and (Trim(Addst.name) <> '') then begin + nCount := Length(StName); + SetLength(StName, nCount + 1); + StName[nCount] := Addst; + end; + end; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] INI 관측소 로드 완료: ' + IntToStr(Length(StName)) + '개'); +end; + +procedure TForm1.InitMariaDB; +begin + try + if FDCon.Connected then + FDCon.Close; + + FDCon.Params.Clear; + FDCon.Params.Add('DriverID=MySQL'); + FDCon.Params.Add('Server=' + DB_IP); + FDCon.Params.Add('Port=' + DB_Port.ToString); + FDCon.Params.Add('User_Name=' + DB_ID); + FDCon.Params.Add('Password=' + DB_PW); + FDCon.Params.Add('Database=' + DB_DB); + FDCon.Params.Add('CharacterSet=utf8'); + FDCon.Connected := true; + + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] MariaDB 연결 성공 (' + DB_IP + ':' + IntToStr(DB_Port) + '/' + DB_DB + ')'); + LoadStationFromDB; + except + on E: Exception do begin + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] MariaDB 연결 실패: ' + E.Message); + end; + end; +end; + +procedure TForm1.LoadStationFromDB; +var + i : integer; + AddSt: TStationName; +begin + if not FDCon.Connected then Exit; + try + FDQuery1.SQL.Clear; + FDQuery1.SQL.Text := 'SELECT * FROM real_time WHERE LENGTH(sensordata) - LENGTH(REPLACE(sensordata, "/", "")) = 5'; + FDQuery1.Open; + + SetLength(StName, FDQuery1.RecordCount); + i := 0; + FDQuery1.First; + while not FDQuery1.Eof do begin + i := i + 1; + Addst.id := i; + Addst.name := FDQuery1.FieldByName('BlinkerName').AsString; + StName[i-1] := Addst; + FDQuery1.Next; + end; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] MariaDB 실시간 관측소 로드 완료: ' + IntToStr(Length(StName)) + '개'); + except + on E: Exception do begin + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] DB 관측소 로드 실패: ' + E.Message); + end; + end; +end; + +procedure TForm1.cboCollectModeChange(Sender: TObject); +begin + case cboCollectMode.ItemIndex of + 0: // 1. MariaDB (실시간 DB) + begin + FCollectMode := 1; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 수집 방식 전환: MariaDB (실시간 DB)'); + InitMariaDB; + end; + 1: // 2. MODBUS TCP (포트 502) + begin + FCollectMode := 0; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 수집 방식 전환: MODBUS TCP (포트 502)'); + LoadStationFromINI; + end; + end; + + if Assigned(ini) then begin + ini.WriteInteger('CollectMode', 'Mode', FCollectMode); + ini.UpdateFile; + end; +end; + +procedure TForm1.LoadINI; +begin + fn := ChangeFileExt(Application.ExeName,'.INI'); + ini := TIniFile.Create(fn); + if not FileExists(Fn) then begin + Sn := 'Check'; + ini.WriteString(Sn, 'DebugCheck', '0'); + ini.WriteString(Sn, 'SaveCheck', '0'); + ini.WriteString(Sn, 'AutoCheck', '1'); + + Sn := 'Interval'; + ini.WriteInteger(Sn, 'Interval', 10); + + Sn := 'Login'; + ini.WriteString(Sn,'ID','qstech'); + ini.WriteString(Sn,'PW','71737465636831323334'); + ini.WriteString(Sn,'ID2',''); + ini.WriteString(Sn,'PW2',''); + + Sn := 'IP'; + ini.WriteString(Sn, 'IP', '10.254.254.1'); + ini.WriteString(Sn, 'IP2', ''); + + Sn := 'CollectMode'; + ini.WriteInteger(Sn, 'Mode', 1); + + Sn := 'SYSTEM'; + ini.WriteInteger(Sn, 'ID1', 1); + ini.WriteString(Sn, 'StationName1', '202동'); + ini.WriteInteger(Sn, 'ID2', 2); + ini.WriteString(Sn, 'StationName2', '205동'); + + Sn := 'DB'; + ini.WriteString(Sn, 'IP', '127.0.0.1'); + ini.WriteInteger(Sn, 'Port', 3306); + ini.WriteString(Sn, 'ID', 'root'); + ini.WriteString(Sn, 'PW', 'qsentech!1233'); + ini.WriteString(Sn, 'DataBase', 'dust'); + + ini.UpdateFile; + end; + + Sn := 'Check'; + DebugCheck.Checked := ini.ReadString(Sn,'DebugCheck','0') = '1'; + LogCheck.Checked := ini.ReadString(Sn,'SaveCheck','0') = '1'; + AutoCheck.Checked := ini.ReadString(Sn,'AutoCheck','1') = '1'; + if AutoCheck.Checked then AutoChk := True else AutoChk := False; + + Sn := 'Interval'; + SetInterval.Text := IntToStr(ini.ReadInteger(Sn,'Interval',10)); + tmrUpInterval.Interval := StrToIntDef(SetInterval.Text, 10) * 1000; + + Sn := 'Login'; + LoginId := Trim(ini.ReadString(Sn, 'ID', '')); + if LoginId = '' then + LoginId := Trim(ini.ReadString(Sn, 'ID1', 'qstech')); + if LoginId = '' then LoginId := 'qstech'; + + LoginPw := Trim(ini.ReadString(Sn, 'PW', '')); + if LoginPw = '' then + LoginPw := Trim(ini.ReadString(Sn, 'PASS', '')); + if LoginPw = '' then + LoginPw := Trim(ini.ReadString(Sn, 'PW1', '')); + if LoginPw = '' then + LoginPw := Trim(ini.ReadString(Sn, 'PASS1', '71737465636831323334')); + if LoginPw = '' then LoginPw := '71737465636831323334'; + + LoginId2 := Trim(ini.ReadString(Sn, 'ID2', '')); + if LoginId2 = '' then + LoginId2 := LoginId; + + LoginPw2 := Trim(ini.ReadString(Sn, 'PW2', '')); + if LoginPw2 = '' then + LoginPw2 := Trim(ini.ReadString(Sn, 'PASS2', '')); + if LoginPw2 = '' then + LoginPw2 := LoginPw; + + Sn := 'IP'; + ServerIP := Trim(ini.ReadString(Sn, 'IP', '10.254.254.1')); + if ServerIP = '' then + ServerIP := Trim(ini.ReadString(Sn, 'IP1', '10.254.254.1')); + if ServerIP = '' then ServerIP := '10.254.254.1'; + + ServerIP2 := Trim(ini.ReadString(Sn, 'IP2', '')); + FUseServer2 := (ServerIP2 <> '') and (ServerIP2 <> '0.0.0.0'); + + Sn := 'DB'; + DB_IP := ini.ReadString(Sn, 'IP', '127.0.0.1'); + DB_Port := ini.ReadInteger(Sn, 'Port', 3306); + DB_ID := ini.ReadString(Sn, 'ID', 'root'); + DB_PW := ini.ReadString(Sn, 'PW', 'qsentech!1233'); + DB_DB := ini.ReadString(Sn, 'DataBase', 'dust'); + + Sn := 'CollectMode'; + FCollectMode := ini.ReadInteger(Sn, 'Mode', 1); + if FCollectMode = 1 then + cboCollectMode.ItemIndex := 0 + else + cboCollectMode.ItemIndex := 1; + + if FUseServer2 then + LogMemo.Lines.Add('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 환경설정 로드 완료 (서버1: ' + ServerIP + ' [' + LoginId + '], 서버2: ' + ServerIP2 + ' [' + LoginId2 + '])') + else + LogMemo.Lines.Add('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 환경설정 로드 완료 (서버1: ' + ServerIP + ' [' + LoginId + '], 단일서버)'); + + if FCollectMode = 1 then + InitMariaDB + else + LoadStationFromINI; +end; + +procedure TForm1.DebugCheckClick(Sender: TObject); +begin +end; + +procedure TForm1.LogCheckClick(Sender: TObject); +begin +end; + +procedure TForm1.AutoCheckClick(Sender: TObject); +begin + if AutoCheck.Checked then begin + AutoChk := True; + if not cs.Active and not FIsConnecting then + ConnectServer1; + if FUseServer2 and not csHome2.Active and not FIsConnecting2 then + ConnectServer2; + end + else begin + AutoChk := False; + aliveTimer.Enabled := False; + if cs.Active then + cs.Close; + if csHome2.Active then + csHome2.Close; + if cs2.Connected then + cs2.Disconnect; + FIsBound := False; + FIsBound2 := False; + FIsConnecting := False; + FIsConnecting2 := False; + csConnectBtn.Enabled := True; + end; + + Sn := 'Check'; + ini.WriteString(Sn, 'AutoCheck', IfThen(AutoChk, '1', '0')); + ini.UpdateFile; +end; + +procedure TForm1.SetIntervalBtnClick(Sender: TObject); +begin + tmrUpInterval.Interval := StrToIntDef(SetInterval.Text, 10) * 1000; + + Sn := 'Interval'; + ini.WriteInteger(Sn, 'Interval', StrToIntDef(SetInterval.Text, 10)); +end; + +procedure TForm1.AppEvtMinimize(Sender: TObject); +begin + Hide; + WindowState := wsMinimized; + + TrayIcon1.Visible := True; + TrayIcon1.Animate := True; + TrayIcon1.ShowBalloonHint; +end; + +procedure TForm1.TrayIcon1DblClick(Sender: TObject); +begin + TrayIcon1.Visible := False; + Show; + WindowState := wsNormal; + Application.BringToFront; +end; + +procedure TForm1.tmrExitTimer(Sender: TObject); +begin + case tmrExit.Tag of + 0 : begin + tmrExit.Tag := tmrExit.Tag+1; + end; + 1..4 : tmrExit.Tag := tmrExit.Tag+1; + 5 : begin + tmrExit.Tag := tmrExit.Tag+1; + tmrExit.Enabled := false; + ExitOk := true; + Close; + end; + end; +end; + +procedure TForm1.ConnectServer1; +begin + if cs.Active or FIsConnecting then Exit; + FIsConnecting := True; + cs.Host := ServerIP; + cs.Port := 15010; + try + cs.Open; + except + on E: Exception do begin + FIsConnecting := False; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 서버1 소켓 접속 예외: ' + E.Message); + end; + end; +end; + +procedure TForm1.ConnectServer2; +begin + if not FUseServer2 then Exit; + if csHome2.Active or FIsConnecting2 then Exit; + FIsConnecting2 := True; + csHome2.Host := ServerIP2; + csHome2.Port := 15010; + try + csHome2.Open; + except + on E: Exception do begin + FIsConnecting2 := False; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 서버2 소켓 접속 예외: ' + E.Message); + end; + end; +end; + +procedure TForm1.csConnectBtnClick(Sender: TObject); +begin + ConnectServer1; + if FUseServer2 then + ConnectServer2; + csConnectBtn.Enabled := not (cs.Active and (not FUseServer2 or csHome2.Active)); +end; + +procedure TForm1.csConnect(Sender: TObject; Socket: TCustomWinSocket); +begin + FIsConnecting := False; + FIsBound := False; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] Sys>> cs.Connected - 홈넷 서버1 접속 성공'); + SendLoginPacket(Socket, '서버1', LoginId, LoginPw); +end; + +procedure TForm1.csDisconnect(Sender: TObject; Socket: TCustomWinSocket); +begin + FIsConnecting := False; + FIsBound := False; + if not (FUseServer2 and FIsBound2) then + aliveTimer.Enabled := False; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] Sys>> cs.DisConnected - 홈넷 서버1 연결 해제'); + csConnectBtn.Enabled := True; +end; + +procedure TForm1.csError(Sender: TObject; Socket: TCustomWinSocket; + ErrorEvent: TErrorEvent; var ErrorCode: Integer); +begin + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 서버1 Socket Error>> ' + IntToStr(ErrorCode)); + ErrorCode := 0; + FIsConnecting := False; + FIsBound := False; + if not (FUseServer2 and FIsBound2) then + aliveTimer.Enabled := False; + try + cs.Close; + except + end; + csConnectBtn.Enabled := True; +end; + +procedure TForm1.csHome2Connect(Sender: TObject; Socket: TCustomWinSocket); +begin + FIsConnecting2 := False; + FIsBound2 := False; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] Sys>> csHome2.Connected - 홈넷 서버2 접속 성공'); + SendLoginPacket(Socket, '서버2', LoginId2, LoginPw2); +end; + +procedure TForm1.csHome2Disconnect(Sender: TObject; Socket: TCustomWinSocket); +begin + FIsConnecting2 := False; + FIsBound2 := False; + if not FIsBound then + aliveTimer.Enabled := False; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] Sys>> csHome2.DisConnected - 홈넷 서버2 연결 해제'); + csConnectBtn.Enabled := True; +end; + +procedure TForm1.csHome2Error(Sender: TObject; Socket: TCustomWinSocket; + ErrorEvent: TErrorEvent; var ErrorCode: Integer); +begin + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 서버2 Socket Error>> ' + IntToStr(ErrorCode)); + ErrorCode := 0; + FIsConnecting2 := False; + FIsBound2 := False; + if not FIsBound then + aliveTimer.Enabled := False; + try + csHome2.Close; + except + end; + csConnectBtn.Enabled := True; +end; + +procedure TForm1.ProcessServerRead(Socket: TCustomWinSocket; const AServerName: string; var AIsBound: Boolean); +var + pkText : ansistring; + i, n : integer; + buf : array[0..1024] of byte; + ErrChkPk, ErrLogTxt, ErrViewTxt : string; + msgType : integer; +begin + FillMemory(@buf, length(buf), 0); + + pkText := ''; + ErrChkPk := ''; + ErrLogTxt := ''; + ErrViewTxt := ''; + + n := socket.ReceiveLength; + if n <= 0 then Exit; + if n > SizeOf(buf) then n := SizeOf(buf); + socket.ReceiveBuf(buf, n); + + for i := 0 to n-1 do pkText := pkText + format('%0.2X,',[ord(buf[i])]); + + if n >= 8 then begin + for i := 4 to 7 do ErrChkPk := ErrChkPk + format('%0.2X,',[ord(buf[i])]); + end; + + if CompareStr(ErrChkPk, ErrChkText) = 0 then begin + ErrLogTxt := '[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] 오류 패킷 발생 >> '; + + if n >= 4 then begin + for i := n-4 to n-1 do ErrViewTxt := ErrViewTxt + format('%0.2X,',[ord(buf[i])]); + end; + + if CompareStr(ErrViewTxt, '01,00,00,00,') = 0 then + ErrLogTxt := ErrLogTxt + '아이디 불일치' + else if CompareStr(ErrViewTxt, '02,00,00,00,') = 0 then + ErrLogTxt := ErrLogTxt + '비밀번호 불일치' + else if CompareStr(ErrViewTxt, '03,00,00,00,') = 0 then + ErrLogTxt := ErrLogTxt + '해당 필드 존재' + else + ErrLogTxt := ErrLogTxt + '로그인 미수행 상태 또는 기타 오류'; + + AddLog(ErrLogTxt); + AIsBound := False; + Exit; + end; + + msgType := 0; + if n >= 8 then + CopyMemory(@msgType, @buf[4], 4); + + if (msgType = (ENVIRONMENT_SENSOR_TYPE or BIND_ACK)) or ((msgType = 0) and (n = 32) and not AIsBound) then begin + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] RCV Login : Rx<< ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] 로그인(BIND) 성공'); + AIsBound := True; + aliveTimer.Interval := 30000; + aliveTimer.Enabled := True; + end + else if (msgType = (ENVIRONMENT_SENSOR_TYPE or ALIVE_ACK)) or ((msgType = 0) and (n = 32) and AIsBound) then begin + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] RCV Alive : Rx<< ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] 생존 신호 수신 완료'); + end + else if (msgType = (ENVIRONMENT_SENSOR_TYPE or ENVIRONMENT_SENSOR_ADD_ACK)) or ((n > 0) and (n mod 32 = 0)) then begin + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] RCV Add : Rx<< ' + pkText); + if n = 32 then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] 정보 등록(ADD) 완료') + else + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] 정보 등록(ADD) ' + IntToStr(n div 32) + '건 일괄 완료 (길이: ' + IntToStr(n) + ')'); + end + else if msgType = (ENVIRONMENT_SENSOR_TYPE or ENVIRONMENT_SENSOR_REP) then begin + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] RCV Req : Rx<< ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] 정보 요청 응답 완료'); + end + else if msgType = (ENVIRONMENT_SENSOR_TYPE or ENVIRONMENT_SENSOR_MULTI_REP) then begin + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] RCV ReqMulti : Rx<< ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] 정보 요청(멀티) 응답 완료'); + end + else begin + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] RCV Unknown (' + IntToStr(n) + ' bytes): Rx<< ' + pkText); + end; +end; + +procedure TForm1.csRead(Sender: TObject; Socket: TCustomWinSocket); +begin + ProcessServerRead(Socket, '서버1', FIsBound); +end; + +procedure TForm1.csHome2Read(Sender: TObject; Socket: TCustomWinSocket); +begin + ProcessServerRead(Socket, '서버2', FIsBound2); +end; + +procedure TForm1.SendLoginPacket(ASocket: TCustomWinSocket; const AServerName: string; const ALoginId: string; const ALoginPw: string); +var + pk : array[0..27+103+1] of byte; + i, j : integer; + str : string; + pkText : ansistring; +begin + if (ASocket = nil) or (not ASocket.Connected) then Exit; + + FillMemory(@pk, length(pk), 0); + pkText := ''; + + i := $12345678; + CopyMemory(@pk[0],@i,4); + + i := ENVIRONMENT_SENSOR_TYPE or BIND; + CopyMemory(@pk[4],@i,4); + + i := 104; + CopyMemory(@pk[8],@i,4); + + i := 10; + CopyMemory(@pk[12],@i,4); + + i := 100; + CopyMemory(@pk[16],@i,4); + + i := 1000; + CopyMemory(@pk[20],@i,4); + + i := 1234; + CopyMemory(@pk[24],@i,4); + + i := 123; + CopyMemory(@pk[28],@i,4); + + i := 456; + CopyMemory(@pk[32],@i,4); + + i := 789; + CopyMemory(@pk[36],@i,4); + CopyMemory(@pk[40],@i,4); + CopyMemory(@pk[44],@i,4); + CopyMemory(@pk[48],@i,4); + + str := ALoginId; + for i := 1 to length(str) do begin + if 52 + i - 1 < 92 then + pk[52+i-1] := ord(str[i]); + end; + + str := ''; + if (Length(ALoginPw) >= 2) and (Length(ALoginPw) mod 2 = 0) then begin + j := 1; + while (j <= Length(ALoginPw)) and (CharInSet(ALoginPw[j], ['0'..'9', 'a'..'f', 'A'..'F'])) do + Inc(j); + + if j > Length(ALoginPw) then begin + j := 1; + while j < Length(ALoginPw) do begin + str := str + chr(StrToIntDef('$' + Copy(ALoginPw, j, 2), 0)); + Inc(j, 2); + end; + end + else + str := ALoginPw; + end + else + str := ALoginPw; + + for i := 1 to length(str) do begin + if 92 + i - 1 < 132 then + pk[92+i-1] := ord(str[i]); + end; + + for i := 0 to 131 do pkText := pkText + format('%0.2X,',[ord(pk[i])]); + + ASocket.SendBuf(pk, 132); + + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] Send Login : Tx>> ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] 로그인 패킷 전송 완료 (ID: ' + ALoginId + ')'); +end; + +procedure TForm1.loginBtnClick(Sender: TObject); +begin + if cs.Active then + SendLoginPacket(cs.Socket, '서버1', LoginId, LoginPw); + if FUseServer2 and csHome2.Active then + SendLoginPacket(csHome2.Socket, '서버2', LoginId2, LoginPw2); +end; + +procedure TForm1.LogoutBtnClick(Sender: TObject); +begin + FIsBound := False; + FIsBound2 := False; + aliveTimer.Enabled := False; + if cs.Active then + cs.Close; + if csHome2.Active then + csHome2.Close; + csConnectBtn.Enabled := True; +end; + +procedure TForm1.SendAlivePacket(ASocket: TCustomWinSocket; const AServerName: string); +var + pk : array[0..27+35+1] of byte; + i : integer; + pkText : ansistring; +begin + if (ASocket = nil) or (not ASocket.Connected) then Exit; + + FillMemory(@pk, length(pk), 0); + pkText := ''; + + i := $12345678; + CopyMemory(@pk[0],@i,4); + + i := ENVIRONMENT_SENSOR_TYPE or ALIVE; + CopyMemory(@pk[4],@i,4); + + i := 36; + CopyMemory(@pk[8],@i,4); + + i := 10; + CopyMemory(@pk[12],@i,4); + + i := 100; + CopyMemory(@pk[16],@i,4); + + i := 1000; + CopyMemory(@pk[20],@i,4); + + i := 1234; + CopyMemory(@pk[24],@i,4); + + i := 123; + CopyMemory(@pk[28],@i,4); + + i := 456; + CopyMemory(@pk[32],@i,4); + + i := 789; + CopyMemory(@pk[36],@i,4); + CopyMemory(@pk[40],@i,4); + CopyMemory(@pk[44],@i,4); + CopyMemory(@pk[48],@i,4); + + i := 12; + CopyMemory(@pk[52],@i,4); + + i := 34; + CopyMemory(@pk[56],@i,4); + + i := 56; + CopyMemory(@pk[60],@i,4); + + for i := 0 to 63 do pkText := pkText + format('%0.2X,',[ord(pk[i])]); + + ASocket.SendBuf(pk, 64); + + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] Send Alive : Tx>> ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [' + AServerName + '] 생존 신호 패킷 전송 완료'); +end; + +procedure TForm1.AliveBtnClick(Sender: TObject); +begin + if cs.Active and FIsBound then + SendAlivePacket(cs.Socket, '서버1'); + if FUseServer2 and csHome2.Active and FIsBound2 then + SendAlivePacket(csHome2.Socket, '서버2'); +end; + +procedure TForm1.aliveTimerTimer(Sender: TObject); +begin + if cs.Active and FIsBound then + SendAlivePacket(cs.Socket, '서버1'); + if FUseServer2 and csHome2.Active and FIsBound2 then + SendAlivePacket(csHome2.Socket, '서버2'); +end; + +function TForm1.TruncateUTF8ToMaxBytes(const S: string; MaxBytes: Integer): TBytes; +var + i, ByteCount: Integer; + CharLen: Integer; + CharBytes: TBytes; +begin + Result := nil; + ByteCount := 0; + i := 1; + while i <= Length(S) do + begin + if (Ord(S[i]) >= $D800) and (Ord(S[i]) <= $DBFF) and (i < Length(S)) then + CharLen := 2 + else + CharLen := 1; + + try + CharBytes := TEncoding.UTF8.GetBytes(Copy(S, i, CharLen)); + except + CharBytes := nil; + end; + + if (Length(CharBytes) = 0) or (ByteCount + Length(CharBytes) > MaxBytes) then + Break; + + Result := Result + CharBytes; + Inc(ByteCount, Length(CharBytes)); + Inc(i, CharLen); + end; +end; + +procedure TForm1.addBtnClick(Sender: TObject); +var + pk : array[0..27+111+1] of byte; + i : integer; + f : single; + str : string; + utf8Bytes: TBytes; + pkText : ansistring; + handle : HWND; + sendSuccess : boolean; +begin + try + FillMemory(@pk, length(pk), 0); + + pkText := ''; + + i := $12345678; + CopyMemory(@pk[0],@i,4); + + i := ENVIRONMENT_SENSOR_TYPE or ENVIRONMENT_SENSOR_ADD; + CopyMemory(@pk[4],@i,4); + + i := 112; + CopyMemory(@pk[8],@i,4); + + i := 10; + CopyMemory(@pk[12],@i,4); + + i := 100; + CopyMemory(@pk[16],@i,4); + + i := 1000; + CopyMemory(@pk[20],@i,4); + + i := 1234; + CopyMemory(@pk[24],@i,4); + + i := StrToIntDef(nArea.Text,0); + CopyMemory(@pk[28],@i,4); + + str := szDataTime.Text; + for i := 1 to length(str) do begin + pk[32+i-1] := ord(str[i]); + end; + + str := szMangName.Text; + for i := 1 to length(str) do begin + pk[52+i-1] := ord(str[i]); + end; + + str := szDataTerm.Text; + for i := 1 to length(str) do begin + pk[72+i-1] := ord(str[i]); + end; + + str := szStationName.Text; + utf8Bytes := TruncateUTF8ToMaxBytes(str, 20); + for i := 0 to Length(utf8Bytes) - 1 do + pk[92 + i] := utf8Bytes[i]; + + i := StrToIntDef(nPm10Value.Text,0); + CopyMemory(@pk[112],@i,4); + + i := StrToIntDef(nPm25Value.Text,0); + CopyMemory(@pk[116],@i,4); + + f := StrToFloatDef(fCtValue.Text,0); + CopyMemory(@pk[120],@f,4); + + f := StrToFloatDef(fRhValue.Text,0); + CopyMemory(@pk[124],@f,4); + + f := StrToFloatDef(fO3Value.Text,0); + CopyMemory(@pk[128],@f,4); + + f := StrToFloatDef(fNo2Value.Text,0); + CopyMemory(@pk[132],@f,4); + + f := StrToFloatDef(fUvValue.Text,0); + CopyMemory(@pk[136],@f,4); + + for i := 0 to 139 do pkText := pkText + format('%0.2X,',[ord(pk[i])]); + + sendSuccess := false; + + if cs.Active and FIsBound then begin + cs.Socket.SendBuf(pk, 140); + sendSuccess := true; + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버1] Send Add : Tx>> ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버1] 정보 등록 패킷 전송 완료 : ' + StName[AddCount].name); + end; + + if FUseServer2 and csHome2.Active and FIsBound2 then begin + csHome2.Socket.SendBuf(pk, 140); + sendSuccess := true; + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버2] Send Add : Tx>> ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버2] 정보 등록 패킷 전송 완료 : ' + StName[AddCount].name); + end; + + handle := FindWindow(nil, '미세먼지 모니터링 시스템'); + if handle <> 0 then begin + if sendSuccess then + PostMessage(handle, WM_USER + 1, 1, 1) + else + PostMessage(handle, WM_USER + 2, 0, 0); + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 윈도우 메시지 전송.'); + end; + + except + on E: Exception do begin + handle := FindWindow(nil, '미세먼지 모니터링 시스템'); + if handle <> 0 then begin + PostMessage(handle, WM_USER + 2, 0, 0); + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 윈도우 메시지(실패) 전송.'); + end; + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 정보 등록 패킷 전송 오류: ' + E.Message); + end; + end; +end; + +procedure TForm1.reqBtnClick(Sender: TObject); +var + pk : array[0..27+3+1] of byte; + i : integer; + pkText : ansistring; +begin + FillMemory(@pk, length(pk), 0); + + pkText := ''; + + i := $12345678; + CopyMemory(@pk[0],@i,4); + + i := ENVIRONMENT_SENSOR_TYPE or ENVIRONMENT_SENSOR_REQ; + CopyMemory(@pk[4],@i,4); + + i := 4; + CopyMemory(@pk[8],@i,4); + + i := 10; + CopyMemory(@pk[12],@i,4); + + i := 100; + CopyMemory(@pk[16],@i,4); + + i := 1000; + CopyMemory(@pk[20],@i,4); + + i := 1234; + CopyMemory(@pk[24],@i,4); + + i := StrToIntDef(nAreaReq.Text, 0); + CopyMemory(@pk[28],@i,4); + + for i := 0 to 31 do pkText := pkText + format('%0.2X,',[ord(pk[i])]); + + SEND_TYPE := 4; + + if cs.Active and FIsBound then begin + cs.Socket.SendBuf(pk, 32); + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버1] Send Req : Tx>> ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버1] 정보 요청 패킷 전송 완료'); + end; + + if FUseServer2 and csHome2.Active and FIsBound2 then begin + csHome2.Socket.SendBuf(pk, 32); + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버2] Send Req : Tx>> ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버2] 정보 요청 패킷 전송 완료'); + end; +end; + +procedure TForm1.reqMultiBtnClick(Sender: TObject); +var + pk : array[0..27+7+1] of byte; + i : integer; + pkText : ansistring; +begin + FillMemory(@pk, length(pk), 0); + + pkText := ''; + + i := $12345678; + CopyMemory(@pk[0],@i,4); + + i := ENVIRONMENT_SENSOR_TYPE or ENVIRONMENT_SENSOR_MULTI_REQ; + CopyMemory(@pk[4],@i,4); + + i := 8; + CopyMemory(@pk[8],@i,4); + + i := 10; + CopyMemory(@pk[12],@i,4); + + i := 100; + CopyMemory(@pk[16],@i,4); + + i := 1000; + CopyMemory(@pk[20],@i,4); + + i := 1234; + CopyMemory(@pk[24],@i,4); + + i := StrToIntDef(nAreaReq.Text, 0); + CopyMemory(@pk[28],@i,4); + + i := 0; + CopyMemory(@pk[32],@i,4); + + for i := 0 to 35 do pkText := pkText + format('%0.2X,',[ord(pk[i])]); + + SEND_TYPE := 5; + + if cs.Active and FIsBound then begin + cs.Socket.SendBuf(pk, 36); + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버1] Send ReqMulti : Tx>> ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버1] 정보 요청(멀티) 패킷 전송 완료'); + end; + + if FUseServer2 and csHome2.Active and FIsBound2 then begin + csHome2.Socket.SendBuf(pk, 36); + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버2] Send ReqMulti : Tx>> ' + pkText); + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] [서버2] 정보 요청(멀티) 패킷 전송 완료'); + end; +end; + +procedure TForm1.readBtnClick(Sender: TObject); +var + response, response2: TIdBytes; + i : integer; + text : ansistring; + sensorData : string; + splitData : TArray; +begin + if (AddCount < 0) or (AddCount >= Length(StName)) then Exit; + if StName[AddCount].id <= 0 then Exit; + + try + try + if FCollectMode = 1 then begin + // 1. MariaDB 수집 모드 + if not FDCon.Connected then begin + InitMariaDB; + if not FDCon.Connected then Exit; + end; + + FDQuery1.SQL.Clear; + FDQuery1.SQL.Text := format('SELECT * FROM real_time WHERE BlinkerName = "%s"', [StName[AddCount].name]); + FDQuery1.Open; + + if FDQuery1.RecordCount > 0 then begin + sensorData := FDQuery1.FieldByName('SensorData').AsString; + splitData := sensorData.Split(['/']); + + if Length(splitData) >= 4 then begin + nPm10Value.Text := splitData[0]; + nPm25Value.Text := splitData[1]; + fCtValue.Text := splitData[2]; + fRhValue.Text := splitData[3]; + end; + + nArea.Text := IntToStr(StName[AddCount].id); + szStationName.Text := StName[AddCount].name; + szDataTime.Text := FormatDateTime('yyyy-mm-dd hh:nn:ss', Now); + + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] RCV DB Dust [' + StName[AddCount].name + ']: ' + sensorData); + + addBtn.OnClick(nil); + end + else begin + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] DB 데이터 없음 [' + StName[AddCount].name + ']'); + end; + end + else begin + // 0. MODBUS TCP 수집 모드 + cs2.Host := '127.0.0.1'; + cs2.Port := 502; + cs2.ConnectTimeout := 2000; + cs2.ReadTimeout := 2000; + cs2.Connect; + + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 미세먼지 수집기 접속 완료'); + + SetLength(response, 12); + response[0] := 0; + response[1] := 1; + response[2] := 0; + response[3] := 0; + response[4] := 0; + response[5] := 6; + response[6] := 1; + response[7] := $03; + response[8] := 0; + response[9] := IfThen(AutoChk, (StName[AddCount].id - 1) * 3, StrToIntDef(miseAddress.Text, 1)); + response[10] := 0; + response[11] := 3; + + cs2.IOHandler.Write(response); + cs2.IOHandler.WriteBufferFlush; + cs2.IOHandler.ReadBytes(response2, 15); + + nPm10Value.Text := IntToStr(ord(response2[14])); + nPm25Value.Text := IntToStr(ord(response2[12])); + nArea.Text := IntToStr(StName[AddCount].id); + szStationName.Text := StName[AddCount].name; + szDataTime.Text := FormatDateTime('yyyy-mm-dd hh:nn:ss', Now); + + text := ''; + for i := 0 to 14 do text := text + format('%0.2X,',[ord(response2[i])]); + + if DebugCheck.Checked then + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] RCV MODBUS Dust : ' + text); + + addBtn.OnClick(nil); + end; + except + on E: Exception do begin + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 미세먼지 수집 실패 [' + StName[AddCount].name + ']: ' + E.Message); + end; + end; + finally + if (FCollectMode = 0) and cs2.Connected then begin + try + cs2.Disconnect; + except + end; + end; + end; +end; + +procedure TForm1.tmrInitTimer(Sender: TObject); +begin + TotalTimer.Enabled := false; + aliveTimer.Interval := 30000; + aliveTimer.Enabled := false; + tmrUpInterval.Enabled := true; + tmrInit.Enabled := false; + tmrInit.OnTimer := nil; + + if AutoChk then begin + csConnectBtnClick(nil); + end; +end; + +procedure TForm1.tmrUpIntervalTimer(Sender: TObject); +var + i : integer; + anyBound : boolean; +begin + if not AutoChk then Exit; + + // 1. 서버1 재연결 체크 + if not cs.Active then begin + if not FIsConnecting then begin + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 서버1 미연결 - 재연결 시도'); + ConnectServer1; + end; + end; + + // 2. 서버2 재연결 체크 + if FUseServer2 and not csHome2.Active then begin + if not FIsConnecting2 then begin + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] 서버2 미연결 - 재연결 시도'); + ConnectServer2; + end; + end; + + // 3. 인증 완료(BIND)된 서버가 최소 1개 이상 있는지 확인 + anyBound := FIsBound or (FUseServer2 and FIsBound2); + if not anyBound then begin + AddLog('[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] BIND 대기 중 (인증 완료된 서버 없음)'); + Exit; + end; + + // 4. MariaDB 모드일 때 최신 관측소 목록 갱신 + if FCollectMode = 1 then + LoadStationFromDB; + + // 5. 각 관측소별 데이터 수집 및 전송 + for i := 0 to Length(StName) - 1 do begin + if StName[i].id > 0 then begin + AddCount := i; + readBtnClick(nil); + Sleep(50); + end; + end; +end; + +procedure TForm1.TotalTimerTimer(Sender: TObject); +begin +end; + +function TForm1.MD5Str(const S: String): String; +var IdMD5: TIdHashMessageDigest5; +begin + IdMD5:=TIdHashMessageDigest5.Create; + try + Result:=IdMD5.HashStringAsHex(S); + finally + FreeAndNil(IdMD5); + end; +end; + +function TForm1.MD5File(const FilePath: String): String; +var + IdMD5: TIdHashMessageDigest5; + fStream: TFileStream; +begin + Result:=''; + if not FileExists(FilePath) then Exit; + + IdMD5:=TIdHashMessageDigest5.Create; + fStream:=TFileStream.Create(FilePath, fmOpenRead or fmShareDenyWrite); + try + Result:=IdMD5.HashStreamAsHex(fStream); + finally + FreeAndNil(fStream); + FreeAndNil(IdMD5); + end; +end; + +function TForm1.CRC16(Data: AnsiString): AnsiString; +var + i,j,iSum,f : Integer; +begin + iSum := $FFFF; + for i := 1 to Length(Data) do + begin + iSum := iSum xor Ord(Data[i]); + for j := 1 to 8 do + begin + f := iSum and $0001; + iSum := iSum shr 1; + if f = 1 then iSum := iSum xor $A001; + end; + end; + Result := AnsiChar(Lo(iSum)) + AnsiChar(Hi(iSum)); +end; + +procedure TForm1.ClrBtnClick(Sender: TObject); +begin + LogMemo.Clear; +end; + +procedure TForm1.ExitBtnClick(Sender: TObject); +begin + ExitOk := true; + Form1.Close; +end; + +function TForm1.ConvertUTF8ToANSI(const UTF8Text: string): AnsiString; +var + UTF8Bytes: TBytes; + UTF8Stream: TBytesStream; + ANSIText: AnsiString; +begin + UTF8Bytes := TEncoding.UTF8.GetBytes(UTF8Text); + UTF8Stream := TBytesStream.Create(UTF8Bytes); + try + ANSIText := TEncoding.ANSI.GetString(UTF8Stream.Bytes, 0, UTF8Stream.Size); + Result := ANSIText; + finally + UTF8Stream.Free; + end; +end; + +end.