static MongoClient ConnectMongoClient(JSONSCADAConfig jsConfig) { // connect to MongoDB Database server MongoClientSettings settings = MongoClientSettings.FromUrl(new MongoUrl(jsConfig.mongoConnectionString)); if (jsConfig.tlsClientPfxFile != null && jsConfig.tlsClientPfxFile != "") { var pem = System.IO.File.ReadAllText(jsConfig.tlsCaPemFile); byte[] certBuffer = GetBytesFromPEM(pem, "CERTIFICATE"); var caCert = new X509Certificate2(certBuffer); var cliCert = new X509Certificate2(jsConfig.tlsClientPfxFile, jsConfig.tlsClientKeyPassword); settings.UseTls = true; settings.AllowInsecureTls = true; settings.SslSettings = new SslSettings { ClientCertificates = new[] { caCert, cliCert }, CheckCertificateRevocation = false, ServerCertificateValidationCallback = CertificateValidationCallBack }; } return(new MongoClient(settings)); }
static public int AutoKeyMultiplier = 1000000; // maximum number of points on each connection self-published (auto numbered points) // This process updates acquired values in the mongodb collection for realtime data static public async void ProcessMongo(JSONSCADAConfig jsConfig) { do { try { var Client = ConnectMongoClient(jsConfig); var DB = Client.GetDatabase(jsConfig.mongoDatabaseName); var collection = DB.GetCollection <rtData>(RealtimeDataCollectionName); var collectionId = DB.GetCollection <rtDataId>(RealtimeDataCollectionName); var collection_cmd = DB .GetCollection <rtCommand>(CommandsQueueCollectionName); Log("MongoDB Update Thread Started..."); var listWrites = new List <WriteModel <rtData> >(); do { //if (LogLevel >= LogLevelBasic && OPCDataQueue.Count > 0) // Log("MongoDB - Data queue size: " + OPCDataQueue.Count, LogLevelBasic); bool isMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(2500); if (!isMongoLive) { throw new Exception("Error on MongoDB connection "); } Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); OPC_Value iv; while (!OPCDataQueue.IsEmpty && OPCDataQueue.TryPeek(out iv) && OPCDataQueue.TryDequeue(out iv)) { DateTime tt = DateTime.MinValue; BsonValue bsontt = BsonNull.Value; try { if (iv.hasSourceTimestamp) { bsontt = BsonValue.Create(iv.sourceTimestamp); } } catch { tt = DateTime.MinValue; bsontt = BsonNull.Value; } BsonDocument valJSON = new BsonDocument(); try { valJSON = BsonDocument.Parse(iv.valueJson); } catch (Exception e) { Log(iv.conn_name + " - " + e.Message); } if (iv.selfPublish) { // find the json-scada connection for this received value int conn_index = 0; for (int index = 0; index < OPCUAconns.Count; index++) { if (OPCUAconns[index].protocolConnectionNumber == iv.conn_number) { conn_index = index; } } string tag = TagFromOPCParameters(iv); if (!OPCUAconns[conn_index].InsertedTags.Contains(tag)) { // tag not yet inserted // put the tag in the list of inserted, then insert it OPCUAconns[conn_index].InsertedTags.Add(tag); Log(iv.conn_name + " - INSERT NEW TAG: " + tag + " - Addr:" + iv.address); // find a new freee _id key based on the connection number if (OPCUAconns[conn_index].LastNewKeyCreated == 0) { Double AutoKeyId = iv.conn_number * AutoKeyMultiplier; var results = collectionId.Find <rtDataId>(new BsonDocument { { "_id", new BsonDocument { { "$gt", AutoKeyId }, { "$lt", (iv.conn_number + 1) * AutoKeyMultiplier } } } }).Sort(Builders <rtDataId> .Sort.Descending("_id")) .Limit(1) .ToList(); if (results.Count > 0) { OPCUAconns[conn_index].LastNewKeyCreated = results[0]._id.ToDouble() + 1; } else { OPCUAconns[conn_index].LastNewKeyCreated = AutoKeyId; } } else { OPCUAconns[conn_index].LastNewKeyCreated = OPCUAconns[conn_index].LastNewKeyCreated + 1; } var id = OPCUAconns[conn_index].LastNewKeyCreated; // will enqueue to insert the new tag into mongo DB var insert = newRealtimeDoc(iv, id); insert.protocolSourcePublishingInterval = OPCUAconns[conn_index].autoCreateTagPublishingInterval; insert.protocolSourceSamplingInterval = OPCUAconns[conn_index].autoCreateTagSamplingInterval; insert.protocolSourceQueueSize = OPCUAconns[conn_index].autoCreateTagQueueSize; listWrites .Add(new InsertOneModel <rtData>(insert)); // will imediatelly be followed by an update below (to the same tag) } } // update one existing document with received tag value (realtimeData) var update = new BsonDocument { { "$set", new BsonDocument { { "sourceDataUpdate", new BsonDocument { { "valueBsonAtSource", valJSON }, { "valueAtSource", BsonDouble .Create(iv.value) }, { "valueStringAtSource", BsonString .Create(iv.valueString) }, { "asduAtSource", BsonString .Create(iv.asdu.ToString()) }, { "causeOfTransmissionAtSource", BsonString.Create(iv.cot.ToString()) }, { "timeTagAtSource", bsontt }, { "timeTagAtSourceOk", BsonBoolean .Create(iv.hasSourceTimestamp) }, { "timeTag", BsonValue .Create(iv .serverTimestamp) }, { "notTopicalAtSource", BsonBoolean .Create(false) }, { "invalidAtSource", BsonBoolean .Create(!iv .quality ) }, { "overflowAtSource", BsonBoolean .Create(false) }, { "blockedAtSource", BsonBoolean .Create(false) }, { "substitutedAtSource", BsonBoolean .Create(false) } } } } } }; // update filter, avoids updating commands that can have the same address as supervised points var filt = new rtFilt { protocolSourceConnectionNumber = iv.conn_number, protocolSourceObjectAddress = iv.address, origin = "supervised" }; Log("MongoDB - ADD " + iv.address + " " + iv.value, LogLevelDebug); listWrites .Add(new UpdateOneModel <rtData>(filt .ToBsonDocument(), update)); if (listWrites.Count >= BulkWriteLimit) { break; } if (stopWatch.ElapsedMilliseconds > 400) { break; } // Log("Write buffer " + listWrites.Count + " Data " + OPCDataQueue.Count); // give time to breath each 250 dequeues //if ((listWrites.Count % 250)==0) //{ // await Task.Delay(10); //Thread.Yield(); //Thread.Sleep(1); //} } if (listWrites.Count > 0) { Log("MongoDB - Bulk writing " + listWrites.Count + ", Total enqueued data " + OPCDataQueue.Count); var bulkWriteResult = await collection.BulkWriteAsync(listWrites); listWrites.Clear(); Log($"MongoDB - OK:{bulkWriteResult.IsAcknowledged} - Inserted:{bulkWriteResult.InsertedCount} - Updated:{bulkWriteResult.ModifiedCount}"); //Thread.Yield(); //Thread.Sleep(1); } if (OPCDataQueue.IsEmpty) { await Task.Delay(250); } }while (true); } catch (Exception e) { Log("Exception Mongo"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); Thread.Sleep(1000); while (OPCDataQueue.Count > DataBufferLimit // do not let data queue grow more than a limit ) { Log("MongoDB - Dequeue Data", LogLevelDetailed); OPC_Value iv; OPCDataQueue.TryDequeue(out iv); } } }while (true); }
// This process monitor and updates redundancy control of the driver instance in mongodb static async void ProcessRedundancyMongo(JSONSCADAConfig jsConfig) { do { try { var lastActiveNodeKeepAliveTimeTag = DateTime.MinValue; var countKeepAliveUpdates = 0; var countKeepAliveUpdatesLimit = 4; var Client = ConnectMongoClient(jsConfig); var DB = Client.GetDatabase(jsConfig.mongoDatabaseName); // read and process instances configuration var collinsts = DB .GetCollection <protocolDriverInstancesClass >(ProtocolDriverInstancesCollectionName); do { bool isMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(1000); if (!isMongoLive) { throw new Exception("Error on MongoDB connection "); } var collconns = DB .GetCollection <PLC_connection >(ProtocolConnectionsCollectionName); var instances = collinsts .Find(inst => inst.protocolDriver == ProtocolDriverName && inst.protocolDriverInstanceNumber == ProtocolDriverInstanceNumber) .ToList(); var foundinstance = false; foreach (protocolDriverInstancesClass inst in instances) { foundinstance = true; if (!inst.enabled) { Log("Instance disabled!"); Environment.Exit(-1); } var nodefound = false; foreach (var name in inst.nodeNames) { if (JSConfig.nodeName == name) { nodefound = true; } } if (!nodefound) { Log("Node '" + JSConfig.nodeName + "' not found in instances configuration!"); Environment.Exit(-1); } if (inst.activeNodeName == JSConfig.nodeName) { if (!Active) // will go active { Log("Redundancy - ACTIVATING this Node!"); } Active = true; countKeepAliveUpdates = 0; } else { if (Active) // will go inactive { // wait a random time Log("Redundancy - DEACTIVATING this Node (other node active)!"); countKeepAliveUpdates = 0; Random rnd = new Random(); Thread.Sleep(rnd.Next(1000, 5000)); } Active = false; if (lastActiveNodeKeepAliveTimeTag == inst.activeNodeKeepAliveTimeTag) { countKeepAliveUpdates++; } lastActiveNodeKeepAliveTimeTag = inst.activeNodeKeepAliveTimeTag; if (countKeepAliveUpdates > countKeepAliveUpdatesLimit) { // time exceeded, be active Log("Redundancy - ACTIVATING this Node!"); Active = true; } } if (Active) { Log("Redundancy - This node is active."); // update keep alive time var filter1 = Builders <protocolDriverInstancesClass> .Filter .Eq(m => m.protocolDriver, ProtocolDriverName); var filter2 = Builders <protocolDriverInstancesClass> .Filter .Eq(m => m.protocolDriverInstanceNumber, ProtocolDriverInstanceNumber); var filter = Builders <protocolDriverInstancesClass> .Filter .And(filter1, filter2); var update = Builders <protocolDriverInstancesClass> .Update .Set(m => m.activeNodeName, JSConfig.nodeName) .Set(m => m.activeNodeKeepAliveTimeTag, DateTime.Now); var options = new FindOneAndUpdateOptions <protocolDriverInstancesClass, protocolDriverInstancesClass >(); options.IsUpsert = false; await collinsts .FindOneAndUpdateAsync(filter, update, options); // update statistics for connections // foreach (PLC_connection srv in IEC10Xconns) // { // if (!(srv.connection is null)) // { // var stats = srv.connection.GetStatistics(); // var filt = // new BsonDocument(new BsonDocument("protocolConnectionNumber", // srv.protocolConnectionNumber)); // var upd = // new BsonDocument("$set", new BsonDocument{ // {"stats", new BsonDocument{ // { "nodeName", JSConfig.nodeName }, // { "timeTag", BsonDateTime.Create(DateTime.Now) }, // { "isConnected", BsonBoolean.Create(srv.connection.IsRunning) }, // { "rcvdMsgCounter", BsonDouble.Create(stats.RcvdMsgCounter) }, // { "sentMsgCounter", BsonDouble.Create(stats.SentMsgCounter) }, // { "rcvdTestFrActCounter", BsonDouble.Create(stats.RcvdTestFrActCounter) }, // { "rcvdTestFrConCounter", BsonDouble.Create(stats.RcvdTestFrConCounter) } // }}, // }); // var res = collconns.UpdateOneAsync(filt, upd); // } // } } else { if (inst.activeNodeName != "") { Log("Redundancy - This node is INACTIVE! Node '" + inst.activeNodeName + "' is active, wait..."); } else { Log("Redundancy - This node is INACTIVE! No node is active, wait..."); } } break; // process just first result } if (!foundinstance) { if (Active) // will go inactive { // wait a random time Log("Redundancy - DEACTIVATING this Node (no instance found)!"); countKeepAliveUpdates = 0; Random rnd = new Random(); Thread.Sleep(rnd.Next(1000, 5000)); } Active = false; } Thread.Sleep(5000); }while (true); } catch (Exception e) { Log("Exception Mongo"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); System.Threading.Thread.Sleep(3000); } }while (true); }
// This process updates acquired values in the mongodb collection for realtime data static async void ProcessMongo(JSONSCADAConfig jsConfig) { do { try { var Client = ConnectMongoClient(jsConfig); var DB = Client.GetDatabase(jsConfig.mongoDatabaseName); var collection = DB.GetCollection <rtData>(RealtimeDataCollectionName); var collection_cmd = DB .GetCollection <rtCommand>(CommandsQueueCollectionName); var listWrites = new List <WriteModel <rtData> >(); do { bool isMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(1000); if (!isMongoLive) { throw new Exception("Error on MongoDB connection "); } PLC_Value iv; while (PLCDataQueue.TryDequeue(out iv)) { //below code will update one record of the data var update = new BsonDocument { { "$set", new BsonDocument { { "sourceDataUpdate", new BsonDocument { { "valueAtSource", BsonValue .Create(iv.value) }, { "valueStringAtSource", BsonValue .Create(iv.value.ToString()) }, { "asduAtSource", BsonValue .Create(iv.asdu.ToString()) }, { "causeOfTransmissionAtSource", BsonValue.Create(iv.cot.ToString()) }, { "timeTag", BsonValue .Create(iv.time_tag) }, { "notTopicalAtSource", BsonValue .Create(false) }, { "invalidAtSource", BsonValue .Create(false) }, { "overflowAtSource", BsonValue .Create(false) }, { "blockedAtSource", BsonValue .Create(false) }, { "substitutedAtSource", BsonValue .Create(false) } } } } } }; var filt = new rtFilt { protocolSourceConnectionNumber = iv.conn_number, protocolSourceCommonAddress = iv.common_address, protocolSourceObjectAddress = iv.address }; Log("MongoDB - ADD " + iv.address + " " + iv.value, LogLevelDetailed); listWrites .Add(new UpdateOneModel <rtData>(filt .ToBsonDocument(), update)); } if (listWrites.Count > 0) { Log("MongoDB - Bulk write " + listWrites.Count); var bulkWriteResult = await collection.BulkWriteAsync(listWrites); listWrites.Clear(); } else { Thread.Sleep(100); } }while (true); } catch (Exception e) { Log("Exception Mongo"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); System.Threading.Thread.Sleep(3000); while (PLCDataQueue.Count > DataBufferLimit // do not let data queue grow more than a limit ) { Log("Dequeue Data", LogLevelDetailed); PLC_Value iv; PLCDataQueue.TryDequeue(out iv); } } }while (true); }
// This process updates acquired values in the mongodb collection for realtime data static async void ProcessMongo(JSONSCADAConfig jsConfig) { do { try { var Client = ConnectMongoClient(jsConfig); var DB = Client.GetDatabase(jsConfig.mongoDatabaseName); var collection = DB.GetCollection <rtData>(RealtimeDataCollectionName); var collection_cmd = DB .GetCollection <rtCommand>(CommandsQueueCollectionName); var listWrites = new List <WriteModel <rtData> >(); do { bool isMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(1000); if (!isMongoLive) { throw new Exception("Error on MongoDB connection "); } DNP3_Value iv; while (DNP3DataQueue.TryDequeue(out iv)) { DateTime tt = DateTime.MinValue; BsonValue bsontt = BsonNull.Value; try { if (iv.hasSourceTimestamp) { bsontt = BsonValue.Create(iv.sourceTimestamp); } } catch { tt = DateTime.MinValue; bsontt = BsonNull.Value; } string valueString; if (iv.valueString == "") { valueString = iv.value.ToString(); } else { valueString = iv.valueString; } //below code will update one record of the data var update = new BsonDocument { { "$set", new BsonDocument { { "sourceDataUpdate", new BsonDocument { { "valueAtSource", BsonValue .Create(iv.value) }, { "valueStringAtSource", BsonValue .Create(valueString) }, { "valueJSONAtSource", iv.valueBSON }, { "asduAtSource", BsonValue .Create(iv.group.ToString() + " " + iv.variation.ToString()) }, { "causeOfTransmissionAtSource", BsonValue.Create(iv.cot.ToString()) }, { "timeTagAtSource", bsontt }, { "timeTagAtSourceOk", BsonValue .Create(iv.timeStampQuality == TimestampQuality.SYNCHRONIZED) }, { "timeTag", BsonValue .Create(iv.serverTimestamp) }, { "notTopicalAtSource", BsonValue .Create(iv.qCommLost) }, { "invalidAtSource", BsonValue .Create(iv.qCommLost || iv.qReferenceError || !iv.qOnline) }, { "overflowAtSource", BsonValue .Create(iv.qOverrange) }, { "blockedAtSource", BsonValue .Create(!iv.qOnline) }, { "substitutedAtSource", BsonValue .Create(iv.qRemoteForced || iv.qLocalForced) }, { "carryAtSource", BsonValue .Create(iv.qRollover) }, { "transientAtSource", BsonValue .Create(iv.qTransient) }, { "originator", BsonValue .Create(ProtocolDriverName + "|" + iv.conn_number) } } } } } }; var filt = new rtFilt { protocolSourceConnectionNumber = iv.conn_number, protocolSourceCommonAddress = iv.base_group, protocolSourceObjectAddress = iv.address }; Log("MongoDB - ADD - Connection: " + iv.conn_number + " Group: " + iv.base_group + " Address: " + iv.address + " Value: " + iv.value, LogLevelDetailed); listWrites .Add(new UpdateOneModel <rtData>(filt .ToBsonDocument(), update)); } if (listWrites.Count > 0) { Log("MongoDB - Bulk write " + listWrites.Count); var bulkWriteResult = await collection.BulkWriteAsync(listWrites); listWrites.Clear(); } else { Thread.Sleep(100); } }while (true); } catch (Exception e) { Log("Exception Mongo"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); System.Threading.Thread.Sleep(3000); while (DNP3DataQueue.Count > DataBufferLimit // do not let data queue grow more than a limit ) { Log("Dequeue Data", LogLevelDetailed); DNP3_Value iv; DNP3DataQueue.TryDequeue(out iv); } } }while (true); }
// This process watches (via change stream) for commands inserted to a commands collection // When the command is considered valid it is forwarded to the RTU static async void ProcessMongoCmd(JSONSCADAConfig jsConfig) { do { try { var Client = ConnectMongoClient(jsConfig); var DB = Client.GetDatabase(jsConfig.mongoDatabaseName); var collection = DB .GetCollection <rtCommand>(CommandsQueueCollectionName); bool isMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(1000); if (!isMongoLive) { throw new Exception("Error on connection " + jsConfig.mongoConnectionString); } Log("MongoDB CMD CS - Start listening for commands via changestream..."); var filter = "{ operationType: 'insert' }"; var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <rtCommand > >().Match(filter); using (var cursor = await collection.WatchAsync(pipeline)) { await cursor .ForEachAsync(async change => { if (!Active) { return; } // process change event, only process inserts if ( change.OperationType == ChangeStreamOperationType.Insert ) { // consider only commands for this driver { Log("MongoDB CMD CS - Looking for connection " + change .FullDocument .protocolSourceConnectionNumber + "..."); var found = false; foreach (IEC10X_connection srv in IEC10Xconns ) { if ( srv.protocolConnectionNumber == change .FullDocument .protocolSourceConnectionNumber ) { found = true; if ( srv.connection.IsRunning && srv.commandsEnabled ) { InformationObject sc = BuildInfoObj(System .Convert .ToInt32(change .FullDocument .protocolSourceASDU), System .Convert .ToInt32(change .FullDocument .protocolSourceObjectAddress), System .Convert .ToDouble(change .FullDocument .value), System .Convert .ToBoolean(change .FullDocument .protocolSourceCommandUseSBO), System .Convert .ToByte(change .FullDocument .protocolSourceCommandDuration)); if (sc != null) { if ( DateTime .Now .ToLocalTime() .Subtract(change .FullDocument .timeTag .ToLocalTime( )) .Seconds < 10 ) { // execute srv .connection .SendControlCommand(CauseOfTransmission .ACTIVATION, System .Convert .ToInt32(change .FullDocument .protocolSourceCommonAddress), sc); Log("MongoDB CMD CS - " + srv.name + " - " + sc.ToString() + " OA " + change .FullDocument .protocolSourceObjectAddress + " Delivered"); // update as delivered var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument("$set", new BsonDocument("delivered", true)); var result = await collection .UpdateOneAsync(filter, update); } else { // update as expired Log("MongoDB CMD CS - " + srv.name + " - " + sc.ToString() + " OA " + change .FullDocument .protocolSourceObjectAddress + " value " + change .FullDocument .value + " Expired"); var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument("$set", new BsonDocument("cancelReason", "expired")); var result = await collection .UpdateOneAsync(filter, update); } } else { // update as canceled (asdu not implemented) Log("MongoDB CMD CS - " + srv.name + " - " + " OA " + change .FullDocument .protocolSourceObjectAddress + " value " + change .FullDocument .value + " ASDU Not Implemented"); var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument("$set", new BsonDocument("cancelReason", "asdu not implemented")); var result = await collection .UpdateOneAsync(filter, update); } } else { // update as canceled (not connected) Log("MongoDB CMD CS - " + srv.name + " OA " + change .FullDocument .protocolSourceObjectAddress + " value " + change.FullDocument.value + ( srv.commandsEnabled ? " Not Connected" : " Commands Disabled" )); var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument("$set", new BsonDocument("cancelReason", ( srv .commandsEnabled ? "not connected" : "commands disabled" ))); var result = await collection .UpdateOneAsync(filter, update); } break; } } if (!found) { // update as canceled command (not found) Log("MongoDB CMD CS - " + change .FullDocument .protocolSourceConnectionNumber .ToString() + " OA " + change .FullDocument .protocolSourceObjectAddress + " value " + change.FullDocument.value + " Not Found"); var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument("$set", new BsonDocument("cancelReason", "connection not found")); var result = await collection .UpdateOneAsync(filter, update); } } } }); } } catch (Exception e) { Log("Exception MongoCmd"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); System.Threading.Thread.Sleep(3000); } }while (true); }
// This process updates acquired values in the mongodb collection for realtime data static async void ProcessMongo(JSONSCADAConfig jsConfig) { do { try { var Client = ConnectMongoClient(jsConfig); var DB = Client.GetDatabase(jsConfig.mongoDatabaseName); var collection = DB.GetCollection <rtData>(RealtimeDataCollectionName); var collection_cmd = DB .GetCollection <rtCommand>(CommandsQueueCollectionName); var listWrites = new List <WriteModel <rtData> >(); do { bool isMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(1000); if (!isMongoLive) { throw new Exception("Error on MongoDB connection "); } IEC_CmdAck ia; while (IECCmdAckQueue.TryDequeue(out ia)) { var filter1 = Builders <rtCommand> .Filter .Eq(m => m.protocolSourceConnectionNumber, ia.conn_number); var filter2 = Builders <rtCommand> .Filter .Eq(m => m.protocolSourceObjectAddress, ia.object_address); var filter = Builders <rtCommand> .Filter .And(filter1, filter2); var update = Builders <rtCommand> .Update .Set(m => m.ack, ia.ack) .Set(m => m.ackTimeTag, ia.ack_time_tag); // sort by priority then by insert order var sort = Builders <rtCommand> .Sort.Descending("$natural"); var options = new FindOneAndUpdateOptions <rtCommand, rtCommand >(); options.IsUpsert = false; options.Sort = sort; await collection_cmd .FindOneAndUpdateAsync(filter, update, options); } IEC_Value iv; while (IECDataQueue.TryDequeue(out iv)) { DateTime tt = DateTime.MinValue; BsonValue bsontt = BsonNull.Value; try { if (iv.hasSourceTimestampCP24) { var dtnow = DateTime.Now; tt = new DateTime( dtnow.Year, dtnow.Month, dtnow.Day, dtnow.Hour, iv.sourceTimestampCP24.Minute, iv.sourceTimestampCP24.Second, iv.sourceTimestampCP24.Millisecond, DateTimeKind.Local); bsontt = BsonValue.Create(tt); } else if (iv.hasSourceTimestampCP56) { tt = new DateTime(iv .sourceTimestampCP56 .Year + 2000, iv.sourceTimestampCP56.Month, iv.sourceTimestampCP56.DayOfMonth, iv.sourceTimestampCP56.Hour, iv.sourceTimestampCP56.Minute, iv.sourceTimestampCP56.Second, iv.sourceTimestampCP56.Millisecond, DateTimeKind.Local); bsontt = BsonValue.Create(tt); } } catch { tt = DateTime.MinValue; bsontt = BsonNull.Value; } //below code will update one record of the data var update = new BsonDocument { { "$set", new BsonDocument { { "sourceDataUpdate", new BsonDocument { { "valueAtSource", BsonValue .Create(iv.value) }, { "valueStringAtSource", BsonValue .Create(iv.value.ToString()) }, { "asduAtSource", BsonValue .Create(iv.asdu.ToString()) }, { "causeOfTransmissionAtSource", BsonValue.Create(iv.cot.ToString()) }, { "timeTagAtSource", bsontt }, { "timeTagAtSourceOk", BsonValue .Create(!iv .sourceTimestampCP56 .Invalid) }, { "timeTag", BsonValue .Create(iv .serverTimestamp) }, { "notTopicalAtSource", BsonValue .Create(iv .quality .NonTopical) }, { "invalidAtSource", BsonValue .Create(iv .quality .Invalid) }, { "overflowAtSource", BsonValue .Create(iv .quality. Overflow ) }, { "blockedAtSource", BsonValue .Create(iv .quality .Blocked) }, { "substitutedAtSource", BsonValue .Create(iv .quality .Substituted) }, { "originator", BsonValue .Create(ProtocolDriverName + "|" + iv.conn_number) } } } } } }; var filt = new rtFilt { protocolSourceConnectionNumber = iv.conn_number, protocolSourceCommonAddress = iv.common_address, protocolSourceObjectAddress = iv.address }; Log("MongoDB - ADD " + iv.address + " " + iv.value, LogLevelDetailed); listWrites .Add(new UpdateOneModel <rtData>(filt .ToBsonDocument(), update)); } if (listWrites.Count > 0) { Log("MongoDB - Bulk write " + listWrites.Count); var bulkWriteResult = await collection.BulkWriteAsync(listWrites); listWrites.Clear(); } else { Thread.Sleep(100); } }while (true); } catch (Exception e) { Log("Exception Mongo"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); System.Threading.Thread.Sleep(3000); while (IECDataQueue.Count > DataBufferLimit // do not let data queue grow more than a limit ) { Log("Dequeue Data", LogLevelDetailed); IEC_Value iv; IECDataQueue.TryDequeue(out iv); } } }while (true); }
// This process watches (via change stream) for commands inserted to a commands collection // When the command is considered valid it is forwarded to the RTU static async void ProcessMongoCmd(JSONSCADAConfig jsConfig) { do { try { var Client = ConnectMongoClient(jsConfig); var DB = Client.GetDatabase(jsConfig.mongoDatabaseName); var collection = DB .GetCollection <rtCommand>(CommandsQueueCollectionName); bool isMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(1000); if (!isMongoLive) { throw new Exception("Error on connection " + jsConfig.mongoConnectionString); } Log("MongoDB CMD CS - Start listening for commands via changestream..."); var filter = "{ operationType: 'insert' }"; var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <rtCommand > >().Match(filter); using (var cursor = await collection.WatchAsync(pipeline)) { await cursor .ForEachAsync(async change => { if (!Active) { return; } // process change event, only process inserts if ( change.OperationType == ChangeStreamOperationType.Insert ) { // consider only commands for this driver { Log("MongoDB CMD CS - Looking for connection " + change .FullDocument .protocolSourceConnectionNumber + "..."); var found = false; foreach (DNP3_connection srv in DNP3conns ) { if ( srv.protocolConnectionNumber == change .FullDocument .protocolSourceConnectionNumber ) { found = true; var cs = srv.channel.GetChannelStatistics(); if ( srv.isConnected && srv.commandsEnabled ) { var group = change.FullDocument.protocolSourceCommonAddress; var variation = change.FullDocument.protocolSourceASDU; if (group == 41 || group == 12) { if ( // check for command expired DateTime .Now .ToLocalTime() .Subtract(change .FullDocument .timeTag .ToLocalTime( )) .Seconds < 10 ) { // can execute System.Threading.Tasks.Task <CommandTaskResult> cmdTask = null; if (group == 12) { OperationType ot = OperationType.NUL; TripCloseCode tc = TripCloseCode.NUL; switch (System.Convert.ToUInt16(change.FullDocument.protocolSourceCommandDuration)) { default: case 0: ot = OperationType.NUL; break; case 1: if (change.FullDocument.value != 0) { ot = OperationType.PULSE_ON; } else { ot = OperationType.PULSE_OFF; } break; case 2: if (change.FullDocument.value != 0) { ot = OperationType.PULSE_OFF; } else { ot = OperationType.PULSE_ON; } break; case 3: if (change.FullDocument.value != 0) { ot = OperationType.LATCH_ON; } else { ot = OperationType.LATCH_OFF; } break; case 4: if (change.FullDocument.value != 0) { ot = OperationType.LATCH_OFF; } else { ot = OperationType.LATCH_ON; } break; case 11: if (change.FullDocument.value != 0) { ot = OperationType.PULSE_ON; tc = TripCloseCode.CLOSE; } else { ot = OperationType.PULSE_OFF; tc = TripCloseCode.TRIP; } break; case 13: if (change.FullDocument.value != 0) { ot = OperationType.LATCH_ON; tc = TripCloseCode.CLOSE; } else { ot = OperationType.LATCH_OFF; tc = TripCloseCode.TRIP; } break; case 21: if (change.FullDocument.value != 0) { ot = OperationType.PULSE_ON; tc = TripCloseCode.TRIP; } else { ot = OperationType.PULSE_OFF; tc = TripCloseCode.CLOSE; } break; case 23: if (change.FullDocument.value != 0) { ot = OperationType.LATCH_ON; tc = TripCloseCode.TRIP; } else { ot = OperationType.LATCH_OFF; tc = TripCloseCode.CLOSE; } break; } ControlRelayOutputBlock crob = new ControlRelayOutputBlock(ot, tc, false, 1, 0, 0); if (System.Convert.ToBoolean(change.FullDocument.protocolSourceCommandUseSBO)) { cmdTask = srv.master.SelectAndOperate( crob, System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } else { cmdTask = srv.master.DirectOperate( crob, System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } } else if (group == 41 && variation == 1) { if (System.Convert.ToBoolean(change.FullDocument.protocolSourceCommandUseSBO)) { cmdTask = srv.master.SelectAndOperate( new AnalogOutputInt32(System.Convert.ToInt32(change.FullDocument.value)), System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } else { cmdTask = srv.master.DirectOperate( new AnalogOutputInt32(System.Convert.ToInt32(change.FullDocument.value)), System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } } else if (group == 41 && variation == 2) { if (System.Convert.ToBoolean(change.FullDocument.protocolSourceCommandUseSBO)) { cmdTask = srv.master.SelectAndOperate( new AnalogOutputInt16(System.Convert.ToInt16(change.FullDocument.value)), System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } else { cmdTask = srv.master.DirectOperate( new AnalogOutputInt16(System.Convert.ToInt16(change.FullDocument.value)), System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } } else if (group == 41 && variation == 3) { if (System.Convert.ToBoolean(change.FullDocument.protocolSourceCommandUseSBO)) { cmdTask = srv.master.SelectAndOperate( new AnalogOutputFloat32(System.Convert.ToSingle(change.FullDocument.value)), System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } else { cmdTask = srv.master.DirectOperate( new AnalogOutputFloat32(System.Convert.ToSingle(change.FullDocument.value)), System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } } else if (group == 41 && variation == 4) { if (System.Convert.ToBoolean(change.FullDocument.protocolSourceCommandUseSBO)) { cmdTask = srv.master.SelectAndOperate( new AnalogOutputDouble64(System.Convert.ToDouble(change.FullDocument.value)), System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } else { cmdTask = srv.master.DirectOperate( new AnalogOutputDouble64(System.Convert.ToDouble(change.FullDocument.value)), System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } } else if (group == 41) // group 41, other variations defaults to float32 { if (System.Convert.ToBoolean(change.FullDocument.protocolSourceCommandUseSBO)) { cmdTask = srv.master.SelectAndOperate( new AnalogOutputFloat32(System.Convert.ToSingle(change.FullDocument.value)), System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } else { cmdTask = srv.master.DirectOperate( new AnalogOutputFloat32(System.Convert.ToSingle(change.FullDocument.value)), System.Convert.ToUInt16(change.FullDocument.protocolSourceObjectAddress), TaskConfig.Default); } } if (cmdTask != null) { _ = cmdTask.ContinueWith((result) => { Console.WriteLine("Result: " + result.Result); Log("MongoDB CMD CS - " + srv.name + " - Command " + " TAG:" + change.FullDocument.tag + " GRP:" + change.FullDocument.protocolSourceCommonAddress + " VAR:" + change.FullDocument.protocolSourceASDU + " OBJ:" + change.FullDocument.protocolSourceObjectAddress + " Value:" + change.FullDocument.value + " Delivered"); // update as delivered var filter = new BsonDocument(new BsonDocument("_id", change.FullDocument.id)); var update = new BsonDocument("$set", new BsonDocument { { "delivered", true }, { "ack", result.Result.Results.FirstOrDefault().PointState == CommandPointState.SUCCESS && result.Result.Results.FirstOrDefault().Status == CommandStatus.SUCCESS }, { "ackTimeTag", BsonValue.Create(DateTime.Now) }, { "resultDescription", result.Result.ToString() } }); var res = collection .UpdateOneAsync(filter, update); }); } else { Console.WriteLine("Command Error"); } } else { // update as expired Log("MongoDB CMD CS - " + srv.name + " - Command " + " TAG:" + change.FullDocument.tag + " GRP:" + change.FullDocument.protocolSourceCommonAddress + " VAR:" + change.FullDocument.protocolSourceASDU + " OBJ:" + change.FullDocument.protocolSourceObjectAddress + " Value:" + change.FullDocument.value + " Expired"); var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument("$set", new BsonDocument("cancelReason", "expired")); var result = await collection .UpdateOneAsync(filter, update); } } else { // update as canceled (asdu not implemented) Log("MongoDB CMD CS - " + srv.name + " - Command " + " TAG:" + change.FullDocument.tag + " GRP:" + change.FullDocument.protocolSourceCommonAddress + " VAR:" + change.FullDocument.protocolSourceASDU + " OBJ:" + change.FullDocument.protocolSourceObjectAddress + " Value:" + change.FullDocument.value + " ASDU Not Implemented"); var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument("$set", new BsonDocument("cancelReason", "asdu not implemented")); var result = await collection .UpdateOneAsync(filter, update); } } else { // update as canceled (not connected) Log("MongoDB CMD CS - " + srv.name + " - Command " + " TAG:" + change.FullDocument.tag + " GRP:" + change.FullDocument.protocolSourceCommonAddress + " VAR:" + change.FullDocument.protocolSourceASDU + " OBJ:" + change.FullDocument.protocolSourceObjectAddress + " Value:" + change.FullDocument.value + change.FullDocument.value + ( srv.commandsEnabled ? " Not Connected" : " Commands Disabled" )); var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument("$set", new BsonDocument("cancelReason", ( srv .commandsEnabled ? "not connected" : "commands disabled" ))); var result = await collection .UpdateOneAsync(filter, update); } break; } } if (!found) { // update as canceled command (not found) Log("MongoDB CMD CS - " + change .FullDocument .protocolSourceConnectionNumber .ToString() + " - Command " + " TAG:" + change.FullDocument.tag + " GRP:" + change.FullDocument.protocolSourceCommonAddress + " VAR:" + change.FullDocument.protocolSourceASDU + " OBJ:" + change.FullDocument.protocolSourceObjectAddress + " Value:" + change.FullDocument.value + " Not Found"); var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument("$set", new BsonDocument("cancelReason", "connection not found")); var result = await collection .UpdateOneAsync(filter, update); } } } }); } } catch (Exception e) { Log("Exception MongoCmd"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); System.Threading.Thread.Sleep(3000); } }while (true); }
// This process watches (via change stream) for point updates // Forward data to its connections static async void ProcessMongoCS(JSONSCADAConfig jsConfig) { do { try { var Client = ConnectMongoClient(jsConfig); var DB = Client.GetDatabase(jsConfig.mongoDatabaseName); var collection = DB .GetCollection <rtData>(RealtimeDataCollectionName); bool isMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(1000); if (!isMongoLive) { throw new Exception("Error on connection " + jsConfig.mongoConnectionString); } Log("MongoDB CMD CS - Start listening for realtime data updates via changestream..."); // observe updates and replaces, avoid updates with sourceDataUpdateField (those are handled by cs_data_processor.js) var filter = "{ $or: [{ $and:[{ 'updateDescription.updatedFields.sourceDataUpdate': { $exists: false } },{ operationType: 'update' }] }, { operationType: 'replace'}] }"; var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <rtData > >().Match(filter); var changeStreamOptions = new ChangeStreamOptions { FullDocument = ChangeStreamFullDocumentOption.UpdateLookup }; using (var cursor = await collection.WatchAsync(pipeline, changeStreamOptions)) { await cursor .ForEachAsync(change => { // process change event, only process updates and replaces if ( change.OperationType == ChangeStreamOperationType.Update || change.OperationType == ChangeStreamOperationType.Replace ) { if (change.FullDocument != null) { if (change.FullDocument.protocolDestinations != null) { foreach (var dst in change.FullDocument.protocolDestinations) { foreach (IEC10X_connection srv in IEC10Xconns ) { if (dst.protocolDestinationConnectionNumber == srv.protocolConnectionNumber) { var conNameStr = srv.name + " - "; var quality = new QualityDescriptor(); quality.Invalid = false; if (change.FullDocument.invalid != null) { quality.Invalid |= change.FullDocument.invalid.ToBoolean(); } if (change.FullDocument.overflow != null) { quality.Invalid |= change.FullDocument.overflow.ToBoolean(); } if (change.FullDocument.transient != null) { quality.Invalid |= change.FullDocument.transient.ToBoolean(); } if (change.FullDocument.substituted != null) { quality.Substituted = change.FullDocument.substituted.ToBoolean(); } quality.Blocked = false; quality.NonTopical = false; CP56Time2a cp56timesrc = null; if (change.FullDocument.timeTagAtSource != null) { cp56timesrc = new CP56Time2a(System.Convert.ToDateTime(change.FullDocument.timeTagAtSource).AddHours(dst.protocolDestinationHoursShift.ToDouble())); cp56timesrc.Invalid = false; if (change.FullDocument.timeTagAtSourceOk != null) { cp56timesrc.Invalid = !change.FullDocument.timeTagAtSourceOk.ToBoolean(); } else { cp56timesrc.Invalid = true; } } var io = BuildInfoObj( dst.protocolDestinationASDU.ToInt32(), dst.protocolDestinationObjectAddress.ToInt32(), change.FullDocument.value.ToDouble(), false, 0, quality, cp56timesrc ); if (io != null) { // queue data to make possible to assemble an ASDU with many elements, will send on DequeueIecInfo InfoCA ica = new InfoCA() { io = io, ca = dst.protocolDestinationCommonAddress.ToInt32() }; srv.infoCAQueue.Enqueue(ica); if (LogLevel >= LogLevelDetailed) { Log(conNameStr + "Spont Tag:" + change.FullDocument.tag + " Value:" + change.FullDocument.value + " Key:" + change.FullDocument._id + " TI:" + dst.protocolDestinationASDU.ToInt32() + " CA:" + dst.protocolDestinationCommonAddress + (cp56timesrc == null ? "" : " " + cp56timesrc.ToString()), LogLevelDetailed); } } } } } } } } }); } } catch (Exception e) { Log("Exception MongoCS"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); Thread.Sleep(3000); } }while (true); }
// This process updates acquired values in the mongodb collection for realtime data static public async void ProcessMongo(JSONSCADAConfig jsConfig) { do { try { var Client = ConnectMongoClient(jsConfig); var DB = Client.GetDatabase(jsConfig.mongoDatabaseName); var collection = DB.GetCollection <rtData>(RealtimeDataCollectionName); var collection_cmd = DB .GetCollection <rtCommand>(CommandsQueueCollectionName); Log("MongoDB Update Thread Started..."); var listWrites = new List <WriteModel <rtData> >(); do { //if (LogLevel >= LogLevelBasic && OPCDataQueue.Count > 0) // Log("MongoDB - Data queue size: " + OPCDataQueue.Count, LogLevelBasic); // Log("1"); bool isMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(1000); if (!isMongoLive) { throw new Exception("Error on MongoDB connection "); } // Log("2"); IEC_CmdAck ia; if (OPCCmdAckQueue.Count > 0) { while (OPCCmdAckQueue.TryDequeue(out ia)) { var filter1 = Builders <rtCommand> .Filter .Eq(m => m.protocolSourceConnectionNumber, ia.conn_number); var filter2 = Builders <rtCommand> .Filter .Eq(m => m.protocolSourceObjectAddress, ia.object_address); var filter = Builders <rtCommand> .Filter .And(filter1, filter2); var update = Builders <rtCommand> .Update .Set(m => m.ack, ia.ack) .Set(m => m.ackTimeTag, ia.ack_time_tag); // sort by priority then by insert order var sort = Builders <rtCommand> .Sort.Descending("$natural"); var options = new FindOneAndUpdateOptions <rtCommand, rtCommand >(); options.IsUpsert = false; options.Sort = sort; await collection_cmd .FindOneAndUpdateAsync(filter, update, options); } } // Log("3"); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); OPC_Value iv; while (!OPCDataQueue.IsEmpty && OPCDataQueue.TryPeek(out iv) && OPCDataQueue.TryDequeue(out iv)) { // Log("3.1"); DateTime tt = DateTime.MinValue; BsonValue bsontt = BsonNull.Value; try { if (iv.hasSourceTimestamp) { bsontt = BsonValue.Create(iv.sourceTimestamp); } } catch { tt = DateTime.MinValue; bsontt = BsonNull.Value; } BsonDocument valJSON = new BsonDocument(); try { valJSON = BsonDocument.Parse(iv.valueJson); } catch (Exception e) { Log(iv.conn_name + " - " + e.Message); } // Log("3.2"); if (iv.selfPublish) { string tag = TagFromOPCParameters(iv); if (!InsertedTags.Contains(tag)) { // look for the tag var task = await collection.FindAsync <rtData>(new BsonDocument { { "tag", TagFromOPCParameters(iv) } }); List <rtData> list = await task.ToListAsync(); // await Task.Delay(10); //Thread.Yield(); //Thread.Sleep(1); InsertedTags.Add(tag); if (list.Count == 0) { Log(iv.conn_name + " - INSERT - " + iv.address); // hash to create keys var id = HashStringToInt(iv.address); var insert = newRealtimeDoc(iv, id); int conn_index = 0; // normal for loop for (int index = 0; index < OPCUAconns.Count; index++) { if (OPCUAconns[index].protocolConnectionNumber == iv.conn_number) { conn_index = index; } } insert.protocolSourcePublishingInterval = OPCUAconns[conn_index].autoCreateTagPublishingInterval; insert.protocolSourceSamplingInterval = OPCUAconns[conn_index].autoCreateTagSamplingInterval; insert.protocolSourceQueueSize = OPCUAconns[conn_index].autoCreateTagQueueSize; listWrites .Add(new InsertOneModel <rtData>(insert)); } } } //below code will update one record of the data var update = new BsonDocument { { "$set", new BsonDocument { { "sourceDataUpdate", new BsonDocument { { "valueBsonAtSource", valJSON }, { "valueAtSource", BsonDouble .Create(iv.value) }, { "valueStringAtSource", BsonString .Create(iv.valueString) }, { "asduAtSource", BsonString .Create(iv.asdu.ToString()) }, { "causeOfTransmissionAtSource", BsonString.Create(iv.cot.ToString()) }, { "timeTagAtSource", bsontt }, { "timeTagAtSourceOk", BsonBoolean .Create(iv.hasSourceTimestamp) }, { "timeTag", BsonValue .Create(iv .serverTimestamp) }, { "notTopicalAtSource", BsonBoolean .Create(false) }, { "invalidAtSource", BsonBoolean .Create(!iv .quality ) }, { "overflowAtSource", BsonBoolean .Create(false) }, { "blockedAtSource", BsonBoolean .Create(false) }, { "substitutedAtSource", BsonBoolean .Create(false) } } } } } }; var filt = new rtFilt { protocolSourceConnectionNumber = iv.conn_number, protocolSourceCommonAddress = iv.common_address, protocolSourceObjectAddress = iv.address }; Log("MongoDB - ADD " + iv.address + " " + iv.value, LogLevelDebug); // Log("3.3"); listWrites .Add(new UpdateOneModel <rtData>(filt .ToBsonDocument(), update)); if (listWrites.Count >= BulkWriteLimit) { break; } if (stopWatch.ElapsedMilliseconds > 400) { break; } // Log("3.4 - Write buffer " + listWrites.Count + " Data " + OPCDataQueue.Count); // give time to breath each 250 dequeues //if ((listWrites.Count % 250)==0) //{ // await Task.Delay(10); //Thread.Yield(); //Thread.Sleep(1); //} } // Log("4"); if (listWrites.Count > 0) { Log("MongoDB - Bulk write " + listWrites.Count + " Data " + OPCDataQueue.Count); var bulkWriteResult = await collection.BulkWriteAsync(listWrites); listWrites.Clear(); //Thread.Yield(); //Thread.Sleep(1); } if (OPCDataQueue.IsEmpty) { await Task.Delay(250); } // Log("6"); }while (true); } catch (Exception e) { Log("Exception Mongo"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); Thread.Sleep(1000); while (OPCDataQueue.Count > DataBufferLimit // do not let data queue grow more than a limit ) { Log("MongoDB - Dequeue Data", LogLevelDetailed); OPC_Value iv; OPCDataQueue.TryDequeue(out iv); } } }while (true); }
// This process watches (via change stream) for commands inserted to a commands collection // When the command is considered valid it is forwarded to the RTU static async void ProcessMongoCmd(JSONSCADAConfig jsConfig) { do { try { var Client = ConnectMongoClient(jsConfig); var DB = Client.GetDatabase(jsConfig.mongoDatabaseName); var collection = DB .GetCollection <rtCommand>(CommandsQueueCollectionName); bool isMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(1000); if (!isMongoLive) { throw new Exception("Error on connection " + jsConfig.mongoConnectionString); } Log("MongoDB CMD CS - Start listening for commands via changestream..."); var filter = "{ operationType: 'insert' }"; var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <rtCommand > >().Match(filter); using (var cursor = await collection.WatchAsync(pipeline)) { await cursor .ForEachAsync(async change => { if (!Active) { return; } // process change event, only process inserts if ( change.OperationType == ChangeStreamOperationType.Insert ) { Log("MongoDB CMD CS - Looking for connection " + change .FullDocument .protocolSourceConnectionNumber + "..."); var found = false; foreach (OPCUA_connection srv in OPCUAconns ) { if ( srv.protocolConnectionNumber == change .FullDocument .protocolSourceConnectionNumber ) { found = true; int timeDif = DateTime .Now .ToLocalTime() .Subtract(change .FullDocument .timeTag .ToLocalTime( )) .Seconds; // test for command expired if (timeDif > 10) { // update as expired Log("MongoDB CMD CS - " + srv.name + " - " + " Address " + change .FullDocument .protocolSourceObjectAddress + " value " + change .FullDocument .value + " Expired, " + timeDif + " Seconds old"); var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument("$set", new BsonDocument("cancelReason", "expired")); var result = await collection .UpdateOneAsync(filter, update); break; } if ( srv.connection.session.Connected && srv.commandsEnabled ) { WriteValueCollection nodesToWrite = new WriteValueCollection(); WriteValue WriteVal = new WriteValue(); WriteVal.NodeId = new NodeId(System .Convert .ToString(change.FullDocument.protocolSourceObjectAddress)); WriteVal.AttributeId = Attributes.Value; WriteVal.Value = new DataValue(); switch (change.FullDocument.protocolSourceASDU.ToString().ToLower()) { case "boolean": WriteVal.Value.Value = System.Convert.ToBoolean(System .Convert.ToDouble(change.FullDocument.value) != 0.0); break; case "sbyte": WriteVal.Value.Value = System .Convert .ToSByte(change.FullDocument.value); break; case "byte": WriteVal.Value.Value = System .Convert .ToByte(change.FullDocument.value); break; case "int16": WriteVal.Value.Value = System .Convert .ToInt16(change.FullDocument.value); break; case "uint16": WriteVal.Value.Value = System .Convert .ToUInt16(change.FullDocument.value); break; case "int32": WriteVal.Value.Value = System .Convert .ToInt16(change.FullDocument.value); break; case "uint32": WriteVal.Value.Value = System .Convert .ToUInt16(change.FullDocument.value); break; case "int64": WriteVal.Value.Value = System .Convert .ToInt16(change.FullDocument.value); break; case "uint64": WriteVal.Value.Value = System .Convert .ToUInt16(change.FullDocument.value); break; case "float": WriteVal.Value.Value = System .Convert .ToSingle(change.FullDocument.value); break; case "double": WriteVal.Value.Value = System .Convert .ToDouble(change.FullDocument.value); break; case "datetime": WriteVal.Value.Value = System .Convert .ToDateTime(change.FullDocument.value); break; case "string": WriteVal.Value.Value = System .Convert .ToString(change.FullDocument.value); break; } nodesToWrite.Add(WriteVal); // Write the node attributes StatusCodeCollection results = null; DiagnosticInfoCollection diagnosticInfos; Log("MongoDB CMD CS - " + srv.name + " - Writing node..."); // Call Write Service srv.connection.session.Write(null, nodesToWrite, out results, out diagnosticInfos); var okres = false; var resultDescription = ""; if (results.Count > 0) { resultDescription = results[0].ToString(); if (StatusCode.IsGood(results[0])) { okres = true; } } Log("MongoDB CMD CS - " + srv.name + " - " + " Address: " + change .FullDocument .protocolSourceObjectAddress + " - Command delivered - " + results[0].ToString()); // update as delivered var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument { { "$set", new BsonDocument { { "delivered", true }, { "ack", okres }, { "ackTimeTag", new BsonDateTime(DateTime.Now) }, { "resultDescription", resultDescription } } } }; var result = await collection .UpdateOneAsync(filter, update); } else { // update as canceled (not connected) Log("MongoDB CMD CS - " + srv.name + " OA " + change .FullDocument .protocolSourceObjectAddress + " value " + change.FullDocument.value + ( srv.commandsEnabled ? " Not Connected" : " Commands Disabled" )); var filter = new BsonDocument(new BsonDocument("_id", change .FullDocument .id)); var update = new BsonDocument("$set", new BsonDocument("cancelReason", ( srv .commandsEnabled ? "not connected" : "commands disabled" ))); var result = await collection .UpdateOneAsync(filter, update); } break; } } if (!found) { // not for a connection managed by this driver instance, just ignore } } }); } } catch (Exception e) { Log("Exception MongoCmd"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); Thread.Sleep(3000); } }while (true); }