Author Archives for Anthony Pavia

















Serial Control Plus is an application that comes pre-installed on all JNIORs. It allows you to connect either serially or through TCP to a JNIOR, and give it commands to activate the JNIOR’s I/O. This post will explain how to setup and use Serial Control Plus on your JNIOR.

To start, as mentioned before Serial Control Plus is already pre-installed on all JNIORs. To activate it, you need to go to the JNIOR DCP. This can be accessed by either right clicking the JNIOR in the JNIOR Support Tool and going to Tools/Open Web Page, or by typing the JNIOR’s IP address into the URL of your computer’s web browser. Once on the DCP, you’ll go to the applications section on the configuration tab and click the checkbox next to Serial Control Plus and reboot your JNIOR. This will allow you to use Serial Control Plus on that JNIOR.

Once you have activated the Serial Control Plus application on your JNIOR, you can now send commands to the JNIOR through it. We are going to open the command line from the support tool to activate commands on this JNIOR for this example. To open the command line from the Support Tool, you’ll go to the Tools bar at the top of the Support Tool and select Command Line.

Once you have the command line open, you’ll need to configure the settings of the command line to send commands to the JNIOR. To have the right settings to communicate with the JNIOR, we need to select how we communicate to the JNIOR. Serial Control Plus can communicate with the JNIOR two ways. Either you can connect to the JNIOR with a serial connection or a TCP connection.

To connect serially with the JNIOR, you need to plug a serial cable into the Aux Port of the JNIOR. Once you do that we need to select the correct settings in the command line window. As in the picture below, next to the connect button for the connection type you’ll select COM, baud type is 9600, Data bits is 8, Stop bits is 1, Parity is none, and hardware/software is set to none. For the Option drop-down, select all the choices.

To connect through TCP, you just need the JNIOR to be on the network to connect. As in the picture below next to the connect button for the connection type you’ll select TCP/IP, you’ll set the JNIOR’s IP, and the Port to connect on is 9202. For the Option drop-down, select all the choices.

Once you’ve decided on your connection type and configured the command line accordingly, you should be able to send commands to the JNIOR. Here are commands for controlling and monitoring I/O.

Controlling I/O

The following commands can be used to close, open and pulse outputs.

cX: Close the output (relay is “on” closing the contact)
where x = 1 through 8 for the internal relay outputs on the JNIOR
and x = +1 through +8 for the external relay outputs on the 4 Relay Output Expansion Modules

oX: Open the output (relay is “off” opening the contact)
where x = 1 through 8 for the internal relay outputs on the JNIOR
and x = +1 through +8 for the external relay outputs on the 4 Relay Output Expansion Modules

p=yyy Pulse duration (milliseconds) and is used in conjunction with the ‘close’ or ‘open’ command


Examples:
c2p=1000 close output 2 for 1 second and then open again
c+2p=1000 close output 10 for 1 second and then open again
o3p=10000 open output 3 for 10 seconds and then close again
c* Close all outputs at the same time (includes internal and external)
o* Open all outputs at the same time (includes internal and external)


These commands can be abbreviated and used in combination, such as:
c1 close relay output 1
c+1 close relay output 9 (first output on first expansion module)
c+5 close relay output 13 (first output on second expansion module)
c1+1+5 combination of the above all in one command
c1234 close relay outputs 1 through 4
c1368 close relay outputs 1, 3, 6, 8
o125 open relay outputs 1, 2, 5
c1+1p=1000 close relay outputs 1, 9 and pulse each for 1 second simultaneously

Monitoring I/O

Whenever an input (or output) changes status (low-to-high or high-to-low), the following is sent out by the JNIOR:

INx=1 Input x (1 – 8) has gone high (on)
OUTx=1 Output x (1 – 16) has gone high (on)
INx=0 Input x (1 – 8) has gone low (off)
OUTx=1 Output x (1 – 16) has gone low (off)


The default setting for the Registry Key AppData/Serial_Control/SendCounts is false. If you change this key to true and reboot, with each message stating the input status, a count value will also be included. Whenever an input changes status (low-to-high or high-to-low), the following is sent out by the JNIOR:

INx=1,yyy Input x (1 – 8) has gone high (on), counter value = yyy
INx=0,yyy Input x (1 – 8) has gone low (off), counter value = yyy


Note: These monitoring messages are sent out individually over the serial port or Ethernet. The JNIOR does not report the status of more than one input/counter in the same message.

With this, you should now be able to use Serial Control Plus to control and monitor a JNIOR’s I/O!

The series 3 JNIORs used the Java Applet as a GUI. Years ago the browsers stopped supporting Java Applets due to security concerns. You are no longer able to open the series 3 Java Applet GUI in the browser. You can still access it by launching it locally since it is installed as part of the JNIOR Support Tool. The security concerns over java applets are not present when launching the Java Application locally. Here is how to access the Java Applet for a JNIOR.

NOTE: The Java Applet GUI should only be used with series 3 JNIORs.

First, make sure you have the JNIOR support tool downloaded. Here is a link for it.

Name Version Release Date Size MD5
JNIOR Support Tool v7.10 Jul 15 2020 13 MB 4fd5a1b0617a59a7f6802663ec3f789e

Once you have downloaded the Support Tool, you’ll want to open it and find the JNIOR you wish to access the Java Applet for in the Beacon tab. Then by right clicking it, you’ll then go to Tools/Classic Monitor, Configuration, Control Application option and select it.

After selecting this for, the Java Applet for your JNIOR should open.

Name Version Release Date Size MD5
Grapher v4.1 Jun 18 2020 788 KB 75e992513636e0c45c7aa7f71d8c1303

! Fixed bugs.

Grapher 4.0 - A change in navigation August 23, 2019

Name Version Release Date Size MD5
Grapher v4.0 Aug 22 2019 783 KB a7967fd9878171af565ff1faf677ae14

Several changes affecting how you navigate in time.

Added the ability to change the configured duration view of the graph. Previously the default was a hard-coded 4 hours. Once you brought up the graph you could have used the mouse wheel to zoom out or zoom in. The graph would always load showing the past 4 hours.

In this version we removed the ability to zoom in and out using the mouse wheel. We also removed dragging the graph in the future or past using the mouse. This was done because it was noted that too often the mouse is accidentally being used to modify the graph view.

Since the mouse interaction was removed to zoom and pan, we added buttons below the graph the facilitate the ability to move forward and backward in time.

The fast step buttons move the graph forward or backward by the entire duration. If you are looking at today, as shown below, pressing the fast backwards button will show you yesterday. The single step buttons move by 1/4 the duration. Looking at 24 hours and pressing the single step buttons will move the graph by 6 hours.

Name Version Release Date Size MD5
JNIOR Support Tool v7.10 Jul 15 2020 13 MB 4fd5a1b0617a59a7f6802663ec3f789e

! Corrected an issue where opening an update project would encounter a non-empty temp folder.

+ Added the ability to open multiple Device files

+ Added the ability to open multiple Macro files

+ Added the ability to open multiple Update Projects

! addresses an issue where the Update Notification was always being shown at startup, even when the most recent version was on the JNIOR.

JNIOR Support Tool 7.9 May 16, 2019

