This project has moved. For the latest updates, please go here.

Changing Configuration Programmatically

Developer
Apr 26, 2013 at 3:29 PM
Is it possible for me to set the configuration of an adapter (particularly the InputMeasurementKeys and OutputMeasurements properties) from the Initialize method?

I am assuming so but do I need call a method to refresh the adaptera after I have made the changes?

Thanks!!!
Kevin
Coordinator
Apr 27, 2013 at 4:38 PM
The input and output measurements are defined via properties in the base classes for all adapters - so basically if these get changed in the connection string for your adapter they will be applied when the adapter is re-initialized.

That is "INITIALIZE <myadapter>" from a console session.

Thanks!
Ritchie
Developer
Apr 27, 2013 at 4:44 PM
So if I edit those properties in my overridden Initialize() method before I make a call to base.Initialize(), then the properties that I just set should remain intact and my PublishFrame() method will receive the input measurements as defined by the custom initialization, even if the connection string was originally empty (when I configured the adapter in the Manager UI)?

Thanks,

Kevin
Developer
Apr 27, 2013 at 4:45 PM
Actually, I bet when I call base.Initialize() whatever is in my connection string overrides the property changes I just made... So my theory is wrong
Coordinator
Apr 27, 2013 at 5:01 PM
Are you trying to make dynamic changes without reinitializing your adapter?
Developer
Apr 27, 2013 at 5:13 PM
I would like to read inputMeasurementKeys and output measurements (during initialization i think) from a custom table in the database to facilitate naming translation to the RTO.

The process and code that I have created (and what we are currently using) requires to much manual effort and handing off the modeling task to someone who isn't comfortable with our system (managements intention, btw) has proven difficult and error prone. This, and one of our IT employees is keen on maintaining a translation table in our openPDC DB... Not my preferred solution but neither is read-only DBs :)

If you know of another way to accomplish this I am open to suggestions. I have been working under the assumption that to truly rename measurements that I need to pass them through an adapter to a virtual device. The AlternateTag field isn't sufficient and is also used by our ISD adapter to send stuff to EMS.

Thanks!!

Kevin

Apr 27, 2013 at 6:45 PM
I have a similar thread that I am abandoning, since this one is converging on my issue. I originally stated:

For reasons too vast to mention, I have a need to enable/disable action adapters from a program. I can modify the object via the database, but the adapters need to be 'poked' to be aware of the state change. Apparently this is done via:

TimeSeriesFramework.UI.CommonFunctions.SendCommandToService("Initialize " + runtimeId);

This call fails due to 'Application is currently disconnected from service'. I must be missing some pre-initializing of the CommonFunctions, but I can't find the correct method... Is there some single-call that will solve my problem?

To which staphen (Shephen) replied:

Hi patpentz,

Try calling CommonFunctions.ConnectWindowsServiceClient().

Unfortunately I have tried calling this routine, including with/without the one parameter TRUE. More on this on Monday. But my intention (later) is to update the 'inputmeasurementKeys' and 'outputmeasurements' in some cases, and the 'variablelist' in others, as conditions change, via database UPDATE calls, and then calling the SendCommandToService:Initialize. From reading this thread, this should work, as long as the output measurements have been correctly created. This can all be done programatically, I believe, using SQL Server UPDATE calls, and then the SendCommandToService command.

Am I missing anything?
Coordinator
Apr 27, 2013 at 7:17 PM
Hey Kevin,

As you suspected, the properties are populated by the base class when Initialize() is called. If you set the properties in your Initialize() method after calling base.Initialize(), your values should stick.

Stephen
Coordinator
Apr 27, 2013 at 7:23 PM
patpentz,

Yes, that works as well, if you are modifying the inputMeasurementKeys and outputMeasurements out-of-process. Also note that to disable adapters, you will need to instead call SendCommandToService:ReloadConfig (with no parameters). I believe Kevin was asking if he could programmatically populate those fields in his custom adapter, without his values being overridden by what is in the connection string.

