unit uNILMDeviceForm; { NILM 장비 추가 / 수정 다이얼로그 폼 - 기본 정보 입력 (device_id, 명칭, 위치, 상수타입, 활성여부, 메모) - 센서 그룹별 CheckBox 설정 (탭 페이지) - 상수 타입 변경 시 L2/L3 탭 표시/숨김 처리 } interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, System.Generics.Collections, System.UITypes, System.Math, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ComCtrls, Vcl.Grids, uNILMTypes; type TfNILMDevice = class(TForm) pnlTop : TPanel; lblTitle : TLabel; pnlBottom : TPanel; btnSave : TButton; btnCancel : TButton; pnlBasic : TPanel; lblBasicInfo : TLabel; lblDeviceID : TLabel; edtDeviceID : TEdit; lblDeviceName: TLabel; edtDeviceName: TEdit; lblLocation : TLabel; edtLocation : TEdit; lblPhaseType : TLabel; rgPhaseType : TRadioGroup; chkIsActive : TCheckBox; lblMqttTopic : TLabel; edtMqttTopic : TEdit; lblMemo : TLabel; memMemo : TMemo; lblOpTargetPhase: TLabel; cmbOpTargetPhase: TComboBox; lblOpThresholdOffCurrent: TLabel; edtOpThresholdOffCurrent: TEdit; lblOpThresholdRunCurrent: TLabel; edtOpThresholdRunCurrent: TEdit; lblOpThresholdPF: TLabel; edtOpThresholdPF: TEdit; pnlSensor : TPanel; lblSensorCfg : TLabel; grdSensor : TStringGrid; procedure btnSaveClick(Sender: TObject); procedure btnCancelClick(Sender: TObject); procedure rgPhaseTypeClick(Sender: TObject); procedure FormCreate(Sender: TObject); private FIsNew : Boolean; FDevice : TNILMDevice; procedure BuildSensorUI; function ValidateInput: Boolean; procedure PopulateFromDevice; procedure ApplyToDevice; public constructor Create(AOwner: TComponent; const ADevice: TNILMDevice; AIsNew: Boolean); reintroduce; /// 다이얼로그 실행 후 결과 장비 정보 반환, OK 여부 반환 function Execute(out AResult: TNILMDevice): Boolean; end; implementation uses uNILMManager; {$R *.dfm} { TfNILMDevice } constructor TfNILMDevice.Create(AOwner: TComponent; const ADevice: TNILMDevice; AIsNew: Boolean); begin inherited Create(AOwner); FIsNew := AIsNew; FDevice := ADevice; if not Assigned(FDevice.SensorList) then FDevice.SensorList := TNILMSensorConfigList.Create; end; procedure TfNILMDevice.FormCreate(Sender: TObject); begin if FIsNew then Caption := 'NILM 장비 추가' else Caption := 'NILM 장비 수정'; lblTitle.Caption := Caption; end; procedure TfNILMDevice.PopulateFromDevice; begin edtDeviceID.Text := IntToStr(FDevice.DeviceID); edtDeviceName.Text := FDevice.DeviceName; edtLocation.Text := FDevice.Location; chkIsActive.Checked:= FDevice.IsActive; edtMqttTopic.Text := FDevice.MqttTopic; memMemo.Text := FDevice.Memo; cmbOpTargetPhase.ItemIndex := cmbOpTargetPhase.Items.IndexOf(FDevice.OpTargetPhase); if cmbOpTargetPhase.ItemIndex < 0 then cmbOpTargetPhase.ItemIndex := cmbOpTargetPhase.Items.IndexOf('AVG'); edtOpThresholdOffCurrent.Text := FloatToStr(FDevice.OpThresholdOffCurrent); edtOpThresholdRunCurrent.Text := FloatToStr(FDevice.OpThresholdRunCurrent); edtOpThresholdPF.Text := FloatToStr(FDevice.OpThresholdPF); if FDevice.PhaseType = PHASE_3PHASE then rgPhaseType.ItemIndex := 0 else rgPhaseType.ItemIndex := 1; BuildSensorUI; end; procedure TfNILMDevice.BuildSensorUI; var Is3Phase: Boolean; SC : TNILMSensorConfig; i : Integer; begin Is3Phase := (rgPhaseType.ItemIndex = 0); // 센서 목록이 없으면 기본 템플릿 생성 if FDevice.SensorList.Count = 0 then begin FDevice.SensorList.Clear; var TempList: TNILMSensorConfigList; if Is3Phase then TempList := TNILMSensorTemplate.BuildDefaultSensors(FDevice.DeviceID, PHASE_3PHASE) else TempList := TNILMSensorTemplate.BuildDefaultSensors(FDevice.DeviceID, PHASE_SINGLE); FDevice.SensorList.AddRange(TempList); TempList.Free; end; grdSensor.RowCount := Max(2, FDevice.SensorList.Count + 1); grdSensor.Cells[0, 0] := '그룹'; grdSensor.Cells[1, 0] := '센서 항목'; grdSensor.Cells[2, 0] := '사용(O/X)'; grdSensor.Cells[3, 0] := 'JSON 키'; grdSensor.Cells[4, 0] := 'FieldKey'; grdSensor.ColWidths[0] := 150; grdSensor.ColWidths[1] := 120; grdSensor.ColWidths[2] := 90; grdSensor.ColWidths[3] := 140; grdSensor.ColWidths[4] := 0; for i := 0 to FDevice.SensorList.Count - 1 do begin SC := FDevice.SensorList[i]; grdSensor.Cells[0, i+1] := SC.SensorGroup; grdSensor.Cells[1, i+1] := SC.FieldLabel; if SC.IsEnabled then grdSensor.Cells[2, i+1] := 'O' else grdSensor.Cells[2, i+1] := 'X'; grdSensor.Cells[3, i+1] := SC.JsonKey; grdSensor.Cells[4, i+1] := SC.FieldKey; end; end; procedure TfNILMDevice.rgPhaseTypeClick(Sender: TObject); begin FDevice.SensorList.Clear; BuildSensorUI; end; function TfNILMDevice.ValidateInput: Boolean; var ID : Integer; D : Double; Mgr: TNILMManager; begin Result := False; if not TryStrToInt(edtDeviceID.Text, ID) or (ID <= 0) then begin MessageDlg('장비 번호는 1 이상의 숫자여야 합니다.', mtError, [mbOK], 0); edtDeviceID.SetFocus; Exit; end; if Trim(edtDeviceName.Text) = '' then begin MessageDlg('장비 명칭을 입력해주세요.', mtError, [mbOK], 0); edtDeviceName.SetFocus; Exit; end; if not TryStrToFloat(edtOpThresholdOffCurrent.Text, D) then begin MessageDlg('전원꺼짐 기준 전류는 숫자여야 합니다.', mtError, [mbOK], 0); edtOpThresholdOffCurrent.SetFocus; Exit; end; if not TryStrToFloat(edtOpThresholdRunCurrent.Text, D) then begin MessageDlg('가동 기준 전류는 숫자여야 합니다.', mtError, [mbOK], 0); edtOpThresholdRunCurrent.SetFocus; Exit; end; if not TryStrToFloat(edtOpThresholdPF.Text, D) then begin MessageDlg('가동 기준 파워팩터는 숫자여야 합니다.', mtError, [mbOK], 0); edtOpThresholdPF.SetFocus; Exit; end; Mgr := TNILMManager.Create; try try if Mgr.DeviceIDExists(ID, FDevice.ID) then begin MessageDlg(Format('장비 번호 %d 는 이미 등록되어 있습니다.', [ID]), mtError, [mbOK], 0); edtDeviceID.SetFocus; Exit; end; except on E: Exception do begin MessageDlg('DB 연결이 없어 중복 확인을 건너뜁니다.' + sLineBreak + E.Message, mtWarning, [mbOK], 0); end; end; finally Mgr.Free; end; Result := True; end; procedure TfNILMDevice.ApplyToDevice; var SC : TNILMSensorConfig; i : Integer; begin FDevice.DeviceID := StrToIntDef(edtDeviceID.Text, 0); FDevice.DeviceName := Trim(edtDeviceName.Text); FDevice.Location := Trim(edtLocation.Text); FDevice.IsActive := chkIsActive.Checked; FDevice.Memo := memMemo.Text; // FDevice.MqttTopic := FDevice.BuildTopic; // 토픽 자동생성 금지 FDevice.OpTargetPhase := cmbOpTargetPhase.Text; FDevice.OpThresholdOffCurrent := StrToFloatDef(edtOpThresholdOffCurrent.Text, 0.0); FDevice.OpThresholdRunCurrent := StrToFloatDef(edtOpThresholdRunCurrent.Text, 0.0); FDevice.OpThresholdPF := StrToFloatDef(edtOpThresholdPF.Text, 0.0); if rgPhaseType.ItemIndex = 0 then FDevice.PhaseType := PHASE_3PHASE else FDevice.PhaseType := PHASE_SINGLE; FDevice.SensorList.Clear; for i := 1 to grdSensor.RowCount - 1 do begin if Trim(grdSensor.Cells[1, i]) = '' then Continue; SC.ID := 0; SC.DeviceID := FDevice.DeviceID; SC.SensorGroup := grdSensor.Cells[0, i]; SC.FieldLabel := grdSensor.Cells[1, i]; SC.IsEnabled := (Trim(grdSensor.Cells[2, i]) = 'O'); SC.JsonKey := Trim(grdSensor.Cells[3, i]); SC.FieldKey := Trim(grdSensor.Cells[4, i]); SC.DisplayOrder := i; FDevice.SensorList.Add(SC); end; end; procedure TfNILMDevice.btnSaveClick(Sender: TObject); begin if ValidateInput then begin ApplyToDevice; ModalResult := mrOk; end; end; procedure TfNILMDevice.btnCancelClick(Sender: TObject); begin ModalResult := mrCancel; end; function TfNILMDevice.Execute(out AResult: TNILMDevice): Boolean; begin PopulateFromDevice; Result := (ShowModal = mrOk); if Result then AResult := FDevice; end; end.