diff --git a/lib/coffez.js b/lib/coffez.js index a322f68..741014f 100644 --- a/lib/coffez.js +++ b/lib/coffez.js @@ -1,4 +1,3 @@ -// Generated by CoffeeScript 1.10.0 /* @@ -404,5 +403,3 @@ module.exports = root.coffez; }).call(this); - -//# sourceMappingURL=coffez.js.map diff --git a/lib/config.js b/lib/config.js index 049c7fa..5fa4394 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,4 +1,3 @@ -// Generated by CoffeeScript 1.10.0 /* @@ -44,5 +43,3 @@ module.exports = dds; }).call(this); - -//# sourceMappingURL=config.js.map diff --git a/lib/control-commands.js b/lib/control-commands.js index 1eced07..3c1581b 100644 --- a/lib/control-commands.js +++ b/lib/control-commands.js @@ -1,4 +1,3 @@ -// Generated by CoffeeScript 1.10.0 /* @@ -17,7 +16,7 @@ */ (function() { - var Close, CloseCmd, CloseDataReader, CloseDataReaderCmd, CloseDataWriter, CloseDataWriterCmd, CommandId, Connect, ConnectCmd, ConnectDataReader, ConnectDataReaderCmd, ConnectDataWriter, ConnectDataWriterCmd, ConnectedDataReaderEvt, ConnectedDataWriterEvt, ConnectedRuntimeEvt, CreateDataReader, CreateDataReaderCmd, CreateDataWriter, CreateDataWriterCmd, CreateEntity, CreateTopic, CreateTopicCmd, CreatedDataReaderEvt, CreatedDataWriterEvt, CreatedTopicEvt, DataAvailableEvt, Disconnect, DisconnectCmd, DisconnectedDataReaderEvt, DisconnectedDataWriterEvt, DisconnectedRuntimeEvt, EntityKind, ErrorEvt, EventHeader, EventId, Header, OnConnectedDataReader, OnConnectedDataWriter, OnConnectedRuntime, OnCreatedDataReader, OnCreatedDataWriter, OnCreatedTopic, OnDataAvailable, OnDisconnectedDataReader, OnDisconnectedDataWriter, OnDisconnectedRuntime, OnError, WriteData, WriteDataCmd, WriteLog, WriteLogCmd, dds; + var Close, CloseCmd, CloseDataReader, CloseDataReaderCmd, CloseDataWriter, CloseDataWriterCmd, CommandId, Connect, ConnectCmd, ConnectDataReader, ConnectDataReaderCmd, ConnectDataWriter, ConnectDataWriterCmd, ConnectedDataReaderEvt, ConnectedDataWriterEvt, ConnectedRuntimeEvt, CreateDataReader, CreateDataReaderCmd, CreateDataWriter, CreateDataWriterCmd, CreateEntity, CreateTopic, CreateTopicCmd, CreatedDataReaderEvt, CreatedDataWriterEvt, CreatedTopicEvt, DataAvailableEvt, Disconnect, DisconnectCmd, DisconnectedDataReaderEvt, DisconnectedDataWriterEvt, DisconnectedRuntimeEvt, DisposeData, DisposeDataCmd, DisposeDataWriter, DisposeDataWriterCmd, EntityKind, ErrorEvt, EventHeader, EventId, Header, OnConnectedDataReader, OnConnectedDataWriter, OnConnectedRuntime, OnCreatedDataReader, OnCreatedDataWriter, OnCreatedTopic, OnDataAvailable, OnDisconnectedDataReader, OnDisconnectedDataWriter, OnDisconnectedRuntime, OnError, WriteData, WriteDataCmd, WriteLog, WriteLogCmd, dds; dds = {}; @@ -29,7 +28,8 @@ Disconnect: 4, Close: 5, Write: 6, - Log: 7 + Log: 7, + Dispose: 8 }; EventId = { @@ -114,6 +114,10 @@ CloseDataWriter = CreateEntity(CloseDataWriterCmd); + DisposeDataWriterCmd = Header(CommandId.Dispose, EntityKind.DataWriter); + + DisposeDataWriter = CreateEntity(DisposeDataWriterCmd); + dds.ConnectCmd = ConnectCmd; dds.Connect = Connect; @@ -166,6 +170,16 @@ }; }; + DisposeDataCmd = Header(CommandId.Dispose, EntityKind.DataWriter); + + DisposeData = function(s, id) { + return { + h: DisposeDataCmd, + data: s, + eid: id + }; + }; + dds.ConnectDataWriterCmd = ConnectDataWriterCmd; dds.ConnectDataWriter = ConnectDataWriter; @@ -174,6 +188,10 @@ dds.WriteData = WriteData; + dds.DisposeDataCmd = DisposeDataCmd; + + dds.DisposeData = DisposeData; + ConnectDataReaderCmd = Header(CommandId.Connect, EntityKind.DataWriter); ConnectDataReader = function(addr, id) { @@ -364,5 +382,3 @@ module.exports = dds; }).call(this); - -//# sourceMappingURL=control-commands.js.map diff --git a/lib/control-link.js b/lib/control-link.js index 0884f1e..a380697 100644 --- a/lib/control-link.js +++ b/lib/control-link.js @@ -1,4 +1,3 @@ -// Generated by CoffeeScript 1.10.0 /* @@ -162,6 +161,7 @@ if (this.connected) { this.connected = false; this.ctrlSock.map(function(s) { + console.log("[control-link] closing socket"); return s.close(); }); return this.crtSock = z_._None; @@ -170,6 +170,7 @@ ControlLink.prototype.createTopic = function(topic, qos, eid) { var cmd, scmd; + console.log("[control-link] Creating Topic for eid = " + eid); cmd = CreateTopicMsg(this.sn, topic); this.tmap[this.sn] = eid; this.sn = this.sn + 1; @@ -212,6 +213,7 @@ }): guid = msg.b.eid; url = Config.runtime.readerPrefixURL(this.server) + '/' + guid; + console.log("[control-link] sn = " + msg.h.sn + ", eid = " + this.drmap[msg.h.sn]); evt = drt.OnCreatedDataReader(url, this.drmap[msg.h.sn]); delete this.drmap[msg.h.sn]; return this.emit('postMessage', evt); @@ -221,6 +223,7 @@ }): guid = msg.b.eid; url = Config.runtime.writerPrefixURL(this.server) + '/' + guid; + console.log("[control-link] sn = " + msg.h.sn + ", eid = " + this.dwmap[msg.h.sn]); evt = drt.OnCreatedDataWriter(url, this.dwmap[msg.h.sn]); delete this.dwmap[msg.h.sn]; return this.emit('postMessage', evt); @@ -228,6 +231,7 @@ cid: drt.CommandId.OK, ek: drt.EntityKind.Topic }): + console.log("[control-link] Topic sn = " + msg.h.sn + " eid = " + this.tmap[msg.h.sn]); evt = drt.OnCreatedTopic(this.tmap[msg.h.sn]); delete this.tmap[msg.h.sn]; return this.emit('postMessage', evt); @@ -260,19 +264,23 @@ util.inherits(ControlLinkWorker, EventEmitter); ControlLinkWorker.prototype.postMessage = function(cmd) { + console.log("[control-link] CtrlWorker received cmd: " + JSON.stringify(cmd)); switch (false) { case !z_.match(cmd.h, drt.ConnectCmd): + console.log("[control-link]: cmd = Connect (" + cmd.url + ")"); return this.ctrlLink.connect(cmd.url, cmd.authToken); case !z_.match(cmd.h, drt.CreateTopicCmd): return this.ctrlLink.createTopic(cmd.topic, cmd.qos, cmd.eid); case !z_.match(cmd.h, drt.CreateDataReaderCmd): + console.log("[control-link] CreateDataReader: " + cmd.eid); return this.ctrlLink.createDataReader(cmd.topic, cmd.qos, cmd.eid); case !z_.match(cmd.h, drt.CreateDataWriterCmd): + console.log("[control-link] CreateDataWriter: " + cmd.eid); return this.ctrlLink.createDataWriter(cmd.topic, cmd.qos, cmd.eid); case !z_.match(cmd.h, drt.Disconnect): return this.ctrlLink.disconnect(); default: - return console.log("[control-worker] Worker Received Unknown Command!"); + return console.log("[control-link] Worker Received Unknown Command!"); } }; @@ -283,5 +291,3 @@ module.exports = ControlLinkWorker; }).call(this); - -//# sourceMappingURL=control-link.js.map diff --git a/lib/dds-runtime.js b/lib/dds-runtime.js index 432b94a..44050e0 100644 --- a/lib/dds-runtime.js +++ b/lib/dds-runtime.js @@ -1,4 +1,3 @@ -// Generated by CoffeeScript 1.10.0 /* @@ -78,14 +77,13 @@ this.onRcvWorkerMessage = bind(this.onRcvWorkerMessage, this); this.onSendWorkerMessage = bind(this.onSendWorkerMessage, this); this.onCtrlWorkerMessage = bind(this.onCtrlWorkerMessage, this); + this.disposeData = bind(this.disposeData, this); this.writeData = bind(this.writeData, this); this.sn = 0; this.eidCount = 0; this.drmap = {}; this.dwmap = {}; this.tmap = {}; - this.connected = false; - this.closed = false; this.onconnect = function(evt) { console.log("[dds-runtime]: onconnect"); return this.connected = true; @@ -99,6 +97,8 @@ console.log("[dds-runtime]: ondisconnect"); return this.connected = false; }; + this.connected = false; + this.closed = false; this.needToReEstablishConnections = false; this.ctrlLink = new ControlLink(); console.log("[dds-runtime]: ctrlLink (" + this.ctrlLink + ")"); @@ -183,7 +183,7 @@ Runtime.prototype.registerTopic = function(t) { var ct, eid; - console.log("[[dds-runtime]: Defining topic " + t.tinfo.tname); + console.log("[dds-runtime]: Defining topic " + t.tinfo.tname); eid = this.generateEntityId(); t.eid = eid; this.tmap[eid] = t; @@ -193,23 +193,49 @@ Runtime.prototype.unregisterTopic = function(t) {}; - Runtime.prototype.closeDataReader = function(dr) {}; + Runtime.prototype.closeDataReader = function(dr) { + console.log("[dds-runtime]: Cleaning up DR with eid = " + dr.eid); + return delete this.drmap[dr.eid]; + }; - Runtime.prototype.closeDataWriter = function(dw) {}; + Runtime.prototype.closeDataWriter = function(dw) { + console.log("[dds-runtime]: Cleaning up DW with eid = " + dw.eid); + return delete this.dwmap[dw.eid]; + }; Runtime.prototype.writeData = function(dw, s) { var cmd, data, sdata; data = Array.isArray(s) ? s : [s]; - sdata = JSON.stringify(data); + sdata = JSON.stringify(data, function(key, value) { + if (value !== value) { + return 'NaN'; + } + return value; + }); cmd = drt.WriteData(sdata, dw.eid); return this.sendWorker.postMessage(cmd); }; + Runtime.prototype.disposeData = function(dw, s) { + var cmd, data, sdata; + data = Array.isArray(s) ? s : [s]; + sdata = JSON.stringify(data, function(key, value) { + if (value !== value) { + return 'NaN'; + } + return value; + }); + cmd = drt.DisposeData(sdata, dw.eid); + return this.sendWorker.postMessage(cmd); + }; + Runtime.prototype.onCtrlWorkerMessage = function(evt) { var cmd, dr, dw, e, eid, ref, ref1; e = evt; switch (false) { case !z_.match(e.h, drt.ConnectedRuntimeEvt): + this.connected = true; + console.log("[dds-runtime]: Runtime Connected."); if (this.needToReEstablishConnections) { console.log("[dds-runtime]: Re-establishing DataReaders and DataWriters connections"); ref = this.dwmap; @@ -228,7 +254,7 @@ return this.onconnect(e); case !z_.match(e.h, drt.DisconnectedRuntimeEvt): console.log("[dds-runtime]: Runtime Disconnected."); - return this.ondisconnect(e); + return this.disconnect(); case !z_.match(e.h, drt.CreatedTopicEvt): console.log("[dds-runtime]: Topic created with eid = " + e.eid); return this.tmap[e.eid].onregistered(e); @@ -242,6 +268,8 @@ return this.sendWorker.postMessage(cmd); case !z_.match(e.h, drt.WriteLogCmd): return console.log("[dds-runtime]: " + e.kind + "]: " + e.msg); + case !z_.match(e.h.eid, drt.EventId.Error): + return console.log("[dds-runtime]: " + e.h.kind + "]: " + e.msg); default: return console.log("[dds-runtime]: Driver received invalid command from CtrlWorker"); } @@ -254,7 +282,10 @@ case !z_.match(e.h, drt.ConnectedDataWriterEvt): return this.dwmap[e.eid].onconnect(e); case !z_.match(e.h, drt.DisconnectedDataWriterEvt): - return this.dwmap[e.eid].ondisconnect(e); + if (this.dwmap[e.eid]) { + return this.dwmap[e.eid].ondisconnect(e); + } + break; case !z_.match(e.h, drt.WriteLogCmd): return console.log("[dds-runtime]: [Log: " + e.kind + "]: " + e.msg); default: @@ -269,9 +300,15 @@ case !z_.match(e.h, drt.ConnectedDataReaderEvt): return this.drmap[e.eid].onconnect(e); case !z_.match(e.h, drt.DisconnectedDataReaderEvt): - return this.drmap[e.eid].ondisconnect(e); + if (this.drmap[e.eid]) { + return this.drmap[e.eid].ondisconnect(e); + } + break; case !z_.match(e.h, drt.DataAvailableEvt): - return this.drmap[e.eid].onDataAvailable(e.data); + if (this.drmap[e.eid]) { + return this.drmap[e.eid].onDataAvailable(e.data); + } + break; case !z_.match(e.h, drt.WriteLogCmd): return console.log("[dds-runtime]: [Log: " + e.kind + "]: " + e.msg); default: @@ -296,6 +333,7 @@ this.rcvWorker.postMessage(drt.Disconnect); this.drmap = {}; this.dwmap = {}; + console.log("[dds-runtime]: calling close..."); return this.onclose(); } }; @@ -333,5 +371,3 @@ module.exports = dds; }).call(this); - -//# sourceMappingURL=dds-runtime.js.map diff --git a/lib/dds.js b/lib/dds.js index 7b4b81f..06691ba 100644 --- a/lib/dds.js +++ b/lib/dds.js @@ -1,4 +1,3 @@ -// Generated by CoffeeScript 1.10.0 /* @@ -17,7 +16,7 @@ */ (function() { - var ContentFilter, DSCommandId, DSEntityKind, DataCache, DataReader, DataWriter, Durability, DurabilityKind, EntityQos, History, HistoryKind, JSONTopicType, JSONTopicTypeName, JSONTopicTypeSupport, KeyValueTopicType, KeyValueTopicTypeName, Partition, PolicyId, Reliability, ReliabilityKind, TimeFilter, Topic, TopicInfo, UserDefinedTopicTypeSupport, coffez, createCommand, createHeader, createTopicInfo, dds, isBuiltinTopicType, isJSONTopicType, isKeyValueTopicType, typesSupport, z_, + var ContentFilter, DSCommandId, DSEntityKind, DataCache, DataReader, DataWriter, DestinationOrder, DestinationOrderKind, Durability, DurabilityKind, EntityQos, History, HistoryKind, JSONTopicType, JSONTopicTypeName, JSONTopicTypeSupport, KeyValueTopicType, KeyValueTopicTypeName, Partition, PolicyId, Reliability, ReliabilityKind, SampleInfo, TimeFilter, Topic, TopicInfo, UserDefinedTopicTypeSupport, coffez, createCommand, createHeader, createTopicInfo, dds, isBuiltinTopicType, isJSONTopicType, isKeyValueTopicType, typesSupport, z_, slice = [].slice, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -34,7 +33,7 @@ @namespace dds */ - dds.VERSION = "__project.version__"; + dds.VERSION = "1.2.4"; PolicyId = { History: 0, @@ -43,9 +42,10 @@ ContentFilter: 3, TimeFilter: 4, Durability: 5, - TransportPriority: 6, - Ownership: 7, - OwnershipStrenght: 8 + DestinationOrder: 6, + TransportPriority: 7, + Ownership: 8, + OwnershipStrength: 9 }; @@ -212,6 +212,36 @@ }; + /* + Destination Order Policy + */ + + DestinationOrderKind = { + ByReceptionTimestamp: 0, + BySourceTimestamp: 1 + }; + + + /** + DestinationOrder QoS Policy. + @memberof dds# + @property ByReceptionTimestamp - data is ordered based on the reception time at each Subscriber + @property BySourceTimestamp - data is ordered based on a time stamp placed at the source (by the Service or by the application) + @example var qos = DestinationOrder.ByReceptionTimestamp + */ + + DestinationOrder = { + ByReceptionTimestamp: { + id: PolicyId.DestinationOrder, + k: DestinationOrderKind.ByReceptionTimestamp + }, + BySourceTimestamp: { + id: PolicyId.DestinationOrder, + k: DestinationOrderKind.BySourceTimestamp + } + }; + + /** Creates any of the DDS entities quality of service, including DataReaderQos and DataWriterQos. @constructor @@ -261,6 +291,10 @@ dds.ContentFilter = ContentFilter; + dds.DestinationOrderKind = DestinationOrderKind; + + dds.DestinationOrder = DestinationOrder; + /** Topic quality of service object @@ -294,11 +328,39 @@ dds.DataWriterQos = EntityQos; + + /** + SampleInfo is accessed in data samples via the `$info` property and provides instance lifecycle information + @memberof dds# + @property {Enum} SampleState - A value of `1` is Read, a value of `2` is NotRead + @property {Enum} ViewState - A value of `1` is New, a value of `2` is NotNew + @property {Enum} InstanceState - A value of `1` is Alive, a value of `2` is NotAliveDisposed, a value of `4` + is NotAliveNoWriters + */ + + SampleInfo = { + SampleState: { + Read: 1, + NotRead: 2 + }, + ViewState: { + New: 1, + NotNew: 2 + }, + InstanceState: { + Alive: 1, + NotAliveDisposed: 2, + NotAliveNoWriters: 4 + } + }; + + dds.SampleInfo = SampleInfo; + JSONTopicTypeName = "org.omg.dds.types.JSONTopicType"; JSONTopicType = (function() { - function JSONTopicType(value) { - this.value = value; + function JSONTopicType(value1) { + this.value = value1; } return JSONTopicType; @@ -306,9 +368,9 @@ })(); KeyValueTopicType = (function() { - function KeyValueTopicType(key1, value) { + function KeyValueTopicType(key1, value1) { this.key = key1; - this.value = value; + this.value = value1; } return KeyValueTopicType; @@ -333,13 +395,32 @@ id: 0, injectType: function(s) { var v; - v = new JSONTopicType(JSON.stringify(s)); + v = new JSONTopicType(JSON.stringify(s, function(key, value) { + if (value !== value) { + return 'NaN'; + } + return value; + })); console.log("InjectedType = " + (JSON.stringify(v))); return v; }, extractType: function(s) { - var v; - v = JSON.parse(s.value); + var error, m, v; + m = s.value; + try { + v = JSON.parse(m); + } catch (_error) { + error = _error; + m = m(replace(/([:,]|:\[)NaN/g, function(matched) { + return matched.replace('NaN', '"NaN"'); + })); + v = JSON.parse(m, function(key, value) { + if (value === 'NaN') { + return NaN; + } + return value; + }); + } console.log("Extracted Type = " + v); return v; } @@ -364,26 +445,27 @@ this.qos = qos1; this.ttype = ttype1; this.tregtype = tregtype1; - - /** - Creates a `Topic` in the domain `did`, named `tname`, having `qos` Qos, - for the type `ttype` whose registered name is `tregtype` - @constructor - @param {number} did - DDS domain ID - @param {string} tname - topic name - @param {TopicQos} qos - topic Qos - @param {string} ttype - topic type. If not specified, a generic type is used. - @param {string} tregtype - topic registered type name. If not specified, 'ttype' is used. - - @classdesc defines a DDS Topic - @memberof dds - */ } return TopicInfo; })(); + + /** + Creates a `Topic` in the domain `did`, named `tname`, having `qos` Qos, + for the type `ttype` whose registered name is `tregtype` + @constructor + @param {number} did - DDS domain ID + @param {string} tname - topic name + @param {TopicQos} qos - topic Qos + @param {string} ttype - topic type. If not specified, a generic type is used. + @param {string} tregtype - topic registered type name. If not specified, 'ttype' is used. + + @classdesc defines a DDS Topic + @memberof dds + */ + Topic = (function() { function Topic(did, tname, qos, ttype, tregtype) { var idid, ns; @@ -499,9 +581,23 @@ }; DataReader.prototype.onDataAvailable = function(m) { - var d; + var d, error, parsedm; this.receivedSamples += 1; - d = this.typeSupport.extractType(JSON.parse(m)); + try { + parsedm = JSON.parse(m); + } catch (_error) { + error = _error; + m = m.replace(/([:,]|:\[)NaN/g, function(matched) { + return matched.replace('NaN', '"NaN"'); + }); + parsedm = JSON.parse(m, function(key, value) { + if (value === 'NaN') { + return NaN; + } + return value; + }); + } + d = this.typeSupport.extractType(parsedm); return this.handlers.forEach(function(h) { return h(d); }); @@ -535,10 +631,10 @@ @classdesc defines a DDS data writer. This type is used to write data for a specific topic with a given QoS. - A `DataWriter` goes through different states, it is intially disconnected and changes to the connected + A `DataWriter` goes through different states, it is initially disconnected and changes to the connected state when the underlying transport connection is successfully established with the server. - At this point a `DataWriter` can be explicitely closed or disconnected. A disconnection can happen - as the result of a network failure or server failure. Disconnection and reconnections are managed by the + At this point a `DataWriter` can be explicitly closed or disconnected. A disconnection can happen + as the result of a network failure or server failure. Disconnections and reconnections are managed by the runtime. @memberof dds */ @@ -548,6 +644,7 @@ this.runtime = runtime; this.topic = topic1; this.qos = qos1; + this.dispose = bind(this.dispose, this); this.write = bind(this.write, this); this.onclose = function() {}; this.closed = false; @@ -563,7 +660,9 @@ /** Writes one or more samples. - @param {...data-type} ds - data sample + The returned samples contained the SampleInfo + accessible from the `info` property + @param {...data-type} ds - data samples @memberof! dds.DataWriter# @function write */ @@ -580,6 +679,27 @@ return this.sentSamples += xs.length; }; + + /** + Dispose one or more instances. + @param {...data-type} ds - data samples, each containing + the key of the instance to be disposed + @memberof! dds.DataWriter# + @function dispose + */ + + DataWriter.prototype.dispose = function() { + var ds, xs; + ds = 1 <= arguments.length ? slice.call(arguments, 0) : []; + xs = ds.map((function(_this) { + return function(s) { + return _this.typeSupport.injectType(s); + }; + })(this)); + this.runtime.disposeData(this, xs); + return this.sentSamples += xs.length; + }; + DataWriter.prototype.resetStats = function() { return this.sentSamples = 0; }; @@ -1105,5 +1225,3 @@ module.exports = dds; }).call(this); - -//# sourceMappingURL=dds.js.map diff --git a/lib/reader-link.js b/lib/reader-link.js index 575164b..6c2d068 100644 --- a/lib/reader-link.js +++ b/lib/reader-link.js @@ -1,4 +1,3 @@ -// Generated by CoffeeScript 1.10.0 /* @@ -40,18 +39,19 @@ setupReaderSocket = function(worker, url, eid) { var socket; socket = new WebSocket(url); - console.log("Created Websocket for DR at " + url); + console.log("[reader-link] Created Websocket for DR at " + url); socketMap[eid] = socket; urlMap[eid] = url; socket.onopen = (function(_this) { return function(evt) { - console.log("DR Websocket is open"); + console.log("[reader-link] DR Websocket is open"); + connected = true; return worker.emit('postMessage', drt.OnConnectedDataReader(url, eid)); }; })(this); socket.onclose = (function(_this) { return function(evt) { - console.log("DR Websocket is closed"); + console.log("[reader-link] DR Websocket is closed"); delete socketMap[eid]; delete urlMap[eid]; return worker.emit('postMessage', drt.OnDisconnectedDataReader(url, eid)); @@ -72,7 +72,7 @@ for (eid in socketMap) { s = socketMap[eid]; s.close(); - worker.emit('postMessage', OnDisconnectedDataReader(urlMap[eid], eid)); + worker.emit('postMessage', drt.OnDisconnectedDataReader(urlMap[eid], eid)); } socketMap = {}; return urlMap = {}; @@ -89,12 +89,14 @@ ReadLinkWorker.prototype.postMessage = function(cmd) { switch (false) { case !z_.match(cmd.h, drt.ConnectDataReaderCmd): - console.log("Setting-up Socket for : " + cmd.eid + ", at " + cmd.url); + console.log("[reader-link] Setting-up Socket for : " + cmd.eid + ", at " + cmd.url); return setupReaderSocket(this, cmd.url, cmd.eid); + case !z_.match(cmd.h, drt.Connect): + return connect(); case !z_.match(cmd.h, drt.Disconnect): return disconnect(this); default: - return console.log("Reader Worker Received Unknown Command!"); + return console.log("[reader-link] Reader Worker Received Unknown Command!"); } }; @@ -105,5 +107,3 @@ module.exports = ReadLinkWorker; }).call(this); - -//# sourceMappingURL=reader-link.js.map diff --git a/lib/writer-link.js b/lib/writer-link.js index f8732a7..68b6fed 100644 --- a/lib/writer-link.js +++ b/lib/writer-link.js @@ -1,4 +1,3 @@ -// Generated by CoffeeScript 1.10.0 /* @@ -40,18 +39,19 @@ setupWriterSocket = function(worker, url, eid) { var socket; socket = new WebSocket(url); - console.log("Created Websocket for DW at " + url); + console.log("[writer-link] Created Websocket for DW at " + url); socketMap[eid] = socket; urlMap[eid] = url; socket.onopen = (function(_this) { return function(evt) { - console.log("DW Writer Websocket is open"); + console.log("[writer-link] DW Writer Websocket is open"); + connected = true; return worker.emit('postMessage', drt.OnConnectedDataWriter(url, eid)); }; })(this); return socket.onclose = (function(_this) { return function(evt) { - console.log("DW Websocket is closed"); + console.log("[writer-link] DW Websocket is closed at " + url + ", eid " + eid); delete socketMap[eid]; return worker.emit('postMessage', drt.OnDisconnectedDataWriter(url, eid)); }; @@ -65,7 +65,6 @@ for (eid in socketMap) { s = socketMap[eid]; s.close(); - rworker.emit('postMessage', drt.OnDisconnectedDataWriter(urlMap[eid], eid)); } socketMap = {}; return urlMap = {}; @@ -80,26 +79,41 @@ util.inherits(WriteLinkWorker, EventEmitter); WriteLinkWorker.prototype.postMessage = function(cmd) { - var e, error, s, socket; + var e, s, socket; switch (false) { case !z_.match(cmd.h, drt.ConnectDataWriterCmd): - console.log("Setting-up Socket for DW : " + cmd.eid + ", at " + cmd.url); + console.log("[writer-link] Setting-up Socket for DW : " + cmd.eid + ", at " + cmd.url); return setupWriterSocket(this, cmd.url, cmd.eid); case !z_.match(cmd.h, drt.WriteDataCmd): socket = socketMap[cmd.eid]; - s = cmd.data; - try { - return socket.send(s); - } catch (error) { - e = error; - console.log("Exception while sending data"); - return console.log(JSON.stringify(e)); + if (socket && socket.readyState === 1) { + s = cmd.data; + try { + return socket.send("write/" + s); + } catch (_error) { + e = _error; + console.log("[writer-link] Exception while sending data"); + return console.log(JSON.stringify(e)); + } + } + break; + case !z_.match(cmd.h, drt.DisposeDataCmd): + socket = socketMap[cmd.eid]; + if (socket && socket.readyState === 1) { + s = cmd.data; + try { + return socket.send("dispose/" + s); + } catch (_error) { + e = _error; + console.log("[writer-link] Exception while sending data"); + return console.log(JSON.stringify(e)); + } } break; case !z_.match(cmd.h, drt.Disconnect): return disconnect(); default: - return console.log("Reader Worker Received Unknown Command!"); + return console.log("[writer-link] Reader Worker Received Unknown Command!"); } }; @@ -110,5 +124,3 @@ module.exports = WriteLinkWorker; }).call(this); - -//# sourceMappingURL=writer-link.js.map diff --git a/src/control-commands.coffee b/src/control-commands.coffee index ad63531..c46a5a8 100644 --- a/src/control-commands.coffee +++ b/src/control-commands.coffee @@ -30,6 +30,7 @@ CommandId = Close: 5 Write: 6 Log: 7 + Dispose: 8 EventId = Error: 0 @@ -79,6 +80,8 @@ CreateDataWriterCmd = Header(CommandId.Create, EntityKind.DataWriter) CreateDataWriter = CreateEntity(CreateDataWriterCmd) CloseDataWriterCmd = Header(CommandId.Close, EntityKind.DataWriter) CloseDataWriter = CreateEntity(CloseDataWriterCmd) +DisposeDataWriterCmd = Header(CommandId.Dispose, EntityKind.DataWriter); +DisposeDataWriter = CreateEntity(DisposeDataWriterCmd); dds.ConnectCmd = ConnectCmd dds.Connect = Connect @@ -106,11 +109,15 @@ ConnectDataWriter = (addr, id) -> h:ConnectDataWriterCmd, url: addr, eid: id WriteDataCmd = Header(CommandId.Write, EntityKind.DataWriter) WriteData = (s, id) -> h: WriteDataCmd, data: s, eid: id +DisposeDataCmd = Header(CommandId.Dispose, EntityKind.DataWriter); +DisposeData = (s, id) -> h: DisposeDataCmd, data: s, eid: id dds.ConnectDataWriterCmd = ConnectDataWriterCmd dds.ConnectDataWriter = ConnectDataWriter dds.WriteDataCmd = WriteDataCmd dds.WriteData = WriteData +dds.DisposeDataCmd = DisposeDataCmd; +dds.DisposeData = DisposeData ######################################################################################################################## ## Receiver Worker Commands ######################################################################################################################## diff --git a/src/control-link.coffee b/src/control-link.coffee index 546e17d..1616ad0 100644 --- a/src/control-link.coffee +++ b/src/control-link.coffee @@ -135,11 +135,15 @@ class ControlLink disconnect: () -> if (@connected) @connected = false - @ctrlSock.map((s) -> s.close()) + @ctrlSock.map((s) -> + console.log("[control-link] closing socket") + s.close() + ) @crtSock = z_._None # Creates a remote topic createTopic: (topic, qos, eid) -> + console.log("[control-link] Creating Topic for eid = " + eid) cmd = CreateTopicMsg(@sn, topic) @tmap[@sn] = eid @sn = @sn + 1 @@ -173,6 +177,7 @@ class ControlLink when z_.match(msg.h, {cid: drt.CommandId.OK, ek: drt.EntityKind.DataReader}) guid = msg.b.eid url = Config.runtime.readerPrefixURL(@server) + '/' + guid + console.log("[control-link] sn = " + msg.h.sn + ", eid = " + @drmap[msg.h.sn]) evt = drt.OnCreatedDataReader(url, @drmap[msg.h.sn]) delete @drmap[msg.h.sn] this.emit('postMessage', evt) @@ -180,11 +185,13 @@ class ControlLink when z_.match(msg.h, {cid: drt.CommandId.OK, ek: drt.EntityKind.DataWriter}) guid = msg.b.eid url = Config.runtime.writerPrefixURL(@server) + '/' + guid + console.log("[control-link] sn = " + msg.h.sn + ", eid = " + @dwmap[msg.h.sn]) evt = drt.OnCreatedDataWriter(url, @dwmap[msg.h.sn]) delete @dwmap[msg.h.sn] this.emit('postMessage', evt) when z_.match(msg.h, {cid: drt.CommandId.OK, ek: drt.EntityKind.Topic}) + console.log("[control-link] Topic sn = " + msg.h.sn + " eid = " + @tmap[msg.h.sn]) evt = drt.OnCreatedTopic(@tmap[msg.h.sn]) delete @tmap[msg.h.sn] this.emit('postMessage', evt) @@ -210,24 +217,28 @@ class ControlLinkWorker postMessage: (cmd) -> + console.log("[control-link] CtrlWorker received cmd: " + JSON.stringify(cmd)) switch when z_.match(cmd.h, drt.ConnectCmd) + console.log("[control-link]: cmd = Connect (" + cmd.url + ")") @ctrlLink.connect(cmd.url, cmd.authToken) when z_.match(cmd.h, drt.CreateTopicCmd) @ctrlLink.createTopic(cmd.topic, cmd.qos, cmd.eid) when z_.match(cmd.h, drt.CreateDataReaderCmd) + console.log("[control-link] CreateDataReader: " + cmd.eid) @ctrlLink.createDataReader(cmd.topic, cmd.qos, cmd.eid) when z_.match(cmd.h, drt.CreateDataWriterCmd) + console.log("[control-link] CreateDataWriter: " + cmd.eid) @ctrlLink.createDataWriter(cmd.topic, cmd.qos, cmd.eid) when z_.match(cmd.h, drt.Disconnect) @ctrlLink.disconnect() else - console.log("[control-worker] Worker Received Unknown Command!") + console.log("[control-link] Worker Received Unknown Command!") module.exports = ControlLinkWorker diff --git a/src/dds-runtime.coffee b/src/dds-runtime.coffee index ae98cf9..5c4e6a0 100644 --- a/src/dds-runtime.coffee +++ b/src/dds-runtime.coffee @@ -61,8 +61,6 @@ class Runtime @drmap = {} @dwmap = {} @tmap = {} - @connected = false - @closed = false @onconnect = (evt) -> console.log("[dds-runtime]: onconnect") @connected = true @@ -76,6 +74,8 @@ class Runtime console.log("[dds-runtime]: ondisconnect") @connected = false + @connected = false + @closed = false @needToReEstablishConnections = false @ctrlLink = new ControlLink() console.log("[dds-runtime]: ctrlLink (#{@ctrlLink})") @@ -146,7 +146,7 @@ class Runtime @param {Topic} t - Topic to be registered ### registerTopic: (t) -> - console.log("[[dds-runtime]: Defining topic #{t.tinfo.tname}") + console.log("[dds-runtime]: Defining topic #{t.tinfo.tname}") eid = @generateEntityId() t.eid = eid @tmap[eid] = t @@ -156,20 +156,43 @@ class Runtime ## -- TODO: --------------------------------------------------------------------------------- unregisterTopic: (t) -> +## ------------------------------------------------------------------------------------------- closeDataReader: (dr) -> + console.log("[dds-runtime]: Cleaning up DR with eid = #{dr.eid}") + delete @drmap[dr.eid] closeDataWriter: (dw) -> + console.log("[dds-runtime]: Cleaning up DW with eid = #{dw.eid}") + delete @dwmap[dw.eid] + ## ------------------------------------------------------------------------------------------- writeData: (dw, s) => data = if (Array.isArray(s)) then s else [s] - sdata = JSON.stringify(data) + sdata = JSON.stringify(data, (key, value) -> + if (value != value) + return 'NaN'; + value; + ) cmd = drt.WriteData(sdata, dw.eid) @sendWorker.postMessage(cmd) + disposeData: (dw, s) => + data = if (Array.isArray(s)) then s else [s] + sdata = JSON.stringify(data, (key, value) -> + if (value != value) + return 'NaN' + value + ) + cmd = drt.DisposeData(sdata, dw.eid); + @sendWorker.postMessage(cmd) + + onCtrlWorkerMessage: (evt) => e = evt switch when z_.match(e.h, drt.ConnectedRuntimeEvt) + @connected = true; + console.log("[dds-runtime]: Runtime Connected.") if (@needToReEstablishConnections) console.log("[dds-runtime]: Re-establishing DataReaders and DataWriters connections") for eid,dw of @dwmap @@ -182,7 +205,7 @@ class Runtime when z_.match(e.h, drt.DisconnectedRuntimeEvt) console.log("[dds-runtime]: Runtime Disconnected.") - @ondisconnect(e) + @disconnect() when z_.match(e.h, drt.CreatedTopicEvt) console.log("[dds-runtime]: Topic created with eid = #{e.eid}") @@ -198,10 +221,12 @@ class Runtime cmd = drt.ConnectDataWriter(e.url, e.eid) @sendWorker.postMessage(cmd) - when z_.match(e.h, drt.WriteLogCmd) console.log("[dds-runtime]: #{e.kind }]: #{e.msg}") + when z_.match(e.h.eid, drt.EventId.Error) + console.log("[dds-runtime]: #{e.h.kind }]: #{e.msg}") + else console.log("[dds-runtime]: Driver received invalid command from CtrlWorker") @@ -215,7 +240,9 @@ class Runtime @dwmap[e.eid].onconnect(e) when z_.match(e.h, drt.DisconnectedDataWriterEvt) - @dwmap[e.eid].ondisconnect(e) + if (@dwmap[e.eid]) + return @dwmap[e.eid].ondisconnect(e) + break; when z_.match(e.h, drt.WriteLogCmd) console.log("[dds-runtime]: [Log: #{e.kind }]: #{e.msg}") @@ -231,10 +258,14 @@ class Runtime @drmap[e.eid].onconnect(e) when z_.match(e.h, drt.DisconnectedDataReaderEvt) - @drmap[e.eid].ondisconnect(e) + if (@drmap[e.eid]) + return @drmap[e.eid].ondisconnect(e) + break when z_.match(e.h, drt.DataAvailableEvt) - @drmap[e.eid].onDataAvailable(e.data) + if (@drmap[e.eid]) + return @drmap[e.eid].onDataAvailable(e.data) + break when z_.match(e.h, drt.WriteLogCmd) console.log("[dds-runtime]: [Log: #{e.kind }]: #{e.msg}") @@ -258,6 +289,7 @@ class Runtime @rcvWorker.postMessage(drt.Disconnect) @drmap = {} @dwmap = {} + console.log("[dds-runtime]: calling close...") @onclose() diff --git a/src/dds.coffee b/src/dds.coffee index 18d7ad1..ceaaadc 100644 --- a/src/dds.coffee +++ b/src/dds.coffee @@ -37,7 +37,7 @@ Defines the core Vortex-Web-Client javascript library. It includes the JavaScrip # exports = module.exports = dds # exports.dds = dds -dds.VERSION = "__project.version__" +dds.VERSION = "1.2.4" ######################################################################################################################## ## QoS Policies and Entities QoS @@ -50,9 +50,10 @@ PolicyId = ContentFilter: 3 TimeFilter: 4 Durability: 5 - TransportPriority: 6 - Ownership: 7 - OwnershipStrenght: 8 + DestinationOrder: 6 + TransportPriority: 7 + Ownership: 8 + OwnershipStrength: 9 ### @@ -179,6 +180,30 @@ Durability = id: PolicyId.Durability k: DurabilityKind.Persistent +### + Destination Order Policy +### +DestinationOrderKind = + ByReceptionTimestamp: 0 + BySourceTimestamp: 1 + + +###* + DestinationOrder QoS Policy. + @memberof dds# + @property ByReceptionTimestamp - data is ordered based on the reception time at each Subscriber + @property BySourceTimestamp - data is ordered based on a time stamp placed at the source (by the Service or by the application) + @example var qos = DestinationOrder.ByReceptionTimestamp +### +DestinationOrder = + ByReceptionTimestamp: + id: PolicyId.DestinationOrder + k: DestinationOrderKind.ByReceptionTimestamp + BySourceTimestamp: + id: PolicyId.DestinationOrder + k: DestinationOrderKind.BySourceTimestamp + + ###* Creates any of the DDS entities quality of service, including DataReaderQos and DataWriterQos. @constructor @@ -214,6 +239,8 @@ dds.DurabilityKind = DurabilityKind dds.Durability = Durability dds.TimeFilter = TimeFilter dds.ContentFilter = ContentFilter +dds.DestinationOrderKind = DestinationOrderKind +dds.DestinationOrder = DestinationOrder ###* Topic quality of service object @@ -246,6 +273,28 @@ dds.DataWriterQos = EntityQos ## DDS Entities ######################################################################################################################## +###* + SampleInfo is accessed in data samples via the `$info` property and provides instance lifecycle information + @memberof dds# + @property {Enum} SampleState - A value of `1` is Read, a value of `2` is NotRead + @property {Enum} ViewState - A value of `1` is New, a value of `2` is NotNew + @property {Enum} InstanceState - A value of `1` is Alive, a value of `2` is NotAliveDisposed, a value of `4` + is NotAliveNoWriters +### + +SampleInfo = + SampleState: + Read: 1 + NotRead: 2 + ViewState: + New: 1 + NotNew: 2 + InstanceState: + Alive: 1 + NotAliveDisposed: 2 + NotAliveNoWriters: 4 + +dds.SampleInfo = SampleInfo JSONTopicTypeName = "org.omg.dds.types.JSONTopicType" @@ -264,12 +313,27 @@ isBuiltinTopicType = (t) -> isJSONTopicType(t) or isKeyValueTopicType(t) JSONTopicTypeSupport = id: 0 injectType: (s) -> - v = new JSONTopicType(JSON.stringify(s)) + v = new JSONTopicType(JSON.stringify(s, (key, value) -> + if (value != value) + return 'NaN' + value + )) console.log("InjectedType = #{JSON.stringify(v)}") v extractType: (s) -> - v = JSON.parse(s.value) + m = s.value + try + v = JSON.parse(m) + catch error + m = m(replace(/([:,]|:\[)NaN/g, (matched) -> + matched.replace('NaN', '"NaN"') + )) + v = JSON.parse(m, (key, value) -> + if (value == 'NaN') + return NaN + value + ) console.log("Extracted Type = #{v}") v @@ -285,21 +349,19 @@ typesSupport = [JSONTopicTypeSupport, UserDefinedTopicTypeSupport] class TopicInfo constructor: (@did, @tname, @qos, @ttype, @tregtype) -> +###* + Creates a `Topic` in the domain `did`, named `tname`, having `qos` Qos, + for the type `ttype` whose registered name is `tregtype` + @constructor + @param {number} did - DDS domain ID + @param {string} tname - topic name + @param {TopicQos} qos - topic Qos + @param {string} ttype - topic type. If not specified, a generic type is used. + @param {string} tregtype - topic registered type name. If not specified, 'ttype' is used. - - ###* - Creates a `Topic` in the domain `did`, named `tname`, having `qos` Qos, - for the type `ttype` whose registered name is `tregtype` - @constructor - @param {number} did - DDS domain ID - @param {string} tname - topic name - @param {TopicQos} qos - topic Qos - @param {string} ttype - topic type. If not specified, a generic type is used. - @param {string} tregtype - topic registered type name. If not specified, 'ttype' is used. - - @classdesc defines a DDS Topic - @memberof dds - ### + @classdesc defines a DDS Topic + @memberof dds +### class Topic constructor: (did, tname, qos, ttype, tregtype) -> if (arguments.length < 2) @@ -392,7 +454,18 @@ class DataReader onDataAvailable: (m) => @receivedSamples += 1 - d = @typeSupport.extractType(JSON.parse(m)) + try + parsedm = JSON.parse(m); + catch error + m = m.replace(/([:,]|:\[)NaN/g, (matched) -> + matched.replace('NaN', '"NaN"') + ) + parsedm = JSON.parse(m, (key, value) -> + if (value == 'NaN') + return NaN + value + ) + d = @typeSupport.extractType(parsedm); @handlers.forEach((h) -> h(d)) ###* @@ -416,10 +489,10 @@ class DataReader @classdesc defines a DDS data writer. This type is used to write data for a specific topic with a given QoS. - A `DataWriter` goes through different states, it is intially disconnected and changes to the connected + A `DataWriter` goes through different states, it is initially disconnected and changes to the connected state when the underlying transport connection is successfully established with the server. - At this point a `DataWriter` can be explicitely closed or disconnected. A disconnection can happen - as the result of a network failure or server failure. Disconnection and reconnections are managed by the + At this point a `DataWriter` can be explicitly closed or disconnected. A disconnection can happen + as the result of a network failure or server failure. Disconnections and reconnections are managed by the runtime. @memberof dds ### @@ -437,7 +510,9 @@ class DataWriter ###* Writes one or more samples. - @param {...data-type} ds - data sample + The returned samples contained the SampleInfo + accessible from the `info` property + @param {...data-type} ds - data samples @memberof! dds.DataWriter# @function write ### @@ -446,6 +521,17 @@ class DataWriter @runtime.writeData(this, xs) @sentSamples += xs.length + ###* + Dispose one or more instances. + @param {...data-type} ds - data samples, each containing + the key of the instance to be disposed + @memberof! dds.DataWriter# + @function dispose + ### + dispose: (ds...) => + xs = ds.map((s) => @typeSupport.injectType(s)) + @runtime.disposeData(this, xs); + @sentSamples += xs.length resetStats: () -> @sentSamples = 0 diff --git a/src/reader-link.coffee b/src/reader-link.coffee index 1833b98..77fb2dc 100644 --- a/src/reader-link.coffee +++ b/src/reader-link.coffee @@ -30,16 +30,17 @@ urlMap = {} setupReaderSocket = (worker, url, eid) -> socket = new WebSocket(url) - console.log("Created Websocket for DR at #{url}") + console.log("[reader-link] Created Websocket for DR at #{url}") socketMap[eid] = socket urlMap[eid] = url socket.onopen = (evt) => - console.log("DR Websocket is open") + console.log("[reader-link] DR Websocket is open") + connected = true; worker.emit('postMessage', drt.OnConnectedDataReader(url, eid)) socket.onclose = (evt) => - console.log("DR Websocket is closed") + console.log("[reader-link] DR Websocket is closed") delete socketMap[eid] delete urlMap[eid] worker.emit('postMessage', drt.OnDisconnectedDataReader(url, eid)) @@ -54,7 +55,7 @@ disconnect = (worker) -> connected = false for eid,s of socketMap s.close() - worker.emit('postMessage', OnDisconnectedDataReader(urlMap[eid], eid)) + worker.emit('postMessage', drt.OnDisconnectedDataReader(urlMap[eid], eid)) socketMap = {} urlMap = {} @@ -69,16 +70,16 @@ class ReadLinkWorker postMessage: (cmd) -> switch when z_.match(cmd.h, drt.ConnectDataReaderCmd) - console.log("Setting-up Socket for : " + cmd.eid + ", at " + cmd.url) + console.log("[reader-link] Setting-up Socket for : " + cmd.eid + ", at " + cmd.url) setupReaderSocket(this, cmd.url, cmd.eid) - #when z_.match(cmd.h, drt.Connect) - # connect(this) + when z_.match(cmd.h, drt.Connect) + connect() when z_.match(cmd.h, drt.Disconnect) disconnect(this) else - console.log("Reader Worker Received Unknown Command!") + console.log("[reader-link] Reader Worker Received Unknown Command!") module.exports = ReadLinkWorker \ No newline at end of file diff --git a/src/writer-link.coffee b/src/writer-link.coffee index 0333c53..401c6fe 100644 --- a/src/writer-link.coffee +++ b/src/writer-link.coffee @@ -30,16 +30,17 @@ urlMap = {} setupWriterSocket = (worker, url, eid) -> socket = new WebSocket(url) - console.log("Created Websocket for DW at #{url}") + console.log("[writer-link] Created Websocket for DW at #{url}") socketMap[eid] = socket urlMap[eid] = url socket.onopen = (evt) => - console.log("DW Writer Websocket is open") + console.log("[writer-link] DW Writer Websocket is open") + connected = true; worker.emit('postMessage', drt.OnConnectedDataWriter(url, eid)) socket.onclose = (evt) => - console.log("DW Websocket is closed") + console.log("[writer-link] DW Websocket is closed at #{url}, eid #{eid}") delete socketMap[eid] worker.emit('postMessage', drt.OnDisconnectedDataWriter(url, eid)) @@ -48,7 +49,6 @@ disconnect = () -> connected = false for eid,s of socketMap s.close() - rworker.emit('postMessage', drt.OnDisconnectedDataWriter(urlMap[eid], eid)) socketMap = {} urlMap = {} @@ -64,22 +64,33 @@ class WriteLinkWorker switch when z_.match(cmd.h, drt.ConnectDataWriterCmd) - console.log("Setting-up Socket for DW : " + cmd.eid + ", at " + cmd.url) + console.log("[writer-link] Setting-up Socket for DW : #{cmd.eid}, at #{cmd.url}") setupWriterSocket(this, cmd.url, cmd.eid) when z_.match(cmd.h, drt.WriteDataCmd) socket = socketMap[cmd.eid] - s = cmd.data - try - socket.send(s) - catch e - console.log("Exception while sending data") - console.log(JSON.stringify(e)) + if (socket && socket.readyState == 1) + s = cmd.data; + try + return socket.send("write/" + s) + catch e + console.log("[writer-link] Exception while sending data") + return console.log(JSON.stringify(e)) + + when z_.match(cmd.h, drt.DisposeDataCmd) + socket = socketMap[cmd.eid] + if (socket && socket.readyState == 1) + s = cmd.data; + try + return socket.send("dispose/" + s) + catch e + console.log("[writer-link] Exception while sending data") + return console.log(JSON.stringify(e)) when z_.match(cmd.h, drt.Disconnect) disconnect() else - console.log("Reader Worker Received Unknown Command!") + console.log("[writer-link] Reader Worker Received Unknown Command!") module.exports = WriteLinkWorker