Thanks,
Stephen
Apr 28, 2013 at 12:47 AM
As for the disable of adapters (I assume also for enable adapters), if one or more adapters have their 'enable' state changed (0 or 1), only one call to SendCommandToService("ReloadConfig") is required, with no additional parameter (runtimeId, as in the 'Initialize' call).
Apr 29, 2013 at 12:42 PM
I tried calling ConnectWindowsServiceClient, both with and without the optional parameter (true). In both cases the error was 'Application is currently disconnected from service'.
    try
        {
            TimeSeriesFramework.UI.CommonFunctions.ConnectWindowsServiceClient(true);

            // Send initialize commands to all affected adapters
            foreach (KeyValuePair<CustomAdapterInfo.CustomAdapterInfoKey, CustomAdapterInfo> customAdapterKeyValuePair in GlobalData.CustomAdapterInfoList)
            {
                CustomAdapterInfo customAdapterInfo = customAdapterKeyValuePair.Value;

                if (customAdapterInfo.RuntimeId != 0)
                {
                    string[] adapterNameParts = customAdapterInfo.Key.CustomAdapterName.Split(new Char[] { '#' });
                    if (adapterNameParts[0].Equals(ActiveDeviceInfo.Key.Acronym) ||
                        ((PreviousActiveDeviceInfo != null) && adapterNameParts[0].Equals(PreviousActiveDeviceInfo.Key.Acronym)))
                    {
                        Console.WriteLine("Initialize Adapter {0} with runtime ID {1}", customAdapterInfo.Key.CustomAdapterName, customAdapterInfo.RuntimeId);
                        TimeSeriesFramework.UI.CommonFunctions.SendCommandToService("Initialize " + customAdapterInfo.RuntimeId);
                    }
                }
            }
        }
        catch (Exception e)
        {
            GlobalData.DataLogger.Warn("Failed to initialize modified adapters", e);
        }

        try
        {
            TimeSeriesFramework.UI.CommonFunctions.ConnectWindowsServiceClient();
            TimeSeriesFramework.UI.CommonFunctions.SendCommandToService("ReloadConfig");
        }
        catch (Exception e)
        {
            GlobalData.DataLogger.Warn("Failed to initialize (2) modified adapters", e);
        }
Coordinator
Apr 29, 2013 at 1:32 PM
Edited Apr 29, 2013 at 1:32 PM
You should definitely call the method without the optional parameter. Even so, you may see that you're still disconnected after calling it because it is using an asynchronous connection to the service. You can check the connected state of the client:
WindowsServiceClient client = CommonFunctions.GetWindowsServiceClient();

if (client.Helper.RemotingClient.CurrentState == ClientState.Connected)
{
    // client is connected
}
You can use a loop to wait until the client is connected. You can also have a look at the openPDC Manager project (MainWindow.xaml.cs) to see how it maintains a connection using an asynchronous loop, but I would warn that it seems to be quite a bit more complex.

Stephen
Apr 29, 2013 at 2:47 PM
I created a loop as you suggested, but after 10 loops, each with a 5 second delay (for a total of 50 seconds), the ClientState never transitioned to 'Connected'.
            TimeSeriesFramework.UI.CommonFunctions.ConnectWindowsServiceClient();
            WindowsServiceClient client = TimeSeriesFramework.UI.CommonFunctions.GetWindowsServiceClient();

            for (int waitCount = 0; waitCount < maxLoopCount; waitCount++)
            {
                if (client.Helper.RemotingClient.CurrentState == ClientState.Connected)
                {
                    ...
                    break;
                }
                System.Threading.Thread.Sleep(5000);
            }
Coordinator
Apr 29, 2013 at 5:16 PM
After looking into this for a bit, I discovered that in order for CommonFunctions.ConnectWindowsServiceClient to work properly, you must set the current node ID using CommonFunctions.SetAsCurrentNodeID(). However this also caused problems in my console test app because both CommonFunctions.SetAsCurrentNodeID() and CommonFunctions.GetWindowsServiceClient() call CommonFunctions.ConnectWindowsServiceClient() as a side-effect. This was causing a race condition that always resulted in an unhandled exception in my app. Here's a code sample that did work:
        static void Main(string[] args)
        {
            Guid nodeID = Guid.Parse("d1ebffbb-d318-463f-9822-394fd0e968b0");
            WindowsServiceClient client;

            nodeID.SetAsCurrentNodeID();
            CommonFunctions.DisconnectWindowsServiceClient();
            client = CommonFunctions.GetWindowsServiceClient();

            while (client.Helper.RemotingClient.CurrentState != ClientState.Connected)
            {
                Console.WriteLine("Connecting...");
                Thread.Sleep(1000);
            }

            Console.WriteLine("Connected!");
            Console.ReadLine();
        }
