| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687 | using Aitex.Core.RT.DataCenter;using Aitex.Core.RT.Device;using Aitex.Core.RT.Event;using Aitex.Core.RT.Log;using Aitex.Core.RT.OperationCenter;using Aitex.Core.RT.Routine;using Aitex.Core.RT.SCCore;using Aitex.Core.Util;using MECF.Framework.Common.Beckhoff.ModuleIO;using MECF.Framework.Common.CommonData.Loader;using MECF.Framework.Common.TwinCat;using MECF.Framework.Common.Utilities;using CyberX8_Core;using CyberX8_RT.Devices.AXIS.CANOpen;using System;using System.Collections.Generic;using System.Linq;using System.Reflection;using System.Text;using System.Threading.Tasks;using MECF.Framework.Common.IOCore;namespace CyberX8_RT.Devices.Loader{    public class LoaderSideDevice : BaseDevice, IDevice    {        /// <summary>        ///         /// </summary>        private enum LoaderSideOperation        {            None,            DoorOn,            DoorOff,            VacuumOn,            VacuumOff,            WhBladderOn,            WhBladderOff,            TransBladderOn,            TransBladderOff,            TransHighOn,            TransHighOff,            BernoulliBladderOn,            BernoulliBladderOff,            BernoulliN2On,            BernoulliN2Off,            Load,            Unload,            InstallCRS,            StartCycle,            StopCycle        }        #region 常量        private const string WAFER_PRESENT = "WaferPresent";        private const string DOOR_UNLOCK = "DoorUnlock";        private const string DOOR_UPPER_LOCKED = "DoorUpperLocked";        private const string DOOR_UPPER_UNLOCKED = "DoorUpperUnlocked";        private const string DOOR_LOWER_LOCKED = "DoorLowerLocked";        private const string DOOR_LOWER_UNLOCKED = "DoorLowerUnlocked";        private const string CRS_VACUUM="CRSVacuum";        private const string CRS_VACUUM_VALUE = "CRSVacuumValue";        private const string WH_BLADDER = "WHBladder";        private const string WH_BLADDER_PRESSURE="WHBladderPressure";        private const string TRANS_BLADDER="TransBladder";        private const string TRANS_HIGH="TransHigh";        private const string TRANS_RETRACTED="TransRetracted";        private const string TRANS_PRESSURE = "TransPressure";        private const string BERNOULLI_BLADDER= "BernoulliBladder";        private const string BERNOULLI_EXTENDED="BernoulliExtended";        private const string BERNOULLI_N2="BernoulliN2";        private const string BERNOULLI_N2_SECOND = "BernoulliN2Second";        private const string BERNOULLI_PRESSURE="BernoulliPressure";        private const string WAFER_HOLDER_LOCKED = "WaferHolderLocked";        private const string BERNOULLI_BLADDER_PRESSURE="BernoulliBladderPressure";        private const string SIDE_DATA = "SideData";        private const string CURRENT_OPERATION = "CurrentOperation";        #endregion        #region 内部变量        /// <summary>        /// 数据        /// </summary>        private LoaderSideData _siderData=new LoaderSideData();        /// <summary>        /// 状态        /// </summary>        private RState _status;        /// <summary>        /// 当前操作        /// </summary>        private LoaderSideOperation _currentOperation;        /// <summary>        /// 变量是否初始化字典        /// </summary>        private Dictionary<string, bool> _variableInitializeDic = new Dictionary<string, bool>();        /// <summary>        /// 当前次数        /// </summary>        private string _currentCycle = "";        #region routine        /// <summary>        /// door lock routine        /// </summary>        private LoaderSideDoorLockRoutine _doorlockRoutine;        /// <summary>        /// Vacuum Routine        /// </summary>        private LoaderSideVacuumRoutine _vacuumRoutine;        /// <summary>        /// WH Bladder Routine        /// </summary>        private LoaderSideWhBladderRoutine _whBladderRoutine;        /// <summary>        /// Bernoulli Bladder Routine        /// </summary>        private LoaderSideBernoulliBladderRoutine _bernoulliBladderRoutine;        /// <summary>        /// Load Routine        /// </summary>        private LoaderLoadRoutine _loadRoutine;        /// <summary>        /// Unload Routine        /// </summary>        private LoaderUnloadRoutine _unloadRoutine;        /// <summary>        /// Install Routine        /// </summary>        private LoaderInstallCRSRoutine _installCRSRoutine;        /// <summary>        /// Cycle Routine        /// </summary>        private LoaderSideCycleRoutine _cycleRoutine;        #endregion        #endregion        #region 属性        /// <summary>        /// 数据        /// </summary>        public LoaderSideData SideData        {            get { return _siderData; }            set { _siderData = value; }        }        /// <summary>        /// 状态        /// </summary>        public RState Status { get { return _status; } }        /// <summary>        /// 当前次数        /// </summary>        public String CurrentCycle { get { return _currentCycle; } }        /// <summary>        /// 所有io变量是否初始化        /// </summary>        public bool IOInitialized { get { return IOVariableDictionaryUtil.AllIoVariableInitialized(eEvent.ERR_LOADER,Module,_variableInitializeDic); } }        #endregion        /// <summary>        /// 构造函数        /// </summary>        /// <param name="moduleName"></param>        /// <param name="name"></param>        public LoaderSideDevice(string moduleName,string name):base(moduleName,name,name,name)        {        }        /// <summary>        /// 初始化        /// </summary>        /// <returns></returns>        public bool Initialize()        {            SubscribeData();            SubscribeValueAction();            InitializeRoutine();            InitializeOperation();            return true;        }        /// <summary>        /// 初始化Routine        /// </summary>        private void InitializeRoutine()        {            _doorlockRoutine = new LoaderSideDoorLockRoutine($"{Module}.{Name}");            _vacuumRoutine = new LoaderSideVacuumRoutine($"{Module}.{Name}");            _whBladderRoutine = new LoaderSideWhBladderRoutine($"{Module}.{Name}");            _bernoulliBladderRoutine = new LoaderSideBernoulliBladderRoutine($"{Module}.{Name}");            _loadRoutine = new LoaderLoadRoutine(Module,Name);            _unloadRoutine = new LoaderUnloadRoutine(Module,Name);            _installCRSRoutine = new LoaderInstallCRSRoutine(Module,Name);            _cycleRoutine = new LoaderSideCycleRoutine(Module, Name);        }        /// <summary>        /// 订阅数据        /// </summary>        private void SubscribeData()        {            DATA.Subscribe($"{Module}.{Name}.{SIDE_DATA}", () => _siderData,SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.CurrentCycle", () => _currentCycle, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.CurrentCycleStep", () =>             {                if(_currentOperation==LoaderSideOperation.StartCycle)                {                    return _cycleRoutine.CurrentStep;                }                else                {                    return "";                }            }, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.{CURRENT_OPERATION}", () => _currentOperation.ToString(), SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.CRSVacuumValue",()=>_siderData.CRSVacuumValue, SubscriptionAttribute.FLAG.IgnoreSaveDB);             DATA.Subscribe($"{Module}.{Name}.CRSVacuum", () => _siderData.CRSVacuum, SubscriptionAttribute.FLAG.IgnoreSaveDB);             DATA.Subscribe($"{Module}.{Name}.BernoulliPressure", () => _siderData.BernoulliPressure, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.BernoulliBladder", () => _siderData.BernoulliBladder, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.BernoulliBladderPressure", () => _siderData.BernoulliBladderPressure, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.BernoulliExtended", () => _siderData.BernoulliExtended, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.BernoulliN2", () => _siderData.BernoulliN2, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.BernoulliN2Second", () => _siderData.BernoulliN2Second, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.DoorLowerLocked", () => _siderData.DoorLowerLocked, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.DoorLowerUnlocked", () => _siderData.DoorLowerUnlocked, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.DoorUpperLocked", () => _siderData.DoorUpperLocked, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.DoorUpperUnlocked", () => _siderData.DoorUpperUnlocked, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.TransBladder", () => _siderData.TransBladder, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.TransHigh", () => _siderData.TransHigh, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.TransPressure", () => _siderData.TransPressure, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.TransRetracted", () => _siderData.TransRetracted, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.WaferPresent", () => _siderData.WaferPresent, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.WSBladder", () => _siderData.WHBladder, SubscriptionAttribute.FLAG.IgnoreSaveDB);            DATA.Subscribe($"{Module}.{Name}.WSBladderPressure", () => _siderData.WHBladderPressure, SubscriptionAttribute.FLAG.IgnoreSaveDB);        }        /// <summary>        /// 订阅变量数值发生变化        /// </summary>        private void SubscribeValueAction()        {            BeckhoffIoSubscribeUpdateVariable(WAFER_PRESENT);            BeckhoffIoSubscribeUpdateVariable(DOOR_UNLOCK);            BeckhoffIoSubscribeUpdateVariable(DOOR_UPPER_LOCKED);            BeckhoffIoSubscribeUpdateVariable(DOOR_UPPER_UNLOCKED);            BeckhoffIoSubscribeUpdateVariable(DOOR_LOWER_LOCKED);            BeckhoffIoSubscribeUpdateVariable(DOOR_LOWER_UNLOCKED);            BeckhoffIoSubscribeUpdateVariable(CRS_VACUUM);            BeckhoffIoSubscribeUpdateVariable(CRS_VACUUM_VALUE);            BeckhoffIoSubscribeUpdateVariable(WH_BLADDER);            BeckhoffIoSubscribeUpdateVariable(WH_BLADDER_PRESSURE);            BeckhoffIoSubscribeUpdateVariable(TRANS_BLADDER);            BeckhoffIoSubscribeUpdateVariable(TRANS_HIGH);            BeckhoffIoSubscribeUpdateVariable(TRANS_RETRACTED);            BeckhoffIoSubscribeUpdateVariable(TRANS_PRESSURE);            BeckhoffIoSubscribeUpdateVariable(BERNOULLI_BLADDER);            BeckhoffIoSubscribeUpdateVariable(BERNOULLI_EXTENDED);            BeckhoffIoSubscribeUpdateVariable(BERNOULLI_N2);            BeckhoffIoSubscribeUpdateVariable(BERNOULLI_N2_SECOND);            BeckhoffIoSubscribeUpdateVariable(BERNOULLI_PRESSURE);            BeckhoffIoSubscribeUpdateVariable(WAFER_HOLDER_LOCKED);            BeckhoffIoSubscribeUpdateVariable(BERNOULLI_BLADDER_PRESSURE);        }        /// <summary>        /// 订阅IO变量        /// </summary>        /// <param name="variable"></param>        private void BeckhoffIoSubscribeUpdateVariable(string variable)        {            _variableInitializeDic[variable] = false;            IOModuleManager.Instance.SubscribeModuleVariable($"{Module}.{Name}", variable, UpdateVariableValue);        }        /// <summary>        /// 初始化操作        /// </summary>        private void InitializeOperation()        {            OP.Subscribe($"{Module}.{Name}.DoorLockOn",(cmd,args)=> { return DoorLockOnAction(); });            OP.Subscribe($"{Module}.{Name}.DoorLockOff",(cmd,args)=> { return DoorLockOffAction(); });            OP.Subscribe($"{Module}.{Name}.VacuumOn", VacuumOnAction);            OP.Subscribe($"{Module}.{Name}.VacuumOff", VacuumOffAction);            OP.Subscribe($"{Module}.{Name}.WhBladderOn", WhBladderOnAction);            OP.Subscribe($"{Module}.{Name}.WhBladderOff", WhBladderOffAction);            OP.Subscribe($"{Module}.{Name}.BernoulliBladderOn", BernoulliBladderOnAction);            OP.Subscribe($"{Module}.{Name}.BernoulliBladderOff", BernoulliBladderOffAction);            OP.Subscribe($"{Module}.{Name}.BernoulliN2On", BernoulliN2OnAction);            OP.Subscribe($"{Module}.{Name}.BernoulliN2Off", BernoulliN2OffAction);            OP.Subscribe($"{Module}.{Name}.Load", LoadAction);            OP.Subscribe($"{Module}.{Name}.Unload", UnloadAction);            OP.Subscribe($"{Module}.{Name}.InstallCRS", InstallCRSAction);            OP.Subscribe($"{Module}.{Name}.StartCycle", StartCycleAction);            OP.Subscribe($"{Module}.{Name}.StopCycle", StopCycleAction);        }        #region Operation        /// <summary>        /// Door Lock On操作        /// </summary>        public bool DoorLockOnAction()        {            if (!JudgeRunningState(LoaderSideOperation.DoorOn))            {                _currentOperation = LoaderSideOperation.DoorOn;                _status = _doorlockRoutine.Start(true);                return true;            }            else            {                return false;            }        }        /// <summary>        /// Door Lock Off操作        /// </summary>        public bool DoorLockOffAction()        {            if (!JudgeRunningState(LoaderSideOperation.DoorOff))            {                _currentOperation = LoaderSideOperation.DoorOff;                _status = _doorlockRoutine.Start(false);                return true;            }            else            {                return false;            }        }        /// <summary>        /// Vacuum On 操作        /// </summary>        private bool VacuumOnAction(string cmd, object[] args)        {            if (!JudgeRunningState(LoaderSideOperation.VacuumOn))            {                _currentOperation = LoaderSideOperation.VacuumOn;                _status = _vacuumRoutine.Start(true);                return true;            }            else            {                return false;            }        }        /// <summary>        /// Vacuum On 操作        /// </summary>        private bool VacuumOffAction(string cmd, object[] args)        {            if (!JudgeRunningState(LoaderSideOperation.VacuumOff))            {                _currentOperation = LoaderSideOperation.VacuumOff;                _status = _vacuumRoutine.Start(false);                return true;            }            else            {                return false;            }        }        /// <summary>        /// WH Bladder On 操作        /// </summary>        private bool WhBladderOnAction(string cmd, object[] args)        {            if (!JudgeRunningState(LoaderSideOperation.WhBladderOn))            {                _currentOperation = LoaderSideOperation.WhBladderOn;                _status = _whBladderRoutine.Start(true);                return true;            }            else            {                return false;            }        }        /// <summary>        /// WH Bladder Off 操作        /// </summary>        private bool WhBladderOffAction(string cmd, object[] args)        {            if (!JudgeRunningState(LoaderSideOperation.WhBladderOff))            {                _currentOperation = LoaderSideOperation.WhBladderOff;                _status = _whBladderRoutine.Start(false);                return true;            }            else            {                return false;            }        }        /// <summary>        /// Bernoulli Bladder On 操作        /// </summary>        private bool BernoulliBladderOnAction(string cmd, object[] args)        {            if (!JudgeRunningState(LoaderSideOperation.BernoulliBladderOn))            {                _currentOperation = LoaderSideOperation.BernoulliBladderOn;                _status = _bernoulliBladderRoutine.Start(true);                return true;            }            else            {                return false;            }        }        /// <summary>        /// Bernoulli Bladder Off 操作        /// </summary>        private bool BernoulliBladderOffAction(string cmd, object[] args)        {            if (!JudgeRunningState(LoaderSideOperation.BernoulliBladderOff))            {                _currentOperation = LoaderSideOperation.BernoulliBladderOff;                _status = _bernoulliBladderRoutine.Start(false);                return true;            }            else            {                return false;            }        }        /// <summary>        /// Bernoulli N2 On 操作        /// </summary>        public bool BernoulliN2OnAction(string cmd, object[] args)        {            string ioName = BeckhoffModuleIOManager.Instance.GetIoNameByInnerModuleName($"{Module}.{Name}.{BERNOULLI_N2}");            bool result = IOModuleManager.Instance.WriteIoValue(ioName, true);                        return result;        }        /// <summary>        /// Bernoulli N2 Off 操作        /// </summary>        public bool BernoulliN2OffAction(string cmd, object[] args)        {            string ioName = BeckhoffModuleIOManager.Instance.GetIoNameByInnerModuleName($"{Module}.{Name}.{BERNOULLI_N2}");            bool result = IOModuleManager.Instance.WriteIoValue(ioName, false);                        return result;        }        /// <summary>        /// Load 操作        /// </summary>        public bool LoadAction(string cmd, object[] args)        {            if (!JudgeRunningState(LoaderSideOperation.Load))            {                _currentOperation = LoaderSideOperation.Load;                _status = _loadRoutine.Start();                return true;            }            else            {                return false;            }        }        /// <summary>        /// Unload 操作        /// </summary>        public bool UnloadAction(string cmd, object[] args)        {            if (!JudgeRunningState(LoaderSideOperation.Unload))            {                _currentOperation = LoaderSideOperation.Unload;                _status = _unloadRoutine.Start();                return true;            }            else            {                return false;            }        }        /// <summary>        /// Install CRS 操作        /// </summary>        private bool InstallCRSAction(string cmd, object[] args)        {            if (!JudgeRunningState(LoaderSideOperation.InstallCRS))            {                _currentOperation = LoaderSideOperation.InstallCRS;                _status = _installCRSRoutine.Start();                return true;            }            else            {                return false;            }        }        /// <summary>        /// StartCycle 操作        /// </summary>        private bool StartCycleAction(string cmd, object[] args)        {            if (!JudgeRunningState(LoaderSideOperation.StartCycle))            {                _currentOperation = LoaderSideOperation.StartCycle;                _status = _cycleRoutine.Start(args);                bool result= _status == RState.Running ;                if(result)                {                    _currentCycle = _cycleRoutine.CurrentCycle.ToString();                }                else                {                    _currentCycle = "";                }                return result;            }            else            {                return false;            }        }        /// <summary>        /// StopCycle 操作        /// </summary>        private bool StopCycleAction(string cmd, object[] args)        {            if (_currentOperation==LoaderSideOperation.StartCycle)            {                AbortRoutine();                return true;            }            else            {                return true;            }        }        /// <summary>        /// 判定运行状态        /// </summary>        /// <returns></returns>        private bool JudgeRunningState(LoaderSideOperation operation)        {            if (_status == RState.Running)            {                EV.PostAlarmLog($"{Module}.{Name}", eEvent.ERR_LOADER, $"{Module}.{Name} current execute {_currentOperation},cannot {operation}");                return true;            }            return false;        }        #endregion        /// 更新变量数值        /// </summary>        /// <param name="variable"></param>        /// <param name="value"></param>        private void UpdateVariableValue(string variable, object value)        {            PropertyInfo property = SideData.GetType().GetProperty(variable);            if (property != null)            {                property.SetValue(SideData, value);            }        }        /// <summary>        /// 定时器        /// </summary>        /// <returns></returns>        public bool OnTimer()        {            if (_status == RState.Running)            {                if (_currentOperation != LoaderSideOperation.None)                {                    IRoutine routine = GetCurrentRoutine(_currentOperation);                    if (routine != null)                    {                        CheckRoutineState(routine, _currentOperation);                    }                    else                    {                        EndOperation(RState.End);                    }                    if(_currentOperation==LoaderSideOperation.StartCycle)                    {                        _currentCycle = _cycleRoutine.CurrentCycle.ToString();                    }                }            }            return true;        }        /// <summary>        /// 获取当前操作对应的Routine        /// </summary>        /// <param name="currentOperation"></param>        /// <returns></returns>        private IRoutine GetCurrentRoutine(LoaderSideOperation currentOperation)        {            switch (currentOperation)            {                case LoaderSideOperation.DoorOn:                case LoaderSideOperation.DoorOff:                    return _doorlockRoutine;                case LoaderSideOperation.VacuumOn:                case LoaderSideOperation.VacuumOff:                    return _vacuumRoutine;                case LoaderSideOperation.WhBladderOn:                case LoaderSideOperation.WhBladderOff:                    return _whBladderRoutine;                case LoaderSideOperation.BernoulliBladderOn:                case LoaderSideOperation.BernoulliBladderOff:                    return _bernoulliBladderRoutine;                case LoaderSideOperation.Load:                    return _loadRoutine;                case LoaderSideOperation.Unload:                    return _unloadRoutine;                case LoaderSideOperation.InstallCRS:                    return _installCRSRoutine;                case LoaderSideOperation.StartCycle:                    return _cycleRoutine;                default:                    return null;            }        }        /// <summary>        /// 检验Routine状态        /// </summary>        /// <param name="routine"></param>        /// <param name="currentOperation"></param>        private void CheckRoutineState(IRoutine routine, LoaderSideOperation currentOperation)        {            RState state = routine.Monitor();            if (state == RState.End)            {                EndOperation(RState.End);            }            else if (state == RState.Failed || state == RState.Timeout)            {                LOG.WriteLog(eEvent.ERR_LOADER, $"{Module}.{Name}", $"{currentOperation} error");                EndOperation(RState.Failed);            }        }        /// <summary>        /// 结束操作        /// </summary>        private void EndOperation(RState state)        {            _status = state;            _currentOperation = LoaderSideOperation.None;        }        /// <summary>        /// 中止Routine        /// </summary>        public void AbortRoutine()        {            IRoutine routine = GetCurrentRoutine(_currentOperation);            if(routine!=null)            {                routine.Abort();                _currentOperation=LoaderSideOperation.None;                _status = RState.End;                _currentCycle = "";            }        }        #region 设备接口        /// <summary>        /// 监控        /// </summary>        public void Monitor()        {                    }        public void Reset()        {        }        public void Terminate()        {            AbortRoutine();        }        #endregion    }}
 |