Autogenerate Views inside the Perspective View Folder Structure (Self Aware SCADA)

I’m trying to solve the problem of manually building out Main Views when I have a data model that encompasses all of the information necessary to auto generate said Main Views.

Outlined below is information describing the background of the problem I’m trying to solve with the Ideal Solution. In this context I’m using the idea of ‘Self Aware SCADA’ to have the ability to auto generate views from a dataset within a defined data model.

Design Purpose:

  • Auto generate views (inside the Perspective Views Folder Structure) dynamically from a given dataset
  • Automate the Manual build process of the Main View (referenced in Step 2 below).

Current Main View Build Process:

  • Step 1 - Add Main View tags to the data model which has a 1 to 1 relationship defined between udts and Templated Views
  • Step 2 - Manually buildout the Main View with the appropriate embedded Templated View configurations. Then set the folder tag path for tags to be populated on the embedded Templated Views configured inside the Main View.
  • Step 3 - Main View Build is complete.

Solution Requirements:

  • Autogenerate any type of view and configuration inside the Perspective Views Folder Structure.
  • I don’t want to write to the perspective view file structure externally. This would require manually enabling security on folders.

Ideal Solution:

  • Create a Project Library Script that imports and uses the same java classes that are doing the work when manually creating views in the designer.

Is the Ideal Solution that I’m proposing feasible and how would I get started?

Any information or suggestions for alternative solutions would be greatly appreciated. Thank you!

I can offer some tips, but be aware that everything you do here is outside the realm of supported territory, so you will have to do a lot of the legwork yourself.

To start, you’ll need to get to the project.

from com.inductiveautomation.ignition.designer import IgnitionDesigner

context = IgnitionDesigner.getFrame().context

Will get you a DesignerContext.
DesignerContext has a method getProject(), which gives you a DesignableProject.

DesignableProject has a ton of utility methods. You will need to learn how our ProjectResource class works and how it stores data, and how resources are keyed by type and location.

For the actual resource, you will want to manually construct a ViewConfig instance with the appropriate children.

Once you have a view config, GSON-serialize it appropriately, store it with the correct resource data key in your ProjectResource, and then you’ll have new resources showing up in your designer. You’ll probably have to save in the designer, then reopen the project for the rest of Perspective to pick them up properly.

3 Likes

@PGriffith Is there a method available for saving the new resources or is saving manually in the designer the only option?

This is great thank you for the help!

You could programmatically invoke the Save or Save All ‘actions’ in the designer, but there’s no exposed API for it. I’m not sure of any other approach that gets around the requirement to avoid direct filesystem manipulation on the gateway.

1 Like

@PGriffith When I try to serialize the ViewConfig created with the ViewConfig.StandardGsonAdapter.serialize() method. The TypeError returned expects 4 arguments but in the documentation below there is only 3.

Do you know if the documentation is missing the 4th argument or what I’m getting wrong here?

Thank you for the help!


https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.19/com/inductiveautomation/perspective/common/config/ViewConfig.StandardGsonAdapter.html

https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.19/com/inductiveautomation/perspective/common/config/ViewConfig.html

serialize is an instance method, so you’d have to construct the adapter first:
ViewConfig.StandardGsonAdapter().serialize(src, type, context)

But the better way to use GSON would be to create a GSON instance (conveniently, we have a helper method for just that):

from com.inductiveautomation.perspective.common import PerspectiveModule

gson = PerspectiveModule.createPerspectiveCompatibleGson()
gson.toJson(view)
1 Like

@PGriffith Could you please give me an example of how to store the gson serialized view to the ProjectResource? I’m having trouble determining which classes/methods to use and how to use them appropriately.

Thank you for the help!

Here is an other option, its creating a view.json

1 Like

If you’re creating a resource from whole cloth, then you would just create a new builder:
ProjectResource.newBuilder().setProjectName(project).setResourcePath(resourcePath).putData(ViewConfig.RESOURCE_FILENAME, serializedView).build()

Modifying an existing resource is usually easier, but I thought you were going to always be creating new resources.

1 Like

@victordcq Thanks for the recommendation. I’ve tested this solution and it works great but required me to disable security on folders in order to add new views. I’m trying to provide users the ability to add assets and then create the views for those assets from the browser in perspective. I’m hoping using the native java classes/methods will give a more seamless approach to accomplishing this task.

@PGriffith I want to add new views to an existing project under a specific folder location. For example in the screenshot below the new views should get created in the ‘SCADA’ folder. With all that in mind are my sequence of steps correct? (assuming my arguments are valid)

image

Thanks for your patience, I’m still trying to learn the terminology, purpose of these classes and how they work together.

You need to create an instance of com.inductiveautomation.ignition.common.project.resource.ResourcePath, via the two-arg constructor: ResourcePath(com.inductiveautomation.ignition.common.project.resource.ResourceType, java.lang.String)

So resourcePath = ResourcePath(ViewConfig.RESOURCE_TYPE, "SCADA/whateverNameYouWant")
Where ViewConfig is com.inductiveautomation.perspective.common.config.ViewConfig.

Your project name can be dynamic with system.project.getProjectName().

1 Like

hm as what are you running the gateway service? if you run it as admin or whatever it should create the files with the same rights

1 Like

@victordcq That seems right because when I executed from the script console I don’t think I was running as admin.

@PGriffith How would I invoke the “Save or Save All ‘actions’ in the designer”?

Currently I am able to create new views in the designer with a script using the classes and methods you’ve suggested. After manually saving the project updates in the designer the new views are displayed in perspective. I’m trying to automate the saving step of this process.

Also would and approach using the ‘updateProject’ method below work or something similar?

No. updateProject is the opposite direction; it’s pulling changes from the gateway into the current Designer session. Like I said earlier - there’s no direct way to invoke the save/save all actions.

IgnitionDesigner has a private method handleSave. With reflection, you can invoke this method despite it’s “private” status. It takes the following arguments:

boolean saveAs,
String newName,
boolean commitOnly,
boolean skipReOpen,
boolean showDialog

Whatever you do here is totally unsupported and subject to change between Ignition versions. This private method could start taking a new argument in the next Ignition release, and you would only know about it once your code stops working.

@PGriffith could you please give me and example of how to invoke the "handleSave" method correctly with jython reflection? I've tried multiple variations and have been unsuccessful. With my latest attempt I can't figure out if I'm calling handleSave incorrectly or there is additional requirements that I'm missing.

Thank you for the help!

If you install Ignition Extensions it adds a system.project.save() method. You can also look into the code to see how I'm calling it via reflection.

2 Likes

@PGriffith This is awesome thank you!

@PGriffith How do I assign JsonObjects to other JsonObjects?

In the example below I'm trying to set the anchor property to the rotate property for the position argument in the ComponentConfig() object but unable to with addProperty().