Phase 06 complete: - Namespace renamed in 62 .cs files (namespace + using directives) - AssemblyName + RootNamespace updated in csproj files - FanucProgramManager.csproj → NcProgramManager.csproj - FanucProgramManager.sln → NcProgramManager.sln - FanucProgramManager.Tests/ → NcProgramManager.Tests/ - FANUCMachine.cs deleted (dead code, zero references) Fanuc-prefixed class names kept (FanucMachine, FanucProgram, IFanucMachine) — manufacturer identifiers, not project names. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
739 lines
28 KiB
C#
Executable file
739 lines
28 KiB
C#
Executable file
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using NcProgramManager;
|
|
using NcProgramManager.Cnc;
|
|
using NcProgramManager.Cnc.Models;
|
|
|
|
namespace NcProgramManager.Cnc.Fanuc
|
|
{
|
|
public sealed class FanucMachine : IFanucMachine
|
|
{
|
|
private const int AlarmSlots = 17;
|
|
private const int OperatorMsgSlots = 17;
|
|
private const int TransferChunkSize = 1024;
|
|
private const int UploadBufferSize = 10240;
|
|
private const int Param_PiecesCount = 6711;
|
|
private const int Param_CycleTimeMs = 6757;
|
|
private const int Param_CycleTimeMin = 6758;
|
|
private const string DefaultDownloadFolder = "//CNC_MEM/USER/PATH1/";
|
|
private const string SeedProgramName = "OSTD";
|
|
private const string SeedProgramBody = "%\n<OSTD>\n%";
|
|
|
|
private readonly FanucConnectionConfig _config;
|
|
private readonly INcProgramValidator _validator;
|
|
private readonly List<CncError> _currentErrors = new List<CncError>();
|
|
private readonly SemaphoreSlim _gate = new SemaphoreSlim(1, 1);
|
|
|
|
private ushort _hndl;
|
|
private FanucCapabilities _caps;
|
|
private IFocasDialect _dialect;
|
|
private ConnectionState _state = ConnectionState.Disconnected;
|
|
|
|
public FanucMachine(FanucConnectionConfig config)
|
|
{
|
|
if (config == null) throw new ArgumentNullException("config");
|
|
_config = config;
|
|
_validator = new NcProgramValidator(CncManufacturer.Fanuc);
|
|
}
|
|
|
|
internal FanucMachine(FanucConnectionConfig config, INcProgramValidator validator)
|
|
{
|
|
if (config == null) throw new ArgumentNullException("config");
|
|
_config = config;
|
|
_validator = validator ?? new NcProgramValidator(CncManufacturer.Fanuc);
|
|
}
|
|
|
|
public string Name { get { return _config.Name; } }
|
|
public ConnectionState State { get { return _state; } }
|
|
public FanucCapabilities Capabilities { get { return _caps; } }
|
|
|
|
public event EventHandler<ConnectionState> StateChanged;
|
|
|
|
public Task<CncResult<bool>> ConnectAsync(CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return RunGuardedAsync(() => ConnectCore(ct), ct);
|
|
}
|
|
|
|
public Task DisconnectAsync()
|
|
{
|
|
return RunGuardedAsync<bool>(() => { DisconnectCore(); return true; }, CancellationToken.None);
|
|
}
|
|
|
|
public Task<CncResult<MachineSnapshot>> ReadAsync(CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return RunGuardedAsync(() => ReadCore(ct), ct);
|
|
}
|
|
|
|
public Task<CncResult<string>> ReadActiveProgramAsync(CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return RunGuardedAsync(() => ReadActiveProgramCore(ct), ct);
|
|
}
|
|
|
|
public Task<CncResult<string>> ReadProgramAsync(string path, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return RunGuardedAsync(() => ReadProgramCore(path, ct), ct);
|
|
}
|
|
|
|
public Task<CncResult<bool>> WriteProgramAsync(string path, CncProgram program, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return RunGuardedAsync(() => WriteProgramCore(path, program, ct), ct);
|
|
}
|
|
|
|
public Task<CncResult<bool>> SelectMainProgramAsync(string path, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return RunGuardedAsync(() => SelectMainProgramCore(path), ct);
|
|
}
|
|
|
|
public Task<CncResult<bool>> DeleteProgramAsync(string path, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return RunGuardedAsync(() => DeleteProgramCore(path), ct);
|
|
}
|
|
|
|
public Task<CncResult<string>> ReadToolDataAsync(string path, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return RunGuardedAsync(() => UploadFocasFile(1, path, ct), ct);
|
|
}
|
|
|
|
public Task<CncResult<string>> ReadWorkZeroDataAsync(string path, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return RunGuardedAsync(() => UploadFocasFile(5, path, ct), ct);
|
|
}
|
|
|
|
public Task<CncResult<bool>> WriteToolDataAsync(string folderPath, string content, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return RunGuardedAsync(() => DownloadFocasFile(1, folderPath, content, ct), ct);
|
|
}
|
|
|
|
public Task<CncResult<bool>> WriteWorkZeroDataAsync(string folderPath, string content, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
return RunGuardedAsync(() => DownloadFocasFile(5, folderPath, content, ct), ct);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
try { DisconnectAsync().GetAwaiter().GetResult(); } catch { }
|
|
try { _gate.Dispose(); } catch { }
|
|
}
|
|
|
|
private async Task<CncResult<T>> RunGuardedAsync<T>(Func<T> body, CancellationToken ct)
|
|
{
|
|
await _gate.WaitAsync(ct).ConfigureAwait(false);
|
|
try
|
|
{
|
|
return await Task.Run(() =>
|
|
{
|
|
_currentErrors.Clear();
|
|
try
|
|
{
|
|
T value = body();
|
|
if (_currentErrors.Count > 0)
|
|
return CncResult<T>.Fail(_currentErrors.ToArray());
|
|
return CncResult<T>.Ok(value);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return CncResult<T>.Cancelled();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_currentErrors.Add(new CncError(0, "FanucMachine", ex.Message));
|
|
return CncResult<T>.Fail(_currentErrors.ToArray());
|
|
}
|
|
}, ct).ConfigureAwait(false);
|
|
}
|
|
finally
|
|
{
|
|
_gate.Release();
|
|
}
|
|
}
|
|
|
|
private bool ConnectCore(CancellationToken ct)
|
|
{
|
|
if (_state == ConnectionState.Connected) return true;
|
|
SetState(ConnectionState.Connecting);
|
|
ct.ThrowIfCancellationRequested();
|
|
|
|
short ret;
|
|
switch (_config.Transport)
|
|
{
|
|
case FanucTransport.Ethernet:
|
|
short timeoutSec = (short)Math.Max(1, (int)_config.ConnectTimeout.TotalSeconds);
|
|
ret = Focas1.cnc_allclibhndl3(_config.IpAddress, _config.Port, timeoutSec, out _hndl);
|
|
break;
|
|
case FanucTransport.Hssb:
|
|
ret = Focas1.cnc_allclibhndl2(_config.HssbNode, out _hndl);
|
|
break;
|
|
case FanucTransport.Library:
|
|
ret = Focas1.cnc_allclibhndl(out _hndl);
|
|
break;
|
|
default:
|
|
Record((short)Focas1.focas_ret.EW_ATTRIB, "Connect");
|
|
SetState(ConnectionState.Faulted);
|
|
return false;
|
|
}
|
|
|
|
if (ret != Focas1.EW_OK)
|
|
{
|
|
Record(ret, "cnc_allclibhndl");
|
|
SetState(ConnectionState.Faulted);
|
|
return false;
|
|
}
|
|
|
|
if (ct.IsCancellationRequested)
|
|
{
|
|
try { Focas1.cnc_freelibhndl(_hndl); } catch { }
|
|
SetState(ConnectionState.Disconnected);
|
|
ct.ThrowIfCancellationRequested();
|
|
}
|
|
|
|
_caps = FanucDialectDetector.Detect(_hndl);
|
|
_dialect = FocasDialectFactory.Create(_caps);
|
|
if (_caps.Dialect == FanucDialect.Unknown)
|
|
_currentErrors.Add(new CncError(0, "cnc_sysinfo", "Unknown CNC model, falling back to Legacy dialect"));
|
|
|
|
SetState(ConnectionState.Connected);
|
|
return true;
|
|
}
|
|
|
|
private void DisconnectCore()
|
|
{
|
|
if (_state == ConnectionState.Disconnected) return;
|
|
short ret = Focas1.cnc_freelibhndl(_hndl);
|
|
if (ret != Focas1.EW_OK)
|
|
Record(ret, "cnc_freelibhndl");
|
|
SetState(ConnectionState.Disconnected);
|
|
}
|
|
|
|
private MachineSnapshot ReadCore(CancellationToken ct)
|
|
{
|
|
EnsureConnected();
|
|
ct.ThrowIfCancellationRequested();
|
|
|
|
Focas1.cnc_setpath(_hndl, 1);
|
|
|
|
var snap = new MachineSnapshot
|
|
{
|
|
CapturedAt = DateTime.Now,
|
|
Alarms = new List<string>(),
|
|
OperatorMessages = new List<string>(),
|
|
Mode = MachineMode.Unknown,
|
|
Run = RunState.Unknown,
|
|
Motion = MotionState.Unknown
|
|
};
|
|
|
|
snap.CycleTime = ReadCycleTime();
|
|
ct.ThrowIfCancellationRequested();
|
|
snap.PieceCount = ReadPieceCount();
|
|
ct.ThrowIfCancellationRequested();
|
|
snap.Alarms = ReadAlarms();
|
|
ct.ThrowIfCancellationRequested();
|
|
snap.OperatorMessages = ReadOperatorMessages();
|
|
ct.ThrowIfCancellationRequested();
|
|
|
|
var stateInfo = ReadMachineState();
|
|
snap.Mode = stateInfo.Mode;
|
|
snap.Run = stateInfo.Run;
|
|
snap.Motion = stateInfo.Motion;
|
|
snap.Emergency = stateInfo.Emergency;
|
|
snap.AlarmActive = stateInfo.Alarm;
|
|
|
|
if (snap.Mode != MachineMode.Edit)
|
|
{
|
|
ct.ThrowIfCancellationRequested();
|
|
var prog = ReadProgramInfo();
|
|
snap.MainProgram = prog.MainProgram;
|
|
snap.SubProgram = prog.SubProgram;
|
|
snap.ProgramComment = prog.Comment;
|
|
}
|
|
|
|
return snap;
|
|
}
|
|
|
|
private string ReadActiveProgramCore(CancellationToken ct)
|
|
{
|
|
EnsureConnected();
|
|
short ret;
|
|
string identifier = _dialect.ReadMainProgramIdentifier(_hndl, out ret);
|
|
if (ret != Focas1.EW_OK)
|
|
{
|
|
Record(ret, "ReadMainProgramIdentifier");
|
|
return null;
|
|
}
|
|
return UploadProgramByIdentifier(identifier, ct);
|
|
}
|
|
|
|
private string ReadProgramCore(string path, CancellationToken ct)
|
|
{
|
|
EnsureConnected();
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
_currentErrors.Add(new CncError(0, "ReadProgram", "Path is empty"));
|
|
return null;
|
|
}
|
|
return UploadProgramByIdentifier(path, ct);
|
|
}
|
|
|
|
private bool WriteProgramCore(string folderPath, CncProgram program, CancellationToken ct)
|
|
{
|
|
EnsureConnected();
|
|
if (program == null || string.IsNullOrEmpty(program.Content))
|
|
{
|
|
_currentErrors.Add(new CncError(0, "WriteProgram", "Program content is empty"));
|
|
return false;
|
|
}
|
|
var vr = _validator.Validate(program.Name ?? folderPath, program.Content);
|
|
if (!vr.Success)
|
|
{
|
|
string msg = string.Join("; ", vr.Errors
|
|
.Where(e => e.Severity == ValidationSeverity.Error)
|
|
.Select(e => e.Message));
|
|
_currentErrors.Add(new CncError(0, "Validator", msg));
|
|
return false;
|
|
}
|
|
if (!CanDownload()) return false;
|
|
if (string.IsNullOrEmpty(folderPath)) folderPath = DefaultDownloadFolder;
|
|
|
|
string fileName = ExtractFileNameFromContent(program);
|
|
if (string.IsNullOrEmpty(fileName))
|
|
{
|
|
_currentErrors.Add(new CncError(0, "WriteProgram", "Cannot extract program name from content"));
|
|
return false;
|
|
}
|
|
|
|
EnsureSeedProgram(folderPath, ct);
|
|
ct.ThrowIfCancellationRequested();
|
|
|
|
if (_dialect.SupportsPathBasedPrograms)
|
|
Focas1.cnc_pdf_del(_hndl, folderPath + fileName);
|
|
|
|
short ret = _dialect.DownloadStart(_hndl, folderPath);
|
|
if (ret != Focas1.EW_OK)
|
|
{
|
|
Record(ret, "DownloadStart");
|
|
_dialect.DownloadEnd(_hndl);
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (!TransferChunked(program.Content, ct)) return false;
|
|
}
|
|
finally
|
|
{
|
|
_dialect.DownloadEnd(_hndl);
|
|
}
|
|
|
|
if (_dialect.SupportsPathBasedPrograms)
|
|
{
|
|
ret = Focas1.cnc_pdf_slctmain(_hndl, folderPath + fileName);
|
|
if (ret != Focas1.EW_OK)
|
|
{
|
|
Record(ret, "cnc_pdf_slctmain");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private bool SelectMainProgramCore(string path)
|
|
{
|
|
EnsureConnected();
|
|
if (!_dialect.SupportsPathBasedPrograms)
|
|
{
|
|
_currentErrors.Add(new CncError((short)Focas1.focas_ret.EW_NOOPT,
|
|
"SelectMainProgram", "Path-based selection not supported on this CNC"));
|
|
return false;
|
|
}
|
|
short ret = Focas1.cnc_pdf_slctmain(_hndl, path);
|
|
if (ret != Focas1.EW_OK) { Record(ret, "cnc_pdf_slctmain"); return false; }
|
|
return true;
|
|
}
|
|
|
|
private bool DeleteProgramCore(string path)
|
|
{
|
|
EnsureConnected();
|
|
if (!_dialect.SupportsPathBasedPrograms)
|
|
{
|
|
_currentErrors.Add(new CncError((short)Focas1.focas_ret.EW_NOOPT,
|
|
"DeleteProgram", "Path-based delete not supported on this CNC"));
|
|
return false;
|
|
}
|
|
short ret = Focas1.cnc_pdf_del(_hndl, path);
|
|
if (ret != Focas1.EW_OK) { Record(ret, "cnc_pdf_del"); return false; }
|
|
return true;
|
|
}
|
|
|
|
private string UploadFocasFile(short fileType, string path, CancellationToken ct)
|
|
{
|
|
EnsureConnected();
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
_currentErrors.Add(new CncError(0, "UploadFocasFile", "Path is empty"));
|
|
return null;
|
|
}
|
|
long programNumber = TryParseProgramNumber(path);
|
|
short ret = _dialect.UploadStart(_hndl, path, programNumber, fileType);
|
|
if (ret != Focas1.EW_OK)
|
|
{
|
|
Record(ret, "UploadStart");
|
|
_dialect.UploadEnd(_hndl);
|
|
return null;
|
|
}
|
|
var output = new StringBuilder();
|
|
char[] buffer = new char[UploadBufferSize + 1];
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
ct.ThrowIfCancellationRequested();
|
|
int len = UploadBufferSize;
|
|
short r = _dialect.UploadData(_hndl, ref len, buffer);
|
|
if (r == (short)Focas1.focas_ret.EW_BUFFER)
|
|
{
|
|
if (ct.WaitHandle.WaitOne(100)) ct.ThrowIfCancellationRequested();
|
|
continue;
|
|
}
|
|
if (r == Focas1.EW_OK && len > 0)
|
|
{
|
|
output.Append(buffer, 0, len);
|
|
if (buffer[len - 1] == '%') break;
|
|
}
|
|
else
|
|
{
|
|
if (r != Focas1.EW_OK) Record(r, "UploadData");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
finally { _dialect.UploadEnd(_hndl); }
|
|
return output.ToString();
|
|
}
|
|
|
|
private bool DownloadFocasFile(short fileType, string folderPath, string content, CancellationToken ct)
|
|
{
|
|
EnsureConnected();
|
|
if (string.IsNullOrEmpty(content))
|
|
{
|
|
_currentErrors.Add(new CncError(0, "DownloadFocasFile", "Content is empty"));
|
|
return false;
|
|
}
|
|
if (string.IsNullOrEmpty(folderPath)) folderPath = DefaultDownloadFolder;
|
|
short ret = _dialect.DownloadStart(_hndl, folderPath, fileType);
|
|
if (ret != Focas1.EW_OK)
|
|
{
|
|
Record(ret, "DownloadStart");
|
|
_dialect.DownloadEnd(_hndl);
|
|
return false;
|
|
}
|
|
try { return TransferChunked(content, ct); }
|
|
finally { _dialect.DownloadEnd(_hndl); }
|
|
}
|
|
|
|
private TimeSpan ReadCycleTime()
|
|
{
|
|
var ms = new Focas1.IODBPSD();
|
|
var min = new Focas1.IODBPSD();
|
|
short r1 = Focas1.cnc_rdparam(_hndl, Param_CycleTimeMs, 0, 8, ms);
|
|
short r2 = Focas1.cnc_rdparam(_hndl, Param_CycleTimeMin, 0, 8, min);
|
|
if (r1 != Focas1.EW_OK) Record(r1, "cnc_rdparam(cycle ms)");
|
|
if (r2 != Focas1.EW_OK) Record(r2, "cnc_rdparam(cycle min)");
|
|
return TimeSpan.FromMinutes(min.u.ldata) + TimeSpan.FromMilliseconds(ms.u.ldata);
|
|
}
|
|
|
|
private long ReadPieceCount()
|
|
{
|
|
try
|
|
{
|
|
if (_config.PieceCounterMacro == -1)
|
|
{
|
|
var p = new Focas1.IODBPSD();
|
|
short ret = Focas1.cnc_rdparam(_hndl, Param_PiecesCount, 0, 8, p);
|
|
if (ret != Focas1.EW_OK) { Record(ret, "cnc_rdparam(pieces)"); return 0; }
|
|
return p.u.ldata;
|
|
}
|
|
var macro = new Focas1.ODBM();
|
|
short macRet = Focas1.cnc_rdmacro(_hndl, (short)_config.PieceCounterMacro, 10, macro);
|
|
if (macRet != Focas1.EW_OK) { Record(macRet, "cnc_rdmacro(pieces)"); return 0; }
|
|
long val = Math.Abs(macro.mcr_val);
|
|
if (macro.dec_val > 0) val = val / (long)Math.Pow(10, macro.dec_val);
|
|
return macro.mcr_val < 0 ? -val : val;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_currentErrors.Add(new CncError(0, "ReadPieceCount", ex.Message));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private IReadOnlyList<string> ReadAlarms()
|
|
{
|
|
string[] buf = new string[AlarmSlots];
|
|
short num = AlarmSlots;
|
|
short ret = _dialect.ReadAlarmMessages(_hndl, ref num, buf);
|
|
if (ret != Focas1.EW_OK) Record(ret, "ReadAlarmMessages");
|
|
var list = new List<string>(num);
|
|
for (int i = 0; i < num; i++)
|
|
if (!string.IsNullOrEmpty(buf[i])) list.Add(buf[i]);
|
|
return list;
|
|
}
|
|
|
|
private IReadOnlyList<string> ReadOperatorMessages()
|
|
{
|
|
string[] buf = new string[OperatorMsgSlots];
|
|
short num = 1;
|
|
short ret = _dialect.ReadOperatorMessages(_hndl, ref num, buf);
|
|
if (ret != Focas1.EW_OK) Record(ret, "ReadOperatorMessages");
|
|
var list = new List<string>(num);
|
|
for (int i = 0; i < num; i++)
|
|
if (!string.IsNullOrEmpty(buf[i])) list.Add(buf[i]);
|
|
return list;
|
|
}
|
|
|
|
private struct StateInfo
|
|
{
|
|
public MachineMode Mode;
|
|
public RunState Run;
|
|
public MotionState Motion;
|
|
public bool Emergency;
|
|
public bool Alarm;
|
|
}
|
|
|
|
private StateInfo ReadMachineState()
|
|
{
|
|
var s = new StateInfo
|
|
{
|
|
Mode = MachineMode.Unknown,
|
|
Run = RunState.Unknown,
|
|
Motion = MotionState.Unknown
|
|
};
|
|
var odbst = new Focas1.ODBST();
|
|
short ret = Focas1.cnc_statinfo(_hndl, odbst);
|
|
if (ret != Focas1.EW_OK) { Record(ret, "cnc_statinfo"); return s; }
|
|
|
|
switch (odbst.aut)
|
|
{
|
|
case 0: s.Mode = MachineMode.Mdi; break;
|
|
case 1: s.Mode = MachineMode.Auto; break;
|
|
case 3: s.Mode = MachineMode.Edit; break;
|
|
case 4: s.Mode = MachineMode.Handle; break;
|
|
case 5: s.Mode = MachineMode.Jog; break;
|
|
}
|
|
switch (odbst.run)
|
|
{
|
|
case 1: s.Run = RunState.Stop; break;
|
|
case 3: s.Run = RunState.ExecutingIso; break;
|
|
}
|
|
switch (odbst.motion)
|
|
{
|
|
case 0: s.Motion = MotionState.Idle; break;
|
|
case 1: s.Motion = MotionState.Moving; break;
|
|
case 2: s.Motion = MotionState.Hold; break;
|
|
}
|
|
s.Emergency = odbst.emergency == 1;
|
|
s.Alarm = odbst.alarm == 1;
|
|
return s;
|
|
}
|
|
|
|
private struct ProgramInfo
|
|
{
|
|
public string MainProgram;
|
|
public string SubProgram;
|
|
public string Comment;
|
|
}
|
|
|
|
private ProgramInfo ReadProgramInfo()
|
|
{
|
|
var info = new ProgramInfo { MainProgram = "", SubProgram = "", Comment = "" };
|
|
|
|
var prog = new Focas1.ODBPRO();
|
|
short ret = Focas1.cnc_rdprgnum(_hndl, prog);
|
|
if (ret != Focas1.EW_OK) { Record(ret, "cnc_rdprgnum"); return info; }
|
|
info.SubProgram = "O" + prog.data.ToString();
|
|
info.MainProgram = "O" + prog.mdata.ToString();
|
|
|
|
if (_dialect.SupportsPathBasedPrograms)
|
|
{
|
|
byte[] mainPath = new byte[242];
|
|
short pathRet = Focas1.cnc_pdf_rdmain(_hndl, mainPath);
|
|
if (pathRet == Focas1.EW_OK)
|
|
{
|
|
string str = ASCIIEncoding.ASCII.GetString(mainPath).TrimEnd('\0');
|
|
string[] parts = str.Split('/');
|
|
if (parts.Length > 0) info.MainProgram = parts[parts.Length - 1];
|
|
}
|
|
}
|
|
|
|
byte[] commentBuf = new byte[100];
|
|
int charsRead;
|
|
short cret = _dialect.ReadActiveProgramComment(_hndl, commentBuf, out charsRead);
|
|
if (cret != Focas1.EW_OK) { Record(cret, "ReadActiveProgramComment"); return info; }
|
|
|
|
string raw = ASCIIEncoding.ASCII.GetString(commentBuf).TrimEnd('\0');
|
|
int open = raw.IndexOf('(');
|
|
int close = raw.IndexOf(')');
|
|
if (open >= 0 && close > open)
|
|
info.Comment = raw.Substring(open + 1, close - open - 1);
|
|
else
|
|
info.Comment = "";
|
|
return info;
|
|
}
|
|
|
|
private string UploadProgramByIdentifier(string identifier, CancellationToken ct)
|
|
{
|
|
long programNumber = TryParseProgramNumber(identifier);
|
|
short ret = _dialect.UploadStart(_hndl, identifier, programNumber);
|
|
if (ret != Focas1.EW_OK)
|
|
{
|
|
Record(ret, "UploadStart");
|
|
_dialect.UploadEnd(_hndl);
|
|
return null;
|
|
}
|
|
|
|
var output = new StringBuilder();
|
|
char[] buffer = new char[UploadBufferSize + 1];
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
ct.ThrowIfCancellationRequested();
|
|
int len = UploadBufferSize;
|
|
short r = _dialect.UploadData(_hndl, ref len, buffer);
|
|
if (r == (short)Focas1.focas_ret.EW_BUFFER)
|
|
{
|
|
if (ct.WaitHandle.WaitOne(100)) ct.ThrowIfCancellationRequested();
|
|
continue;
|
|
}
|
|
if (r == Focas1.EW_OK && len > 0)
|
|
{
|
|
output.Append(buffer, 0, len);
|
|
if (len > 0 && buffer[len - 1] == '%') break;
|
|
}
|
|
else
|
|
{
|
|
if (r != Focas1.EW_OK) Record(r, "UploadData");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_dialect.UploadEnd(_hndl);
|
|
}
|
|
return output.ToString();
|
|
}
|
|
|
|
private bool TransferChunked(string content, CancellationToken ct)
|
|
{
|
|
int len = content.Length;
|
|
int startPos = 0;
|
|
while (len > 0)
|
|
{
|
|
ct.ThrowIfCancellationRequested();
|
|
int num = len >= TransferChunkSize ? TransferChunkSize : len;
|
|
char[] chunk = new char[num];
|
|
content.CopyTo(startPos, chunk, 0, num);
|
|
|
|
short ret = _dialect.DownloadData(_hndl, ref num, chunk);
|
|
if (ret == (short)Focas1.focas_ret.EW_BUFFER)
|
|
{
|
|
if (ct.WaitHandle.WaitOne(100)) ct.ThrowIfCancellationRequested();
|
|
continue;
|
|
}
|
|
if (ret == Focas1.EW_OK)
|
|
{
|
|
startPos += num;
|
|
len -= num;
|
|
continue;
|
|
}
|
|
Record(ret, "DownloadData");
|
|
var dtl = new Focas1.ODBERR();
|
|
Focas1.cnc_getdtailerr(_hndl, dtl);
|
|
Debug.WriteLine("FOCAS detail err: " + dtl.err_no + " " + dtl.err_dtno);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private bool CanDownload()
|
|
{
|
|
var odbst = new Focas1.ODBST();
|
|
short ret = Focas1.cnc_statinfo(_hndl, odbst);
|
|
if (ret != Focas1.EW_OK) { Record(ret, "cnc_statinfo(canDownload)"); return false; }
|
|
if (odbst.aut != 0 && odbst.aut != 3)
|
|
{
|
|
Record((short)Focas1.focas_ret.EW_MODE, "CanDownload");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void EnsureSeedProgram(string folderPath, CancellationToken ct)
|
|
{
|
|
if (!_dialect.SupportsPathBasedPrograms) return;
|
|
short selRet = Focas1.cnc_pdf_slctmain(_hndl, folderPath + SeedProgramName);
|
|
if (selRet == Focas1.EW_OK) return;
|
|
|
|
Focas1.cnc_pdf_del(_hndl, folderPath + SeedProgramName);
|
|
short sStart = _dialect.DownloadStart(_hndl, folderPath);
|
|
if (sStart != Focas1.EW_OK) { _dialect.DownloadEnd(_hndl); return; }
|
|
TransferChunked(SeedProgramBody, ct);
|
|
_dialect.DownloadEnd(_hndl);
|
|
Focas1.cnc_pdf_slctmain(_hndl, folderPath + SeedProgramName);
|
|
}
|
|
|
|
private static string ExtractFileNameFromContent(CncProgram program)
|
|
{
|
|
if (!string.IsNullOrEmpty(program.Name)) return program.Name;
|
|
string content = program.Content ?? "";
|
|
int start = content.IndexOf('<');
|
|
int end = content.IndexOf('>');
|
|
if (start >= 0 && end > start) return content.Substring(start + 1, end - start - 1);
|
|
return null;
|
|
}
|
|
|
|
private static long TryParseProgramNumber(string identifier)
|
|
{
|
|
if (string.IsNullOrEmpty(identifier)) return 0;
|
|
string s = identifier;
|
|
int slash = s.LastIndexOf('/');
|
|
if (slash >= 0 && slash < s.Length - 1) s = s.Substring(slash + 1);
|
|
if (s.Length > 0 && (s[0] == 'O' || s[0] == 'o')) s = s.Substring(1);
|
|
long n;
|
|
return long.TryParse(s, out n) ? n : 0;
|
|
}
|
|
|
|
private void EnsureConnected()
|
|
{
|
|
if (_state != ConnectionState.Connected || _dialect == null)
|
|
throw new InvalidOperationException("FanucMachine is not connected.");
|
|
}
|
|
|
|
private void Record(short code, string source)
|
|
{
|
|
var err = FanucErrorTranslator.Translate(code, source);
|
|
_currentErrors.Add(err);
|
|
if (FanucErrorTranslator.IsDisconnectError(code))
|
|
{
|
|
SetState(ConnectionState.Faulted);
|
|
try { Focas1.cnc_freelibhndl(_hndl); } catch { }
|
|
SetState(ConnectionState.Disconnected);
|
|
}
|
|
}
|
|
|
|
private void SetState(ConnectionState next)
|
|
{
|
|
if (_state == next) return;
|
|
_state = next;
|
|
var h = StateChanged;
|
|
if (h != null)
|
|
{
|
|
try { h(this, next); } catch { }
|
|
}
|
|
}
|
|
}
|
|
}
|