Name Version Release Date Size MD5
JNIOR Support Tool v7.9 May 16 2019 13 MB 2c22f26f4e87d724e0f7c918095eb8c0
  • The JNIOR Support Tool version 7.9 addresses an issue with new installs.  The C:\INTEG\JNIOR Support Tool directory was not getting created upon install.  This would prevent the Support Tool from opening.
  • Also in this update is a selection for the new Barco Series 4 projector.

One of the useful things about Tasker, is that it can include communicating with other devices within its tasks. This is possible by including a networks action, but before those actions can be used devices need to be added to a Tasker application. This post will explain how to create devices to be used in actions for tasks.

To start, we’ll begin by going to the devices tab of the Tasker application.

Here we can select the “Add Device” button which brings up a dialog box to add a device to the current workspace. 

In this dialog, two things need to be defined to create the device. This first value needed is the name of the device. The second value needed is the device type, which can either be an Ethernet or SNMP Device. Depending which Device type you choose changes the what information you can configure for the device after creating it.

Creating an Ethernet Device

If the Ethernet Device type was selected, the configurable option for the created device should look like this:

Two values of the Ethernet Device need configured in order to use it in a Task action, the IP Address and the TCP Port values. These need to be set to the IP Address and TCP Port values on the device, so that when they are used in actions, the JNIOR can properly communicate with the device. Another post has an example of using a TCP Send with an Ethernet Device.

Creating an SNMP Device

If the SNMP Device type was selected, the configurable option for the created device should look like this:

Three values of the SNMP Device need configured in order to use it in a  Task action which are, the IP Address, the UDP Port, and the Community Name. These are needed for the JNIOR to connect to the SNMP Device and you obtain them from the SNMP Device. A different post shows how to use a SNMP Device with an SNMP Trap.

With this, you should have created devices in Tasker that can be implemented in actions.

Schedules add functionality to task created in Tasker because it gives them the ability to place a time for when the tasks should occur. This post will explain the different types of schedules that can be created, and how they can configured.

Creating a Schedule

To start, we’ll go to the Schedule tab of the Tasker application in order to create a schedule.

After going to the Schedule tab, the first thing to do is add a schedule in the Schedule tab. After giving it a name, the new schedule will have 3 parts to it, the name, the rules, and the task to be executed from it.

The Name section already has the name you gave it when it was created, but that section also gives you the options to edit the name, have the schedule enabled or not by checking the checkbox, or delete the schedule.

The Task Name section allow you to select a task from a drop down list or manual enter the name of a task that will execute at when the schedule is set.

Adding Rules to a Schedule

The Schedule Rules section is what allows the schedule to determine the times that which the Task selected in the Task Name section will execute. Clicking the “Add Rule” button opens the rules dialog box.

When adding rules to a schedule, there are 4 types of rules you can add, reboot, sunrise, sunset, and schedule. The first three are similar, where you simply click on the Schedule Type and select sunrise, sunset, or reboot from the drop down list. This will make it so that the task you set with that schedule will run at either sunrise, sunset, or on reboot, depending on which one you picked and no other options need to be selected. 

Picking the Schedule option in the Schedule Type, lets you set the custom options for creating a schedule. The first option after the Schedule Type is the Start On option. This lets you select what day the schedule will begin to activate. Once the date reaches the day you selected it will run that schedule from then on. 

After that is the Start Time option. This allows you to select from the hours and minutes in a 24 hour format when the scheduled task will begin. 

Next is the Repeat Every option, which lets you how often in a time interval you want the task to reoccur. It adds a End Time option once a value has been added to the Repeat Every option. It can be set similar to the Start Time option that will decide when the Task will stop repeating by. 

After that is the Date Selection Type option, which lets you choose between letting the task execute None (Which is one time), Daily, Weekly, or Monthly. Depending on what option you choose, this changes the Recur Every option. Picking the None option  will make the task only run on the Start On date you picked. The Daily option will let you choose how many days between the task should run. The Weekly option will let you choose what days of the week the task should run. The Monthly option will let you choose what days of  month the task should run.

With this, you should be able to assign schedules for any task you have created.

The Tasker Application has a lot of different functionality built into its actions, but it also can handle logic too. One of those actions that you can create in Tasker is the If block action. This post will create two If blocks in Tasker, one being the If block and the other being the If/else block.

Create an If Block

To start, create a new workspace by going to the File Drop-Down and selecting new. After that, go into the Task tab of Tasker and select the “Add Task” button. Once you name your Task, you’ll click on it and select the “Add Action” button. This will bring up the Action Dialog box. Here we need to select the If block action in the Control Structures section of the Action Dialog box. Once we have our If block in the task, we’ll want to add one more action inside of the If block by selecting the “Add Action” button inside of the If block. Here we’ll also select the Pulse Output Relay action.

Setting the If Block Actions

Now that our actions have been added to the Task, we can now configure them to do what we want. For this example, we are going to configure the if statement to activate when Input 1 goes high. To check if the input is high, for the If block value field we’ll enter din[1].state == 1.

With this, the If block will Pulse Output Relay 1 for 1 second if Input 1 is high.

Create an If/Else Block

In the workspace, go into the Task tab of Tasker and select the “Add Task” button. Once you name your Task, you’ll click on it and select the “Add Action” button. This will bring up the Action Dialog box. Here we need to select the If/else block action in the Control Structures section of the Action Dialog box. Once we have our If block in the task, we’ll want to add two more actions inside of the If/else block by selecting the “Add Action” button inside of the If/else block. Here we’ll select the Pulse Output Relay action inside the conditional part of the If/else block , and the Pulse Output Relay action inside for the “else” part of the If/else block.

Setting the If/Else Block Actions

With the actions being added to the Task, we can now configure them to do what we want. For this example, we are going to configure the If block to Pulse Output Relay 1 when Input 1 goes high, and Pulse Output Relay 2 when Input 1 isn’t high. For the If/else block value field, to check if the input is high we’ll enter din[1].state == 1. For the first Pulse Output Relay, we’ll set the channel to 1, and for the other pulse relay we’ll set the channel to 2.

With this, the If/else block should Pulse Output Relay 1 for 1 second when Input 1 is high, and Pulse Output Relay 2 for 1 second when Input 1 is not high.

The Tasker application has been made to handle lots of different types of functionality. This post will go into looking at the Set Variable action and then a logic example using the set variable to send fire alarm macros to different devices. Doing this example requires the environmental or temperature sensor to be completed.

The Set Variable Action

The Set Variable Action allows you to create a variable in the first value field of the action, and then assign it a value in the second value field. This is useful when using other control structure actions, such as loops or if blocks.

Fire Alarm Example

Note: Values in this example will most likely differ from your.

In this Example, before we create the Fire Alarm Task, we need to create the devices that will receive the macro we send for the fire alarm, like shown above. You’ll also need to the have the Cinema application updated to your JNIOR. To start, go to the device tab and click the “Add Device” button. Add as many Devices as you have JNIORs you plan to send a Fire Alarm macro to. Each device you’ll enter its IP address, and TCP Port number from the Cinema Server Client Registry Key. The Registry Key is under AppData/Cinema/CinemaServerClient/TcpPort in the registry tab of the JNIOR’s webpage.

