using System; using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Opc.Ua; using Opc.Ua.Configuration; using Microsoft.Extensions.Configuration; using MP.MONO.ADAPTER; using MP.MONO.Core; using MP.MONO.Core.CONF; using MP.MONO.Core.DTO; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System.Collections.Generic; using static MP.MONO.Core.Enums; namespace MP.MONO.ADAPTER.OPC { /// /// The program. /// public static class Program { private static string redisConf = ""; private static string confPath = ""; private static string lineSep = "---------------------------------------------"; private static Logger Log = LogManager.GetCurrentClassLogger(); private static bool verboseLog = false; private static bool logWriting = false; private static AlarmReportingMode alarmMode = AlarmReportingMode.ND; /// /// Redis channel manager /// private static ISubscriber sub { get; set; } /// /// Redis DB /// private static IDatabase? redisDb { get; set; } private static Dictionary LogSimulator = new Dictionary(); private static Dictionary LastSend = new Dictionary(); private static DateTime lastLog = DateTime.Now.AddMinutes(-1); /// /// Main entry point. /// public static async Task Main(string[] args) { TextWriter output = Console.Out; output.WriteLine("Egalware | MP.MONO.ADAPTER | OPC UA Console Client"); output.WriteLine("OPC UA library: {0} @ {1} -- {2}", Utils.GetAssemblyBuildNumber(), Utils.GetAssemblyTimestamp().ToString("G", CultureInfo.InvariantCulture), Utils.GetAssemblySoftwareVersion()); // init parte config, vedere https://blog.hildenco.com/2020/05/configuration-in-net-core-console.html var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); var builder = new ConfigurationBuilder() .AddJsonFile($"appsettings.json", true, true) .AddJsonFile($"appsettings.{env}.json", true, true) .AddEnvironmentVariables(); var config = builder.Build(); // imposto variabili di base redisConf = config.GetConnectionString("Redis"); confPath = Path.Combine(Directory.GetCurrentDirectory(), "conf"); Random rand = new Random(); List? statusList = new List(); List? modeList = new List(); // fix numero minimo dei thread pool x evitare collasso chiamate redis ThreadPool.SetMinThreads(10, 10); logInfo(lineSep, true, true); logInfo($"Starting Machine ADAPTER", true, true); logInfo($"Redis server param: {redisConf.Substring(0, 20)}...", false, true); logInfo(lineSep, true, true); logInfo("", true, true); // Setup REDIS ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", true); ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(redisConf); sub = redis.GetSubscriber(); redisDb = redis.GetDatabase(); // salvo configurazioni in redis setupConfRedis(); // modalità gestione allarmi alarmMode = config.GetValue("OptPar:AlarmMode"); await redisDb.StringSetAsync(Constants.ALARMS_MODE_KEY, JsonConvert.SerializeObject(alarmMode)); // avvio il vero e proprio programma di comunicaizone OPC-UA var currIob = new IobOpcUa(confPath, config); logInfo($"Started IOB at path {confPath}", true, true); #if false // The application name and config file names var applicationName = "MP.MONO.ADAPTER.OPC"; var configSectionName = "MpMonoAdapterOpc"; var usage = $"Usage: dotnet {applicationName}.dll [OPTIONS]"; // command line options bool showHelp = false; bool autoAccept = false; bool logConsole = false; bool appLog = false; bool renewCertificate = false; string password = null; int timeout = Timeout.Infinite; Mono.Options.OptionSet options = new Mono.Options.OptionSet { usage, { "h|help", "show this message and exit", h => showHelp = h != null }, { "a|autoaccept", "auto accept certificates (for testing only)", a => autoAccept = a != null }, { "c|console", "log to console", c => logConsole = c != null }, { "l|log", "log app output", c => appLog = c != null }, { "p|password=", "optional password for private key", (string p) => password = p }, { "r|renew", "renew application certificate", r => renewCertificate = r != null }, { "t|timeout=", "timeout in seconds to exit application", (int t) => timeout = t * 1000 }, }; try { // parse command line and set options var extraArg = ConsoleUtils.ProcessCommandLine(output, args, options, ref showHelp, false); // connect Url? Uri serverUrl = new Uri("opc.tcp://localhost:62541/Quickstarts/ReferenceServer"); if (!string.IsNullOrEmpty(extraArg)) { serverUrl = new Uri(extraArg); } // log console output to logger if (logConsole && appLog) { output = new LogWriter(); } // Define the UA Client application ApplicationInstance.MessageDlg = new ApplicationMessageDlg(output); CertificatePasswordProvider PasswordProvider = new CertificatePasswordProvider(password); ApplicationInstance application = new ApplicationInstance { ApplicationName = applicationName, ApplicationType = ApplicationType.Client, ConfigSectionName = configSectionName, CertificatePasswordProvider = PasswordProvider }; // load the application configuration. var config = await application.LoadApplicationConfiguration(silent: false); // setup the logging ConsoleUtils.ConfigureLogging(config, applicationName, logConsole, Microsoft.Extensions.Logging.LogLevel.Information); // delete old certificate if (renewCertificate) { await application.DeleteApplicationInstanceCertificate().ConfigureAwait(false); } // check the application certificate. bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false, minimumKeySize: 0).ConfigureAwait(false); if (!haveAppCertificate) { throw new ErrorExitException("Application instance certificate invalid!", ExitCode.ErrorCertificate); } // wait for timeout or Ctrl-C var quitEvent = ConsoleUtils.CtrlCHandler(); // connect to a server until application stopped bool quit = false; DateTime start = DateTime.UtcNow; int waitTime = int.MaxValue; do { if (timeout > 0) { waitTime = timeout - (int)DateTime.UtcNow.Subtract(start).TotalMilliseconds; if (waitTime <= 0) { break; } } // create the UA Client object and connect to configured server. UAClient uaClient = new UAClient(application.ApplicationConfiguration, output, ClientBase.ValidateResponse) { AutoAccept = autoAccept }; bool connected = await uaClient.ConnectAsync(serverUrl.ToString()); if (connected) { // Run tests for available methods. uaClient.ReadNodes(); uaClient.WriteNodes(); uaClient.Browse(); uaClient.CallMethod(); uaClient.SubscribeToDataChanges(); // Wait for some DataChange notifications from MonitoredItems quit = quitEvent.WaitOne(Math.Min(30_000, waitTime)); uaClient.Disconnect(); } else { output.WriteLine("Could not connect to server! Retry in 10 seconds or Ctrl-C to quit."); quit = quitEvent.WaitOne(Math.Min(10_000, waitTime)); } } while (!quit); output.WriteLine("\nClient stopped."); } catch (Exception ex) { output.WriteLine(ex.Message); } #endif logInfo("Running - press CTRL-C to stop ADAPTER", false, true); logInfo("", false, true); // wait for timeout or Ctrl-C var quitEvent = ConsoleUtils.CtrlCHandler(); bool quit = false; DateTime start = DateTime.UtcNow; int waitTime = int.MaxValue; // Ciclo infinito x attesa chiusura con CTRL-C do { // se non fosse connesso... riprovo la connessione... if (!currIob.connectionOk) { currIob.tryConnect(); } // attesa... Thread.Sleep(100); // verifico se c'è evento quit quit = quitEvent.WaitOne(Math.Min(1_000, waitTime)); } while (!quit); // disconnetto alla fine... if (currIob.connectionOk) { currIob.tryDisconnect(); } } /// /// verifica esistenza file oppure lo crea... /// private static void checkFilePresent(string filePath) { // verific presenza file log... if (!File.Exists(filePath)) { File.WriteAllText(filePath, $"{filePath} created!"); } } /// /// Setup e salvataggio redis delle conf (es modi/stati) /// private static void setupConfRedis() { ConfigManager configManager = new ConfigManager(redisConf, confPath); if (alarmMode == AlarmReportingMode.RawList) { _ = configManager.getAlarmsRawConf(); } else { _ = configManager.getAlarmsBankConf(); } // altre conf _ = configManager.getMachineModeConf(); _ = configManager.getMachineStatusConf(); _ = configManager.getParamsConf(); } /// /// Effettua log INFO su file e se richiesto su console /// private static void logInfo(string msg, bool log2file = true, bool log2console = false) { if (log2console) { Console.WriteLine(msg); } if (log2file) { Log.Info(msg); } } /// /// Effettua log ERROR su file e se richiesto su console /// private static void logError(string msg, bool log2file = true, bool log2console = false) { if (log2console) { Console.WriteLine(msg); } if (log2file) { Log.Error(msg); } } private static void saveAndSendMessage(string memKey, string value, string notifyChannel, string message) { // effettuo la scrittura nell'area di memoria indicata SE passato intervallo minimo bool doSend = true; if (LastSend.ContainsKey(memKey)) { if (DateTime.Now.Subtract(LastSend[memKey]).TotalSeconds < 60) { doSend = false; } } else { LastSend.Add(memKey, DateTime.Now); } if (doSend) { redisDb.StringSetAsync(memKey, value); LastSend[memKey] = DateTime.Now; logInfo($"Redis Cache Key: {memKey}"); } //redisDb.SetAdd(memKey, value); // invio notifica tramite il canale richiesto sub.Publish(notifyChannel, message); if (verboseLog) { logInfo($"[{notifyChannel}] key: {memKey} | val: {value} | message: {message}"); } else { try { if (!logWriting) { if (LogSimulator.ContainsKey(notifyChannel)) { LogSimulator[notifyChannel]++; } else { LogSimulator.Add(notifyChannel, 1); } logWriting = true; // vedo se loggare... DateTime adesso = DateTime.Now; if (adesso.Subtract(lastLog).TotalSeconds > 15) { lastLog = adesso; logInfo(lineSep); // lavoro su copia... var LogSimulatorCopy = new Dictionary(LogSimulator); foreach (var item in LogSimulatorCopy) { logInfo($"Redis mQueue {item.Key,-20}{item.Value,12}"); } logInfo(lineSep); } logWriting = false; } } catch (Exception ex) { logError($"ERROR{Environment.NewLine}{ex}"); } } } } }