Skip to content

Commit dd650fb

Browse files
authored
(8.3) Optional Licensing Model (#483)
* request license * add designer license cues
1 parent abd526f commit dd650fb

21 files changed

Lines changed: 368 additions & 10 deletions

File tree

.changeset/cold-ties-exist.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@embr-jvm/core-designer': patch
3+
---
4+
5+
Add an Embr splash screen for unlicensed gateways.

.changeset/cute-chefs-trade.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@embr-jvm/core-designer': patch
3+
---
4+
5+
Add an `Embr Help` button to the designer's Help menu.

.changeset/fast-bears-divide.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@embr-jvm/core-designer': patch
3+
---
4+
5+
Add a button to the Designer's status bar for unlicensed gateways. Clicking on this button will navigate the user to Embr's documentation.
6+
- The button's tooltip displays the license status for each Embr module.

.changeset/modern-cities-joke.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
'@embr-modules/charts': major
3+
'@embr-modules/thermo': major
4+
'@embr-modules/periscope': minor
5+
'@embr-modules/snmp': minor
6+
---
7+
8+
Introduce an optional licensing model.
9+
10+
## Embr Module Licensing
11+
- To offer a more complete support model, Musson Industrial is introducing optional licensing for the Embr suite.
12+
- Don't worry, these modules will always remain free to use and open source.
13+
14+
15+
Here's what's changing:
16+
17+
**Standard Edition**
18+
- Embr modules are free to use but will run in trial mode when unlicensed.
19+
- **No functionality will be impacted by an expired trial.**
20+
- You can remove the trial status by purchasing a low-cost license from our web store.
21+
22+
**Maker Edition**
23+
- Embr modules are free to use with no trial mode.
24+
Licenses can be purchased at the [Musson Industrial Web Store](https://mussonindustrial.com/store).
25+
26+
## Expected Questions
27+
28+
- **Q:** Is Embr still open source?
29+
- **A:** Yes! The entire Embr suite is still MIT-licensed, and this will never change.
30+
31+
32+
- **Q:** Can I continue to use Embr Charts/Periscope/Thermo/SNMP for free?
33+
- **A:** Yes! The modules will report as being in trial mode, but no functionality will be impacted. You will still continue to receive updates.
34+
35+
36+
- **Q:** Why should I purchase a license?
37+
- **A:** A license removes the trial banner and supports continued development of the Embr suite, helping ensure these modules remain useful, reliable, and actively maintained.
38+
39+
40+
- **Q:** What if I upgrade from 8.1 to 8.3?
41+
- **A:** License keys are not tied to a specific Ignition version and will never expire.
42+
43+
44+
- **Q:** Will you offer redundant gateway, integrator, or bulk purchase discounts?
45+
- **A:** We aim to keep licensing simple, affordable, and automated, so there are currently no additional discounts.
46+
47+
48+
- **Q:** I have questions regarding licenses, special setups, or long-term support guarantees.
49+
- **A:** We're here for you! For setups that fall outside the standard automated licensing process, please reach out so we can provide guidance.
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package com.mussonindustrial.embr.common
22

3+
import com.inductiveautomation.ignition.common.model.PlatformEdition
4+
35
object Embr {
46
val CHARTS = EmbrModuleMeta("com.mussonindustrial.embr.charts", "embr-charts", "/embr/charts")
5-
7+
val PERISCOPE =
8+
EmbrModuleMeta("com.mussonindustrial.embr.periscope", "embr-periscope", "/embr/periscope")
69
val EVENT_STREAM =
710
EmbrModuleMeta(
811
"com.mussonindustrial.embr.eventstream",
912
"embr-event-stream",
1013
"/embr/event-stream",
1114
)
12-
1315
val SNMP = EmbrModuleMeta("com.mussonindustrial.embr.snmp", "embr-snmp", "/embr/snmp")
1416
val THERMO = EmbrModuleMeta("com.mussonindustrial.embr.thermo", "embr-thermo", "/embr/thermo")
17+
18+
const val DOCUMENTATION_URL = "https://docs.mussonindustrial.com/"
19+
20+
fun isLicenseRequired(): Boolean {
21+
return !PlatformEdition.isMaker()
22+
}
1523
}
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
package com.mussonindustrial.embr.designer
22

3+
import com.inductiveautomation.ignition.common.licensing.LicenseMode
4+
import com.inductiveautomation.ignition.common.modules.ModuleInfo
35
import com.inductiveautomation.ignition.designer.model.DesignerContext
46
import com.mussonindustrial.embr.common.EmbrCommonContextExtension
57

68
interface EmbrDesignerContext :
7-
DesignerContext, EmbrCommonContextExtension, EmbrDesignerContextExtension
9+
DesignerContext, EmbrCommonContextExtension, EmbrDesignerContextExtension {
10+
11+
val embrModules: List<ModuleInfo>
12+
get() = modules.filter { it.id.contains("com.mussonindustrial.embr") }.sortedBy { it.name }
13+
14+
val unlicensedEmbrModules: List<ModuleInfo>
15+
get() = embrModules.filter { getLicenseState(it.id).licenseMode != LicenseMode.Activated }
16+
}

libraries/core/designer/src/main/kotlin/com/mussonindustrial/embr/designer/EmbrDesignerContextImpl.kt

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,50 @@ package com.mussonindustrial.embr.designer
33
import com.inductiveautomation.ignition.designer.model.DesignerContext
44
import com.mussonindustrial.embr.common.EmbrCommonContextExtension
55
import com.mussonindustrial.embr.common.EmbrCommonContextExtensionImpl
6+
import com.mussonindustrial.embr.designer.gui.EmbrHelpMenuItem
7+
import com.mussonindustrial.embr.designer.gui.EmbrStartupModal
8+
import com.mussonindustrial.embr.designer.gui.EmbrStatusBarButton
9+
import com.mussonindustrial.embr.designer.gui.onWindowOpenedOnce
10+
import javax.swing.JFrame
11+
import javax.swing.JMenu
612

713
open class EmbrDesignerContextImpl(private val context: DesignerContext) :
814
EmbrDesignerContext,
915
DesignerContext by context,
10-
EmbrCommonContextExtension by EmbrCommonContextExtensionImpl(context)
16+
EmbrCommonContextExtension by EmbrCommonContextExtensionImpl(context) {
17+
18+
init {
19+
if (!isInitialized()) initialize()
20+
}
21+
22+
val helpMenu: JMenu?
23+
get() {
24+
val frame = context.frame as JFrame
25+
val jMenuBar = frame.jMenuBar
26+
for (menuIndex in 0..jMenuBar.menuCount) {
27+
val menu = jMenuBar.getMenu(menuIndex)
28+
if (menu.text == "Help") return menu
29+
}
30+
return null
31+
}
32+
33+
private fun initialize() {
34+
setInitialized()
35+
36+
context.frame.onWindowOpenedOnce {
37+
helpMenu?.insert(EmbrHelpMenuItem(), 1)
38+
if (unlicensedEmbrModules.isNotEmpty()) {
39+
EmbrStartupModal()
40+
statusBar.addDisplay(EmbrStatusBarButton(this), 1)
41+
}
42+
}
43+
}
44+
45+
private fun isInitialized(): Boolean {
46+
return System.getProperty("embr.designer.initialized")?.isNotEmpty() == true
47+
}
48+
49+
private fun setInitialized() {
50+
System.setProperty("embr.designer.initialized", "true")
51+
}
52+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.mussonindustrial.embr.designer.gui
2+
3+
import java.util.concurrent.ScheduledExecutorService
4+
import java.util.concurrent.ScheduledFuture
5+
import java.util.concurrent.TimeUnit
6+
import javax.swing.SwingUtilities
7+
8+
fun ScheduledExecutorService.animate(
9+
durationMs: Long,
10+
periodMs: Long = 16L,
11+
update: (Float) -> Unit,
12+
done: () -> Unit,
13+
) {
14+
val start = System.nanoTime()
15+
16+
val task =
17+
object : Runnable {
18+
lateinit var future: ScheduledFuture<*>
19+
20+
override fun run() {
21+
val elapsed = (System.nanoTime() - start) / 1_000_000f
22+
val t = (elapsed / durationMs).coerceIn(0f, 1f)
23+
24+
SwingUtilities.invokeLater {
25+
update(t)
26+
if (t >= 1f) {
27+
future.cancel(false)
28+
done()
29+
}
30+
}
31+
}
32+
}
33+
34+
task.future = scheduleAtFixedRate(task, 0, periodMs, TimeUnit.MILLISECONDS)
35+
}
36+
37+
fun easeOut(t: Float) = 1 - (1 - t) * (1 - t)
38+
39+
fun easeIn(t: Float) = t * t
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.mussonindustrial.embr.designer.gui
2+
3+
import com.inductiveautomation.ignition.client.icons.SvgIconUtil
4+
import javax.swing.Icon
5+
6+
object EmbrDesignerIcons {
7+
val emblem: Icon = SvgIconUtil.getIcon("mussonindustrial_emblem")
8+
val help: Icon = SvgIconUtil.getIcon("mussonindustrial_help")
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.mussonindustrial.embr.designer.gui
2+
3+
import com.inductiveautomation.ignition.client.util.BrowserLauncher
4+
import com.mussonindustrial.embr.common.Embr
5+
import javax.swing.JMenuItem
6+
7+
class EmbrHelpMenuItem : JMenuItem("Embr Help") {
8+
init {
9+
toolTipText = "Launch the Embr user manual in a web browser"
10+
icon = EmbrDesignerIcons.help
11+
addActionListener { BrowserLauncher.openURL(Embr.DOCUMENTATION_URL) }
12+
}
13+
}

0 commit comments

Comments
 (0)