After creating the devices to send macros to, you’ll start creating the task. It starts with the Set Variable actions. These actions are used to define the values of two temperatures received from a temperature sensor or environmental sensor, and the variable used to know when to end the Task. We define the two Temp variables to values our temperature or environmental sensors are picking up, and the other variable will be set to true as our conditional until the macros. Once these values are defined, a while loop is added to the task. This will check if the conditional of the variable we created is true, which is how we set it so that it will always be looping. After that, we add an if block and set the conditional of it.  This is to determine if the movie theater’s screen room is a lot more hotter then the rest of the movie theater, indicating a possible fire. When the temperature difference is above 20 degrees (indicating a possible fire), using as many TCP send actions for devices you have, it sends to those JNIORs to run their FireAlarm macros. It also sets the conditional for the while loop to false, so the task no longer loops, and ends.

With this, you should have a task that constantly monitors the temperatures of two temperature/environmental sensors, and when the difference between them is greater then 20, sends out the fire alarm macros and ends the task.

Signals and Triggers are very useful tools in the Tasker application. Signals are I/O values on either the JNIOR or a compatible expansion module with the JNIOR. Triggers are reactions to when a signal occurs. This post will explain all the parts of the Signal and Trigger tabs in detail.

The Signal Tab

To start, the Signal tab is needed in order to use the Triggers tab, so we’ll start with Signals. Signals are I/O values on either the JNIOR or a compatible expansion module with the JNIOR. Each Signal values has what device the Signal is coming from, the channel number of the value, and then the type of value its getting. These can be used either in a Trigger or to set/evaluate the value of a variable defined in a task.

The Trigger Tab

The Trigger is a reaction to a Signal value. Triggers are made up of first, a Trigger value that watches a Signal to react to, along with what the Signal value needs to be to activate the Trigger. Second, is the Reset value that monitors a Signal like the Trigger value does. The Reset value when activated resets the Trigger so it can activate again. A Trigger won’t activate after it has activated once already, unless the reset value activates. Lastly, a task name is selected as the task to run when the Trigger value is met and the Trigger activates.

These steps show show you how to properly create a Signal and Trigger.

Example

The example below will show you how to make a Trigger activate when Relay output 1 on the JNIOR goes High.

We’ll create two Signals with the first ones values being: Device is the Relay Output states of a JNIOR, the channel 1, and the type being the state. The second ones values would be: Device is the Relay Output states of a JNIOR, the channel 2, and the type being the state.

Afterwards you would begin configuring a Trigger. The Trigger values would be the Example Signal we created, and its trigger value will be high. For the Reset values, we’ll select the Example Signal Reset we created as the Reset value, and its reset value will be high as well. Lastly, we’ll pick whichever task we want to run during the Trigger activation as the Task value. In this example my task I created is Example Task.

When working in Tasker, at some point you might need to move a workspace from one PC to another. You also might want to have the workspaces saved on your PC rather than in the JNIOR. The upload and download functionality in Tasker allows you to save workspaces as JSON files, as well as upload them onto the Tasker application as a workspace.

To start, its good to know what a workspace is. It is configuration settings for the Tasker application. While the current configuration is the one you currently view when accessing the Tasker Web Page, other tasker configurations could have been created and sit in the background. Upload and Download are ways to access these different configurations that have been created and saved.

To download a workspace from Tasker, you’ll want to click on the file tab and in the drop-down select the download option.

When selecting the download option, it immediately begins the download of a JSON file. This is the file that can be uploaded as the chosen JSON file for the current workspace.

To upload a workspace from Tasker, you’ll want to click on the file tab and in the drop-down select the upload option.

Clicking the upload option  will prompt you if you wish to continue. This is because uploading a workspace gets rid of the one you are currently making. Make sure that if you want to keep your current workspace configuration, that you save it before you continue with the upload. Uploading will place the JSON file into the workspace folder inside the Tasker directory on the JNIOR.

The Tasker Application has a lot of different functionality built into its actions, but it also can handle logic too. One of those actions that you can create in Tasker is the loop action. This post will create two loops in Tasker, one being the for loop and the other being the while loop.

Create a For Loop

To start, create a new workspace, by going to the File Drop-Down and selecting new. After that, go into the Task tab of Tasker and select the “Add Task” button. Once you name your Task, you’ll click on it and select the “Add Action” button. This will bring up the Action Dialog box. Here we need to select the for loop action in the Control Structures section of the Action Dialog box. Once we have our loop in the task, we’ll want to add two more actions inside of the loop by selecting the “Add Action” button inside of the loop. Here we’ll also select the Pulse Relay action and the Sleep action.

Setting the For Loop Action Values

Now that we have our Task’s actions added, we can configure their values to what we want, which is to have our loop use a variable to pulse relays 1 – 4 for 2 seconds, with a .5 second delay between them. To do this, we’ll start by setting the variable in the first field value of the loop action, which in this example we will make $a. The other loop fields are to set how many times the loop will repeat, and since we want to pulse 4 relays we’ll set the second loop field to 1 and the third to 4.

Next for the Pulse Relays action, the first field will be replaced with the variable we created in the first field of the loop action, $a. After that we’ll want the second field set to 2, since we want it to pulse for 2 seconds. Lastly, we’ll set the sleep action to 2.5 seconds so that no relay is pulsing at the same time as another, and has .5 second delay between each other.

With this, your loop should be configured properly. Now it will loop through output relays 1 – 4 on your JNIOR, pulsing each relay for 2 seconds with a .5 second delay between each relay pulsing.

Creating a While Loop

For creating a while loop, in your workspace go into the Task tab of Tasker and select the “Add Task” button. Once you name your Task, you’ll click on it and select the “Add Action” button. This will bring up the Action Dialog box. Here we need to select the while loop action in the Control Structures section of the Action Dialog box. Once we have our loop in the task, we’ll want to add two more actions inside of the loop by selecting the “Add Action” button inside of the loop. Here we’ll also select the Pulse Relay action and the Sleep action.

Setting the While Loop Action Values

Now that we have our Task’s actions added, we can configure their values to what we want, which is to have our loop use a variable to check the state of input 1, and if its high (equal to 1) to pulse output 1 for 1 second with a one second delay between it every time it pulses. To do this, we’ll start by setting the variable in the field value of the loop action to din[1].state == 1 to check if input 1’s state is high. Then we’ll set the pulse output of whichever relay we wish to pulse for 1 second, with a second sleep so it will pulse for 1 second, and then wait 1 second.

Tasker is able to perform many different things, one of which is the ability to log information. Tasker contains a Logger tab that can create Logger profiles to record data. These Loggers tell what needs logged and which file it should be stored in when used in a task. This post will show how to record temperature values from an environmental sensor using Tasker.

In order to setup Tasker to record temperatures from a environmental sensor, there are a few step we need to take:

  1. First we need to create Signals in the Signal Tab. These will be used in the Logger tab to record their values.
  2. Next we create a Logger in the Logger tab. Here we use the Signals to document their values in log files.
  3. After creating the Logger, we now go to the Task tab and create a task that includes the Logger that was just created.
  4. Finally, once the task is created, in the Schedule tab we create a Schedule that runs the task for however often we wish to record the environmental sensor values.

