private async void OnNotification(MonitoredItem item, MonitoredItemNotificationEventArgs e) { //MonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification; //Console.WriteLine("Notification Received for Variable \"{0}\" and Value = {1} type {2}.", item.DisplayName, notification.Value, notification.TypeId); foreach (var value in item.DequeueValues()) { if (value != null) { string tp = "unknown"; try { if (value.WrappedValue.TypeInfo != null) { tp = value.WrappedValue.TypeInfo.BuiltInType.ToString(); // Log(conn_name + " - " + item.ResolvedNodeId + "TYPE: " + tp, LogLevelDetailed); } else { Log(conn_name + " - " + item.ResolvedNodeId + " TYPE: ?????", LogLevelDetailed); } Log(conn_name + " - " + item.ResolvedNodeId + " " + item.DisplayName + " " + value.Value + " " + value.SourceTimestamp + " " + value.StatusCode, LogLevelDetailed); if (value.Value != null) { Double dblValue = 0.0; string strValue = ""; try { if (tp == "Variant") { try { dblValue = System.Convert.ToDouble(value.Value); } catch { try { dblValue = System.Convert.ToInt64(value.Value); } catch { try { dblValue = System.Convert.ToInt32(value.Value); } catch { dblValue = 0; try { var opt = new JsonSerializerOptions { NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString }; strValue = JsonSerializer.Serialize(value.Value, opt); } catch { strValue = value.Value.ToString(); } } } } } else if (tp == "DateTime") { dblValue = ((DateTimeOffset)System.Convert.ToDateTime(value.Value)).ToUnixTimeMilliseconds(); strValue = System.Convert.ToDateTime(value.Value).ToString("o"); } else { dblValue = System.Convert.ToDouble(value.Value); strValue = value.Value.ToString(); } } catch (Exception excpt) { strValue = value.Value.ToString(); } var options = new JsonSerializerOptions { NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString }; OPC_Value iv = new OPC_Value() { valueJson = JsonSerializer.Serialize(value, options), selfPublish = true, address = item.ResolvedNodeId.ToString(), asdu = tp, isDigital = true, value = dblValue, valueString = strValue, hasSourceTimestamp = value.SourceTimestamp != DateTime.MinValue, sourceTimestamp = value.SourceTimestamp, serverTimestamp = DateTime.Now, quality = StatusCode.IsGood(value.StatusCode), cot = 3, conn_number = conn_number, conn_name = conn_name, common_address = "", display_name = item.DisplayName }; OPCDataQueue.Enqueue(iv); } } catch (Exception excpt) { Log(conn_name + " - " + excpt.Message); Log(conn_name + " - " + "TYPE:" + tp); Log(conn_name + " - " + item.ResolvedNodeId + " " + item.DisplayName + " " + value.Value + " " + value.SourceTimestamp + " " + value.StatusCode); } } else { Log(conn_name + " - " + item.ResolvedNodeId + " " + item.DisplayName + " NULL VALUE!", LogLevelDetailed); } Thread.Yield(); Thread.Sleep(1); //if ((OPCDataQueue.Count % 50) == 0) //{ // await Task.Delay(200); //} } }
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 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); }