📜 ⬆️ ⬇️

“I am watching you” or how to make SCADA from CADa (MultiCAD.NET API)

Immediately I confess, this article just has a “catchy, promising” title :)
But in fact , this week I had little free time and therefore this time the note will be short and very conceptual. Therefore, it is natural that this SCADA will not come.

In the last article we figured out how to make your user object in the domestic CAD system NanoCAD using the MultiCAD.NET API (both for the paid and for the free version of the program), this time we will use the previously developed object (door) and teach it to monitor the text status the file .

Why do we need this? You are welcome under the cat.
')



Content:

Part I: Introduction.
Part II: We write code under NanoCAD 8.5
Part III: Trying to adapt to the free NanoCAD 5.1.
Part IV: Conclusion

In the beginning, honestly, the idea of ​​using NanoCAD as a base for a simple SCADA system mostly belongs to DRZugrik , I just finally got around to check it out. In order not to upset anyone's expectations, I repeat once again, in this article we will only lift the veil and consider in my opinion a bit non-standard concept of using NanoCAD, no more.

Since picking in the MultiCAD.NET API is my new hobby, we will continue to work with it. If you are not at all familiar with this API, you can see the first article on this topic.
Well, we will use the developments from the second article in particular, our pseudo-three-dimensional door.

In brief, let me remind you, last time we learned to create custom primitives in NanoCAD, and eventually we created a wall and a door, our door was made as a pseudo-three-dimensional object (just lines without solid properties) and knew how to open and close depending on the selected value in the properties . To save time and not invent anything new, we’ll use it.

Let's return to the question: “Why do we need to read data from a text file?”. I answer - reading from a file in some way will be a simulation of working with equipment. Theoretically, it would be possible to read data from a web server or somehow fit the reading from a real piece of hardware, but we will focus on the simplest concept.

Below the spoiler is the full code of the updated class for NanoCAD 8.5 , this code is also available on GitHub , if you suddenly forgot how to create a project for NanoCAD API in MS Visual Studio, here is a brief instruction for NC 8.5 and for NC 5.1 Free

Just in case, I warn you - not a programmer , and I just started to learn the NanoCAD API, so there may be errors in the code or implementation curves of something.

One of the critical errors sometimes occurs when copying objects and trying to reassign monitoring functions (however, errors with copying surfaced in the previous version), and sometimes errors just fly out of the blue. If someone more experienced will tell you what the reason will be grateful.

I also warn you that the doors normally work only if each door has its own unique file, otherwise there may be failures.

