I´m looking for a way to activate the configuration and update the boot project via C#.
My Twincat 3 project is compiled and all necessary file are in the /_Boot folder.
Next step is a C# programm (actually unit tests) that loads and executes the project on my PLC.
So far I have read through Beckhoff Information System, but couldn´t find any hint.
You need the Twincat Automation Interface API in order to activate your configuration and start the PLC.
An example from the official documentation:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EnvDTE100;
using System.IO;
using TCatSysManagerLib;
namespace ActivatePreviousConfiguration{
class Program
{
static void Main(string[] args)
{
Type t = System.Type.GetTypeFromProgID("VisualStudio.DTE.10.0");
EnvDTE.DTE dte = (EnvDTE.DTE)System.Activator.CreateInstance(t);
dte.SuppressUI = false;
dte.MainWindow.Visible = true;
EnvDTE.Solution sol = dte.Solution;
sol.Open(#"C:\Temp\SolutionFolder\MySolution1\MySolution1.sln");
EnvDTE.Project pro = sol.Projects.Item(1);
ITcSysManager sysMan = pro.Object;
sysMan.ActivateConfiguration();
sysMan.StartRestartTwinCAT();
}
}
}
There are also many other things you can do with this api, for example generate code for your PLC..
You can find the documentation here:
Automation Interface pdf
If you only have the _Boot folder at your disposal, you just have to copy the content of _Boot\TwinCAT RT(x64)\Plc to your target boot folder C:\TwinCAT\3.1\Boot\Plc and start the PLC via ADS-Command.
The PLC will boot with the replaced compiled project.
Here an example from the official ADS-Documentation for starting the plc:
static void Main(string[] args)
{
//Create a new instance of class TcAdsClient
TcAdsClient tcClient = new TcAdsClient();
try
{
// Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 Port=851
tcClient.Connect(851);
Console.WriteLine(" PLC Run\t[R]");
Console.WriteLine(" PLC Stop\t[S]");
Console.WriteLine("\r\nPlease choose \"Run\" or \"Stop\" and confirm with enter..");
string sInput = Console.ReadLine().ToLower();
//Process user input and apply chosen state
do{
switch (sInput)
{
case "r": tcClient.WriteControl(new StateInfo(AdsState.Run, tcClient.ReadState().DeviceState)); break;
case "s": tcClient.WriteControl(new StateInfo(AdsState.Stop, tcClient.ReadState().DeviceState)); break;
default: Console.WriteLine("Please choose \"Run\" or \"Stop\" and confirm with enter.."); sInput = Console.ReadLine().ToLower(); break;
}
} while (sInput != "r" && sInput != "s");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
}
finally
{
tcClient.Dispose();
}
}
Related
I'm attempting to make a C# script to send CPU temp and usage statistics to a raspberry pi (it's an LED cube project). I tried to use Python to do it, but the library it used, psutil, does not support sensor readings on Windows.
I'm using the OpenHardwareMonitorLib dll file to try and get the CPU stats. owever, it throws an error won the lines "Computer computer = new Computer(); computer.Open();". The error is:
"System.MissingMethodException: 'Method not found: 'System.Threading.Mutex System.Threading.Mutex.OpenExisting(System.String, System.Security.AccessControl.MutexRights)'.'"
I've tried everything I can think of and everything I have found on google. I can't remember them all, but these are some of them:
Installing Powershell 7
Adding the SystemManagement.dll file to the project (which got me past the previous error to this one)
Installing the newest .NET 4.8 and all of the extras around it.
Added a requirement to run as Admin to the app manifest.
I've put the code on Github (https://github.com/verdammte/led_cube), but here's the C# code in question:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using OpenHardwareMonitor.Hardware;
namespace Get_CPU_Temp5
{
class Program
{
public class UpdateVisitor : IVisitor
{
public void VisitComputer(IComputer computer)
{
computer.Traverse(this);
}
public void VisitHardware(IHardware hardware)
{
hardware.Update();
foreach (IHardware subHardware in hardware.SubHardware) subHardware.Accept(this);
}
public void VisitSensor(ISensor sensor) { }
public void VisitParameter(IParameter parameter) { }
}
static void GetSystemInfo()
{
UpdateVisitor updateVisitor = new UpdateVisitor();
Computer computer = new Computer();
computer.Open();
computer.CPUEnabled = true;
computer.Accept(updateVisitor);
for (int i = 0; i < computer.Hardware.Length; i++)
{
if (computer.Hardware[i].HardwareType == HardwareType.CPU)
{
for (int j = 0; j < computer.Hardware[i].Sensors.Length; j++)
{
if (computer.Hardware[i].Sensors[j].SensorType == SensorType.Temperature)
Console.WriteLine(computer.Hardware[i].Sensors[j].Name + ":" + computer.Hardware[i].Sensors[j].Value.ToString() + "\r");
}
}
}
computer.Close();
}
static void Main(string[] args)
{
while (true)
{
GetSystemInfo();
}
}
}
}
All I want to do is get the CPU usage and CPU temperature and send them to a remote IP. I feel like this shouldn't be this hard.
Based off your repository it looks like your console app is targeting .NET Core but that .dll you are referencing is in .NET Framework. Try creating your project in .NET Framework 4.5+.
I have made a console application with which I need to see system information.
When I run the application, I can only see the following on the console:
Usage: sysinfo <cpu|win|net|host|user>
Press any key to continue . . .
I have written this program as console application (.net core), I don't know why I cannot see the information about my system?
My code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SystemInfo
{
class Program
{
class SysInfo
{
public string win, net, cpu;
public string hostname, username;
public SysInfo()
{
net = Environment.Version.ToString();
win = Environment.OSVersion.ToString();
cpu = Environment.ProcessorCount.ToString();
hostname = Environment.MachineName.ToString();
username = Environment.UserName.ToString();
}
}
static void Main(string[] args)
{
string p;
SysInfo info = new SysInfo();
if (args.Length > 0) p = args[0];
else p = "null";
switch (p)
{
case "cpu":
Console.WriteLine("CPU count: {0}", info.cpu);
break;
case "win":
Console.WriteLine("Windows Version: {0}", info.win);
break;
case "net":
Console.WriteLine(".NET Version: {0}", info.net);
break;
case "host":
Console.WriteLine("Hostname: {0}", info.hostname);
break;
case "user":
Console.WriteLine("Username: {0}", info.username);
break;
default:
Console.WriteLine("Usage: sysinfo <cpu|win|net|host|user>");
break;
}
}
}
}
If you run this in debug (via visual studio) you will need to pass the args.
Go to Project-> Properties. Then click on the Debug tab,
and fill in your arguments in the textbox called Command line
arguments.
refference
If you run the actual compiled exe file, then simply add the desired argument.
For example:
c:\>appname.exe cpu
c:\>appname.exe win
c:\>appname.exe user
....
The console output is the expected behaviour for your application. To get system information, you need to pass a parameter, e.g. like this sysinfo cpu
Edit: If you want to read the strings from the Console you could do it e.g. in a loop
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SystemInfo
{
class Program
{
class SysInfo
{
public string win, net, cpu;
public string hostname, username;
public SysInfo()
{
net = Environment.Version.ToString();
win = Environment.OSVersion.ToString();
cpu = Environment.ProcessorCount.ToString();
hostname = Environment.MachineName.ToString();
username = Environment.UserName.ToString();
}
}
static void Main()
{
string p;
SysInfo info = new SysInfo();
while (true)
{
p = Console.ReadLine();
switch (p)
{
case "cpu":
Console.WriteLine("CPU count: {0}", info.cpu);
break;
case "win":
Console.WriteLine("Windows Version: {0}", info.win);
break;
case "net":
Console.WriteLine(".NET Version: {0}", info.net);
break;
case "host":
Console.WriteLine("Hostname: {0}", info.hostname);
break;
case "user":
Console.WriteLine("Username: {0}", info.username);
break;
default:
Console.WriteLine("Usage: sysinfo <cpu|win|net|host|user>");
break;
}
}
}
}
}
On a sidenote, I'd then also add a case for exiting the loop.
You are reading args when the program starts. Did you forget to pass arguments to your app? Your switch condition is not matched with one of your cases, then it prints out as default case.
You can pass arguments in Visual Studio as answered before: https://stackoverflow.com/a/3697320/3678882
i just started to develop applications for AutoCAD 2016. I want to load my dLLs into a separate AppDomain, so that i don't have to restart ACAD all the time.
After a lot of research and trying i ended up with a pipeline solution
using System.Addin and System.Addin.Contract.
I use only interfaces and standardclasses for the Views Contract and Adapters like in this example here.
This is my addin containing one methode to write Hello into Acad's Editor and a second methode for drawing a line.
using System.AddIn;
using CADAddinView;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
namespace CADAddIn
{
[AddIn("cadAddIn", Version = "1.0.0.0")]
public class CADAddIn : ICADAddinView
{
public void drawLine()
{
Document acDoc = Autodesk.AutoCAD.ApplicationServices.Core.Application.DocumentManager.MdiActiveDocument;
Database acCurDb = acDoc.Database;
using (DocumentLock acLckDoc = acDoc.LockDocument())
{
using (Transaction acTrans = acCurDb.TransactionManager.StartTransaction())
{
DBObject blkTbl = acTrans.GetObject(acCurDb.BlockTableId, OpenMode.ForRead);
BlockTable acBlkTbl = blkTbl as BlockTable;
BlockTableRecord acBlkTblRec = (BlockTableRecord)acTrans.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
Polyline acPoly = new Polyline();
acPoly.SetDatabaseDefaults();
acPoly.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0);
acPoly.AddVertexAt(0, new Point2d(100, 100), 0, 0, 0);
acBlkTblRec.AppendEntity(acPoly);
acTrans.AddNewlyCreatedDBObject(acPoly, true);
acTrans.Commit();
}
}
}
public void sayHello()
{
Editor ed = Autodesk.AutoCAD.ApplicationServices.Core.Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("Hello");
}
}
}
this is my HostApplication:
using System.AddIn.Hosting;
using System.Windows.Forms;
using CADHostView;
using System;
using System.Collections.ObjectModel;
using Autodesk.AutoCAD.Runtime;
namespace CADHost
{
public class CADHost
{
[CommandMethod("sayHello")]
public static void sayHello()
{
string addInPath = Environment.CurrentDirectory + "\\Pipeline";
string[] warnings = AddInStore.Update(addInPath);
foreach (string warning in warnings)
{
MessageBox.Show(warning);
}
Collection<AddInToken> tokens = AddInStore.FindAddIns(typeof(ICADHostView), addInPath);
if (tokens.Count == 0)
{
MessageBox.Show("No AddIn found.");
}
else
{
AddInToken cadToken = tokens[0];
ICADHostView cadApp = cadToken.Activate<ICADHostView>(AddInSecurityLevel.Host);
cadApp.sayHello();
}
}
[CommandMethod("drawLine")]
public static void drawLine()
{
string addInPath = Environment.CurrentDirectory + "\\Pipeline";
string[] warnings = AddInStore.Update(addInPath);
foreach (string warning in warnings)
{
MessageBox.Show(warning);
}
Collection<AddInToken> tokens = AddInStore.FindAddIns(typeof(ICADHostView), addInPath);
if (tokens.Count == 0)
{
MessageBox.Show("No AddIn found.");
}
else
{
AddInToken cadToken = tokens[0];
ICADHostView cadApp = cadToken.Activate<ICADHostView>(AddInSecurityLevel.Host);
cadApp.drawLine();
}
}
}
}
Both of the two applications reference to three standard-Dlls from Acad:
accoremgd.dll, acdbmgd.dll, acmgd.dll.
In both projects these dlls have the option local copy false.
If i start then i get an Exception, where the programm cannot find the file "accoremgd.dll" and Acad crashes.
So i tried to set the Option local copy true only for the Addin.
Now it works for the "sayHello"-Methode.
but i get an invalide cast exception when acBlkTbl is initialised.
Would be great if someone has the last steps for me to make this work.
Also great would be a working example must not be made with the Addinsystem
i only want to make this work for not restarting acad all the time^^
Thank you for your help
matthias
I don't believe a separate AppDomain will work, when you call AutoCAD object types it will go to the main AppDomain and get messed up...
As just want to edit your code and don't restart, you'll be better with Edit & Continue feature (available since VC2013 on AutoCAD 2015, I believe).
This is not supported. AutoCAD is a very old and complex program and most of the AutoCAD API objects cannot be used in remote fashion.
Please read:
http://through-the-interface.typepad.com/through_the_interface/2008/09/tired-of-not-be.html
http://forums.autodesk.com/t5/net/netload-is-there-a-net-unload-command/td-p/2404002
https://www.theswamp.org/index.php?topic=38675.0
In the #3, you can see that the AutoCAD development team confirmed that there are some global variables which will prevent working this way.
I gave up my tries to solve this problem. My current "best" solution is to load dlls at the start of AutoCAD. At least i don't have to netload every dll.
If someone has a better solution feel free to tell me^^ Thanks to all that answered. matthias
I am getting this issue: No connection could be made because the target machine actively refused it 127.0.0.1:3310.
using System;
using System.Linq;
using nClam;
class Program
{
static void Main(string[] args)
{
var clam = new ClamClient("localhost", 3310);
var scanResult = clam.ScanFileOnServer(#"C:\inetpub\wwwroot\rarextract\parts\eicar_com.zip"); //any file you would like!
switch(scanResult.Result)
{
case ClamScanResults.Clean:
Console.WriteLine("The file is clean!");
break;
case ClamScanResults.VirusDetected:
Console.WriteLine("Virus Found!");
Console.WriteLine("Virus name: {0}", scanResult.InfectedFiles.First().VirusName);
break;
case ClamScanResults.Error:
Console.WriteLine("Woah an error occured! Error: {0}", scanResult.RawResult);
break;
}
}
}
It looks like you do not have the ClamWin service installed or running on the machine that is executing your code. I wrote some directions a to install it here: http://architectryan.com/2011/05/19/nclam-a-dotnet-library-to-virus-scan/.
I have a library that handles reading and writing a cache file. This library is used by a Windows Service and several instances of a console application on the same machine. The console application runs when a user logs in.
I am getting occasional IO errors saying the cache file is in use by another process. I assume that collisions are occurring between the different application instances and service trying to read and write at the same time.
Is there a way to lock the file when it is in use and force all other requests to "wait in line" to access the file?
private void SaveCacheToDisk(WindowsUser user) {
string serializedCache = SerializeCache(_cache);
//encryt
serializedCache = AES.Encrypt(serializedCache);
string path = user == null ? ApplicationHelper.CacheDiskPath() :
_registry.GetCachePath(user);
string appdata = user == null ? ApplicationHelper.ClientApplicationDataFolder() :
_registry.GetApplicationDataPath(user);
if (Directory.Exists(appdata) == false) {
Directory.CreateDirectory(appdata);
}
if (File.Exists(path) == false) {
using (FileStream stream = File.Create(path)) { }
}
using (FileStream stream = File.Open(path, FileMode.Truncate)) {
using (StreamWriter writer = new StreamWriter(stream)) {
writer.Write(serializedCache);
}
}
}
private string ReadCacheFromDisk(WindowsUser user) {
//cache file path
string path = user == null ? ApplicationHelper.CacheDiskPath() :
_registry.GetCachePath(user);
using (FileStream stream = File.Open(path, FileMode.Open)) {
using (StreamReader reader = new StreamReader(stream)) {
string serializedCache = reader.ReadToEnd();
//decrypt
serializedCache = AES.Decrypt(serializedCache);
return serializedCache;
}
}
}
Sure, you could use a mutex and permit access only when holding the mutex.
You could use a cross-process EventWaitHandle. This lets you create and use a WaitHandle that's identified across processes by name. A thread is notified when it's its turn, does some work, and then indicates it's done allowing another thread to proceed.
Note that this only works if every process/thread is referring to the same named WaitHandle.
The EventWaitHandle constructors with strings in their signature create named system synchronization events.
One option you could consider is having the console applications route their file access through the service, that way there's only one process accessing the file and you can synchronise access to it there.
One way of implementing this is by remoting across an IPC channel (and here's another example from weblogs.asp.net). We used this technique in a project for the company I work for and it works well, with our specific case providing a way for a .net WebService to talk to a Windows Service running on the same machine.
Sample based on the weblogs.asp.net example
Basically what you need to do with the code below is create a Solution, add two Console Apps (one called "Server" and the other called "Client" and one Library to it. Add a reference to the Library to both console apps, paste the code below in and add a reference to System.Runtime.Remoting to both Server & Console.
Run the Server app, then run the client app. Observe the fact that the server app has a message passed to it by the client. You can extend this to any number of messages/tasks
// Server:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
namespace RemotingSample
{
public class Server
{
public Server()
{
}
public static int Main(string[] args)
{
IpcChannel chan = new IpcChannel("Server");
//register channel
ChannelServices.RegisterChannel(chan, false);
//register remote object
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RemotingSample.RemoteObject),
"RemotingServer",
WellKnownObjectMode.SingleCall);
Console.WriteLine("Server Activated");
Console.ReadLine();
return 0;
}
}
}
// Client:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using RemotingSample;
namespace RemotingSample
{
public class Client
{
public Client()
{
}
public static int Main(string[] args)
{
IpcChannel chan = new IpcChannel("Client");
ChannelServices.RegisterChannel(chan);
RemoteObject remObject = (RemoteObject)Activator.GetObject(
typeof(RemotingSample.RemoteObject),
"ipc://Server/RemotingServer");
if (remObject == null)
{
Console.WriteLine("cannot locate server");
}
else
{
remObject.ReplyMessage("You there?");
}
return 0;
}
}
}
// Shared Library:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
namespace RemotingSample
{
public class RemoteObject : MarshalByRefObject
{
public RemoteObject()
{
Console.WriteLine("Remote object activated");
}
public String ReplyMessage(String msg)
{
Console.WriteLine("Client : " + msg);//print given message on console
return "Server : I'm alive !";
}
}
}
Check out the TextWriter.Synchronized method.
http://msdn.microsoft.com/en-us/library/system.io.textwriter.synchronized.aspx
This should let you do this:
TextWriter.Synchronized(writer).Write(serializedCache);