choieigun 5b4a856a2e AutoRun Simulation 추가
- AutoRun은 수정 안함(들여쓰기 변경)
- Timer 기반 시뮬레이션
2026-02-21 02:34:10 +09:00

200 lines
6.9 KiB
C#

using Aurora;
using Aurora.Api;
using AuroraPLC;
using Hermes;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WeldingCommon.Devices;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Rebar;
namespace ILSOS_CCW_H52A.Device
{
internal partial class PLCControllerCCWH52A : PLCController
{
public PLCControllerCCWH52A(string deviceId, ePLCNetType plcNetType, PLCMMapFileType fileType, IAuroraEnvironment aurora) : base(deviceId, plcNetType, fileType, aurora)
{
}
protected override int DoActivate(IDeviceConfig config)
{
var ret = base.DoActivate(config);
if (ret == E1.OK && IsSimul) {
//WriteBit((int)eBitInput.PLC_AUTO_STATUS, true, ePLCDevice.B, false);
//WriteBit((int)eBitInput.PLC_AUTO_REQ, true, ePLCDevice.B, false);
SetMonitoringService(EquipmentSimulator, 1);
}
return ret;
}
protected override int DoDeactivate()
{
StopMonitoringService();
var res = base.DoDeactivate();
if (res != E1.OK) {
Log.E("Deactivate is failed");
return res;
}
return res;
}
// -----------------------------
// Simulation state
// -----------------------------
private const int PLC_CURRENT_POS_1_ADDR = 4; // 기존 (mod 0~360)
private readonly Random _rand = new Random();
private readonly Stopwatch _sw = Stopwatch.StartNew();
private long _lastMs = -1;
private const int RotaryJigCount = 16;
private const double JigPerMsec = 300;
private const double DegreePerRev = 360.0;
private const double OneCycleMsec = JigPerMsec * RotaryJigCount;
private const double DegreePerMsec = DegreePerRev / OneCycleMsec;
private const double DegreePerJig = DegreePerRev / RotaryJigCount; // 22.5
private const double HalfDegreePerJig = DegreePerJig / 2.0; // 11.25
// PLC_CURRENT_POS_1 = degree * 100000
private const double PlcAngleScale = 100_000.0;
// 시퀀스가 "다음 지그"를 미리 읽으므로 안전하게 2칸 선행(권장)
private const int LeadSectors = 2;
// Motor position
private readonly object _absLock = new object();
public double GetSimAbsDeg()
{
long value = System.Threading.Interlocked.Read(ref _angleAbsDeg);
return value / 100000.0;
}
private long _angleAbsDeg = 0; // 누적 각도(계속 증가)
private double _angleModDeg = 0.0; // 0~360 랩핑 각도
// PLC USE_WELD_JIG bits
private readonly bool[] _canExist = new bool[RotaryJigCount];
private readonly bool[] _decided = new bool[RotaryJigCount]; // 이번 회전에서 해당 섹터가 결정됐는지
private int _prevSector = -1;
private static void FillBool(bool[] arr, bool value)
{
if (arr == null)
return;
for (int i = 0; i < arr.Length; i++)
arr[i] = value;
}
private static int Mod(int x, int m)
{
int r = x % m;
return (r < 0) ? (r + m) : r;
}
private int GetSectorFromAngle(double angleDeg)
{
// 시퀀스의 (angle + half)/per와 동일한 기준으로 섹터 결정
return (int)((angleDeg + HalfDegreePerJig) / DegreePerJig) % RotaryJigCount;
}
private void OnSectorChanged(int prev, int curr)
{
// 첫 진입
if (prev < 0) {
// 처음 시작 시에는 전부 OFF 상태에서 시작
FillBool(_canExist, false);
FillBool(_decided, false);
} else {
// 랩(15 -> 0) 감지: 새 바퀴 시작으로 보고 "지나간 영역 OFF"를 강하게 반영
if (prev == RotaryJigCount - 1 && curr == 0) {
FillBool(_canExist, false);
FillBool(_decided, false);
} else {
// 지나간 섹터(prev)는 OFF 처리
_canExist[prev] = false;
_decided[prev] = false; // 다음 회전에서 다시 결정되도록
}
}
// 시퀀스는 "다음 지그"를 읽으므로 curr+1부터 선행 결정
for (int k = 1; k <= LeadSectors; k++) {
int s = Mod(curr + k, RotaryJigCount);
if (!_decided[s]) {
// 7/8 확률 ON (0~7 중 0만 OFF로 만들려면 >0)
bool isCanExist = true;// (_rand.Next(0, 8) > 0);
_canExist[s] = isCanExist;
_decided[s] = true;
}
}
// 현재 섹터(curr)도 켜고 싶다면 여기서 결정 가능
}
private void EquipmentSimulator()
{
if (!ReadBit((int)eBitOutput.PC_AUTO_STATUS, ePLCDevice.B, false).data)
return;
long now = _sw.ElapsedMilliseconds;
if (_lastMs < 0)
_lastMs = now;
long dtMs = now - _lastMs;
if (dtMs < 0)
dtMs = 0;
if (dtMs > 100)
dtMs = 100;
_lastMs = now;
// abs 누적각 (시뮬 전용)
long deltaScaled = (long)Math.Round(DegreePerMsec * dtMs * 100000.0);
System.Threading.Interlocked.Add(ref _angleAbsDeg, deltaScaled);
double absDeg = GetSimAbsDeg();
_angleModDeg = absDeg % DegreePerRev;
if (_angleModDeg < 0)
_angleModDeg += DegreePerRev;
// mod 각도
int plcAngleMod = (int)Math.Round(_angleModDeg * PlcAngleScale);
WriteDWord(PLC_CURRENT_POS_1_ADDR, plcAngleMod, ePLCDevice.W, false);
// 섹터 변경/비트 처리 로직은 기존 그대로
int sectorNow = GetSectorFromAngle(_angleModDeg);
if (sectorNow != _prevSector) {
OnSectorChanged(_prevSector, sectorNow);
_prevSector = sectorNow;
}
WriteBitBlock((int)eBitInput.PLC_USE_WELD_JIG_1, RotaryJigCount, _canExist, ePLCDevice.B, false);
}
public void ResetSimulator()
{
_lastMs = -1;
System.Threading.Interlocked.Exchange(ref _angleAbsDeg, 0);
_angleModDeg = 0.0;
FillBool(_canExist, false);
FillBool(_decided, false);
_prevSector = -1;
// 각도 초기화 반영
WriteDWord(PLC_CURRENT_POS_1_ADDR, 0, ePLCDevice.W, false);
// 지그 비트 초기화 반영
WriteBitBlock((int)eBitInput.PLC_USE_WELD_JIG_1, RotaryJigCount, _canExist, ePLCDevice.B, false);
}
}
}