From 1283d1c570ad7f1f9a4bd48e10d4bba267652791 Mon Sep 17 00:00:00 2001 From: Harald Wolff Date: Tue, 22 Dec 2020 23:34:05 +0100 Subject: [PATCH] Improvements to WebUI --- ln.ethercat.service/Program.cs | 6 - .../api/v1/ControllerWebSocket.cs | 33 ++++ ln.ethercat.service/www/html/spa.html | 40 ++++- ln.ethercat.service/www/static/css/spa.css | 69 +++++++-- .../www/static/pages/controller.html | 65 +++++--- ln.ethercat/controller/Controller.cs | 26 +++- .../controller/drives/CIA402Controller.cs | 143 +++++++++++------- .../controller/remote/StupidSerialRemote.cs | 101 +++++++++++-- 8 files changed, 362 insertions(+), 121 deletions(-) diff --git a/ln.ethercat.service/Program.cs b/ln.ethercat.service/Program.cs index c3e5be2..2400125 100644 --- a/ln.ethercat.service/Program.cs +++ b/ln.ethercat.service/Program.cs @@ -36,8 +36,6 @@ namespace ln.ethercat.service ethercatService.ECMaster.OnStateChange += (ECMaster ECMaster, ECSlaveState newState) => { if (newState == ECSlaveState.OPERATIONAL) { - ECMaster.DriveControllers[1].IgnoredByController = true; - if (ECMaster.GetSDOValue(1, 0x2012, 32, out SDOValue svEnableDrives)) { svEnableDrives.SetValue((byte)0x00); @@ -52,7 +50,6 @@ namespace ln.ethercat.service ethercatService.ECMaster.Controller.OnStateChanged += (controller, state) => { Logging.Log(LogLevel.DEBUG, "ControllerState=={0}", state); - if (state == ControllerStates.NOTREADY) { if (controller.ECMaster.GetSDOValue(1, 0x2012, 32, out SDOValue svEnableDrives)) @@ -60,9 +57,6 @@ namespace ln.ethercat.service Logging.Log(LogLevel.DEBUG, "ControllerState=={0} powering up enable signals", state); svEnableDrives.SetValue((byte)0x01); } - } else if (state == ControllerStates.READY) - { - controller.ECMaster.DriveControllers[1].DriveMode = DriveMode.TORQUE; } }; diff --git a/ln.ethercat.service/api/v1/ControllerWebSocket.cs b/ln.ethercat.service/api/v1/ControllerWebSocket.cs index 1707f79..ec82e7b 100644 --- a/ln.ethercat.service/api/v1/ControllerWebSocket.cs +++ b/ln.ethercat.service/api/v1/ControllerWebSocket.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Reflection; using ln.ethercat.controller; using ln.ethercat.controller.drives; using ln.http.websocket; using ln.json; +using ln.json.mapping; using ln.logging; +using ln.type; namespace ln.ethercat.service.api.v1 { @@ -48,6 +51,8 @@ namespace ln.ethercat.service.api.v1 .Add("State", controller.ControllerState.ToString()) .Add("Drives", jsonDriveControllers) .Add("CycleCounter", controller.CycleCounter) + .Add("DisableRemoteWatchdog", controller.DisableRemoteWatchdog) + .Add("IgnoreRemoteInterface", controller.IgnoreRemoteInterface) ; @@ -100,12 +105,40 @@ namespace ln.ethercat.service.api.v1 public override void Received(string textMessage) { + object target; + base.Received(textMessage); JSONValue jsonTextValue = JSONParser.Parse(textMessage); if (jsonTextValue is JSONObject jsonMessage) { string eventName = jsonMessage["event"].ToNative().ToString(); + JSONObject jsonValue = jsonMessage["value"] as JSONObject; + + switch (eventName) + { + case "set": + string propName = jsonValue["name"].ToNative().ToString(); + object propValue = jsonValue["value"].ToNative(); + target = Controller; + if (jsonValue.ContainsKey("drive")) + target = Controller.ECMaster.DriveControllers[(Int64)jsonValue["drive"].ToNative()]; + + PropertyInfo propertyInfo = target.GetType().GetProperty(propName); + propertyInfo.SetValue(target, Cast.To(propValue, propertyInfo.PropertyType)); + break; + case "action": + string methodName = jsonValue["method"].ToNative().ToString(); + JSONMapper.DefaultMapper.Deserialize(jsonValue["arguments"], out object[] arguments); + target = Controller; + if (jsonValue.ContainsKey("drive")) + target = Controller.ECMaster.DriveControllers[(Int64)jsonValue["drive"].ToNative()]; + + MethodInfo methodInfo = target.GetType().GetMethod(methodName); + methodInfo.Invoke(target,new object[0]); + break; + + } } } diff --git a/ln.ethercat.service/www/html/spa.html b/ln.ethercat.service/www/html/spa.html index db0a1d8..2d7debd 100644 --- a/ln.ethercat.service/www/html/spa.html +++ b/ln.ethercat.service/www/html/spa.html @@ -95,12 +95,46 @@ LN template: src, data: function(){ let d = { controller: {}, drive_controller: null }; - let socket = $ECAPP.connectWebSocket("/api/v1/sockets/controller"); - socket.onmessage = (evt)=>{ + d.socket = $ECAPP.connectWebSocket("/api/v1/sockets/controller"); + d.socket.onmessage = (evt)=>{ let json = JSON.parse(evt.data); - d.controller = json; + d.controller = json.value; }; return d; + }, + methods: { + drive_set: function(drive, propName, propValue){ + let jsonMessage = { + event: "set", + value: { + drive: drive, + name: propName, + value: propValue + } + }; + this.socket.send(JSON.stringify(jsonMessage)); + }, + ctrl_set: function(propName, propValue){ + let jsonMessage = { + event: "set", + value: { + name: propName, + value: propValue + } + }; + this.socket.send(JSON.stringify(jsonMessage)); + }, + ctrl_action: function(methodName, arguments){ + arguments = arguments || []; + let jsonMessage = { + event: "action", + value: { + method: methodName, + arguments: arguments + } + }; + this.socket.send(JSON.stringify(jsonMessage)); + } } } }]); diff --git a/ln.ethercat.service/www/static/css/spa.css b/ln.ethercat.service/www/static/css/spa.css index 3991bb6..1665cd8 100644 --- a/ln.ethercat.service/www/static/css/spa.css +++ b/ln.ethercat.service/www/static/css/spa.css @@ -22,6 +22,7 @@ main, .flex { } main > *, .flex > * { + flex-basis: 100%; flex-grow: 1; margin: 0px 8px; } @@ -29,9 +30,17 @@ main > *, .flex > * { main > aside, .flex > .aside{ flex-basis: 25%; flex-grow: 0; + min-width: 25%; } - +.group { + display: block; + border: 1px solid #ccc; + padding: 8px; +} +.group > fieldset { + border: none; +} h2 { border-bottom: 1px solid black; @@ -43,20 +52,41 @@ button, span { font-style: normal; text-decoration: none; } +button.selected { + color: #10E010; +} +button.true { + color: #10E010; +} +button.false { + color: #B0B0B0; +} fieldset { position: relative; display: flex; } -fieldset > label { - flex-grow: 0; -} - -fieldset > input, fieldset > .value { +fieldset > :first-child { flex-grow: 1; + flex-basis: 50%; +} +fieldset > :nth-child(2) { + flex-grow: 1; + flex-basis: 40%; +} +fieldset > :last-child { + flex-grow: 1; + flex-basis: 25%; +} +fieldset.equal > * { + flex-basis: 50%; + margin: 4px; } +.flex { + display: flex; +} .panel { padding: 6px; } @@ -88,6 +118,24 @@ fieldset > input, fieldset > .value { border-radius: 8px;; background-color: #808080; } +.state.large { + width: 240px; +} + +.state.READY { + color: #108010; +} +.state.OPERATIONAL { + color: #10E010; +} +.state.ERROR, .state.FAULT { + color: white; + background-color: #C04040; +} +.state.BOOT, .state.INIT { + color: #000080; +} + .state.active { background-color: #10E010; @@ -104,15 +152,10 @@ fieldset > input, fieldset > .value { .state.active.orange { background-color: #e0a810; } - - -.OPERATIONAL { - background-color: #10E010; -} -.SAFE_OP { +.state.SAFE_OP { background-color: #e0a810; } -.PRE_OP { +.state.PRE_OP { background-color: #B0B0B0; } diff --git a/ln.ethercat.service/www/static/pages/controller.html b/ln.ethercat.service/www/static/pages/controller.html index 1e8382a..d4ba47c 100644 --- a/ln.ethercat.service/www/static/pages/controller.html +++ b/ln.ethercat.service/www/static/pages/controller.html @@ -1,32 +1,47 @@

Controller

+
+ +
+ + {{ controller.State }} +
+
+ + + +
+
+ + +
+
+ +
+
+ + {{ drive.DriveState }} + {{ drive.OEMState }} +
+
+ + {{ drive.DriveMode }} + + +
+
+ + + + +
+
+
+

{{ controller }}

-
-

Drive Controller

- - - - - - - - - - -

- {{ drive_controller }} -

- -
-
- - - - -
-
-
\ No newline at end of file diff --git a/ln.ethercat/controller/Controller.cs b/ln.ethercat/controller/Controller.cs index b9e82cc..e8baf1e 100644 --- a/ln.ethercat/controller/Controller.cs +++ b/ln.ethercat/controller/Controller.cs @@ -7,7 +7,6 @@ using ln.logging; namespace ln.ethercat.controller { - public delegate void ControllerLogicDelegate(Controller controller); public delegate void ControllerStateChangeDelegate(Controller controller, ControllerStates newState); @@ -45,6 +44,8 @@ namespace ln.ethercat.controller Thread threadWatchdog; int wdogCounter; + public bool DisableRemoteWatchdog { get; set; } + int remoteWatchdogCounter; public int WatchdogReset { get; set; } = 5; public Controller(ECMaster ecMaster) @@ -67,7 +68,6 @@ namespace ln.ethercat.controller foreach (DriveController driveController in ECMaster.DriveControllers) driveController.Initialize(); - } void UpdateControllerState() @@ -172,15 +172,17 @@ namespace ln.ethercat.controller } } + public void RemoteTriggerWatchdog() => remoteWatchdogCounter = 5; + public void RemoteUpdateTarget(int drive, double targetValue) - { + { if (!IgnoreRemoteInterface) { - targetValue = Math.Clamp(targetValue, 0, 1); + targetValue = Math.Clamp(targetValue, -1, 1); if (drive < 0) throw new ArgumentOutOfRangeException(nameof(targetValue)); - if (drive > ECMaster.DriveControllers.Length) + if (drive >= ECMaster.DriveControllers.Length) { Logging.Log(LogLevel.WARNING, "Controller: RemoteUpdateTarget(): trying to update non-existent drive {0}", drive); return; @@ -217,7 +219,19 @@ namespace ln.ethercat.controller try { Thread.Sleep(TimeSpan.FromSeconds(ControllerLoopInterval)); - if ((wdogCounter--) < 0) + + if (wdogCounter > 0) + { + wdogCounter--; + } else + { + ChangeState(ControllerStates.FAULT); + } + + if (remoteWatchdogCounter > 0) + { + remoteWatchdogCounter--; + } else if (!DisableRemoteWatchdog) { ChangeState(ControllerStates.FAULT); } diff --git a/ln.ethercat/controller/drives/CIA402Controller.cs b/ln.ethercat/controller/drives/CIA402Controller.cs index cd9f04e..a635be8 100644 --- a/ln.ethercat/controller/drives/CIA402Controller.cs +++ b/ln.ethercat/controller/drives/CIA402Controller.cs @@ -1,6 +1,7 @@ using System; +using System.Diagnostics; using ln.logging; namespace ln.ethercat.controller.drives @@ -42,6 +43,8 @@ namespace ln.ethercat.controller.drives SDOValue svModesOfOperation; SDOValue svModesOfOperationDisplay; SDOValue svActualCurrent; + SDOValue svVLTargetVelocity; + SDOValue svVLActualVelocity; SDOValue svMaxSpeed; @@ -62,7 +65,9 @@ namespace ln.ethercat.controller.drives new PDOMappingRequest(slave, 0x6077, 0, false), new PDOMappingRequest(slave, 0x6060, 0, true), new PDOMappingRequest(slave, 0x6061, 0, false), - new PDOMappingRequest(slave, 0x6078, 0, false) + new PDOMappingRequest(slave, 0x6078, 0, false), + new PDOMappingRequest(slave, 0x6042, 0, true), + new PDOMappingRequest(slave, 0x6044, 0, false), }; ecMaster.RequestPDOMapping(mappingRequests); } @@ -82,68 +87,81 @@ namespace ln.ethercat.controller.drives ECMaster.GetSDOValue(Slave, 0x6060, 0, out svModesOfOperation) && ECMaster.GetSDOValue(Slave, 0x6061, 0, out svModesOfOperationDisplay) && ECMaster.GetSDOValue(Slave, 0x6080, 0, out svMaxSpeed) && + ECMaster.GetSDOValue(Slave, 0x6044, 0, out svVLActualVelocity) && + ECMaster.GetSDOValue(Slave, 0x6042, 0, out svVLTargetVelocity) && ECMaster.GetSDOValue(Slave, 0x6078, 0, out svActualCurrent) )) throw new ArgumentOutOfRangeException("CIA402Controller could not retrieve SDOvalues for CiA402 profile"); + + MotorMaxSpeed = svMaxSpeed.GetValue(); } public override void UpdateStates() { CIA402State = GetCIA402State(); } - public override void UpdateDrive() { - - if (CIA402State != CIA402TargetState) + public override void UpdateDrive() + { + if (CIA402TargetState != CIA402States.UNDEFINED) { - switch (CIA402State) + if (CIA402State != CIA402TargetState) { - case CIA402States.NOT_READY_TO_SWITCH_ON: - svControlWord.SetValue((ushort)0x0000); - ModeOfOperation = CIA402ModesOfOperation.CYCLIC_SYNC_TORQUE; - break; - case CIA402States.SWITCH_ON_DISABLED: - svControlWord.SetValue((ushort)0x0006); - break; - case CIA402States.READY_TO_SWITCH_ON: - switch (CIA402TargetState) - { - case CIA402States.SWITCH_ON_DISABLED: - svControlWord.SetValue((ushort)0x0000); - break; - case CIA402States.SWITCHED_ON: - case CIA402States.OPERATION_ENABLED: - svControlWord.SetValue((ushort)0x0007); - break; - } - break; - case CIA402States.SWITCHED_ON: - switch (CIA402TargetState) - { - case CIA402States.SWITCH_ON_DISABLED: - case CIA402States.READY_TO_SWITCH_ON: - svControlWord.SetValue((ushort)0x0006); - break; - case CIA402States.OPERATION_ENABLED: - svControlWord.SetValue((ushort)0x000F); - break; - } - break; - case CIA402States.OPERATION_ENABLED: - switch (CIA402TargetState) - { - case CIA402States.SWITCH_ON_DISABLED: - case CIA402States.READY_TO_SWITCH_ON: - svControlWord.SetValue((ushort)0x0006); - break; - case CIA402States.SWITCHED_ON: - svControlWord.SetValue((ushort)0x0007); - break; - } - break; + switch (CIA402State) + { + case CIA402States.FAULT: + case CIA402States.FAULT_REACTION_ACTIVE: + CIA402TargetState = CIA402States.UNDEFINED; + break; + case CIA402States.NOT_READY_TO_SWITCH_ON: + svControlWord.SetValue((ushort)0x0000); + ModeOfOperation = CIA402ModesOfOperation.CYCLIC_SYNC_TORQUE; + break; + case CIA402States.SWITCH_ON_DISABLED: + svControlWord.SetValue((ushort)0x0006); + break; + case CIA402States.READY_TO_SWITCH_ON: + switch (CIA402TargetState) + { + case CIA402States.SWITCH_ON_DISABLED: + svControlWord.SetValue((ushort)0x0000); + break; + case CIA402States.SWITCHED_ON: + case CIA402States.OPERATION_ENABLED: + svControlWord.SetValue((ushort)0x0007); + break; + } + break; + case CIA402States.SWITCHED_ON: + switch (CIA402TargetState) + { + case CIA402States.SWITCH_ON_DISABLED: + case CIA402States.READY_TO_SWITCH_ON: + svControlWord.SetValue((ushort)0x0006); + break; + case CIA402States.OPERATION_ENABLED: + svControlWord.SetValue((ushort)0x000F); + break; + } + break; + case CIA402States.OPERATION_ENABLED: + switch (CIA402TargetState) + { + case CIA402States.SWITCH_ON_DISABLED: + case CIA402States.READY_TO_SWITCH_ON: + svControlWord.SetValue((ushort)0x0006); + break; + case CIA402States.SWITCHED_ON: + svControlWord.SetValue((ushort)0x0007); + break; + } + break; + } + } else { + CIA402TargetState = CIA402States.UNDEFINED; } } if (DriveMode == DriveMode.UNDEFINED) - DriveMode = DriveMode.TORQUE; + DriveMode = DriveMode.SPEED; } public override DriveStates DriveState @@ -182,6 +200,7 @@ namespace ln.ethercat.controller.drives case CIA402ModesOfOperation.CYCLIC_SYNC_POSITION: return DriveMode.POSITION; case CIA402ModesOfOperation.CYCLIC_SYNC_VELOCITY: + case CIA402ModesOfOperation.VL_VELOCITY_MODE: return DriveMode.SPEED; case CIA402ModesOfOperation.CYCLIC_SYNC_TORQUE: return DriveMode.TORQUE; @@ -196,7 +215,7 @@ namespace ln.ethercat.controller.drives ModeOfOperation = CIA402ModesOfOperation.CYCLIC_SYNC_POSITION; break; case DriveMode.SPEED: - ModeOfOperation = CIA402ModesOfOperation.CYCLIC_SYNC_VELOCITY; + ModeOfOperation = CIA402ModesOfOperation.VL_VELOCITY_MODE; break; case DriveMode.TORQUE: ModeOfOperation = CIA402ModesOfOperation.CYCLIC_SYNC_TORQUE; @@ -214,11 +233,19 @@ namespace ln.ethercat.controller.drives public CIA402ModesOfOperation ModeOfOperation { get => (ECMaster.EthercatState==ECSlaveState.OPERATIONAL) ? (CIA402ModesOfOperation)(svModesOfOperationDisplay?.GetValue() ?? 0) : CIA402ModesOfOperation.NO_MODE_CHANGE; - set => svModesOfOperation.SetValue((sbyte)value); + set { + svModesOfOperation.SetValue((sbyte)value); + } } public override double ActualPosition => (svActualPosition?.GetValue() ?? 0) ; - public override double ActualSpeed => (double)(svActualSpeed?.GetValue() ?? 0) / (1000.0 * MotorMaxSpeed); + public override double ActualSpeed { + get { + if (ModeOfOperation == CIA402ModesOfOperation.CYCLIC_SYNC_VELOCITY) + return (double)(svActualSpeed?.GetValue() ?? 0) / (1000.0 * MotorMaxSpeed); + return (double)(svVLActualVelocity?.GetValue() ?? 0) / MotorMaxSpeed; + } + } public override double ActualTorque => (double)(svActualTorque?.GetValue() ?? 0) / 1000.0; public override double ActualLoad => (double)(svActualCurrent?.GetValue() ?? 0) / 1000.0; @@ -228,8 +255,16 @@ namespace ln.ethercat.controller.drives } public override double TargetSpeed { - get => (double)svTargetSpeed.GetValue() / (1000.0 * MotorMaxSpeed); - set => svTargetSpeed.SetValue((int)(value * (1000.0 * MotorMaxSpeed))); + get { + if (ModeOfOperation == CIA402ModesOfOperation.CYCLIC_SYNC_VELOCITY) + return (double)svTargetSpeed.GetValue() / (1000.0 * MotorMaxSpeed); + return (double)(svVLTargetVelocity?.GetValue() ?? 0) / MotorMaxSpeed; + } + set { + if (ModeOfOperation == CIA402ModesOfOperation.CYCLIC_SYNC_VELOCITY) + svTargetSpeed.SetValue((int)(value * (1000.0 * MotorMaxSpeed))); + svVLTargetVelocity.SetValue((short)(value * MotorMaxSpeed)); + } } public override double TargetTorque { get => (double)svTargetTorque.GetValue() / 1000.0; diff --git a/ln.ethercat/controller/remote/StupidSerialRemote.cs b/ln.ethercat/controller/remote/StupidSerialRemote.cs index 977f23a..fcd54b7 100644 --- a/ln.ethercat/controller/remote/StupidSerialRemote.cs +++ b/ln.ethercat/controller/remote/StupidSerialRemote.cs @@ -2,11 +2,8 @@ using System; using System.IO.Ports; -using System.Net.Http.Headers; -using System.Text; using System.Threading; -using ln.logging; -using ln.type; +using ln.ethercat.controller.drives; namespace ln.ethercat.controller.remote { @@ -33,15 +30,29 @@ namespace ln.ethercat.controller.remote LOAD100 = (1<<15), ALL = -1 } + + public enum FeederOperation + { + NONE, + LEFT, + RIGHT, + TORQUE + } + public class StupidSerialRemote : ControllerRemote { public string SerialPortName { get; } + public FeederOperation FeederOperation { get; set; } + SerialPort serialPort; bool stopReceiverThread; Thread threadReceiver; + public double MainTarget { get; set; } + public double FeedTarget { get; set; } + public StupidSerialRemote(Controller controller) :this(controller, SerialPort.GetPortNames()[0]){} public StupidSerialRemote(Controller controller, string serialDevice) @@ -77,12 +88,50 @@ namespace ln.ethercat.controller.remote if (Controller.ECMaster.DriveControllers[0].ActualLoad >= 1) leds |= StupidLEDs.LOAD100; SetLEDs(leds); + + Controller.RemoteUpdateTarget(0, MainTarget); + + switch (FeederOperation) + { + case FeederOperation.NONE: + Controller.RemoteUpdateTarget(1, 0); + break; + case FeederOperation.LEFT: + if (Controller.ECMaster.DriveControllers[1].DriveMode == DriveMode.SPEED) + { + Controller.RemoteUpdateTarget(1, FeedTarget); + } else { + Controller.ECMaster.DriveControllers[1].DriveMode = DriveMode.SPEED; + Controller.RemoteUpdateTarget(1, 0); + } + break; + case FeederOperation.RIGHT: + if (Controller.ECMaster.DriveControllers[1].DriveMode == DriveMode.SPEED) + { + Controller.RemoteUpdateTarget(1, -FeedTarget); + } else { + Controller.ECMaster.DriveControllers[1].DriveMode = DriveMode.SPEED; + Controller.RemoteUpdateTarget(1, 0); + } + break; + case FeederOperation.TORQUE: + if (Controller.ECMaster.DriveControllers[1].DriveMode == DriveMode.TORQUE) + { + Controller.RemoteUpdateTarget(1, FeedTarget); + } else { + Controller.ECMaster.DriveControllers[1].DriveMode = DriveMode.TORQUE; + Controller.RemoteUpdateTarget(1, 0); + } + break; + } + break; case ControllerStates.ENABLING: break; case ControllerStates.DISABLING: break; } + } void Receiver() @@ -92,18 +141,25 @@ namespace ln.ethercat.controller.remote string rxLine = serialPort.ReadLine(); //Logging.Log(LogLevel.DEBUGDETAIL, rxLine); if (rxLine.Length >= 6) - { - ushort av = ushort.Parse(rxLine.Substring(2,4), System.Globalization.NumberStyles.HexNumber); + { + ushort av = 0; + ushort.TryParse(rxLine.Substring(2,4), System.Globalization.NumberStyles.HexNumber, null, out av); + switch (rxLine[0]) { case 'A': int drive = rxLine[1] - '0'; - if (drive == 0) drive = 1; - else if (drive == 1) drive = 2; - else if (drive == 2) drive = 0; double rel = (double)av / 65535; - if ((drive >= 0) && (drive < Controller.ECMaster.DriveControllers.Length)) - Controller.RemoteUpdateTarget(drive, rel); + switch (drive) + { + case 0: + FeedTarget = rel; + break; + case 2: + MainTarget = rel; + break; + } + Controller.RemoteTriggerWatchdog(); break; case 'B': switch (rxLine[1]) @@ -115,19 +171,36 @@ namespace ln.ethercat.controller.remote if (Controller.ControllerState == ControllerStates.FAULT) Controller.RemoteAction(CRActions.CLEARFAULT); else if (Controller.ControllerState == ControllerStates.READY) + { + Controller.ECMaster.DriveControllers[0].DriveMode = DriveMode.SPEED; Controller.RemoteAction(CRActions.ENABLE); + } break; case 1: Controller.RemoteAction(CRActions.DISABLE); break; + } + break; + case 'D': + switch (av) + { case 3: - // ToDo: Feeder left + FeederOperation = FeederOperation.LEFT; break; case 4: - // ToDo: Feeder right + FeederOperation = FeederOperation.RIGHT; break; case 14: - // ToDo: cycle drilling + FeederOperation = FeederOperation.TORQUE; + break; } + break; + case 'U': + switch (av) + { + case 3: + case 4: + case 14: + FeederOperation = FeederOperation.NONE; break; } break;