Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions .github/workflows/main-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Publish new version upon tag commit
on:
push:
tags:
- '*'
- '[0-9].[0-9].[0-9]'
jobs:
build:
runs-on: ubuntu-latest
Expand All @@ -29,24 +29,9 @@ jobs:
--keystoreFile=keystore.pfx
--keystorePassword="${{ secrets.KEYSTORE_PASSWORD }}"
--certAlias=ignition-extensions
- name: Upload signed module
uses: actions/upload-artifact@v3
with:
name: signed
path: build/Ignition-Extensions.modl
release:
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v2
- name: Download signed module
uses: actions/download-artifact@v3
with:
name: signed
path: artifacts
- name: Create release
uses: marvinpinto/action-automatic-releases@latest
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
prerelease: false
files: artifacts/*
files: build/Ignition-Extensions.modl
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
# Ignition Extensions

A (hopefully) community driven Ignition module project to house utilities that are often useful, but just too niche (or
have enough footguns) to go into Ignition itself.
potentially risky) to go into Ignition itself.

# Usage

Simply download the .modl file from the [latest release](https://github.com/IgnitionModuleDevelopmentCommunity/ignition-extensions/releases) and install it to your gateway.
Simply download the .modl file from
the [latest release](https://github.com/IgnitionModuleDevelopmentCommunity/ignition-extensions/releases) and install it
to your gateway.

# Contribution

Contributions are welcome. This project is polyglot and set up for both Kotlin and Java. There are example utilities
written in both Kotlin and Java to extend from. Ideas for new features should start as issues for broader discussion.

# Building

This project uses Gradle, and the Gradle Module Plugin. Use `./gradlew build` to assemble artifacts,
and `./gradlew zipModule` to build an unsigned module file for installation into a development gateway.

# Contribution
# Testing

Contributions are welcome. This project is polyglot and set up for both Kotlin and Java. There are example utilities
written in both Kotlin and Java to extend from. Ideas for new features should start as issues for broader discussion.
The easiest way to test is a local Docker installation. Simple run `docker compose up` in the root of this repository to
stand up a local development gateway. Use `./gradlew deployModl` to install the locally built module on that test
gateway.
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ ignitionModule {

skipModlSigning.set(!findProperty("signModule").toString().toBoolean())
}

tasks.deployModl {
hostGateway.set("http://localhost:18088")
}
4 changes: 4 additions & 0 deletions client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ java {
}
}

kotlin {
jvmToolchain(libs.versions.java.map(String::toInt).get())
}

dependencies {
compileOnly(libs.bundles.client)
compileOnly(projects.common)
Expand Down
10 changes: 7 additions & 3 deletions client/src/main/kotlin/org/imdc/extensions/client/ClientHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import com.inductiveautomation.ignition.client.model.ClientContext
import com.inductiveautomation.ignition.common.BundleUtil
import com.inductiveautomation.ignition.common.licensing.LicenseState
import com.inductiveautomation.ignition.common.script.ScriptManager
import com.inductiveautomation.ignition.common.script.hints.PropertiesFileDocProvider
import com.inductiveautomation.vision.api.client.AbstractClientModuleHook
import org.imdc.extensions.common.DatasetExtensions
import org.imdc.extensions.common.ExtensionDocProvider
import org.imdc.extensions.common.UtilitiesExtensions
import org.imdc.extensions.common.addPropertyBundle

Expand All @@ -20,11 +20,15 @@ class ClientHook : AbstractClientModuleHook() {
BundleUtil.get().apply {
addPropertyBundle<DatasetExtensions>()
addPropertyBundle<UtilitiesExtensions>()
addPropertyBundle<ClientProjectExtensions>()
}
}

override fun initializeScriptManager(manager: ScriptManager) {
manager.addScriptModule("system.dataset", DatasetExtensions, PropertiesFileDocProvider())
manager.addScriptModule("system.util", UtilitiesExtensions(context), PropertiesFileDocProvider())
manager.apply {
addScriptModule("system.dataset", DatasetExtensions, ExtensionDocProvider)
addScriptModule("system.util", UtilitiesExtensions(context), ExtensionDocProvider)
addScriptModule("system.project", ClientProjectExtensions(context), ExtensionDocProvider)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.imdc.extensions.client

import com.inductiveautomation.ignition.client.model.ClientContext
import com.inductiveautomation.ignition.common.project.Project
import com.inductiveautomation.ignition.common.script.hints.ScriptFunction
import org.imdc.extensions.common.ProjectExtensions

class ClientProjectExtensions(private val context: ClientContext) : ProjectExtensions {
@ScriptFunction(docBundlePrefix = "ClientProjectExtensions")
override fun getProject(): Project {
return requireNotNull(context.project)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
getProject.desc=Retrieves the current project.
getProject.returns=The current project as a RuntimeProject instance.
4 changes: 4 additions & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ java {
}
}

kotlin {
jvmToolchain(libs.versions.java.map(String::toInt).get())
}

dependencies {
compileOnly(libs.ignition.common)
testImplementation(libs.ignition.common)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public UtilitiesExtensions(CommonContext context) {
}

@ScriptFunction(docBundlePrefix = "UtilitiesExtensions")
@UnsafeExtension
public CommonContext getContext() {
return context;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.imdc.extensions.common

import com.inductiveautomation.ignition.common.script.hints.PropertiesFileDocProvider
import com.inductiveautomation.ignition.common.script.hints.ScriptFunctionDocProvider
import java.lang.reflect.Method

private val propertiesFileDocProvider = PropertiesFileDocProvider()

private val WARNING = """
THIS IS AN UNOFFICIAL IGNITION EXTENSION.
IT MAY RELY ON OR EXPOSE UNDOCUMENTED OR DANGEROUS FUNCTIONALITY.
USE AT YOUR OWN RISK.
""".trimIndent()

object ExtensionDocProvider : ScriptFunctionDocProvider by propertiesFileDocProvider {
override fun getMethodDescription(path: String, method: Method): String {
val methodDescription: String? = propertiesFileDocProvider.getMethodDescription(path, method)
val unsafeAnnotation = method.getAnnotation<UnsafeExtension>()

return buildString {
if (unsafeAnnotation != null) {
append("<html><b>")
append(WARNING)
if (unsafeAnnotation.note.isNotEmpty()) {
append("<br>").append(unsafeAnnotation.note)
}
append("</b><br><br>")
}
append(methodDescription.orEmpty())
}
}
}

annotation class UnsafeExtension(val note: String = "")
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.imdc.extensions.common

import com.inductiveautomation.ignition.common.project.Project

interface ProjectExtensions {
fun getProject(): Project
}
11 changes: 8 additions & 3 deletions common/src/main/kotlin/org/imdc/extensions/common/Utilities.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ import com.inductiveautomation.ignition.common.BundleUtil
import com.inductiveautomation.ignition.common.Dataset
import org.python.core.Py
import org.python.core.PyObject
import java.lang.reflect.Method

class PyObjectAppendable(target: PyObject) : Appendable {
private val writeMethod = target.__getattr__("write")

override fun append(csq: CharSequence) = this.apply {
override fun append(csq: CharSequence): Appendable = this.apply {
writeMethod.__call__(Py.newStringOrUnicode(csq.toString()))
}

override fun append(csq: CharSequence, start: Int, end: Int) = this.apply {
override fun append(csq: CharSequence, start: Int, end: Int): Appendable = this.apply {
append(csq.subSequence(start, end))
}

override fun append(c: Char) = this.apply {
override fun append(c: Char): Appendable = this.apply {
append(c.toString())
}
}
Expand Down Expand Up @@ -48,3 +49,7 @@ inline fun <reified T> BundleUtil.addPropertyBundle() {
T::class.java.name.replace('.', '/'),
)
}

inline fun <reified T : Annotation> Method.getAnnotation(): T? {
return getAnnotation(T::class.java)
}
4 changes: 4 additions & 0 deletions designer/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ java {
}
}

kotlin {
jvmToolchain(libs.versions.java.map(String::toInt).get())
}

dependencies {
compileOnly(libs.bundles.designer)
compileOnly(projects.common)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package org.imdc.extensions.designer
import com.inductiveautomation.ignition.common.BundleUtil
import com.inductiveautomation.ignition.common.licensing.LicenseState
import com.inductiveautomation.ignition.common.script.ScriptManager
import com.inductiveautomation.ignition.common.script.hints.PropertiesFileDocProvider
import com.inductiveautomation.ignition.designer.model.AbstractDesignerModuleHook
import com.inductiveautomation.ignition.designer.model.DesignerContext
import org.imdc.extensions.common.DatasetExtensions
import org.imdc.extensions.common.ExtensionDocProvider
import org.imdc.extensions.common.UtilitiesExtensions
import org.imdc.extensions.common.addPropertyBundle

Expand All @@ -20,11 +20,15 @@ class DesignerHook : AbstractDesignerModuleHook() {
BundleUtil.get().apply {
addPropertyBundle<DatasetExtensions>()
addPropertyBundle<UtilitiesExtensions>()
addPropertyBundle<DesignerProjectExtensions>()
}
}

override fun initializeScriptManager(manager: ScriptManager) {
manager.addScriptModule("system.dataset", DatasetExtensions, PropertiesFileDocProvider())
manager.addScriptModule("system.util", UtilitiesExtensions(context), PropertiesFileDocProvider())
manager.apply {
addScriptModule("system.dataset", DatasetExtensions, ExtensionDocProvider)
addScriptModule("system.util", UtilitiesExtensions(context), ExtensionDocProvider)
addScriptModule("system.project", DesignerProjectExtensions(context), ExtensionDocProvider)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.imdc.extensions.designer

import com.inductiveautomation.ignition.common.script.hints.ScriptFunction
import com.inductiveautomation.ignition.designer.IgnitionDesigner
import com.inductiveautomation.ignition.designer.model.DesignerContext
import com.inductiveautomation.ignition.designer.project.DesignableProject
import org.apache.commons.lang3.reflect.MethodUtils
import org.imdc.extensions.common.ProjectExtensions
import org.imdc.extensions.common.UnsafeExtension

class DesignerProjectExtensions(private val context: DesignerContext) : ProjectExtensions {
@ScriptFunction(docBundlePrefix = "DesignerProjectExtensions")
@UnsafeExtension
override fun getProject(): DesignableProject {
return requireNotNull(context.project)
}

@ScriptFunction(docBundlePrefix = "DesignerProjectExtensions")
@UnsafeExtension
fun save() {
MethodUtils.invokeMethod(
context.frame,
true, // forceAccess
"handleSave",
false, // saveAs
null, // newName
false, // commitOnly
false, // skipReopen
false, // showDialog
)
}

@ScriptFunction(docBundlePrefix = "DesignerProjectExtensions")
@UnsafeExtension
fun update() {
(context.frame as IgnitionDesigner).updateProject()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
getProject.desc=Retrieves the current project.
getProject.returns=The current project as a DesignableProject instance.
update.desc=Pulls in external changes made to this project from the gateway.
update.returns=None
save.desc=Programmatically invokes the save action.
save.returns=None
20 changes: 20 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
services:
gateway:
image: inductiveautomation/ignition:8.1.20
ports:
- 18088:8088
environment:
GATEWAY_ADMIN_PASSWORD: password
IGNITION_EDITION: standard
ACCEPT_IGNITION_EULA: "Y"
volumes:
- gateway-data:/usr/local/bin/ignition/data
command: >
-n Ignition-module-dev
-d
--
-Dignition.allowunsignedmodules=true
-Dia.developer.moduleupload=true

volumes:
gateway-data:
4 changes: 4 additions & 0 deletions gateway/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ java {
}
}

kotlin {
jvmToolchain(libs.versions.java.map(String::toInt).get())
}

dependencies {
compileOnly(libs.bundles.gateway)
compileOnly(projects.common)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package org.imdc.extensions.gateway
import com.inductiveautomation.ignition.common.BundleUtil
import com.inductiveautomation.ignition.common.licensing.LicenseState
import com.inductiveautomation.ignition.common.script.ScriptManager
import com.inductiveautomation.ignition.common.script.hints.PropertiesFileDocProvider
import com.inductiveautomation.ignition.gateway.model.AbstractGatewayModuleHook
import com.inductiveautomation.ignition.gateway.model.GatewayContext
import org.imdc.extensions.common.DatasetExtensions
import org.imdc.extensions.common.ExtensionDocProvider
import org.imdc.extensions.common.UtilitiesExtensions
import org.imdc.extensions.common.addPropertyBundle

Expand All @@ -20,15 +20,19 @@ class GatewayHook : AbstractGatewayModuleHook() {
BundleUtil.get().apply {
addPropertyBundle<DatasetExtensions>()
addPropertyBundle<UtilitiesExtensions>()
addPropertyBundle<GatewayProjectExtensions>()
}
}

override fun startup(activationState: LicenseState) {}
override fun shutdown() {}

override fun initializeScriptManager(manager: ScriptManager) {
manager.addScriptModule("system.dataset", DatasetExtensions, PropertiesFileDocProvider())
manager.addScriptModule("system.util", UtilitiesExtensions(context), PropertiesFileDocProvider())
manager.apply {
addScriptModule("system.dataset", DatasetExtensions, ExtensionDocProvider)
addScriptModule("system.util", UtilitiesExtensions(context), ExtensionDocProvider)
addScriptModule("system.project", GatewayProjectExtensions(context), ExtensionDocProvider)
}
}

override fun isFreeModule(): Boolean = true
Expand Down
Loading