454 lines
13 KiB
ObjectPascal
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.
|