Apr 29, 2013 at 7:07 PM
Sad to say, my loop never connects. After 50 seconds of waiting, the loop is exited with failure. Is there anything I can check on to see why I am not connecting?
        MasterNodeInfo.Id.SetAsCurrentNodeID();
        CommonFunctions.DisconnectWindowsServiceClient();
        WindowsServiceClient client = CommonFunctions.GetWindowsServiceClient();

        try
        {
            for (int waitCount = 0; waitCount < maxLoopCount; waitCount++)
            {
                Console.WriteLine("Seconds waited: {0} State: {1}", waitSeconds * waitCount, client.Helper.RemotingClient.CurrentState.ToString());
                if (client.Helper.RemotingClient.CurrentState == ClientState.Connected)
                {
                    init = true;

                    Console.WriteLine("ReloadConfig");
                    CommonFunctions.SendCommandToService("ReloadConfig");


                    break;
                }
                System.Threading.Thread.Sleep(waitMilliSeconds);  // Wait for waitSeconds seconds
            }
            if (!init)
            {
                Console.WriteLine("Never Connected to Windows Service Client after waiting {0} seconds", waitSeconds * maxLoopCount);
            }
        }
        catch (Exception e)
        {

        }
Coordinator
Apr 29, 2013 at 7:22 PM
Edited Apr 29, 2013 at 7:23 PM
Two other things that I just thought to add:

1) I assumed that you did since you didn't seem to encounter any errors in CommonFunctions.ConnectWindowsServiceClient(), but does your app have your database connection string and data provider string defined in its configuration file? It needs to be defined there just like it is in openPDC.exe.config. Here's the config file for my console test app.
<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="categorizedSettings" type="TVA.Configuration.CategorizedSettingsSection, TVA.Core" />
  </configSections>
  <categorizedSettings>
    <systemSettings>
      <add name="ConnectionString" value="Data Source=localhost\SQLEXPRESS; Initial Catalog=openPDC; Integrated Security=SSPI"
        description="Configuration connection string" encrypted="false" />
      <add name="DataProviderString" value="AssemblyName={System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089}; ConnectionType=System.Data.SqlClient.SqlConnection; AdapterType=System.Data.SqlClient.SqlDataAdapter"
        description="Configuration database ADO.NET data provider assembly type creation string used when ConfigurationType=Database"
        encrypted="false" />
    </systemSettings>
  </categorizedSettings>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
  </startup>
</configuration>
2) Are you running this application on the same system as the openPDC Manager? If you use these methods in CommonFunctions, it will be using the same connection parameters (hostname, port, etc) as the openPDC Manager does.

Thanks,
Stephen
Apr 29, 2013 at 8:16 PM
yes, my config file contains the same text as does the openPDC. I am also running on the same systems as the openPDC.
Coordinator
Apr 29, 2013 at 8:54 PM
You can view the connection parameters the app is trying to use to connect. Try adding this line after your call to CommonFunctions.GetWindowsServiceClient().
Console.WriteLine(CommonFunctions.ServiceConnectionString(null));
If it doesn't throw an error and your connection parameters look reasonable but you still can't get it to connect, you may want to try bypassing CommonFunctions entirely by creating your own WindowsServiceClient. If you do, you'll have more control over the connection parameters, when connect gets called, whether to invoke it asynchronously, and when to dispose of the WindowsServiceClient.
        static void Main(string[] args)
        {
            using (WindowsServiceClient client = new WindowsServiceClient("server=localhost:8500;integratedsecurity=true"))
            {
                TcpClient remotingClient;
                ISecurityProvider provider;
                UserData userData;

                if (SecurityProviderCache.TryGetCachedProvider(CommonFunctions.CurrentUser, out provider))
                {
                    userData = provider.UserData;

                    if ((object)userData != null)
                    {
                        client.Helper.Username = userData.LoginID;
                        client.Helper.Password = SecurityProviderUtility.EncryptPassword(provider.Password);
                        remotingClient = client.Helper.RemotingClient as TcpClient;

                        if ((object)remotingClient != null && (object)provider.SecurePassword != null && provider.SecurePassword.Length > 0)
                            remotingClient.NetworkCredential = new NetworkCredential(userData.LoginID, provider.SecurePassword);
                    }
                }

                client.Helper.RemotingClient.MaxConnectionAttempts = -1;
                client.Helper.Connect();
                client.Helper.SendRequest("ReloadConfig");
            }
        }
Apr 30, 2013 at 1:47 PM
the value written was: server=localhost:8500;integratedsecurity=true;

I have tried the final code-fragment, and it seems to work... you don't have any timed loops in that code-fragment, so I didn't place one there. Is this correct?
Coordinator
Apr 30, 2013 at 2:46 PM
Yep. The call to client.Helper.Connect() is synchronous so it will return once the client is connected.