SungjuNewPrime/NILM_Agent/AgentNILM_MQTTClient(Delphi13 VCL)/uNILMDeviceForm.pas
2026-09-04 11:15:47 +09:00

454 lines
13 KiB
ObjectPascal

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;
grpEstimate : TGroupBox;
chkIsEstimatePower: TCheckBox;
lblNominalVoltage : TLabel;
edtNominalVoltage : TEdit;
lblAssumedPF : TLabel;
edtAssumedPF : TEdit;
lblEstimateWiring : TLabel;
cmbEstimateWiring : TComboBox;
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); override;
destructor Destroy; override;
procedure InitForAdd;
procedure InitForEdit(const ADevice: TNILMDevice);
/// 다이얼로그 실행 후 결과 장비 정보 반환, OK 여부 반환
function Execute(out AResult: TNILMDevice): Boolean;
end;
TfNILMDeviceForm = TfNILMDevice;
implementation
uses
uNILMManager;
{$R *.dfm}
{ TfNILMDevice }
constructor TfNILMDevice.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FIsNew := True;
FDevice.SensorList := TNILMSensorConfigList.Create;
end;
destructor TfNILMDevice.Destroy;
begin
if Assigned(FDevice.SensorList) then
FreeAndNil(FDevice.SensorList);
inherited Destroy;
end;
procedure TfNILMDevice.FormCreate(Sender: TObject);
begin
if FIsNew then
Caption := 'NILM 장비 추가'
else
Caption := 'NILM 장비 수정';
lblTitle.Caption := Caption;
end;
procedure TfNILMDevice.InitForAdd;
begin
FIsNew := True;
Caption := 'NILM 장비 추가';
lblTitle.Caption := Caption;
edtDeviceID.ReadOnly := False;
edtDeviceID.Text := '';
edtDeviceName.Text := '';
edtLocation.Text := '';
chkIsActive.Checked := True;
edtMqttTopic.Text := 'QST/SUNGJOO/NEWPRIME/NE002/';
memMemo.Text := '';
cmbOpTargetPhase.ItemIndex := cmbOpTargetPhase.Items.IndexOf('AVG');
edtOpThresholdOffCurrent.Text := '0.5';
edtOpThresholdRunCurrent.Text := '1.0';
edtOpThresholdPF.Text := '0.7';
rgPhaseType.ItemIndex := 0; // 3P3W default
chkIsEstimatePower.Checked := True;
edtNominalVoltage.Text := '380.0';
edtAssumedPF.Text := '0.90';
cmbEstimateWiring.ItemIndex := 1; // 3상 평형 (P = √3 * V * I * PF)
if not Assigned(FDevice.SensorList) then
FDevice.SensorList := TNILMSensorConfigList.Create;
FDevice.SensorList.Clear;
BuildSensorUI;
end;
procedure TfNILMDevice.InitForEdit(const ADevice: TNILMDevice);
var
SC: TNILMSensorConfig;
begin
FIsNew := False;
FDevice.ID := ADevice.ID;
FDevice.DeviceID := ADevice.DeviceID;
FDevice.DeviceName := ADevice.DeviceName;
FDevice.Location := ADevice.Location;
FDevice.PhaseType := ADevice.PhaseType;
FDevice.IsActive := ADevice.IsActive;
FDevice.Memo := ADevice.Memo;
FDevice.MqttTopic := ADevice.MqttTopic;
FDevice.CreatedAt := ADevice.CreatedAt;
FDevice.UpdatedAt := ADevice.UpdatedAt;
FDevice.OpThresholdOffCurrent := ADevice.OpThresholdOffCurrent;
FDevice.OpThresholdRunCurrent := ADevice.OpThresholdRunCurrent;
FDevice.OpThresholdPF := ADevice.OpThresholdPF;
FDevice.OpTargetPhase := ADevice.OpTargetPhase;
FDevice.IsEstimatePower := ADevice.IsEstimatePower;
FDevice.NominalVoltage := ADevice.NominalVoltage;
FDevice.AssumedPF := ADevice.AssumedPF;
FDevice.EstimateWiring := ADevice.EstimateWiring;
if not Assigned(FDevice.SensorList) then
FDevice.SensorList := TNILMSensorConfigList.Create;
FDevice.SensorList.Clear;
if Assigned(ADevice.SensorList) then
begin
for SC in ADevice.SensorList do
FDevice.SensorList.Add(SC);
end;
Caption := 'NILM 장비 수정';
lblTitle.Caption := Caption;
edtDeviceID.ReadOnly := True;
PopulateFromDevice;
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);
chkIsEstimatePower.Checked := FDevice.IsEstimatePower;
edtNominalVoltage.Text := FloatToStr(IfThen(FDevice.NominalVoltage > 0, FDevice.NominalVoltage, 220.0));
edtAssumedPF.Text := FloatToStr(IfThen(FDevice.AssumedPF > 0, FDevice.AssumedPF, 0.90));
if SameText(FDevice.EstimateWiring, '3PHASE_BALANCED') then
cmbEstimateWiring.ItemIndex := 1
else
cmbEstimateWiring.ItemIndex := 0;
if FDevice.PhaseType = PHASE_3P3W then
rgPhaseType.ItemIndex := 0
else if (FDevice.PhaseType = PHASE_3P4W) or (FDevice.PhaseType = PHASE_3PHASE) then
rgPhaseType.ItemIndex := 1
else if FDevice.PhaseType = PHASE_SINGLE then
rgPhaseType.ItemIndex := 2
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 rgPhaseType.ItemIndex = 0 then
TempList := TNILMSensorTemplate.BuildDefaultSensors(FDevice.DeviceID, PHASE_3P3W)
else if rgPhaseType.ItemIndex = 1 then
TempList := TNILMSensorTemplate.BuildDefaultSensors(FDevice.DeviceID, PHASE_3P4W)
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] := 130;
grdSensor.ColWidths[1] := 230;
grdSensor.ColWidths[2] := 80;
grdSensor.ColWidths[3] := 180;
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
if rgPhaseType.ItemIndex in [0, 1] then
begin
// 3상 3선식 또는 3상 4선식 선택 시: 3상 평형 추정 및 380V 기본
cmbEstimateWiring.ItemIndex := 1; // 3상 평형 (P = √3 * V * I * PF)
if (edtNominalVoltage.Text = '') or (edtNominalVoltage.Text = '220') or (edtNominalVoltage.Text = '220.0') then
edtNominalVoltage.Text := '380.0';
end
else
begin
// 단상 선택 시: 단상 추정 및 220V 기본
cmbEstimateWiring.ItemIndex := 0; // 단상 (P = V * I * PF)
if (edtNominalVoltage.Text = '') or (edtNominalVoltage.Text = '380') or (edtNominalVoltage.Text = '380.0') then
edtNominalVoltage.Text := '220.0';
end;
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 := Trim(edtMqttTopic.Text);
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);
FDevice.IsEstimatePower := chkIsEstimatePower.Checked;
FDevice.NominalVoltage := StrToFloatDef(edtNominalVoltage.Text, 220.0);
FDevice.AssumedPF := StrToFloatDef(edtAssumedPF.Text, 0.90);
if cmbEstimateWiring.ItemIndex = 1 then
FDevice.EstimateWiring := '3PHASE_BALANCED'
else
FDevice.EstimateWiring := 'SINGLE';
if rgPhaseType.ItemIndex = 0 then
FDevice.PhaseType := PHASE_3P3W
else if rgPhaseType.ItemIndex = 1 then
FDevice.PhaseType := PHASE_3P4W
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);
var
Mgr: TNILMManager;
begin
if ValidateInput then
begin
ApplyToDevice;
Mgr := TNILMManager.Create;
try
Mgr.SaveDevice(FDevice);
finally
Mgr.Free;
end;
ModalResult := mrOk;
end;
end;
procedure TfNILMDevice.btnCancelClick(Sender: TObject);
begin
ModalResult := mrCancel;
end;
function TfNILMDevice.Execute(out AResult: TNILMDevice): Boolean;
var
SC: TNILMSensorConfig;
begin
PopulateFromDevice;
Result := (ShowModal = mrOk);
if Result then
begin
AResult := FDevice;
AResult.SensorList := TNILMSensorConfigList.Create;
if Assigned(FDevice.SensorList) then
begin
for SC in FDevice.SensorList do
AResult.SensorList.Add(SC);
end;
end;
end;
end.