Page 1 of 1

Have some fun making a state-machine tray app

Posted: Fri Nov 13, 2020 10:00 pm
by Jafadmin
demo_tray.tar.gz
(19.15 KiB) Downloaded 79 times

This is a simple C app that can compile against either gtk+-2.0 or gtk+-3.0. You will need your devx.sfs file for this and make sure your gtk-2.0 (or 3.0) include directories are in /usr/include (hint: symlinks are friends) . Check the 'MakeFile' then just run 'make' from a terminal in the MakeFile folder.

This little C file makes a tray app called demo_tray that displays a red or green icon depending on whether or not the file "/tmp/traydemo.txt" exists.

Left-clicking the tray icon will delete the file, Right-clicking will recreate the file. But what's the fun in that, right?

Start the tray app from one terminal window and let it run. Now open another terminal window and execute these commands a few times:

Code: Select all

root# touch /tmp/traydemo.txt 
root# rm /tmp/traydemo.txt

.. and watch the blinky light. It will follow the state of the traydemo.txt file.

So give the C source file a good read, and use it as a starting place to do your own tray app experiments. For instance, instead of making direct system calls, you can use the "system()" function to execute a yummy Xdialog bash script :thumbup: :itswhatialwaysdo:

(You might think this 'state-machine' stuff as arcane, but I got semi-notorious for re-writing the kermit file transfer protocol back in the late 80's for DOS. File transfer protocols are built on the science of state machines. So are networking/communication protocols.)


Re: Have some fun making a state-machine tray app

Posted: Fri Nov 13, 2020 10:17 pm
by Jafadmin

The g_timeout_add(interval, ChkStatus, NULL) function in main() installs our ChkStatus() function which is the actual state machine. The interval (global unsigned int) variable (set at 3000 miliseconds (3 seconds)) controls the polling interval.

So you create all your state machine wizardry in the ChkStatus() function. (What if 5 states & 5 different colored icons? 🤔 )

The 'send' state machine/switcher from my kermit rewrite:

Code: Select all

while(TRUE)          // Do this as long as necessary
{
    switch(state)
    {
        case 'D': state = sdata();  break;   // Data-Send state
        case 'F': state = sfile();  break;   // File-Send
        case 'Z': state = seof();   break;   // End-of-File
        case 'S': state = sinit();  break;   // Send-Init
        case 'B': state = sbreak(); break;   // Break-Send
        case 'C': return (TRUE);             // Complete
        case 'A': close(fd); return (FALSE); // "Abort"
        default:  return (FALSE);            // Unknown, fail
    }
}

Re: Have some fun making a state-machine tray app

Posted: Wed Dec 02, 2020 4:25 pm
by rockedge

this is great!
I am using it now to put an indicator on the tray when using X10 remote light switches that I wrote in python that will use /tmp/traydemo.txt as a flag to indicate ON/OFF state. So I can close the switch GUI and still have a lamp state indicator and when I restart the X10 light switch GUI it will open in the correct position.

I am doing a similar thing using a PHP web based lamp switch.


Re: Have some fun making a state-machine tray app

Posted: Sat Dec 05, 2020 5:03 pm
by rockedge

So using the example I was inspired to add some lines to make a nice little utility.

There are several lamp fixtures around the house that can be turned on and off plus can be dimmed and brightened remotely through the use of X10 modules. I have a CM19a X10 USB transceiver that is connected to a DELL desktop. Plugged in outlets there are X10 lamp modules and a X10 receiver/appliance switch. This receiver takes messages sent from the CM19a and transmits these signals through the house's electrical system where the individually addressed lamp modules can pull the message meant for it. The lamp module then can turn on what is plugged into it on,off,dim,brighten.
Also throughout the house there are X10 remote wireless motion detectors that the CM19a can receive a message from, there is motion or it is light or dark. This capability is used by Zoneminder running on the DELL which handles local and network cameras and recording events alarmed by these motion detectors. This lightens the CPU loads by not having to use the motion detection analysis from Zoneminder for each individual camera.

The CM19a is used through a Python2 driver which runs as a daemon.

So for lamp switches I wrote a small GUI in python2 to control the individually addressed lamp modules. Also I have written a switch panel that is a web based page in PHP and can be used via the Internet or LAN.

The problem was the python switches did not have state persistence. So if I turn lets say lamp module B9 ON, and I closed the GUI, when I restarted the program it was always off and did not reflect the state the lamp module B9 was actually in. Same with the PHP web version. Now with this utility an indicator of On/Off state is resident in the tray. Both switches written in the different languages can use the same flag file called /tmp/switch_on.txt which will trigger the correct response from the utility x10_tray and display the red or green indicator.

Python switch GUI example:

Code: Select all

#!/usr/bin/env python2
import pathlib
import os
import requests
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from pathlib import Path

class SwitcherWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="X10 B9 Switch")
        self.set_border_width(10)

        hbox = Gtk.Box(spacing=6)
        self.add(hbox)
        
        button = Gtk.Button.new_with_label("B9 Dim")
        button.connect("clicked", self.on_click_me_dimb9)
        hbox.pack_start(button, True, True, 0)  
        
        button = Gtk.Button.new_with_label("B9 Bright")
        button.connect("clicked", self.on_click_me_brightb9)
        hbox.pack_start(button, True, True, 0) 
        
        switch = Gtk.Switch()
        switch.connect("notify::active", self.on_switch_activated,"9")
        filename = Path('/tmp/switch_on.txt')
        if filename.is_file():
    # file exists
            switch.set_active(True)
            filename.touch(exist_ok=True)
        else:
	    switch.set_active(False)
        
        hbox.pack_start(switch, True, True, 0)
 
    def on_switch_activated(self, switch, gparam,name):
        URL = "http://192.168.0.5:8008/"
      
        if switch.get_active():
            state = "on"
            params = (
            ('house', 'B'),
            ('unit', name),
            ('command', state),
)
            response = requests.get(URL, params=params)
            filename = Path('/tmp/switch_on.txt')
            filename.touch(exist_ok=True)  # will create file, if it exists will do nothing
        else:
            URL = "http://192.168.0.5:8008/"
            state = "off"
            params = (
                     ('house', 'B'),
                     ('unit', name),
                     ('command', state),
                     )
            response = requests.get(URL, params=params)
            os.remove('/tmp/switch_on.txt')  #remove state indicator flag file
        print("Button", name, "was turned", state)


    def on_click_me_dimb9(self, button):
         URL = "http://192.168.0.5:8008/"
         state = "dim"
         params = (
            ('house', 'B'),
            ('unit', "9"),
            ('command', state),
            )
            
         response = requests.get(URL, params=params)
         print("\"B9 Dim\" button was clicked")
         
    def on_click_me_brightb9(self, button):
         URL = "http://192.168.0.5:8008/"
         state = "bright"
         params = (
            ('house', 'B'),
            ('unit', "9"),
            ('command', state),
            )
            
         response = requests.get(URL, params=params)
         print("\"B9 Bright\" button was clicked")      
  
win = SwitcherWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

This is the C code I added to the example to allow me to turn on and off the lamp module by right or left clicking the state indicator icon in the tray.

Code: Select all

////// The following two functions handle left & right clicks on the tray icon
void tray_icon_on_left_click(GtkStatusIcon *status_icon,  gpointer user_data) 
{
	char DeleteCmd[80];	// Make a shell command to delete the testfile
	
	strcpy(DeleteCmd, "rm ");	
	strcat(DeleteCmd, test_file_name);
        system(DeleteCmd); 
    CURL *curl;
    CURLcode res;
    curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL,"http://192.168.0.5:8008/?house=B&unit=9&command=OFF");
    curl_easy_perform(curl);
       		
}

void tray_icon_on_right_click(GtkStatusIcon *status_icon, guint button,  guint activate_time, gpointer user_data)
{
	char TouchCmd[80];	// Recreate the file using 'touch'
	
	strcpy(TouchCmd, "touch ");	
	strcat(TouchCmd, test_file_name); 
        system(TouchCmd);    
    CURL *curl;
    CURLcode res;
    curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL,"http://192.168.0.5:8008/?house=B&unit=9&command=ON");
    curl_easy_perform(curl);

    // Or .. you could just have it:
    // gtk_main_quit();
}
////// End clicks  ...

then to compile the program added the CURL linked library to the MakeFile:

Code: Select all

gcc -o x10_tray x10_tray.c -w `pkg-config --libs gtk+-3.0` `pkg-config --cflags gtk+-3.0` -lcurl
Screenshot(34).png
Screenshot(34).png (188.47 KiB) Viewed 1475 times