unit uMain; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, System.IniFiles, Data.DB, U_DM, uLogManagerThread; type TfrmMain = class(TForm) GroupBox1: TGroupBox; rgModel: TRadioGroup; GroupBox4: TGroupBox; lbStatus: TListBox; Panel1: TPanel; btnStatRead: TButton; btnReset: TButton; btnExit: TButton; GroupBox2: TGroupBox; btnSoundOff: TButton; btnSound1: TButton; btnSound2: TButton; btnSound3: TButton; btnSound4: TButton; btnSound5: TButton; GroupBox3: TGroupBox; edtIP1: TEdit; edtIP4: TEdit; edtIP3: TEdit; edtIP2: TEdit; Label1: TLabel; Label2: TLabel; edtPort: TEdit; GroupBox5: TGroupBox; btnRedBlink: TButton; Label7: TLabel; Label6: TLabel; Label5: TLabel; Label4: TLabel; Label3: TLabel; btnWhiteOff: TButton; btnWhiteBlink: TButton; btnWhiteOn: TButton; btnBlueOff: TButton; btnBlueBlink: TButton; btnBlueOn: TButton; btnGreenOff: TButton; btnGreenBlink: TButton; btnGreenOn: TButton; btnYellowOff: TButton; btnYellowBlink: TButton; btnYellowOn: TButton; btnRedOff: TButton; btnRedOn: TButton; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure btnLampClick(Sender: TObject); procedure btnSoundClick(Sender: TObject); procedure btnStatReadClick(Sender: TObject); procedure btnResetClick(Sender: TObject); procedure btnExitClick(Sender: TObject); private { Private declarations } c_pIdata: array[0..14] of Byte; c_pIpadd: array[0..3] of Byte; // 환경 설정 변수 FDBHost, FDBUser, FDBPass, FDBName: string; FDBPort: Integer; // 폴링 타이머 FPollingTimer: TTimer; function SendCommand: Boolean; procedure LogMessage(const Msg: string); procedure LoadSettings; procedure InitDB; procedure OnPollingTimer(Sender: TObject); public { Public declarations } end; var frmMain: TfrmMain; function Tcp_Qu_RW(iPort: Integer; var pbIp: Byte; var pbData: Byte): Boolean; stdcall; external 'Qtvc_dll.dll'; implementation {$R *.dfm} const C_lampoff = 0; C_lampon = 1; C_lampblink = 2; D_not = 100; procedure TfrmMain.LoadSettings; var Ini: TIniFile; begin Ini := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'settings.ini'); try FDBHost := Ini.ReadString('DB', 'Host', '0.0.0.0'); FDBPort := Ini.ReadInteger('DB', 'Port', 33063); FDBUser := Ini.ReadString('DB', 'User', 'mmcl_user'); FDBPass := Ini.ReadString('DB', 'Password', ''); FDBName := Ini.ReadString('DB', 'Database', 'mmcl_db'); finally Ini.Free; end; end; procedure TfrmMain.InitDB; begin DM.fdConnEtc.Close; DM.fdConnEtc.Params.Clear; DM.fdConnEtc.Params.Add('DriverID=MySQL'); DM.fdConnEtc.Params.Add('Server=' + FDBHost); DM.fdConnEtc.Params.Add('Port=' + IntToStr(FDBPort)); DM.fdConnEtc.Params.Add('Database=' + FDBName); DM.fdConnEtc.Params.Add('User_Name=' + FDBUser); if FDBPass <> '' then DM.fdConnEtc.Params.Add('Password=' + FDBPass); DM.fdConnEtc.Params.Add('CharacterSet=utf8mb4'); try DM.fdConnEtc.Connected := True; LogMessage('DB 연결 성공 (' + FDBHost + ')'); AddLog_Thread('DB 연결 성공 (' + FDBHost + ')'); except on E: Exception do begin LogMessage('DB 연결 실패: ' + E.Message); AddLog_Thread('DB 연결 실패: ' + E.Message); end; end; end; procedure TfrmMain.FormCreate(Sender: TObject); var i: Integer; begin // Initialize LED data for i := 0 to 14 do c_pIdata[i] := D_not; c_pIdata[0] := 1; // 1-write, 0-read c_pIdata[1] := 0; // type default // 1. 로그 시스템 초기화 InitLogger(ExtractFilePath(Application.ExeName) + 'Logs', 'LEDAgent', rtDaily); AddLog_Thread('=== LEDAgent 시작 ==='); // 2. INI 설정 로드 LoadSettings; // 3. DB 연결 InitDB; // 4. 폴링 타이머 가동 (1초=1000ms) FPollingTimer := TTimer.Create(Self); FPollingTimer.Interval := 1000; FPollingTimer.OnTimer := OnPollingTimer; FPollingTimer.Enabled := True; end; procedure TfrmMain.FormDestroy(Sender: TObject); begin if Assigned(FPollingTimer) then begin FPollingTimer.Enabled := False; FPollingTimer.Free; end; if Assigned(DM) and DM.fdConnEtc.Connected then DM.fdConnEtc.Close; AddLog_Thread('=== LEDAgent 종료 ==='); StopLogger; end; procedure TfrmMain.OnPollingTimer(Sender: TObject); var SensorNo, DbLedPort: Integer; DbLedIp: string; T1, T2, T3: Integer; IpParts: TArray; SendSuccess: Boolean; i: Integer; begin FPollingTimer.Enabled := False; // 겹침 방지 try if not DM.fdConnEtc.Connected then Exit; DM.fdQryEtc.Close; DM.fdQryEtc.SQL.Text := 'SELECT sensor_no, target_ch1_statusID, target_ch2_statusID, target_ch3_statusID, led_ip, led_port ' + 'FROM sensor_info ' + 'WHERE sensor_typeid = 2 ' + ' AND (target_ch1_statusID != value_ch1_statusID ' + ' OR target_ch2_statusID != value_ch2_statusID ' + ' OR target_ch3_statusID != value_ch3_statusID) ' + 'LIMIT 1'; try DM.fdQryEtc.Open; except on E: Exception do begin AddLog_Thread('DB 폴링 에러: ' + E.Message); Exit; end; end; if not DM.fdQryEtc.IsEmpty then begin SensorNo := DM.fdQryEtc.FieldByName('sensor_no').AsInteger; T1 := DM.fdQryEtc.FieldByName('target_ch1_statusID').AsInteger; // Green T2 := DM.fdQryEtc.FieldByName('target_ch2_statusID').AsInteger; // Yellow T3 := DM.fdQryEtc.FieldByName('target_ch3_statusID').AsInteger; // Red DbLedIp := DM.fdQryEtc.FieldByName('led_ip').AsString; DbLedPort := DM.fdQryEtc.FieldByName('led_port').AsInteger; if DbLedPort = 0 then DbLedPort := 20000; LogMessage(Format('DB 명령 감지 - Sensor:%d, IP:%s (Target: G:%d Y:%d R:%d)', [SensorNo, DbLedIp, T1, T2, T3])); AddLog_Thread(Format('DB 명령 감지 - Sensor:%d, IP:%s (Target: G:%d Y:%d R:%d)', [SensorNo, DbLedIp, T1, T2, T3])); // 1. LED 전송용 데이터 배열 구성 for i := 0 to 14 do c_pIdata[i] := D_not; c_pIdata[0] := 1; // write 모드 c_pIdata[1] := 0; // 모델 기본값 c_pIdata[7] := 0; // 사운드 끄기 기본 c_pIdata[4] := T1; // Green (2: Red, 3: Yellow, 4: Green) c_pIdata[3] := T2; // Yellow c_pIdata[2] := T3; // Red // 2. IP 및 Port 준비 (DB 기준) IpParts := DbLedIp.Split(['.']); if Length(IpParts) = 4 then begin c_pIpadd[0] := StrToIntDef(IpParts[0], 192); c_pIpadd[1] := StrToIntDef(IpParts[1], 168); c_pIpadd[2] := StrToIntDef(IpParts[2], 200); c_pIpadd[3] := StrToIntDef(IpParts[3], 114); end; // 3. DLL 호출하여 하드웨어 제어 SendSuccess := Tcp_Qu_RW(DbLedPort, c_pIpadd[0], c_pIdata[0]); if SendSuccess then begin AddLog_Thread('하드웨어 제어 성공'); LogMessage('하드웨어 제어 성공'); // 4. DB 상태 갱신 (Handshake 완료) DM.fdQryEtc.Close; DM.fdQryEtc.SQL.Text := 'UPDATE sensor_info ' + 'SET value_ch1_statusID = :v1, value_ch2_statusID = :v2, value_ch3_statusID = :v3 ' + 'WHERE sensor_no = :sno'; DM.fdQryEtc.ParamByName('v1').AsInteger := T1; DM.fdQryEtc.ParamByName('v2').AsInteger := T2; DM.fdQryEtc.ParamByName('v3').AsInteger := T3; DM.fdQryEtc.ParamByName('sno').AsInteger := SensorNo; DM.fdQryEtc.ExecSQL; AddLog_Thread('DB 완료 갱신 (Handshake 종료)'); LogMessage('DB 반영 완료'); end else begin AddLog_Thread('TCP/IP 제어 실패 (하드웨어 연결 확인)'); LogMessage('LED 통신 실패'); end; end; finally FPollingTimer.Enabled := True; end; end; //--------------------------------------------------------- // 기존 UI 매뉴얼 테스트 로직 (수동 테스트용 유지) //--------------------------------------------------------- procedure TfrmMain.LogMessage(const Msg: string); begin lbStatus.Items.Insert(0, FormatDateTime('hh:nn:ss', Now) + ' ' + Msg); end; function TfrmMain.SendCommand: Boolean; var iPort: Integer; begin Result := False; try c_pIpadd[0] := StrToIntDef(edtIP1.Text, 192); c_pIpadd[1] := StrToIntDef(edtIP2.Text, 168); c_pIpadd[2] := StrToIntDef(edtIP3.Text, 200); c_pIpadd[3] := StrToIntDef(edtIP4.Text, 114); iPort := StrToIntDef(edtPort.Text, 20000); c_pIdata[1] := rgModel.ItemIndex; Result := Tcp_Qu_RW(iPort, c_pIpadd[0], c_pIdata[0]); if Result then LogMessage('[Success send]') else LogMessage('[Send Error]'); except on E: Exception do LogMessage('[Error] ' + E.Message); end; end; procedure TfrmMain.btnLampClick(Sender: TObject); var Btn: TButton; ColorIdx: Integer; Action: Integer; i: Integer; begin for i := 2 to 6 do c_pIdata[i] := D_not; c_pIdata[7] := D_not; c_pIdata[0] := 1; Btn := Sender as TButton; if (Btn = btnRedOn) or (Btn = btnRedBlink) or (Btn = btnRedOff) then ColorIdx := 2 else if (Btn = btnYellowOn) or (Btn = btnYellowBlink) or (Btn = btnYellowOff) then ColorIdx := 3 else if (Btn = btnGreenOn) or (Btn = btnGreenBlink) or (Btn = btnGreenOff) then ColorIdx := 4 else if (Btn = btnBlueOn) or (Btn = btnBlueBlink) or (Btn = btnBlueOff) then ColorIdx := 5 else if (Btn = btnWhiteOn) or (Btn = btnWhiteBlink) or (Btn = btnWhiteOff) then ColorIdx := 6 else Exit; if Btn.Caption = 'ON' then Action := C_lampon else if Btn.Caption = 'ON/OFF' then Action := C_lampblink else Action := C_lampoff; c_pIdata[ColorIdx] := Action; SendCommand; end; procedure TfrmMain.btnSoundClick(Sender: TObject); var Btn: TButton; i: Integer; begin for i := 2 to 6 do c_pIdata[i] := D_not; c_pIdata[0] := 1; Btn := Sender as TButton; if Btn = btnSoundOff then c_pIdata[7] := 0 else if Btn = btnSound1 then c_pIdata[7] := 1 else if Btn = btnSound2 then c_pIdata[7] := 2 else if Btn = btnSound3 then c_pIdata[7] := 3 else if Btn = btnSound4 then c_pIdata[7] := 4 else if Btn = btnSound5 then c_pIdata[7] := 5 else c_pIdata[7] := D_not; SendCommand; end; procedure TfrmMain.btnStatReadClick(Sender: TObject); var iPort: Integer; Success: Boolean; StatusStr: string; begin try c_pIpadd[0] := StrToIntDef(edtIP1.Text, 192); c_pIpadd[1] := StrToIntDef(edtIP2.Text, 168); c_pIpadd[2] := StrToIntDef(edtIP3.Text, 200); c_pIpadd[3] := StrToIntDef(edtIP4.Text, 114); iPort := StrToIntDef(edtPort.Text, 20000); c_pIdata[0] := 0; Success := Tcp_Qu_RW(iPort, c_pIpadd[0], c_pIdata[0]); if Success then begin StatusStr := '[Read Success] '; if c_pIdata[2] = 0 then StatusStr := StatusStr + 'R-OFF ' else if c_pIdata[2] = 1 then StatusStr := StatusStr + 'R-ON ' else if c_pIdata[2] = 2 then StatusStr := StatusStr + 'R-BLINK '; if c_pIdata[3] = 0 then StatusStr := StatusStr + 'Y-OFF ' else if c_pIdata[3] = 1 then StatusStr := StatusStr + 'Y-ON ' else if c_pIdata[3] = 2 then StatusStr := StatusStr + 'Y-BLINK '; if c_pIdata[4] = 0 then StatusStr := StatusStr + 'G-OFF ' else if c_pIdata[4] = 1 then StatusStr := StatusStr + 'G-ON ' else if c_pIdata[4] = 2 then StatusStr := StatusStr + 'G-BLINK '; LogMessage(StatusStr); end else LogMessage('[Read Error]'); except on E: Exception do LogMessage('[Error] ' + E.Message); end; end; procedure TfrmMain.btnResetClick(Sender: TObject); begin lbStatus.Clear; end; procedure TfrmMain.btnExitClick(Sender: TObject); begin Close; end; end.