Full code for door under NC 8.5
// Version 1.1 //Use Microsoft .NET Framework 4 and MultiCad.NET API 7.0 //Class for demonstrating the capabilities of MultiCad.NET //Assembly for the Nanocad 8.5 SDK is recommended (however, it is may be possible in the all 8. family) //Link imapimgd, mapimgd.dll and mapibasetypes.dll from SDK //Link System.Windows.Forms and System.Drawing //The commands: draws a pseudo 3D door. //This code in the part of non-infringing rights Nanosoft can be used and distributed in any accessible ways. //For the consequences of the code application, the developer is not responsible. // V 1.0. More detailed - https://habrahabr.ru/post/342680/ // V 1.1. More detailed - https://habrahabr.ru/post/343772/ // PS A big thanks to Alexander Vologodsky for help in developing a method for pivoting object. using System; using System.Collections.Generic; using System.ComponentModel; using System.Windows.Forms; using Multicad.Runtime; using Multicad.DatabaseServices; using Multicad.Geometry; using Multicad.CustomObjectBase; using Multicad; //added in V 1.1. for monitoring using System.Security.Permissions; using System.IO; using Multicad.AplicationServices; namespace nanodoor2 { //change "8b0986c0-4163-42a4-b005-187111b499d7" for your Guid from Assembly. // Be careful GUID for door and wall classes must be different! // Otherwise there will be problems with saving and moving [CustomEntity(typeof(DoorPseudo3D), "8b0986c0-4163-42a4-b005-187111b499d9", "DoorPseudo3D", "DoorPseudo3D Sample Entity")] [Serializable] public class DoorPseudo3D : McCustomBase { // First and second vertices of the box private Point3d _pnt1 = new Point3d(0, 0, 0); private double _h = 2085; private Vector3d _vecStraightDirection = new Vector3d(1, 0, 0); private Vector3d _vecDirectionClosed = new Vector3d(1, 0, 0); public enum Status {closed , middle, open }; private Status _dStatus = Status.closed; //added in V 1.1. (monitor fileds) private bool _monitor = false; private string _monFilePath = @"E:\test.txt"; // if it's Serialized you can't copy the object in CAD editor [NonSerialized] private FileSystemWatcher _watcher ; [NonSerialized] private FileSystemEventHandler _watchHandler; [CommandMethod("DrawDoor", CommandFlags.NoCheck | CommandFlags.NoPrefix)] public void DrawDoor() { DoorPseudo3D door = new DoorPseudo3D(); door.PlaceObject(); } public override void OnDraw(GeometryBuilder dc) { dc.Clear(); // Define the basic points for drawing Point3d pnt1 = new Point3d(0, 0, 0); Point3d pnt2 = new Point3d(pnt1.X + 984, pnt1.Y, 0); Point3d pnt3 = new Point3d(pnt2.X + 0, pnt1.Y+50, 0); Point3d pnt4 = new Point3d(pnt1.X , pnt3.Y, 0); // Set the color to ByObject value dc.Color = McDbEntity.ByObject; Vector3d hvec = new Vector3d(0, 0, _h); // Draw the upper and lower sides dc.DrawPolyline(new Point3d[] { pnt1, pnt2, pnt3, pnt4, pnt1 }); dc.DrawPolyline(new Point3d[] { pnt1.Add(hvec), pnt2.Add(hvec), pnt3.Add(hvec), pnt4.Add(hvec), pnt1.Add(hvec)}); // Draw the edges dc.DrawLine(pnt1, pnt1.Add(hvec)); dc.DrawLine(pnt2, pnt2.Add(hvec)); dc.DrawLine(pnt3, pnt3.Add(hvec)); dc.DrawLine(pnt4, pnt4.Add(hvec)); // Drawing a Door Handle dc.DrawLine(pnt2.Add(new Vector3d( - 190, -0, _h*0.45)), pnt2.Add(new Vector3d(-100, 0, _h * 0.45))); dc.DrawLine(pnt3.Add(new Vector3d(-190, 0, _h * 0.45)), pnt3.Add(new Vector3d(-100, 0, _h * 0.45))); // Create contours for the front and rear sides and hatch them // In this demo, we hatch only two sides, you can tailor the others yourself List<Polyline3d> c1 = new List<Polyline3d>(); c1.Add(new Polyline3d( new List<Point3d>() { pnt1, pnt1.Add(hvec), pnt2.Add(hvec), pnt2, pnt1, })); List<Polyline3d> c2 = new List<Polyline3d>(); c2.Add(new Polyline3d( new List<Point3d>() { pnt4, pnt4.Add(hvec), pnt3.Add(hvec), pnt3, pnt4, })); dc.DrawGeometry(new Hatch(c1, "JIS_WOOD", 0, 170, false, HatchStyle.Normal, PatternType.PreDefined, 500), 1); dc.DrawGeometry(new Hatch(c2, "JIS_WOOD", 0, 170, false, HatchStyle.Normal, PatternType.PreDefined, 500), 1); } public override hresult PlaceObject(PlaceFlags lInsertType) { InputJig jig = new InputJig(); // Get the first box point from the jig InputResult res = jig.GetPoint("Select first point:"); if (res.Result != InputResult.ResultCode.Normal) return hresult.e_Fail; _pnt1 = res.Point; // Add the object to the database DbEntity.AddToCurrentDocument(); return hresult.s_Ok; } /// <summary> /// Method for changing the object's SC (the graph is built at the origin of coordinates). /// </ summary> /// <param name = "tfm"> The matrix for changing the position of the object. </ param> /// <returns> True - if the matrix is passed, False - if not. </ returns> public override bool GetECS(out Matrix3d tfm) { // Create a matrix that transforms the object. // The object is drawn in coordinates(0.0), then it is transformed with the help of this matrix. tfm = Matrix3d.Displacement(this._pnt1.GetAsVector()) * Matrix3d.Rotation (-this._vecStraightDirection.GetAngleTo(Vector3d.XAxis, Vector3d.ZAxis), Vector3d.ZAxis, Point3d.Origin); return true; } public override void OnTransform(Matrix3d tfm) { // To be able to cancel(Undo) McUndoPoint undo = new McUndoPoint(); undo.Start(); // Get the coordinates of the base point and the rotation vector this.TryModify(); this._pnt1 = this._pnt1.TransformBy(tfm); this.TryModify(); this._vecStraightDirection = this._vecStraightDirection.TransformBy(tfm); // We move the door only when it is closed if not - undo if (_dStatus == Status.closed) _vecDirectionClosed = _vecStraightDirection; else { MessageBox.Show("Please transform only closed door"); undo.Undo(); } undo.Stop(); } //Define the custom properties of the object [DisplayName("Height")] [Description("Height of door")] [Category("Door options")] public double HDoor { get { return _h; } set { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; _h = value; } } [DisplayName("Door status")] [Description("Door may be: closed, middle, open")] [Category("Door options")] public Status Stat { get { return _dStatus; } set { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; // Change the rotation vector for each of the door states switch (value) { case Status.closed: _vecStraightDirection = _vecDirectionClosed; break; case Status.middle: _vecStraightDirection = _vecDirectionClosed.Add(_vecDirectionClosed.GetPerpendicularVector().Negate() * 0.575) ; break; case Status.open: _vecStraightDirection = _vecDirectionClosed.GetPerpendicularVector()*-1; break; default: _vecStraightDirection = _vecDirectionClosed; break; } _dStatus = value; } } // Create a grip for the base point of the object public override bool GetGripPoints(GripPointsInfo info) { info.AppendGrip(new McSmartGrip<DoorPseudo3D>(_pnt1, (obj, g, offset) => { obj.TryModify(); obj._pnt1 += offset; })); return true; } //Define the monitoring custom properties , added v. 1.1: // added in v. 1.1 [DisplayName("Monitoring")] [Description("Monitoring of file for door")] [Category("Monitoring")] public bool Monitor { get { return _monitor; } set { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; _monitor = value; if (_monitor) { StartMonitoring(); } else StopMonitoring(); } } // added in v. 1.1 [DisplayName("File path for Monitoring")] [Description("Monitoring of file for door")] [Category("Monitoring")] public string MonitoringFilPath { get { return _monFilePath; } set { //for hot change filename if (Monitor) { StopMonitoring(); if (!TryModify()) return; _monFilePath = value; StartMonitoring(); McContext.ShowNotification("Monitored file is changed"); } else { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; _monFilePath = value; } } } //Define the methods, added v. 1.1: // added in v. 1.1 [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] public void StartMonitoring() { _watcher = new FileSystemWatcher(); if (File.Exists(_monFilePath)) { _watcher.Path = Path.GetDirectoryName(_monFilePath); _watcher.Filter = Path.GetFileName(_monFilePath); _watchHandler = new FileSystemEventHandler(OnChanged); _watcher.Changed += _watchHandler; _watcher.EnableRaisingEvents = true; } else McContext.ShowNotification("File: " + _monFilePath + " " + "not Exists"); } // added in v. 1.1 public void StopMonitoring() { if (_watcher != null & _watchHandler != null) { _watcher.Changed -= _watchHandler; _watcher.EnableRaisingEvents = false; } } // added in v. 1.1 private void OnChanged(object source, FileSystemEventArgs e) { McContext.ShowNotification("File: " + e.FullPath + " " + e.ChangeType); //read new value from file try { if (File.Exists(_monFilePath)) { int mStatus = -1; McContext.ShowNotification("File exists "); using (StreamReader sr = new StreamReader(new FileStream(_monFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) { if (sr.BaseStream.CanRead) { McContext.ShowNotification("can read "); if (int.TryParse(sr.ReadLine(), out mStatus)) { McContext.ShowNotification("parse correct "); if (Enum.IsDefined(typeof(Status), mStatus)) { if (!TryModify()) return; Stat = (Status) mStatus; if (!TryModify()) return; if (!DbEntity.Update()) return; McContext.ShowNotification("Door state is changed"); McContext.ExecuteCommand("REGENALL"); } else McContext.ShowNotification("Incorrect data in the file. Should be in diapason: 0, 1, 2 "); } } else McContext.ShowNotification("Can't read file "); } } else McContext.ShowNotification("File not exists "); _watcher.EnableRaisingEvents = false; // disable tracking } finally { _watcher.EnableRaisingEvents = true; // reconnect tracking } } } // TODO: There are many shortcomings in this code. // Including failures when working with copying, moving objects and saving files, you can improve it if you want. } 


