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 DigitalInputs { get; set; } = new ObservableCollection(); public ObservableCollection DigitalOutputs { get; set; } = new ObservableCollection(); public ObservableCollection DigitalInputsTest { get; set; } = new ObservableCollection(); public ObservableCollection DigitalOutputsTest { get; set; } = new ObservableCollection(); public ObservableCollection 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 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> InputMaps = new Dictionary>(); public Dictionary> OutputMaps = new Dictionary>(); 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 input = new ObservableCollection(); ObservableCollection output = new ObservableCollection(); 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 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 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 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 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); } } } } }