This Community site is new! Please help us build a community around the JNIOR.
Sign up, and help share your knowledge. Please sign-up even if you do not plan to post as a sign of support.
If there is evidence of a demand we will continue to develop the content here.

CineKey Cinema UI

You got ideas? Let's hear 'em. Here we can talk about your experiences programming on the JNIOR or items that you may wish to have INTEG assist you with.
Post Reply
bscloutier
Posts: 401
Joined: Thu Sep 14, 2017 12:55 pm

CineKey Cinema UI

Post by bscloutier » Mon Dec 04, 2017 12:59 pm

We've developed a User Interface (UI) to work alongside of our Cinema Application for the JNIOR. Since then we have refactored its name to CineKey and I've added an outer loop to the CineConnect object allowing it to reconnect as needed. I've also set it up so the target IP address is configurable.

What if you have multiple auditoriums and want to execute macros in any one of those from the same UI? Well we can of course do it. Let's think about it and see what we can do to make it happen.

So here's the plan:
  1. Configure IP addresses as a comma-separated list.
  2. Start a CineConnect instance for each auditorium (IP address).
  3. Insert a Location Menu ahead of the Main Menu so you can select the Auditorium.

bscloutier
Posts: 401
Joined: Thu Sep 14, 2017 12:55 pm

Re: CineKey Cinema UI

Post by bscloutier » Mon Dec 04, 2017 1:30 pm

So let's start with the configuration. I am going to make one stipulation. You must use the same port (default 9610) for all of the target JNIORs. We can leave such complexity for the day when someone finds a good logical reason for it.

Now the host string that is stored in the Registry Key /Cinekey/Host or retrieved from the first parameter on the command line can now be a comma-separated list.