Let us briefly analyze some new points (regarding the previous article).

 //added in V 1.1. for monitoring using System.Security.Permissions; using System.IO; using Multicad.AplicationServices; 

New namespaces. We will need to monitor files, read from a file, and also just to display messages in the program console.

 //added in V 1.1. for monitoring //added in V 1.1. (monitor fileds) private bool _monitor = false; private string _monFilePath = @"E:\test.txt"; FileSystemEventHandler _watchHandler; FileSystemWatcher _watcher; 

New types and class fields for properties that allow you to track the status of the door.

  //Define the monitoring custom properties , added v. 1.1: // added in v. 1.1 [DisplayName("Monitoring")] [Description("Monitoring of file for door")] [Category("Monitoring")] public bool Monitor { get { return _monitor; } set { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; _monitor = value; if (_monitor) { StartMonitoring(); } else StopMonitoring(); } } // added in v. 1.1 [DisplayName("File path for Monitoring")] [Description("Monitoring of file for door")] [Category("Monitoring")] public string MonitoringFilPath { get { return _monFilePath; } set { //for hot change filename if (Monitor) { StopMonitoring(); if (!TryModify()) return; _monFilePath = value; StartMonitoring(); McContext.ShowNotification("Monitored file is changed"); } else { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; _monFilePath = value; } } } 


