NohDoyeon241104 c7f12b0bda feat: Recipe/Page 핸드셰이크 에코 수렴 루프 적용
PLC가 PC 에코 넘버(PC_PAGE_NO/PC_RECIPE_NO) 일치를 상시 확인하는 것으로 확정(260806)됨에 따라, PC 책임을 검증에서 '정확히 읽고 → 내부 상태 갱신 → 에코'로 전환.

- CheckRecipeNoComp/CheckPageNoComp를 수렴 루프로 재작성 (에코 후 되읽어 일치 확인, 어긋나면 재읽기, 맞는 즉시 다음 노드로)
- 에코 없이 return 하던 경로 제거 (RecipeNo=0/미등록 → 에코 안 하고 재시도)
- 범위 밖 PageNo: 3초 무한대기 → 200ms 재읽기, 에코 안 함
- Page 3회 수집(600ms)·Recipe 800ms 재폴링 제거 → 정상 흐름 지연 0
- while 탈출 후 context.Stopped 가드 추가
- ReadOutWord(eWordOutput) 헬퍼 추가 (ReadOutBit 패턴 준용)
- 시뮬레이션 가드: IsSimul이면 수렴 생략 (PLC 워드가 항상 0이라 못 빠져나감)

구버전은 #region 260806 으로 전량 주석 보존 — 원복 시 블록 교체.
분석: docs/분석/2026/08/06/
2026-08-06 11:41:58 +09:00

147 lines
7.3 KiB
C#

