diff --git a/libecmbind/libecmbind.c b/libecmbind/libecmbind.c index 2ca46a2..7029373 100644 --- a/libecmbind/libecmbind.c +++ b/libecmbind/libecmbind.c @@ -75,7 +75,7 @@ int ecmbind_processdata() int ecmbind_processdata2(char *managed_iomap, int size) { - if (size != ecd_iomap_size) + if (size < ecd_iomap_size) return -1; memcpy( ecd_iomap, managed_iomap, ecd_iomap_size); @@ -88,7 +88,7 @@ int ecmbind_processdata2(char *managed_iomap, int size) return wkc; } -int ecmbind_recover() +int ecmbind_recover() { int slave = 0; ec_group[0].docheckstate = FALSE; @@ -169,25 +169,16 @@ int ecmbind_get_pdo_entries(ecd_pdo_entry_t* table, int length) return ecd_pdo_map_length; } -int ecmbind_pdo_read(int slave, int index, int subindex, char* buffer, int size) -{ - return -1; -} -int ecmbind_pdo_write(int slave, int index, int subindex, char* buffer, int size) -{ - return -1; -} - int ecmbind_sdo_read(int slave, int index, int subindex, char* buffer, int size) { - int wkc = ec_SDOread(slave, index, subindex, FALSE, &size, buffer, 100000); + int wkc = ec_SDOread(slave, index, subindex, FALSE, &size, buffer, 250000L); if (wkc <= 0) - return -1; + return wkc; return size; } int ecmbind_sdo_write(int slave, int index, int subindex, char* buffer, int size) { - return -1; + return ec_SDOwrite(slave, index, subindex, FALSE, size, buffer, 250000L); } /* diff --git a/ln.ethercat.service/EthercatService.cs b/ln.ethercat.service/EthercatService.cs index 9f17db9..e32dc06 100644 --- a/ln.ethercat.service/EthercatService.cs +++ b/ln.ethercat.service/EthercatService.cs @@ -1,6 +1,10 @@ - - +using System; +using System.IO; using System.Threading; +using System.Timers; +using ln.application; +using ln.ethercat.controller; +using ln.ethercat.controller.drives; using ln.ethercat.service.api.v1; using ln.http; using ln.http.router; @@ -12,49 +16,105 @@ namespace ln.ethercat.service public class EthercatService { - public ECMaster ECMaster { get; } + public ECMaster ECMaster { get; private set; } + public Controller Controller { get; private set; } + [StaticArgument(Option = 'i', LongOption = "interface")] + public string EthercatInterfaceName { get; set; } + + [StaticArgument(LongOption = "web-root")] + public string WebRootPath { get; set; } + HTTPServer httpServer; LoggingRouter httpLoggingRouter; SimpleRouter httpRouter; + + StaticRouter staticRouter; + StaticRouter templateRouter; EthercatApiController apiController; + ControllerApiController controllerApiController; + + + System.Timers.Timer timerWebsockets; public EthercatService(string interfaceName) { - ECMaster = new ECMaster(interfaceName); - - Initialize(); } - void Initialize() + public void Initialize() { + if (EthercatInterfaceName == null) + throw new ArgumentNullException(nameof(EthercatInterfaceName)); + + if (WebRootPath == null) + WebRootPath = AppContext.BaseDirectory; + + + ECMaster = new ECMaster(EthercatInterfaceName); + ECMaster.OnStateChange += MasterStateChanged; + httpRouter = new SimpleRouter(); httpLoggingRouter = new LoggingRouter(httpRouter); - apiController = new EthercatApiController(ECMaster); + apiController = new EthercatApiController(this); httpRouter.AddSimpleRoute("/api/v1/*", apiController); + staticRouter = new StaticRouter(Path.Combine(WebRootPath, "www", "static")); + httpRouter.AddSimpleRoute("/*", staticRouter); + + templateRouter = new StaticRouter(Path.Combine(WebRootPath, "www", "html")); + templateRouter.AddIndex("spa.html"); + httpRouter.AddSimpleRoute("/*", templateRouter); + httpServer = new HTTPServer(httpLoggingRouter); httpServer.AddEndpoint(new Endpoint(IPv6.ANY, 7676)); + Controller = new Controller(); + Controller.Add(new CIA402Controller(ECMaster, 1)); + Controller.Add(new CIA402Controller(ECMaster, 2)); + + controllerApiController = new ControllerApiController(Controller); + httpRouter.AddSimpleRoute("/api/v1/*", controllerApiController); + + + timerWebsockets = new System.Timers.Timer(250); + timerWebsockets.Elapsed += WebSocketTimerMethod; } public void Start() { httpServer.Start(); - ECMaster.Start(); - while (ECMaster.MasterState != ECMasterState.RUNNING) - Thread.Sleep(100); - - Logging.Log(LogLevel.INFO,"ECMaster is ready (ExpectedWorkCounter={0})", ECMaster.ExpectedWorkCounter); + timerWebsockets.Start(); } + void WebSocketTimerMethod(object sender, ElapsedEventArgs e) + { + try{ + EthercatWebSocket.SendProcessData(ECMaster); + } catch (Exception ex) + { + Logging.Log(ex); + } + } + + void MasterStateChanged(ECMaster sender,ECSlaveState newState) + { + switch (newState) + { + case ECSlaveState.OPERATIONAL: + Controller.Start(); + break; + default: + Controller.Stop(); + break; + } + } } diff --git a/ln.ethercat.service/Program.cs b/ln.ethercat.service/Program.cs index b787dab..6c74019 100644 --- a/ln.ethercat.service/Program.cs +++ b/ln.ethercat.service/Program.cs @@ -1,6 +1,9 @@ using System; using System.Text; using System.Threading; +using ln.application; +using ln.ethercat.controller; +using ln.ethercat.controller.drives; using ln.logging; using ln.type; @@ -18,25 +21,13 @@ namespace ln.ethercat.service Logging.Log(LogLevel.INFO, "ECMBind version: {0}", versionString.ToString()); EthercatService ethercatService = new EthercatService(args[0]); + + ArgumentContainer argumentContainer = new ArgumentContainer(); + argumentContainer.AddOptions(ethercatService); + argumentContainer.Parse(ref args); + + ethercatService.Initialize(); ethercatService.Start(); - -/* - while (true) - { - Thread.Sleep(100); - for (int n=1;n <= ecMaster.CountSlaves;n++) - { - //Logging.Log(LogLevel.DEBUG, "Slave {0} is in state {1}", n, ecMaster.ReadSlaveState(n)); - if (ecMaster.ReadSDO(new DOAddr(n, 0x1000),out byte[] typeBytes)) - { - Logging.Log(LogLevel.DEBUG, "Slave {0} has type {1}", n, typeBytes.ToHexString()); - } else { - Logging.Log(LogLevel.DEBUG, "ReadSDO() failed"); - } - } - } - -*/ } } } diff --git a/ln.ethercat.service/api/v1/ControllerApiController.cs b/ln.ethercat.service/api/v1/ControllerApiController.cs index 7b038b3..722e06f 100644 --- a/ln.ethercat.service/api/v1/ControllerApiController.cs +++ b/ln.ethercat.service/api/v1/ControllerApiController.cs @@ -1,13 +1,122 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Net.Mail; +using System.Reflection; +using System.Timers; +using ln.ethercat.controller; +using ln.ethercat.controller.drives; +using ln.http; using ln.http.api; +using ln.http.api.attributes; +using ln.http.websocket; +using ln.json; +using ln.json.mapping; +using ln.logging; +using ln.type; namespace ln.ethercat.service.api.v1 { public class ControllerApiController : WebApiController { + Controller Controller; + public ControllerApiController(Controller controller) + { + Controller = controller; + } + + [GET("/sockets/controller")] + public HttpResponse GetControllerSocket() + { + Timer timer = new Timer(250); + JSONWebSocketResponse websocket = new JSONWebSocketResponse(); + websocket.OnWebSocketStateChanged += (Socket, newstate) => { + if (newstate == WebSocketState.CLOSED) + { + timer.Stop(); + timer.Dispose(); + } + }; + + timer.Elapsed += (s,e) => { + try{ + JSONObject controllerState = new JSONObject() + .Add("DriveControllers", new JSONArray().Add(Controller.DriveControllers.Select((dc=> new JSONObject() + .Add("slave", dc.Slave) + .Add("DriveController", dc.GetType().Name) + .Add("DriveState", dc.DriveState.ToString()) + )))) + .Add("DrivesState", Controller.DrivesState.ToString()) + .Add("IsRunning", Controller.IsRunning) + ; + websocket.Send(controllerState); + } catch (Exception ex) + { + Logging.Log(ex); + } + }; + + timer.Start(); + return websocket; + } + + [GET("/sockets/controller/drives/:drive")] + public HttpResponse GetDriveControllerSocket(int drive) + { + DriveController driveController = Controller.DriveControllers[drive]; + + Timer timer = new Timer(250); + JSONWebSocketResponse websocket = new JSONWebSocketResponse(); + websocket.OnWebSocketStateChanged += (Socket, newstate) => { + if (newstate == WebSocketState.CLOSED) + { + timer.Stop(); + timer.Dispose(); + } + }; + + websocket.OnWebSocketReceivedText += (s,text) => { + JSONObject message = (JSONObject)JSONParser.Parse(text); + switch (message["event"].ToNative().ToString()) + { + case "action": + Logging.Log(LogLevel.DEBUG, "DriveControllerSocket: action: {0}", message["value"].ToNative().ToString()); + driveController.GetType().GetMethod(message["value"].ToNative().ToString()).Invoke(driveController, new object[0]{}); + break; + case "set": + JSONObject jsonSet = (message["value"] as JSONObject); + foreach (string key in jsonSet.Keys) + { + PropertyInfo propertyInfo = driveController.GetType().GetProperty(key); + propertyInfo.SetValue(driveController, Cast.To(jsonSet[key].ToNative(), propertyInfo.PropertyType)); + } + break; + } + }; + + timer.Elapsed += (s,e) => { + try{ + JSONObject driveControllerState = new JSONObject() + .Add("id", drive) + .Add("DriveState", driveController.DriveState.ToString()) + .Add("OEMDriveState", driveController.OEMDriveState) + .Add("DriveMode", driveController.DriveMode) + .Add("OEMDriveMode", driveController.OEMDriveMode) + ; + websocket.Send(driveControllerState); + } catch (Exception ex) + { + Logging.Log(ex); + } + }; + + timer.Start(); + return websocket; + } } diff --git a/ln.ethercat.service/api/v1/EthercatApiController.cs b/ln.ethercat.service/api/v1/EthercatApiController.cs index 66b1ad7..e5a8b0f 100644 --- a/ln.ethercat.service/api/v1/EthercatApiController.cs +++ b/ln.ethercat.service/api/v1/EthercatApiController.cs @@ -1,8 +1,11 @@ +using System; +using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; +using System.Timers; using ln.http; using ln.http.api; using ln.http.api.attributes; +using ln.http.websocket; using ln.json; using ln.json.mapping; using ln.logging; @@ -12,35 +15,37 @@ namespace ln.ethercat.service.api.v1 public class EthercatApiController : WebApiController { - ECMaster ECMaster; - public EthercatApiController(ECMaster ecMaster) + ECMaster ECMaster => EthercatService.ECMaster; + EthercatService EthercatService; + public EthercatApiController(EthercatService ethercatService) { - ECMaster = ecMaster; + EthercatService = ethercatService; } [GET("/slaves")] public int GetSlaveCount() => ECMaster.CountSlaves; - [GET("/slaves/:slave/sdo/:index/:subindex")] - [GET("/slaves/:slave/sdo/:index")] - public HttpResponse ReadSDO(int slave,int index,int subindex = 0) - { - if (ECMaster.GetSDO(new SDOAddr(slave, index, subindex), out SDO sdo)) - { - Logging.Log(LogLevel.DEBUG, "SDO updated: {0}", sdo); - return HttpResponse - .OK() - .Content(JSONMapper.DefaultMapper.ToJson(sdo)) - .ContentType("application/json") - ; - } - return HttpResponse.RequestTimeout().Content("master could not read from slave"); - } - [GET("/slaves/:slave/sdo")] - public SDO[] GetServiceDescriptors(int slave) + [GET("/slaves/:slave_id/sdo")] + public HttpResponse GetServiceDescriptors(UInt16 slave_id) { - return ECMaster.GetSDOs(slave).ToArray(); + if (ECMaster.GetSlave(slave_id, out ECSlave slave)) + { + JSONArray descriptorList = new JSONArray(); + + foreach (SDODescriptor sdoDescriptor in slave.GetSDODescriptors()) + { + descriptorList.Add(new JSONObject() + .Add("slave", sdoDescriptor.SlaveId) + .Add("index", sdoDescriptor.Index) + .Add("datatype", sdoDescriptor.DataType.ToString()) + .Add("objectcode", sdoDescriptor.ObjectCode.ToString()) + .Add("name", sdoDescriptor.Name) + ); + } + return HttpResponse.OK().Content(descriptorList); + } + return HttpResponse.NoContent(); } [GET("/slaves/:slave/state")] @@ -79,8 +84,6 @@ namespace ln.ethercat.service.api.v1 [GET("/master/pdomap")] public PDO[] GetPDOMap() => ECMaster.GetPDOMap(); - [GET("/master/pdos")] - public SDO[] GetPDOs() => ECMaster.GetPDOMap().Select((pdo)=>pdo.SDO).ToArray(); [POST("/master/action")] public HttpResponse PostMasterAction(string action) @@ -98,7 +101,8 @@ namespace ln.ethercat.service.api.v1 } } - + [GET("/socket")] + HttpResponse OpenWebSocket(HttpRequest httpRequest) => new EthercatWebSocket(EthercatService); } } \ No newline at end of file diff --git a/ln.ethercat.service/api/v1/EthercatWebSocket.cs b/ln.ethercat.service/api/v1/EthercatWebSocket.cs new file mode 100644 index 0000000..8f20d31 --- /dev/null +++ b/ln.ethercat.service/api/v1/EthercatWebSocket.cs @@ -0,0 +1,221 @@ + + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using ln.http.websocket; +using ln.json; +using ln.json.mapping; +using ln.logging; + +namespace ln.ethercat.service.api.v1 +{ + public class EthercatWebSocket : WebSocketResponse + { + /************** Static ***************************************************************************/ + static List activeWebSockets = new List(); + + public static void SendProcessData(ECMaster ecMaster) + { + JSONArray jsonPDOList = new JSONArray(); + + foreach (PDO pdo in ecMaster.GetPDOMap()) + { + if (JSONMapper.DefaultMapper.Serialize(pdo.SDOValue, out JSONValue jsonValue) && jsonValue is JSONObject jsonPDOValue) + { + jsonPDOValue.Add("Value", pdo.SDOValue.GetValue()); + jsonPDOList.Add(jsonPDOValue); + } + } + + JSONObject message = Message("pdo", jsonPDOList); + string messageText = message.ToString(); + + JSONArray slaveStates = new JSONArray(); + for (int slave=1; slave <= ecMaster.CountSlaves; slave++) + { + slaveStates.Add(new JSONObject() + .Add("id", new JSONNumber(slave)) + .Add("state", new JSONString(ecMaster.ReadSlaveState(slave).ToString())) + ); + } + JSONObject stateMessage = Message("state", + new JSONObject() + .Add("state", ecMaster.EthercatState.ToString()) + .Add("slaves", slaveStates) + ); + string stateMessageText = stateMessage.ToString(); + + foreach (EthercatWebSocket webSocket in activeWebSockets) + { + webSocket.Send(messageText); + webSocket.Send(stateMessageText); + webSocket.SendSubscribedSDOs(); + } + } + + public static JSONObject Message(string eventName, JSONValue value) => + new JSONObject() + .Add("event", eventName) + .Add("value", value); + public static JSONObject Message(long id, string eventName, JSONValue value) => + Message(eventName, value) + .Add("id", id); + + public static JSONObject Message(string eventName, object value){ + if (JSONMapper.DefaultMapper.Serialize(value, out JSONValue jsonValue)) + return Message(eventName, jsonValue); + throw new NotSupportedException("the value {0} could not be serialized"); + } + public static JSONObject Message(long id, string eventName, object value) => + Message(eventName, value) + .Add("id", id); + + + + /************** Instance ***************************************************************************/ + + HashSet subscribedSDOs = new HashSet(); + + EthercatService EthercatService; + public EthercatWebSocket(EthercatService ethercatService) + { + EthercatService = ethercatService; + OnWebSocketStateChanged += StateChanged; + } + + void StateChanged(WebSocketResponse sender, WebSocketState newState) + { + switch (newState) + { + case WebSocketState.OPEN: + Logging.Log(LogLevel.DEBUG, "EthercatWebSocket -> active"); + activeWebSockets.Add(this); + break; + case WebSocketState.CLOSED: + Logging.Log(LogLevel.DEBUG, "EthercatWebSocket -> inactive"); + activeWebSockets.Remove(this); + break; + } + } + + public override void Received(string textMessage) + { + base.Received(textMessage); + + JSONValue jsonTextValue = JSONParser.Parse(textMessage); + if (jsonTextValue is JSONObject jsonMessage) + { + string eventName = jsonMessage["event"].ToNative().ToString(); + + switch (eventName) + { + case "subscribe": + if (JSONMapper.DefaultMapper.Deserialize(jsonMessage["value"], out SDOAddr[] addresses)) + { + Subscribe(addresses); + } + break; + case "unsubscribe": + if (JSONMapper.DefaultMapper.Deserialize(jsonMessage["value"], out addresses)) + { + Unsubscribe(addresses); + } + break; + case "action": + + break; + } + } + } + + public void Subscribe(IEnumerable addresses) + { + lock(subscribedSDOs) + foreach (SDOAddr address in addresses) + { + if ( + EthercatService.ECMaster.GetSlave(address.Slave, out ECSlave slave) && + slave.GetDescriptor(address.Index, out SDODescriptor descriptor) + ) + { + subscribedSDOs.Add(descriptor); + Logging.Log("EthercatWebSocket: subscribed: {0}", address); + } + } + Logging.Log("EthercatWebSocket: subscribed now: {0}", subscribedSDOs.Count); + } + public void Unsubscribe(IEnumerable addresses) + { + lock(subscribedSDOs) + foreach (SDOAddr address in addresses) + { + if ( + EthercatService.ECMaster.GetSlave(address.Slave, out ECSlave slave) && + slave.GetDescriptor(address.Index, out SDODescriptor descriptor) + ) + { + subscribedSDOs.Remove(descriptor); + Logging.Log("EthercatWebSocket: unsubscribed: {0}", address); + } + } + Logging.Log("EthercatWebSocket: subscribed now: {0}", subscribedSDOs.Count); + } + + public void SendSubscribedSDOs() + { + JSONArray jsonSDOList = new JSONArray(); + lock(subscribedSDOs) + foreach (SDODescriptor descriptor in subscribedSDOs) + { + if ( + JSONMapper.DefaultMapper.Serialize(descriptor, out JSONValue jsonValue) && + jsonValue is JSONObject jsonDescriptor + ) + { + jsonSDOList.Add(jsonDescriptor); + + JSONArray jsonValues = new JSONArray(); + jsonDescriptor["Values"] = jsonValues; + + byte maxValueIndex = 0; + + foreach (SDOValue sdoValue in descriptor.GetValues()) + { + if (JSONMapper.DefaultMapper.Serialize(sdoValue, out JSONValue jsonSDOValue) && jsonSDOValue is JSONObject jsonSDOValueObject) + { + try{ + if (sdoValue.SubIndex <= maxValueIndex) + { + if (sdoValue.GetRawValue(out byte[] rawValue)) + { + ECDataTypeConverter.FromEthercat(sdoValue.DataType, rawValue, out object value); + jsonSDOValueObject.Add("Value", value); + + if ((descriptor.MaxSubIndex > 0) && (sdoValue.SubIndex == 0)) + { + //Logging.Log(LogLevel.DEBUG, "--> {0}:{1:X4}.{2} = {3}", descriptor.SlaveId, descriptor.Index, sdoValue.SubIndex, jsonSDOValueObject["Value"].ToString()); + maxValueIndex = rawValue[0]; + } + } + } + + } catch (IOException) + { + jsonSDOValueObject.Add("Value", "-"); + } + jsonValues.Add(jsonSDOValue); + } + } + } + } + + JSONObject jsonMessage = Message("sdo", jsonSDOList); + Send(jsonMessage.ToString()); + } + + } +} \ No newline at end of file diff --git a/ln.ethercat.service/ln.ethercat.service.csproj b/ln.ethercat.service/ln.ethercat.service.csproj index 14892f2..678997a 100644 --- a/ln.ethercat.service/ln.ethercat.service.csproj +++ b/ln.ethercat.service/ln.ethercat.service.csproj @@ -7,12 +7,16 @@ - - - - + + + + + + + + diff --git a/ln.ethercat.service/www/html/spa.html b/ln.ethercat.service/www/html/spa.html new file mode 100644 index 0000000..5676f8c --- /dev/null +++ b/ln.ethercat.service/www/html/spa.html @@ -0,0 +1,209 @@ + + + + ln.ethercat Diagnostics Application + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + Slave States: + {{ slave.id }}: {{ slave.state }} + +
+
+
+
+
+ Steuerung + Slave {{ slave.id }} +
+ + +
+ +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/ln.ethercat.service/www/static/css/ln.vue.css b/ln.ethercat.service/www/static/css/ln.vue.css new file mode 100644 index 0000000..fdc7d11 --- /dev/null +++ b/ln.ethercat.service/www/static/css/ln.vue.css @@ -0,0 +1,508 @@ +@charset 'UTF-8'; + +@font-face { + font-family: 'typicons'; + src: url('../fonts/typicons.eot'); + src: url('../fonts/typicons.eot?#iefix') format('embedded-opentype'), + url('../fonts/typicons.woff') format('woff'), + url('../fonts/typicons.ttf') format('truetype'), + url('../fonts/typicons.svg#typicons') format('svg'); + + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'fa-regular'; + src: url('../fonts/fa-regular-400.eot'); + src: url('../fonts/fa-regular-400.eot?#iefix') format('embedded-opentype'), + url('../fonts/fa-regular-400.woff') format('woff'), + url('../fonts/fa-regular-400.ttf') format('truetype'), + url('../fonts/fa-regular-400.svg#typicons') format('svg'); + + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'fa-solid'; + src: url('../fonts/fa-solid-900.eot'); + src: url('../fonts/fa-solid-900.eot?#iefix') format('embedded-opentype'), + url('../fonts/fa-solid-900.woff') format('woff'), + url('../fonts/fa-solid-900.ttf') format('truetype'), + url('../fonts/fa-solid-900.svg#typicons') format('svg'); + + font-weight: normal; + font-style: normal; +} + +* { + flex-grow: 1; +} + +div { + display: inline-block; +} + +h1, h2, h3, h4, h5 { + display: block; +} + +.h1, .h2, .h3, .h4, .h5 { + display: inline-block; + +} + +h1,.h1 { + font-size: 200%; + padding: 8px; +} +h2,.h2 { + font-size: 150%; + padding: 8px; +} +h3,.h3 { + font-size: 110%; + padding: 8px; +} + +.flex { + display: flex; + flex-direction: row; +} + +.flex.column { + flex-direction: column; +} + +.popup { + position: absolute; + top: 100%; + right: 0%; + + width: 100%; + + border: 1px solid black; + background-color: white; + + padding: 8px; +} + +sym { + display: inline-block; + width: 24px; + height: 24px; +} +sym.trash::before { + content: '\f056'; + font-family: 'fa-solid'; + width: 16px; + height: 14px; + color: red; +} + +.no-border { + border: none; + display: inline-block; + padding: 0px; + margin: 0px; +} + +.fa-solid { + font-family: 'fa-solid'; + font-size: 20px; +} + +div.ln-tooltip { + position: absolute; + + padding:5px; + background-color: white; + + border-radius: 0px 6px 6px 6px; + border: 1px solid black; + + opacity: 0.0; + transition: opacity 300ms linear; + + pointer-events: none; +} +div.ln-tooltip[VISIBLE] { + opacity: 1.0; + transition: opacity 300ms linear; +} + +body { + font-family: Arial, Helvetica, sans-serif; + font-size: 12px; +} + +.block { + display: block; +} + +.ln-view { + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + + display: flex; + flex-direction: column; +} + +section#header { + flex-grow: 0; + margin-bottom: 1em; + background-color: whitesmoke; +} + +section#body { + flex-grow: 1; + overflow: auto; +} + +section#footer { + flex-grow: 0; + background-color: whitesmoke; +} + +.json { + white-space: pre; + font-family: 'Courier New', Courier, monospace; + font-size: 12px; +} + +input, div.ln-select > select, div.ln-select, div.ln-upload, textarea { + display: inline-block; + position: relative; + + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + + font-family: Arial, Helvetica, sans-serif; + font-size: 12px; + + border: none; + border-radius: 5px; +} + +input, div.ln-select, div.ln-upload { + width: 180px; + height: 24px; + + padding: 4px; + padding-left: 8px; + + background-color: rgb(231, 246, 255); +} + +textarea { + width: 180px; + height: 48px; + + padding: 4px; + padding-left: 8px; + + background-color: rgb(231, 246, 255); +} + +input.long, div.ln-select.long, div.ln-upload.long, textarea.long, div.ln-slider.long { + width: 360px; +} + +div.ln-select > select { + position: relative; + display: inline-block; + width: 100%; + height: 100%; + background-color: inherit; + + padding: 0px; + margin: 0px; +} +div.ln-select::after { + position: absolute; + display: inline-block; + + top: 4px; + right: 10px; + + content: '\f107'; + + font-family: 'fa-solid'; + font-size: 18px; + + pointer-events: none; +} + +.ln-identity { + position: relative; + display: inline-block; + flex-grow: 0; + flex-shrink: 0; + width: 200px; + + text-align: center; + + padding: 8px; + + font-size: 150%; +} + +.ln-navbar { + display: block; + width: 100%; + + border-top: 1px solid black; + border-bottom: 1px solid black; + + padding-top: 4px; + padding-bottom: 4px; + +} + +.ln-navitem { + position: relative; + display: inline-block; + + font-family: Arial, Helvetica, sans-serif; + font-size: 14px; + + margin-left: 12px; + margin-right: 12px; + +} + +.ln-navitem > a { + font-style: normal; + text-decoration: none; + color: black; +} + +div.ln-navitem > div.ln-nav-children { + position: absolute; + + white-space: pre; + left: 0px; + top: 100%; + opacity: 0.0; + + transition: opacity 300ms; + + border: 1px solid black; + border-top: none; + background-color: white; + + padding: 8px; + + pointer-events: none; +} + +div.ln-navitem:hover > div.ln-nav-children { + opacity: 1.0; + + transition: opacity 300ms; + pointer-events: visible; +} + +div.ln-nav-children > .ln-navitem { + display: block; + margin-top: 4px; + padding-top: 4px; + border-top: 1px solid #C0C0C0; +} +div.ln-nav-children > .ln-navitem:first-of-type { + margin-top: 0px; + border-top: none; +} + +div.ln-statusbar { + position: relative; + bottom: 0px; + left: 0px; + right: 0px; + + border-top: 1px solid black; + + padding: 8px; + + display: flex; + flex-direction: row; +} +div.ln-statusbar > div { + flex-grow: 1; + text-align: center; + + padding: 4px; + padding-left: 8px; + padding-right: 8px; + + border-left: 1px solid #D0D0D0; +} +div.ln-statusbar > div:first-of-type { + text-align: left; + border-left: none; +} + +div.ln-statusbar > div.ln-background-tasks { + flex-grow: 0; +} + +.ln-background-tasks { + position: relative; +} +div.ln-background-tasks > div { + display: block; + position: absolute; + + white-space: pre; + + left: 4px; + bottom: 80%; + + overflow-y: visible; + overflow-x: hidden; + + background-color: blanchedalmond; + border: 1px solid black; + + padding: 4px; + padding-right: 32px; + padding-left: 16px; + + margin-right: 24px; + + opacity: 0; + height: 10px; + + transition: opacity 500ms 50ms, height 0ms 550ms; +} +div.ln-background-tasks:hover > div { + opacity: 1.0; + height: 8em; + + transition: opacity 500ms 50ms, height 500ms 50ms; +} +div.ln-background-tasks > div > div[state="waiting"] { + color: silver; +} +div.ln-background-tasks > div > div[state="failed"] { + color: red; +} +div.ln-background-tasks > div > div[state="ready"] { + color: green; +} + +div.ln-upload { + display: inline-block; + position: relative; + + height: inherit; + + padding-top: 1em; + min-height: 3em; + text-align: center; +} + +div.ln-upload-file { + display: block; + background-color: white; + font-family: 'Courier New', Courier, monospace; + padding: 2px; + + margin-left: 12px; + margin-right: 12px; + + text-align: left; + + border: 1px solid black; + border-radius: 4px; + + margin: 2px; +} + +div.ln-slider { + width: 180px; + text-align: center; +} +input.ln-slider { + display: block; + width: 100%; +} + +input[type=range]::-webkit-slider-thumb { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: 1px solid rgb(80, 188, 255); + border-radius: 6px; + height: 12px; + width: 12px; + + cursor: pointer; + background-color: rgb(80, 188, 255); +} +input[type=range]::-moz-range-thumb { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: 1px solid rgb(80, 188, 255); + border-radius: 6px; + height: 12px; + width: 12px; + + cursor: pointer; + background-color: rgb(80, 188, 255); + +} +input[type=range]::-ms-thumb { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: 1px solid rgb(80, 188, 255); + border-radius: 6px; + height: 12px; + width: 12px; + + cursor: pointer; + + background-color: rgb(80, 188, 255); +} + + + +table.ln-table { + position: relative; + border-collapse: collapse; + + width: 90%; + margin-left: 5%; +} + +table.ln-table td { + padding: 4px; + padding-left: 8px; + padding-right: 8px; +} + +table.ln-table > thead { + position: relative; + + border-bottom: 2px solid black; +} + +table.ln-table > tbody > tr > td { +} + +table.ln-table > tbody > tr:hover > td { + background-color: rgb(231, 246, 255) +} +table.ln-table > tbody > tr[selected] > td { + background-color: rgb(80, 188, 255) +} + + + diff --git a/ln.ethercat.service/www/static/css/spa.css b/ln.ethercat.service/www/static/css/spa.css new file mode 100644 index 0000000..07c54c4 --- /dev/null +++ b/ln.ethercat.service/www/static/css/spa.css @@ -0,0 +1,120 @@ +html, body { + font-family: Arial, Helvetica, sans-serif; + position: relative; +} + +header { + display: block; + position: relative; + width: 100%; + border-bottom: 1px solid black; + padding: 16px 0px; +} + +header > .wrapper { + display: block; + position: relative; + margin: auto; +} + +main, .flex { + display: flex; +} + +main > *, .flex > * { + flex-grow: 1; + margin: 0px 8px; +} + +main > aside, .flex > .aside{ + flex-basis: 25%; + flex-grow: 0; +} + + + +h2 { + border-bottom: 1px solid black; +} + +button, span { + display: inline-block; + font-size: 1rem; + font-style: normal; + text-decoration: none; +} + +fieldset { + position: relative; + display: flex; +} + +fieldset > input { + flex-grow: 1; +} +fieldset > label { + flex-grow: 0; +} + + +.panel { + padding: 6px; +} + +.left > .panel { + padding-right: 24px; + border-right: 1px solid #B0B0B0; +} +.right > .panel { + padding-left: 24px; + border-left: 1px solid #B0B0B0; +} + +.panel > article { + border-bottom: 1px solid #cccccc; + padding: 8px 0px; +} + + +.value { + text-align: right; + font-weight: bold; +} + +.state { + width: 160px; + padding: 8px 16px; + margin: 2px 8px; + border-radius: 8px;; + background-color: #808080; +} + +.state.active { + background-color: #10E010; +} + +.state.active.silver { + background-color: #B0B0B0; +} + +.state.active.green { + background-color: #10E010; +} + +.state.active.orange { + background-color: #e0a810; +} + + +.OPERATIONAL { + background-color: #10E010; +} +.SAFE_OP { + background-color: #e0a810; +} +.PRE_OP { + background-color: #B0B0B0; +} + + + diff --git a/ln.ethercat.service/www/static/fonts/fa-regular-400.eot b/ln.ethercat.service/www/static/fonts/fa-regular-400.eot new file mode 100644 index 0000000..e1bcc44 Binary files /dev/null and b/ln.ethercat.service/www/static/fonts/fa-regular-400.eot differ diff --git a/ln.ethercat.service/www/static/fonts/fa-regular-400.svg b/ln.ethercat.service/www/static/fonts/fa-regular-400.svg new file mode 100644 index 0000000..f32e41e --- /dev/null +++ b/ln.ethercat.service/www/static/fonts/fa-regular-400.svg @@ -0,0 +1,803 @@ + + + + + +Created by FontForge 20190801 at Mon Sep 23 12:53:49 2019 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ln.ethercat.service/www/static/fonts/fa-regular-400.ttf b/ln.ethercat.service/www/static/fonts/fa-regular-400.ttf new file mode 100644 index 0000000..5267d85 Binary files /dev/null and b/ln.ethercat.service/www/static/fonts/fa-regular-400.ttf differ diff --git a/ln.ethercat.service/www/static/fonts/fa-regular-400.woff b/ln.ethercat.service/www/static/fonts/fa-regular-400.woff new file mode 100644 index 0000000..f513c1c Binary files /dev/null and b/ln.ethercat.service/www/static/fonts/fa-regular-400.woff differ diff --git a/ln.ethercat.service/www/static/fonts/fa-regular-400.woff2 b/ln.ethercat.service/www/static/fonts/fa-regular-400.woff2 new file mode 100644 index 0000000..2e72e87 Binary files /dev/null and b/ln.ethercat.service/www/static/fonts/fa-regular-400.woff2 differ diff --git a/ln.ethercat.service/www/static/fonts/fa-solid-900.eot b/ln.ethercat.service/www/static/fonts/fa-solid-900.eot new file mode 100644 index 0000000..c867e7e Binary files /dev/null and b/ln.ethercat.service/www/static/fonts/fa-solid-900.eot differ diff --git a/ln.ethercat.service/www/static/fonts/fa-solid-900.svg b/ln.ethercat.service/www/static/fonts/fa-solid-900.svg new file mode 100644 index 0000000..401b7f7 --- /dev/null +++ b/ln.ethercat.service/www/static/fonts/fa-solid-900.svg @@ -0,0 +1,4667 @@ + + + + + +Created by FontForge 20190801 at Mon Sep 23 12:53:50 2019 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ln.ethercat.service/www/static/fonts/fa-solid-900.ttf b/ln.ethercat.service/www/static/fonts/fa-solid-900.ttf new file mode 100644 index 0000000..16d4469 Binary files /dev/null and b/ln.ethercat.service/www/static/fonts/fa-solid-900.ttf differ diff --git a/ln.ethercat.service/www/static/fonts/fa-solid-900.woff b/ln.ethercat.service/www/static/fonts/fa-solid-900.woff new file mode 100644 index 0000000..608f9e1 Binary files /dev/null and b/ln.ethercat.service/www/static/fonts/fa-solid-900.woff differ diff --git a/ln.ethercat.service/www/static/fonts/fa-solid-900.woff2 b/ln.ethercat.service/www/static/fonts/fa-solid-900.woff2 new file mode 100644 index 0000000..5a60d47 Binary files /dev/null and b/ln.ethercat.service/www/static/fonts/fa-solid-900.woff2 differ diff --git a/ln.ethercat.service/www/static/fonts/typicons.eot b/ln.ethercat.service/www/static/fonts/typicons.eot new file mode 100644 index 0000000..0873b12 Binary files /dev/null and b/ln.ethercat.service/www/static/fonts/typicons.eot differ diff --git a/ln.ethercat.service/www/static/fonts/typicons.svg b/ln.ethercat.service/www/static/fonts/typicons.svg new file mode 100644 index 0000000..63929fe --- /dev/null +++ b/ln.ethercat.service/www/static/fonts/typicons.svg @@ -0,0 +1,1180 @@ + + + + +Created by FontForge 20120731 at Sun Jul 27 14:53:18 2014 + By Stephen Hutchings +(c) Stephen Hutchings 2012 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ln.ethercat.service/www/static/fonts/typicons.ttf b/ln.ethercat.service/www/static/fonts/typicons.ttf new file mode 100644 index 0000000..f7df94b Binary files /dev/null and b/ln.ethercat.service/www/static/fonts/typicons.ttf differ diff --git a/ln.ethercat.service/www/static/fonts/typicons.woff b/ln.ethercat.service/www/static/fonts/typicons.woff new file mode 100644 index 0000000..14dc020 Binary files /dev/null and b/ln.ethercat.service/www/static/fonts/typicons.woff differ diff --git a/ln.ethercat.service/www/static/js/ecapp.js b/ln.ethercat.service/www/static/js/ecapp.js new file mode 100644 index 0000000..3c71602 --- /dev/null +++ b/ln.ethercat.service/www/static/js/ecapp.js @@ -0,0 +1,161 @@ + +class EthercatApplication +{ + + constructor(options){ + this.state = { state: "N/A", slaves: [] }; + this.processdata = []; + this.refresh_interval = 1000; + this.ac_sockets = []; + + this.subscriptions = {}; + this.subscribed = { + sdo: [] + } + + this.data = { + slaves: { + } + }; + + let pageURI = window.location; + let scheme = pageURI.scheme == "https" ? "wss:" : "ws:"; + let host = pageURI.host; + this.base_url = scheme + "//" + host; + + this.connectMainSocket(); + } + + connectMainSocket(){ + this.socket = this.connectWebSocket("/api/v1/socket", true); + this.socket.onmessage = (evt)=>{ + let message = JSON.parse(evt.data); + switch (message.event) + { + case "state": + this.applyState(message.value); + break; + case "pdo": + this.processdata = message.value; + break; + case "sdo": + this.subscribed.sdo = message.value; + break; + default: + //console.log("unknown event received: " + message.event); + break; + } + }; + this.socket.onerror = (evt)=>{ + console.log("weboscket error: ", evt); + this.socket.close(); + }; + this.socket.onclose = (evt)=>{ + console.log("weboscket closed: ", evt); + if (evt.target == this.socket) + setTimeout(()=>this.connectMainSocket(), 1000); + }; + } + + connectWebSocket(path, keep){ + let ws = new WebSocket(this.base_url + path); + if (!keep) + { + console.log("add to dispose list:", ws); + this.ac_sockets.push(ws); + } + return ws; + } + + closeWebSockets(){ + while (this.ac_sockets.length) + { + let ws = this.ac_sockets.pop(); + console.log("disposing:", ws); + ws.close(); + } + } + + sendMessage(eventName, o){ + this.socket.send(JSON.stringify({ + event: eventName, + value: o + })); + } + + applyState(state){ + this.state = state; + } + + POST(url, o){ + return fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(o) + }) + } + + getSDOList(slave_id){ + if (!this.data.slaves[slave_id]) + this.data.slaves[slave_id] = {}; + if (!this.data.slaves[slave_id].sdolist) + { + this.data.slaves[slave_id].sdolist = []; + + fetch("/api/v1/slaves/" + slave_id + "/sdo") + .then((b) => { + b.json() + .then((json)=>{ + json.forEach((sdo)=>{ + this.data.slaves[slave_id].sdolist.push(sdo); + }); + }); + }); + } + return this.data.slaves[slave_id].sdolist; + } + + subscribe(slave, sdo_index){ + slave = parseInt(slave); + if (!this.subscriptions[slave]) this.subscriptions[slave] = {}; + if (!Object.keys(this.subscriptions[slave]).includes(sdo_index)) + { + this.subscriptions[slave][sdo_index] = 1; + this.sendMessage("subscribe", [ + { + Slave: slave, + Index: sdo_index, + SubIndex: 0, + }, + ]); + } else { + this.subscriptions[slave][sdo_index]++; + } + } + + unsubscribe(slave, sdo_index){ + slave = parseInt(slave); + if (this.subscriptions[slave] && Object.keys(this.subscriptions[slave]).includes(sdo_index)) + { + this.subscriptions[slave][sdo_index]--; + if (!this.subscriptions[slave][sdo_index]) + { + this.sendMessage("unsubscribe", [ + { + Slave: slave, + Index: sdo_index, + SubIndex: 0, + }, + ]); + } + } + + } + + requestState(slave_id, state){ + this.POST("/api/v1/slaves/" + slave_id + "/state", { state }); + } + +} \ No newline at end of file diff --git a/ln.ethercat.service/www/static/js/lib/ln.base64.js b/ln.ethercat.service/www/static/js/lib/ln.base64.js new file mode 100644 index 0000000..d330541 --- /dev/null +++ b/ln.ethercat.service/www/static/js/lib/ln.base64.js @@ -0,0 +1,84 @@ +(function(){ + /*\ + |*| + |*| Base64 / binary data / UTF-8 strings utilities (#1) + |*| + |*| https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding + |*| + |*| Author: madmurphy + |*| + |*| Harald Wolff-Thobaben: Small adaptions to create a static class + |*| + \*/ + class Base64 + { + constructor(){ + } + + static b64ToUint6(nChr){ + return nChr > 64 && nChr < 91 ? + nChr - 65 + : nChr > 96 && nChr < 123 ? + nChr - 71 + : nChr > 47 && nChr < 58 ? + nChr + 4 + : nChr === 43 ? + 62 + : nChr === 47 ? + 63 + : + 0; + } + static uint6ToB64(nUint6){ + return nUint6 < 26 ? + nUint6 + 65 + : nUint6 < 52 ? + nUint6 + 71 + : nUint6 < 62 ? + nUint6 - 4 + : nUint6 === 62 ? + 43 + : nUint6 === 63 ? + 47 + : + 65; + } + + static encode(aBytes){ + var eqLen = (3 - (aBytes.length % 3)) % 3, sB64Enc = ""; + + for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { + nMod3 = nIdx % 3; + nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); + if (nMod3 === 2 || aBytes.length - nIdx === 1) { + sB64Enc += String.fromCharCode(Base64.uint6ToB64(nUint24 >>> 18 & 63), Base64.uint6ToB64(nUint24 >>> 12 & 63), Base64.uint6ToB64(nUint24 >>> 6 & 63), Base64.uint6ToB64(nUint24 & 63)); + nUint24 = 0; + } + } + + return eqLen === 0 ? + sB64Enc + : + sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? "=" : "=="); + } + + static decode(sBase64, nBlockSize) { + var + sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length, + nOutLen = nBlockSize ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockSize) * nBlockSize : nInLen * 3 + 1 >>> 2, aBytes = new Uint8Array(nOutLen); + + for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { + nMod4 = nInIdx & 3; + nUint24 |= Base64.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; + if (nMod4 === 3 || nInLen - nInIdx === 1) { + for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { + aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; + } + nUint24 = 0; + } + } + return aBytes; + } + } + LN.$add("LN.Base64",Base64); +})(); \ No newline at end of file diff --git a/ln.ethercat.service/www/static/js/lib/ln.identities.js b/ln.ethercat.service/www/static/js/lib/ln.identities.js new file mode 100644 index 0000000..3e35039 --- /dev/null +++ b/ln.ethercat.service/www/static/js/lib/ln.identities.js @@ -0,0 +1,51 @@ +(function(){ + + class LNIdentity + { + constructor(src){ + if (!src) + src = {}; + this.IdentityName = src.IdentityName || ""; + this.UniqueID = src.UniqueID || null; + this.Roles = src.AssignedRoles || []; + } + + findRolesByName(identityName){ + let roleMask = 0; + this.Roles.forEach(role => { + if (role.IdentityName == identityName) + roleMask = role.Roles; + }); + return roleMask; + } + findRolesByID(identityUniqueID){ + let roleMask = 0; + this.Roles.forEach(role => { + if (role.UniqueID == identityUniqueID) + roleMask = role.Roles; + }); + return roleMask;; + } + + hasRole(role,identityName){ + let roles = this.findRolesByName(identityName); + return (roles & role) == role; + } + + } + + LNIdentity.VIEW = (1<<0); + LNIdentity.USE = LNIdentity.VIEW | (1<<1); + LNIdentity.CONTROL = LNIdentity.VIEW | (1<<2); + LNIdentity.MANAGE = LNIdentity.CONTROL | (1<<3); + LNIdentity.ADMIN = 0x0000FFFF; + + LNIdentity.MANAGEROLES = (1<<16); + + LNIdentity.IMPERSONATE = (1<<24); + LNIdentity.OWN = 0x0FFFFFFF; + LNIdentity.BE = 0x0000FFFF; + LNIdentity.SUPER = 0x7FFFFFFF; + + LN.$add("LN.Identity",LNIdentity); +})(); \ No newline at end of file diff --git a/ln.ethercat.service/www/static/js/lib/ln.promise.js b/ln.ethercat.service/www/static/js/lib/ln.promise.js new file mode 100644 index 0000000..59db42d --- /dev/null +++ b/ln.ethercat.service/www/static/js/lib/ln.promise.js @@ -0,0 +1,59 @@ +(function(){ + class LNPromise + { + constructor(executor, label){ + this.promise = new Promise( + (resolve,reject) => { + this.resolve = (v) => { + this._s.state = "ready"; + resolve(v); + this.release(); + }, + this.reject = (e) => { + this._s.state = "failed"; + reject(e); + this.release(); + } + executor && executor( + this.resolve, + this.reject + ); + } + ); + + if (!label) + label = "N.D."; + + this._s = { + label, + state: "waiting" + }; + + this.idx = LN.$unique(); + LNPromise.$current[this.idx] = this._s; + } + + label(){ + return this._s.label; + } + + state(){ + return this._s.state; + } + + release(){ + setTimeout(()=>{ + Vue.delete(LNPromise.$current, this.idx); + },1000); + } + + then(){ + return this.promise.then.apply(this.promise, arguments); + } + + static getCurrentPromises(){ return LNPromise.$current; } + } + LNPromise.$current = {}; + + LN.$add("LN.Promise",LNPromise); +})(); \ No newline at end of file diff --git a/ln.ethercat.service/www/static/js/lib/ln.vue.components.js b/ln.ethercat.service/www/static/js/lib/ln.vue.components.js new file mode 100644 index 0000000..a8475fe --- /dev/null +++ b/ln.ethercat.service/www/static/js/lib/ln.vue.components.js @@ -0,0 +1,724 @@ +(function(){ + +let nextTooltipKey = 0; +var tooltipData = {}; + +var tootlipVisible = false; +var tooltipEl = LN.$(`
`); + + + +function findTooltipData(el){ + let tooltip = null; + while (!tooltip) + { + tooltip = tooltipData[el.dataset['tooltip']]; + el = el.parentElement; + } + return tooltip; +} + +function tooltipShow(ev,tooltip){ + tooltipEl.innerText = tooltip.value; + tooltipEl.setAttribute("VISIBLE",""); + + tootlipVisible = true; +} +function tooltipHide(ev,tooltip){ + tooltipEl.removeAttribute("VISIBLE"); + tootlipVisible = false; +} + +function tooltipMouseOver(ev){ + if (!document.body.contains(tooltipEl)){ + document.body.appendChild(tooltipEl); + LN.$idle(()=>{ tooltipMouseOver(ev);}); + return; + } + + let tooltip = findTooltipData(ev.target); + if (!tooltip) + console.log(ev); + + if (tooltipEl){ + tooltipEl.style.left = `${ev.pageX+3}px`; + tooltipEl.style.top = `${ev.pageY+3}px`; + } + + if (tooltip.timeout) + clearTimeout(tooltip.timeout); + if (tootlipVisible){ + tooltip.timeout = setTimeout(() => { + tooltipHide(ev,tooltip); + }, (tooltip.delay ? (tooltip.delay / 4) : 200)); + } else { + tooltip.timeout = setTimeout(() => { + tooltipShow(ev,tooltip); + }, tooltip.delay || 800); + } +} + +function tooltipMouseOut(ev){ + let tooltip = findTooltipData(ev.target); + if (tooltip.timeout) + clearTimeout(tooltip.timeout); + tooltipHide(ev,tooltip); +} + +Vue.directive('tooltip',{ + bind: function(el, binding, vnode){ + let tooltip = { + value: binding.value, + timeout: null, + delay: null, + }; + let tooltipKey = nextTooltipKey++; + tooltipData[tooltipKey] = tooltip; + el.dataset['tooltip'] = tooltipKey; + + el.addEventListener('mousemove',tooltipMouseOver); + el.addEventListener('mouseout',tooltipMouseOut); + + }, + update: function(el, binding, vnode, oldVnode){ + let tooltip = findTooltipData(el); + tooltip.value = binding.value; + }, + unbind: function(el, binding, vnode){ + el.removeEventListener('mouseover',tooltipMouseOver); + el.removeEventListener('mouseout',tooltipMouseOut); + }, +}); + + +Vue.component('ln-statusbar',{ + data: function(){ + return { + current: LN.Promise.getCurrentPromises(), + }; + }, + computed: { + CurrentPromises: function(){ + return this.current; + } + }, + template: ` +
+
{{ LNVue.$_.statusText }}
+
+ NOT LOGGED IN + + {{ LNVue.$_.identity.IdentityName }} + +
+
{{ LNVue.$_.socket && LNVue.$_.socket._state }}
+
{{ Object.keys(CurrentPromises).length || "No" }} Background Tasks +
+
{{ promise.label }}
+
+
+
{{ LNVue.$_.Version() }}
+
+ `, +}); + +Vue.component('ln-navitem',{ + props: { + value: { + type: Object, + required: true, + }, + key: String, + }, + template: ` +
+ + {{ value.label || value.path }} + + {{ value.label }} +
+ +
+
+`, +}); + +Vue.component('ln-navbar',{ + props: { + value: { + type: Object, + default: function(){ + return LNVue.$_.navigation; + } + }, + component: { + type: [ Object, String ], + default: 'ln-navitem', + }, + }, + template: ` +
+
{{ item.label || item.path }}
+
` +}); + +Vue.component('ln-textfield',{ + props: { + value: { + type: String, + required: true, + }, + }, + template: ``, +}); +Vue.component('ln-password',{ + props: { + value: { + type: String, + required: true, + }, + }, + template: ``, +}); + +Vue.component('ln-textarea',{ + props: { + value: { + type: String, + required: true, + }, + }, + template: ``, +}); + +Vue.component('ln-number',{ + model: { + prop: "value", + event: "input", + }, + props: { + value: { + required: false, + type: Number, + }, + float: { + type: Boolean, + default: false, + }, + }, + + template: ` +`, +}); + +Vue.component('ln-slider',{ + model: { + prop: 'value', + event: 'change', + }, + props: { + value: { + type: Number, + default: 1, + }, + min: { + type: Number, + default: 0, + }, + max: { + type: Number, + default: 100, + }, + step: { + type: Number, + default: 1, + }, + islogarithmic: { + type: Boolean, + default: false, + }, + values: { + default: null, + } + }, + data: function(){ + let d = { + }; + + if (this.values instanceof Array){ + this.min = 0; + this.max = this.values.length - 1; + this.step = 1; + } else if (this.values instanceof Object){ + this.min = 0; + this.max = Object.keys(this.values).length - 1; + this.step = 1; + } else if (this.islogarithmic){ + /* ToDo: Implement */ + } else { + + } + +// console.log(`min=${this.min} max=${this.max} step=${this.step} value=${this.value}`); + + return d; + }, + computed: { + keys: function(){ + if (this.values instanceof Array) + { + let keys = new Array(this.values.length); + for (let n=0;n + {{ displayValue }} +`, + +}); + +Vue.component("ln-file",{ + props: { + file: { + type: Object, + required: true, + }, + remove: { + type: Function, + required: false, + } + }, + methods: { + }, + template: `
{{ file.name }} ({{ file.size }}bytes) + +
` +}); + +Vue.component('ln-upload',{ + model: { + prop: "files", + event: "change", + }, + props: { + maxSize: { + type: Number, + default: 0, + }, + files: { + type: Array, + default: [], + } + }, + data: function(){ return { maxSize: this.maxSize, files: this.files, }; }, + methods: { + drop: function(dropEvent){ + dropEvent.preventDefault(); + + for (let n=0;nDateien hierher ziehen oder auswählen:
+
+ + + `, +}); + +Vue.component('ln-select',{ + model: { + prop: "value", + event: "change", + }, + props: { + items: { + required: true, + }, + value: { + required: true, + }, + empty: { + type: Boolean, + default: true, + }, + render: { + type: Function, + default: function(value){ + return value; + } + }, + key: { + type: Function, + default: function(key,value){ + return key; + } + } + }, + computed: { + prepared: { + get: function(){ + let prepared = {}; + LN.$each(this.items,(element,index) => { + prepared[index] = element; + }); + return prepared; + }, + }, + }, + methods: { + changed: function(ev){ + this.$emit("change", ev.target.value ); + }, + }, + + template: `
+ +
+`, +}); + +Vue.component('ln-identity',{ + data: function(){ + return { + popupVisible: false, + }; + }, + methods: { + logout(){ + LNVue.$_.authenticate("",null,"",""); + this.popupVisible = false; + }, + popup(){ + this.popupVisible = !this.popupVisible; + }, + }, + template: `
+
+ {{ LNVue.$_.identity.IdentityName }} +
+
Not logged in +
+ + +
`, +}); + +Vue.component('ln-popup',{ + props: { + title: String, + visible: { + type: Boolean, + default: false + } + }, + methods: { + show: function(){ + this.visible = true; + }, + hide: function(){ + this.visible = false; + } + }, + template: `
+
{{ title }}
+
+ +
+ +
+
+ `, +}); + +Vue.component('ln-login-pane',{ + props: { + }, + methods: { + stage1: function(){ + console.log("login stage1", this.identityName); + + LNVue.$_ + .requestChallenges(this.identityName) + .then((challenges)=>{ + challenges = challenges.message.Challenges; + if (challenges.length > 0){ + LNVue.$_.identity.IdentityName = this.identityName; + LNVue.$_.identity.challenges = challenges; + } + }); + }, + logout(){ + LNVue.$_.authenticate("",null,"",""); + }, + authenticateSeededPassword(challenge){ + let encoder = new TextEncoder(); + let seed = LNVue.decodeHex(challenge.AuthenticationParameters); + let password = encoder.encode(challenge.prove); + + let secretSource = ArrayBuffer.combine(seed, password, seed); + + crypto.subtle.digest("SHA-256",secretSource) + .then((secret)=>{ + let challengebytes = LN.Base64.decode(challenge.Challenge); + secret = new Uint8Array(secret); + + let proveSource = ArrayBuffer.combine(challengebytes, secret, challengebytes); + + crypto.subtle.digest("SHA-256",proveSource) + .then((prove)=>{ + prove = LN.Base64.encode(new Uint8Array(prove)); + LNVue.$_.authenticate(this.identityName,challenge.SecureAttributeID,challenge.Challenge,prove); + }); + }); + + } + + }, + data: ()=>{ + return { + identityName: "", + }; + }, + template: `
+
+
Logged in as
+ {{ LNVue.$_.identity.IdentityName }} + +
+
+
+ + + +
+
+
+
+
+ +
{{ challenge.SecureAttributeLabel }} + + + +
+
+
+
`, +}); + + +})(); \ No newline at end of file diff --git a/ln.ethercat.service/www/static/js/lib/ln.vue.js b/ln.ethercat.service/www/static/js/lib/ln.vue.js new file mode 100644 index 0000000..ac3cf61 --- /dev/null +++ b/ln.ethercat.service/www/static/js/lib/ln.vue.js @@ -0,0 +1,274 @@ +(function (){ + + let exModule = { + label: 'Example Module', + routes: [ + { + path: '/about', + template: `This is about this example!`, + } + ], + navigation: { + '99': { + label: 'About', + href: '/about' + } + } + + + + }; + + let defaultOptions = { + element: 'body', + modules: [ exModule, ], + }; + + class LNVue + { + constructor(el,options = {}){ + this.options = Object.assign({ + routes: [], + data: {}, + }, options ); + + this._el = el; + this.data = Object.assign({}, options.data, { LNVue: this, msg: "Hello World" }); + this.promises = []; + + this.statusText = "LNVue preparing"; + + Vue.prototype.$LNVue = this; + LNVue.$_ = this; + + this.navigation = {}; + this.identity = new LN.Identity(); + + Promise + .all(LNVue.promises) + .then(()=>{ + this.status("LNVue: starting"); + + LNVue.vueRouter.addRoutes([{ + path: "*", + component: { + template: `

404 Not Found

The URL you tried to reach is not existing.`, + }, + }]); + }, + (cause)=>{ + this.status("LNVue: start failed: " + cause); + }); + + this.vue = null; + + LNVue.$instance.resolve(this); + } + + Start(){ + Promise + .all(this.promises) + .then(()=>{ + LN.$idle(()=>{ + this.vue = new Vue({ + el: this._el, + data: this.data, + router: LNVue.vueRouter, + }); + }); + }); + LN.$idle(()=>{ + this.socket = new LN.Vue.WebSocket(this); + this.socket.open(); + }); + LN.$idle(()=>{ + LNVue.$start.resolve(this); + }); + } + + storage(){ + return window.localStorage; + } + + sessionID(){ + if (arguments.length == 1){ + this.storage().setItem("LNVueSessionID",arguments[0]); + console.log("LNVue.SID <= " + arguments[0]); + return this; + } else + { + let sid = this.storage().getItem("LNVueSessionID"); + console.log("LNVue.SID == " + sid); + if (!sid) + { + sid = "00000000-0000-0000-0000-000000000000"; + } + return sid; + } + } + + Version(){ return "0.2alpha"; }; + getCurrentPromises() { + return LN.Promise.getCurrentPromises(); + } + + status(){ + if (arguments.length == 1){ + this.statusText = arguments[0]; + return this; + } else if (arguments.length == 0){ + return this.statusText; + } else + throw "LNVue.status(): too many arguments"; + } + + addModule(modSpec){ + if (modSpec.navigation instanceof Object){ + LNVue.deepAssign(modSpec.navigation,this.navigation); + } + + LN.$each(modSpec.routes,(key,route)=>{ + if ((route instanceof Object) && route.url) + { + let p = new LN.Promise((resolve,reject)=>{ + LN.$fetch(route.url) + .then((src)=>{ + this.addRoute(key,{ template: src, data: ()=>{ return this.data; }, }); + resolve(); + }, + (cause)=>{ + console.log("loading route.url failed: ",cause); + }); + },`addModule(${route.url})`); + this.promises.push(p); + } else if (route instanceof Object){ + this.addRoute(key,{ template: route.template, data: ()=>{ return this.data; }, } ); + } else { + this.addRoute(key,{ template: route, data: ()=>{ return this.data; }, } ); + } + }); + } + + addRoute(path,component){ + LNVue.vueRouter.addRoutes([ + { path, component, }, + ]); + + if (this.vue){ + let route = this.vue.$route; + LNVue.vueRouter.replace("/"); + LNVue.vueRouter.replace(route); + } + } + + /* Authentication API */ + + requestChallenges(identityName,secureAttributeTypeName){ + return new Promise((resolve,reject)=>{ + this.socket.request("AuthenticationRequest",{ + IdentityName: identityName, + SecureAttributeTypeName: secureAttributeTypeName, + }) + .then((challenges)=>{ + resolve(challenges); + }, + (error)=>{ + console.log("Login challenges could not be retrieved", error); + } + ); + }); + } + + authenticate(identityName,secureAttributeID,challenge,prove){ + let authenticationProve = { + IdentityName: identityName, + SecureAttributeUniqueID: secureAttributeID, + Challenge: challenge, + Prove: prove, + }; + this.socket.request("AuthenticationProve", authenticationProve) + .then((identity)=>{ + this.identity = new LN.Identity(identity.message); + }, + (error)=>{ + this.identity = new LN.Identity(); + }); + } + + rpc(moduleName,methodName,parameters){ + return new Promise((resolve,reject)=>{ + let rpcCall = { + module: moduleName, + method: methodName, + parameters: parameters + }; + this.socket.request("RPCCall",rpcCall) + .then( + (result)=>{ + if (result.message.error) + { + console.log("rpc call failed", result.message.error); + reject(result.message.error); + } + else + resolve(result.message.Result); + }, + (error)=>{ + console.log("rpc failed", error); + reject(error); + } + ); + }); + } + + static $LNVue(){ + return LNVue.$_; + } + } + LNVue.$instance = new LN.Promise(()=>{},'LN.Vue Instance Promise'); + LNVue.$start = new LN.Promise(()=>{},'LN Vue Startup Promise'); + + + + LNVue.vueRouter = new VueRouter({ + mode: 'history', + routes: [], + }); + + LNVue.deepAssign = function(source,target){ + LN.$each(source,function(key){ + if (target[key] instanceof Object){ + LNVue.deepAssign(src[key],target[key]); + } else { + target[key] = source[key]; + } + }); + } + + LNVue.routes = []; + LNVue.promises = []; + + LNVue.encodeHex = (bytes) => bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), ''); + LNVue.decodeHex = (hexString) => new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16))); + + ArrayBuffer.combine = function(...args){ + let byteLength = 0; + args.forEach((arg,index)=>{ + byteLength = byteLength + arg.byteLength; + }); + + let result = new Uint8Array(byteLength); + let p = 0; + args.forEach((arg,index)=>{ + for (let n=0;n + + + {{ column.label }} + + + + + {{ row[key] }} + + + + + +`, +}); + +})(); diff --git a/ln.ethercat.service/www/static/js/lib/ln.vue.webauthn.js b/ln.ethercat.service/www/static/js/lib/ln.vue.webauthn.js new file mode 100644 index 0000000..36c1bd6 --- /dev/null +++ b/ln.ethercat.service/www/static/js/lib/ln.vue.webauthn.js @@ -0,0 +1,45 @@ +(function(){ + +// LNVue.prototype.createCredentials = function(_challenge,rp,userId,crossplatform,attestation){ +// crossplatform = crossplatform ? true : false; +// if (!attestation) +// attestation = "none"; + +// let credCreateOptions = { +// challenge: Uint8Array.from(_challenge, c => c.charCodeAt(0)), +// rp: { +// id: rp, +// name: 'LN.Vue Demo Application', +// }, +// user: { +// id: Int8Array.from(userId, c => c.charCodeAt(0)), +// name: 'SomeUser', +// displayName: "Some user for now...", +// }, +// pubKeyCredParams: [ +// { +// type: 'public-key', +// alg: -7, +// } +// ], +// authenticatorSelection: { +// authenticatorAttachment: crossplatform ? 'cross-platform' : 'platform', +// }, +// timeout: 60000, +// attestation: attestation, +// }; + +// console.log(credCreateOptions); +// navigator.credentials.create({ +// publicKey: credCreateOptions +// }); + +// }; + +// LNVue.prototype.createAuthToken = function(){ + +// }; + + + +})(); diff --git a/ln.ethercat.service/www/static/js/lib/ln.vue.websocket.js b/ln.ethercat.service/www/static/js/lib/ln.vue.websocket.js new file mode 100644 index 0000000..2e6a8b4 --- /dev/null +++ b/ln.ethercat.service/www/static/js/lib/ln.vue.websocket.js @@ -0,0 +1,137 @@ +(function(){ + class LNVueWebSocket + { + constructor(lnvue,o){ + this.LNVue = lnvue; + this.options = Object.assign({},o); + + if (!this.options.url) + this.options.url = this.constructURL(); + + this._id = 1; + this.defaultTimeout = 30000; + this.websocket = null; + this.callbacks = {}; + + this._state = "initialized"; + this._retry = null; + + this.closing = false; + } + + constructURL(){ + var pageURI = window.location; + + var scheme = pageURI.scheme == "https" ? "wss:" : "ws:"; + var host = pageURI.host; + + return scheme + "//" + host + "/socket"; + } + + open(){ + if (this._retry){ + clearTimeout(this._retry); + } + this.closing = false; + + this.websocket = new WebSocket(this.options.url); + this.websocket.onopen = (e) =>{ + console.log("WebSocket connected"); + this._state = "ONLINE"; + + let WSHello = { + ApplicationSessionID: this.LNVue.sessionID(), + }; + console.log("WSHello request",WSHello); + + this.request("WSHello",WSHello) + .then((wsh)=>{ + console.log("WSHello response",wsh); + this.LNVue.sessionID(wsh.message.ApplicationSessionID); + this.LNVue.identity = new LN.Identity(wsh.message.SessionIdentity); + }); + }; + this.websocket.onclose = (e)=>{ this._onclose(e); this._state = "OFFLINE"; }; + this.websocket.onerror = (e)=>{ this._onerror(e); this._state = "ERROR"; }; + this.websocket.onmessage = (e)=>{ this._onmessage(e); }; + return this; + } + + close(){ + if (this.websocket){ + this.closing = true; + this.websocket.close(200,"close() called"); + } + } + + request(msgtype,msg,timeout){ + let message = { + id: this._id++, + type: msgtype, + message: msg, + } + + if (!timeout) + timeout = this.defaultTimeout; + + if (timeout != -1){ + return new Promise((resolve,reject)=>{ + let to = setTimeout(()=>{ + delete this.callbacks[message.id]; + reject("timed out"); + },timeout); + + this.callbacks[message.id] = (msgtype,msg)=>{ + clearTimeout(to); + delete this.callbacks[message.id]; + if (msgtype == "error") + reject(msg); + else + resolve({type: msgtype,message: msg}); + }; + + this.websocket.send( + JSON.stringify(message) + ); + }); + } else { + new Promise((resolve,reject)=>{ + this.websocket.send( + JSON.stringify(message) + ); + resolve(); + }); + } + + } + + + _onclose(evt){ + this.websocket = null; + this.options.onclose && this.options.onclose(evt); + if (!this.closing) + { + this._retry = setTimeout(() => { + this._retry = null; + console.log("reconnect...") + this.open(); + }, 5000); + } + } + _onerror(evt){ + this.options.onerror && this.options.onerror(evt); + } + _onmessage(evt){ + try + { + let j = JSON.parse(evt.data); + let cb = this.callbacks[ j.id ]; + cb && cb(j.type,j.message); + } catch(exc){ + console.log(exc,evt.data); + } + + } + } + LN.$add("LN.Vue.WebSocket",LNVueWebSocket); +})(); diff --git a/ln.ethercat.service/www/static/js/ln.js b/ln.ethercat.service/www/static/js/ln.js new file mode 100644 index 0000000..3813f1f --- /dev/null +++ b/ln.ethercat.service/www/static/js/ln.js @@ -0,0 +1,112 @@ +(function(globals){ + let _unique = new Date().getTime(); + let __idles__ = []; + + class LN + { + constructor(opts){ + } + + static $each(oa,cb){ + if (oa instanceof Array){ + let result = []; + oa.forEach((value,index)=>{ + result.push(cb.call(value,index,value)); + }); + return result; + } else if (oa instanceof Object){ + let result = {}; + Object.keys(oa).forEach((key)=>{ + if (oa.hasOwnProperty(key)){ + result[key] = cb.call(oa[key],key,oa[key]); + } + }); + return result; + } + } + static $idle(cb,thisval = null){ + let scheduled = __idles__.length > 0; + let n=0; + + for (;n<__idles__.length;n++){ + let idle = __idles__[n]; + if ((idle[0] == cb) && (idle[1] == thisval)) + break; + } + if (n == __idles__.length) + __idles__.push([cb,thisval]); + + if (!scheduled) + setTimeout(()=>{ + while (__idles__.length > 0){ + let idle = __idles__.pop(); + idle[0].call(idle[1]); + } + },0); + } + static $unique(){ + return _unique++; + } + static $fetch(url,cb){ + return new LN.Promise((resolve,reject)=>{ + fetch(url) + .then((response => { + if (response.status.toString().startsWith("2")) + { + let t = response.text(); + cb && cb(t,null); + resolve(t); + } else { + cb && cb(null, response.statusText); + reject(response.statusText); + } + })); + }, `fetch(${url})`); + } + + static $resolve(v){ + if (v instanceof Function) + return v(); + return v; + } + + static $add(classpath,c){ + let p = classpath.split("."); + if (p.length < 1) + throw "invalid classpath"; + + let container = globals; + while (p.length > 1){ + let next = p.shift(); + if (!container[next]) + container[next] = {} + container = container[next]; + } + + let prev = container[p[0]]; + container[p[0]] = c; + + if (prev) + { + LN.$each(prev,(key,value)=>{ + c[key] = value; + }); + } + }; + + + static $(selector){ + if (selector.startsWith("<")) + { + let el = document.createElement("parse"); + el.innerHTML = selector; + return el.firstChild; + } else { + throw "NotImplemented"; + } + } + } + LN.prototypes = {}; + + LN.$add("LN", LN); +})(window); diff --git a/ln.ethercat.service/www/static/js/pdopanel.js b/ln.ethercat.service/www/static/js/pdopanel.js new file mode 100644 index 0000000..e52b9a7 --- /dev/null +++ b/ln.ethercat.service/www/static/js/pdopanel.js @@ -0,0 +1,27 @@ +(function(){ + + Vue.component('pdo-panel',{ + props: { + processdata: { + type: Array, + required: true, + }, + }, + computed: { + }, + data: function(){ + return {} + }, + template: ` +
+

Prozessdaten

+
+
{{sdo.Descriptor.Index.toString(16).toUpperCase()}}.{{sdo.SubIndex}} {{ sdo.Name }}
+
{{ sdo.Value }}
+
+
+ `, + }); + + +})(); \ No newline at end of file diff --git a/ln.ethercat.service/www/static/js/sdopanel.js b/ln.ethercat.service/www/static/js/sdopanel.js new file mode 100644 index 0000000..23b2d81 --- /dev/null +++ b/ln.ethercat.service/www/static/js/sdopanel.js @@ -0,0 +1,35 @@ +(function(){ + + Vue.component('sdo-panel',{ + props: { + subscribed: { + type: Array, + required: true, + default: [] + }, + }, + computed: { + }, + data: function(){ + return {} + }, + template: ` +
+

SDO Panel

+
+

{{sdo.Index.toString(16).toUpperCase()}} {{ sdo.Name }} ({{ sdo.DataType}} / M:{{ sdo.MaxSubIndex }})

+
    +
    + .{{sdovalue.SubIndex}} {{ sdovalue.Name }} ({{ sdo.DataType}}) + +
    +
+
+
+ `, + }); + + +})(); \ No newline at end of file diff --git a/ln.ethercat.service/www/static/js/sdoselect.js b/ln.ethercat.service/www/static/js/sdoselect.js new file mode 100644 index 0000000..a1acb25 --- /dev/null +++ b/ln.ethercat.service/www/static/js/sdoselect.js @@ -0,0 +1,46 @@ +(function(){ + + Vue.component('sdo-select',{ + props: { + descriptors: { + type: Array, + required: true, + }, + }, + data: function(){ + return { + filters: { + slave: null, + name: "", + index: null, + datatype: null, + }, + selectedIndex: null, + }; + }, + methods: { + getFilteredDescriptors: function(){ + return this.descriptors.filter((sdo)=> + (!this.filters.name || sdo.name.toLowerCase().includes(this.filters.name.toLowerCase())) ); // && + // (!this.filters.index || sdo.index.toString(16).toLowerCase().includes(this.filters.index.toLowerCase())) && + // (!this.filters.datatype || sdo.datatype.toLowerCase().includes(this.filters.datatype.toLowerCase())) && + // (!this.filters.slave || sdo.address.slave.toLowerCase().includes(this.filters.slave.toLowerCase())) + // ); + }, + }, + template: ` + + + + + `, + }); + + +})(); \ No newline at end of file diff --git a/ln.ethercat.service/www/static/js/sdotable.js b/ln.ethercat.service/www/static/js/sdotable.js new file mode 100644 index 0000000..ef0400b --- /dev/null +++ b/ln.ethercat.service/www/static/js/sdotable.js @@ -0,0 +1,72 @@ +(function(){ + + Vue.component('sdo-table',{ + props: { + sdolist: { + type: Array, + required: true, + }, + }, + computed: { + }, + data: function(){ + return { + filters: { + slave: null, + name: null, + index: null, + datatype: null, + } + }; + }, + methods: { + getFilteredSDOList: function(){ + return this.sdolist.filter((sdo)=> + (!this.filters.name || sdo.Name.toLowerCase().includes(this.filters.name.toLowerCase())) && + (!this.filters.index || sdo.Address.Index.toString(16).toLowerCase().includes(this.filters.index.toLowerCase())) && + (!this.filters.datatype || sdo.DataType.toLowerCase().includes(this.filters.datatype.toLowerCase())) && + (!this.filters.slave || sdo.Address.Slave.toLowerCase().includes(this.filters.slave.toLowerCase())) + ); + }, + }, + template: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#SlaveNameIndexSubIndexData TypeCurrent Value
#
{{ sdo.Address.Slave }}{{ sdo.Name }}0x{{ sdo.Address.Index.toString(16) }}{{ sdo.Address.SubIndex }}{{ sdo.DataType }}{{ sdo.Value }}
+ `, + }); + + +})(); \ No newline at end of file diff --git a/ln.ethercat.service/www/static/js/vue/vue-router.js b/ln.ethercat.service/www/static/js/vue/vue-router.js new file mode 100644 index 0000000..fa6ff7c --- /dev/null +++ b/ln.ethercat.service/www/static/js/vue/vue-router.js @@ -0,0 +1,2890 @@ +/*! + * vue-router v3.1.3 + * (c) 2019 Evan You + * @license MIT + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.VueRouter = factory()); +}(this, function () { 'use strict'; + + /* */ + + function assert (condition, message) { + if (!condition) { + throw new Error(("[vue-router] " + message)) + } + } + + function warn (condition, message) { + if ( !condition) { + typeof console !== 'undefined' && console.warn(("[vue-router] " + message)); + } + } + + function isError (err) { + return Object.prototype.toString.call(err).indexOf('Error') > -1 + } + + function isExtendedError (constructor, err) { + return ( + err instanceof constructor || + // _name is to support IE9 too + (err && (err.name === constructor.name || err._name === constructor._name)) + ) + } + + function extend (a, b) { + for (var key in b) { + a[key] = b[key]; + } + return a + } + + var View = { + name: 'RouterView', + functional: true, + props: { + name: { + type: String, + default: 'default' + } + }, + render: function render (_, ref) { + var props = ref.props; + var children = ref.children; + var parent = ref.parent; + var data = ref.data; + + // used by devtools to display a router-view badge + data.routerView = true; + + // directly use parent context's createElement() function + // so that components rendered by router-view can resolve named slots + var h = parent.$createElement; + var name = props.name; + var route = parent.$route; + var cache = parent._routerViewCache || (parent._routerViewCache = {}); + + // determine current view depth, also check to see if the tree + // has been toggled inactive but kept-alive. + var depth = 0; + var inactive = false; + while (parent && parent._routerRoot !== parent) { + var vnodeData = parent.$vnode && parent.$vnode.data; + if (vnodeData) { + if (vnodeData.routerView) { + depth++; + } + if (vnodeData.keepAlive && parent._inactive) { + inactive = true; + } + } + parent = parent.$parent; + } + data.routerViewDepth = depth; + + // render previous view if the tree is inactive and kept-alive + if (inactive) { + return h(cache[name], data, children) + } + + var matched = route.matched[depth]; + // render empty node if no matched route + if (!matched) { + cache[name] = null; + return h() + } + + var component = cache[name] = matched.components[name]; + + // attach instance registration hook + // this will be called in the instance's injected lifecycle hooks + data.registerRouteInstance = function (vm, val) { + // val could be undefined for unregistration + var current = matched.instances[name]; + if ( + (val && current !== vm) || + (!val && current === vm) + ) { + matched.instances[name] = val; + } + } + + // also register instance in prepatch hook + // in case the same component instance is reused across different routes + ;(data.hook || (data.hook = {})).prepatch = function (_, vnode) { + matched.instances[name] = vnode.componentInstance; + }; + + // register instance in init hook + // in case kept-alive component be actived when routes changed + data.hook.init = function (vnode) { + if (vnode.data.keepAlive && + vnode.componentInstance && + vnode.componentInstance !== matched.instances[name] + ) { + matched.instances[name] = vnode.componentInstance; + } + }; + + // resolve props + var propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]); + if (propsToPass) { + // clone to prevent mutation + propsToPass = data.props = extend({}, propsToPass); + // pass non-declared props as attrs + var attrs = data.attrs = data.attrs || {}; + for (var key in propsToPass) { + if (!component.props || !(key in component.props)) { + attrs[key] = propsToPass[key]; + delete propsToPass[key]; + } + } + } + + return h(component, data, children) + } + }; + + function resolveProps (route, config) { + switch (typeof config) { + case 'undefined': + return + case 'object': + return config + case 'function': + return config(route) + case 'boolean': + return config ? route.params : undefined + default: + { + warn( + false, + "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " + + "expecting an object, function or boolean." + ); + } + } + } + + /* */ + + var encodeReserveRE = /[!'()*]/g; + var encodeReserveReplacer = function (c) { return '%' + c.charCodeAt(0).toString(16); }; + var commaRE = /%2C/g; + + // fixed encodeURIComponent which is more conformant to RFC3986: + // - escapes [!'()*] + // - preserve commas + var encode = function (str) { return encodeURIComponent(str) + .replace(encodeReserveRE, encodeReserveReplacer) + .replace(commaRE, ','); }; + + var decode = decodeURIComponent; + + function resolveQuery ( + query, + extraQuery, + _parseQuery + ) { + if ( extraQuery === void 0 ) extraQuery = {}; + + var parse = _parseQuery || parseQuery; + var parsedQuery; + try { + parsedQuery = parse(query || ''); + } catch (e) { + warn(false, e.message); + parsedQuery = {}; + } + for (var key in extraQuery) { + parsedQuery[key] = extraQuery[key]; + } + return parsedQuery + } + + function parseQuery (query) { + var res = {}; + + query = query.trim().replace(/^(\?|#|&)/, ''); + + if (!query) { + return res + } + + query.split('&').forEach(function (param) { + var parts = param.replace(/\+/g, ' ').split('='); + var key = decode(parts.shift()); + var val = parts.length > 0 + ? decode(parts.join('=')) + : null; + + if (res[key] === undefined) { + res[key] = val; + } else if (Array.isArray(res[key])) { + res[key].push(val); + } else { + res[key] = [res[key], val]; + } + }); + + return res + } + + function stringifyQuery (obj) { + var res = obj ? Object.keys(obj).map(function (key) { + var val = obj[key]; + + if (val === undefined) { + return '' + } + + if (val === null) { + return encode(key) + } + + if (Array.isArray(val)) { + var result = []; + val.forEach(function (val2) { + if (val2 === undefined) { + return + } + if (val2 === null) { + result.push(encode(key)); + } else { + result.push(encode(key) + '=' + encode(val2)); + } + }); + return result.join('&') + } + + return encode(key) + '=' + encode(val) + }).filter(function (x) { return x.length > 0; }).join('&') : null; + return res ? ("?" + res) : '' + } + + /* */ + + var trailingSlashRE = /\/?$/; + + function createRoute ( + record, + location, + redirectedFrom, + router + ) { + var stringifyQuery = router && router.options.stringifyQuery; + + var query = location.query || {}; + try { + query = clone(query); + } catch (e) {} + + var route = { + name: location.name || (record && record.name), + meta: (record && record.meta) || {}, + path: location.path || '/', + hash: location.hash || '', + query: query, + params: location.params || {}, + fullPath: getFullPath(location, stringifyQuery), + matched: record ? formatMatch(record) : [] + }; + if (redirectedFrom) { + route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery); + } + return Object.freeze(route) + } + + function clone (value) { + if (Array.isArray(value)) { + return value.map(clone) + } else if (value && typeof value === 'object') { + var res = {}; + for (var key in value) { + res[key] = clone(value[key]); + } + return res + } else { + return value + } + } + + // the starting route that represents the initial state + var START = createRoute(null, { + path: '/' + }); + + function formatMatch (record) { + var res = []; + while (record) { + res.unshift(record); + record = record.parent; + } + return res + } + + function getFullPath ( + ref, + _stringifyQuery + ) { + var path = ref.path; + var query = ref.query; if ( query === void 0 ) query = {}; + var hash = ref.hash; if ( hash === void 0 ) hash = ''; + + var stringify = _stringifyQuery || stringifyQuery; + return (path || '/') + stringify(query) + hash + } + + function isSameRoute (a, b) { + if (b === START) { + return a === b + } else if (!b) { + return false + } else if (a.path && b.path) { + return ( + a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && + a.hash === b.hash && + isObjectEqual(a.query, b.query) + ) + } else if (a.name && b.name) { + return ( + a.name === b.name && + a.hash === b.hash && + isObjectEqual(a.query, b.query) && + isObjectEqual(a.params, b.params) + ) + } else { + return false + } + } + + function isObjectEqual (a, b) { + if ( a === void 0 ) a = {}; + if ( b === void 0 ) b = {}; + + // handle null value #1566 + if (!a || !b) { return a === b } + var aKeys = Object.keys(a); + var bKeys = Object.keys(b); + if (aKeys.length !== bKeys.length) { + return false + } + return aKeys.every(function (key) { + var aVal = a[key]; + var bVal = b[key]; + // check nested equality + if (typeof aVal === 'object' && typeof bVal === 'object') { + return isObjectEqual(aVal, bVal) + } + return String(aVal) === String(bVal) + }) + } + + function isIncludedRoute (current, target) { + return ( + current.path.replace(trailingSlashRE, '/').indexOf( + target.path.replace(trailingSlashRE, '/') + ) === 0 && + (!target.hash || current.hash === target.hash) && + queryIncludes(current.query, target.query) + ) + } + + function queryIncludes (current, target) { + for (var key in target) { + if (!(key in current)) { + return false + } + } + return true + } + + /* */ + + function resolvePath ( + relative, + base, + append + ) { + var firstChar = relative.charAt(0); + if (firstChar === '/') { + return relative + } + + if (firstChar === '?' || firstChar === '#') { + return base + relative + } + + var stack = base.split('/'); + + // remove trailing segment if: + // - not appending + // - appending to trailing slash (last segment is empty) + if (!append || !stack[stack.length - 1]) { + stack.pop(); + } + + // resolve relative path + var segments = relative.replace(/^\//, '').split('/'); + for (var i = 0; i < segments.length; i++) { + var segment = segments[i]; + if (segment === '..') { + stack.pop(); + } else if (segment !== '.') { + stack.push(segment); + } + } + + // ensure leading slash + if (stack[0] !== '') { + stack.unshift(''); + } + + return stack.join('/') + } + + function parsePath (path) { + var hash = ''; + var query = ''; + + var hashIndex = path.indexOf('#'); + if (hashIndex >= 0) { + hash = path.slice(hashIndex); + path = path.slice(0, hashIndex); + } + + var queryIndex = path.indexOf('?'); + if (queryIndex >= 0) { + query = path.slice(queryIndex + 1); + path = path.slice(0, queryIndex); + } + + return { + path: path, + query: query, + hash: hash + } + } + + function cleanPath (path) { + return path.replace(/\/\//g, '/') + } + + var isarray = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]'; + }; + + /** + * Expose `pathToRegexp`. + */ + var pathToRegexp_1 = pathToRegexp; + var parse_1 = parse; + var compile_1 = compile; + var tokensToFunction_1 = tokensToFunction; + var tokensToRegExp_1 = tokensToRegExp; + + /** + * The main path matching regexp utility. + * + * @type {RegExp} + */ + var PATH_REGEXP = new RegExp([ + // Match escaped characters that would otherwise appear in future matches. + // This allows the user to escape special characters that won't transform. + '(\\\\.)', + // Match Express-style parameters and un-named parameters with a prefix + // and optional suffixes. Matches appear as: + // + // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined] + // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined] + // "/*" => ["/", undefined, undefined, undefined, undefined, "*"] + '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))' + ].join('|'), 'g'); + + /** + * Parse a string for the raw tokens. + * + * @param {string} str + * @param {Object=} options + * @return {!Array} + */ + function parse (str, options) { + var tokens = []; + var key = 0; + var index = 0; + var path = ''; + var defaultDelimiter = options && options.delimiter || '/'; + var res; + + while ((res = PATH_REGEXP.exec(str)) != null) { + var m = res[0]; + var escaped = res[1]; + var offset = res.index; + path += str.slice(index, offset); + index = offset + m.length; + + // Ignore already escaped sequences. + if (escaped) { + path += escaped[1]; + continue + } + + var next = str[index]; + var prefix = res[2]; + var name = res[3]; + var capture = res[4]; + var group = res[5]; + var modifier = res[6]; + var asterisk = res[7]; + + // Push the current path onto the tokens. + if (path) { + tokens.push(path); + path = ''; + } + + var partial = prefix != null && next != null && next !== prefix; + var repeat = modifier === '+' || modifier === '*'; + var optional = modifier === '?' || modifier === '*'; + var delimiter = res[2] || defaultDelimiter; + var pattern = capture || group; + + tokens.push({ + name: name || key++, + prefix: prefix || '', + delimiter: delimiter, + optional: optional, + repeat: repeat, + partial: partial, + asterisk: !!asterisk, + pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?') + }); + } + + // Match any characters still remaining. + if (index < str.length) { + path += str.substr(index); + } + + // If the path exists, push it onto the end. + if (path) { + tokens.push(path); + } + + return tokens + } + + /** + * Compile a string to a template function for the path. + * + * @param {string} str + * @param {Object=} options + * @return {!function(Object=, Object=)} + */ + function compile (str, options) { + return tokensToFunction(parse(str, options)) + } + + /** + * Prettier encoding of URI path segments. + * + * @param {string} + * @return {string} + */ + function encodeURIComponentPretty (str) { + return encodeURI(str).replace(/[\/?#]/g, function (c) { + return '%' + c.charCodeAt(0).toString(16).toUpperCase() + }) + } + + /** + * Encode the asterisk parameter. Similar to `pretty`, but allows slashes. + * + * @param {string} + * @return {string} + */ + function encodeAsterisk (str) { + return encodeURI(str).replace(/[?#]/g, function (c) { + return '%' + c.charCodeAt(0).toString(16).toUpperCase() + }) + } + + /** + * Expose a method for transforming tokens into the path function. + */ + function tokensToFunction (tokens) { + // Compile all the tokens into regexps. + var matches = new Array(tokens.length); + + // Compile all the patterns before compilation. + for (var i = 0; i < tokens.length; i++) { + if (typeof tokens[i] === 'object') { + matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$'); + } + } + + return function (obj, opts) { + var path = ''; + var data = obj || {}; + var options = opts || {}; + var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent; + + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + + if (typeof token === 'string') { + path += token; + + continue + } + + var value = data[token.name]; + var segment; + + if (value == null) { + if (token.optional) { + // Prepend partial segment prefixes. + if (token.partial) { + path += token.prefix; + } + + continue + } else { + throw new TypeError('Expected "' + token.name + '" to be defined') + } + } + + if (isarray(value)) { + if (!token.repeat) { + throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`') + } + + if (value.length === 0) { + if (token.optional) { + continue + } else { + throw new TypeError('Expected "' + token.name + '" to not be empty') + } + } + + for (var j = 0; j < value.length; j++) { + segment = encode(value[j]); + + if (!matches[i].test(segment)) { + throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`') + } + + path += (j === 0 ? token.prefix : token.delimiter) + segment; + } + + continue + } + + segment = token.asterisk ? encodeAsterisk(value) : encode(value); + + if (!matches[i].test(segment)) { + throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"') + } + + path += token.prefix + segment; + } + + return path + } + } + + /** + * Escape a regular expression string. + * + * @param {string} str + * @return {string} + */ + function escapeString (str) { + return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1') + } + + /** + * Escape the capturing group by escaping special characters and meaning. + * + * @param {string} group + * @return {string} + */ + function escapeGroup (group) { + return group.replace(/([=!:$\/()])/g, '\\$1') + } + + /** + * Attach the keys as a property of the regexp. + * + * @param {!RegExp} re + * @param {Array} keys + * @return {!RegExp} + */ + function attachKeys (re, keys) { + re.keys = keys; + return re + } + + /** + * Get the flags for a regexp from the options. + * + * @param {Object} options + * @return {string} + */ + function flags (options) { + return options.sensitive ? '' : 'i' + } + + /** + * Pull out keys from a regexp. + * + * @param {!RegExp} path + * @param {!Array} keys + * @return {!RegExp} + */ + function regexpToRegexp (path, keys) { + // Use a negative lookahead to match only capturing groups. + var groups = path.source.match(/\((?!\?)/g); + + if (groups) { + for (var i = 0; i < groups.length; i++) { + keys.push({ + name: i, + prefix: null, + delimiter: null, + optional: false, + repeat: false, + partial: false, + asterisk: false, + pattern: null + }); + } + } + + return attachKeys(path, keys) + } + + /** + * Transform an array into a regexp. + * + * @param {!Array} path + * @param {Array} keys + * @param {!Object} options + * @return {!RegExp} + */ + function arrayToRegexp (path, keys, options) { + var parts = []; + + for (var i = 0; i < path.length; i++) { + parts.push(pathToRegexp(path[i], keys, options).source); + } + + var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options)); + + return attachKeys(regexp, keys) + } + + /** + * Create a path regexp from string input. + * + * @param {string} path + * @param {!Array} keys + * @param {!Object} options + * @return {!RegExp} + */ + function stringToRegexp (path, keys, options) { + return tokensToRegExp(parse(path, options), keys, options) + } + + /** + * Expose a function for taking tokens and returning a RegExp. + * + * @param {!Array} tokens + * @param {(Array|Object)=} keys + * @param {Object=} options + * @return {!RegExp} + */ + function tokensToRegExp (tokens, keys, options) { + if (!isarray(keys)) { + options = /** @type {!Object} */ (keys || options); + keys = []; + } + + options = options || {}; + + var strict = options.strict; + var end = options.end !== false; + var route = ''; + + // Iterate over the tokens and create our regexp string. + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + + if (typeof token === 'string') { + route += escapeString(token); + } else { + var prefix = escapeString(token.prefix); + var capture = '(?:' + token.pattern + ')'; + + keys.push(token); + + if (token.repeat) { + capture += '(?:' + prefix + capture + ')*'; + } + + if (token.optional) { + if (!token.partial) { + capture = '(?:' + prefix + '(' + capture + '))?'; + } else { + capture = prefix + '(' + capture + ')?'; + } + } else { + capture = prefix + '(' + capture + ')'; + } + + route += capture; + } + } + + var delimiter = escapeString(options.delimiter || '/'); + var endsWithDelimiter = route.slice(-delimiter.length) === delimiter; + + // In non-strict mode we allow a slash at the end of match. If the path to + // match already ends with a slash, we remove it for consistency. The slash + // is valid at the end of a path match, not in the middle. This is important + // in non-ending mode, where "/test/" shouldn't match "/test//route". + if (!strict) { + route = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?'; + } + + if (end) { + route += '$'; + } else { + // In non-ending mode, we need the capturing groups to match as much as + // possible by using a positive lookahead to the end or next path segment. + route += strict && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)'; + } + + return attachKeys(new RegExp('^' + route, flags(options)), keys) + } + + /** + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + * + * @param {(string|RegExp|Array)} path + * @param {(Array|Object)=} keys + * @param {Object=} options + * @return {!RegExp} + */ + function pathToRegexp (path, keys, options) { + if (!isarray(keys)) { + options = /** @type {!Object} */ (keys || options); + keys = []; + } + + options = options || {}; + + if (path instanceof RegExp) { + return regexpToRegexp(path, /** @type {!Array} */ (keys)) + } + + if (isarray(path)) { + return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options) + } + + return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options) + } + pathToRegexp_1.parse = parse_1; + pathToRegexp_1.compile = compile_1; + pathToRegexp_1.tokensToFunction = tokensToFunction_1; + pathToRegexp_1.tokensToRegExp = tokensToRegExp_1; + + /* */ + + // $flow-disable-line + var regexpCompileCache = Object.create(null); + + function fillParams ( + path, + params, + routeMsg + ) { + params = params || {}; + try { + var filler = + regexpCompileCache[path] || + (regexpCompileCache[path] = pathToRegexp_1.compile(path)); + + // Fix #2505 resolving asterisk routes { name: 'not-found', params: { pathMatch: '/not-found' }} + if (params.pathMatch) { params[0] = params.pathMatch; } + + return filler(params, { pretty: true }) + } catch (e) { + { + warn(false, ("missing param for " + routeMsg + ": " + (e.message))); + } + return '' + } finally { + // delete the 0 if it was added + delete params[0]; + } + } + + /* */ + + function normalizeLocation ( + raw, + current, + append, + router + ) { + var next = typeof raw === 'string' ? { path: raw } : raw; + // named target + if (next._normalized) { + return next + } else if (next.name) { + return extend({}, raw) + } + + // relative params + if (!next.path && next.params && current) { + next = extend({}, next); + next._normalized = true; + var params = extend(extend({}, current.params), next.params); + if (current.name) { + next.name = current.name; + next.params = params; + } else if (current.matched.length) { + var rawPath = current.matched[current.matched.length - 1].path; + next.path = fillParams(rawPath, params, ("path " + (current.path))); + } else { + warn(false, "relative params navigation requires a current route."); + } + return next + } + + var parsedPath = parsePath(next.path || ''); + var basePath = (current && current.path) || '/'; + var path = parsedPath.path + ? resolvePath(parsedPath.path, basePath, append || next.append) + : basePath; + + var query = resolveQuery( + parsedPath.query, + next.query, + router && router.options.parseQuery + ); + + var hash = next.hash || parsedPath.hash; + if (hash && hash.charAt(0) !== '#') { + hash = "#" + hash; + } + + return { + _normalized: true, + path: path, + query: query, + hash: hash + } + } + + /* */ + + // work around weird flow bug + var toTypes = [String, Object]; + var eventTypes = [String, Array]; + + var noop = function () {}; + + var Link = { + name: 'RouterLink', + props: { + to: { + type: toTypes, + required: true + }, + tag: { + type: String, + default: 'a' + }, + exact: Boolean, + append: Boolean, + replace: Boolean, + activeClass: String, + exactActiveClass: String, + event: { + type: eventTypes, + default: 'click' + } + }, + render: function render (h) { + var this$1 = this; + + var router = this.$router; + var current = this.$route; + var ref = router.resolve( + this.to, + current, + this.append + ); + var location = ref.location; + var route = ref.route; + var href = ref.href; + + var classes = {}; + var globalActiveClass = router.options.linkActiveClass; + var globalExactActiveClass = router.options.linkExactActiveClass; + // Support global empty active class + var activeClassFallback = + globalActiveClass == null ? 'router-link-active' : globalActiveClass; + var exactActiveClassFallback = + globalExactActiveClass == null + ? 'router-link-exact-active' + : globalExactActiveClass; + var activeClass = + this.activeClass == null ? activeClassFallback : this.activeClass; + var exactActiveClass = + this.exactActiveClass == null + ? exactActiveClassFallback + : this.exactActiveClass; + + var compareTarget = route.redirectedFrom + ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router) + : route; + + classes[exactActiveClass] = isSameRoute(current, compareTarget); + classes[activeClass] = this.exact + ? classes[exactActiveClass] + : isIncludedRoute(current, compareTarget); + + var handler = function (e) { + if (guardEvent(e)) { + if (this$1.replace) { + router.replace(location, noop); + } else { + router.push(location, noop); + } + } + }; + + var on = { click: guardEvent }; + if (Array.isArray(this.event)) { + this.event.forEach(function (e) { + on[e] = handler; + }); + } else { + on[this.event] = handler; + } + + var data = { class: classes }; + + var scopedSlot = + !this.$scopedSlots.$hasNormal && + this.$scopedSlots.default && + this.$scopedSlots.default({ + href: href, + route: route, + navigate: handler, + isActive: classes[activeClass], + isExactActive: classes[exactActiveClass] + }); + + if (scopedSlot) { + if (scopedSlot.length === 1) { + return scopedSlot[0] + } else if (scopedSlot.length > 1 || !scopedSlot.length) { + { + warn( + false, + ("RouterLink with to=\"" + (this.props.to) + "\" is trying to use a scoped slot but it didn't provide exactly one child.") + ); + } + return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot) + } + } + + if (this.tag === 'a') { + data.on = on; + data.attrs = { href: href }; + } else { + // find the first child and apply listener and href + var a = findAnchor(this.$slots.default); + if (a) { + // in case the is a static node + a.isStatic = false; + var aData = (a.data = extend({}, a.data)); + aData.on = aData.on || {}; + // transform existing events in both objects into arrays so we can push later + for (var event in aData.on) { + var handler$1 = aData.on[event]; + if (event in on) { + aData.on[event] = Array.isArray(handler$1) ? handler$1 : [handler$1]; + } + } + // append new listeners for router-link + for (var event$1 in on) { + if (event$1 in aData.on) { + // on[event] is always a function + aData.on[event$1].push(on[event$1]); + } else { + aData.on[event$1] = handler; + } + } + + var aAttrs = (a.data.attrs = extend({}, a.data.attrs)); + aAttrs.href = href; + } else { + // doesn't have child, apply listener to self + data.on = on; + } + } + + return h(this.tag, data, this.$slots.default) + } + }; + + function guardEvent (e) { + // don't redirect with control keys + if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) { return } + // don't redirect when preventDefault called + if (e.defaultPrevented) { return } + // don't redirect on right click + if (e.button !== undefined && e.button !== 0) { return } + // don't redirect if `target="_blank"` + if (e.currentTarget && e.currentTarget.getAttribute) { + var target = e.currentTarget.getAttribute('target'); + if (/\b_blank\b/i.test(target)) { return } + } + // this may be a Weex event which doesn't have this method + if (e.preventDefault) { + e.preventDefault(); + } + return true + } + + function findAnchor (children) { + if (children) { + var child; + for (var i = 0; i < children.length; i++) { + child = children[i]; + if (child.tag === 'a') { + return child + } + if (child.children && (child = findAnchor(child.children))) { + return child + } + } + } + } + + var _Vue; + + function install (Vue) { + if (install.installed && _Vue === Vue) { return } + install.installed = true; + + _Vue = Vue; + + var isDef = function (v) { return v !== undefined; }; + + var registerInstance = function (vm, callVal) { + var i = vm.$options._parentVnode; + if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { + i(vm, callVal); + } + }; + + Vue.mixin({ + beforeCreate: function beforeCreate () { + if (isDef(this.$options.router)) { + this._routerRoot = this; + this._router = this.$options.router; + this._router.init(this); + Vue.util.defineReactive(this, '_route', this._router.history.current); + } else { + this._routerRoot = (this.$parent && this.$parent._routerRoot) || this; + } + registerInstance(this, this); + }, + destroyed: function destroyed () { + registerInstance(this); + } + }); + + Object.defineProperty(Vue.prototype, '$router', { + get: function get () { return this._routerRoot._router } + }); + + Object.defineProperty(Vue.prototype, '$route', { + get: function get () { return this._routerRoot._route } + }); + + Vue.component('RouterView', View); + Vue.component('RouterLink', Link); + + var strats = Vue.config.optionMergeStrategies; + // use the same hook merging strategy for route hooks + strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created; + } + + /* */ + + var inBrowser = typeof window !== 'undefined'; + + /* */ + + function createRouteMap ( + routes, + oldPathList, + oldPathMap, + oldNameMap + ) { + // the path list is used to control path matching priority + var pathList = oldPathList || []; + // $flow-disable-line + var pathMap = oldPathMap || Object.create(null); + // $flow-disable-line + var nameMap = oldNameMap || Object.create(null); + + routes.forEach(function (route) { + addRouteRecord(pathList, pathMap, nameMap, route); + }); + + // ensure wildcard routes are always at the end + for (var i = 0, l = pathList.length; i < l; i++) { + if (pathList[i] === '*') { + pathList.push(pathList.splice(i, 1)[0]); + l--; + i--; + } + } + + { + // warn if routes do not include leading slashes + var found = pathList + // check for missing leading slash + .filter(function (path) { return path && path.charAt(0) !== '*' && path.charAt(0) !== '/'; }); + + if (found.length > 0) { + var pathNames = found.map(function (path) { return ("- " + path); }).join('\n'); + warn(false, ("Non-nested routes must include a leading slash character. Fix the following routes: \n" + pathNames)); + } + } + + return { + pathList: pathList, + pathMap: pathMap, + nameMap: nameMap + } + } + + function addRouteRecord ( + pathList, + pathMap, + nameMap, + route, + parent, + matchAs + ) { + var path = route.path; + var name = route.name; + { + assert(path != null, "\"path\" is required in a route configuration."); + assert( + typeof route.component !== 'string', + "route config \"component\" for path: " + (String( + path || name + )) + " cannot be a " + "string id. Use an actual component instead." + ); + } + + var pathToRegexpOptions = + route.pathToRegexpOptions || {}; + var normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict); + + if (typeof route.caseSensitive === 'boolean') { + pathToRegexpOptions.sensitive = route.caseSensitive; + } + + var record = { + path: normalizedPath, + regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), + components: route.components || { default: route.component }, + instances: {}, + name: name, + parent: parent, + matchAs: matchAs, + redirect: route.redirect, + beforeEnter: route.beforeEnter, + meta: route.meta || {}, + props: + route.props == null + ? {} + : route.components + ? route.props + : { default: route.props } + }; + + if (route.children) { + // Warn if route is named, does not redirect and has a default child route. + // If users navigate to this route by name, the default child will + // not be rendered (GH Issue #629) + { + if ( + route.name && + !route.redirect && + route.children.some(function (child) { return /^\/?$/.test(child.path); }) + ) { + warn( + false, + "Named Route '" + (route.name) + "' has a default child route. " + + "When navigating to this named route (:to=\"{name: '" + (route.name) + "'\"), " + + "the default child route will not be rendered. Remove the name from " + + "this route and use the name of the default child route for named " + + "links instead." + ); + } + } + route.children.forEach(function (child) { + var childMatchAs = matchAs + ? cleanPath((matchAs + "/" + (child.path))) + : undefined; + addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs); + }); + } + + if (!pathMap[record.path]) { + pathList.push(record.path); + pathMap[record.path] = record; + } + + if (route.alias !== undefined) { + var aliases = Array.isArray(route.alias) ? route.alias : [route.alias]; + for (var i = 0; i < aliases.length; ++i) { + var alias = aliases[i]; + if ( alias === path) { + warn( + false, + ("Found an alias with the same value as the path: \"" + path + "\". You have to remove that alias. It will be ignored in development.") + ); + // skip in dev to make it work + continue + } + + var aliasRoute = { + path: alias, + children: route.children + }; + addRouteRecord( + pathList, + pathMap, + nameMap, + aliasRoute, + parent, + record.path || '/' // matchAs + ); + } + } + + if (name) { + if (!nameMap[name]) { + nameMap[name] = record; + } else if ( !matchAs) { + warn( + false, + "Duplicate named routes definition: " + + "{ name: \"" + name + "\", path: \"" + (record.path) + "\" }" + ); + } + } + } + + function compileRouteRegex ( + path, + pathToRegexpOptions + ) { + var regex = pathToRegexp_1(path, [], pathToRegexpOptions); + { + var keys = Object.create(null); + regex.keys.forEach(function (key) { + warn( + !keys[key.name], + ("Duplicate param keys in route with path: \"" + path + "\"") + ); + keys[key.name] = true; + }); + } + return regex + } + + function normalizePath ( + path, + parent, + strict + ) { + if (!strict) { path = path.replace(/\/$/, ''); } + if (path[0] === '/') { return path } + if (parent == null) { return path } + return cleanPath(((parent.path) + "/" + path)) + } + + /* */ + + + + function createMatcher ( + routes, + router + ) { + var ref = createRouteMap(routes); + var pathList = ref.pathList; + var pathMap = ref.pathMap; + var nameMap = ref.nameMap; + + function addRoutes (routes) { + createRouteMap(routes, pathList, pathMap, nameMap); + } + + function match ( + raw, + currentRoute, + redirectedFrom + ) { + var location = normalizeLocation(raw, currentRoute, false, router); + var name = location.name; + + if (name) { + var record = nameMap[name]; + { + warn(record, ("Route with name '" + name + "' does not exist")); + } + if (!record) { return _createRoute(null, location) } + var paramNames = record.regex.keys + .filter(function (key) { return !key.optional; }) + .map(function (key) { return key.name; }); + + if (typeof location.params !== 'object') { + location.params = {}; + } + + if (currentRoute && typeof currentRoute.params === 'object') { + for (var key in currentRoute.params) { + if (!(key in location.params) && paramNames.indexOf(key) > -1) { + location.params[key] = currentRoute.params[key]; + } + } + } + + location.path = fillParams(record.path, location.params, ("named route \"" + name + "\"")); + return _createRoute(record, location, redirectedFrom) + } else if (location.path) { + location.params = {}; + for (var i = 0; i < pathList.length; i++) { + var path = pathList[i]; + var record$1 = pathMap[path]; + if (matchRoute(record$1.regex, location.path, location.params)) { + return _createRoute(record$1, location, redirectedFrom) + } + } + } + // no match + return _createRoute(null, location) + } + + function redirect ( + record, + location + ) { + var originalRedirect = record.redirect; + var redirect = typeof originalRedirect === 'function' + ? originalRedirect(createRoute(record, location, null, router)) + : originalRedirect; + + if (typeof redirect === 'string') { + redirect = { path: redirect }; + } + + if (!redirect || typeof redirect !== 'object') { + { + warn( + false, ("invalid redirect option: " + (JSON.stringify(redirect))) + ); + } + return _createRoute(null, location) + } + + var re = redirect; + var name = re.name; + var path = re.path; + var query = location.query; + var hash = location.hash; + var params = location.params; + query = re.hasOwnProperty('query') ? re.query : query; + hash = re.hasOwnProperty('hash') ? re.hash : hash; + params = re.hasOwnProperty('params') ? re.params : params; + + if (name) { + // resolved named direct + var targetRecord = nameMap[name]; + { + assert(targetRecord, ("redirect failed: named route \"" + name + "\" not found.")); + } + return match({ + _normalized: true, + name: name, + query: query, + hash: hash, + params: params + }, undefined, location) + } else if (path) { + // 1. resolve relative redirect + var rawPath = resolveRecordPath(path, record); + // 2. resolve params + var resolvedPath = fillParams(rawPath, params, ("redirect route with path \"" + rawPath + "\"")); + // 3. rematch with existing query and hash + return match({ + _normalized: true, + path: resolvedPath, + query: query, + hash: hash + }, undefined, location) + } else { + { + warn(false, ("invalid redirect option: " + (JSON.stringify(redirect)))); + } + return _createRoute(null, location) + } + } + + function alias ( + record, + location, + matchAs + ) { + var aliasedPath = fillParams(matchAs, location.params, ("aliased route with path \"" + matchAs + "\"")); + var aliasedMatch = match({ + _normalized: true, + path: aliasedPath + }); + if (aliasedMatch) { + var matched = aliasedMatch.matched; + var aliasedRecord = matched[matched.length - 1]; + location.params = aliasedMatch.params; + return _createRoute(aliasedRecord, location) + } + return _createRoute(null, location) + } + + function _createRoute ( + record, + location, + redirectedFrom + ) { + if (record && record.redirect) { + return redirect(record, redirectedFrom || location) + } + if (record && record.matchAs) { + return alias(record, location, record.matchAs) + } + return createRoute(record, location, redirectedFrom, router) + } + + return { + match: match, + addRoutes: addRoutes + } + } + + function matchRoute ( + regex, + path, + params + ) { + var m = path.match(regex); + + if (!m) { + return false + } else if (!params) { + return true + } + + for (var i = 1, len = m.length; i < len; ++i) { + var key = regex.keys[i - 1]; + var val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]; + if (key) { + // Fix #1994: using * with props: true generates a param named 0 + params[key.name || 'pathMatch'] = val; + } + } + + return true + } + + function resolveRecordPath (path, record) { + return resolvePath(path, record.parent ? record.parent.path : '/', true) + } + + /* */ + + // use User Timing api (if present) for more accurate key precision + var Time = + inBrowser && window.performance && window.performance.now + ? window.performance + : Date; + + function genStateKey () { + return Time.now().toFixed(3) + } + + var _key = genStateKey(); + + function getStateKey () { + return _key + } + + function setStateKey (key) { + return (_key = key) + } + + /* */ + + var positionStore = Object.create(null); + + function setupScroll () { + // Fix for #1585 for Firefox + // Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678 + // Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with + // window.location.protocol + '//' + window.location.host + // location.host contains the port and location.hostname doesn't + var protocolAndPath = window.location.protocol + '//' + window.location.host; + var absolutePath = window.location.href.replace(protocolAndPath, ''); + window.history.replaceState({ key: getStateKey() }, '', absolutePath); + window.addEventListener('popstate', function (e) { + saveScrollPosition(); + if (e.state && e.state.key) { + setStateKey(e.state.key); + } + }); + } + + function handleScroll ( + router, + to, + from, + isPop + ) { + if (!router.app) { + return + } + + var behavior = router.options.scrollBehavior; + if (!behavior) { + return + } + + { + assert(typeof behavior === 'function', "scrollBehavior must be a function"); + } + + // wait until re-render finishes before scrolling + router.app.$nextTick(function () { + var position = getScrollPosition(); + var shouldScroll = behavior.call( + router, + to, + from, + isPop ? position : null + ); + + if (!shouldScroll) { + return + } + + if (typeof shouldScroll.then === 'function') { + shouldScroll + .then(function (shouldScroll) { + scrollToPosition((shouldScroll), position); + }) + .catch(function (err) { + { + assert(false, err.toString()); + } + }); + } else { + scrollToPosition(shouldScroll, position); + } + }); + } + + function saveScrollPosition () { + var key = getStateKey(); + if (key) { + positionStore[key] = { + x: window.pageXOffset, + y: window.pageYOffset + }; + } + } + + function getScrollPosition () { + var key = getStateKey(); + if (key) { + return positionStore[key] + } + } + + function getElementPosition (el, offset) { + var docEl = document.documentElement; + var docRect = docEl.getBoundingClientRect(); + var elRect = el.getBoundingClientRect(); + return { + x: elRect.left - docRect.left - offset.x, + y: elRect.top - docRect.top - offset.y + } + } + + function isValidPosition (obj) { + return isNumber(obj.x) || isNumber(obj.y) + } + + function normalizePosition (obj) { + return { + x: isNumber(obj.x) ? obj.x : window.pageXOffset, + y: isNumber(obj.y) ? obj.y : window.pageYOffset + } + } + + function normalizeOffset (obj) { + return { + x: isNumber(obj.x) ? obj.x : 0, + y: isNumber(obj.y) ? obj.y : 0 + } + } + + function isNumber (v) { + return typeof v === 'number' + } + + var hashStartsWithNumberRE = /^#\d/; + + function scrollToPosition (shouldScroll, position) { + var isObject = typeof shouldScroll === 'object'; + if (isObject && typeof shouldScroll.selector === 'string') { + // getElementById would still fail if the selector contains a more complicated query like #main[data-attr] + // but at the same time, it doesn't make much sense to select an element with an id and an extra selector + var el = hashStartsWithNumberRE.test(shouldScroll.selector) // $flow-disable-line + ? document.getElementById(shouldScroll.selector.slice(1)) // $flow-disable-line + : document.querySelector(shouldScroll.selector); + + if (el) { + var offset = + shouldScroll.offset && typeof shouldScroll.offset === 'object' + ? shouldScroll.offset + : {}; + offset = normalizeOffset(offset); + position = getElementPosition(el, offset); + } else if (isValidPosition(shouldScroll)) { + position = normalizePosition(shouldScroll); + } + } else if (isObject && isValidPosition(shouldScroll)) { + position = normalizePosition(shouldScroll); + } + + if (position) { + window.scrollTo(position.x, position.y); + } + } + + /* */ + + var supportsPushState = + inBrowser && + (function () { + var ua = window.navigator.userAgent; + + if ( + (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) && + ua.indexOf('Mobile Safari') !== -1 && + ua.indexOf('Chrome') === -1 && + ua.indexOf('Windows Phone') === -1 + ) { + return false + } + + return window.history && 'pushState' in window.history + })(); + + function pushState (url, replace) { + saveScrollPosition(); + // try...catch the pushState call to get around Safari + // DOM Exception 18 where it limits to 100 pushState calls + var history = window.history; + try { + if (replace) { + history.replaceState({ key: getStateKey() }, '', url); + } else { + history.pushState({ key: setStateKey(genStateKey()) }, '', url); + } + } catch (e) { + window.location[replace ? 'replace' : 'assign'](url); + } + } + + function replaceState (url) { + pushState(url, true); + } + + /* */ + + function runQueue (queue, fn, cb) { + var step = function (index) { + if (index >= queue.length) { + cb(); + } else { + if (queue[index]) { + fn(queue[index], function () { + step(index + 1); + }); + } else { + step(index + 1); + } + } + }; + step(0); + } + + /* */ + + function resolveAsyncComponents (matched) { + return function (to, from, next) { + var hasAsync = false; + var pending = 0; + var error = null; + + flatMapComponents(matched, function (def, _, match, key) { + // if it's a function and doesn't have cid attached, + // assume it's an async component resolve function. + // we are not using Vue's default async resolving mechanism because + // we want to halt the navigation until the incoming component has been + // resolved. + if (typeof def === 'function' && def.cid === undefined) { + hasAsync = true; + pending++; + + var resolve = once(function (resolvedDef) { + if (isESModule(resolvedDef)) { + resolvedDef = resolvedDef.default; + } + // save resolved on async factory in case it's used elsewhere + def.resolved = typeof resolvedDef === 'function' + ? resolvedDef + : _Vue.extend(resolvedDef); + match.components[key] = resolvedDef; + pending--; + if (pending <= 0) { + next(); + } + }); + + var reject = once(function (reason) { + var msg = "Failed to resolve async component " + key + ": " + reason; + warn(false, msg); + if (!error) { + error = isError(reason) + ? reason + : new Error(msg); + next(error); + } + }); + + var res; + try { + res = def(resolve, reject); + } catch (e) { + reject(e); + } + if (res) { + if (typeof res.then === 'function') { + res.then(resolve, reject); + } else { + // new syntax in Vue 2.3 + var comp = res.component; + if (comp && typeof comp.then === 'function') { + comp.then(resolve, reject); + } + } + } + } + }); + + if (!hasAsync) { next(); } + } + } + + function flatMapComponents ( + matched, + fn + ) { + return flatten(matched.map(function (m) { + return Object.keys(m.components).map(function (key) { return fn( + m.components[key], + m.instances[key], + m, key + ); }) + })) + } + + function flatten (arr) { + return Array.prototype.concat.apply([], arr) + } + + var hasSymbol = + typeof Symbol === 'function' && + typeof Symbol.toStringTag === 'symbol'; + + function isESModule (obj) { + return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module') + } + + // in Webpack 2, require.ensure now also returns a Promise + // so the resolve/reject functions may get called an extra time + // if the user uses an arrow function shorthand that happens to + // return that Promise. + function once (fn) { + var called = false; + return function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + if (called) { return } + called = true; + return fn.apply(this, args) + } + } + + var NavigationDuplicated = /*@__PURE__*/(function (Error) { + function NavigationDuplicated (normalizedLocation) { + Error.call(this); + this.name = this._name = 'NavigationDuplicated'; + // passing the message to super() doesn't seem to work in the transpiled version + this.message = "Navigating to current location (\"" + (normalizedLocation.fullPath) + "\") is not allowed"; + // add a stack property so services like Sentry can correctly display it + Object.defineProperty(this, 'stack', { + value: new Error().stack, + writable: true, + configurable: true + }); + // we could also have used + // Error.captureStackTrace(this, this.constructor) + // but it only exists on node and chrome + } + + if ( Error ) NavigationDuplicated.__proto__ = Error; + NavigationDuplicated.prototype = Object.create( Error && Error.prototype ); + NavigationDuplicated.prototype.constructor = NavigationDuplicated; + + return NavigationDuplicated; + }(Error)); + + // support IE9 + NavigationDuplicated._name = 'NavigationDuplicated'; + + /* */ + + var History = function History (router, base) { + this.router = router; + this.base = normalizeBase(base); + // start with a route object that stands for "nowhere" + this.current = START; + this.pending = null; + this.ready = false; + this.readyCbs = []; + this.readyErrorCbs = []; + this.errorCbs = []; + }; + + History.prototype.listen = function listen (cb) { + this.cb = cb; + }; + + History.prototype.onReady = function onReady (cb, errorCb) { + if (this.ready) { + cb(); + } else { + this.readyCbs.push(cb); + if (errorCb) { + this.readyErrorCbs.push(errorCb); + } + } + }; + + History.prototype.onError = function onError (errorCb) { + this.errorCbs.push(errorCb); + }; + + History.prototype.transitionTo = function transitionTo ( + location, + onComplete, + onAbort + ) { + var this$1 = this; + + var route = this.router.match(location, this.current); + this.confirmTransition( + route, + function () { + this$1.updateRoute(route); + onComplete && onComplete(route); + this$1.ensureURL(); + + // fire ready cbs once + if (!this$1.ready) { + this$1.ready = true; + this$1.readyCbs.forEach(function (cb) { + cb(route); + }); + } + }, + function (err) { + if (onAbort) { + onAbort(err); + } + if (err && !this$1.ready) { + this$1.ready = true; + this$1.readyErrorCbs.forEach(function (cb) { + cb(err); + }); + } + } + ); + }; + + History.prototype.confirmTransition = function confirmTransition (route, onComplete, onAbort) { + var this$1 = this; + + var current = this.current; + var abort = function (err) { + // after merging https://github.com/vuejs/vue-router/pull/2771 we + // When the user navigates through history through back/forward buttons + // we do not want to throw the error. We only throw it if directly calling + // push/replace. That's why it's not included in isError + if (!isExtendedError(NavigationDuplicated, err) && isError(err)) { + if (this$1.errorCbs.length) { + this$1.errorCbs.forEach(function (cb) { + cb(err); + }); + } else { + warn(false, 'uncaught error during route navigation:'); + console.error(err); + } + } + onAbort && onAbort(err); + }; + if ( + isSameRoute(route, current) && + // in the case the route map has been dynamically appended to + route.matched.length === current.matched.length + ) { + this.ensureURL(); + return abort(new NavigationDuplicated(route)) + } + + var ref = resolveQueue( + this.current.matched, + route.matched + ); + var updated = ref.updated; + var deactivated = ref.deactivated; + var activated = ref.activated; + + var queue = [].concat( + // in-component leave guards + extractLeaveGuards(deactivated), + // global before hooks + this.router.beforeHooks, + // in-component update hooks + extractUpdateHooks(updated), + // in-config enter guards + activated.map(function (m) { return m.beforeEnter; }), + // async components + resolveAsyncComponents(activated) + ); + + this.pending = route; + var iterator = function (hook, next) { + if (this$1.pending !== route) { + return abort() + } + try { + hook(route, current, function (to) { + if (to === false || isError(to)) { + // next(false) -> abort navigation, ensure current URL + this$1.ensureURL(true); + abort(to); + } else if ( + typeof to === 'string' || + (typeof to === 'object' && + (typeof to.path === 'string' || typeof to.name === 'string')) + ) { + // next('/') or next({ path: '/' }) -> redirect + abort(); + if (typeof to === 'object' && to.replace) { + this$1.replace(to); + } else { + this$1.push(to); + } + } else { + // confirm transition and pass on the value + next(to); + } + }); + } catch (e) { + abort(e); + } + }; + + runQueue(queue, iterator, function () { + var postEnterCbs = []; + var isValid = function () { return this$1.current === route; }; + // wait until async components are resolved before + // extracting in-component enter guards + var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid); + var queue = enterGuards.concat(this$1.router.resolveHooks); + runQueue(queue, iterator, function () { + if (this$1.pending !== route) { + return abort() + } + this$1.pending = null; + onComplete(route); + if (this$1.router.app) { + this$1.router.app.$nextTick(function () { + postEnterCbs.forEach(function (cb) { + cb(); + }); + }); + } + }); + }); + }; + + History.prototype.updateRoute = function updateRoute (route) { + var prev = this.current; + this.current = route; + this.cb && this.cb(route); + this.router.afterHooks.forEach(function (hook) { + hook && hook(route, prev); + }); + }; + + function normalizeBase (base) { + if (!base) { + if (inBrowser) { + // respect tag + var baseEl = document.querySelector('base'); + base = (baseEl && baseEl.getAttribute('href')) || '/'; + // strip full URL origin + base = base.replace(/^https?:\/\/[^\/]+/, ''); + } else { + base = '/'; + } + } + // make sure there's the starting slash + if (base.charAt(0) !== '/') { + base = '/' + base; + } + // remove trailing slash + return base.replace(/\/$/, '') + } + + function resolveQueue ( + current, + next + ) { + var i; + var max = Math.max(current.length, next.length); + for (i = 0; i < max; i++) { + if (current[i] !== next[i]) { + break + } + } + return { + updated: next.slice(0, i), + activated: next.slice(i), + deactivated: current.slice(i) + } + } + + function extractGuards ( + records, + name, + bind, + reverse + ) { + var guards = flatMapComponents(records, function (def, instance, match, key) { + var guard = extractGuard(def, name); + if (guard) { + return Array.isArray(guard) + ? guard.map(function (guard) { return bind(guard, instance, match, key); }) + : bind(guard, instance, match, key) + } + }); + return flatten(reverse ? guards.reverse() : guards) + } + + function extractGuard ( + def, + key + ) { + if (typeof def !== 'function') { + // extend now so that global mixins are applied. + def = _Vue.extend(def); + } + return def.options[key] + } + + function extractLeaveGuards (deactivated) { + return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true) + } + + function extractUpdateHooks (updated) { + return extractGuards(updated, 'beforeRouteUpdate', bindGuard) + } + + function bindGuard (guard, instance) { + if (instance) { + return function boundRouteGuard () { + return guard.apply(instance, arguments) + } + } + } + + function extractEnterGuards ( + activated, + cbs, + isValid + ) { + return extractGuards( + activated, + 'beforeRouteEnter', + function (guard, _, match, key) { + return bindEnterGuard(guard, match, key, cbs, isValid) + } + ) + } + + function bindEnterGuard ( + guard, + match, + key, + cbs, + isValid + ) { + return function routeEnterGuard (to, from, next) { + return guard(to, from, function (cb) { + if (typeof cb === 'function') { + cbs.push(function () { + // #750 + // if a router-view is wrapped with an out-in transition, + // the instance may not have been registered at this time. + // we will need to poll for registration until current route + // is no longer valid. + poll(cb, match.instances, key, isValid); + }); + } + next(cb); + }) + } + } + + function poll ( + cb, // somehow flow cannot infer this is a function + instances, + key, + isValid + ) { + if ( + instances[key] && + !instances[key]._isBeingDestroyed // do not reuse being destroyed instance + ) { + cb(instances[key]); + } else if (isValid()) { + setTimeout(function () { + poll(cb, instances, key, isValid); + }, 16); + } + } + + /* */ + + var HTML5History = /*@__PURE__*/(function (History) { + function HTML5History (router, base) { + var this$1 = this; + + History.call(this, router, base); + + var expectScroll = router.options.scrollBehavior; + var supportsScroll = supportsPushState && expectScroll; + + if (supportsScroll) { + setupScroll(); + } + + var initLocation = getLocation(this.base); + window.addEventListener('popstate', function (e) { + var current = this$1.current; + + // Avoiding first `popstate` event dispatched in some browsers but first + // history route not updated since async guard at the same time. + var location = getLocation(this$1.base); + if (this$1.current === START && location === initLocation) { + return + } + + this$1.transitionTo(location, function (route) { + if (supportsScroll) { + handleScroll(router, route, current, true); + } + }); + }); + } + + if ( History ) HTML5History.__proto__ = History; + HTML5History.prototype = Object.create( History && History.prototype ); + HTML5History.prototype.constructor = HTML5History; + + HTML5History.prototype.go = function go (n) { + window.history.go(n); + }; + + HTML5History.prototype.push = function push (location, onComplete, onAbort) { + var this$1 = this; + + var ref = this; + var fromRoute = ref.current; + this.transitionTo(location, function (route) { + pushState(cleanPath(this$1.base + route.fullPath)); + handleScroll(this$1.router, route, fromRoute, false); + onComplete && onComplete(route); + }, onAbort); + }; + + HTML5History.prototype.replace = function replace (location, onComplete, onAbort) { + var this$1 = this; + + var ref = this; + var fromRoute = ref.current; + this.transitionTo(location, function (route) { + replaceState(cleanPath(this$1.base + route.fullPath)); + handleScroll(this$1.router, route, fromRoute, false); + onComplete && onComplete(route); + }, onAbort); + }; + + HTML5History.prototype.ensureURL = function ensureURL (push) { + if (getLocation(this.base) !== this.current.fullPath) { + var current = cleanPath(this.base + this.current.fullPath); + push ? pushState(current) : replaceState(current); + } + }; + + HTML5History.prototype.getCurrentLocation = function getCurrentLocation () { + return getLocation(this.base) + }; + + return HTML5History; + }(History)); + + function getLocation (base) { + var path = decodeURI(window.location.pathname); + if (base && path.indexOf(base) === 0) { + path = path.slice(base.length); + } + return (path || '/') + window.location.search + window.location.hash + } + + /* */ + + var HashHistory = /*@__PURE__*/(function (History) { + function HashHistory (router, base, fallback) { + History.call(this, router, base); + // check history fallback deeplinking + if (fallback && checkFallback(this.base)) { + return + } + ensureSlash(); + } + + if ( History ) HashHistory.__proto__ = History; + HashHistory.prototype = Object.create( History && History.prototype ); + HashHistory.prototype.constructor = HashHistory; + + // this is delayed until the app mounts + // to avoid the hashchange listener being fired too early + HashHistory.prototype.setupListeners = function setupListeners () { + var this$1 = this; + + var router = this.router; + var expectScroll = router.options.scrollBehavior; + var supportsScroll = supportsPushState && expectScroll; + + if (supportsScroll) { + setupScroll(); + } + + window.addEventListener( + supportsPushState ? 'popstate' : 'hashchange', + function () { + var current = this$1.current; + if (!ensureSlash()) { + return + } + this$1.transitionTo(getHash(), function (route) { + if (supportsScroll) { + handleScroll(this$1.router, route, current, true); + } + if (!supportsPushState) { + replaceHash(route.fullPath); + } + }); + } + ); + }; + + HashHistory.prototype.push = function push (location, onComplete, onAbort) { + var this$1 = this; + + var ref = this; + var fromRoute = ref.current; + this.transitionTo( + location, + function (route) { + pushHash(route.fullPath); + handleScroll(this$1.router, route, fromRoute, false); + onComplete && onComplete(route); + }, + onAbort + ); + }; + + HashHistory.prototype.replace = function replace (location, onComplete, onAbort) { + var this$1 = this; + + var ref = this; + var fromRoute = ref.current; + this.transitionTo( + location, + function (route) { + replaceHash(route.fullPath); + handleScroll(this$1.router, route, fromRoute, false); + onComplete && onComplete(route); + }, + onAbort + ); + }; + + HashHistory.prototype.go = function go (n) { + window.history.go(n); + }; + + HashHistory.prototype.ensureURL = function ensureURL (push) { + var current = this.current.fullPath; + if (getHash() !== current) { + push ? pushHash(current) : replaceHash(current); + } + }; + + HashHistory.prototype.getCurrentLocation = function getCurrentLocation () { + return getHash() + }; + + return HashHistory; + }(History)); + + function checkFallback (base) { + var location = getLocation(base); + if (!/^\/#/.test(location)) { + window.location.replace(cleanPath(base + '/#' + location)); + return true + } + } + + function ensureSlash () { + var path = getHash(); + if (path.charAt(0) === '/') { + return true + } + replaceHash('/' + path); + return false + } + + function getHash () { + // We can't use window.location.hash here because it's not + // consistent across browsers - Firefox will pre-decode it! + var href = window.location.href; + var index = href.indexOf('#'); + // empty path + if (index < 0) { return '' } + + href = href.slice(index + 1); + // decode the hash but not the search or hash + // as search(query) is already decoded + // https://github.com/vuejs/vue-router/issues/2708 + var searchIndex = href.indexOf('?'); + if (searchIndex < 0) { + var hashIndex = href.indexOf('#'); + if (hashIndex > -1) { + href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex); + } else { href = decodeURI(href); } + } else { + if (searchIndex > -1) { + href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex); + } + } + + return href + } + + function getUrl (path) { + var href = window.location.href; + var i = href.indexOf('#'); + var base = i >= 0 ? href.slice(0, i) : href; + return (base + "#" + path) + } + + function pushHash (path) { + if (supportsPushState) { + pushState(getUrl(path)); + } else { + window.location.hash = path; + } + } + + function replaceHash (path) { + if (supportsPushState) { + replaceState(getUrl(path)); + } else { + window.location.replace(getUrl(path)); + } + } + + /* */ + + var AbstractHistory = /*@__PURE__*/(function (History) { + function AbstractHistory (router, base) { + History.call(this, router, base); + this.stack = []; + this.index = -1; + } + + if ( History ) AbstractHistory.__proto__ = History; + AbstractHistory.prototype = Object.create( History && History.prototype ); + AbstractHistory.prototype.constructor = AbstractHistory; + + AbstractHistory.prototype.push = function push (location, onComplete, onAbort) { + var this$1 = this; + + this.transitionTo( + location, + function (route) { + this$1.stack = this$1.stack.slice(0, this$1.index + 1).concat(route); + this$1.index++; + onComplete && onComplete(route); + }, + onAbort + ); + }; + + AbstractHistory.prototype.replace = function replace (location, onComplete, onAbort) { + var this$1 = this; + + this.transitionTo( + location, + function (route) { + this$1.stack = this$1.stack.slice(0, this$1.index).concat(route); + onComplete && onComplete(route); + }, + onAbort + ); + }; + + AbstractHistory.prototype.go = function go (n) { + var this$1 = this; + + var targetIndex = this.index + n; + if (targetIndex < 0 || targetIndex >= this.stack.length) { + return + } + var route = this.stack[targetIndex]; + this.confirmTransition( + route, + function () { + this$1.index = targetIndex; + this$1.updateRoute(route); + }, + function (err) { + if (isExtendedError(NavigationDuplicated, err)) { + this$1.index = targetIndex; + } + } + ); + }; + + AbstractHistory.prototype.getCurrentLocation = function getCurrentLocation () { + var current = this.stack[this.stack.length - 1]; + return current ? current.fullPath : '/' + }; + + AbstractHistory.prototype.ensureURL = function ensureURL () { + // noop + }; + + return AbstractHistory; + }(History)); + + /* */ + + + + var VueRouter = function VueRouter (options) { + if ( options === void 0 ) options = {}; + + this.app = null; + this.apps = []; + this.options = options; + this.beforeHooks = []; + this.resolveHooks = []; + this.afterHooks = []; + this.matcher = createMatcher(options.routes || [], this); + + var mode = options.mode || 'hash'; + this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false; + if (this.fallback) { + mode = 'hash'; + } + if (!inBrowser) { + mode = 'abstract'; + } + this.mode = mode; + + switch (mode) { + case 'history': + this.history = new HTML5History(this, options.base); + break + case 'hash': + this.history = new HashHistory(this, options.base, this.fallback); + break + case 'abstract': + this.history = new AbstractHistory(this, options.base); + break + default: + { + assert(false, ("invalid mode: " + mode)); + } + } + }; + + var prototypeAccessors = { currentRoute: { configurable: true } }; + + VueRouter.prototype.match = function match ( + raw, + current, + redirectedFrom + ) { + return this.matcher.match(raw, current, redirectedFrom) + }; + + prototypeAccessors.currentRoute.get = function () { + return this.history && this.history.current + }; + + VueRouter.prototype.init = function init (app /* Vue component instance */) { + var this$1 = this; + + assert( + install.installed, + "not installed. Make sure to call `Vue.use(VueRouter)` " + + "before creating root instance." + ); + + this.apps.push(app); + + // set up app destroyed handler + // https://github.com/vuejs/vue-router/issues/2639 + app.$once('hook:destroyed', function () { + // clean out app from this.apps array once destroyed + var index = this$1.apps.indexOf(app); + if (index > -1) { this$1.apps.splice(index, 1); } + // ensure we still have a main app or null if no apps + // we do not release the router so it can be reused + if (this$1.app === app) { this$1.app = this$1.apps[0] || null; } + }); + + // main app previously initialized + // return as we don't need to set up new history listener + if (this.app) { + return + } + + this.app = app; + + var history = this.history; + + if (history instanceof HTML5History) { + history.transitionTo(history.getCurrentLocation()); + } else if (history instanceof HashHistory) { + var setupHashListener = function () { + history.setupListeners(); + }; + history.transitionTo( + history.getCurrentLocation(), + setupHashListener, + setupHashListener + ); + } + + history.listen(function (route) { + this$1.apps.forEach(function (app) { + app._route = route; + }); + }); + }; + + VueRouter.prototype.beforeEach = function beforeEach (fn) { + return registerHook(this.beforeHooks, fn) + }; + + VueRouter.prototype.beforeResolve = function beforeResolve (fn) { + return registerHook(this.resolveHooks, fn) + }; + + VueRouter.prototype.afterEach = function afterEach (fn) { + return registerHook(this.afterHooks, fn) + }; + + VueRouter.prototype.onReady = function onReady (cb, errorCb) { + this.history.onReady(cb, errorCb); + }; + + VueRouter.prototype.onError = function onError (errorCb) { + this.history.onError(errorCb); + }; + + VueRouter.prototype.push = function push (location, onComplete, onAbort) { + var this$1 = this; + + // $flow-disable-line + if (!onComplete && !onAbort && typeof Promise !== 'undefined') { + return new Promise(function (resolve, reject) { + this$1.history.push(location, resolve, reject); + }) + } else { + this.history.push(location, onComplete, onAbort); + } + }; + + VueRouter.prototype.replace = function replace (location, onComplete, onAbort) { + var this$1 = this; + + // $flow-disable-line + if (!onComplete && !onAbort && typeof Promise !== 'undefined') { + return new Promise(function (resolve, reject) { + this$1.history.replace(location, resolve, reject); + }) + } else { + this.history.replace(location, onComplete, onAbort); + } + }; + + VueRouter.prototype.go = function go (n) { + this.history.go(n); + }; + + VueRouter.prototype.back = function back () { + this.go(-1); + }; + + VueRouter.prototype.forward = function forward () { + this.go(1); + }; + + VueRouter.prototype.getMatchedComponents = function getMatchedComponents (to) { + var route = to + ? to.matched + ? to + : this.resolve(to).route + : this.currentRoute; + if (!route) { + return [] + } + return [].concat.apply([], route.matched.map(function (m) { + return Object.keys(m.components).map(function (key) { + return m.components[key] + }) + })) + }; + + VueRouter.prototype.resolve = function resolve ( + to, + current, + append + ) { + current = current || this.history.current; + var location = normalizeLocation( + to, + current, + append, + this + ); + var route = this.match(location, current); + var fullPath = route.redirectedFrom || route.fullPath; + var base = this.history.base; + var href = createHref(base, fullPath, this.mode); + return { + location: location, + route: route, + href: href, + // for backwards compat + normalizedTo: location, + resolved: route + } + }; + + VueRouter.prototype.addRoutes = function addRoutes (routes) { + this.matcher.addRoutes(routes); + if (this.history.current !== START) { + this.history.transitionTo(this.history.getCurrentLocation()); + } + }; + + Object.defineProperties( VueRouter.prototype, prototypeAccessors ); + + function registerHook (list, fn) { + list.push(fn); + return function () { + var i = list.indexOf(fn); + if (i > -1) { list.splice(i, 1); } + } + } + + function createHref (base, fullPath, mode) { + var path = mode === 'hash' ? '#' + fullPath : fullPath; + return base ? cleanPath(base + '/' + path) : path + } + + VueRouter.install = install; + VueRouter.version = '3.1.3'; + + if (inBrowser && window.Vue) { + window.Vue.use(VueRouter); + } + + return VueRouter; + +})); diff --git a/ln.ethercat.service/www/static/js/vue/vue.js b/ln.ethercat.service/www/static/js/vue/vue.js new file mode 100644 index 0000000..087ee42 --- /dev/null +++ b/ln.ethercat.service/www/static/js/vue/vue.js @@ -0,0 +1,6 @@ +/*! + * Vue.js v2.6.10 + * (c) 2014-2019 Evan You + * Released under the MIT License. + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).Vue=t()}(this,function(){"use strict";var e=Object.freeze({});function t(e){return null==e}function n(e){return null!=e}function r(e){return!0===e}function i(e){return"string"==typeof e||"number"==typeof e||"symbol"==typeof e||"boolean"==typeof e}function o(e){return null!==e&&"object"==typeof e}var a=Object.prototype.toString;function s(e){return"[object Object]"===a.call(e)}function c(e){var t=parseFloat(String(e));return t>=0&&Math.floor(t)===t&&isFinite(e)}function u(e){return n(e)&&"function"==typeof e.then&&"function"==typeof e.catch}function l(e){return null==e?"":Array.isArray(e)||s(e)&&e.toString===a?JSON.stringify(e,null,2):String(e)}function f(e){var t=parseFloat(e);return isNaN(t)?e:t}function p(e,t){for(var n=Object.create(null),r=e.split(","),i=0;i-1)return e.splice(n,1)}}var m=Object.prototype.hasOwnProperty;function y(e,t){return m.call(e,t)}function g(e){var t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}var _=/-(\w)/g,b=g(function(e){return e.replace(_,function(e,t){return t?t.toUpperCase():""})}),$=g(function(e){return e.charAt(0).toUpperCase()+e.slice(1)}),w=/\B([A-Z])/g,C=g(function(e){return e.replace(w,"-$1").toLowerCase()});var x=Function.prototype.bind?function(e,t){return e.bind(t)}:function(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n};function k(e,t){t=t||0;for(var n=e.length-t,r=new Array(n);n--;)r[n]=e[n+t];return r}function A(e,t){for(var n in t)e[n]=t[n];return e}function O(e){for(var t={},n=0;n0,Z=J&&J.indexOf("edge/")>0,G=(J&&J.indexOf("android"),J&&/iphone|ipad|ipod|ios/.test(J)||"ios"===K),X=(J&&/chrome\/\d+/.test(J),J&&/phantomjs/.test(J),J&&J.match(/firefox\/(\d+)/)),Y={}.watch,Q=!1;if(z)try{var ee={};Object.defineProperty(ee,"passive",{get:function(){Q=!0}}),window.addEventListener("test-passive",null,ee)}catch(e){}var te=function(){return void 0===B&&(B=!z&&!V&&"undefined"!=typeof global&&(global.process&&"server"===global.process.env.VUE_ENV)),B},ne=z&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function re(e){return"function"==typeof e&&/native code/.test(e.toString())}var ie,oe="undefined"!=typeof Symbol&&re(Symbol)&&"undefined"!=typeof Reflect&&re(Reflect.ownKeys);ie="undefined"!=typeof Set&&re(Set)?Set:function(){function e(){this.set=Object.create(null)}return e.prototype.has=function(e){return!0===this.set[e]},e.prototype.add=function(e){this.set[e]=!0},e.prototype.clear=function(){this.set=Object.create(null)},e}();var ae=S,se=0,ce=function(){this.id=se++,this.subs=[]};ce.prototype.addSub=function(e){this.subs.push(e)},ce.prototype.removeSub=function(e){h(this.subs,e)},ce.prototype.depend=function(){ce.target&&ce.target.addDep(this)},ce.prototype.notify=function(){for(var e=this.subs.slice(),t=0,n=e.length;t-1)if(o&&!y(i,"default"))a=!1;else if(""===a||a===C(e)){var c=Pe(String,i.type);(c<0||s0&&(st((u=e(u,(a||"")+"_"+c))[0])&&st(f)&&(s[l]=he(f.text+u[0].text),u.shift()),s.push.apply(s,u)):i(u)?st(f)?s[l]=he(f.text+u):""!==u&&s.push(he(u)):st(u)&&st(f)?s[l]=he(f.text+u.text):(r(o._isVList)&&n(u.tag)&&t(u.key)&&n(a)&&(u.key="__vlist"+a+"_"+c+"__"),s.push(u)));return s}(e):void 0}function st(e){return n(e)&&n(e.text)&&!1===e.isComment}function ct(e,t){if(e){for(var n=Object.create(null),r=oe?Reflect.ownKeys(e):Object.keys(e),i=0;i0,a=t?!!t.$stable:!o,s=t&&t.$key;if(t){if(t._normalized)return t._normalized;if(a&&r&&r!==e&&s===r.$key&&!o&&!r.$hasNormal)return r;for(var c in i={},t)t[c]&&"$"!==c[0]&&(i[c]=pt(n,c,t[c]))}else i={};for(var u in n)u in i||(i[u]=dt(n,u));return t&&Object.isExtensible(t)&&(t._normalized=i),R(i,"$stable",a),R(i,"$key",s),R(i,"$hasNormal",o),i}function pt(e,t,n){var r=function(){var e=arguments.length?n.apply(null,arguments):n({});return(e=e&&"object"==typeof e&&!Array.isArray(e)?[e]:at(e))&&(0===e.length||1===e.length&&e[0].isComment)?void 0:e};return n.proxy&&Object.defineProperty(e,t,{get:r,enumerable:!0,configurable:!0}),r}function dt(e,t){return function(){return e[t]}}function vt(e,t){var r,i,a,s,c;if(Array.isArray(e)||"string"==typeof e)for(r=new Array(e.length),i=0,a=e.length;idocument.createEvent("Event").timeStamp&&(sn=function(){return cn.now()})}function un(){var e,t;for(an=sn(),rn=!0,Qt.sort(function(e,t){return e.id-t.id}),on=0;onon&&Qt[n].id>e.id;)n--;Qt.splice(n+1,0,e)}else Qt.push(e);nn||(nn=!0,Ye(un))}}(this)},fn.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||o(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(e){Re(e,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,e,t)}}},fn.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},fn.prototype.depend=function(){for(var e=this.deps.length;e--;)this.deps[e].depend()},fn.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||h(this.vm._watchers,this);for(var e=this.deps.length;e--;)this.deps[e].removeSub(this);this.active=!1}};var pn={enumerable:!0,configurable:!0,get:S,set:S};function dn(e,t,n){pn.get=function(){return this[t][n]},pn.set=function(e){this[t][n]=e},Object.defineProperty(e,n,pn)}function vn(e){e._watchers=[];var t=e.$options;t.props&&function(e,t){var n=e.$options.propsData||{},r=e._props={},i=e.$options._propKeys=[];e.$parent&&$e(!1);var o=function(o){i.push(o);var a=Me(o,t,n,e);xe(r,o,a),o in e||dn(e,"_props",o)};for(var a in t)o(a);$e(!0)}(e,t.props),t.methods&&function(e,t){e.$options.props;for(var n in t)e[n]="function"!=typeof t[n]?S:x(t[n],e)}(e,t.methods),t.data?function(e){var t=e.$options.data;s(t=e._data="function"==typeof t?function(e,t){le();try{return e.call(t,t)}catch(e){return Re(e,t,"data()"),{}}finally{fe()}}(t,e):t||{})||(t={});var n=Object.keys(t),r=e.$options.props,i=(e.$options.methods,n.length);for(;i--;){var o=n[i];r&&y(r,o)||(a=void 0,36!==(a=(o+"").charCodeAt(0))&&95!==a&&dn(e,"_data",o))}var a;Ce(t,!0)}(e):Ce(e._data={},!0),t.computed&&function(e,t){var n=e._computedWatchers=Object.create(null),r=te();for(var i in t){var o=t[i],a="function"==typeof o?o:o.get;r||(n[i]=new fn(e,a||S,S,hn)),i in e||mn(e,i,o)}}(e,t.computed),t.watch&&t.watch!==Y&&function(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;i-1:"string"==typeof e?e.split(",").indexOf(t)>-1:(n=e,"[object RegExp]"===a.call(n)&&e.test(t));var n}function An(e,t){var n=e.cache,r=e.keys,i=e._vnode;for(var o in n){var a=n[o];if(a){var s=xn(a.componentOptions);s&&!t(s)&&On(n,o,r,i)}}}function On(e,t,n,r){var i=e[t];!i||r&&i.tag===r.tag||i.componentInstance.$destroy(),e[t]=null,h(n,t)}!function(t){t.prototype._init=function(t){var n=this;n._uid=bn++,n._isVue=!0,t&&t._isComponent?function(e,t){var n=e.$options=Object.create(e.constructor.options),r=t._parentVnode;n.parent=t.parent,n._parentVnode=r;var i=r.componentOptions;n.propsData=i.propsData,n._parentListeners=i.listeners,n._renderChildren=i.children,n._componentTag=i.tag,t.render&&(n.render=t.render,n.staticRenderFns=t.staticRenderFns)}(n,t):n.$options=De($n(n.constructor),t||{},n),n._renderProxy=n,n._self=n,function(e){var t=e.$options,n=t.parent;if(n&&!t.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(e)}e.$parent=n,e.$root=n?n.$root:e,e.$children=[],e.$refs={},e._watcher=null,e._inactive=null,e._directInactive=!1,e._isMounted=!1,e._isDestroyed=!1,e._isBeingDestroyed=!1}(n),function(e){e._events=Object.create(null),e._hasHookEvent=!1;var t=e.$options._parentListeners;t&&qt(e,t)}(n),function(t){t._vnode=null,t._staticTrees=null;var n=t.$options,r=t.$vnode=n._parentVnode,i=r&&r.context;t.$slots=ut(n._renderChildren,i),t.$scopedSlots=e,t._c=function(e,n,r,i){return Pt(t,e,n,r,i,!1)},t.$createElement=function(e,n,r,i){return Pt(t,e,n,r,i,!0)};var o=r&&r.data;xe(t,"$attrs",o&&o.attrs||e,null,!0),xe(t,"$listeners",n._parentListeners||e,null,!0)}(n),Yt(n,"beforeCreate"),function(e){var t=ct(e.$options.inject,e);t&&($e(!1),Object.keys(t).forEach(function(n){xe(e,n,t[n])}),$e(!0))}(n),vn(n),function(e){var t=e.$options.provide;t&&(e._provided="function"==typeof t?t.call(e):t)}(n),Yt(n,"created"),n.$options.el&&n.$mount(n.$options.el)}}(wn),function(e){var t={get:function(){return this._data}},n={get:function(){return this._props}};Object.defineProperty(e.prototype,"$data",t),Object.defineProperty(e.prototype,"$props",n),e.prototype.$set=ke,e.prototype.$delete=Ae,e.prototype.$watch=function(e,t,n){if(s(t))return _n(this,e,t,n);(n=n||{}).user=!0;var r=new fn(this,e,t,n);if(n.immediate)try{t.call(this,r.value)}catch(e){Re(e,this,'callback for immediate watcher "'+r.expression+'"')}return function(){r.teardown()}}}(wn),function(e){var t=/^hook:/;e.prototype.$on=function(e,n){var r=this;if(Array.isArray(e))for(var i=0,o=e.length;i1?k(t):t;for(var n=k(arguments,1),r='event handler for "'+e+'"',i=0,o=t.length;iparseInt(this.max)&&On(a,s[0],s,this._vnode)),t.data.keepAlive=!0}return t||e&&e[0]}}};!function(e){var t={get:function(){return F}};Object.defineProperty(e,"config",t),e.util={warn:ae,extend:A,mergeOptions:De,defineReactive:xe},e.set=ke,e.delete=Ae,e.nextTick=Ye,e.observable=function(e){return Ce(e),e},e.options=Object.create(null),M.forEach(function(t){e.options[t+"s"]=Object.create(null)}),e.options._base=e,A(e.options.components,Tn),function(e){e.use=function(e){var t=this._installedPlugins||(this._installedPlugins=[]);if(t.indexOf(e)>-1)return this;var n=k(arguments,1);return n.unshift(this),"function"==typeof e.install?e.install.apply(e,n):"function"==typeof e&&e.apply(null,n),t.push(e),this}}(e),function(e){e.mixin=function(e){return this.options=De(this.options,e),this}}(e),Cn(e),function(e){M.forEach(function(t){e[t]=function(e,n){return n?("component"===t&&s(n)&&(n.name=n.name||e,n=this.options._base.extend(n)),"directive"===t&&"function"==typeof n&&(n={bind:n,update:n}),this.options[t+"s"][e]=n,n):this.options[t+"s"][e]}})}(e)}(wn),Object.defineProperty(wn.prototype,"$isServer",{get:te}),Object.defineProperty(wn.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(wn,"FunctionalRenderContext",{value:Tt}),wn.version="2.6.10";var En=p("style,class"),Nn=p("input,textarea,option,select,progress"),jn=function(e,t,n){return"value"===n&&Nn(e)&&"button"!==t||"selected"===n&&"option"===e||"checked"===n&&"input"===e||"muted"===n&&"video"===e},Dn=p("contenteditable,draggable,spellcheck"),Ln=p("events,caret,typing,plaintext-only"),Mn=function(e,t){return Hn(t)||"false"===t?"false":"contenteditable"===e&&Ln(t)?t:"true"},In=p("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),Fn="http://www.w3.org/1999/xlink",Pn=function(e){return":"===e.charAt(5)&&"xlink"===e.slice(0,5)},Rn=function(e){return Pn(e)?e.slice(6,e.length):""},Hn=function(e){return null==e||!1===e};function Bn(e){for(var t=e.data,r=e,i=e;n(i.componentInstance);)(i=i.componentInstance._vnode)&&i.data&&(t=Un(i.data,t));for(;n(r=r.parent);)r&&r.data&&(t=Un(t,r.data));return function(e,t){if(n(e)||n(t))return zn(e,Vn(t));return""}(t.staticClass,t.class)}function Un(e,t){return{staticClass:zn(e.staticClass,t.staticClass),class:n(e.class)?[e.class,t.class]:t.class}}function zn(e,t){return e?t?e+" "+t:e:t||""}function Vn(e){return Array.isArray(e)?function(e){for(var t,r="",i=0,o=e.length;i-1?hr(e,t,n):In(t)?Hn(n)?e.removeAttribute(t):(n="allowfullscreen"===t&&"EMBED"===e.tagName?"true":t,e.setAttribute(t,n)):Dn(t)?e.setAttribute(t,Mn(t,n)):Pn(t)?Hn(n)?e.removeAttributeNS(Fn,Rn(t)):e.setAttributeNS(Fn,t,n):hr(e,t,n)}function hr(e,t,n){if(Hn(n))e.removeAttribute(t);else{if(q&&!W&&"TEXTAREA"===e.tagName&&"placeholder"===t&&""!==n&&!e.__ieph){var r=function(t){t.stopImmediatePropagation(),e.removeEventListener("input",r)};e.addEventListener("input",r),e.__ieph=!0}e.setAttribute(t,n)}}var mr={create:dr,update:dr};function yr(e,r){var i=r.elm,o=r.data,a=e.data;if(!(t(o.staticClass)&&t(o.class)&&(t(a)||t(a.staticClass)&&t(a.class)))){var s=Bn(r),c=i._transitionClasses;n(c)&&(s=zn(s,Vn(c))),s!==i._prevClass&&(i.setAttribute("class",s),i._prevClass=s)}}var gr,_r,br,$r,wr,Cr,xr={create:yr,update:yr},kr=/[\w).+\-_$\]]/;function Ar(e){var t,n,r,i,o,a=!1,s=!1,c=!1,u=!1,l=0,f=0,p=0,d=0;for(r=0;r=0&&" "===(h=e.charAt(v));v--);h&&kr.test(h)||(u=!0)}}else void 0===i?(d=r+1,i=e.slice(0,r).trim()):m();function m(){(o||(o=[])).push(e.slice(d,r).trim()),d=r+1}if(void 0===i?i=e.slice(0,r).trim():0!==d&&m(),o)for(r=0;r-1?{exp:e.slice(0,$r),key:'"'+e.slice($r+1)+'"'}:{exp:e,key:null};_r=e,$r=wr=Cr=0;for(;!zr();)Vr(br=Ur())?Jr(br):91===br&&Kr(br);return{exp:e.slice(0,wr),key:e.slice(wr+1,Cr)}}(e);return null===n.key?e+"="+t:"$set("+n.exp+", "+n.key+", "+t+")"}function Ur(){return _r.charCodeAt(++$r)}function zr(){return $r>=gr}function Vr(e){return 34===e||39===e}function Kr(e){var t=1;for(wr=$r;!zr();)if(Vr(e=Ur()))Jr(e);else if(91===e&&t++,93===e&&t--,0===t){Cr=$r;break}}function Jr(e){for(var t=e;!zr()&&(e=Ur())!==t;);}var qr,Wr="__r",Zr="__c";function Gr(e,t,n){var r=qr;return function i(){null!==t.apply(null,arguments)&&Qr(e,i,n,r)}}var Xr=Ve&&!(X&&Number(X[1])<=53);function Yr(e,t,n,r){if(Xr){var i=an,o=t;t=o._wrapper=function(e){if(e.target===e.currentTarget||e.timeStamp>=i||e.timeStamp<=0||e.target.ownerDocument!==document)return o.apply(this,arguments)}}qr.addEventListener(e,t,Q?{capture:n,passive:r}:n)}function Qr(e,t,n,r){(r||qr).removeEventListener(e,t._wrapper||t,n)}function ei(e,r){if(!t(e.data.on)||!t(r.data.on)){var i=r.data.on||{},o=e.data.on||{};qr=r.elm,function(e){if(n(e[Wr])){var t=q?"change":"input";e[t]=[].concat(e[Wr],e[t]||[]),delete e[Wr]}n(e[Zr])&&(e.change=[].concat(e[Zr],e.change||[]),delete e[Zr])}(i),rt(i,o,Yr,Qr,Gr,r.context),qr=void 0}}var ti,ni={create:ei,update:ei};function ri(e,r){if(!t(e.data.domProps)||!t(r.data.domProps)){var i,o,a=r.elm,s=e.data.domProps||{},c=r.data.domProps||{};for(i in n(c.__ob__)&&(c=r.data.domProps=A({},c)),s)i in c||(a[i]="");for(i in c){if(o=c[i],"textContent"===i||"innerHTML"===i){if(r.children&&(r.children.length=0),o===s[i])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===i&&"PROGRESS"!==a.tagName){a._value=o;var u=t(o)?"":String(o);ii(a,u)&&(a.value=u)}else if("innerHTML"===i&&qn(a.tagName)&&t(a.innerHTML)){(ti=ti||document.createElement("div")).innerHTML=""+o+"";for(var l=ti.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;l.firstChild;)a.appendChild(l.firstChild)}else if(o!==s[i])try{a[i]=o}catch(e){}}}}function ii(e,t){return!e.composing&&("OPTION"===e.tagName||function(e,t){var n=!0;try{n=document.activeElement!==e}catch(e){}return n&&e.value!==t}(e,t)||function(e,t){var r=e.value,i=e._vModifiers;if(n(i)){if(i.number)return f(r)!==f(t);if(i.trim)return r.trim()!==t.trim()}return r!==t}(e,t))}var oi={create:ri,update:ri},ai=g(function(e){var t={},n=/:(.+)/;return e.split(/;(?![^(]*\))/g).forEach(function(e){if(e){var r=e.split(n);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t});function si(e){var t=ci(e.style);return e.staticStyle?A(e.staticStyle,t):t}function ci(e){return Array.isArray(e)?O(e):"string"==typeof e?ai(e):e}var ui,li=/^--/,fi=/\s*!important$/,pi=function(e,t,n){if(li.test(t))e.style.setProperty(t,n);else if(fi.test(n))e.style.setProperty(C(t),n.replace(fi,""),"important");else{var r=vi(t);if(Array.isArray(n))for(var i=0,o=n.length;i-1?t.split(yi).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var n=" "+(e.getAttribute("class")||"")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function _i(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(yi).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t),e.classList.length||e.removeAttribute("class");else{for(var n=" "+(e.getAttribute("class")||"")+" ",r=" "+t+" ";n.indexOf(r)>=0;)n=n.replace(r," ");(n=n.trim())?e.setAttribute("class",n):e.removeAttribute("class")}}function bi(e){if(e){if("object"==typeof e){var t={};return!1!==e.css&&A(t,$i(e.name||"v")),A(t,e),t}return"string"==typeof e?$i(e):void 0}}var $i=g(function(e){return{enterClass:e+"-enter",enterToClass:e+"-enter-to",enterActiveClass:e+"-enter-active",leaveClass:e+"-leave",leaveToClass:e+"-leave-to",leaveActiveClass:e+"-leave-active"}}),wi=z&&!W,Ci="transition",xi="animation",ki="transition",Ai="transitionend",Oi="animation",Si="animationend";wi&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(ki="WebkitTransition",Ai="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Oi="WebkitAnimation",Si="webkitAnimationEnd"));var Ti=z?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(e){return e()};function Ei(e){Ti(function(){Ti(e)})}function Ni(e,t){var n=e._transitionClasses||(e._transitionClasses=[]);n.indexOf(t)<0&&(n.push(t),gi(e,t))}function ji(e,t){e._transitionClasses&&h(e._transitionClasses,t),_i(e,t)}function Di(e,t,n){var r=Mi(e,t),i=r.type,o=r.timeout,a=r.propCount;if(!i)return n();var s=i===Ci?Ai:Si,c=0,u=function(){e.removeEventListener(s,l),n()},l=function(t){t.target===e&&++c>=a&&u()};setTimeout(function(){c0&&(n=Ci,l=a,f=o.length):t===xi?u>0&&(n=xi,l=u,f=c.length):f=(n=(l=Math.max(a,u))>0?a>u?Ci:xi:null)?n===Ci?o.length:c.length:0,{type:n,timeout:l,propCount:f,hasTransform:n===Ci&&Li.test(r[ki+"Property"])}}function Ii(e,t){for(;e.length1}function Ui(e,t){!0!==t.data.show&&Pi(t)}var zi=function(e){var o,a,s={},c=e.modules,u=e.nodeOps;for(o=0;ov?_(e,t(i[y+1])?null:i[y+1].elm,i,d,y,o):d>y&&$(0,r,p,v)}(p,h,y,o,l):n(y)?(n(e.text)&&u.setTextContent(p,""),_(p,null,y,0,y.length-1,o)):n(h)?$(0,h,0,h.length-1):n(e.text)&&u.setTextContent(p,""):e.text!==i.text&&u.setTextContent(p,i.text),n(v)&&n(d=v.hook)&&n(d=d.postpatch)&&d(e,i)}}}function k(e,t,i){if(r(i)&&n(e.parent))e.parent.data.pendingInsert=t;else for(var o=0;o-1,a.selected!==o&&(a.selected=o);else if(N(Wi(a),r))return void(e.selectedIndex!==s&&(e.selectedIndex=s));i||(e.selectedIndex=-1)}}function qi(e,t){return t.every(function(t){return!N(t,e)})}function Wi(e){return"_value"in e?e._value:e.value}function Zi(e){e.target.composing=!0}function Gi(e){e.target.composing&&(e.target.composing=!1,Xi(e.target,"input"))}function Xi(e,t){var n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}function Yi(e){return!e.componentInstance||e.data&&e.data.transition?e:Yi(e.componentInstance._vnode)}var Qi={model:Vi,show:{bind:function(e,t,n){var r=t.value,i=(n=Yi(n)).data&&n.data.transition,o=e.__vOriginalDisplay="none"===e.style.display?"":e.style.display;r&&i?(n.data.show=!0,Pi(n,function(){e.style.display=o})):e.style.display=r?o:"none"},update:function(e,t,n){var r=t.value;!r!=!t.oldValue&&((n=Yi(n)).data&&n.data.transition?(n.data.show=!0,r?Pi(n,function(){e.style.display=e.__vOriginalDisplay}):Ri(n,function(){e.style.display="none"})):e.style.display=r?e.__vOriginalDisplay:"none")},unbind:function(e,t,n,r,i){i||(e.style.display=e.__vOriginalDisplay)}}},eo={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function to(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abstract?to(zt(t.children)):e}function no(e){var t={},n=e.$options;for(var r in n.propsData)t[r]=e[r];var i=n._parentListeners;for(var o in i)t[b(o)]=i[o];return t}function ro(e,t){if(/\d-keep-alive$/.test(t.tag))return e("keep-alive",{props:t.componentOptions.propsData})}var io=function(e){return e.tag||Ut(e)},oo=function(e){return"show"===e.name},ao={name:"transition",props:eo,abstract:!0,render:function(e){var t=this,n=this.$slots.default;if(n&&(n=n.filter(io)).length){var r=this.mode,o=n[0];if(function(e){for(;e=e.parent;)if(e.data.transition)return!0}(this.$vnode))return o;var a=to(o);if(!a)return o;if(this._leaving)return ro(e,o);var s="__transition-"+this._uid+"-";a.key=null==a.key?a.isComment?s+"comment":s+a.tag:i(a.key)?0===String(a.key).indexOf(s)?a.key:s+a.key:a.key;var c=(a.data||(a.data={})).transition=no(this),u=this._vnode,l=to(u);if(a.data.directives&&a.data.directives.some(oo)&&(a.data.show=!0),l&&l.data&&!function(e,t){return t.key===e.key&&t.tag===e.tag}(a,l)&&!Ut(l)&&(!l.componentInstance||!l.componentInstance._vnode.isComment)){var f=l.data.transition=A({},c);if("out-in"===r)return this._leaving=!0,it(f,"afterLeave",function(){t._leaving=!1,t.$forceUpdate()}),ro(e,o);if("in-out"===r){if(Ut(a))return u;var p,d=function(){p()};it(c,"afterEnter",d),it(c,"enterCancelled",d),it(f,"delayLeave",function(e){p=e})}}return o}}},so=A({tag:String,moveClass:String},eo);function co(e){e.elm._moveCb&&e.elm._moveCb(),e.elm._enterCb&&e.elm._enterCb()}function uo(e){e.data.newPos=e.elm.getBoundingClientRect()}function lo(e){var t=e.data.pos,n=e.data.newPos,r=t.left-n.left,i=t.top-n.top;if(r||i){e.data.moved=!0;var o=e.elm.style;o.transform=o.WebkitTransform="translate("+r+"px,"+i+"px)",o.transitionDuration="0s"}}delete so.mode;var fo={Transition:ao,TransitionGroup:{props:so,beforeMount:function(){var e=this,t=this._update;this._update=function(n,r){var i=Zt(e);e.__patch__(e._vnode,e.kept,!1,!0),e._vnode=e.kept,i(),t.call(e,n,r)}},render:function(e){for(var t=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,i=this.$slots.default||[],o=this.children=[],a=no(this),s=0;s-1?Gn[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:Gn[e]=/HTMLUnknownElement/.test(t.toString())},A(wn.options.directives,Qi),A(wn.options.components,fo),wn.prototype.__patch__=z?zi:S,wn.prototype.$mount=function(e,t){return function(e,t,n){var r;return e.$el=t,e.$options.render||(e.$options.render=ve),Yt(e,"beforeMount"),r=function(){e._update(e._render(),n)},new fn(e,r,S,{before:function(){e._isMounted&&!e._isDestroyed&&Yt(e,"beforeUpdate")}},!0),n=!1,null==e.$vnode&&(e._isMounted=!0,Yt(e,"mounted")),e}(this,e=e&&z?Yn(e):void 0,t)},z&&setTimeout(function(){F.devtools&&ne&&ne.emit("init",wn)},0);var po=/\{\{((?:.|\r?\n)+?)\}\}/g,vo=/[-.*+?^${}()|[\]\/\\]/g,ho=g(function(e){var t=e[0].replace(vo,"\\$&"),n=e[1].replace(vo,"\\$&");return new RegExp(t+"((?:.|\\n)+?)"+n,"g")});var mo={staticKeys:["staticClass"],transformNode:function(e,t){t.warn;var n=Fr(e,"class");n&&(e.staticClass=JSON.stringify(n));var r=Ir(e,"class",!1);r&&(e.classBinding=r)},genData:function(e){var t="";return e.staticClass&&(t+="staticClass:"+e.staticClass+","),e.classBinding&&(t+="class:"+e.classBinding+","),t}};var yo,go={staticKeys:["staticStyle"],transformNode:function(e,t){t.warn;var n=Fr(e,"style");n&&(e.staticStyle=JSON.stringify(ai(n)));var r=Ir(e,"style",!1);r&&(e.styleBinding=r)},genData:function(e){var t="";return e.staticStyle&&(t+="staticStyle:"+e.staticStyle+","),e.styleBinding&&(t+="style:("+e.styleBinding+"),"),t}},_o=function(e){return(yo=yo||document.createElement("div")).innerHTML=e,yo.textContent},bo=p("area,base,br,col,embed,frame,hr,img,input,isindex,keygen,link,meta,param,source,track,wbr"),$o=p("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source"),wo=p("address,article,aside,base,blockquote,body,caption,col,colgroup,dd,details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,title,tr,track"),Co=/^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,xo=/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,ko="[a-zA-Z_][\\-\\.0-9_a-zA-Z"+P.source+"]*",Ao="((?:"+ko+"\\:)?"+ko+")",Oo=new RegExp("^<"+Ao),So=/^\s*(\/?)>/,To=new RegExp("^<\\/"+Ao+"[^>]*>"),Eo=/^]+>/i,No=/^",""":'"',"&":"&"," ":"\n"," ":"\t","'":"'"},Io=/&(?:lt|gt|quot|amp|#39);/g,Fo=/&(?:lt|gt|quot|amp|#39|#10|#9);/g,Po=p("pre,textarea",!0),Ro=function(e,t){return e&&Po(e)&&"\n"===t[0]};function Ho(e,t){var n=t?Fo:Io;return e.replace(n,function(e){return Mo[e]})}var Bo,Uo,zo,Vo,Ko,Jo,qo,Wo,Zo=/^@|^v-on:/,Go=/^v-|^@|^:/,Xo=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,Yo=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,Qo=/^\(|\)$/g,ea=/^\[.*\]$/,ta=/:(.*)$/,na=/^:|^\.|^v-bind:/,ra=/\.[^.\]]+(?=[^\]]*$)/g,ia=/^v-slot(:|$)|^#/,oa=/[\r\n]/,aa=/\s+/g,sa=g(_o),ca="_empty_";function ua(e,t,n){return{type:1,tag:e,attrsList:t,attrsMap:ma(t),rawAttrsMap:{},parent:n,children:[]}}function la(e,t){Bo=t.warn||Sr,Jo=t.isPreTag||T,qo=t.mustUseProp||T,Wo=t.getTagNamespace||T;t.isReservedTag;zo=Tr(t.modules,"transformNode"),Vo=Tr(t.modules,"preTransformNode"),Ko=Tr(t.modules,"postTransformNode"),Uo=t.delimiters;var n,r,i=[],o=!1!==t.preserveWhitespace,a=t.whitespace,s=!1,c=!1;function u(e){if(l(e),s||e.processed||(e=fa(e,t)),i.length||e===n||n.if&&(e.elseif||e.else)&&da(n,{exp:e.elseif,block:e}),r&&!e.forbidden)if(e.elseif||e.else)a=e,(u=function(e){var t=e.length;for(;t--;){if(1===e[t].type)return e[t];e.pop()}}(r.children))&&u.if&&da(u,{exp:a.elseif,block:a});else{if(e.slotScope){var o=e.slotTarget||'"default"';(r.scopedSlots||(r.scopedSlots={}))[o]=e}r.children.push(e),e.parent=r}var a,u;e.children=e.children.filter(function(e){return!e.slotScope}),l(e),e.pre&&(s=!1),Jo(e.tag)&&(c=!1);for(var f=0;f]*>)","i")),p=e.replace(f,function(e,n,r){return u=r.length,Do(l)||"noscript"===l||(n=n.replace(//g,"$1").replace(//g,"$1")),Ro(l,n)&&(n=n.slice(1)),t.chars&&t.chars(n),""});c+=e.length-p.length,e=p,A(l,c-u,c)}else{var d=e.indexOf("<");if(0===d){if(No.test(e)){var v=e.indexOf("--\x3e");if(v>=0){t.shouldKeepComment&&t.comment(e.substring(4,v),c,c+v+3),C(v+3);continue}}if(jo.test(e)){var h=e.indexOf("]>");if(h>=0){C(h+2);continue}}var m=e.match(Eo);if(m){C(m[0].length);continue}var y=e.match(To);if(y){var g=c;C(y[0].length),A(y[1],g,c);continue}var _=x();if(_){k(_),Ro(_.tagName,e)&&C(1);continue}}var b=void 0,$=void 0,w=void 0;if(d>=0){for($=e.slice(d);!(To.test($)||Oo.test($)||No.test($)||jo.test($)||(w=$.indexOf("<",1))<0);)d+=w,$=e.slice(d);b=e.substring(0,d)}d<0&&(b=e),b&&C(b.length),t.chars&&b&&t.chars(b,c-b.length,c)}if(e===n){t.chars&&t.chars(e);break}}function C(t){c+=t,e=e.substring(t)}function x(){var t=e.match(Oo);if(t){var n,r,i={tagName:t[1],attrs:[],start:c};for(C(t[0].length);!(n=e.match(So))&&(r=e.match(xo)||e.match(Co));)r.start=c,C(r[0].length),r.end=c,i.attrs.push(r);if(n)return i.unarySlash=n[1],C(n[0].length),i.end=c,i}}function k(e){var n=e.tagName,c=e.unarySlash;o&&("p"===r&&wo(n)&&A(r),s(n)&&r===n&&A(n));for(var u=a(n)||!!c,l=e.attrs.length,f=new Array(l),p=0;p=0&&i[a].lowerCasedTag!==s;a--);else a=0;if(a>=0){for(var u=i.length-1;u>=a;u--)t.end&&t.end(i[u].tag,n,o);i.length=a,r=a&&i[a-1].tag}else"br"===s?t.start&&t.start(e,[],!0,n,o):"p"===s&&(t.start&&t.start(e,[],!1,n,o),t.end&&t.end(e,n,o))}A()}(e,{warn:Bo,expectHTML:t.expectHTML,isUnaryTag:t.isUnaryTag,canBeLeftOpenTag:t.canBeLeftOpenTag,shouldDecodeNewlines:t.shouldDecodeNewlines,shouldDecodeNewlinesForHref:t.shouldDecodeNewlinesForHref,shouldKeepComment:t.comments,outputSourceRange:t.outputSourceRange,start:function(e,o,a,l,f){var p=r&&r.ns||Wo(e);q&&"svg"===p&&(o=function(e){for(var t=[],n=0;nc&&(s.push(o=e.slice(c,i)),a.push(JSON.stringify(o)));var u=Ar(r[1].trim());a.push("_s("+u+")"),s.push({"@binding":u}),c=i+r[0].length}return c-1"+("true"===o?":("+t+")":":_q("+t+","+o+")")),Mr(e,"change","var $$a="+t+",$$el=$event.target,$$c=$$el.checked?("+o+"):("+a+");if(Array.isArray($$a)){var $$v="+(r?"_n("+i+")":i)+",$$i=_i($$a,$$v);if($$el.checked){$$i<0&&("+Br(t,"$$a.concat([$$v])")+")}else{$$i>-1&&("+Br(t,"$$a.slice(0,$$i).concat($$a.slice($$i+1))")+")}}else{"+Br(t,"$$c")+"}",null,!0)}(e,r,i);else if("input"===o&&"radio"===a)!function(e,t,n){var r=n&&n.number,i=Ir(e,"value")||"null";Er(e,"checked","_q("+t+","+(i=r?"_n("+i+")":i)+")"),Mr(e,"change",Br(t,i),null,!0)}(e,r,i);else if("input"===o||"textarea"===o)!function(e,t,n){var r=e.attrsMap.type,i=n||{},o=i.lazy,a=i.number,s=i.trim,c=!o&&"range"!==r,u=o?"change":"range"===r?Wr:"input",l="$event.target.value";s&&(l="$event.target.value.trim()"),a&&(l="_n("+l+")");var f=Br(t,l);c&&(f="if($event.target.composing)return;"+f),Er(e,"value","("+t+")"),Mr(e,u,f,null,!0),(s||a)&&Mr(e,"blur","$forceUpdate()")}(e,r,i);else if(!F.isReservedTag(o))return Hr(e,r,i),!1;return!0},text:function(e,t){t.value&&Er(e,"textContent","_s("+t.value+")",t)},html:function(e,t){t.value&&Er(e,"innerHTML","_s("+t.value+")",t)}},isPreTag:function(e){return"pre"===e},isUnaryTag:bo,mustUseProp:jn,canBeLeftOpenTag:$o,isReservedTag:Wn,getTagNamespace:Zn,staticKeys:function(e){return e.reduce(function(e,t){return e.concat(t.staticKeys||[])},[]).join(",")}(ba)},xa=g(function(e){return p("type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap"+(e?","+e:""))});function ka(e,t){e&&($a=xa(t.staticKeys||""),wa=t.isReservedTag||T,function e(t){t.static=function(e){if(2===e.type)return!1;if(3===e.type)return!0;return!(!e.pre&&(e.hasBindings||e.if||e.for||d(e.tag)||!wa(e.tag)||function(e){for(;e.parent;){if("template"!==(e=e.parent).tag)return!1;if(e.for)return!0}return!1}(e)||!Object.keys(e).every($a)))}(t);if(1===t.type){if(!wa(t.tag)&&"slot"!==t.tag&&null==t.attrsMap["inline-template"])return;for(var n=0,r=t.children.length;n|^function\s*(?:[\w$]+)?\s*\(/,Oa=/\([^)]*?\);*$/,Sa=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,Ta={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},Ea={esc:["Esc","Escape"],tab:"Tab",enter:"Enter",space:[" ","Spacebar"],up:["Up","ArrowUp"],left:["Left","ArrowLeft"],right:["Right","ArrowRight"],down:["Down","ArrowDown"],delete:["Backspace","Delete","Del"]},Na=function(e){return"if("+e+")return null;"},ja={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:Na("$event.target !== $event.currentTarget"),ctrl:Na("!$event.ctrlKey"),shift:Na("!$event.shiftKey"),alt:Na("!$event.altKey"),meta:Na("!$event.metaKey"),left:Na("'button' in $event && $event.button !== 0"),middle:Na("'button' in $event && $event.button !== 1"),right:Na("'button' in $event && $event.button !== 2")};function Da(e,t){var n=t?"nativeOn:":"on:",r="",i="";for(var o in e){var a=La(e[o]);e[o]&&e[o].dynamic?i+=o+","+a+",":r+='"'+o+'":'+a+","}return r="{"+r.slice(0,-1)+"}",i?n+"_d("+r+",["+i.slice(0,-1)+"])":n+r}function La(e){if(!e)return"function(){}";if(Array.isArray(e))return"["+e.map(function(e){return La(e)}).join(",")+"]";var t=Sa.test(e.value),n=Aa.test(e.value),r=Sa.test(e.value.replace(Oa,""));if(e.modifiers){var i="",o="",a=[];for(var s in e.modifiers)if(ja[s])o+=ja[s],Ta[s]&&a.push(s);else if("exact"===s){var c=e.modifiers;o+=Na(["ctrl","shift","alt","meta"].filter(function(e){return!c[e]}).map(function(e){return"$event."+e+"Key"}).join("||"))}else a.push(s);return a.length&&(i+=function(e){return"if(!$event.type.indexOf('key')&&"+e.map(Ma).join("&&")+")return null;"}(a)),o&&(i+=o),"function($event){"+i+(t?"return "+e.value+"($event)":n?"return ("+e.value+")($event)":r?"return "+e.value:e.value)+"}"}return t||n?e.value:"function($event){"+(r?"return "+e.value:e.value)+"}"}function Ma(e){var t=parseInt(e,10);if(t)return"$event.keyCode!=="+t;var n=Ta[e],r=Ea[e];return"_k($event.keyCode,"+JSON.stringify(e)+","+JSON.stringify(n)+",$event.key,"+JSON.stringify(r)+")"}var Ia={on:function(e,t){e.wrapListeners=function(e){return"_g("+e+","+t.value+")"}},bind:function(e,t){e.wrapData=function(n){return"_b("+n+",'"+e.tag+"',"+t.value+","+(t.modifiers&&t.modifiers.prop?"true":"false")+(t.modifiers&&t.modifiers.sync?",true":"")+")"}},cloak:S},Fa=function(e){this.options=e,this.warn=e.warn||Sr,this.transforms=Tr(e.modules,"transformCode"),this.dataGenFns=Tr(e.modules,"genData"),this.directives=A(A({},Ia),e.directives);var t=e.isReservedTag||T;this.maybeComponent=function(e){return!!e.component||!t(e.tag)},this.onceId=0,this.staticRenderFns=[],this.pre=!1};function Pa(e,t){var n=new Fa(t);return{render:"with(this){return "+(e?Ra(e,n):'_c("div")')+"}",staticRenderFns:n.staticRenderFns}}function Ra(e,t){if(e.parent&&(e.pre=e.pre||e.parent.pre),e.staticRoot&&!e.staticProcessed)return Ha(e,t);if(e.once&&!e.onceProcessed)return Ba(e,t);if(e.for&&!e.forProcessed)return za(e,t);if(e.if&&!e.ifProcessed)return Ua(e,t);if("template"!==e.tag||e.slotTarget||t.pre){if("slot"===e.tag)return function(e,t){var n=e.slotName||'"default"',r=qa(e,t),i="_t("+n+(r?","+r:""),o=e.attrs||e.dynamicAttrs?Ga((e.attrs||[]).concat(e.dynamicAttrs||[]).map(function(e){return{name:b(e.name),value:e.value,dynamic:e.dynamic}})):null,a=e.attrsMap["v-bind"];!o&&!a||r||(i+=",null");o&&(i+=","+o);a&&(i+=(o?"":",null")+","+a);return i+")"}(e,t);var n;if(e.component)n=function(e,t,n){var r=t.inlineTemplate?null:qa(t,n,!0);return"_c("+e+","+Va(t,n)+(r?","+r:"")+")"}(e.component,e,t);else{var r;(!e.plain||e.pre&&t.maybeComponent(e))&&(r=Va(e,t));var i=e.inlineTemplate?null:qa(e,t,!0);n="_c('"+e.tag+"'"+(r?","+r:"")+(i?","+i:"")+")"}for(var o=0;o>>0}(a):"")+")"}(e,e.scopedSlots,t)+","),e.model&&(n+="model:{value:"+e.model.value+",callback:"+e.model.callback+",expression:"+e.model.expression+"},"),e.inlineTemplate){var o=function(e,t){var n=e.children[0];if(n&&1===n.type){var r=Pa(n,t.options);return"inlineTemplate:{render:function(){"+r.render+"},staticRenderFns:["+r.staticRenderFns.map(function(e){return"function(){"+e+"}"}).join(",")+"]}"}}(e,t);o&&(n+=o+",")}return n=n.replace(/,$/,"")+"}",e.dynamicAttrs&&(n="_b("+n+',"'+e.tag+'",'+Ga(e.dynamicAttrs)+")"),e.wrapData&&(n=e.wrapData(n)),e.wrapListeners&&(n=e.wrapListeners(n)),n}function Ka(e){return 1===e.type&&("slot"===e.tag||e.children.some(Ka))}function Ja(e,t){var n=e.attrsMap["slot-scope"];if(e.if&&!e.ifProcessed&&!n)return Ua(e,t,Ja,"null");if(e.for&&!e.forProcessed)return za(e,t,Ja);var r=e.slotScope===ca?"":String(e.slotScope),i="function("+r+"){return "+("template"===e.tag?e.if&&n?"("+e.if+")?"+(qa(e,t)||"undefined")+":undefined":qa(e,t)||"undefined":Ra(e,t))+"}",o=r?"":",proxy:true";return"{key:"+(e.slotTarget||'"default"')+",fn:"+i+o+"}"}function qa(e,t,n,r,i){var o=e.children;if(o.length){var a=o[0];if(1===o.length&&a.for&&"template"!==a.tag&&"slot"!==a.tag){var s=n?t.maybeComponent(a)?",1":",0":"";return""+(r||Ra)(a,t)+s}var c=n?function(e,t){for(var n=0,r=0;r':'
',ts.innerHTML.indexOf(" ")>0}var os=!!z&&is(!1),as=!!z&&is(!0),ss=g(function(e){var t=Yn(e);return t&&t.innerHTML}),cs=wn.prototype.$mount;return wn.prototype.$mount=function(e,t){if((e=e&&Yn(e))===document.body||e===document.documentElement)return this;var n=this.$options;if(!n.render){var r=n.template;if(r)if("string"==typeof r)"#"===r.charAt(0)&&(r=ss(r));else{if(!r.nodeType)return this;r=r.innerHTML}else e&&(r=function(e){if(e.outerHTML)return e.outerHTML;var t=document.createElement("div");return t.appendChild(e.cloneNode(!0)),t.innerHTML}(e));if(r){var i=rs(r,{outputSourceRange:!1,shouldDecodeNewlines:os,shouldDecodeNewlinesForHref:as,delimiters:n.delimiters,comments:n.comments},this),o=i.render,a=i.staticRenderFns;n.render=o,n.staticRenderFns=a}}return cs.call(this,e,t)},wn.compile=rs,wn}); \ No newline at end of file diff --git a/ln.ethercat.service/www/static/pages/controller.html b/ln.ethercat.service/www/static/pages/controller.html new file mode 100644 index 0000000..add9687 --- /dev/null +++ b/ln.ethercat.service/www/static/pages/controller.html @@ -0,0 +1,37 @@ +
+

Controller

+

+ {{ controller }} +

+
+ Drive Controller {{ idx }} +
+
+

Drive Controller

+ + + + + + + + + + +

+ {{ drive_controller }} +

+ +
+
+ + + + +
+
+ +
+
\ No newline at end of file diff --git a/ln.ethercat.service/www/static/pages/pdo.html b/ln.ethercat.service/www/static/pages/pdo.html new file mode 100644 index 0000000..3933e26 --- /dev/null +++ b/ln.ethercat.service/www/static/pages/pdo.html @@ -0,0 +1,4 @@ +
+

Prozessdaten

+ +
\ No newline at end of file diff --git a/ln.ethercat.service/www/static/pages/slave.html b/ln.ethercat.service/www/static/pages/slave.html new file mode 100644 index 0000000..3c06145 --- /dev/null +++ b/ln.ethercat.service/www/static/pages/slave.html @@ -0,0 +1,12 @@ +
+
+
+

Slave {{ slave_id }}

+ +
+ +
+
\ No newline at end of file diff --git a/ln.ethercat/ECDataTypeConverter.cs b/ln.ethercat/ECDataTypeConverter.cs index 5e9af7f..15de7d3 100644 --- a/ln.ethercat/ECDataTypeConverter.cs +++ b/ln.ethercat/ECDataTypeConverter.cs @@ -1,13 +1,174 @@ using System; +using System.Collections.Generic; using System.Text; +using ln.logging; using ln.type; namespace ln.ethercat { + public delegate bool ToEthercatDelegate(object value, out byte[] ethercatBytes); + public delegate bool FromEthercatDelegate(byte[] ethercatBytes, out object value); + + public interface IECValueConverter { + + Type NativeType { get; } + bool ToEthercat(object value, out byte[] ethercatBytes); + bool FromEthercat(byte[] ethercatBytes, out object value); + } + + public class ECValueConverterAdapter : IECValueConverter + { + + public ToEthercatDelegate ToEthercatDelegate { get; } + public FromEthercatDelegate FromEthercatDelegate { get; } + + public Type NativeType { get; set; } + + public ECValueConverterAdapter(Type nativeType, ToEthercatDelegate toEthercatDelegate,FromEthercatDelegate fromEthercatDelegate) + { + NativeType = nativeType; + ToEthercatDelegate = toEthercatDelegate; + FromEthercatDelegate = fromEthercatDelegate; + } + + public bool ToEthercat(object value, out byte[] ethercatBytes) => ToEthercatDelegate(value, out ethercatBytes); + public bool FromEthercat(byte[] ethercatBytes, out object value) => FromEthercatDelegate(ethercatBytes, out value); + } + public static class ECDataTypeConverter { - public static object ConvertFromEthercat(ECDataTypes dataType, byte[] rawData) + static Dictionary valueConverters = new Dictionary(); + public static void AddConverter(ECDataTypes dataType, IECValueConverter valueConverter) => valueConverters.Add(dataType, valueConverter); + + public static bool FromEthercat(ECDataTypes dataType, byte[] ethercatBytes, out object value) { + try + { + if (valueConverters.TryGetValue(dataType, out IECValueConverter converter)) + return converter.FromEthercat(ethercatBytes, out value); + } catch (Exception e) + { + Logging.Log(LogLevel.ERROR, "Exception caught while converting {0} = {1} from ethercat to native value", dataType, ethercatBytes.ToHexString()); + Logging.Log(e); + } + value = null; + return false; + } + + public static bool ToEthercat(ECDataTypes dataType, object value, out byte[] ethercatBytes) + { + try + { + if (valueConverters.TryGetValue(dataType, out IECValueConverter converter)) + return converter.ToEthercat(Cast.To(value, converter.NativeType), out ethercatBytes); + } catch (Exception e) + { + Logging.Log(LogLevel.ERROR, "Exception caught while converting {0} = {1} to ethercat from native value", dataType, value); + Logging.Log(e); + } + + ethercatBytes = null; + return false; + } + + + public static object FromEthercat(ECDataTypes dataType, byte[] ethercatBytes) + { + try{ + if (FromEthercat(dataType, ethercatBytes, out object value)) + return value; + } catch (Exception e) + { + Logging.Log(LogLevel.ERROR, "Exception caught while converting {0} = {1} from ethercat to native value", dataType, ethercatBytes.ToHexString()); + Logging.Log(e); + } + throw new NotSupportedException(String.Format("converting {0} is currently not supported", dataType )); + } + public static byte[] ToEthercat(ECDataTypes dataType, object value) + { + try + { + if (ToEthercat(dataType, value, out byte[] ethercatBytes)) + return ethercatBytes; + + } catch (Exception e) + { + Logging.Log(LogLevel.ERROR, "Exception caught while converting {0} = {1} to ethercat from native value", dataType, value); + Logging.Log(e); + } + throw new NotSupportedException(String.Format("converting {0} to {1} is currently not supported", value.GetType().Name, dataType)); + } + + static ECDataTypeConverter(){ + AddConverter(ECDataTypes.SINT, new ECValueConverterAdapter( + typeof(sbyte), + (object value, out byte[] ethercatBytes) => { ethercatBytes = new byte[]{ (byte)(sbyte)value }; return true; }, + (byte[] ethercatBytes, out object value) => { value = (sbyte)ethercatBytes[0]; return true; } + )); + AddConverter(ECDataTypes.INT, new ECValueConverterAdapter( + typeof(short), + (object value, out byte[] ethercatBytes) => { ethercatBytes = BitConverter.GetBytes((short)value); return true; }, + (byte[] ethercatBytes, out object value) => { value = BitConverter.ToInt16(ethercatBytes); return true; } + )); + AddConverter(ECDataTypes.DINT, new ECValueConverterAdapter( + typeof(int), + (object value, out byte[] ethercatBytes) => { ethercatBytes = BitConverter.GetBytes((int)value); return true; }, + (byte[] ethercatBytes, out object value) => { value = BitConverter.ToInt32(ethercatBytes); return true; } + )); + AddConverter(ECDataTypes.LINT, new ECValueConverterAdapter( + typeof(long), + (object value, out byte[] ethercatBytes) => { ethercatBytes = BitConverter.GetBytes((long)value); return true; }, + (byte[] ethercatBytes, out object value) => { value = BitConverter.ToInt64(ethercatBytes); return true; } + )); + AddConverter(ECDataTypes.USINT, new ECValueConverterAdapter( + typeof(byte), + (object value, out byte[] ethercatBytes) => { ethercatBytes = new byte[]{ (byte)value }; return true; }, + (byte[] ethercatBytes, out object value) => { value = ethercatBytes[0]; return true; } + )); + AddConverter(ECDataTypes.UINT, new ECValueConverterAdapter( + typeof(ushort), + (object value, out byte[] ethercatBytes) => { ethercatBytes = BitConverter.GetBytes((ushort)value); return true; }, + (byte[] ethercatBytes, out object value) => { value = BitConverter.ToUInt16(ethercatBytes); return true; } + )); + AddConverter(ECDataTypes.UDINT, new ECValueConverterAdapter( + typeof(UInt32), + (object value, out byte[] ethercatBytes) => { ethercatBytes = BitConverter.GetBytes((UInt32)value); return true; }, + (byte[] ethercatBytes, out object value) => { value = BitConverter.ToUInt32(ethercatBytes); return true; } + )); + AddConverter(ECDataTypes.ULINT, new ECValueConverterAdapter( + typeof(UInt64), + (object value, out byte[] ethercatBytes) => { ethercatBytes = BitConverter.GetBytes((UInt64)value); return true; }, + (byte[] ethercatBytes, out object value) => { value = BitConverter.ToUInt64(ethercatBytes); return true; } + )); + + AddConverter(ECDataTypes.REAL, new ECValueConverterAdapter( + typeof(float), + (object value, out byte[] ethercatBytes) => { ethercatBytes = BitConverter.GetBytes((float)value); return true; }, + (byte[] ethercatBytes, out object value) => { value = BitConverter.ToSingle(ethercatBytes); return true; } + )); + AddConverter(ECDataTypes.LREAL, new ECValueConverterAdapter( + typeof(double), + (object value, out byte[] ethercatBytes) => { ethercatBytes = BitConverter.GetBytes((double)value); return true; }, + (byte[] ethercatBytes, out object value) => { value = BitConverter.ToDouble(ethercatBytes); return true; } + )); + + AddConverter(ECDataTypes.STRING, new ECValueConverterAdapter( + typeof(string), + (object value, out byte[] ethercatBytes) => { ethercatBytes = Encoding.ASCII.GetBytes(value.ToString()); return true; }, + (byte[] ethercatBytes, out object value) => { value = Encoding.ASCII.GetString(ethercatBytes); return true; } + )); + + AddConverter(ECDataTypes.NONE, new ECValueConverterAdapter( + typeof(byte[]), + (object value, out byte[] ethercatBytes) => { ethercatBytes = Extensions.BytesFromHexString(value as string); return true; }, + (byte[] ethercatBytes, out object value) => { value = ethercatBytes.ToHexString(); return true; } + )); + + + + } + +/* if (rawData == null) return null; @@ -18,25 +179,57 @@ namespace ln.ethercat case ECDataTypes.NONE: return null; case ECDataTypes.REAL: - return rawData.GetSingle(ref offset, Endianess.BIG); + return rawData.GetSingle(ref offset, Endianess.LITTLE); case ECDataTypes.LREAL: - return rawData.GetDouble(ref offset, Endianess.BIG); + return rawData.GetDouble(ref offset, Endianess.LITTLE); case ECDataTypes.STRING: return Encoding.ASCII.GetString(rawData); case ECDataTypes.INT: - return rawData.GetShort(ref offset, Endianess.BIG); + return rawData.GetShort(ref offset, Endianess.LITTLE); case ECDataTypes.DINT: - return rawData.GetInt(ref offset, Endianess.BIG); + return rawData.GetInt(ref offset, Endianess.LITTLE); case ECDataTypes.UINT: - return rawData.GetUShort(ref offset, Endianess.BIG); + return rawData.GetUShort(ref offset, Endianess.LITTLE); case ECDataTypes.UDINT: - return rawData.GetUInt(ref offset, Endianess.BIG); + return rawData.GetUInt(ref offset, Endianess.LITTLE); case ECDataTypes.USINT: return rawData[offset]; default: return rawData; } } + + { + if (value is byte[] bytes) + return bytes; + + switch (dataType) + { + case ECDataTypes.NONE: + return new byte[0]; + case ECDataTypes.REAL: + return BitConverter.GetBytes((float)(double)value); + case ECDataTypes.LREAL: + return BitConverter.GetBytes((double)value); + case ECDataTypes.STRING: + return Encoding.ASCII.GetBytes(value.ToString()); + case ECDataTypes.INT: + return BitConverter.GetBytes((short)value); + case ECDataTypes.DINT: + return BitConverter.GetBytes((int)value); + case ECDataTypes.UINT: + return BitConverter.GetBytes((ushort)value); + case ECDataTypes.UDINT: + return BitConverter.GetBytes((uint)value); + case ECDataTypes.SINT: + return BitConverter.GetBytes((byte)value); + case ECDataTypes.USINT: + return BitConverter.GetBytes((byte)value); + default: + throw new NotImplementedException(); + } + } + */ } } \ No newline at end of file diff --git a/ln.ethercat/ECDataTypes.cs b/ln.ethercat/ECDataTypes.cs index 5b48983..d8baf48 100644 --- a/ln.ethercat/ECDataTypes.cs +++ b/ln.ethercat/ECDataTypes.cs @@ -31,4 +31,7 @@ namespace ln.ethercat ULINT = 0x0261, BYTE = 0x0262 } + + + } diff --git a/ln.ethercat/ECMBind.cs b/ln.ethercat/ECMBind.cs index 7d717c9..e0850a3 100644 --- a/ln.ethercat/ECMBind.cs +++ b/ln.ethercat/ECMBind.cs @@ -80,6 +80,8 @@ namespace ln.ethercat [DllImport("lib/libecmbind.so")] public static extern Int32 ecmbind_processdata(); + [DllImport("lib/libecmbind.so")] + public static extern Int32 ecmbind_processdata2(byte[] iomap, int size); [DllImport("lib/libecmbind.so")] public static extern Int32 ecmbind_recover(); diff --git a/ln.ethercat/ECMaster.cs b/ln.ethercat/ECMaster.cs index 4cdccbf..ce352a1 100644 --- a/ln.ethercat/ECMaster.cs +++ b/ln.ethercat/ECMaster.cs @@ -1,7 +1,9 @@ using System; +using System.Buffers; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection.Metadata; using System.Security.Cryptography.X509Certificates; @@ -26,7 +28,8 @@ namespace ln.ethercat public int TIMEOUT_PREOP = 3000; public int TIMEOUT_SAFEOP = 10000; public int TIMEOUT_BACKTO_SAFEOP = 200; - public int INTERVALL_PROCESSDATA = 20; + public int INTERVALL_PROCESSDATA = 1; + public int INTERVALL_WATCHDOG = 250; public event ECStateChange OnStateChange; @@ -57,8 +60,10 @@ namespace ln.ethercat } - Dictionary pdoMap = new Dictionary(); - SDOCache sdoCache; + List pdoMap = new List(); + public PDO[] GetPDOMap() => pdoMap.ToArray(); + + Dictionary slaves = new Dictionary(); public ECMaster(string interfaceName) { @@ -68,8 +73,6 @@ namespace ln.ethercat Logging.Log(LogLevel.INFO, "ecmbind_initialize({0}) = {1}", interfaceName, result); if (result<=0) throw new Exception("ecmbind_initialize failed"); - - sdoCache = new SDOCache(this); } bool stopProcessing; @@ -79,12 +82,15 @@ namespace ln.ethercat } Thread threadProcessData; + Thread threadWatchdog; public bool Start() { - if (threadProcessData?.IsAlive ?? false) + if ((threadProcessData?.IsAlive ?? false) || (threadWatchdog?.IsAlive ?? false)) throw new Exception("already started"); + stopProcessing = false; + EthercatState = ECSlaveState.BOOT; ExpectedWorkCounter = 0; @@ -109,31 +115,23 @@ namespace ln.ethercat threadProcessData = new Thread(MasterThread); threadProcessData.Start(); + Thread.Sleep(20); + if (!RequestState(ECSlaveState.SAFE_OP, out ECSlaveState slaveState, TIMEOUT_SAFEOP)) { Stop(); return false; } - if (!ReadSDOIndeces()) - { - Logging.Log(LogLevel.WARNING, "ECMaster: could not read SDO indeces"); - } + threadWatchdog = new Thread(Watchdog); + threadWatchdog.Start(); -/* - if (!RequestState(ECSlaveState.OPERATIONAL, out slaveState, TIMEOUT_SAFEOP)) - { - if (slaveState < ECSlaveState.SAFE_OP) - Stop(); - return false; - } -*/ } return true; } public void Stop() - { + { if (threadProcessData?.IsAlive ?? false) { stopProcessing = true; @@ -141,11 +139,38 @@ namespace ln.ethercat threadProcessData.Join(); threadProcessData = null; + + if (!Thread.CurrentThread.Equals(threadWatchdog)) + { + threadWatchdog?.Join(); + threadWatchdog = null; + } } } object lockIOMap = new object(); + byte[] iomap = new byte[8192]; + + void Watchdog() + { + // if (!ReadSDOIndeces()) + // { + // Logging.Log(LogLevel.WARNING, "ECMaster: could not read SDO indeces"); + // } + + if (!RequestState(ECSlaveState.OPERATIONAL, out ECSlaveState slaveState, TIMEOUT_SAFEOP)) + { + if (slaveState < ECSlaveState.SAFE_OP) + Stop(); + } + + while (!stopProcessing) + { + UpdateEthercatState(); + Thread.Sleep(250); + } + } void MasterThread() { @@ -157,7 +182,17 @@ namespace ln.ethercat lock (lockIOMap) { - wkc = ECMBind.ecmbind_processdata(); + wkc = ECMBind.ecmbind_processdata2(iomap, iomap.Length); + if (wkc != ExpectedWorkCounter) + { + int success = ECMBind.ecmbind_recover(); + if (success > 0) + ExpectedWorkCounter = success; + else + { + RequestSlaveState(0, ECSlaveState.SAFE_OP); + } + } } Thread.Sleep(INTERVALL_PROCESSDATA); @@ -173,24 +208,33 @@ namespace ln.ethercat } } - public bool ReadSDO(SDOAddr addr,out byte[] data) + public bool ReadSDO(UInt16 slave,UInt16 index, byte subIndex, out byte[] rawValue) { - byte[] buffer = new byte[128]; - int size; + rawValue = + new byte[128]; + int dataSize; + lock (lockIOMap) - size = ECMBind.ecmbind_sdo_read(addr.Slave,addr.Index,addr.SubIndex, buffer, buffer.Length); - - if (size < 0) { - data = new byte[0]; - return false; + dataSize = + ECMBind.ecmbind_sdo_read(slave, index, subIndex, rawValue, rawValue.Length ); + if (dataSize <= 0) + { + Logging.Log(LogLevel.ERROR, "ECMBind.ecmbind_sdo_read({0},{1},{2},..,{3}) == {4}", slave, index, subIndex, rawValue.Length, rawValue.ToHexString()); + throw new IOException(); + } } - data = buffer.Slice(0, size); + rawValue = rawValue.Slice(0, dataSize); return true; } - public bool WriteSDO(SDOAddr addr,byte[] data) + + public bool WriteSDO(UInt16 slave,UInt16 index, byte subIndex, byte[] rawValue) { - throw new NotImplementedException(); + int result; + lock (lockIOMap) + result = ECMBind.ecmbind_sdo_write(slave, index, subIndex, rawValue, rawValue.Length); + Logging.Log(LogLevel.DEBUG, "ECMaster: WriteSDO({0},{1},{2},{3}) => {4}", slave, index, subIndex, rawValue.ToHexString(), result); + return result > 0; } public ECSlaveState ReadSlaveState(int slave) => ECMBind.ecmbind_get_slave_state(slave); @@ -223,108 +267,69 @@ namespace ln.ethercat return (reachedState >= requestedState); } - public int RequestSlaveState(int slave, ECSlaveState slaveState) => ECMBind.ecmbind_write_slave_state(slave, slaveState); - - public bool GetSDO(SDOAddr address,out SDO sdo) + public int RequestSlaveState(int slave, ECSlaveState slaveState) => ECMBind.ecmbind_write_slave_state(slave, slaveState); + + + public bool GetSlave(UInt16 slave_id, out ECSlave slave) { - sdo = sdoCache.GetOrCreateDescriptor(address); - return true; - } - - public IEnumerable GetSDOs(int slave) => sdoCache.GetSlaveCache(slave).Values; - - -/* public bool ReadSDOSubDescriptors(SDODescriptor descriptor) - { - bool success = true; - - for (int sub = 1; sub < descriptor.SubDescriptors.Length; sub++) + if (!slaves.TryGetValue(slave_id, out slave)) { - if (IOLocked(()=>ECMBind.ecmbind_read_objectdescription_entry((ushort)descriptor.Slave, (ushort)descriptor.Index, (ushort)sub, (int slave, int index, ECDataTypes dataType, ECObjectCodes objectCode, int current_sub, String name) => { - //Logging.Log(LogLevel.DEBUG,"SDO Entry --> {0} {1} {2} {3} {4} {5}",slave,index, dataType, objectCode, current_sub, name); - - descriptor.SubDescriptors[current_sub].DataType = dataType; - descriptor.SubDescriptors[current_sub].Name = name; - })) <= 0) - { - //Logging.Log(LogLevel.WARNING,"ECMaster: ReadSDODescriptors({0}: failed to read descriptor for 0x{1:x8}.{2}", descriptor.Slave, descriptor.Index, sub); - success = false; - } - } - return success; - } -*/ - public bool ReadSDOIndeces() - { - for (int slave=1; slave <= CountSlaves; slave++) - { - if (!ReadSDOIndex(slave)) + if (slave_id > CountSlaves) return false; + + slave = new ECSlave(this, slave_id); + slaves.Add(slave_id, slave); } return true; } - public bool ReadSDOIndex(int slave) + + + public bool GetSDOValue(SDOAddr address, out SDOValue sdoValue) => GetSDOValue(address.Slave, address.Index, address.SubIndex, out sdoValue); + public bool GetSDOValue(UInt16 slave_id, UInt16 index,byte subIndex, out SDOValue sdoValue) { - IOLocked(()=>ECMBind.ecmbind_enumerate_servicedescriptors(slave, (int slave, int index) => { - sdoCache.GetOrCreateDescriptor(new SDOAddr(slave, index)); - })); - Logging.Log(LogLevel.DEBUG, "Indexed SDOs of slave {0}", slave); - return true; + sdoValue = null; + return GetSlave(slave_id, out ECSlave slave) && slave.GetDescriptor(index, out SDODescriptor descriptor) && descriptor.GetValue(subIndex, out sdoValue); } -/* - public bool ReadSDODescriptor(SDODescriptor descriptor) + public bool GetIOmapData(int offset,int length,out byte[] rawData) { - IOLocked(()=>ECMBind.ecmbind_enumerate_servicedescriptors(slave, (int slave, int index) => { - descriptors.Add(new SDODescriptor(){ - Slave = slave, - Index = index - }); - })); - - } - - public SDODescriptor[] ReadSDODescriptors(int slave) - { - List descriptors = new List(); - - IOLocked(()=>ECMBind.ecmbind_enumerate_servicedescriptors(slave, (int slave, int index) => { - descriptors.Add(new SDODescriptor(){ - Slave = slave, - Index = index - }); - })); - - foreach (SDODescriptor descriptor in descriptors) + lock (lockIOMap) + if ((offset >= 0) && (length > 0) && (offset+length < iomap.Length)) { - if (IOLocked(()=>ECMBind.ecmbind_read_objectdescription(descriptor.Slave, descriptor.Index, (int slave, int index, ECDataTypes dataType, ECObjectCodes objectCode, int maxsub, String name) => { - descriptor.MaxSubindex = maxsub; - descriptor.ObjectCode = objectCode; - descriptor.CreateAndInitializeSubDescriptors(maxsub+1); - descriptor.SubDescriptors[0].DataType = dataType; - descriptor.SubDescriptors[0].Name = name ?? ""; - - //Logging.Log(LogLevel.DEBUG, "SDODescriptor: {0}", descriptor); - //Logging.Log(LogLevel.DEBUG,"SDO --> {0} {1} {2} {3} {4} {5}",slave,index, dataType,objectCode, maxsub, name); - - switch (descriptor.ObjectCode) - { - case ECObjectCodes.VAR: - break; - default: - ReadSDOSubDescriptors(descriptor); - break; - } - })) <= 0) - { - Logging.Log(LogLevel.WARNING,"ECMaster: ReadSDODescriptors({0}: failed to read descriptor for 0x{1:x8}", slave, descriptor.Index); - } - + rawData = iomap.Slice(offset, length); + return true; } - - return descriptors.ToArray(); + Logging.Log(LogLevel.ERROR, "GetIOmapData({0},{1}) failed", offset, length); + rawData = null; + return false; + } + public bool SetIOmapData(int offset,int length,byte[] rawData) + { + lock (lockIOMap) + if ((offset >= 0) && (length > 0) && (offset+length < iomap.Length)) + { + Logging.Log(LogLevel.DEBUG, "ECMaster: SetIOmapData({0},{1},{2})", offset, length, rawData.ToHexString()); + Buffer.BlockCopy(rawData, 0, iomap, offset, length); + return true; + } + Logging.Log(LogLevel.ERROR, "SetIOmapData({0},{1}) failed", offset, length); + rawData = null; + return false; + } + + public bool GetPDOItem(SDOValue sdoValue, out PDO pdo) + { + foreach (PDO _pdo in pdoMap) + { + if (_pdo.SDOValue.Equals(sdoValue)) + { + pdo = _pdo; + return true; + } + } + pdo = null; + return false; } -*/ public void UpdatePDOMap() { @@ -343,33 +348,10 @@ namespace ln.ethercat lock (this) { IOMapPtr = ECMBind.ecmbind_get_iomap(); - pdoMap.Clear(); - - foreach (PDO pdo in pdoList) - pdoMap.Add(pdo.Address, pdo); + pdoMap = pdoList; } - } - public PDO[] GetPDOMap() => pdoMap.Values.ToArray(); - public bool TryGetPDO(SDOAddr address, out PDO pdo) => pdoMap.TryGetValue(address, out pdo); - - -/* - public unsafe bool IOMapExtract(int offset,int size, byte[] buffer) - { - if (buffer.Length < size) - throw new ArgumentOutOfRangeException(nameof(buffer)); - - byte* iomap = (byte*)(IOMapPtr.ToPointer()) + offset; - fixed (byte* b = buffer) - { - Buffer.MemoryCopy(iomap, b, size, size); - } - - return true; - } -*/ T IOLocked(Func f) { lock (lockIOMap) diff --git a/ln.ethercat/ECSlave.cs b/ln.ethercat/ECSlave.cs new file mode 100644 index 0000000..9341952 --- /dev/null +++ b/ln.ethercat/ECSlave.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ln.collections; +using ln.ethercat.controller; +using ln.logging; +using ln.type; + +namespace ln.ethercat +{ + + public class ECSlave + { + internal ECMaster ECMaster; + public UInt16 Id { get; } + + MappingBTree sdoCache = new MappingBTree((sdo)=>sdo.Index); + + public ECSlave(ECMaster ecMaster,UInt16 slave_id) + { + ECMaster = ecMaster; + Id = slave_id; + + ECMBind.ecmbind_enumerate_servicedescriptors(Id, (int slave, int index)=>{ + sdoCache.Add(new SDODescriptor(this, (UInt16)index)); + }); + + foreach (SDODescriptor sdo in sdoCache) + sdo.Update(); + } + + public IEnumerable GetSDODescriptors() => sdoCache.Values; + + public bool GetDescriptor(UInt16 index, out SDODescriptor descriptor) => sdoCache.TryGet(index, out descriptor); + + + } + + public class SDODescriptor + { + internal ECSlave Slave; + public UInt16 SlaveId => Slave.Id; + public UInt16 Index { get; } + + public string Name { get; private set; } + public ECDataTypes DataType { get; set; } + public ECObjectCodes ObjectCode { get; set; } + public int MaxSubIndex { get; set; } + + MappingBTree valueCache; + + public SDODescriptor(ECSlave slave, UInt16 index) + { + Slave = slave; + Index = index; + } + + public void Update() + { + if (ECMBind.ecmbind_read_objectdescription(Slave.Id, Index, (int slave, int index, ECDataTypes dataType, ECObjectCodes objectCode, int maxsub, String name)=>{ + Name = name; + DataType = dataType; + ObjectCode = objectCode; + MaxSubIndex = maxsub; + }) <= 0) + { + Logging.Log(LogLevel.WARNING, "cannot create SDO instance for {0}:{1:X4} not found.", Slave.Id, Index); + } + } + + void EnsureValueCache() + { + if (valueCache == null) + { + valueCache = new MappingBTree((v)=>v.SubIndex); + for (byte subIndex = 0; subIndex <= MaxSubIndex; subIndex++) + valueCache.Add(new SDOValue(this, subIndex)); + } + } + + public IEnumerable GetValues() + { + EnsureValueCache(); + return valueCache.Values; + } + + public SDOValue GetValue(byte subIndex) + { + EnsureValueCache(); + return valueCache[subIndex]; + } + + public bool GetValue(byte subIndex, out SDOValue sdoValue) + { + EnsureValueCache(); + return valueCache.TryGet(subIndex, out sdoValue); + } + + + public override string ToString() => String.Format("[SDODescriptor Slave={0} Index=0x{1:X4} ObjectCode={2} DataType={3}]", Slave.Id, Index, ObjectCode, DataType); + } + + public class SDOValue + { + public SDODescriptor Descriptor; + public byte SubIndex { get; } + + public string Name { get; private set; } + public ECDataTypes DataType { get; private set; } + + public SDOValue(SDODescriptor descriptor,byte subIndex) + { + Descriptor = descriptor; + SubIndex = subIndex; + + if (SubIndex == 0) + { + DataType = descriptor.DataType; + Name = descriptor.Name; + } else { + if (ECMBind.ecmbind_read_objectdescription_entry(Descriptor.SlaveId, descriptor.Index, SubIndex, (int slave, int index, ECDataTypes dataType, ECObjectCodes objectCode, int si, String name)=>{ + Name = name; + DataType = dataType; + }) <= 0) + { + Logging.Log(LogLevel.WARNING, "SDOValue: could not read object description entry {0}:{1:X4}.{2}", descriptor.SlaveId, descriptor.Index, SubIndex); + } + } + } + + + public T GetValue() => (T)GetValue(); + public object GetValue(){ + if (GetValue(out object value)) + return value; + throw new IOException(); + } + public bool GetValue(out object value) + { + if (GetRawValue(out byte[] data)) + return ECDataTypeConverter.FromEthercat(DataType, data, out value); + value = null; + return false; + } + + public bool GetRawValue(out byte[] rawValue) + { + if (Descriptor.Slave.ECMaster.GetPDOItem(this, out PDO pdo)) + { + return Descriptor.Slave.ECMaster.GetIOmapData(pdo.AddressOffset, pdo.ByteLength, out rawValue); + } else { + return Descriptor.Slave.ECMaster.ReadSDO(Descriptor.SlaveId, Descriptor.Index, SubIndex, out rawValue); + } + } + + public void SetValue(T value) + { + if (ECDataTypeConverter.ToEthercat(DataType, value, out byte[] rawValue)) + { + SetRawValue(rawValue); + } else { + Logging.Log(LogLevel.ERROR, "SDOValue: SetValue<>({0}): could not convert to ethercat", value); + } + } + + public void SetRawValue(byte[] rawValue) + { + if (Descriptor.Slave.ECMaster.GetPDOItem(this, out PDO pdo)) + { + Descriptor.Slave.ECMaster.SetIOmapData(pdo.AddressOffset, pdo.ByteLength, rawValue); + } else { + Descriptor.Slave.ECMaster.WriteSDO(Descriptor.SlaveId, Descriptor.Index, SubIndex, rawValue); + } + } + + } + +} \ No newline at end of file diff --git a/ln.ethercat/ECSlaveState.cs b/ln.ethercat/ECSlaveState.cs index 9b3060f..9dc2095 100644 --- a/ln.ethercat/ECSlaveState.cs +++ b/ln.ethercat/ECSlaveState.cs @@ -13,6 +13,6 @@ namespace ln.ethercat SAFE_OP = 4, OPERATIONAL = 8, ERROR = 16, - ACK = 16 + //ACK = 16 } } \ No newline at end of file diff --git a/ln.ethercat/PDO.cs b/ln.ethercat/PDO.cs index b90935c..5a62eed 100644 --- a/ln.ethercat/PDO.cs +++ b/ln.ethercat/PDO.cs @@ -8,8 +8,7 @@ namespace ln.ethercat { readonly ECMaster ECMaster; - public SDO SDO { get; } - public SDOAddr Address => SDO.Address; + public SDOValue SDOValue{ get; } public int AddressOffset { get; set; } public int AddressBit { get; set; } @@ -22,8 +21,9 @@ namespace ln.ethercat public PDO(ECMaster ecMaster, UInt16 slave, UInt16 index, byte subIndex) { ECMaster = ecMaster; - ECMaster.GetSDO(new SDOAddr(slave, index, subIndex), out SDO sdo); - SDO = sdo; + if (!ECMaster.GetSDOValue(slave, index, subIndex, out SDOValue sdoValue)) + throw new Exception(String.Format("PDO: could not retrieve SDOValue {0}:{1:X4}.{2}", slave, index, subIndex)); + SDOValue = sdoValue; } diff --git a/ln.ethercat/SDO.cs b/ln.ethercat/SDO.cs index fb8f78c..16a07b7 100644 --- a/ln.ethercat/SDO.cs +++ b/ln.ethercat/SDO.cs @@ -7,86 +7,96 @@ using ln.type; namespace ln.ethercat { - public class SDO - { - public static SDO Create(ECMaster ecMaster, SDOAddr address) - { - SDO sdo = new SDO(ecMaster, address); - if (address.SubIndex == 0) - { - if (ECMBind.ecmbind_read_objectdescription(address.Slave, address.Index, (int slave, int index, ECDataTypes dataType, ECObjectCodes objectCode, int maxsub, String name)=>{ - sdo.DataType = dataType; - sdo.ObjectCode = objectCode; - sdo.MaxSubIndex = maxsub; - sdo.Name = name; - }) <= 0) - { - Logging.Log(LogLevel.WARNING, "cannot create SDO instance for {0}. not found.", address); - return null; - } - } else { - if (ECMBind.ecmbind_read_objectdescription_entry((ushort)address.Slave, (ushort)address.Index, (ushort)address.SubIndex, (int slave, int index, ECDataTypes dataType, ECObjectCodes objectCode, int maxsub, String name)=>{ - sdo.DataType = dataType; - sdo.MaxSubIndex = -1; - sdo.Name = name; - }) <= 0) - { - Logging.Log(LogLevel.WARNING, "cannot create SDO instance for {0}. not found.", address); - return null; - } - } - return sdo; - } +// public class SDO +// { - public SDOAddr Address { get; } - public string Name { get; private set; } +// public static SDO Create(ECMaster ecMaster, SDOAddr address) +// { +// SDO sdo = new SDO(ecMaster, address); +// if (address.SubIndex == 0) +// { +// if (ECMBind.ecmbind_read_objectdescription(address.Slave, address.Index, (int slave, int index, ECDataTypes dataType, ECObjectCodes objectCode, int maxsub, String name)=>{ +// sdo.DataType = dataType; +// sdo.ObjectCode = objectCode; +// sdo.MaxSubIndex = maxsub; +// sdo.Name = name; +// }) <= 0) +// { +// Logging.Log(LogLevel.WARNING, "cannot create SDO instance for {0}. not found.", address); +// return null; +// } +// } else { +// if (ECMBind.ecmbind_read_objectdescription_entry((ushort)address.Slave, (ushort)address.Index, (ushort)address.SubIndex, (int slave, int index, ECDataTypes dataType, ECObjectCodes objectCode, int maxsub, String name)=>{ +// sdo.DataType = dataType; +// sdo.MaxSubIndex = -1; +// sdo.Name = name; +// }) <= 0) +// { +// Logging.Log(LogLevel.WARNING, "cannot create SDO instance for {0}. not found.", address); +// return null; +// } +// } +// return sdo; +// } - public ECDataTypes DataType { get; set; } - public ECObjectCodes ObjectCode { get; set; } - public int MaxSubIndex { get; set; } +// public SDOAddr Address { get; } +// public string Name { get; private set; } - public bool IsPartOfPDO => ECMaster.TryGetPDO(Address, out PDO pdo); +// public ECDataTypes DataType { get; set; } +// public ECObjectCodes ObjectCode { get; set; } +// public int MaxSubIndex { get; set; } - byte[] rawData; - public byte[] RawData { - get { - if (ECMaster.TryGetPDO(Address, out PDO pdo)) - { - if ((rawData == null) || (rawData.Length != pdo.ByteLength)) - rawData = new byte[pdo.ByteLength]; +// public bool IsPartOfPDO => ECMaster.TryGetPDO(Address, out PDO pdo); - ECMBind.ecmbind_iomap_get(pdo.AddressOffset, rawData, pdo.ByteLength); - } else { - ECMaster.ReadSDO(Address, out rawData); - } - return rawData; - } - set => rawData = value; - } - public object Value { - get => ECDataTypeConverter.ConvertFromEthercat(DataType ,RawData); - set => throw new NotImplementedException(); - } +// byte[] rawData; +// public byte[] RawData { +// get { +// if (ECMaster.TryGetPDO(Address, out PDO pdo)) +// { +// if ((rawData == null) || (rawData.Length != pdo.ByteLength)) +// rawData = new byte[pdo.ByteLength]; - ECMaster ECMaster { get; } +// ECMBind.ecmbind_iomap_get(pdo.AddressOffset, rawData, pdo.ByteLength); +// } else { +// if (!ECMaster.ReadSDO(Address, out rawData)) +// if (!ECMaster.ReadSDO(Address, out rawData)) +// rawData = new byte[pdo.ByteLength]; +// } +// return rawData; +// } +// set { +// if (ECMaster.TryGetPDO(Address, out PDO pdo)) +// { +// // ECMBind.ecmbind_iomap_get(pdo.AddressOffset, rawData, pdo.ByteLength); +// } else { +// ECMaster.WriteSDO(Address, value); +// } +// } +// } +// public object Value { +// get => ECDataTypeConverter.FromEthercat(DataType ,RawData); // { object o = ECDataTypeConverter.ConvertFromEthercat(DataType ,RawData); Logging.Log("--> {0} {1}", Address, o?.ToString()); return o; } +// set => RawData = ECDataTypeConverter.ToEthercat(DataType, value); +// } - SDO(ECMaster ecMaster, SDOAddr address) - { - ECMaster = ecMaster; - Address = address; - } +// ECMaster ECMaster { get; } - public override string ToString() - { - return string.Format("[SDO Slave={0} Index={1:X4}.{7} MaxSubindex={2} ObjectCode={5} DataType={3} Name={4} RawData={6}]", Address.Slave, Address.Index, MaxSubIndex, DataType, Name, ObjectCode, RawData?.ToHexString(), Address.SubIndex); - } +// SDO(ECMaster ecMaster, SDOAddr address) +// { +// ECMaster = ecMaster; +// Address = address; +// } - public override bool Equals(object obj) => (obj is SDO other) && Address.Equals(other.Address); - public override int GetHashCode() => Address.GetHashCode(); +// public override string ToString() +// { +// return string.Format("[SDO Slave={0} Index={1:X4}.{7} MaxSubindex={2} ObjectCode={5} DataType={3} Name={4} RawData={6}]", Address.Slave, Address.Index, MaxSubIndex, DataType, Name, ObjectCode, RawData?.ToHexString(), Address.SubIndex); +// } - public T GetValue() => Cast.To(Value); +// public override bool Equals(object obj) => (obj is SDO other) && Address.Equals(other.Address); +// public override int GetHashCode() => Address.GetHashCode(); - } +// public T GetValue() => Cast.To(Value); + +// } } \ No newline at end of file diff --git a/ln.ethercat/SDOAddr.cs b/ln.ethercat/SDOAddr.cs index 9b79b06..280865f 100644 --- a/ln.ethercat/SDOAddr.cs +++ b/ln.ethercat/SDOAddr.cs @@ -1,25 +1,25 @@ +using System; + namespace ln.ethercat { public class SDOAddr { - public int Slave { get; private set; } - public int Index { get; private set; } - public int SubIndex { get; private set; } + public UInt16 Slave { get; private set; } + public UInt16 Index { get; private set; } + public byte SubIndex { get; private set; } - public long Linear { get; private set; } - public int Compact { get; private set; } + public long Linear => ((long)(ulong)Slave << 32) | ((long)Index << 16) | (long)SubIndex; + public int Compact => ((Slave << 24) | (Index << 8) | SubIndex); - public SDOAddr(int slave, int index) : this(slave, index, 0) { } - public SDOAddr(int slave, int index, int subindex) + public SDOAddr(){} + public SDOAddr(UInt16 slave, UInt16 index) : this(slave, index, 0) { } + public SDOAddr(UInt16 slave, UInt16 index, byte subindex) { Slave = slave; Index = index; SubIndex = subindex; - - Linear = ((long)(ulong)Slave << 32) | ((long)Index << 16) | (long)SubIndex; - Compact = ((Slave << 24) | (Index << 8) | SubIndex) ^ ((Slave & 0xFF00) << 24); } public override bool Equals(object obj) => (obj is SDOAddr other) && (Linear == other.Linear); diff --git a/ln.ethercat/SDOCache.cs b/ln.ethercat/SDOCache.cs index d491877..632334d 100644 --- a/ln.ethercat/SDOCache.cs +++ b/ln.ethercat/SDOCache.cs @@ -6,43 +6,43 @@ using ln.collections; namespace ln.ethercat { - public class SDOCache - { - ECMaster ECMaster { get; } + // public class SDOCache + // { + // ECMaster ECMaster { get; } - Dictionary> slaveCaches = new Dictionary>(); + // Dictionary> slaveCaches = new Dictionary>(); - public SDOCache(ECMaster ecMaster) - { - ECMaster = ecMaster; - } + // public SDOCache(ECMaster ecMaster) + // { + // ECMaster = ecMaster; + // } - public BTree GetSlaveCache(int slave) - { - if (!slaveCaches.TryGetValue(slave, out BTree slaveCache)) - { - slaveCache = new BTree(); - slaveCaches.Add(slave, slaveCache); - } - return slaveCache; - } + // public BTree GetSlaveCache(int slave) + // { + // if (!slaveCaches.TryGetValue(slave, out BTree slaveCache)) + // { + // slaveCache = new BTree(); + // slaveCaches.Add(slave, slaveCache); + // } + // return slaveCache; + // } - public SDO GetOrCreateDescriptor(SDOAddr address) - { - BTree slaveCache = GetSlaveCache(address.Slave); + // public SDO GetOrCreateDescriptor(SDOAddr address) + // { + // BTree slaveCache = GetSlaveCache(address.Slave); - if (!slaveCache.TryGet(address.Linear, out SDO sdo)) - { - sdo = SDO.Create(ECMaster, address); - slaveCache.Add(sdo.Address.Linear, sdo); - } - return sdo; - } + // if (!slaveCache.TryGet(address.Linear, out SDO sdo)) + // { + // sdo = SDO.Create(ECMaster, address); + // slaveCache.Add(sdo.Address.Linear, sdo); + // } + // return sdo; + // } - public void Clear() => slaveCaches.Clear(); - public bool Contains(SDOAddr addr) => GetSlaveCache(addr.Slave).ContainsKey(addr); - public SDO this[SDOAddr address] => GetSlaveCache(address.Slave)[address.Linear]; + // public void Clear() => slaveCaches.Clear(); + // public bool Contains(SDOAddr addr) => GetSlaveCache(addr.Slave).ContainsKey(addr); + // public SDO this[SDOAddr address] => GetSlaveCache(address.Slave)[address.Linear]; - } + // } } \ No newline at end of file diff --git a/ln.ethercat/controller/Controller.cs b/ln.ethercat/controller/Controller.cs index f17e8d3..e044875 100644 --- a/ln.ethercat/controller/Controller.cs +++ b/ln.ethercat/controller/Controller.cs @@ -42,6 +42,10 @@ namespace ln.ethercat.controller Logging.Log(LogLevel.WARNING, "Controller; Start(): already started"); } else { stopRequested = false; + + foreach (DriveController driveController in driveControllers) + driveController.Initialize(); + threadController = new Thread(ControllerThread); threadController.Start(); } diff --git a/ln.ethercat/controller/drives/CIA402Controller.cs b/ln.ethercat/controller/drives/CIA402Controller.cs index 988f588..43ed13e 100644 --- a/ln.ethercat/controller/drives/CIA402Controller.cs +++ b/ln.ethercat/controller/drives/CIA402Controller.cs @@ -30,35 +30,40 @@ namespace ln.ethercat.controller.drives public class CIA402Controller : DriveController { - public readonly SDOAddr saControlWord; - public readonly SDOAddr saStatusWord; - public readonly SDOAddr saErrorCode; - public readonly SDOAddr saTargetPosition; - public readonly SDOAddr saTargetSpeed; - public readonly SDOAddr saTargetTorque; - public readonly SDOAddr saActualPosition; - public readonly SDOAddr saActualSpeed; - public readonly SDOAddr saActualTorque; - public readonly SDOAddr saModesOfOperation; - public readonly SDOAddr saModesOfOperationDisplay; + SDOValue svControlWord; + SDOValue svStatusWord; + SDOValue svErrorCode; + SDOValue svTargetPosition; + SDOValue svTargetSpeed; + SDOValue svTargetTorque; + SDOValue svActualPosition; + SDOValue svActualSpeed; + SDOValue svActualTorque; + SDOValue svModesOfOperation; + SDOValue svModesOfOperationDisplay; + - public CIA402Controller(ECMaster ecMaster,int slave) + public CIA402Controller(ECMaster ecMaster,UInt16 slave) :base(ecMaster, slave) { - saErrorCode = new SDOAddr(slave, 0x603f); - saControlWord = new SDOAddr(slave, 0x6040); - saStatusWord = new SDOAddr(slave, 0x6041); - - saTargetPosition = new SDOAddr(slave, 0x607A); - saTargetSpeed = new SDOAddr(slave, 0x60FF); - saTargetTorque = new SDOAddr(slave, 0x6071); - - saActualPosition = new SDOAddr(slave, 0x6064); - saActualSpeed = new SDOAddr(slave, 0x606C); - saActualTorque = new SDOAddr(slave, 0x6077); + } - saModesOfOperation = new SDOAddr(slave, 0x6060); - saModesOfOperationDisplay = new SDOAddr(slave, 0x6061); + public override void Initialize() + { + if (!( + ECMaster.GetSDOValue(Slave, 0x603F, 0, out svErrorCode) && + ECMaster.GetSDOValue(Slave, 0x6040, 0, out svControlWord) && + ECMaster.GetSDOValue(Slave, 0x6041, 0 , out svStatusWord) && + ECMaster.GetSDOValue(Slave, 0x607A ,0, out svTargetPosition) && + ECMaster.GetSDOValue(Slave, 0x60FF, 0, out svTargetSpeed) && + ECMaster.GetSDOValue(Slave, 0x6071, 0, out svTargetTorque) && + ECMaster.GetSDOValue(Slave, 0x6064, 0, out svActualPosition) && + ECMaster.GetSDOValue(Slave, 0x606C, 0, out svActualSpeed) && + ECMaster.GetSDOValue(Slave, 0x6077, 0, out svActualTorque) && + ECMaster.GetSDOValue(Slave, 0x6060, 0, out svModesOfOperation) && + ECMaster.GetSDOValue(Slave, 0x6061, 0, out svModesOfOperationDisplay) + )) + throw new ArgumentOutOfRangeException("CIA402Controller could not retrieve SDOvalues for CiA402 profile"); } public override void UpdateStates() { } @@ -90,6 +95,7 @@ namespace ln.ethercat.controller.drives public override string OEMDriveState { get => GetCIA402State().ToString(); } + public override string OEMDriveMode => ModeOfOperation.ToString(); public override DriveMode DriveMode { get { @@ -112,10 +118,10 @@ namespace ln.ethercat.controller.drives ModeOfOperation = CIA402ModesOfOperation.CYCLIC_SYNC_POSITION; break; case DriveMode.SPEED: - ModeOfOperation = CIA402ModesOfOperation.CYCLIC_SYNC_POSITION; + ModeOfOperation = CIA402ModesOfOperation.CYCLIC_SYNC_VELOCITY; break; case DriveMode.TORQUE: - ModeOfOperation = CIA402ModesOfOperation.CYCLIC_SYNC_POSITION; + ModeOfOperation = CIA402ModesOfOperation.CYCLIC_SYNC_TORQUE; break; default: Logging.Log(LogLevel.WARNING, "CIA402Controller: DriveMode {0} not supported", value); @@ -124,42 +130,45 @@ namespace ln.ethercat.controller.drives } } + public void SetDriveModePosition() => DriveMode = DriveMode.POSITION; + public void SetDriveModeSpeed() => DriveMode = DriveMode.SPEED; + public void SetDriveModeTorque() => DriveMode = DriveMode.TORQUE; + public CIA402ModesOfOperation ModeOfOperation { - get => (CIA402ModesOfOperation)GetSDOValue(saModesOfOperationDisplay); - set => SetSDOValue(saModesOfOperation, (byte)value); + get => (CIA402ModesOfOperation)(svModesOfOperationDisplay.GetValue()); + set => svModesOfOperation.SetValue((sbyte)value); } - - public override decimal ActualPosition => GetSDOValue(saActualPosition); - public override decimal ActualSpeed => GetSDOValue(saActualSpeed); - public override decimal ActualTorque => GetSDOValue(saActualTorque); + public override decimal ActualPosition => svActualPosition.GetValue(); + public override decimal ActualSpeed => svActualSpeed.GetValue(); + public override decimal ActualTorque => svActualTorque.GetValue(); public override decimal TargetPosition { - get => GetSDOValue(saTargetPosition); - set => SetSDOValue(saTargetPosition, value); + get => svTargetPosition.GetValue(); + set => svTargetPosition.SetValue((int)value); } public override decimal TargetSpeed { - get => GetSDOValue(saTargetSpeed); - set => SetSDOValue(saTargetSpeed, value); + get => svTargetSpeed.GetValue(); + set => svTargetSpeed.SetValue((int)value); } public override decimal TargetTorque { - get => GetSDOValue(saTargetTorque); - set => SetSDOValue(saTargetTorque, value); + get => svTargetTorque.GetValue(); + set => svTargetTorque.SetValue((short)value); } - public override int ErrorCode => GetSDOValue(saErrorCode); + public override int ErrorCode => svErrorCode.GetValue(); public override string ErrorText => ErrorCode.ToString(); - public override void EnableDrive(bool enable) + public override void Enable(bool enable) { if (enable) { switch (CIA402State) { case CIA402States.SWITCHED_ON: - SetSDOValue(saControlWord, (UInt16)0x000F); + svControlWord.SetValue((UInt16)0x000F); break; default: Logging.Log(LogLevel.WARNING, "CIA402Controller: EnableDrive(): current state is {0}, can't enable drive", CIA402State.ToString()); @@ -169,7 +178,7 @@ namespace ln.ethercat.controller.drives switch (CIA402State) { case CIA402States.OPERATION_ENABLED: - SetSDOValue(saControlWord, (UInt16)0x0007); + svControlWord.SetValue((UInt16)0x0007); break; default: Logging.Log(LogLevel.WARNING, "CIA402Controller: EnableDrive(): current state is {0}, can't disable drive", CIA402State.ToString()); @@ -193,11 +202,11 @@ namespace ln.ethercat.controller.drives break; case CIA402States.SWITCH_ON_DISABLED: case CIA402States.READY_TO_SWITCH_ON: - SetSDOValue(saControlWord, (UInt16)0x0007); // Switch ON + svControlWord.SetValue((UInt16)0x0007); // Switch ON break; } } else { - SetSDOValue(saControlWord, (UInt16)0x0006); + svControlWord.SetValue((UInt16)0x0006); } } @@ -206,7 +215,7 @@ namespace ln.ethercat.controller.drives switch (CIA402State) { case CIA402States.FAULT: - SetSDOValue(saControlWord, (UInt16)0x0086); + svControlWord.SetValue((UInt16)0x0086); break; } } @@ -214,27 +223,25 @@ namespace ln.ethercat.controller.drives CIA402States CIA402State => GetCIA402State(); public CIA402States GetCIA402State() { - if (ECMaster.GetSDO(saStatusWord, out SDO sdoStatusWord)) - { - UInt16 statusword = sdoStatusWord.GetValue(); + UInt16 statusword = svStatusWord.GetValue(); - if ((statusword & 0x004F)==0) - return CIA402States.NOT_READY_TO_SWITCH_ON; - if ((statusword & 0x004F)==0x0040) - return CIA402States.SWITCH_ON_DISABLED; - if ((statusword & 0x006F)==0x0021) - return CIA402States.READY_TO_SWITCH_ON; - if ((statusword & 0x006F)==0x0023) - return CIA402States.SWITCHED_ON; - if ((statusword & 0x006F)==0x0027) - return CIA402States.OPERATION_ENABLED; - if ((statusword & 0x006F)==0x0007) - return CIA402States.QUICK_STOP_ACTIVE; - if ((statusword & 0x004F)==0x000F) - return CIA402States.FAULT_REACTION_ACTIVE; - if ((statusword & 0x004F)==0x0008) - return CIA402States.FAULT; - } + if ((statusword & 0x004F)==0) + return CIA402States.NOT_READY_TO_SWITCH_ON; + if ((statusword & 0x004F)==0x0040) + return CIA402States.SWITCH_ON_DISABLED; + if ((statusword & 0x006F)==0x0021) + return CIA402States.READY_TO_SWITCH_ON; + if ((statusword & 0x006F)==0x0023) + return CIA402States.SWITCHED_ON; + if ((statusword & 0x006F)==0x0027) + return CIA402States.OPERATION_ENABLED; + if ((statusword & 0x006F)==0x0007) + return CIA402States.QUICK_STOP_ACTIVE; + if ((statusword & 0x004F)==0x000F) + return CIA402States.FAULT_REACTION_ACTIVE; + if ((statusword & 0x004F)==0x0008) + return CIA402States.FAULT; + return CIA402States.UNDEFINED; } diff --git a/ln.ethercat/controller/drives/DriveController.cs b/ln.ethercat/controller/drives/DriveController.cs index 164d01f..97120a8 100644 --- a/ln.ethercat/controller/drives/DriveController.cs +++ b/ln.ethercat/controller/drives/DriveController.cs @@ -24,14 +24,16 @@ namespace ln.ethercat.controller.drives public abstract class DriveController { protected ECMaster ECMaster { get; } - public int Slave { get; } + public UInt16 Slave { get; } - public DriveController(ECMaster ecMaster, int slave) + public DriveController(ECMaster ecMaster, UInt16 slave) { ECMaster = ecMaster; Slave = slave; } + public abstract void Initialize(); + /* called by controller before control loops and logic */ public abstract void UpdateStates(); /* called by controller after user logic */ @@ -45,14 +47,15 @@ namespace ln.ethercat.controller.drives public abstract void ClearFault(); public abstract DriveMode DriveMode { get; set; } + public abstract string OEMDriveMode { get; } public void PowerOn() => Power(true); public void PowerOff() => Power(false); public abstract void Power(bool poweron); - public void EnableDrive() => EnableDrive(true); - public void DisableDrive() => EnableDrive(false); - public abstract void EnableDrive(bool enabled); + public void EnableDrive() => Enable(true); + public void DisableDrive() => Enable(false); + public abstract void Enable(bool enabled); public abstract decimal ActualPosition { get; } public abstract decimal ActualSpeed { get; } @@ -60,28 +63,7 @@ namespace ln.ethercat.controller.drives public abstract decimal TargetPosition { get; set; } public abstract decimal TargetSpeed { get; set; } public abstract decimal TargetTorque { get; set; } - - - public T GetSDOValue(UInt16 index,byte subIndex) - { - if (ECMaster.GetSDO(new SDOAddr(Slave, index, subIndex), out SDO sdo)) - return sdo.GetValue(); - throw new Exception("DriveController: failed to get sdo value"); - } - public T GetSDOValue(SDOAddr sa) - { - if (ECMaster.GetSDO(sa, out SDO sdo)) - return sdo.GetValue(); - throw new Exception("DriveController: failed to get sdo value"); - } - - public void SetSDOValue(UInt16 slave, UInt16 index, byte subIndex, T value) => SetSDOValue(new SDOAddr(slave,index,subIndex), value); - public void SetSDOValue(SDOAddr sa, T value) - { - if (ECMaster.GetSDO(sa, out SDO sdo)) - sdo.Value = value; - } - + } diff --git a/ln.ethercat/lib/libecmbind.so b/ln.ethercat/lib/libecmbind.so index d49743a..fcb6537 100755 Binary files a/ln.ethercat/lib/libecmbind.so and b/ln.ethercat/lib/libecmbind.so differ diff --git a/ln.ethercat/ln.ethercat.csproj b/ln.ethercat/ln.ethercat.csproj index 205ce7d..4a5a2af 100644 --- a/ln.ethercat/ln.ethercat.csproj +++ b/ln.ethercat/ln.ethercat.csproj @@ -17,8 +17,8 @@ - - + +