unit uMain; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, uMQTTComps, StdCtrls, Grids, BaseGrid, AdvGrid, uMQTT , ExtCtrls, DateUtils, ComCtrls, Menus, ScktComp, inifiles, Buttons, MQTT, DB, System.JSON, 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.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt, FireDAC.Phys.PGDef, FireDAC.VCLUI.Wait, FireDAC.Comp.UI, FireDAC.Phys.PG, FireDAC.Comp.DataSet, FireDAC.Comp.Client, System.Generics.Collections, System.StrUtils, cxGraphics, cxControls, cxLookAndFeels, cxLookAndFeelPainters, cxStyles, cxCustomData, cxFilter, cxData, cxDataStorage, cxEdit, cxNavigator, dxDateRanges, dxScrollbarAnnotations, cxGridLevel, cxGridCustomTableView, cxGridTableView, cxClasses, cxGridCustomView, cxGrid, VclTee.TeeGDIPlus, VCLTee.TeEngine, VCLTee.TeeProcs, VCLTee.Chart, VCLTee.Series, system.Math, Vcl.WinXPickers; type TfrmMain = class(TForm) MQTTClient1: TMQTTClient; initTimer: TTimer; StatusBar1: TStatusBar; MainMenu1: TMainMenu; File1: TMenuItem; Config1: TMenuItem; N2: TMenuItem; Connect1: TMenuItem; Disconnect1: TMenuItem; insertTimer: TTimer; File2: TMenuItem; DebugMode1: TMenuItem; N8: TMenuItem; ProgramExit1: TMenuItem; NILM1: TMenuItem; Edit1: TMenuItem; N1: TMenuItem; Panel2: TPanel; Label5: TLabel; Label6: TLabel; Timer_DateTime: TTimer; TrayIcon1: TTrayIcon; tmrExit: TTimer; FDConnection1: TFDConnection; FDQuery1: TFDQuery; FDPhysPgDriverLink1: TFDPhysPgDriverLink; FDGUIxWaitCursor1: TFDGUIxWaitCursor; MQTTClient1_old: TMQTTClient; PageControl1: TPageControl; TabSheet1: TTabSheet; TabSheet2: TTabSheet; ListBox1: TListBox; Panel1: TPanel; Label2: TLabel; Label3: TLabel; Label4: TLabel; cxGrid_NILM: TcxGrid; cxGrid_NILM_Tv: TcxGridTableView; cxGrid_NILM_Lv: TcxGridLevel; cxGrid_NILM_TvColumn1: TcxGridColumn; cxGrid_NILM_TvColumn2: TcxGridColumn; cxGrid_NILM_TvColumn3: TcxGridColumn; cxGrid_NILM_TvColumn4: TcxGridColumn; cxGrid_NILM_TvColumn5: TcxGridColumn; cxGrid_NILM_TvColumn6: TcxGridColumn; cxGrid_NILM_TvColumn7: TcxGridColumn; cxGrid_NILM_TvColumn8: TcxGridColumn; cxGrid_NILM_TvColumn9: TcxGridColumn; cxGrid_NILM_TvColumn10: TcxGridColumn; cxGrid_NILM_TvColumn11: TcxGridColumn; cxGrid_NILM_TvColumn12: TcxGridColumn; cxGrid_NILM_TvColumn13: TcxGridColumn; cxGrid_NILM_TvColumn14: TcxGridColumn; cxGrid_NILM_TvColumn15: TcxGridColumn; cxGrid_NILM_TvColumn16: TcxGridColumn; cxGrid_NILM_TvColumn17: TcxGridColumn; cxGrid_NILM_TvColumn18: TcxGridColumn; cxGrid_NILM_TvColumn19: TcxGridColumn; cxGrid_NILM_TvColumn20: TcxGridColumn; cxGrid_NILM_TvColumn21: TcxGridColumn; cxGrid_NILM_TvColumn22: TcxGridColumn; cxGrid_NILM_TvColumn23: TcxGridColumn; cxGrid_NILM_TvColumn24: TcxGridColumn; cxGrid_NILM_TvColumn25: TcxGridColumn; cxGrid_NILM_TvColumn26: TcxGridColumn; cxGrid_NILM_TvColumn0: TcxGridColumn; TabSheet3: TTabSheet; Panel3: TPanel; Panel4: TPanel; ComboBox1: TComboBox; Label1: TLabel; Label7: TLabel; ComboBox2: TComboBox; Button1: TButton; Edit2: TEdit; rb_RealTimeChart: TRadioButton; rb_LogloadChart: TRadioButton; Panel5: TPanel; DatePicker2: TDatePicker; TimePicker1: TTimePicker; Label8: TLabel; FDQuery2: TFDQuery; cb_PF: TCheckBox; cb_Vrms: TCheckBox; cb_Irms: TCheckBox; cb_Var: TCheckBox; cb_Va: TCheckBox; cb_Pwr: TCheckBox; CheckBox1: TCheckBox; Panel6: TPanel; Chart_main: TChart; Panel7: TPanel; Memo1: TMemo; Splitter1: TSplitter; Button2: TButton; Button3: TButton; procedure initTimerTimer(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure Connect1Click(Sender: TObject); procedure Disconnect1Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure insertTimerTimer(Sender: TObject); procedure ListBox1DblClick(Sender: TObject); procedure ProgramExit1Click(Sender: TObject); procedure DebugMode1Click(Sender: TObject); procedure Config1Click(Sender: TObject); procedure Edit1Click(Sender: TObject); procedure N1Click(Sender: TObject); procedure MQTTClient1ClientID(Sender: TObject; var aClientID: UTF8String); procedure MQTTClient1EnableChange(Sender: TObject); procedure MQTTClient1Failure(Sender: TObject; aReason: Integer; var CloseClient: Boolean); procedure MQTTClient1Mon(Sender: TObject; aStr: string); procedure MQTTClient1Msg(Sender: TObject; aTopic: UTF8String; aMessage: AnsiString; aQos: TMQTTQOSType; aRetained: Boolean); procedure MQTTClient1Offline(Sender: TObject; Graceful: Boolean); procedure MQTTClient1Online(Sender: TObject); procedure Timer_DateTimeTimer(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); procedure AppEvtMinimize(Sender: TObject); procedure tmrExitTimer(Sender: TObject); procedure TrayIcon1DblClick(Sender: TObject); procedure Button1Click(Sender: TObject); procedure ComboBox1Change(Sender: TObject); procedure rb_LogloadChartClick(Sender: TObject); procedure TimePicker1Change(Sender: TObject); procedure DatePicker2Change(Sender: TObject); procedure cb_PFClick(Sender: TObject); procedure Chart_mainClickSeries(Sender: TCustomChart; Series: TChartSeries; ValueIndex: Integer; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private declarations } Series1: TLineSeries; // 전력량 데이터를 저장할 라인 시리즈 Series2: TLineSeries; // 전력량 데이터를 저장할 라인 시리즈 Series3: TLineSeries; // 전력량 데이터를 저장할 라인 시리즈 Series4: TLineSeries; // 전력량 데이터를 저장할 라인 시리즈 Series5: TLineSeries; // 전력량 데이터를 저장할 라인 시리즈 Series6: TLineSeries; // 전력량 데이터를 저장할 라인 시리즈 FMaxPoints: Integer; // 차트에 표시할 최대 데이터 포인트 수 debug_mode : Boolean; msg_flaw_stop : boolean; dumyTopic : string; debug_mode_count, hubPort : integer; ExitOk : boolean; procedure InsertData(topic : string; msg : AnsiString); procedure NILM_Grid_CLear; procedure InitializeChart(ck:integer); procedure LoadLogChart; public //read_gwid : integer; fIOTGW : array [1..max_gw] of TIOTGW; fNILM : array [1..max_gw] of TNILM; appPath : string; dbIP, dbPW, dbUser, dbSchema : string; dbIP_PG, dbUser_PG, dbPW_PG, dbSchema_PG : string; dbPort, dbPort_PG, brokerPort : integer; mqtt_topic, brokerIP, ClientID, mqtt_user, mqtt_pw : string; aRetain: Boolean; old_NilmID : integer; aQos: TMQTTQOSType; NILM_ID_List : TList; NILM_TOPIC_List : TList; // NILM_Map: TDictionary; // NILM_Key, NILM_Value: Integer; function DB_Connect:Boolean; function DB_Close:Boolean; function xAddSqlMark(velStr: String): String; function xAddSqlDoubleMark(velStr: String): string; procedure PrintLog(msg, msgType : String); procedure MQTTParsing(data : AnsiString; aTopic : String); procedure ClearIOTGW(); procedure SendMQTT_CtrlCommand(sTopic, sMsg : string); procedure insertSQL(sql : string); end; var frmMain: TfrmMain; no : Double; implementation {$R *.dfm} uses global, uConfigMQTT, uConfigNILM, uDevConfig; procedure TfrmMain.Button1Click(Sender: TObject); begin FMaxPoints := StrToInt(edit2.Text); // 차트에 100개의 데이터 포인트만 표시 // 차트 초기화 함수 호출 if rb_RealTimeChart.Checked then InitializeChart(0) else InitializeChart(1); end; procedure TfrmMain.Button2Click(Sender: TObject); begin memo1.Clear; end; procedure TfrmMain.Button3Click(Sender: TObject); var log_path : string; begin // save text // log_path := StringReplace(Application.ExeName,'.exe','_NILM_'+ComboBox1.Text+'_'+ComboBox2.Text+'_'+FormatDateTime('yyyymmdd_hhnnss.zzz',Now)+'.txt',[rfReplaceAll]); log_path := '.\log\NILM_CK'+'_'+FormatDateTime('yyyymmdd_hhnnss.zzz',Now)+'.txt'; Memo1.Lines.SaveToFile(log_path); end; procedure TfrmMain.cb_PFClick(Sender: TObject); begin Button1.Click; if rb_LogloadChart.Checked then LoadLogChart; end; procedure TfrmMain.Chart_mainClickSeries(Sender: TCustomChart; Series: TChartSeries; ValueIndex: Integer; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin Memo1.Lines.Add( '[ '+ 'NILM_'+RightStr('00000'+ComboBox1.Text,5)+' , '+ 'CH_'+ComboBox2.Text+' , '+ // 'No:'+ AdvColumnGrid1.Cells[0,ValueIndex]+','+ // 'currtime:'+AdvColumnGrid1.Cells[1,ValueIndex]+','+ 'PF:'+ Series1.YValueToText( Series1.YValue[ValueIndex] ) +' , '+ 'Vrms:'+ Series2.YValueToText( Series2.YValue[ValueIndex] ) +' , '+ 'Irms:'+ Series3.YValueToText( Series3.YValue[ValueIndex] ) +' , '+ 'Var:'+ Series4.YValueToText( Series4.YValue[ValueIndex] ) +' , '+ 'Va:'+ Series5.YValueToText( Series5.YValue[ValueIndex] ) +' , '+ 'Pwr:'+ Series6.YValueToText( Series6.YValue[ValueIndex] ) + ' ]'+#13 ); Memo1.SetFocus; end; procedure TfrmMain.ClearIOTGW(); var p, i : integer; begin for p:=1 to max_gw do begin fIOTGW[p]._TOPIC := ''; for i:=0 to 15 do fIOTGW[p]._DI[i] := false; for i:=0 to 7 do fIOTGW[p]._DO[i] := false; for i:=0 to 3 do fIOTGW[p]._AI[i] := 0; for i:=0 to 9 do fIOTGW[p]._TEMP[i] := 0.0; for i:=0 to 9 do fIOTGW[p]._HUMID[i] := 0.0; fIOTGW[p].run_dd := 0; fIOTGW[p].run_hh := 0; fIOTGW[p].run_mm := 0; fIOTGW[p].run_ss := 0; fNILM[p].idxNo := 0; fNILM[p]._TOPIC := ''; fNILM[p]._NAME := ''; for i:=1 to 3 do begin fNILM[p]._VALUE.PF[i] := 0.0; fNILM[p]._VALUE.Vrms[i] := 0.0; fNILM[p]._VALUE.Irms[i] := 0.0; fNILM[p]._VALUE._Var[i] := 0.0; fNILM[p]._VALUE.Va[i] := 0.0; fNILM[p]._VALUE.Pwr[i] := 0.0; fNILM[p]._VALUE.Gyro[i] := 0.0; fNILM[p]._VALUE.Accel[i] := 0.0; end; fNILM[p]._VALUE.Total_P := 0.0; fNILM[p]._VALUE.Total_Q := 0.0; fNILM[p]._VALUE.Total_S := 0.0; fNILM[p]._VALUE.Total_PF := 0.0; fNILM[p]._VALUE.wh := 0; fNILM[p]._VALUE.freq := 0.0; fNILM[p]._VALUE.rssi := 0; fNILM[p]._VALUE.seq := 0; fNILM[p]._VALUE.COMM := 0; fNILM[p]._VALUE.wire := ''; fNILM[p]._VALUE.TempC := 0; fNILM[p]._VALUE.TempExt := 0.0; fNILM[p]._VALUE.Vibration := 0.0; fNILM[p]._VALUE.TiltAngle := 0.0; fNILM[p].rcvCount := 0; fNILM[p].oldRcvCount := 0; fNILM[p].oper := 0; end; end; procedure TfrmMain.InsertData(topic : string; msg : AnsiString); begin end; //MQTT 데이터 파싱 procedure TfrmMain.MQTTClient1ClientID(Sender: TObject; var aClientID: UTF8String); begin PrintLog('ClientID :: ' + aClientID, 'SYS'); end; procedure TfrmMain.MQTTClient1EnableChange(Sender: TObject); begin if TMQTTClient(Sender).Enabled then printlog('Client is enabled.', 'SYS') else printlog('Client is disabled.', 'SYS'); end; procedure TfrmMain.MQTTClient1Failure(Sender: TObject; aReason: Integer; var CloseClient: Boolean); begin printlog('---- Failure Reported ' + FailureNames(aReason), 'SYS'); end; procedure TfrmMain.MQTTClient1Mon(Sender: TObject; aStr: string); begin printlog(aStr, 'SYS'); end; procedure TfrmMain.MQTTClient1Msg(Sender: TObject; aTopic: UTF8String; aMessage: AnsiString; aQos: TMQTTQOSType; aRetained: Boolean); begin try if (LeftStr(aMessage,1)='{') and (RightStr(aMessage,1)='}') then begin PrintLog('Topic : '+Utf8ToAnsi(aTopic)+', Msg='+aMessage, 'DEBUG'); MQTTParsing(aMessage,Utf8ToAnsi(aTopic)); end else begin PrintLog('[Not Entry]Topic : '+Utf8ToAnsi(aTopic), 'DEBUG'); end; except on e : exception do begin printlog('MQTTClient1Msg Error :: '+e.Message, 'SYS'); end; end; end; procedure TfrmMain.MQTTClient1Offline(Sender: TObject; Graceful: Boolean); begin if Graceful then begin printlog('Client Gracefully Disconnected.', 'SYS'); end else begin printlog('Client Terminated Unexpectedly.', 'SYS'); end; end; procedure TfrmMain.MQTTClient1Online(Sender: TObject); var s: TStringList; x : Integer; begin printlog('Client is online.', 'SYS'); s := TStringList.Create; //NILM_TOPIC_List : TList; if NILM_TOPIC_List.Count > 0 then begin for x := 0 to NILM_TOPIC_List.Count-1 do s.AddObject(NILM_TOPIC_List[x], TObject(aQos)); end; MQTTClient1.Subscribe(s); s.Free; end; procedure TfrmMain.MQTTParsing(data : AnsiString; aTopic : String); var tempTopic_list : TStringList; pf, Vrms, Irms, Var_, Va, Pwr: Single; nilmID, ch, k, pIdx : integer; grid_rowck : integer; devStr, numStr: string; ch_c : char; JSONObject, ChObj, TotObj, SlaveObj : TJSONObject; ChArr, SlaveArr : TJSONArray; jvCh, jvTot: TJSONValue; hasRealVoltage, hasRealPower, hasCurrent : Boolean; sumP, sumQ, sumS, maxI, nomV, assPF : Double; function GetValNum(const AObj: TJSONObject; const AKey: string; ADefault: Double = 0.0): Double; var LVal: TJSONValue; begin Result := ADefault; if AObj = nil then Exit; LVal := AObj.GetValue(AKey); if Assigned(LVal) and (not (LVal is TJSONNull)) then Result := StrToFloatDef(LVal.Value, ADefault); end; function GetValInt(const AObj: TJSONObject; const AKey: string; ADefault: Integer = 0): Integer; var LVal: TJSONValue; begin Result := ADefault; if AObj = nil then Exit; LVal := AObj.GetValue(AKey); if Assigned(LVal) and (not (LVal is TJSONNull)) then Result := StrToIntDef(LVal.Value, ADefault); end; function GetValInt64(const AObj: TJSONObject; const AKey: string; ADefault: Int64 = 0): Int64; var LVal: TJSONValue; begin Result := ADefault; if AObj = nil then Exit; LVal := AObj.GetValue(AKey); if Assigned(LVal) and (not (LVal is TJSONNull)) then Result := StrToInt64Def(LVal.Value, ADefault); end; function GetValStr(const AObj: TJSONObject; const AKey, ADefault: string): string; var LVal: TJSONValue; begin Result := ADefault; if AObj = nil then Exit; LVal := AObj.GetValue(AKey); if Assigned(LVal) and (not (LVal is TJSONNull)) then Result := LVal.Value; end; procedure UpdateRealtimeChart(ANilmID, ACh: Integer; APF, AVrms, AIrms, AVar, AVa, APwr: Double); begin if not rb_RealTimeChart.Checked then Exit; if CheckBox1.Checked then Exit; if Trim(ComboBox1.Text) = '' then Exit; if (ANilmID = StrToIntDef(ComboBox1.Text, -1)) and (ACh = ComboBox2.ItemIndex + 1) then begin if cb_PF.Checked and cb_Vrms.Checked and cb_Irms.Checked and cb_Var.Checked and cb_Va.Checked and cb_Pwr.Checked then begin Series1.AddXY(no, APF * 1000000, '', clRed); Series2.AddXY(no, AVrms * 1000, '', $000080FF); Series3.AddXY(no, AIrms * 100000, '', clYellow); Series4.AddXY(no, AVar * 1000, '', clGreen); Series5.AddXY(no, AVa * 1000, '', clBlue); Series6.AddXY(no, APwr * 100, '', clPurple); end else begin Series1.AddXY(no, APF, '', clRed); Series2.AddXY(no, AVrms, '', $000080FF); Series3.AddXY(no, AIrms, '', clYellow); Series4.AddXY(no, AVar, '', clGreen); Series5.AddXY(no, AVa, '', clBlue); Series6.AddXY(no, APwr, '', clPurple); end; if Series1.Count > FMaxPoints then Series1.Delete(0); if Series2.Count > FMaxPoints then Series2.Delete(0); if Series3.Count > FMaxPoints then Series3.Delete(0); if Series4.Count > FMaxPoints then Series4.Delete(0); if Series5.Count > FMaxPoints then Series5.Delete(0); if Series6.Count > FMaxPoints then Series6.Delete(0); Series1.Visible := cb_PF.Checked; Series2.Visible := cb_Vrms.Checked; Series3.Visible := cb_Irms.Checked; Series4.Visible := cb_Var.Checked; Series5.Visible := cb_Va.Checked; Series6.Visible := cb_Pwr.Checked; if Series1.Count > 0 then Chart_Main.Axes.Bottom.SetMinMax( Series1.XValues.Value[Max(0, Series1.Count - FMaxPoints)], Series1.XValues.Value[Series1.Count - 1] ); no := no + 1; end; end; begin try if Trim(data) = '' then Exit; InsertData(aTopic, data); JSONObject := TJSONObject.ParseJSONValue(data) as TJSONObject; if JSONObject = nil then Exit; tempTopic_list := TStringList.Create; try nilmID := 0; // 1순위: JSON의 dev 필드 확인 (예: "NILM_001", "NILM_032") devStr := GetValStr(JSONObject, 'dev', ''); if devStr <> '' then begin numStr := ''; for ch_c in devStr do if ch_c in ['0'..'9'] then numStr := numStr + ch_c; nilmID := StrToIntDef(numStr, 0); end; // 2순위: 토픽 경로에서 추출 if nilmID <= 0 then begin Strsplit('/', aTopic, tempTopic_list); if tempTopic_list.Count > 0 then begin if (tempTopic_list.Strings[tempTopic_list.Count - 1] = 'status') or (tempTopic_list.Strings[tempTopic_list.Count - 1] = 'event') then begin if tempTopic_list.Count > 1 then nilmID := StrToIntDef(tempTopic_list.Strings[tempTopic_list.Count - 2], 0); end else begin nilmID := StrToIntDef(tempTopic_list.Strings[tempTopic_list.Count - 1], 0); end; end; end; // 특수 토픽 (status, gateway, event) 처리 if aTopic.EndsWith('/status') then begin printlog(Format('[노드상태] 장비 %d: online=%s, loss_rate=%s', [nilmID, GetValStr(JSONObject, 'online', ''), GetValStr(JSONObject, 'loss_rate', '0')]), 'SYS'); Exit; end else if aTopic.EndsWith('/gateway') then begin printlog(Format('[게이트웨이] %s: online=%s, nodes=%d/%d', [GetValStr(JSONObject, 'gw', ''), GetValStr(JSONObject, 'online', ''), GetValInt(JSONObject, 'nodes_online', 0), GetValInt(JSONObject, 'nodes_total', 0)]), 'SYS'); Exit; end else if aTopic.EndsWith('/event') then begin printlog(Format('[ALARM] 장비 %d: %s (코드:%d, 값:%.2f)', [nilmID, GetValStr(JSONObject, 'name', ''), GetValInt(JSONObject, 'code', 0), GetValNum(JSONObject, 'value', 0.0)]), 'SYS'); Exit; end; if (nilmID <= 0) or (nilmID > max_gw) or (fNILM[nilmID].idxNo <= 0) then Exit; grid_rowck := NILM_ID_List.IndexOf(nilmID); // ========================================================================= // CASE 1: 최신 표준 단일 규격 (ch 배열 및 total 객체) // ========================================================================= jvCh := JSONObject.GetValue('ch'); if (jvCh <> nil) and (jvCh is TJSONArray) then begin ChArr := jvCh as TJSONArray; fNILM[nilmID]._VALUE.seq := GetValInt(JSONObject, 'seq', 0); fNILM[nilmID]._VALUE.COMM := GetValInt(JSONObject, 'COMM', 0); fNILM[nilmID]._VALUE.rssi := GetValInt(JSONObject, 'rssi', 0); fNILM[nilmID]._VALUE.wire := GetValStr(JSONObject, 'wire', '3P4W'); fNILM[nilmID]._VALUE.freq := GetValNum(JSONObject, 'freq', 60.0); fNILM[nilmID]._VALUE.wh := GetValInt64(JSONObject, 'wh', 0); for k := 0 to Min(2, ChArr.Count - 1) do begin if (ChArr.Items[k] <> nil) and (ChArr.Items[k] is TJSONObject) then begin ChObj := ChArr.Items[k] as TJSONObject; pIdx := k + 1; // 1..3 fNILM[nilmID]._VALUE.Vrms[pIdx] := GetValNum(ChObj, 'v', 0.0); fNILM[nilmID]._VALUE.Irms[pIdx] := GetValNum(ChObj, 'i', 0.0); fNILM[nilmID]._VALUE.Pwr[pIdx] := GetValNum(ChObj, 'p', 0.0); fNILM[nilmID]._VALUE._Var[pIdx] := GetValNum(ChObj, 'q', 0.0); fNILM[nilmID]._VALUE.Va[pIdx] := GetValNum(ChObj, 's', 0.0); fNILM[nilmID]._VALUE.PF[pIdx] := GetValNum(ChObj, 'pf', 0.0); end; end; jvTot := JSONObject.GetValue('total'); if (jvTot <> nil) and (jvTot is TJSONObject) then begin TotObj := jvTot as TJSONObject; fNILM[nilmID]._VALUE.Total_P := GetValNum(TotObj, 'p', 0.0); fNILM[nilmID]._VALUE.Total_Q := GetValNum(TotObj, 'q', 0.0); fNILM[nilmID]._VALUE.Total_S := GetValNum(TotObj, 's', 0.0); fNILM[nilmID]._VALUE.Total_PF := GetValNum(TotObj, 'pf', 0.0); end; // 환경/센서 데이터 fNILM[nilmID]._VALUE.TempC := GetValNum(JSONObject, 'temperature', GetValNum(JSONObject, 'TempC', fNILM[nilmID]._VALUE.TempC)); fNILM[nilmID]._VALUE.TempExt := GetValNum(JSONObject, 'tempExt', 0.0); fNILM[nilmID]._VALUE.Accel[1] := GetValNum(JSONObject, 'accX', GetValNum(JSONObject, 'aX', fNILM[nilmID]._VALUE.Accel[1])); fNILM[nilmID]._VALUE.Accel[2] := GetValNum(JSONObject, 'accY', GetValNum(JSONObject, 'aY', fNILM[nilmID]._VALUE.Accel[2])); fNILM[nilmID]._VALUE.Accel[3] := GetValNum(JSONObject, 'accZ', GetValNum(JSONObject, 'aZ', fNILM[nilmID]._VALUE.Accel[3])); fNILM[nilmID]._VALUE.Vibration := GetValNum(JSONObject, 'vibration', 0.0); fNILM[nilmID]._VALUE.Gyro[1] := GetValNum(JSONObject, 'gyroX', GetValNum(JSONObject, 'gX', fNILM[nilmID]._VALUE.Gyro[1])); fNILM[nilmID]._VALUE.Gyro[2] := GetValNum(JSONObject, 'gyroY', GetValNum(JSONObject, 'gY', fNILM[nilmID]._VALUE.Gyro[2])); fNILM[nilmID]._VALUE.Gyro[3] := GetValNum(JSONObject, 'gyroZ', GetValNum(JSONObject, 'gZ', fNILM[nilmID]._VALUE.Gyro[3])); fNILM[nilmID]._VALUE.TiltAngle := GetValNum(JSONObject, 'tiltAngle', 0.0); end // ========================================================================= // CASE 2: 하위 호환 slave_data 구조 // ========================================================================= else if JSONObject.GetValue('slave_data') <> nil then begin SlaveArr := JSONObject.GetValue('slave_data'); if (SlaveArr <> nil) and (SlaveArr.Count > 0) and (SlaveArr.Items[0] is TJSONObject) then begin SlaveObj := SlaveArr.Items[0] as TJSONObject; fNILM[nilmID]._VALUE.seq := GetValInt(SlaveObj, 'seq', 0); fNILM[nilmID]._VALUE.COMM := GetValInt(SlaveObj, 'COMM', 0); fNILM[nilmID]._VALUE.rssi := GetValInt(SlaveObj, 'rssi', 0); fNILM[nilmID]._VALUE.wire := GetValStr(SlaveObj, 'wire', '3P4W'); fNILM[nilmID]._VALUE.freq := GetValNum(SlaveObj, 'frequency', 60.0); fNILM[nilmID]._VALUE.wh := GetValInt64(SlaveObj, 'wh', 0); // A상 (CH 1) fNILM[nilmID]._VALUE.PF[1] := GetValNum(SlaveObj, 'power_factor_a', GetValNum(SlaveObj, 'pfA', 0.0)); fNILM[nilmID]._VALUE.Vrms[1] := GetValNum(SlaveObj, 'vrms_a', GetValNum(SlaveObj, 'voltageA', 0.0)); fNILM[nilmID]._VALUE.Irms[1] := GetValNum(SlaveObj, 'irms_a', GetValNum(SlaveObj, 'currentA', 0.0)); fNILM[nilmID]._VALUE.Pwr[1] := GetValNum(SlaveObj, 'active_power_a', GetValNum(SlaveObj, 'wA', 0.0)); fNILM[nilmID]._VALUE._Var[1] := GetValNum(SlaveObj, 'reactive_power_a', GetValNum(SlaveObj, 'Var_A', 0.0)); fNILM[nilmID]._VALUE.Va[1] := GetValNum(SlaveObj, 'apparent_power_a', GetValNum(SlaveObj, 'Va_A', 0.0)); // B상 (CH 2) if fNILM[nilmID]._VALUE.wire <> '3P3W' then begin fNILM[nilmID]._VALUE.PF[2] := GetValNum(SlaveObj, 'power_factor_b', GetValNum(SlaveObj, 'pfB', 0.0)); fNILM[nilmID]._VALUE.Vrms[2] := GetValNum(SlaveObj, 'vrms_b', GetValNum(SlaveObj, 'voltageB', 0.0)); fNILM[nilmID]._VALUE.Irms[2] := GetValNum(SlaveObj, 'irms_b', GetValNum(SlaveObj, 'currentB', 0.0)); fNILM[nilmID]._VALUE.Pwr[2] := GetValNum(SlaveObj, 'active_power_b', GetValNum(SlaveObj, 'wB', 0.0)); fNILM[nilmID]._VALUE._Var[2] := GetValNum(SlaveObj, 'reactive_power_b', GetValNum(SlaveObj, 'Var_B', 0.0)); fNILM[nilmID]._VALUE.Va[2] := GetValNum(SlaveObj, 'apparent_power_b', GetValNum(SlaveObj, 'Va_B', 0.0)); end; // C상 (CH 3) fNILM[nilmID]._VALUE.PF[3] := GetValNum(SlaveObj, 'power_factor_c', GetValNum(SlaveObj, 'pfC', 0.0)); fNILM[nilmID]._VALUE.Vrms[3] := GetValNum(SlaveObj, 'vrms_c', GetValNum(SlaveObj, 'voltageC', 0.0)); fNILM[nilmID]._VALUE.Irms[3] := GetValNum(SlaveObj, 'irms_c', GetValNum(SlaveObj, 'currentC', 0.0)); fNILM[nilmID]._VALUE.Pwr[3] := GetValNum(SlaveObj, 'active_power_c', GetValNum(SlaveObj, 'wC', 0.0)); fNILM[nilmID]._VALUE._Var[3] := GetValNum(SlaveObj, 'reactive_power_c', GetValNum(SlaveObj, 'Var_C', 0.0)); fNILM[nilmID]._VALUE.Va[3] := GetValNum(SlaveObj, 'apparent_power_c', GetValNum(SlaveObj, 'Va_C', 0.0)); // Total fNILM[nilmID]._VALUE.Total_P := GetValNum(SlaveObj, 'total_p', 0.0); fNILM[nilmID]._VALUE.Total_Q := GetValNum(SlaveObj, 'total_q', 0.0); fNILM[nilmID]._VALUE.Total_S := GetValNum(SlaveObj, 'total_s', 0.0); fNILM[nilmID]._VALUE.Total_PF := GetValNum(SlaveObj, 'total_pf', 0.0); // 환경/센서 fNILM[nilmID]._VALUE.TempC := GetValNum(SlaveObj, 'temperature', GetValNum(SlaveObj, 'TempC', fNILM[nilmID]._VALUE.TempC)); fNILM[nilmID]._VALUE.Accel[1] := GetValNum(SlaveObj, 'accX', GetValNum(SlaveObj, 'aX', fNILM[nilmID]._VALUE.Accel[1])); fNILM[nilmID]._VALUE.Accel[2] := GetValNum(SlaveObj, 'accY', GetValNum(SlaveObj, 'aY', fNILM[nilmID]._VALUE.Accel[2])); fNILM[nilmID]._VALUE.Accel[3] := GetValNum(SlaveObj, 'accZ', GetValNum(SlaveObj, 'aZ', fNILM[nilmID]._VALUE.Accel[3])); fNILM[nilmID]._VALUE.Gyro[1] := GetValNum(SlaveObj, 'gyroX', GetValNum(SlaveObj, 'gX', fNILM[nilmID]._VALUE.Gyro[1])); fNILM[nilmID]._VALUE.Gyro[2] := GetValNum(SlaveObj, 'gyroY', GetValNum(SlaveObj, 'gY', fNILM[nilmID]._VALUE.Gyro[2])); fNILM[nilmID]._VALUE.Gyro[3] := GetValNum(SlaveObj, 'gyroZ', GetValNum(SlaveObj, 'gZ', fNILM[nilmID]._VALUE.Gyro[3])); end; end // ========================================================================= // CASE 3: 구형 CH 분할 포맷 ("CH": "01", "02", "03") // ========================================================================= else if JSONObject.GetValue('CH') <> nil then begin ch := StrToIntDef(JSONObject.GetValue('CH').Value, 0); case ch of 1, 2, 3: begin pf := GetValNum(JSONObject, 'PF', 0.0); Vrms := GetValNum(JSONObject, 'Vrms', 0.0); Irms := GetValNum(JSONObject, 'Irms', 0.0); Var_ := GetValNum(JSONObject, 'Var_', 0.0); Va := GetValNum(JSONObject, 'Va', 0.0); Pwr := GetValNum(JSONObject, 'Pwr', 0.0); fNILM[nilmID]._VALUE.PF[ch] := pf; fNILM[nilmID]._VALUE.Vrms[ch] := Vrms; fNILM[nilmID]._VALUE.Irms[ch] := Irms; fNILM[nilmID]._VALUE._Var[ch] := Var_; fNILM[nilmID]._VALUE.Va[ch] := Va; fNILM[nilmID]._VALUE.Pwr[ch] := Pwr; end; 4: // MPU 센서 또는 03 채널 begin fNILM[nilmID]._VALUE.Gyro[1] := GetValNum(JSONObject, 'gX', fNILM[nilmID]._VALUE.Gyro[1]); fNILM[nilmID]._VALUE.Gyro[2] := GetValNum(JSONObject, 'gY', fNILM[nilmID]._VALUE.Gyro[2]); fNILM[nilmID]._VALUE.Gyro[3] := GetValNum(JSONObject, 'gZ', fNILM[nilmID]._VALUE.Gyro[3]); fNILM[nilmID]._VALUE.Accel[1] := GetValNum(JSONObject, 'aX', fNILM[nilmID]._VALUE.Accel[1]); fNILM[nilmID]._VALUE.Accel[2] := GetValNum(JSONObject, 'aY', fNILM[nilmID]._VALUE.Accel[2]); fNILM[nilmID]._VALUE.Accel[3] := GetValNum(JSONObject, 'aZ', fNILM[nilmID]._VALUE.Accel[3]); fNILM[nilmID]._VALUE.TempC := GetValNum(JSONObject, 'TempC', fNILM[nilmID]._VALUE.TempC); end; end; end; // ========================================================================= // 가상 전력 추정 계산 (1-CT 전류 전용 환경 대응) // ========================================================================= hasRealVoltage := (fNILM[nilmID]._VALUE.Vrms[1] > 0) or (fNILM[nilmID]._VALUE.Vrms[2] > 0) or (fNILM[nilmID]._VALUE.Vrms[3] > 0); hasRealPower := (fNILM[nilmID]._VALUE.Pwr[1] > 0) or (fNILM[nilmID]._VALUE.Pwr[2] > 0) or (fNILM[nilmID]._VALUE.Pwr[3] > 0) or (fNILM[nilmID]._VALUE.Total_P > 0); hasCurrent := (fNILM[nilmID]._VALUE.Irms[1] > 0) or (fNILM[nilmID]._VALUE.Irms[2] > 0) or (fNILM[nilmID]._VALUE.Irms[3] > 0); if hasCurrent and (not hasRealVoltage) and (not hasRealPower) then begin nomV := 220.0; assPF := 0.90; sumP := 0.0; sumQ := 0.0; sumS := 0.0; maxI := 0.0; for k := 1 to 3 do begin if fNILM[nilmID]._VALUE.Irms[k] > 0 then begin fNILM[nilmID]._VALUE.Vrms[k] := nomV; fNILM[nilmID]._VALUE.PF[k] := assPF; fNILM[nilmID]._VALUE.Va[k] := nomV * fNILM[nilmID]._VALUE.Irms[k]; fNILM[nilmID]._VALUE.Pwr[k] := fNILM[nilmID]._VALUE.Va[k] * assPF; fNILM[nilmID]._VALUE._Var[k] := Sqrt(Max(Sqr(fNILM[nilmID]._VALUE.Va[k]) - Sqr(fNILM[nilmID]._VALUE.Pwr[k]), 0.0)); sumP := sumP + fNILM[nilmID]._VALUE.Pwr[k]; sumQ := sumQ + fNILM[nilmID]._VALUE._Var[k]; sumS := sumS + fNILM[nilmID]._VALUE.Va[k]; if fNILM[nilmID]._VALUE.Irms[k] > maxI then maxI := fNILM[nilmID]._VALUE.Irms[k]; end; end; fNILM[nilmID]._VALUE.Total_P := sumP; fNILM[nilmID]._VALUE.Total_Q := sumQ; fNILM[nilmID]._VALUE.Total_S := sumS; fNILM[nilmID]._VALUE.Total_PF := assPF; end; // ========================================================================= // 3P3W / 3P4W Total 전력 자동 합산 보정 // ========================================================================= if (fNILM[nilmID]._VALUE.Total_P = 0) and (fNILM[nilmID]._VALUE.Pwr[1] > 0) then begin if SameText(fNILM[nilmID]._VALUE.wire, '3P3W') then begin fNILM[nilmID]._VALUE.Total_P := fNILM[nilmID]._VALUE.Pwr[1] + fNILM[nilmID]._VALUE.Pwr[3]; fNILM[nilmID]._VALUE.Total_Q := fNILM[nilmID]._VALUE._Var[1] + fNILM[nilmID]._VALUE._Var[3]; end else begin fNILM[nilmID]._VALUE.Total_P := fNILM[nilmID]._VALUE.Pwr[1] + fNILM[nilmID]._VALUE.Pwr[2] + fNILM[nilmID]._VALUE.Pwr[3]; fNILM[nilmID]._VALUE.Total_Q := fNILM[nilmID]._VALUE._Var[1] + fNILM[nilmID]._VALUE._Var[2] + fNILM[nilmID]._VALUE._Var[3]; end; fNILM[nilmID]._VALUE.Total_S := Sqrt(Sqr(fNILM[nilmID]._VALUE.Total_P) + Sqr(fNILM[nilmID]._VALUE.Total_Q)); if fNILM[nilmID]._VALUE.Total_S > 0 then fNILM[nilmID]._VALUE.Total_PF := fNILM[nilmID]._VALUE.Total_P / fNILM[nilmID]._VALUE.Total_S; end; // 수신 카운트 증가 fNILM[nilmID].rcvCount := fNILM[nilmID].rcvCount + 1; if fNILM[nilmID].rcvCount > 65535 then fNILM[nilmID].rcvCount := 1; // 라벨 캡션 갱신 Label2.Caption := Format('[ID : %d / CH : 01] PF : %.3f, Vrms : %.3f, Irms : %.3f, Var : %.3f, Va : %.3f, Pwr : %.3f', [nilmID, fNILM[nilmID]._VALUE.PF[1], fNILM[nilmID]._VALUE.Vrms[1], fNILM[nilmID]._VALUE.Irms[1], fNILM[nilmID]._VALUE._Var[1], fNILM[nilmID]._VALUE.Va[1], fNILM[nilmID]._VALUE.Pwr[1]]); Label3.Caption := Format('[ID : %d / CH : 02] PF : %.3f, Vrms : %.3f, Irms : %.3f, Var : %.3f, Va : %.3f, Pwr : %.3f', [nilmID, fNILM[nilmID]._VALUE.PF[2], fNILM[nilmID]._VALUE.Vrms[2], fNILM[nilmID]._VALUE.Irms[2], fNILM[nilmID]._VALUE._Var[2], fNILM[nilmID]._VALUE.Va[2], fNILM[nilmID]._VALUE.Pwr[2]]); if debug_mode then begin Label4.Caption := Format('[ID : %d / MPU] gX : %.3f, gY : %.3f, gZ : %.3f, aX : %.3f, aY : %.3f, aZ : %.3f, Temp : %.2f', [nilmID, fNILM[nilmID]._VALUE.Gyro[1], fNILM[nilmID]._VALUE.Gyro[2], fNILM[nilmID]._VALUE.Gyro[3], fNILM[nilmID]._VALUE.Accel[1], fNILM[nilmID]._VALUE.Accel[2], fNILM[nilmID]._VALUE.Accel[3], fNILM[nilmID]._VALUE.TempC]); end; // cxGrid 실시간 반영 if (PageControl1.TabIndex = 0) and (grid_rowck >= 0) then with cxGrid_NILM_Tv.DataController do begin try BeginUpdate; // CH 1 Values[grid_rowck, cxGrid_NILM_TvColumn1.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.PF[1]]); Values[grid_rowck, cxGrid_NILM_TvColumn2.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Vrms[1]]); Values[grid_rowck, cxGrid_NILM_TvColumn3.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Irms[1]]); Values[grid_rowck, cxGrid_NILM_TvColumn4.Index] := Format('%.3f', [fNILM[nilmID]._VALUE._Var[1]]); Values[grid_rowck, cxGrid_NILM_TvColumn5.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Va[1]]); Values[grid_rowck, cxGrid_NILM_TvColumn6.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Pwr[1]]); // CH 2 Values[grid_rowck, cxGrid_NILM_TvColumn7.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.PF[2]]); Values[grid_rowck, cxGrid_NILM_TvColumn8.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Vrms[2]]); Values[grid_rowck, cxGrid_NILM_TvColumn9.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Irms[2]]); Values[grid_rowck, cxGrid_NILM_TvColumn10.Index] := Format('%.3f', [fNILM[nilmID]._VALUE._Var[2]]); Values[grid_rowck, cxGrid_NILM_TvColumn11.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Va[2]]); Values[grid_rowck, cxGrid_NILM_TvColumn12.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Pwr[2]]); // CH 3 Values[grid_rowck, cxGrid_NILM_TvColumn13.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.PF[3]]); Values[grid_rowck, cxGrid_NILM_TvColumn14.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Vrms[3]]); Values[grid_rowck, cxGrid_NILM_TvColumn15.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Irms[3]]); Values[grid_rowck, cxGrid_NILM_TvColumn16.Index] := Format('%.3f', [fNILM[nilmID]._VALUE._Var[3]]); Values[grid_rowck, cxGrid_NILM_TvColumn17.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Va[3]]); Values[grid_rowck, cxGrid_NILM_TvColumn18.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Pwr[3]]); // MPU 및 센서 Values[grid_rowck, cxGrid_NILM_TvColumn19.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Gyro[1]]); Values[grid_rowck, cxGrid_NILM_TvColumn20.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Gyro[2]]); Values[grid_rowck, cxGrid_NILM_TvColumn21.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Gyro[3]]); Values[grid_rowck, cxGrid_NILM_TvColumn22.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Accel[1]]); Values[grid_rowck, cxGrid_NILM_TvColumn23.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Accel[2]]); Values[grid_rowck, cxGrid_NILM_TvColumn24.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.Accel[3]]); Values[grid_rowck, cxGrid_NILM_TvColumn25.Index] := Format('%.3f', [fNILM[nilmID]._VALUE.TempC]); // 시각 Values[grid_rowck, cxGrid_NILM_TvColumn26.Index] := FormatDateTime('yymmdd hhnnss', Now); finally EndUpdate; end; end; // 실시간 차트 업데이트 (선택된 채널 1..3에 대해 갱신) for k := 1 to 3 do begin UpdateRealtimeChart(nilmID, k, fNILM[nilmID]._VALUE.PF[k], fNILM[nilmID]._VALUE.Vrms[k], fNILM[nilmID]._VALUE.Irms[k], fNILM[nilmID]._VALUE._Var[k], fNILM[nilmID]._VALUE.Va[k], fNILM[nilmID]._VALUE.Pwr[k]); end; finally tempTopic_list.Free; JSONObject.Free; end; except on e: Exception do begin printlog('MQTTParsing Error :: ' + e.Message, 'SYS'); end; end; end; procedure TfrmMain.PrintLog(msg, msgType : string); var f : TextFile; log: TStringList; i : integer; logfile, buf : string; begin if ListBox1.Items.Count >= 300 then ListBox1.Items.Clear; if msgType = 'DEBUG' then begin if debug_mode then begin if not msg_flaw_stop then ListBox1.Items.Insert(0, FormatDateTime('hh:nn:ss :: ',now)+msg); end; end else if msgType = 'SYS' then begin ListBox1.Items.Insert(0, FormatDateTime('hh:nn:ss :: ',now)+msg); logfile := ''; logfile := appPath+'log\'+formatdatetime('yyyymmddhh',now())+'.log'; if FileExists(logfile) then begin log := TStringList.Create; AssignFile(f, logfile); reset(f); while not eof(f) do begin ReadLn(f, buf); log.Add(buf); end; log.add(FormatDateTime('hh:nn:ss :: ',now)+msg); CloseFile(F); AssignFile(f, logfile); rewrite(f); for i := 0 to log.Count - 1 do begin WriteLn(f, log.strings[i]); end; CloseFile(F); log.Free; end else begin log := TStringList.Create; log.add(FormatDateTime('hh:nn:ss :: ',now)+msg); AssignFile(f, logfile); rewrite(f); for i := 0 to log.Count - 1 do begin WriteLn(f, log.strings[i]); end; CloseFile(F); log.Free; end; end; end; procedure TfrmMain.initTimerTimer(Sender: TObject); var ini : TInifile; pwd : string; begin old_NilmID := 0; debug_mode := false; debug_mode_count := 0; msg_flaw_stop := false; ListBox1.Clear; mqtt_topic := ''; mqtt_topic := 'QST/NILM/LVDT/#'; pwd := ''; pwd := appPath+'NILM_Manager.ini'; if FileExists(pwd) then begin ini := TInifile.Create(pwd); with ini do begin mqtt_topic := ReadString('MQTT','TOPIC',mqtt_topic); brokerIP := ReadString('MQTT','BrokerIP','183.111.125.203'); brokerPort := ReadInteger('MQTT','BrokerPort',1883); ClientID := ReadString('MQTT','ClientID','NILM_1st'); mqtt_user := ReadString('MQTT','User','qsentech'); mqtt_pw := ReadString('MQTT','Password','qsentech!1233'); dbIP := ReadString('DATABASE','IP','192.168.200.199'); dbUser := ReadString('DATABASE','User','root'); dbPW := ReadString('DATABASE','PW','1233'); dbSchema := ReadString('DATABASE','Schema','nilm'); dbPort := ReadInteger('DATABASE','Port',3306); dbIP_PG := ReadString('DATABASE_PG','IP','192.168.200.199'); dbUser_PG := ReadString('DATABASE_PG','User','root'); dbPW_PG := ReadString('DATABASE_PG','PW','54323'); dbSchema_PG := ReadString('DATABASE_PG','Schema','neol'); dbPort_PG := ReadInteger('DATABASE_PG','Port',3306); hubPort := ReadInteger('MQTT_HUB','Port',6969); end; ini.Free; end; Statusbar1.Panels.Items[2].Text := format('%d',[MQTTClient1.Port]); Statusbar1.Panels.Items[3].Text := mqtt_topic; MQTTClient1.Activate(false); Sleep(300); randomize(); MQTTClient1.ClientID := ClientID+'_'+inttostr(random(999999)); MQTTClient1.Host := brokerIP; MQTTClient1.Port := brokerPort; MQTTClient1.Username := mqtt_user; MQTTClient1.Password := mqtt_pw; MQTTClient1.Activate(true); // PG 서버접속 if not DB_Connect then begin printlog('Database Connect Error :: PG', 'DB'); end; frmNILM.ShowNILM(); if NILM_ID_List.Count > 0 then begin NILM_Grid_CLear; end; frmDevConfig.ShowDev(); insertTimer.Enabled := True; initTimer.Enabled := false; AppEvtMinimize(self); end; procedure TfrmMain.NILM_Grid_CLear; var row_ck, i : Integer; begin with cxGrid_NILM_Tv.DataController do begin try BeginUpdate; RecordCount := 0; finally EndUpdate; end; try BeginUpdate; ComboBox1.Clear; for I := 0 to NILM_ID_List.Count-1 do begin row_ck := AppendRecord; Values[row_ck,cxGrid_NILM_TvColumn0.Index] := NILM_ID_List[i]; ComboBox1.AddItem(NILM_ID_List[i].ToString,nil); end; finally EndUpdate; if ComboBox1.Items.Count > 0 then ComboBox1.ItemIndex := 0; end; end; end; procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction); begin if insertTimer.Enabled then insertTimer.Enabled := False; if Timer_DateTime.Enabled then Timer_DateTime.Enabled := False; MQTTClient1.Activate(false); NILM_ID_List.Free; NILM_TOPIC_List.Free; // NILM_Map.Free; end; procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin if ExitOk then begin CanClose := true; end else begin CanClose := false; case MessageBox(handle, '"예(Y)"를 클릭하면, MQTT Client가 트레이로 최소화 됩니다.'+#$0D#$0A#$0D#$0A+'"아니오(N)"를 클릭하면 프로그램이 종료됩니다. ', 'MQTT Client :: 트레이로 최소화 할까요?', MB_YESNOCANCEL) of ID_YES : begin TrayIcon1.BalloonTitle := 'MQTT Client 화면을 다시 보려면...'; TrayIcon1.BalloonHint := '트레이 아이콘을 더블클릭하세요.'; //TrayIcon1.BalloonFlags := bfInfo; AppEvtMinimize(self); end; ID_NO : begin //종료 타이머 시작하고 끝나면 ExitOk~ tmrExit.Tag := 0; tmrExit.Enabled := true; end; end;//case.. end;//if~ end; procedure TfrmMain.AppEvtMinimize(Sender: TObject); begin Hide; WindowState := wsMinimized; TrayIcon1.Visible := True; TrayIcon1.Animate := True; TrayIcon1.ShowBalloonHint; end; procedure TfrmMain.Connect1Click(Sender: TObject); begin MQTTClient1.Activate(false); sleep(300); MQTTClient1.Activate(true); end; procedure TfrmMain.Disconnect1Click(Sender: TObject); begin MQTTClient1.Activate(false); end; procedure TfrmMain.FormCreate(Sender: TObject); begin NILM_ID_List := TList.Create; NILM_TOPIC_List := TList.Create; // NILM_Map := TDictionary.Create; Label2.Caption := ''; Label3.Caption := ''; Label4.Caption := ''; PageControl1.TabIndex := 1; appPath := ''; appPath := ExtractFilePath(Application.ExeName); if not DirectoryExists(appPath + 'log') then if not CreateDir(appPath + 'log') then raise Exception.Create('Cannot create ' + appPath + 'log'); ClearIOTGW(); Label6.Caption := format('시작 시간 : %s', [FormatDateTime('YYYY-MM-DD HH:mm:ss', now)]); Button1.Click; DatePicker2.Date := Now; TimePicker1.Time := Now; end; procedure TfrmMain.SendMQTT_CtrlCommand(sTopic, sMsg : string); var pTopic : UTF8String; begin pTopic := ''; pTopic := sTopic; MQTTClient1.Publish(pTopic, sMsg, aQos, false); end; procedure TfrmMain.TimePicker1Change(Sender: TObject); begin LoadLogChart; end; procedure TfrmMain.LoadLogChart; var ck_date : TDateTime; pf, Vrms, Irms, Var_, Va, Pwr : Single; begin if FDConnection1.Connected then begin ck_date := StrToDateTime(FormatDateTime('yyyy-mm-dd',DatePicker2.Date) +' '+FormatDateTime('hh',TimePicker1.Time)+':00:00'); FDQuery2.Close; FDQuery2.SQL.Clear; FDQuery2.SQL.Text := ' SELECT * FROM '+format('nilm_%.5d',[fNILM[StrToInt(combobox1.Text)].idxNo])+' '+ ' WHERE "lastUpdate" >= ''' +FormatDateTime('yyyy-mm-dd hh:nn:ss',ck_date) +''' '+ ' AND "lastUpdate" < ''' +FormatDateTime('yyyy-mm-dd hh:nn:ss',IncHour(ck_date,1)) +''' '+ ' order by "lastUpdate"; '; // ShowMessage(FDQuery2.SQL.Text); FDQuery2.Open; FMaxPoints := FDQuery2.RecordCount; // 차트 초기화 함수 호출 InitializeChart(1); no := 0; if FDQuery2.RecordCount> 0 then begin FDQuery2.First; While not FDQuery2.Eof do begin if ComboBox2.ItemIndex = 0 then begin pf := FDQuery2.FieldByName('pf_1').AsSingle; Vrms := FDQuery2.FieldByName('Vrms_1').AsSingle; Irms := FDQuery2.FieldByName('Irms_1').AsSingle; Var_ := FDQuery2.FieldByName('Var_1').AsSingle; Va := FDQuery2.FieldByName('Va_1').AsSingle; Pwr := FDQuery2.FieldByName('Pwr_1').AsSingle; end else if ComboBox2.ItemIndex = 1 then begin pf := FDQuery2.FieldByName('pf_2').AsSingle; Vrms := FDQuery2.FieldByName('Vrms_2').AsSingle; Irms := FDQuery2.FieldByName('Irms_2').AsSingle; Var_ := FDQuery2.FieldByName('Var_2').AsSingle; Va := FDQuery2.FieldByName('Va_2').AsSingle; Pwr := FDQuery2.FieldByName('Pwr_2').AsSingle; end; { pf if CheckBox1.Checked then Series1.Add(ck*1000000, AdvColumnGrid1.Cells[0,i], $000080FF); Volt if CheckBox2.Checked then Series2.Add(ck1*1000, AdvColumnGrid1.Cells[0,i], clRed); CUR if CheckBox3.Checked then Series3.Add(ck2*100000, AdvColumnGrid1.Cells[0,i], clGreen); Var_ if CheckBox4.Checked then Series4.Add(ck3*1000, AdvColumnGrid1.Cells[0,i], $00FF0080); Va if CheckBox5.Checked then Series5.Add(ck4*1000, AdvColumnGrid1.Cells[0,i], $00FF8000); } if cb_PF.Checked and cb_Vrms.Checked and cb_Irms.Checked and cb_Var.Checked and cb_Va.Checked and cb_Pwr.Checked then begin Series1.AddXY(no, pf*1000000, '', clRed); // x: 시간, y: PF Series2.AddXY(no, Vrms*1000, '', $000080FF); // x: 시간, y: Vrms Series3.AddXY(no, Irms*100000, '', clYellow); // x: 시간, y: Irms Series4.AddXY(no, Var_*1000, '', clGreen); // x: 시간, y: Pwr Series5.AddXY(no, Va*1000, '', clBlue); // x: 시간, y: Pwr Series6.AddXY(no, Pwr*100, '', clPurple); // x: 시간, y: Pwr end else begin Series1.AddXY(no, pf, '', clRed); // x: 시간, y: PF Series2.AddXY(no, Vrms, '', $000080FF); // x: 시간, y: Vrms Series3.AddXY(no, Irms, '', clYellow); // x: 시간, y: Irms Series4.AddXY(no, Var_, '', clGreen); // x: 시간, y: Pwr Series5.AddXY(no, Va, '', clBlue); // x: 시간, y: Pwr Series6.AddXY(no, Pwr, '', clPurple); // x: 시간, y: Pwr end; Series1.Visible := cb_PF.Checked; Series2.Visible := cb_Vrms.Checked; Series3.Visible := cb_Irms.Checked; Series4.Visible := cb_Var.Checked; Series5.Visible := cb_Va.Checked; Series6.Visible := cb_Pwr.Checked; no := no + 1; FDQuery2.Next; end; Chart_Main.Axes.Bottom.SetMinMax( 0, FDQuery2.RecordCount-1 ); end; end; end; procedure TfrmMain.Timer_DateTimeTimer(Sender: TObject); begin Label5.Caption := FormatDateTime('YYYY-MM-DD HH:mm:ss', now); end; procedure TfrmMain.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;//case.. end; procedure TfrmMain.TrayIcon1DblClick(Sender: TObject); begin TrayIcon1.Visible := False; Show; WindowState := wsNormal; Application.BringToFront; end; procedure TfrmMain.insertSQL(sql : string); begin try if not FDConnection1.Connected then FDConnection1.Connected := True; if FDConnection1.Connected then begin FDQuery1.Active := false; FDQuery1.SQL.Clear; FDQuery1.SQL.Add(sql); FDQuery1.ExecSQL; end; except on e : exception do begin printlog('[insertSQL()]Database Connect Error :: '+e.Message, 'DB'); end; end; end; procedure TfrmMain.insertTimerTimer(Sender: TObject); var pMsg, S, sql : string; pTopic : UTF8String; i, p : integer; x : integer; begin insertTimer.Enabled := false; if debug_mode then begin Statusbar1.Panels.Items[0].Text := 'DEBUG'; Inc(debug_mode_count, 1); if debug_mode_count > 1800 then // Debug Mode는 30분만 동작한다. CPU 점유률 때문에, 24시간 돌리지 않는다. begin debug_mode_count := 0; debug_mode := false; DebugMode1.Checked := debug_mode; Panel1.Visible := False; end; end else begin Statusbar1.Panels.Items[0].Text := 'RUN'; end; if (MQTTClient1.Online) then begin Statusbar1.Panels.Items[1].Text := 'CONN'; // dumyTopic := ''; // dumyTopic := 'SMART-FARM/QST/MQTT_HUB/PUB/0000'; // pTopic := ''; // pTopic := dumyTopic; // pMsg := ''; // S := ''; // S := formatdatetime('yyyymmddhhnnss',now()); // pMsg := format('{DATE_TIME=%s}', [S]);; // MQTTClient1.Publish(pTopic, pMsg, aQos, false); end else begin Statusbar1.Panels.Items[1].Text := 'DisCON'; end; // for p:=1 to max_gw do NILM_ID_List for i:=0 to NILM_ID_List.Count-1 do begin p := NILM_ID_List[i]; if (fNILM[p].rcvCount>0) and (fNILM[p].rcvCount <> fNILM[p].oldRcvCount) then begin fNILM[p].oldRcvCount := fNILM[p].RcvCount; sql := ''; sql := 'UPDATE "nilm_value" SET '+ '"pf_1"='+floattostr(fNILM[p]._VALUE.PF[1] )+', '+ '"pf_2"='+floattostr(fNILM[p]._VALUE.PF[2] )+', '+ '"pf_3"='+floattostr(fNILM[p]._VALUE.PF[3] )+', '+ '"Vrms_1"='+floattostr(fNILM[p]._VALUE.Vrms[1] )+', '+ '"Vrms_2"='+floattostr(fNILM[p]._VALUE.Vrms[2] )+', '+ '"Vrms_3"='+floattostr(fNILM[p]._VALUE.Vrms[3] )+', '+ '"Irms_1"='+floattostr(fNILM[p]._VALUE.Irms[1] )+', '+ '"Irms_2"='+floattostr(fNILM[p]._VALUE.Irms[2] )+', '+ '"Irms_3"='+floattostr(fNILM[p]._VALUE.Irms[3] )+', '+ '"Var_1"='+floattostr(fNILM[p]._VALUE._Var[1] )+', '+ '"Var_2"='+floattostr(fNILM[p]._VALUE._Var[2] )+', '+ '"Var_3"='+floattostr(fNILM[p]._VALUE._Var[3] )+', '+ '"Va_1"='+floattostr(fNILM[p]._VALUE.Va[1] )+', '+ '"Va_2"='+floattostr(fNILM[p]._VALUE.Va[2] )+', '+ '"Va_3"='+floattostr(fNILM[p]._VALUE.Va[3] )+', '+ '"Pwr_1"='+floattostr(fNILM[p]._VALUE.Pwr[1] )+', '+ '"Pwr_2"='+floattostr(fNILM[p]._VALUE.Pwr[2] )+', '+ '"Pwr_3"='+floattostr(fNILM[p]._VALUE.Pwr[3] )+', '+ '"gX"='+floattostr(fNILM[p]._VALUE.Gyro[1])+', '+ '"gY"='+floattostr(fNILM[p]._VALUE.Gyro[2])+', '+ '"gZ"='+floattostr(fNILM[p]._VALUE.Gyro[3])+', '+ '"aX"='+floattostr(fNILM[p]._VALUE.Accel[1])+', '+ '"aY"='+floattostr(fNILM[p]._VALUE.Accel[2])+', '+ '"aZ"='+floattostr(fNILM[p]._VALUE.Accel[3])+', '+ '"TempC"='+floattostr(fNILM[p]._VALUE.TempC)+', '+ '"lastUpdate"=now()'+ ' WHERE "NilmID"='+inttostr(p); if fNILM[p].idxNo > 0 then insertSQL(sql); sql := ''; sql := 'INSERT INTO "'+format('nilm_%.5d',[fNILM[p].idxNo])+'" VALUES(DEFAULT, '+ ''+inttostr(p)+', '+ // Nilm ID ''+floattostr(fNILM[p]._VALUE.PF[1] )+', 0, '+ ''+floattostr(fNILM[p]._VALUE.Vrms[1] )+', '+ ''+floattostr(fNILM[p]._VALUE.Irms[1] )+', '+ ''+floattostr(fNILM[p]._VALUE._Var[1] )+', '+ ''+floattostr(fNILM[p]._VALUE.Va[1] )+', '+ ''+floattostr(fNILM[p]._VALUE.Pwr[1] )+', '+ ''+floattostr(fNILM[p]._VALUE.PF[2] )+', 0, '+ ''+floattostr(fNILM[p]._VALUE.Vrms[2] )+', '+ ''+floattostr(fNILM[p]._VALUE.Irms[2] )+', '+ ''+floattostr(fNILM[p]._VALUE._Var[2] )+', '+ ''+floattostr(fNILM[p]._VALUE.Va[2] )+', '+ ''+floattostr(fNILM[p]._VALUE.Pwr[2] )+', '+ ''+floattostr(fNILM[p]._VALUE.PF[3] )+', 0, '+ ''+floattostr(fNILM[p]._VALUE.Vrms[3] )+', '+ ''+floattostr(fNILM[p]._VALUE.Irms[3] )+', '+ ''+floattostr(fNILM[p]._VALUE._Var[3] )+', '+ ''+floattostr(fNILM[p]._VALUE.Va[3] )+', '+ ''+floattostr(fNILM[p]._VALUE.Pwr[3] )+', '+ ''+floattostr(fNILM[p]._VALUE.Gyro[1])+', '+ ''+floattostr(fNILM[p]._VALUE.Gyro[2])+', '+ ''+floattostr(fNILM[p]._VALUE.Gyro[3])+', '+ ''+floattostr(fNILM[p]._VALUE.Accel[1])+', '+ ''+floattostr(fNILM[p]._VALUE.Accel[2])+', '+ ''+floattostr(fNILM[p]._VALUE.Accel[3])+', '+ ''+floattostr(fNILM[p]._VALUE.TempC)+', '+ 'now())'; if fNILM[p].idxNo > 0 then insertSQL(sql); for x := 1 to 2 do begin sql := ''; sql := ' SELECT dev_status, range FROM "dev_info" '+ ' WHERE "nilm_id"='+inttostr(p)+ ' AND "nilm_chNo"='+inttostr(x); FDQuery2.Close; FDQuery2.SQL.Clear; FDQuery2.SQL.Text := sql; FDQuery2.Open; if FDQuery2.RecordCount > 0 then begin if (FDQuery2.FieldByName('range').AsInteger > fNILM[p]._VALUE.Va[x]) and (FDQuery2.FieldByName('dev_status').AsString <> '대기') then begin //대기 sql := ''; sql := ' UPDATE "dev_info" SET '+ ' "dev_status"= ''대기'', '+ ' "lastUpdate"=now()'+ ' WHERE "nilm_id"='+inttostr(p)+ ' AND "nilm_chNo"='+inttostr(x); if fNILM[p].idxNo > 0 then insertSQL(sql); end else if (FDQuery2.FieldByName('range').AsInteger <= fNILM[p]._VALUE.Va[x]) and (FDQuery2.FieldByName('dev_status').AsString <> '가동') then begin //가동 sql := ''; sql := ' UPDATE "dev_info" SET '+ ' "dev_status"= ''가동'', '+ ' "lastUpdate"=now()'+ ' WHERE "nilm_id"='+inttostr(p)+ ' AND "nilm_chNo"='+inttostr(x); if fNILM[p].idxNo > 0 then insertSQL(sql); end; end; end; end else begin for x := 1 to 2 do begin sql := ''; sql := ' SELECT dev_status, range FROM "dev_info" '+ ' WHERE "nilm_id"='+inttostr(p)+ ' AND "nilm_chNo"='+inttostr(x); FDQuery2.Close; FDQuery2.SQL.Clear; FDQuery2.SQL.Text := sql; FDQuery2.Open; if FDQuery2.RecordCount > 0 then begin if (FDQuery2.FieldByName('dev_status').AsString <> '대기') then begin //대기 sql := ''; sql := ' UPDATE "dev_info" SET '+ ' "dev_status"= ''대기'', '+ ' "lastUpdate"=now()'+ ' WHERE "nilm_id"='+inttostr(p)+ ' AND "nilm_chNo"='+inttostr(x); if fNILM[p].idxNo > 0 then insertSQL(sql); end; end; end; end; end; insertTimer.Enabled := True; end; procedure TfrmMain.ListBox1DblClick(Sender: TObject); begin msg_flaw_stop := not msg_flaw_stop; end; procedure TfrmMain.ProgramExit1Click(Sender: TObject); begin frmMain.Close; end; procedure TfrmMain.rb_LogloadChartClick(Sender: TObject); begin Panel5.Visible := rb_LogloadChart.Checked; if rb_RealTimeChart.Checked then begin FMaxPoints := StrToInt(edit2.Text); // 차트에 100개의 데이터 포인트만 표시 // 차트 초기화 함수 호출 InitializeChart(0); CheckBox1.Visible := True; end else begin LoadLogChart; CheckBox1.Visible := False; end; end; procedure TfrmMain.DebugMode1Click(Sender: TObject); begin DebugMode1.Checked := not DebugMode1.Checked; debug_mode := DebugMode1.Checked; debug_mode_count := 0; Panel1.Visible := debug_mode; end; procedure TfrmMain.ComboBox1Change(Sender: TObject); begin Button1.Click; if rb_LogloadChart.Checked then LoadLogChart; end; procedure TfrmMain.Config1Click(Sender: TObject); begin frmConfigMQTT.Show; end; procedure TfrmMain.Edit1Click(Sender: TObject); begin frmNILM.Show; end; procedure TfrmMain.N1Click(Sender: TObject); begin frmDevConfig.Show; end; function TfrmMain.DB_Connect:Boolean; begin FDPhysPgDriverLink1.VendorLib := '.\libpq-10.dll'; FDConnection1.Connected := False; FDConnection1.Params.Clear; FDConnection1.Params.DriverID := 'PG'; FDConnection1.Params.Database := Trim(dbSchema_PG); FDConnection1.Params.UserName := Trim(dbUser_PG); FDConnection1.Params.Password := Trim(dbPW_PG); FDConnection1.Params.Add('Server=' + Trim(dbIP_PG)); FDConnection1.Params.Add('Port=' + Trim(IntToStr(dbPort_PG))); FDConnection1.Params.Add('CharacterSet=UTF8'); try FDConnection1.Connected := True; Result := True; // Memo1.Lines.Add('연결 성공'); // Memo1.Lines.Add('"Q1" 버튼을 누른 후 "쿼리" 버튼을 누르세요!!'); except on E: Exception do begin Result := False; // Memo1.Lines.Add('연결 실패: ' + E.Message); end; end; end; procedure TfrmMain.DatePicker2Change(Sender: TObject); begin LoadLogChart; end; function TfrmMain.DB_Close:Boolean; begin try if FDConnection1.Connected then FDConnection1.Connected := False; Result := True; except Result := False; end; end; function TfrmMain.xAddSqlDoubleMark(velStr: String): string; begin Result := ''; Result := '"' + velStr + '"'; end; function TfrmMain.xAddSqlMark(velStr: String): String; begin Result := ''; Result := '''' + velStr + ''''; end; procedure TfrmMain.InitializeChart(ck:integer); var x : Integer; begin // 기존 시리즈가 있다면 모두 제거 Chart_Main.SeriesList.Clear; Series1 := TLineSeries.Create(Self); Series2 := TLineSeries.Create(Self); Series3 := TLineSeries.Create(Self); Series4 := TLineSeries.Create(Self); Series5 := TLineSeries.Create(Self); Series6 := TLineSeries.Create(Self); // 라인 시리즈 추가 Chart_Main.AddSeries(Series1); Chart_Main.AddSeries(Series2); Chart_Main.AddSeries(Series3); Chart_Main.AddSeries(Series4); Chart_Main.AddSeries(Series5); Chart_Main.AddSeries(Series6); // 시리즈 설정 Series1.Title := 'PF'; Series1.Color := clRed; // 선 색상 Series1.LinePen.Width := 1; // 선 두께 Series1.Pointer.Visible := False; // 포인트 표시 여부 (선만 보려면 False) // 시리즈 설정 Series2.Title := 'Vrms'; Series2.Color := $000080FF; // 선 색상 Series2.LinePen.Width := 1; // 선 두께 Series2.Pointer.Visible := False; // 포인트 표시 여부 (선만 보려면 False) // 시리즈 설정 Series3.Title := 'Irms'; Series3.Color := clYellow; // 선 색상 Series3.LinePen.Width := 1; // 선 두께 Series3.Pointer.Visible := False; // 포인트 표시 여부 (선만 보려면 False) // 시리즈 설정 Series4.Title := 'Var'; Series4.Color := clGreen; // 선 색상 Series4.LinePen.Width := 1; // 선 두께 Series4.Pointer.Visible := False; // 포인트 표시 여부 (선만 보려면 False) // 시리즈 설정 Series5.Title := 'Va'; Series5.Color := clBlue; // 선 색상 Series5.LinePen.Width := 1; // 선 두께 Series5.Pointer.Visible := False; // 포인트 표시 여부 (선만 보려면 False) // 시리즈 설정 Series6.Title := 'Pwr'; Series6.Color := clPurple; // 선 색상 Series6.LinePen.Width := 1; // 선 두께 Series6.Pointer.Visible := False; // 포인트 표시 여부 (선만 보려면 False) // 차트 축 설정 Chart_Main.Axes.Bottom.Title.Caption := '시간'; Chart_Main.Axes.Left.Title.Caption := 'PF / Vrms / Irms / Var / Va / Pwr'; Chart_Main.Axes.Left.Minimum := 0; // 최소값 설정 (필요에 따라 조절) Chart_Main.Axes.Left.Maximum := 100; // 최대값 설정 (필요에 따라 조절) Chart_Main.Axes.Bottom.DateTimeFormat := 'mm:ss'; // 시간 포맷 설정 // 차트 제목 Chart_Main.Title.Text.Text := '실시간 (PF / Vrms / Irms / Var / Va / Pwr) 모니터링'; Chart_Main.Title.Visible := True; // 범례 표시 Chart_Main.Legend.Visible := True; // 애니메이션 효과 (선택 사항) // Chart1.Options.ClipSeries := False; // 스크롤링 시 부드러운 움직임 if ck = 0 then begin for x := 0 to FMaxPoints-1 do begin Series1.AddXY(x, 0, '', clRed); // x: 시간, y: PF Series2.AddXY(x, 0, '', $000080FF); // x: 시간, y: Vrms Series3.AddXY(x, 0, '', clYellow); // x: 시간, y: Irms Series4.AddXY(x, 0, '', clGreen); // x: 시간, y: Var Series5.AddXY(x, 0, '', clBlue); // x: 시간, y: Va Series6.AddXY(x, 0, '', clPurple); // x: 시간, y: Pwr end; no := FMaxPoints; end; end; end.