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.