using Hermes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WeldingCommon.Devices;
namespace ILSOS_PrismaticRD_PTOP.Device
{
partial class PLCControllerPTOP
{
public (int err, bool data) ReadBit(eBitInput address) => ReadBit((ushort)address, ePLCDevice.M);
public (int err, bool data) ReadOutBit(eBitOutput address) => ReadBit((ushort)address, ePLCDevice.M, isInput: false);
public int WriteBit(eBitOutput addrOffset, bool onOff, ePLCDevice device = ePLCDevice.M, bool isOutput = true) => base.WriteBit((int)addrOffset, onOff, ePLCDevice.M, isOutput);
public (int err, short data) ReadWord(eWordInput address) => base.ReadWord((ushort)address, ePLCDevice.D);
public int WriteWord(eWordOutput addrOffset, short data, ePLCDevice device = ePLCDevice.D, bool isOutput = true) => base.WriteWord((int)addrOffset, data, ePLCDevice.D, isOutput);
#region 260806 신규 — 출력 워드 되읽기 (원복: 이 region 통째로 삭제)
// 비트에는 ReadOutBit(위)이 있는데 워드에는 대응 헬퍼가 없어 base.ReadWord(..., isInput:false)를
// 직접 불러야 했다(WriteWordToBit 참고). 에코 워드(PC_PAGE_NO/PC_RECIPE_NO)가 실제로
// 반영됐는지 확인하는 용도.
public (int err, short data) ReadOutWord(eWordOutput address) => base.ReadWord((ushort)address, ePLCDevice.D, isInput: false);
#endregion
public (int err, int data) ReadDWord(eWordInput address) => base.ReadDWord((ushort)address, ePLCDevice.D);
public int WriteDWord(eWordOutput addrOffset, int data, ePLCDevice device = ePLCDevice.D, bool isOutput = true) => base.WriteDWord((int)addrOffset, data, ePLCDevice.D, isOutput);
public (int err, short[] datas) ReadWords(eWordInput startAddress, eWordInput endAddress) => base.ReadWordBlock((int)startAddress, endAddress - startAddress + 1, ePLCDevice.D);
public int WriteWords(eWordOutput startAddress, eWordOutput endAddress, short[] data, ePLCDevice device = ePLCDevice.D, bool isOutput = true) => base.WriteWordBlock((int)startAddress, endAddress - startAddress, data, ePLCDevice.D, isOutput);
#region Word Bitmasking >> 워드 영역을 비트마스킹으로 사용하는 경우
public int WriteWordToBit((eWordOutput addrOffset, int bitNo) address, bool on)
{
var value = base.ReadWord((int)address.addrOffset, ePLCDevice.D, false);
short current = value.data;
if (on) {
current |= (short)(1 << address.bitNo);
} else {
current &= (short)~(1 << address.bitNo);
}
return base.WriteWord((int)address.addrOffset, current, ePLCDevice.D);
}
public bool ReadWordToBit((eWordInput addrOffset, int bitNo) address)
{
var value = base.ReadWord((int)address.addrOffset, ePLCDevice.D);
short current = value.data;
return (current & (1 << address.bitNo)) != 0;
}
#endregion
public double ReadDWordByDivision(eWordInput input)
{
var result = ReadDWord((int)input);
if (result.err != E1.OK) {
// 로그 에러 넣기
//Log.E("Error While Reading Double Word Division {0}", result.err.ToString());
}
int divisionIndex = ConvertTo2WordDivisionIndex(input);
return (double)(result.data / divisionIndex);
}
public int ConvertTo2WordDivisionIndex(eWordInput input)
{
var id = input.ToString();
if (id.Contains("RAMPING_START_POWER") || id.Contains("RAMPING_END_POWER") || id.Contains("NUM") || id.Contains("TIME"))
return 1;
else if (id.Contains("SPEED") || id.Contains("POWER_CORE") || id.Contains("POWER_RING"))
return 10;
//else if (id.Contains("POWER_CORE") || id.Contains("POWER_RING"))
// return 100;
else// if (id.Contains("DIST"))
return 1000;
//if (id == "PLC_HOR_WELDING_RECIPE_NUM1" ||
// id == "PLC_VER_WELDING_MAIN_POWER_CORE1" ||
// id == "PLC_VER_WELDING_MAIN_POWER_RING1" ||
// id == "PLC_VER_WELDING_MAIN_RAMPING_START_POWER1" ||
// id == "PLC_VER_WELDING_MAIN_RAMPING_END_POWER1" ||
// id == "PLC_VER_WELDING_SUB_POWER_CORE1" ||
// id == "PLC_VER_WELDING_SUB_POWER_RING1" ||
// id == "PLC_VER_WELDING_RECIPE_NUM1" ||
// id == "PLC_HOR_WELDING_MAIN_POWER_CORE1" ||
// id == "PLC_HOR_WELDING_MAIN_POWER_RING1" ||
// id == "PC_HOR_WELDING_RECIPE_NUM1" ||
// id == "PC_HOR_WELDING_MAIN_POWER_CORE1" ||
// id == "PC_HOR_WELDING_MAIN_POWER_RING1" ||
// id == "PC_VER_WELDING_RECIPE_NUM1" ||
// id == "PC_VER_WELDING_MAIN_POWER_CORE1" ||
// id == "PC_VER_WELDING_MAIN_POWER_RING1" ||
// id == "PC_VER_WELDING_SUB_POWER_CORE1" ||
// id == "PC_VER_WELDING_SUB_POWER_RING1" ||
// id == "PC_VER_WELDING_MAIN_RAMPING_START_POWER1"||
// id == "PC_VER_WELDING_MAIN_RAMPING_END_POWER1"
// )
// return true;
//else
// return false;
}
public int ConvertTo2WordDivisionIndex(eWordOutput input)
{
var id = input.ToString();
if (id.Contains("RAMPING_START_POWER") || id.Contains("RAMPING_END_POWER") || id.Contains("NUM") || id.Contains("TIME"))
return 1;
else if (id.Contains("SPEED") || id.Contains("POWER_CORE") || id.Contains("POWER_RING"))
return 10;
//else if (id.Contains("POWER_CORE") || id.Contains("POWER_RING"))
// return 100;
else// if (id.Contains("DIST"))
return 1000;
//if (id == "PLC_HOR_WELDING_RECIPE_NUM1" ||
// id == "PLC_VER_WELDING_MAIN_POWER_CORE1" ||
// id == "PLC_VER_WELDING_MAIN_POWER_RING1" ||
// id == "PLC_VER_WELDING_MAIN_RAMPING_START_POWER1" ||
// id == "PLC_VER_WELDING_MAIN_RAMPING_END_POWER1" ||
// id == "PLC_VER_WELDING_SUB_POWER_CORE1" ||
// id == "PLC_VER_WELDING_SUB_POWER_RING1" ||
// id == "PLC_VER_WELDING_RECIPE_NUM1" ||
// id == "PLC_HOR_WELDING_MAIN_POWER_CORE1" ||
// id == "PLC_HOR_WELDING_MAIN_POWER_RING1" ||
// id == "PC_HOR_WELDING_RECIPE_NUM1" ||
// id == "PC_HOR_WELDING_MAIN_POWER_CORE1" ||
// id == "PC_HOR_WELDING_MAIN_POWER_RING1" ||
// id == "PC_VER_WELDING_RECIPE_NUM1" ||
// id == "PC_VER_WELDING_MAIN_POWER_CORE1" ||
// id == "PC_VER_WELDING_MAIN_POWER_RING1" ||
// id == "PC_VER_WELDING_SUB_POWER_CORE1" ||
// id == "PC_VER_WELDING_SUB_POWER_RING1" ||
// id == "PC_VER_WELDING_MAIN_RAMPING_START_POWER1"||
// id == "PC_VER_WELDING_MAIN_RAMPING_END_POWER1"
// )
// return true;
//else
// return false;
}
}
}