The first property is responsible for enabling and disabling file tracking.
Attention! I did not implement the reading of data from the file when this option is enabled, the real synchronization of the object will begin after the first change of the read file.

The second property contains the address to the destination file (we track one file for one object, and unique or will fail). If file monitoring is turned on, when this property changes, monitoring to a new file switches automatically.

Let's go further

 //Define the methods, added v. 1.1: // added in v. 1.1 [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] public void StartMonitoring() { _watcher = new FileSystemWatcher(); if (File.Exists(_monFilePath)) { _watcher.Path = Path.GetDirectoryName(_monFilePath); _watcher.Filter = Path.GetFileName(_monFilePath); _watchHandler = new FileSystemEventHandler(OnChanged); _watcher.Changed += _watchHandler; _watcher.EnableRaisingEvents = true; } else McContext.ShowNotification("File: " + _monFilePath + " " + "not Exists"); } // added in v. 1.1 public void StopMonitoring() { if (_watcher != null & _watchHandler != null) { _watcher.Changed -= _watchHandler; _watcher.EnableRaisingEvents = false; } } 

The first method using the usual .NET library allows us to subscribe to system information about a file change.

The second method - unsubscribe us from tracking.
[PermissionSet (SecurityAction.Demand, Name = "FullTrust")] like most of the method code taken from Microsoft's example . Decided not to touch.

Remained the last piece of the updated code.

 // added in v. 1.1 private void OnChanged(object source, FileSystemEventArgs e) { McContext.ShowNotification("File: " + e.FullPath + " " + e.ChangeType); //read new value from file try { if (File.Exists(_monFilePath)) { int mStatus = -1; McContext.ShowNotification("File exists "); using (StreamReader sr = new StreamReader(new FileStream(_monFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) { if (sr.BaseStream.CanRead) { if (int.TryParse(sr.ReadLine(), out mStatus)) { if (Enum.IsDefined(typeof(Status), mStatus)) { if (!this.TryModify()) return; this.Stat = (Status)mStatus; if (!TryModify()) return; this.DbEntity.Update(); McContext.ExecuteCommand("REGENALL"); McContext.ShowNotification("Door state is changed"); } else McContext.ShowNotification("Incorrect data in the file. Should be in diapason: 0, 1, 2 "); } } else McContext.ShowNotification("Can't read file "); } } else McContext.ShowNotification("File not exists "); _watcher.EnableRaisingEvents = false; // disable tracking } finally { _watcher.EnableRaisingEvents = true; // reconnect tracking } } 