Creating Signals

Signals are Input/Output values from the JNIOR or from expansion modules compatible with the JNIOR. In this example we are using a environmental sensor expansion module to record these values. First we’ll create a new workspace using the File Drop-Down. Then in the Signals tab, After clicking the “Add Signal” button, we’ll create a new signal called Temp_1. After the Signal is created, it has three value fields to fill out. The first one is the Signal drop down value, where you will pick where we are getting the Input/Output values from. As stated previously, we are grabbing these values from a environmental sensor, so we’ll select it from the drop down options. Next is the channel for the environmental sensor. Each channel represents a different environmental sensor. Since we are only using one sensor, we’ll select channel 1. Lastly is the variable type, and while the values you can get are either Celsius, Fahrenheit, or humidity, we are going to be logging the info in Fahrenheit, so that’s what we’ll chose.

Creating a Logger

Now that the Signal is created, we can create a Logger to record those values to a file. We’ll go to the Logger tab and select “Add Logger”. After naming the Logger, three values fields need filled in. The first is the Log File Path, which is the where in the folder directory of the JNIOR the files  recording the Signal information will be saved. In our example, we used the file path /flash/tasker/logging/temps-{{date(YYMMDD)}}.csv. The ending of the filepath is to specify the date as the name of the .csv file being saved. After this is the Columns field which create columns of the recorded information in the log file. This example will just have {{Temp_1}} which will specify the Signal we previously created as the information being logged. Lastly, is the file retention count. All this does is decide how many files of information Logger will create before overwriting previous files, so if the number is set to 10, once Logger has created 10 files of information, next file it create will overwrite the previous 1st Logger file. This example uses 30.

Creating a Task

With the Logger being created, we now need a task that uses our Logger so it will activate and record the data we want. In the Task tab, we’ll select the button “Add Task”. Once the Task is named, click on its name and select “Add Action”. The Action Dialog box will appear which has all the actions a task can perform. Here we’ll select the Log Profile action, which we can use to call the Logger we created in the Task. Once the action is added, the only value to enter is the name of the Logger we just created. This will now make the Task activate the Logger each time its run.

Creating a Schedule

Lastly, having a task activate a Logger is good, but it will only record when you activate it. Adding the task to a schedule will allow us to record these values on a time interval. Going to the Schedule tab, you’ll click on the “Add Schedule” button. After naming the Schedule, there will be two options to fill out. The first one appears by selecting the “Add Rule” button, which will bring up the Rule Dialog box where you can configure when the schedule is timed. For this example we will set the Schedule type to schedule so we have more options rules to pick from. The start on date is  the date for whenever you want the schedule to begin activating the task you set with it. Next is the Start Time, Repeat Every, and End Time options (End Time appears after you edit Repeat Every). We set both the Start and End Time to Midnight so that the interval is always running. Repeat Every is set to 2.5 minutes so that with the Start and End time set, the task will run every 2.5 minutes for an entire day. For the Date Selection type, we used daily in this example so we can set the Recur Every option to 1. This will make the task run every day. Lastly, after completing the Rules for the schedule, we add a task that we want the schedule to activate based off the rules we set for it, which will be the task we created in the Tasks tab.

Once the Schedule is completed, the Logger will now record the Signal values of the environmental sensor as values in a .csv file from a task that is scheduled to run every 2.5 minutes, every day.

Sometimes you may want something to occur at a specific time of day or sometimes you may want something to occur with certain events during the day.  The “events” in this case are the Sunrise and Sunset events.  These events occur at different times during the day based on the time of year and your geographic location.  Tasker grabs the location from the JNIOR registry, and uses it to calculate the time of sunrise and sunset for that day.

We had written a custom application called the SunEquation Application to accomplish the following before implementing the logic in Tasker.  You may ask, “What is the SunEquation application and can Tasker handle the same logic?”  The answer is that Tasker is a heavyweight application that uses more processing power than a simple custom application that was designed to do one thing.  The SunEquation application was written for someone that was already running two other applications and the addition of Tasker might slow those other applications down.  Specifically the DMX application is sensitive to performance and that application is one of the two applications that were in use.

Below is an example to send commands to the Cinema Server Client  in Cinema to run macros. This requires having the support tool downloaded along with the applications Tasker and Cinema updated on to your JNIOR.  There are several steps that will need to be configured to get this to work.

  1. Create Macros in the Support Tool that you wish to have run at Sunrise and Sunset.
  2. Create a Task that will perform the action of requesting that the macros get run in Cinema. The action in these Tasks will depend on a Device object that will need to be created as well.
  3. Create Schedules that will execute the Tasks based on Sunrise and Sunset.

Create the Macros

To start, we need to create the macros in the support tool that will be run in Cinema as requested by Tasker. Opening the Support tool, under the macro tab, we can create a macro for sunrise and sunset, and we’ll rename them to that as well, adding whatever actions the macros should perform. This then needs to be published to the JNIOR.

Create a Device

After creating the macros, we need to create a device in Tasker. We first need to create a new workspace using the File Drop-Down before creating a device. After that we’ll go to the device tab in Tasker, and click on the “Add Device” button. This will add a device in Tasker. Now all you need to do is set the IP Address of the JNIOR and the Tcp port number to the same value as the Cinema Server Client port number registry key under AppData/Cinema/CinemaServerClient.

This is an example picture, the values required differ between JNIORs

Create Tasks

The next thing needed to have the macro request sent to the Cinema application is to create TCP send tasks. To create a task you go to the Task tab in Tasker, and select “Add Task”. Once you’ve named the task, you’ll click on the task and select “Add Action”. This will make the action dialog box appear, which contains all the actions you can make your task perform. There you’ll select the TCP Send action. You will then add the device that was just created, and the message should be “run” followed by the name of the macro that was created in the support tool. The macro names also have an \r\n on the end of them to signal where the end of the command is when its being sent. Since we created two macros in the support tool for sunrise and sunset, we’ll create two tasks for them, one for sunrise, and one for sunset. Both of these tasks will be using the same device.

Create a Schedule

To setup macros to run at sunrise and sunset, you’ll now want to go to the schedule tab of the Tasker application. Once you add a schedule, name it, and select the task to run in that schedule, you’ll want to click on “Add Rule”button . As in the example below, since we previously created two tasks for sunrise and sunset, we’ll want two schedules for those tasks, one for sunrise and one for sunset.

Once you’ve clicked “Add Rule”, the Rule Dialog box will open. Here we’ll want to select the Schedule Type option, which will present 4 options. Two of those options will be Sunrise and Sunset.

After selecting the sunrise option for the sunrise task, and the sunset option for the sunset task, those tasks will now run at those times.

Once this is all done, whenever it is sunrise/sunset for the day, Tasker will send commands to Cinema that will execute the macros created in the support tool.

This application was made in order to find out what time during the day that sunrise and sunset will occur. Once these times are found, actions can be set to activate at those times.