Okay so you start to think that we will need to parse the host string. Well that is true but there is a trick. The following instantiates CineConnect for each host. The added code almost seems trivial.

    // Connections
    static CineConnect[] conns = null;
    static CineConnect conn = null;
    
    public static void main(String[] args) throws Throwable {
        
        String host;
        int port;
        
        // obtain port
        if (args.length > 1) {
            port = Integer.parseInt(args[1]);
            JANOS.setRegistryString("Cinekey/Port", Integer.toString(port));
        }
        else
            port = JANOS.getRegistryInt("Cinekey/Port", 9610);
        
        // Obtain host
        if (args.length > 0) {
            host = args[0];
            JANOS.setRegistryString("Cinekey/Host", host);
        }
        
        // Establish connections
        String[] hosts = JANOS.getRegistryKey("Cinekey/Host");
        if (hosts == null || hosts.length == 0)
            System.exit(1);
        
        conns = new CineConnect[hosts.length];
        for (int n = 0; n < hosts.length; n++) {
            System.out.println(hosts[n]);
            conns[n] = new CineConnect(hosts[n], port);
            new Thread(conns[n]).start();
        }
        
        // temporary selection
        conn = conns[0];
bruce_dev /> cinekey 10.0.0.64,localhost
10.0.0.64
localhost
>{"Message":"GetInfo"}
>{"Message":"GetMacroList"}
<{"Message":"GetInfoResponse","Information":"Cinema v2.4.0.477"}
<{"Message":"GetMacroListResponse","MacroList":[]}
host: localhost:9610
java/io/IOException: Unable to connect to remote host

Execution interrupted by console Ctrl-C

bruce_dev />
Well, I don't have Cinema running on this development unit but that is easily rectified. Here too we temporarily leave the UI to control the first host. We'll work on that next. The trick here is that the Registry can already handle comma separated lists of parameters. The parsing is done for us.

bscloutier
Posts: 401
Joined: Thu Sep 14, 2017 12:55 pm

Re: CineKey Cinema UI

Post by bscloutier » Mon Dec 04, 2017 1:37 pm

With this UI running we can see that it starts the two threads.
bruce_dev /> thd
  41 classes loaded.  157.1 KB
   1 JVM instances active.
Console/10.0.0.20:23847/cinekey         2.816   mem: 96.3K  pid: 5  stk: 2.4K  obj: 23.0K  cls: 43.1K
         1.471  1: main, java/lang/Thread, main, RUN
         0.761  2: Thread-0, java/lang/Thread, main, RUN
         0.024  3: Thread-1, java/lang/Thread, main, WAIT

bruce_dev />
Instead of printing the configured hosts how about if we name the threads using the associated host as follows.

        // Establish connections
        String[] hosts = JANOS.getRegistryKey("Cinekey/Host");
        if (hosts == null || hosts.length == 0)
            System.exit(1);
        
        conns = new CineConnect[hosts.length];
        for (int n = 0; n < hosts.length; n++) {
            conns[n] = new CineConnect(hosts[n], port);
            new Thread(conns[n], hosts[n]).start();
        }

So it is a bit more obvious.
bruce_dev /> cinekey &

bruce_dev /> thd
  36 classes loaded.  136.4 KB
   1 JVM instances active.
Console/10.0.0.20:23847/cinekey         2.482   mem: 129.2K  pid: 6  stk: 1.7K  obj: 20.5K  cls: 37.8K
         1.044  1: main, java/lang/Thread, main, RUN
         0.824  2: 10.0.0.64, java/lang/Thread, main, RUN
         0.016  3: localhost, java/lang/Thread, main, WAIT

bruce_dev />

bscloutier
Posts: 401
Joined: Thu Sep 14, 2017 12:55 pm

Re: CineKey Cinema UI

Post by bscloutier » Mon Dec 04, 2017 2:04 pm

Multi host configuration... Done.

Next we will create a new context for our UI. This will follow the Main Menu structure. Let's call it the Selector. We will interpose the Selector context right after the Splash Screen and before the Main Menu. But we will only expose it when there is more than one targeted Cinema application. It's job will be to select the one that we are going to control.

When there is only one target we select it and just run with it.

        // default selection for single target
        conn = conns[0];

Any key gets us out of the Splash Screen but now the destination depends on the configuration. If there are 2 or more target Cinema applications then we use the new menu (context 3). Note that since the initial project I've added a check here to avoid a blank menu of macros as well. Nothing happens if you have a single target and no macros defined. I'll need the same check in the new context.

    static void splash_key(int key) {
        
        // we will need to select the target
        if (conns.length > 1) {
            context = 3;
            return;
        }

        // otherwise go to Main Menu if there are macros defined.
        macros = conn.getMacros();
        if (macros != null && macros.length > 0 && macros[0] != null)
            context = 1;
    }

bscloutier
Posts: 401
Joined: Thu Sep 14, 2017 12:55 pm

Re: CineKey Cinema UI

Post by bscloutier » Mon Dec 04, 2017 2:10 pm

Now if we are exiting the Main Menu we may need to return to the new context. You might want to select a different Cinema application. The Left_Arrow (keypad "D") exits the Main Menu to return to the Splash Screen. Well if we are using the Selector context that becomes our destination.

    static void mainmenu_key(int key) {

        // main menu
        switch (key) {
            case 'D':   // return from menu
                context = (conns.length > 1 ? 3 : 0);
                break;

Simple enough. We have effectively inserted the new context between the Splash Screen and the Main Menu for cases where there are multiple target IP addresses.

bscloutier
Posts: 401
Joined: Thu Sep 14, 2017 12:55 pm

Re: CineKey Cinema UI

Post by bscloutier » Mon Dec 04, 2017 2:29 pm

The new context which is arbitrarily 3 is basically a copy of the Main Menu context. Here we use a _targ suffix to create independent sel_targ and top_targ variables. This keeps all of the menu navigation even though the list of targets is likely to be short not requiring the paging keys. You can see in this where selection of a Cinema application that has not defined macros is prohibited.

    // ---------- Selector Menu -----------------------------------------------
    static int sel_targ = 0;
    static int top_targ = 0;
    
    static void selmenu() {
        int n, line;
        
        // Display Main Menu
        screen_clear();
        
        // proper scrolling
        if (sel_targ > top_targ + 3)
            top_targ = sel_targ - 3;
        else if (sel_targ < top_targ)
            top_targ = sel_targ;
        
        for (line = 0, n = top_targ; n < conns.length && line < 4; n++, line++) {
            screen_write(line, 0, n == sel ? "\u007e" : " ");
            screen_write(line, 1, hosts[n]);
        }
        
        screen_update();
    }
    
    static void selmenu_key(int key) {

        // main menu
        switch (key) {
            case 'D':   // return from menu
                context = 0;
                break;
            case 'B':   // select prior (or wrap to bottom)
                if (--sel_targ < 0)
                    sel_targ = conns.length - 1;
                break;
            case 'H':   // select next (or wrap to top)
                if (++sel_targ >= conns.length)
                    sel_targ = 0;
                break;
            case 'G':   // page down
                top_targ += 4;
                if (top_targ >= conns.length - 4)
                    top_targ = conns.length - 4;
                if (sel_targ < top_targ)
                    sel_targ = top_targ;
                break;
            case 'A':   // page up
                top_targ -= 4;
                if (top_targ < 0)
                    top_targ = 0;
                if (sel_targ > top_targ + 3)
                    sel_targ = top_targ + 3;
                break;
            case 'E':
            case 'C':
                conn = conns[sel_targ];
                macros = conn.getMacros();
                if (macros != null && macros.length > 0 && macros[0] != null)
                    context = 1;
                break;
        }
        
        // Refresh if we are going to display the Splash Screen
        if (context == 0)
            screen_refresh();
    }

bscloutier
Posts: 401
Joined: Thu Sep 14, 2017 12:55 pm

Re: CineKey Cinema UI

Post by bscloutier » Mon Dec 04, 2017 4:11 pm

Okay. After a couple of tweaks and a little debugging I've achieved the goal. Here we connect to just two JNIORs both running the latest Cinema application. You will see that both have their own unique set of macros. I'll execute one on each.





You can connect to many JNIORs, I believe that units are limited to 48 socket connections but don't quote me on that. I can look it up.

bscloutier
Posts: 401
Joined: Thu Sep 14, 2017 12:55 pm

Re: CineKey Cinema UI

Post by bscloutier » Mon Dec 04, 2017 6:04 pm

I've attached the latest code and executable.
Attachments
CineConnect.java
(4.82 KiB) Downloaded 29 times
Cinekey.java
(13.36 KiB) Downloaded 28 times
Cinekey.jar
(19.62 KiB) Downloaded 29 times

bscloutier
Posts: 401
Joined: Thu Sep 14, 2017 12:55 pm

Re: CineKey Cinema UI

Post by bscloutier » Tue Dec 05, 2017 8:55 am

Here are some issues/questions to ponder. Maybe someone has some ideas.

--------------------
1. The last status for a macro displays when you select a macro and move to its Status context. It's confusing if is says "Complete". Does that mean that it just ran and is already complete. You might not realize that you are just seeing the status and still have to select Run to execute the macro.

I could:
  • Include a timestamp with the status somehow. So it says "Competed" but it might be obvious then that it was hours ago or another day.
  • Not display old status (older than a certain amount of time) when entering the macro status context. Then it wouldn't say anything and you would feel compelled to select Run then.
--------------------
2. The GetInfoRequest returns a string with the Cinema application version. This used to be shown on the Splash Screen. But now that you can access multiple JNIORs running the Cinema application and you select which JNIOR after the Splash Screen, it no longer shows unit specific information. Should I add an information option to the Main Menu so you can see that sting?

What do you think?

bscloutier
Posts: 401
Joined: Thu Sep 14, 2017 12:55 pm

Re: CineKey Cinema UI

Post by bscloutier » Wed Dec 06, 2017 9:50 am

I am going to implement a timeout. If the UI is idle for 2 minutes it will revert to the Splash Screen context. The approach is simple. It is described in the Programming Tips section.

To get that done we define the TIMEOUT. Here we want 2 minutes which is 120000 milliseconds.

    // State
    static int context = 0;
    static final int TIMEOUT = 120000;

First we need to create the timer and initialize the timeout (line 5).

    public static void main(String[] args) throws Throwable {
        
        String host;
        int port;
        long timer = JANOS.uptimeMillis() + TIMEOUT;
        
        // obtain port
        if (args.length > 1) {

Now in the main loop when a keypad press is detected we will reset the timer to the new point in the future (line 30). Also as we process the context we check to see if the timer expires (lines 6 and 7). If it does we simply move to the Splash Screen context (0).

        // loop to process keypad input and periodically update the splash screen
        while (context >= 0) {
            int key;
            
            // process timeout and return to Splash Screen if expired
            if (context != 0 && JANOS.uptimeMillis() > timer)
                context = 0;
            
            // Update display according to context.
            switch (context) {
                case 0:
                    splash();
                    break;
                case 1:
                    mainmenu();
                    break;
                case 2:
                    status();
                    break;
                case 3:
                    selmenu();
                    break;
            }
            
            // check for keypad entry and perform action
            try {
                key = auxin.read();
                
                // update timeout timer
                timer = JANOS.uptimeMillis() + TIMEOUT;
                
                // keypad is context dependant
                switch (context) {
                    case 0:
                        splash_key(key);
                        break;
                    case 1:
                        mainmenu_key(key);
                        break;                        
                    case 2:
                        status_key(key);
                        break;
                    case 3:
                        selmenu_key(key);
                        break;
                    default:
                        System.out.println(key);
                }
                
            } catch (IOException ioe) {
                if (!ioe.getMessage().equals("timeout"))
                    throw ioe;
            }
                
        }

And that's it. I can demonstrate with a video but as you might imagine it would be a very boring video. It works.
Attachments
CineConnect.java
(4.82 KiB) Downloaded 30 times
Cinekey.jar
(19.79 KiB) Downloaded 28 times
Cinekey.java
(13.74 KiB) Downloaded 28 times

Post Reply