Method to respond to an event. First, we check if our file exists, then we try to pull out the first line from it, and if it matches the enumeration value (in the range from 0 to 2), then we change our property that controls the state of the door, then execute the standard screen update command, because without it Changes are not always displayed.


For the free version of Nanocad 5.1 , the code is almost the same.

Full code for door under NC 5.1
 // Version 1.1 //Use Microsoft .NET Framework 3.5 and old version of MultiCad.NET (for NC 5.1) //Class for demonstrating the capabilities of MultiCad.NET //Assembly for the Nanocad 5.1 //Link mapimgd.dll from Nanocad SDK //Link System.Windows.Forms and System.Drawing //upd: for version 1.1 also link .NET API: hostdbmg.dll, hostmgd.dll //The commands: draws a pseudo 3D door. //This code in the part of non-infringing rights Nanosoft can be used and distributed in any accessible ways. //For the consequences of the code application, the developer is not responsible. // V 1.0. More detailed - https://habrahabr.ru/post/342680/ // V 1.1. More detailed - https://habrahabr.ru/post/343772/ // PS A big thanks to Alexander Vologodsky for help in developing a method for pivoting object. using System; using System.ComponentModel; using System.Windows.Forms; using Multicad.Runtime; using Multicad.DatabaseServices; using Multicad.Geometry; using Multicad.CustomObjectBase; using Multicad; //added in V 1.1. for monitoring using System.Security.Permissions; using System.IO; using Multicad.AplicationServices; using HostMgd.ApplicationServices; using HostMgd.EditorInput; namespace nanodoor2 { //change "8b0986c0-4163-42a4-b005-187111b499d7" for your Guid from Assembly. // Be careful GUID for door and wall classes must be different! // Otherwise there will be problems with saving and moving [CustomEntity(typeof(DoorPseudo3D_nc51), "b4edac1f-7978-483f-91b1-10503d20735b", "DoorPseudo3D_nc51", "DoorPseudo3D_nc51 Sample Entity")] [Serializable] public class DoorPseudo3D_nc51 : McCustomBase { // First and second vertices of the box private Point3d _pnt1 = new Point3d(0, 0, 0); private double _scale = 1000; private double _h = 2085; private Vector3d _vecStraightDirection = new Vector3d(1, 0, 0); private Vector3d _vecDirectionClosed = new Vector3d(1, 0, 0); public enum Status {closed , middle, open}; private Status _dStatus = Status.closed; //added in V 1.1. (monitor fileds) public enum Mon { off, on}; private Mon _monitor = Mon.off; private string _monFilePath = @"E:\test.txt"; // if it is serialized, you may not be able to copy the object in the CAD editor [NonSerialized] private FileSystemWatcher _watcher; [NonSerialized] private FileSystemEventHandler _watchHandler; [CommandMethod("DrawDoor", CommandFlags.NoCheck | CommandFlags.NoPrefix)] public void DrawDoor() { DoorPseudo3D_nc51 door = new DoorPseudo3D_nc51(); door.PlaceObject(); this.TryModify(); // this.Monitor = false; } public override void OnDraw(GeometryBuilder dc) { dc.Clear(); // Define the basic points for drawing Point3d pnt1 = new Point3d(0, 0, 0); Point3d pnt2 = new Point3d(pnt1.X + (984 * _scale), pnt1.Y, 0); Point3d pnt3 = new Point3d(pnt2.X + 0, pnt1.Y+(50 * _scale), 0); Point3d pnt4 = new Point3d(pnt1.X , pnt3.Y, 0) ; // Set the color to ByObject value dc.Color = McDbEntity.ByObject; Vector3d hvec = new Vector3d(0, 0, _h * _scale) ; // Draw the upper and lower sides dc.DrawPolyline(new Point3d[] { pnt1, pnt2, pnt3, pnt4, pnt1 }); dc.DrawPolyline(new Point3d[] { pnt1.Add(hvec), pnt2.Add(hvec), pnt3.Add(hvec), pnt4.Add(hvec), pnt1.Add(hvec)}); // Draw the edges dc.DrawLine(pnt1, pnt1.Add(hvec)); dc.DrawLine(pnt2, pnt2.Add(hvec)); dc.DrawLine(pnt3, pnt3.Add(hvec)); dc.DrawLine(pnt4, pnt4.Add(hvec)); // Drawing a Door Handle dc.DrawLine(pnt2.Add(new Vector3d( -190 * _scale, -0, _h*0.45 * _scale)), pnt2.Add(new Vector3d(-100 * _scale, 0, _h * 0.45 * _scale))); dc.DrawLine(pnt3.Add(new Vector3d(-190 * _scale, 0, _h * 0.45 * _scale)), pnt3.Add(new Vector3d(-100 * _scale, 0, _h * 0.45 * _scale))); } public override hresult PlaceObject(PlaceFlags lInsertType) { InputJig jig = new InputJig(); // Get the first box point from the jig InputResult res = jig.GetPoint("Select first point:"); if (res.Result != InputResult.ResultCode.Normal) return hresult.e_Fail; _pnt1 = res.Point; Stat = Status.closed; // Add the object to the database DbEntity.AddToCurrentDocument(); // added in v.1. return hresult.s_Ok; } /// <summary> /// Method for changing the object's SC (the graph is built at the origin of coordinates). /// </ summary> /// <param name = "tfm"> The matrix for changing the position of the object. </ param> /// <returns> True - if the matrix is passed, False - if not. </ returns> public override bool GetECS(out Matrix3d tfm) { // Create a matrix that transforms the object. // The object is drawn in coordinates(0.0), then it is transformed with the help of this matrix. tfm = Matrix3d.Displacement(this._pnt1.GetAsVector()) * Matrix3d.Rotation (-this._vecStraightDirection.GetAngleTo(Vector3d.XAxis, Vector3d.ZAxis), Vector3d.ZAxis, Point3d.Origin); return true; } public override void OnTransform(Matrix3d tfm) { // To be able to cancel(Undo) McUndoPoint undo = new McUndoPoint(); undo.Start(); // Get the coordinates of the base point and the rotation vector this.TryModify(); this._pnt1 = this._pnt1.TransformBy(tfm); this.TryModify(); this._vecStraightDirection = this._vecStraightDirection.TransformBy(tfm); // We move the door only when it is closed if not - undo if (_dStatus == Status.closed) _vecDirectionClosed = _vecStraightDirection; else { MessageBox.Show("Please transform only closed door (when its status = 0)"); undo.Undo(); } undo.Stop(); } //Define the custom properties of the object [DisplayName("Height")] [Description("Height of door")] [Category("Door options")] public double HDoor { get { return _h; } set { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; _h = value; } } [DisplayName("DScale")] [Description("Door Scale")] [Category("Door options")] public double DScale { get { return _scale; } set { if (!TryModify()) return; _scale = value; } } [DisplayName("Door status")] [Description("0-closed, 1-midle, 2-open")] [Category("Door options")] public Status Stat { get { return _dStatus; } set { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; // Change the rotation vector for each of the door states switch (value) { case Status.closed: _vecStraightDirection = _vecDirectionClosed; break; case Status.middle: _vecStraightDirection = _vecDirectionClosed.Add(_vecDirectionClosed.GetPerpendicularVector().Negate() * 0.575) ; break; case Status.open: _vecStraightDirection = _vecDirectionClosed.GetPerpendicularVector()*-1; break; default: break; } _dStatus = value; } } // Create a grip for the base point of the object public override bool GetGripPoints(GripPointsInfo info) { info.AppendGrip(new McSmartGrip<DoorPseudo3D_nc51>(_pnt1, (obj, g, offset) => { obj.TryModify(); obj._pnt1 += offset; })); return true; } //Define the monitoring custom properties , added v. 1.1: // added in v. 1.1 [DisplayName("Monitoring")] [Description("Monitoring of file for door")] [Category("Monitoring")] public Mon Monitor { get { return _monitor; } set { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; _monitor = value; if (_monitor==Mon.on) { StartMonitoring(); } else StopMonitoring(); // if (_monitor) // { // StartMonitoring(); // }// Get the command line editor // else StopMonitoring(); } } // added in v. 1.1 [DisplayName("File path for Monitoring")] [Description("Monitoring of file for door")] [Category("Monitoring")] public string MonitoringFilPath { get { return _monFilePath; } set { // Get the command line editor DocumentCollection dm = HostMgd.ApplicationServices.Application.DocumentManager; Editor ed = dm.MdiActiveDocument.Editor; //for hot change filename if (Monitor==Mon.on) { StopMonitoring(); if (!TryModify()) return; _monFilePath = value; StartMonitoring(); ed.WriteMessage("Monitored file is changed"); } else { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; _monFilePath = value; } } } //Define the methods, added v. 1.1: // added in v. 1.1 [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] public void StartMonitoring() { DocumentCollection dm = HostMgd.ApplicationServices.Application.DocumentManager; Editor ed = dm.MdiActiveDocument.Editor; _watcher = new FileSystemWatcher(); if (File.Exists(_monFilePath)) { _watcher.Path = Path.GetDirectoryName(_monFilePath); _watcher.Filter = Path.GetFileName(_monFilePath); _watchHandler = new FileSystemEventHandler(OnChanged); _watcher.Changed += _watchHandler; _watcher.EnableRaisingEvents = true; } else ed.WriteMessage("File: " + _monFilePath + " " + "not Exists"); } // added in v. 1.1 public void StopMonitoring() { if (_watcher != null & _watchHandler != null) { _watcher.Changed -= _watchHandler; _watcher.EnableRaisingEvents = false; } } // added in v. 1.1 private void OnChanged(object source, FileSystemEventArgs e) { DocumentCollection dm = HostMgd.ApplicationServices.Application.DocumentManager; Editor ed = dm.MdiActiveDocument.Editor; ed.WriteMessage("File: " + e.FullPath + " " + e.ChangeType); //read new value from file try { if (File.Exists(_monFilePath)) { int mStatus = -1; ed.WriteMessage("File exists "); using (StreamReader sr = new StreamReader(new FileStream(_monFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) { if (sr.BaseStream.CanRead) { if (int.TryParse(sr.ReadLine(), out mStatus)) { if (Enum.IsDefined(typeof(Status), mStatus)) { if (!TryModify()) return; Stat = (Status)mStatus; //  if (!TryModify()) return; DbEntity.Update(); McContext.ExecuteCommand("REGENALL"); ed.WriteMessage("Door state is changed"); } else ed.WriteMessage("Incorrect data in the file. Should be in diapason: 0, 1, 2 "); } } else ed.WriteMessage("Can't read file "); } } else ed.WriteMessage("File not exists "); _watcher.EnableRaisingEvents = false; // disable tracking } finally { _watcher.EnableRaisingEvents = true; // reconnect tracking } } } // TODO: There are many shortcomings in this code. // Including failures when working with copying, moving objects and saving files, you can improve it if you want. } 