When using the SunEquation application, it needs to be configured after it is updated to a JNIOR. Updating a JNIOR with the application creates new registries in the AppData registry folder. Within the SunEquation registry folder, two registry keys are found, which are latitude and longitude. These need to be filled out with the location information of where the JNIOR will be installed. It uses this information in order to calculate what time the sun will rise and set.

Two macros command will be sent to the Cinema application, at sunrise and sunset. These need to be called “sunrise” and “sunset” when created in the support tool. Here the macros can be configured to complete whatever the user wishes to occur on the JNIOR during those times. Even though the application is set to create macros commands, it can be made to complete other actions instead of macros if needed. If you’d like different actions to occur at sunrise and sunset, contact INTEG support and we’ll alter the application to suit your needs.

The SunEquation application creates a log called sunriseandsunset.log that you can find in the folder tab of your JNIOR’s DCP. In this log, you can view when actions are completed at sunrise and sunset. You can also see at midnight when the it calculates the times for sunrise and sunset for that day. Any errors the application is having can also be viewed in this log, which can help troubleshoot those issues.

Some Series 3 are not up to date enough to appear in beacon, which can make it very confusing to update to a new version in the Support Tool. Here is how to update a JNIOR that isn’t showing in the Support Tool.

The first thing you’ll want to do is make sure that you have the support tool downloaded from INTEG’s website. Here is the link to download the support tool. You’ll also want the newest all-in-one update project for the Series 3, which will include the newest version of the OS inside of it. The Series 3 all-in-one is located here.

Once you have both of those downloaded, you’ll want to open the support tool and go to the update tab.

Once you are at the update tab, you want’ll to click the open project button and select the series 3 all-in-one update project you downloaded earlier and open it in the update tab.

Now that the all-in-one is uploaded to the update tab, we want to publish it to the JNIOR. Click the Publish button and a dialog will appear to select JNIOR’s from Beacon to update. The issue here would be that the JNIOR you need to update won’t appear since its version doesn’t have beacon. What to do instead is to type the IP of the JNIOR in the space provided.

Once you have typed the JNIOR’s IP into the IP space provided, click OK and the JNIOR should begin to update. Afterwards, it should now appear in beacon.

This post goes over how to handle needing more then 4 inputs, when there are only 4 on the JNIOR (This application creates three new inputs). Using multiple inputs being activated at the same time, we can count them as new inputs. This application will be able to send macros using these additional inputs we create.

Below is the code for the full application. Make sure to have properly setup the project before using this code, to learn how to setup a custom java application on the JNIOR, a link is here.

import com.integpg.system.IoEvent;
import com.integpg.system.Iolog;
import com.integpg.system.JANOS;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;

public class TestProject {

    //Used to get List of IO events
    public static Iolog _iolog = new Iolog();
    //Used to keep track of when to refresh Iolog
    public static long refresh_timestamp = 0;
    //Tracks commands that have the correct states to send a macro
    public static int timesCalled;
    //String Array for loading symbol
    public static String[] loadSymbol = {"|", "/", "-", "\\"};
    //loadSymbol's tracker
    public static int loadTracker = 0;

    public static long timeCheck = System.currentTimeMillis();
    
    public static ArrayList<IoEvent> ioevents = new ArrayList<>();

    public static ArrayList<EventInfo> ioEventClass = new ArrayList<>();

    public static class EventInfo {

        int macronumber;
        long timestamp;
        int mask;
        int inputstates;

        EventInfo(int macroNumber, long timeStamp, int Mask, int state) {
            // initialize the input variable from main 
            // function to the global variable of the class 
            macronumber = macroNumber;
            timestamp = timeStamp;
            mask = Mask;
            inputstates = state;
        }
        
        void setMacroNumber(int macroNumberChange) {
            
            if (macroNumberChange == 1) {
                macronumber = 1;
            } else if (macroNumberChange == 2) {
                macronumber = 2;
            } else if (macroNumberChange == 4) {
                macronumber = 3;
            } else if (macroNumberChange == 8) {
                macronumber = 4;
            } else if (macroNumberChange == 3) {
                macronumber = 5;
            } else if (macroNumberChange == 6) {
                macronumber = 6;
            } else if (macroNumberChange == 12) {
                macronumber = 7;
            }
            

            
        }



    }

