2026-01-26 14:51:28 +09:00

705 lines
28 KiB
C#

using Aurora;
using Aurora.Api;
using Aurora.View;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Input;
using System.Windows.Threading;
using static AuroraPLC.PLCBase;
namespace AuroraPLC.Views
{
public class BITValue : NotifyPropertyChangedBase
{
public string Address { get; set; }
private string _description;
public string Description {
get => _description;
set {
if (_description != value) {
_description = value;
NotifyPropertyChanged("Description");
}
}
}
private bool inPutStatus;
public bool InPutStatus {
get { return inPutStatus; }
set {
if (inPutStatus != value) {
inPutStatus = value;
NotifyPropertyChanged("InPutStatus");
}
}
}
private bool outPutStatus;
public bool OutPutStatus {
get { return outPutStatus; }
set {
if (outPutStatus != value) {
outPutStatus = value;
NotifyPropertyChanged("OutPutStatus");
}
}
}
//public string hexAddress { get; set; }
public int addrOffset { get; set; }
private bool _use = true; // DataGridRow의 IsEnable과 Binding되는 property
public bool Use {
get { return _use; }
set { SetProperty(ref _use, value); }
} // DataGridRow의 IsEnable과 Binding되는 property
}
public class UCPLCBitViewModel : NotifyPropertyChangedBase
{
Logger Log = new Logger("PLC Mon. Bit");
public ICommand CMD_VisibleChanged { get; }
public ICommand CMD_CalculateFlatness { get; }
public ICommand CMD_ChangeValue { get; }
// 해당 뷰 enable 상태
/*
private bool _isUCViewEnabled = true;
public bool IsUCViewEnabled { get => _isUCViewEnabled; set => SetProperty(ref _isUCViewEnabled, value); }
*/
public ObservableCollection<BITValue> DigitalInputs { get; set; } = new ObservableCollection<BITValue>();
public ObservableCollection<BITValue> DigitalOutputs { get; set; } = new ObservableCollection<BITValue>();
public ObservableCollection<BITValue> DigitalInputsTest { get; set; } = new ObservableCollection<BITValue>();
public ObservableCollection<BITValue> DigitalOutputsTest { get; set; } = new ObservableCollection<BITValue>();
public ObservableCollection<BITValue> DigitalInputsView {
get {
if (!string.IsNullOrEmpty(FilterInputDescription) && FilterInputDescription.Split('|').Any(x => x.Length > 0)) {
DigitalInputsTest.Clear();
//DigitalInputs.Where(x => x.Description.ToLower().Contains(FilterInputDescription.ToLower())).ToList().ForEach(z => DigitalInputsTest.Add(z));
FilterInputDescription.Split('|').Where(token => !string.IsNullOrEmpty(token)).ToList().ForEach(filterDesc => DigitalInputs.Where(x => x.Description.ToLower().Contains(filterDesc.ToLower())).ToList().ForEach(z => DigitalInputsTest.Add(z)));
return DigitalInputsTest;
} else if (IsInputShowDescriptionExist) {
DigitalInputsTest.Clear();
DigitalInputs.Where(x => x.Description != "").ToList().ForEach(z => DigitalInputsTest.Add(z));
return DigitalInputsTest;
} else {
return DigitalInputs;
}
}
}
public ObservableCollection<BITValue> DigitalOutputsView {
get {
if (!string.IsNullOrEmpty(FilterOutputDescription)) {
DigitalOutputsTest.Clear();
//DigitalOutputs.Where(x => x.Description.ToLower().Contains(FilterOutputDescription.ToLower())).ToList().ForEach(z => DigitalOutputsTest.Add(z));
FilterOutputDescription.Split('|').Where(token => !string.IsNullOrEmpty(token)).ToList().ForEach(filterDesc => DigitalOutputs.Where(x => x.Description.ToLower().Contains(filterDesc.ToLower())).ToList().ForEach(z => DigitalOutputsTest.Add(z)));
return DigitalOutputsTest;
} else if (IsOutputShowDescriptionExist && string.IsNullOrEmpty(FilterOutputDescription)) {
DigitalOutputsTest.Clear();
DigitalOutputs.Where(x => x.Description != "").ToList().ForEach(z => DigitalOutputsTest.Add(z));
return DigitalOutputsTest;
} else {
return DigitalOutputs;
}
}
}
//------------------------
// For Sim Only
public bool IsThisInput { get; private set; }
private bool _isInputShowDescriptionExist;
public bool IsInputShowDescriptionExist {
get { return _isInputShowDescriptionExist; }
set {
if (_isInputShowDescriptionExist != value) {
SetProperty(ref _isInputShowDescriptionExist, value);
NotifyPropertyChanged("DigitalInputsView");
}
}
}
private bool _isOutputShowDescriptionExist;
public bool IsOutputShowDescriptionExist {
get { return _isOutputShowDescriptionExist; }
set {
if (_isOutputShowDescriptionExist != value) {
SetProperty(ref _isOutputShowDescriptionExist, value);
NotifyPropertyChanged("DigitalOutputsView");
}
}
}
private string _FilterInputDescription;
public string FilterInputDescription {
get { return _FilterInputDescription; }
set {
if (_FilterInputDescription != value) {
SetProperty(ref _FilterInputDescription, value);
NotifyPropertyChanged("DigitalInputsView");
}
}
}
private string _FilterOutputDescription;
public string FilterOutputDescription {
get { return _FilterOutputDescription; }
set {
if (_FilterOutputDescription != value) {
SetProperty(ref _FilterOutputDescription, value);
NotifyPropertyChanged("DigitalOutputsView");
}
}
}
// Input/Ouput 공용 Item
private BITValue selectedBitItem;
public BITValue SelectedBitItem {
get {
return selectedBitItem;
}
set {
selectedBitItem = value;
}
}
private BITValue selectedInputItem;
public BITValue SelectedInputItem {
get {
return selectedInputItem;
}
set {
// 반대편 datagrid 의 선택을 해제한다.
selectedOutputItem = null;
NotifyPropertyChanged("SelectedOutputItem");
//====================================
selectedInputItem = value;
// For Sim mode : Input bit change
IsThisInput = true;
selectedBitItem = value;
NotifyPropertyChanged("SelectedBitAddress");
}
}
//====================================
private BITValue selectedOutputItem;
public BITValue SelectedOutputItem {
get {
return selectedOutputItem;
}
set {
// 반대편 datagrid 의 선택을 해제한다.
selectedInputItem = null;
NotifyPropertyChanged("SelectedInputItem");
//=====================================
selectedOutputItem = value;
// For Sim mode : Input bit change
IsThisInput = false;
selectedBitItem = value;
NotifyPropertyChanged("SelectedBitAddress");
}
}
public string SelectedBitAddress {
get {
return SelectedBitItem?.Address;
}
}
private int selectedInputIndex = 0;
public int SelectedInputIndex {
get => selectedInputIndex;
set {
if (selectedInputIndex != value) {
selectedInputIndex = value;
DigitalInputs = InputMaps[value];
NotifyPropertyChanged("DigitalInputs");
}
}
}
private int selectedOutputIndex = 0;
public int SelectedOutputIndex {
get => selectedOutputIndex;
set {
if (selectedOutputIndex != value) {
selectedOutputIndex = value;
DigitalOutputs = OutputMaps[value];
NotifyPropertyChanged("DigitalOutputs");
}
}
}
private bool isActivated = false;
public bool IsActivated {
get => isActivated;
set {
if (isActivated != value) {
isActivated = value;
NotifyPropertyChanged("IsActivated");
}
}
}
public Dictionary<int, ObservableCollection<BITValue>> InputMaps = new Dictionary<int, ObservableCollection<BITValue>>();
public Dictionary<int, ObservableCollection<BITValue>> OutputMaps = new Dictionary<int, ObservableCollection<BITValue>>();
private bool isVisible;
private eLangType curLanguage;
private IDeviceEntry DeviceManager;
private readonly PLCBase _plc;
private ePLCDevice _deviceName;
private readonly DispatcherTimer MonitorTimer;
private bool _changeButtonEnable;
public bool ChangeButtonEnable {
get => _changeButtonEnable;
set => SetProperty(ref _changeButtonEnable, value);
}
public UCPLCBitViewModel(IAuroraEnvironment aurora, string deviceId, ePLCNetType plcNetType, ePLCDevice device = ePLCDevice.B)
{
DeviceManager = aurora.DeviceManager;
CMD_VisibleChanged = new DelegateCommand(VisibleChanged);
CMD_ChangeValue = new DelegateCommand(ChangeBitValue);
_deviceName = device;
_plc = DeviceManager[deviceId] as PLCBase;
ObservableCollection<BITValue> input = new ObservableCollection<BITValue>();
ObservableCollection<BITValue> output = new ObservableCollection<BITValue>();
InputMaps.Add(0, input);
OutputMaps.Add(0, output);
isVisible = false;
curLanguage = PLCBase.Language;
// Config 로부터 Input/Output 메모리 맵 파싱 및 할당
LoadBitFromConfig();
Observable.Merge(
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_ACTIVATED),
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_DEACTIVATED))
.Select(msg => msg.Param as string)
.Where(x => x == deviceId).ObserveOnDispatcher()
.Subscribe(x => OnDeviceActiveChanged());
// Log In/Out 시점 파악을 위한 이벤트 관측하기 : For Write 권한 제어
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_PERMISSION_CHANGED)
.Subscribe(x => UpdateWriteState());
// 언어 변경 이벤트 구독
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_LANGUAGE_CHANGED)
.Select(msg => msg.Param.ToString().Split('-').First().ToLower())//.Where(lan => lan != _currLanguage)
.ObserveOnDispatcher().Subscribe(lan => UpdateLanguage(lan));
DigitalInputs = InputMaps[0];
DigitalOutputs = OutputMaps[0];
// 타이머
MonitorTimer = new DispatcherTimer();
MonitorTimer.Tick += OnMonitoring;
MonitorTimer.Interval = TimeSpan.FromMilliseconds(250);
}
private void UpdateLanguage(string lan)
{
// 예외 조건
// 언어가 변경되었는데, 모니터링 뷰 화면이 아닌 경우,
if (isVisible == false) {
} else { // 모니터링 화면인 경우,
// 타이머 중단
MonitorTimer.Stop();
switch (lan) {
case "en": {
UpdateViewLanguage(eLangType.English);
break;
}
case "ko": {
UpdateViewLanguage(eLangType.Korean);
break;
}
}
// 타이머 재시작
MonitorTimer.Start();
}
}
private void UpdateViewLanguage(eLangType langType)
{
int i = 0;
switch (langType) {
case eLangType.English: {
foreach (var ioValue in DigitalInputs) {
if (_deviceName == ePLCDevice.M) {
ioValue.Description = _plc.MBitInputs[i].description.Comment_EN;
} else {
ioValue.Description = _plc.BitInputs[i].description.Comment_EN;
}
++i;
}
i = 0;
foreach (var ioValue in DigitalOutputs) {
if (_deviceName == ePLCDevice.M) {
ioValue.Description = _plc.MBitOutputs[i].description.Comment_EN;
} else {
ioValue.Description = _plc.BitOutputs[i].description.Comment_EN;
}
++i;
}
break;
}
case eLangType.Korean: {
foreach (var ioValue in DigitalInputs) {
if (_deviceName == ePLCDevice.M) {
ioValue.Description = _plc.MBitInputs[i].description.Comment_KR;
} else {
ioValue.Description = _plc.BitInputs[i].description.Comment_KR;
}
++i;
}
i = 0;
foreach (var ioValue in DigitalOutputs) {
if (_deviceName == ePLCDevice.M) {
ioValue.Description = _plc.MBitOutputs[i].description.Comment_KR;
} else {
ioValue.Description = _plc.BitOutputs[i].description.Comment_KR;
}
++i;
}
break;
}
}
// 뷰모델 및 PLC 언어도 업데이트
PLCBase.Language = langType;
curLanguage = langType;
}
private void UpdateWriteState() // 화면 내에서 사용자 권한이 변경되었을 경우
{
/*
var curPermissionLevel = ApplicationExt.PermissionLevel;
if (curPermissionLevel > (ushort)Aurora.Api.PermissionLevel.LEVEL3 || IsActivated == false) { // 권한 부족
ChangeButtonEnable = false;
} else {
if (PLC.IsViewEnabled) {
ChangeButtonEnable = true;
} else {
ChangeButtonEnable = false;
}
}
*/
// Auto 모드인 경우, 변경 불가
if (PLCStatic.IsViewEnabled) {
ChangeButtonEnable = true;
} else {
ChangeButtonEnable = false;
}
}
private void VisibleChanged(object obj) // 화면 전환 시 동작하는 곳
{
// Change 버튼에 대한 권한 검사를 수행
UpdateWriteState();
isVisible = (bool)obj;
if (isVisible) {
// 뷰모델이 가지고 있는 언어와 현재 Aurora 언어가 다르면, 설명 언어를 변경함
if (curLanguage != _plc.GetLangTypeFrom(ApplicationExt.CurLang)) {
UpdateViewLanguage(PLCBase.Language);
}
MonitorTimer.Start();
} else {
MonitorTimer.Stop();
}
}
private void OnDeviceActiveChanged()
{
IsActivated = _plc.IsActivated;
if (IsActivated == false) {
ChangeButtonEnable = false;
}
}
private void LoadBitFromConfig()
{
if (_deviceName == ePLCDevice.M) {
// Bit Input
var startAddress = _plc.MBitInputs.Count != 0 ? _plc.MBitInputs.OrderBy(x => x.address).First().address : 0;
foreach (var inputB in _plc.MBitInputs) {
// 주소와 설명문으로 파싱
//(var address, var desc) = inputB;
//(int address, string desc) = inputB;
var address = inputB.Item1;
string desc = null;
switch (curLanguage) {
case eLangType.English: {
desc = inputB.Item2.Comment_EN;
break;
}
case eLangType.Korean: {
desc = inputB.Item2.Comment_KR;
break;
}
}
desc?.Trim();
// list 는 클래스의 주소값을 가지고 있음 : 얕은 복사
ObservableCollection<BITValue> list = InputMaps[0];
// Scriber 는 4자리 주소 중에서 앞자리 2개는 고정으로 구성되어 있었다. Cell Repair 는 B5000 ~ (B51F0 + F) 범위 사용 중 (아마도..)
// Scriber 의 2자리만 가지지 말고, 4자리 주소값을 유지한다.
string strAddress = "M" + address.ToString(); // convert to Hex string (4자리)
var ioValue = new BITValue() {
Address = strAddress,
Description = desc,
// 메모리 주소 4자리를 유지하게 수정한다.
//hexAddress = strAddress.Substring(3, strAddress.Length-3), // 마지막 2자리만 보존
//hexAddress = strAddress.Substring(1, strAddress.Length - 1),
addrOffset = address - startAddress,
Use = true // 특정 Value를 사용하지 않을 경우 false로 설정 (DataGridRow의 IsEnabled와 연동)
};
// 저장
list.Add(ioValue);
}
// Bit Output
startAddress = _plc.MBitOutputs.Count != 0 ? _plc.MBitOutputs.OrderBy(x => x.address).First().address : 0;
foreach (var outB in _plc.MBitOutputs) {
// 주소와 설명문으로 파싱
//(var address, var desc) = outB;
var address = outB.Item1;
string desc = null;
switch (curLanguage) {
case eLangType.English: {
desc = outB.Item2.Comment_EN;
break;
}
case eLangType.Korean: {
desc = outB.Item2.Comment_KR;
break;
}
}
desc?.Trim();
// list 는 클래스의 주소값을 가지고 있음
ObservableCollection<BITValue> list;
list = OutputMaps[0];
string strAddress = "M" + address.ToString(); // convert to Hex string (4자리)
var ioValue = new BITValue() {
Address = strAddress,
Description = desc,
//hexAddress = strAddress.Substring(1, strAddress.Length - 1),
addrOffset = address - startAddress,
Use = true // 특정 Value를 사용하지 않을 경우 false로 설정 (DataGridRow의 IsEnabled와 연동)
};
// 저장
list.Add(ioValue);
}
} else {
// Bit Input
var startAddress = _plc.BitInputs.Count != 0 ? _plc.BitInputs.OrderBy(x => x.address).First().address : 0;
foreach (var inputB in _plc.BitInputs) {
// 주소와 설명문으로 파싱
//(var address, var desc) = inputB;
//(int address, string desc) = inputB;
var address = inputB.Item1;
string desc = null;
switch (curLanguage) {
case eLangType.English: {
desc = inputB.Item2.Comment_EN;
break;
}
case eLangType.Korean: {
desc = inputB.Item2.Comment_KR;
break;
}
}
desc?.Trim();
// list 는 클래스의 주소값을 가지고 있음 : 얕은 복사
ObservableCollection<BITValue> list = InputMaps[0];
// Scriber 는 4자리 주소 중에서 앞자리 2개는 고정으로 구성되어 있었다. Cell Repair 는 B5000 ~ (B51F0 + F) 범위 사용 중 (아마도..)
// Scriber 의 2자리만 가지지 말고, 4자리 주소값을 유지한다.
string strAddress = "B" + address.ToString("X4"); // convert to Hex string (4자리)
var ioValue = new BITValue() {
Address = strAddress,
Description = desc,
// 메모리 주소 4자리를 유지하게 수정한다.
//hexAddress = strAddress.Substring(3, strAddress.Length-3), // 마지막 2자리만 보존
//hexAddress = strAddress.Substring(1, strAddress.Length - 1),
addrOffset = address - startAddress,
Use = true // 특정 Value를 사용하지 않을 경우 false로 설정 (DataGridRow의 IsEnabled와 연동)
};
// 저장
list.Add(ioValue);
}
// Bit Output
startAddress = _plc.BitOutputs.Count != 0 ? _plc.BitOutputs.OrderBy(x => x.address).First().address : 0;
foreach (var outB in _plc.BitOutputs) {
// 주소와 설명문으로 파싱
//(var address, var desc) = outB;
var address = outB.Item1;
string desc = null;
switch (curLanguage) {
case eLangType.English: {
desc = outB.Item2.Comment_EN;
break;
}
case eLangType.Korean: {
desc = outB.Item2.Comment_KR;
break;
}
}
desc?.Trim();
// list 는 클래스의 주소값을 가지고 있음
ObservableCollection<BITValue> list;
list = OutputMaps[0];
string strAddress = "B" + address.ToString("X4"); // convert to Hex string (4자리)
var ioValue = new BITValue() {
Address = strAddress,
Description = desc,
//hexAddress = strAddress.Substring(1, strAddress.Length - 1),
addrOffset = address - startAddress,
Use = true // 특정 Value를 사용하지 않을 경우 false로 설정 (DataGridRow의 IsEnabled와 연동)
};
// 저장
list.Add(ioValue);
}
}
}
private void OnMonitoring(object sender, EventArgs e)
{
if (!_plc.IsActivated) {
return;
}
// 뷰 상태 모니터링 : Auto 모드, Write 불가
UpdateWriteState();
// Read Input Bit Data
//var i = 0;
foreach (var ioValue in DigitalInputs) {
//var memAddress = Int32.Parse(ioValue.hexAddress, System.Globalization.NumberStyles.HexNumber);
if (_deviceName == ePLCDevice.M) {
ioValue.InPutStatus = _plc.ReadBit(ioValue.addrOffset, ePLCDevice.M).data;
} else {
ioValue.InPutStatus = _plc.ReadBit(ioValue.addrOffset).data;
}
//ioValue.Use = true;
#if DEBUG
// Data check
//Debug.WriteLine($"Input Bit : index {i}: {ioValue.InPutStatus}");
//i++;
#endif
}
// Read Output Bit data
foreach (var ioValue in DigitalOutputs) {
//int memAddress = Int32.Parse(ioValue.hexAddress, System.Globalization.NumberStyles.HexNumber);
if (_deviceName == ePLCDevice.D) {
ioValue.OutPutStatus = _plc.ReadBit(ioValue.addrOffset, ePLCDevice.D, isInput: false).data;
} else {
ioValue.OutPutStatus = _plc.ReadBit(ioValue.addrOffset, isInput: false).data;
}
//ioValue.Use = true;
}
}
private void ChangeBitValue(object obj)
{
if (!_plc.IsActivated) {
return;
}
/*
// 사용자 권한에 따라서, 아예 해당 버튼을 disable 하는 방식으로 권한 검사를 처리함
// 방어용으로 이중 처리 (삭제 가능함)
var curPermissionLevel = ApplicationExt.PermissionLevel;
if (curPermissionLevel > (ushort)Aurora.Api.PermissionLevel.LEVEL3) { // 권한 부족
return;
}
// PLC 가 Auto 모드이면 Write 불가
if (PLC.CheckProcessAutoMode()) {
return;
}
*/
// 시뮬레이션 모드인 경우에는 Input & Output 모두 변경이 가능해야 한다.
// 리얼 모드인 경우는 기존처럼 Output 만 변경이 가능해야 함
if (SelectedBitItem == null) {
return;
}
// 비고. 리얼 모드에서는 입력의 수정 불가: 리얼모드 & Input 은 무시한다...
if (_plc.IsSimul == false && IsThisInput) {
return;
}
// Hex 메모리 주소문자를 Hex 정수로 변환 ?
// hexAddress 가 2자리 수밖에 없음. 원본 메모리 주소(4자리)를 가지도록 수정 필요
//var memAddress = Int32.Parse(SelectedBitItem.hexAddress, System.Globalization.NumberStyles.HexNumber);
// 시뮬 모드에서 Input 메모리를 선택한건지 검사한다.
if (_deviceName == ePLCDevice.M) {
if (_plc.IsSimul && IsThisInput) { // 시뮬 & 입력부분
_plc.WriteBit(SelectedBitItem.addrOffset, !SelectedBitItem.InPutStatus, ePLCDevice.M, isOutput: false);
} else {
_plc.WriteBit(SelectedBitItem.addrOffset, !SelectedBitItem.OutPutStatus, ePLCDevice.M);
}
} else {
if (_plc.IsSimul && IsThisInput) { // 시뮬 & 입력부분
_plc.WriteBit(SelectedBitItem.addrOffset, !SelectedBitItem.InPutStatus, isOutput: false);
} else {
_plc.WriteBit(SelectedBitItem.addrOffset, !SelectedBitItem.OutPutStatus);
}
}
}
}
}