The difference mainly lies in the fact that McContext.ShowNotification () has not yet been implemented in the old version of the MultiCAD.NET API, so we include the usual .NET API (the hostdbmg.dll and hostmgd.dll libraries). Then we replace this method with a bundle:

  DocumentCollection dm = HostMgd.ApplicationServices.Application.DocumentManager; Editor ed = dm.MdiActiveDocument.Editor; ed.WriteMessage(" ..."); 


Where the first two lines allow access to the input console, and the last one also displays a text message in it.

As a result, we get what is in the picture.



The upper figures for the NC 8.5 version and the lower ones for the NC 5.1 objects are shown both in pseudo-three-dimensional and in two-dimensional form.


Let's sum up.

The example considered here is terribly failing on the one hand, on the other hand it does not take into account the interaction with the server, but somehow this simple example conceptually shows the potential of NanoCAD as a platform for a simple SCADA.

You can do a lot more, add dialogs to acknowledge changes to objects, work with iron, differentiate access levels, and so on. Thanks to the fine possibilities of customizing the user environment, theoretically, the platform can be used as an editor to display the SCADA system, and as a user environment, that is, remove all drawing tools and leave only a window for monitoring and control.

I certainly don’t urge to do a Wonderware InTouch competitor based on NanoCAD, but five years ago I remember the unpretentious SCIDA “Algorithm” from Bolide (it actually exists now , I just didn’t know how it developed in 5 years) and I think An analogue of such a thing (the version that was 5 years ago) could be written based on the NanoCAD platform.

Maybe I will come back to the SCADA topic on the basis of NanoCADa, but for now I will be glad to read constructive comments and of course to wish everyone a good work week!

Source: https://habr.com/ru/post/343772/


All Articles