    public static void createConnectionAndSendMacro(String address, int port) throws InterruptedException {

        String macro = "";
        byte[] macroToBytes;

        try {
            Socket socket = new Socket(address, port);
            DataOutputStream out = new DataOutputStream(socket.getOutputStream());
            System.out.print("Connected " + new Date() + " ");

            for (int index = 0; ioEventClass.size() > index;) {
                
                System.out.println("timeCheck: " + timeCheck + " - Event timestampe " + ioEventClass.get(index).timestamp + " = " + (ioEventClass.get(index).timestamp -  timeCheck));

                if ((ioEventClass.get(index).timestamp -  timeCheck) >= 50) {
                    
                    timeCheck = System.currentTimeMillis();

                    if (ioEventClass.get(index).macronumber > 0 && ioEventClass.get(index).macronumber < 8) {
                        macro = "run Input " + ioEventClass.get(index).macronumber;
                        System.out.println(" " + macro);
                        macroToBytes = macro.getBytes();
                        out.write(macroToBytes);
                        ioEventClass.remove(index);
                        ioevents.remove(index);
                    } else {
                        System.out.println("Macro number invalid, no execution");
                        ioEventClass.remove(index);
                        ioevents.remove(index);
                    }
                } else {

                    System.out.println("Macro is too new, holding info in ioEventClass to get complete macro.");
                    break;
                    
                }
            }

            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static void getEventInfo() {

        _iolog.refresh(refresh_timestamp);
        IoEvent[] ioEvents = _iolog.getInputEvents();

        //loop to send macros if their states match correct input states
        for (int index = ioEvents.length - 1; index >= 0; index--) {

            IoEvent ioEvent = ioEvents[index];

            if (ioEvent.states >= 0 && ioEvent.states < 13) {
                int statesTurnedHigh = ioEvent.states & ioEvent.mask;

                if (0 != statesTurnedHigh) {

                    System.out.println("statesTurnedHigh; " + statesTurnedHigh);
                    if (0 < ioevents.size()) {
                        System.out.println("path 1");
                        IoEvent lastIoEvent = ioevents.get(ioevents.size() - 1);
                        long elapsedtime = ioEvent.timestamp - lastIoEvent.timestamp;
                        System.out.println("elapsedtime; " + elapsedtime);
                        if (elapsedtime >= 20) {
                            System.out.println("path 1-1");
                            ioevents.add(ioEvent);
                            addEventInfoToList(ioevents.get(ioevents.size() - 1));
                        } else {
                            System.out.println("path 1-2");
                            lastIoEvent.states |= statesTurnedHigh;
                            ioEventClass.get(0).setMacroNumber(lastIoEvent.states | statesTurnedHigh);
                        }
                    } else {
                        System.out.println("path 2");
                        ioevents.add(ioEvent);
                        addEventInfoToList(ioevents.get(ioevents.size() - 1));
                    }
                }

                refresh_timestamp = ioEvents[0].timestamp;

            }
        }

        if (!ioevents.isEmpty()) {

            System.out.println("ioevents; " + ioevents.size());
            for (int increment = 0; increment < ioevents.size(); increment++) {
                System.out.println("ioevents info " + ioevents.get(increment).states + " " + ioevents.get(increment).timestamp);
            }

        }

    }

    public static void addEventInfoToList(IoEvent iologEvent) {
        int macroNumber = 0;

        if (iologEvent.states == 1) {
            macroNumber = 1;
        } else if (iologEvent.states == 2) {
            macroNumber = 2;
        } else if (iologEvent.states == 4) {
            macroNumber = 3;
        } else if (iologEvent.states == 8) {
            macroNumber = 4;
        } else if (iologEvent.states == 3) {
            macroNumber = 5;
        } else if (iologEvent.states == 6) {
            macroNumber = 6;
        } else if (iologEvent.states == 12) {
            macroNumber = 7;
        } else {
            System.out.println("Macro number not correct");
        }

        EventInfo eventInfoPlaceholder = new EventInfo(macroNumber, iologEvent.timestamp, iologEvent.mask, iologEvent.states);
        ioEventClass.add(eventInfoPlaceholder);

    }

    public static void main(String[] args) throws InterruptedException {

        //Making sure all lists are cleared of previous information
        _iolog.refresh();
        ioevents.clear();
        ioevents.clear();
        
        //Making sure that Registry keys are set to make socket connection
        String IP = JANOS.getRegistryString("IpConfig/IPAddress", "Went to default");
        int portNumber = JANOS.getRegistryInt("AppData/Cinema/CinemaServerClient/TcpPort", 5000);
        if (portNumber == -1) {
            System.out.println("TcpPort number not set properly, closing application.");
            System.exit(0);
        }
        System.out.println(IP);
        System.out.println(portNumber + "\n");

        while (true) {

            System.out.print("\r                                                                                                            ");
            if (loadTracker > 3) {
                loadTracker = 0;
            }
            System.out.print("\r" + loadSymbol[loadTracker]);
            loadTracker++;
            System.out.print(" [" + timesCalled + "] ");
            timesCalled++;

            Thread.sleep(300);
            getEventInfo();
            if (ioevents.isEmpty() == false) {
                createConnectionAndSendMacro(IP, portNumber);
            }

        }

    }

}

After setting up the project, the first part of the project is calling the right imports and setting the global variables. The _iolog is where to get our list of IOevents to see what inputs are being selected at any time. The refresh_timestamp is so that when we call the _iolog, its only getting the list from the last time we called the _iolog and not the entire list every time its called. The timesCalled is to track how many successful macros have been sent. The loadSymbol is used to create a a loading symbol as the JNIOR waits for inputs to be triggered. Lastly, the loadTracker is to show when the end of the loadSymbol list has been reached to rest it.

import com.integpg.system.IoEvent;
import com.integpg.system.Iolog;
import com.integpg.system.JANOS;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;

public class TestProject {

    //Used to get List of IO events
    public static Iolog _iolog = new Iolog();
    //Used to keep track of when to refresh Iolog
    public static long refresh_timestamp = 0;
    //Tracks commands that have the correct states to send a macro
    public static int timesCalled;
    //String Array for loading symbol
    public static String[] loadSymbol = {"|", "/", "-", "\\"};
    //loadSymbol's tracker
    public static int loadTracker = 0;

    public static long timeCheck = System.currentTimeMillis();
    
    public static ArrayList<IoEvent> ioevents = new ArrayList<>();

    public static ArrayList<EventInfo> ioEventClass = new ArrayList<>();

Moving on we have multiple functions, starting with EventInfo.

The EventInfo constructor grabs all the _iolog information along with the states of the inputs being converted from 1 to 7 . The function setMacroNumber grabs the current state of the inputs on the JNIOR and converts it to the input number 1 – 7

        EventInfo(int macroNumber, long timeStamp, int Mask, int state) {
            // initialize the input variable from main 
            // function to the global variable of the class 
            macronumber = macroNumber;
            timestamp = timeStamp;
            mask = Mask;
            inputstates = state;
        }

        void setMacroNumber(int macroNumberChange) {
            
            if (macroNumberChange == 1) {
                macronumber = 1;
            } else if (macroNumberChange == 2) {
                macronumber = 2;
            } else if (macroNumberChange == 4) {
                macronumber = 3;
            } else if (macroNumberChange == 8) {
                macronumber = 4;
            } else if (macroNumberChange == 3) {
                macronumber = 5;
            } else if (macroNumberChange == 6) {
                macronumber = 6;
            } else if (macroNumberChange == 12) {
                macronumber = 7;
            }
                        
        }

The next function is createConnectionAndSendMacro. This function takes the address and port number you specify and create a tcp connection on it. This will be connected to the Cinema application to create the macros from the inputs triggered. This function goes through the list of IOevents and sends through the tcp connection any of the 1 – 7 states its looking for. Then it closes the connection.

 public static void createConnectionAndSendMacro(String address, int port) throws InterruptedException {

        String macro = "";
        byte[] macroToBytes;

        try {
            Socket socket = new Socket(address, port);
            DataOutputStream out = new DataOutputStream(socket.getOutputStream());
            System.out.print("Connected " + new Date() + " ");

            for (int index = 0; ioEventClass.size() > index;) {
                
                System.out.println("timeCheck: " + timeCheck + " - Event timestampe " + ioEventClass.get(index).timestamp + " = " + (ioEventClass.get(index).timestamp -  timeCheck));

                if ((ioEventClass.get(index).timestamp -  timeCheck) >= 50) {
                    
                    timeCheck = System.currentTimeMillis();

                    if (ioEventClass.get(index).macronumber > 0 && ioEventClass.get(index).macronumber < 8) {
                        macro = "run Input " + ioEventClass.get(index).macronumber;
                        System.out.println(" " + macro);
                        macroToBytes = macro.getBytes();
                        out.write(macroToBytes);
                        ioEventClass.remove(index);
                        ioevents.remove(index);
                    } else {
                        System.out.println("Macro number invalid, no execution");
                        ioEventClass.remove(index);
                        ioevents.remove(index);
                    }
                } else {

                    System.out.println("Macro is too new, holding info in ioEventClass to get complete macro.");
                    break;
                    
                }
            }

            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

Next is the getEventInfo function. This function creates a list of IOevents from the _iolog. Then by going through the list and seeing which values are not within 20 milliseconds of each other and depending on where they are in the list, grab those values from the IOevent and use them in other functions to determine which macro to run.

    public static void getEventInfo2() {

        _iolog.refresh(refresh_timestamp);
        IoEvent[] ioEvents = _iolog.getInputEvents();

        //loop to send macros if their states match correct input states
        for (int index = ioEvents.length - 1; index >= 0; index--) {

            IoEvent ioEvent = ioEvents[index];

            if (ioEvent.states >= 0 && ioEvent.states < 13) {
                int statesTurnedHigh = ioEvent.states & ioEvent.mask;

                if (0 != statesTurnedHigh) {

                    System.out.println("statesTurnedHigh; " + statesTurnedHigh);
                    if (0 < ioevents.size()) {
                        System.out.println("path 1");
                        IoEvent lastIoEvent = ioevents.get(ioevents.size() - 1);
                        long elapsedtime = ioEvent.timestamp - lastIoEvent.timestamp;
                        System.out.println("elapsedtime; " + elapsedtime);
                        if (elapsedtime >= 20) {
                            System.out.println("path 1-1");
                            ioevents.add(ioEvent);
                            addEventInfoToList(ioevents.get(ioevents.size() - 1));
                        } else {
                            System.out.println("path 1-2");
                            lastIoEvent.states |= statesTurnedHigh;
                            ioEventClass.get(0).setMacroNumber(lastIoEvent.states | statesTurnedHigh);
                        }
                    } else {
                        System.out.println("path 2");
                        ioevents.add(ioEvent);
                        addEventInfoToList(ioevents.get(ioevents.size() - 1));
                    }
                }

                refresh_timestamp = ioEvents[0].timestamp;

            }
        }

        if (!ioevents.isEmpty()) {

            System.out.println("ioevents; " + ioevents.size());
            for (int increment = 0; increment < ioevents.size(); increment++) {
                System.out.println("ioevents info " + ioevents.get(increment).states + " " + ioevents.get(increment).timestamp);
            }

        }

    }

The addEventInfoList converts the current input states to an input number 1 – 7 similar to setMacroNumber, but returning all the IOevent information unlike setMacroNumber does.

public static void addEventInfoToList(IoEvent iologEvent) {
        int macroNumber = 0;

        if (iologEvent.states == 1) {
            macroNumber = 1;
        } else if (iologEvent.states == 2) {
            macroNumber = 2;
        } else if (iologEvent.states == 4) {
            macroNumber = 3;
        } else if (iologEvent.states == 8) {
            macroNumber = 4;
        } else if (iologEvent.states == 3) {
            macroNumber = 5;
        } else if (iologEvent.states == 6) {
            macroNumber = 6;
        } else if (iologEvent.states == 12) {
            macroNumber = 7;
        } else {
            System.out.println("Macro number not correct");
        }

        EventInfo eventInfoPlaceholder = new EventInfo(macroNumber, iologEvent.timestamp, iologEvent.mask, iologEvent.states);
        ioEventClass.add(eventInfoPlaceholder);

    }

Lastly, the main function of the program starts by choosing which IP and tcp port number need to be connected to, by having it set in the registry. Then after the loadingSymbol is setup, the functions getEventInfo and createConnectionAndSendMacro get called.

 public static void main(String[] args) throws InterruptedException {

        //Making sure all lists are cleared of previous information
        _iolog.refresh();
        ioevents.clear();
        ioevents.clear();
        
        //Making sure that Registry keys are set to make socket connection
        String IP = JANOS.getRegistryString("IpConfig/IPAddress", "Went to default");
        int portNumber = JANOS.getRegistryInt("AppData/Cinema/CinemaServerClient/TcpPort", 5000);
        if (portNumber == -1) {
            System.out.println("TcpPort number not set properly, closing application.");
            System.exit(0);
        }
        System.out.println(IP);
        System.out.println(portNumber + "\n");

        while (true) {

            System.out.print("\r                                                                                                            ");
            if (loadTracker > 3) {
                loadTracker = 0;
            }
            System.out.print("\r" + loadSymbol[loadTracker]);
            loadTracker++;
            System.out.print(" [" + timesCalled + "] ");
            timesCalled++;

            Thread.sleep(300);
            getEventInfo2();
            if (ioevents.isEmpty() == false) {
                createConnectionAndSendMacro(IP, portNumber);
            }

        }

    }

When using protocols with a JNIOR, you can disable the login to not be prompted when using those protocols through the DCP. This is NOT RECOMMENDED because its makes the JNIOR unsecured.

To disable the login requirement for the JANOS Management Protocol or the JNIOR Protocol you will use the DCP. Once the web page is opened click on the Configuration tab. Then click on the Protocol section near the bottom of the column on the left. Now make your login selections using the checkboxes under the appropriate protocol.

Once you un-check that box, the login will no longer be needed for ANY connection to that port.

This post explains how to send and receive messages from a System Message Pump. There is a previous post showing how to create a System Message Pump. This example uses the additional applications the previous example did and should be included when the project for this application is created. That post can be accessed here. Please look over that post first as this one uses code from and references that post. 

package mqttmessagepump;

import com.integpg.system.JANOS;
import com.integpg.system.MessagePump;
import com.integpg.system.SystemMsg;
import java.util.Json;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MQTTMessagePump implements Runnable {

    private static final MQTTMessagePump THIS = new MQTTMessagePump();
    private static final MessagePump MESSAGE_PUMP = new MessagePump();
    private static final Vector LISTENERS = new Vector<>();

    private static Thread _thread;

    /**
     * Singleton constructor
     */
    private MQTTMessagePump() {
    }

    /**
     * adds a listener that will be alerted of all received messages. The
     * listener will be responsible for determining if the message has meaning
     * to them
     *
     * @param listener
     */
    public static void addListener(MessagePumpListener listener) {
        synchronized (LISTENERS) {
            LISTENERS.addElement(listener);
        }
    }

    /**
     * starts our message pump engine.
     */
    static void start() {
        if (null == _thread) {
            _thread = new Thread(THIS);
            _thread.setName("message-pump-engine");
            _thread.setDaemon(true);
            _thread.start();
        }
    }
    
    //completes the message 1600 after being sent from the MQTT application
    
    public static void completeMessage1600 (String recievedJsonInfo) {
        
        Json holdinginfo = new Json(recievedJsonInfo);
        String[] arrayofkeys = holdinginfo.keyarray();
        String subscribecheck = holdinginfo.getString(arrayofkeys[0]);
        String topicscheck = holdinginfo.getString(arrayofkeys[1]);
        String infocheck = holdinginfo.getString(arrayofkeys[2]);
        System.out.println(arrayofkeys[0] + ": " + subscribecheck + ", " + arrayofkeys[1] + ": " + topicscheck + ", " + arrayofkeys[2] + ": " + infocheck);
 
    }
    
    //creates the message that will be sent to the other program to complete
    
    public static SystemMsg createMessage (int messageType, byte [] messageInfo) {
        
        SystemMsg returnMsg = new SystemMsg();
        returnMsg.type = messageType;
        returnMsg.msg = messageInfo;
        
        System.out.println(returnMsg);
        return returnMsg;
        
    }
    
    //adds a value to the Json object that we be sent as part of the message to the other program
    
    public static Json addToJson(Json Jsonadd, String valuename, Object jsonvalue) {
    
        Jsonadd.put(valuename, jsonvalue);
        return Jsonadd;
        
    }
    
    @Override
    public void run() {
        System.out.println("open the message pump and start monitoring.\r\n");
        MESSAGE_PUMP.open();

        for (;;) {
            // read all messages from the message pump
            SystemMsg systemMsg = MESSAGE_PUMP.getMessage();

            // we must repost as fast as we can
            MESSAGE_PUMP.postMessage(systemMsg);

            // notify all of our listeners

            if (systemMsg.type == 1600) {

                System.out.println();
                System.out.println("Recieved command 1600.\n");
                String jsoninfo = new String(systemMsg.msg);
                completeMessage1600(jsoninfo);           

            }
            
             Json thirdjson = new Json();
             addToJson(thirdjson, "Message", "publish"); 
             addToJson(thirdjson, "Topic", "jnior/(Your JNIOR's Serial Number)/set/digital/outputs/1/state");
             addToJson(thirdjson, "Payload", "true");
             SystemMsg returnMsg = createMessage(1600, thirdjson.toString().getBytes());
             MESSAGE_PUMP.postMessage(returnMsg);    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(MQTTMessagePump.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

    }

    /**
     * Exposes the postMesssage method of the System MessagePump
     *
     * @param msg the message the will be sent to the system message pump
     */
    public static void postMessage(SystemMsg msg) {
        MESSAGE_PUMP.postMessage(msg);
    }

}

To start, before we can create an application to talk with the MQTT application, we need to configure the MQTT application to hook up with a broker. To download the update project for the MQTT application, click here. An online broker is needed in order to use the MQTT protocol. Free ones can be found online, but for this example the free hivemq broker is being used, which can be found here. This broker is public and if you want to use MQTT protocols outside this example, a secure one should be used. The only information you need to have in the MQTT application is the host URL and the port number. Then click connect and make sure the MQTT application is running on the JNIOR.

Once the MQTT application is properly configured, the MQTT coding can be made. After creating a Message Pump program that could send messages to and from another application, the next step is to create and receive messages between the message pump and the MQTT application using the MQTT protocol. To start, we can create a new program that will interact with the MQTT application. We’ll call this program our MQTTMessage program. This sample program will only send a message to toggle an output, but could be edited to do more.

For this example, only the application containing the message pump from the previous example is needed. In the previous example, the message being sent between the applications we Json Objects. The message that needs to be created is formatted as this:

{
"Message" : "publish",
"Topic" : STRING,
"Payload" : STRING
}

The MQTT application also uses Json Objects to communicate through the messages. Looking at the for loop when creating the message to send, The message has to be “publish”, because when you are sending a message to the MQTT application, you are publishing information on a specified topic. The topic is the keyword that other devices are subscribed to. If a device is subscribed to that topic, then the device will receive the payload of the message. The payload is the actual data of the message that any device subscribed to the related topic will receive.

The run function in the previous example’s MessagePumpEngine application is replaced with a new for loop. When creating the message, it needs to be reformatted to match the Json message the MQTT application needs to receive. The message ID number has to be 1600, since that is the message ID the MQTT application is listening to. In this example, the Json object is created as:

{
  "Message" : "publish",
  "Topic" :jnior/"Your JNIOR's IP"/set/digital/outputs/1/state,
  "Payload" : true
}

The topic is to specify the JNIOR, what channel you want to toggle, and if its opened or closed. The payload sets that channels state to true, closing that output.

for (;;) {
            // read all messages from the message pump
            SystemMsg systemMsg = MESSAGE_PUMP.getMessage();

            // we must repost as fast as we can
            MESSAGE_PUMP.postMessage(systemMsg);

            // notify all of our listeners

            if (systemMsg.type == 1600) {

                System.out.println();
                System.out.println("Recieved command 1600.\n");
                String jsoninfo = new String(systemMsg.msg);
                completeMessage1600(jsoninfo);           

            }
            
             Json thirdjson = new Json();
             addToJson(thirdjson, "Message", "publish"); 
             addToJson(thirdjson, "Topic", "jnior/(Your JNIOR's Serial Number)/set/digital/outputs/1/state");
             addToJson(thirdjson, "Payload", "true");
             SystemMsg returnMsg = createMessage(1600, thirdjson.toString().getBytes());
             MESSAGE_PUMP.postMessage(returnMsg);    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(MQTTMessagePump.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

The if statement checking the (systemMsg.type == 1600) is there because the JNIOR being used to publish the message is also listening the for messages on the message pump too, and those messages will have the same message ID 1600.

if (systemMsg.type == 1600) {

                System.out.println();
                System.out.println("Recieved command 1600.\n");
                String jsoninfo = new String(systemMsg.msg);
                completeMessage1600(jsoninfo);           

            }

Inside that if statement is a call to the completeMessage1600() function. That function will grab the data out of the Json object from the message and print it out for you to see.

public static void completeMessage1600 (String recievedJsonInfo) {
        
        Json holdinginfo = new Json(recievedJsonInfo);
        String[] arrayofkeys = holdinginfo.keyarray();
        String subscribecheck = holdinginfo.getString(arrayofkeys[0]);
        String topicscheck = holdinginfo.getString(arrayofkeys[1]);
        String infocheck = holdinginfo.getString(arrayofkeys[2]);
        System.out.println(arrayofkeys[0] + ": " + subscribecheck + ", " + arrayofkeys[1] + ": " + topicscheck + ", " + arrayofkeys[2] + ": " + infocheck);
 
    }

With these changes to the previous message pump application and MQTT application configuration, after running this example program you should be able to create and send messages through the MQTT application using the MQTT protocol.

At some point you may want your JNIOR to always have an output close or pulse, and when you reboot a JNIOR, they get reset. This post will show you how to have your JNIOR automatically close outputs from startup. If you’re looking for different information on the DCP, or don’t know how to access it, you can look here.

To start, make sure you are able to access the DCP (Dynamic Configuration Page) of your JNIOR. Then you’ll want to go to the Configuration tab of the DCP. Here we will be accessing the outputs section.

Here is where you can close or pulse outputs on startup. Simply add a zero to the Initial Action section for whichever channel you wish to close, and it will now always close that input on startup. To pulse the output on startup, simply add any positive value and it will pulse for that many milliseconds instead.

Name Version Release Date Size MD5
MQTT v3.0 Jan 21 2020 490 KB f6a905a5bdc100eae728d250da4c7439

New functionality has been added to the MQTT 3.0 application.

When changing settings in the MQTT application, a reboot will not be needed to get the new settings to take hold. Before when you wanted to change any setting on the MQTT application, after they were saved you would have to reboot to update those changes. Now when you click save change at the top right, the changes are applied at that moment.

Changed how information was processed so that when its retrieved, it will not be as slow when it was using the contain() method. The contain() method referenced more information in an inefficient way. Now the program is configured to get the most relevant information in a much cleaner manner, making it faster.

When a change is made to a device group, the previous name is unsubscribed from. Before this update, when changing the device group name, it would still be subscribed to the previous device group name.

When a change is made to a device host name, the previous name is unsubscribed from. Before this update, when changing the host name in the support tool, it would still be subscribed to the previous device group name.

If the MQTT application is not running on the JNIOR, the application page will be grayed out until it is started again. This is to prevent people from believing they are connected to the broker without the MQTT application running, since you can’t interact with the broker from the JNIOR without that application running.

Implemented the username and password on the MQTT application page so if it is required, it will not establish a connection until that information is entered. This allows a connection to brokers that require usernames and passwords.

Added MQTT functionality for the ten-volt module, 4-20 module, temperature probes, and environmental modules. You can now publish output percentages to the ten-volt and 4-20 modules, and subscribe to all of these listed modules.