lunes, 30 de septiembre de 2013

Migrating Anjuta Projects from version 1.x to 2.x

(versión en español)

Anjuta is a tool which for years has been very useful for me. While the essential tool for design Gtk+ interfaces is Glade, Anjuta aditionally provides an easily integration between different elements necessary for development (glade, the scintilla editor, etc).

That said, we must have clear that nothing prevents us to develop the various components of our program separately: we can maintain a glade session to modify our interface windows, and a separated text editor so we can modify our code. With an additional terminal, we can run and debug partial compilations of our program. Anjuta provides all these operations in an integrated manner.



To further facilitate this integration, Anjuta 1.x provided a set of functions that facilitated some tasks (lookup_widget (), pe). In newer versions of Anjuta this support ceased to exist, because it prioritizes the utilization of libglade library. This library allows to link the program and the screen developed with glade at run time. In Anjuta 1.x, the relationship between the interface and the code is determined at compilation time, using a set of additional functions provided by Anjuta itself (defined in the file support.c).




Getting Familiar with Anjuta 2.x interface


Once you install the new version of Anjuta, some obvious differences with the previous version become evident. The most significant may be that the key combination "Alt + G" does not start Glade as before. In exchange, the way you interact with Glade is much more natural now: we just simply double click on the file that defines the interface of our program.



Another difference is that now Anjuta has several predefined profiles for build our project. Every profile is just a combination of compiler flags needed to build a project optimized for execution (-O3), optimized for debug (-g), etc.. When you build your project, choose the type of profile you want.

Opening an "old" Anjuta 2.x project

For each option available to open an old project, I personally prefer: "File -> New -> 5. Project from existing sources."




After selecting this option, look for the directory containing the "old" project:

The suggested name for the new project is the same as the selected directory. You can change it if you wish. Then we choose the type of backend we want for the project we are importing. In my case, I choosed "autotools backend".



Finally Anjuta creates the new project:



may notice that in the left region of the screen are all inherited files from the previous old project.

If we try "Shift + F7", we must choose the type of profile we are going to use in compilation:


Once choose the type of profile, anjuta tries to build the binary. It does not work initially. We are going to try to overcome this situation.




Adapting the newly imported project

Como comenté anteriormente, muchas funciones de soporte que proveía Anjuta 1.x, ya no son necesarias. Básicamente se puede prescindir de los archivos: "suport.c", "suport.h", "interface.c" e "interface.h". Se pueden eliminar de varias formas. En mi caso, el directorio importado forma parte de un directorio de trabajo de un proyecto almacenado mediante "subversion", así que utilizo la siguiente opción:
As I previously mentioned, many support functions provided by Anjuta 1.x are no longer necessary. Basically, the files "suport.c", "suport.h", "interface.c" and "interface.h" can be deleted. This can be done in several ways. My personal favorite, as the imported directory is part of a working directory of a project stored using "subversion" versioning system, I use the following:



this way, we eliminate all files indicated previously. In addition, you must remove references to these files that no longer exists, in particular we need to edit the file "src/Makefile.am" and eliminate any references to the deleted files. Then we must go to the project directory (using a shell console), and execute the command "./autogen.sh".


This last command regenerates the file "Makefile", avoiding references to any deleted files. It is likely that many development tools used by our old project had changed since the last time we modified it, besides Anjuta and Glade, but also "automake", "autotools", etc. "./autogen.sh" just fix up all references to the current versions of these tools existent in our system.

With a little luck, the next attempt to generate the executable will start to fail for the content of the source files, and not for the assembly of the project itself. For starters, the source programs will still refer to the header files removed ("suport.h" and "interface.h"). We can remove these lines from our code.

Of course, eliminate these lines doesn't solve all our compilation problems. There are still many references to the functions included in those deleted files, and we need to replace them. The following shows some tips about it.
 




Modifying the source code for a successful compilation

The first attempt to compile your code shows a screen like the following:





we see a few alarm messages that indicate that certain functions are no longer existing in our project source files, namely: "add_pixmap_directory", "lookup_widget", "create_window" etc.. We must replace them


I personally have abused enough from some functions such as "lookup_widget". Basically, this function returns a pointer to a widget using the widget's name as the function's argument, and it traverses the entire widget tree until he found it, a very inefficient mechanism. But now we will use the "libglade" library, which analyzes the content of the file ".glade" at runtime and performs the necessary links between the names of the widgets, the associated callback functions, etc.. "libglade" uses a GtkBuilder constructor as a main element, parses the file "xml" at runtime and creates all the widgets, and also sets properties and associated callback functions that are defined in our code for those widgets.

For an efficient use of this library, we must create an structure with pointers to all the widgets that need to be accessed in every callback function (other than those widgets whose action is associated with the function itself as an argument). This structure is initialized when creating the builder. By doing this, we can use it in every callback function.


The first replacement that I will do, is the reformulation of the function generically known as "create_window ()" ("create_ventana_principal ()" in my example), which isn't available anymore after removal of the file "interface.c":

The line:

ventana_principal = create_window ();

becomes:

item = g_slice_new(ItemStruct);

if (create_window(item) == FALSE) {
      printf("Error building screen");
      return(-1);
}


The argument of the function "create_window ()" is a pointer to the structure representing the widgets that need to be accessed in the callback functions. Maybe we don't know the content of the structure at first, but we are going to build it step by step, by subsequently attemps to succesfully recompiling the program. For now, this structure includes at least one element: the pointer to the widget "ventana_principal". A good place to define this structure is the file "callbacks.h":

typedef struct {
   GtkWidget       *ventana_principal;
} ItemStruct;

ItemStruct *item;

We can define the function "create_window()" in the file "main.c":

gboolean create_window (ItemStruct *item)
{
   GtkBuilder *builder;
   GError* error = NULL;

   builder = gtk_builder_new ();
   if (!gtk_builder_add_from_file (builder, UI_FILE, &error)) {
      g_warning ("Can not create builder from UI_FILE: %s", error->message);
      g_error_free (error);
      return FALSE;
   }

   /* link the widget named "ventana_principal", to the correspondent element of the structure */
   item->ventana_principal = GTK_WIDGET (gtk_builder_get_object (builder, "ventana_principal"));

   /* link the signal associated to the widgets */
   gtk_builder_connect_signals (builder, item);

   g_object_unref (builder);

   return TRUE;
}

This function is also the perfect place to define any matter relating to the "look and feel" of our interface, that is not already defined by the file ".xml", or in the ".rc" file we use. UI_FILE is a macro that defines the path to the glade file.

We must delete or comment  those lines that use the "lookup_widget ()" function, that corresponds to the lines indicated by compilation attempts similar to this:

main.c:XXX: error: 'my_variable' undeclared (first use in this function)

"my_variable" is a pointer whose value was previously completed using "lookup_widget()" function. Now this variable is a part of the structure mentioned above, and must be initialized during the invocation of "create_window()". Subsequently, the former structure would be formed as follows:

typedef struct {
   GtkWidget       *ventana_principal;
   GtkWidget       *my_variable;
} ItemStruct;

ItemStruct *item;



And now, "create_window ()" must also include the definition of this pointer to the new widget:

      ...

   /* Associate every widget with the element of the structure that will represent it */
   item->ventana_principal = GTK_WIDGET (gtk_builder_get_object (builder, "ventana_principal"));



   item->my_variable = GTK_WIDGET (gtk_builder_get_object (builder, "my_variable"));


      ...



In every line the compiler shows an error message about "my_variable" unavailability, you should replace this variable by the element of the structure. Example:

gtk_widget_modify_font(GTK_WIDGET(my_variable), pfd);

should be replaced by:

gtk_widget_modify_font(GTK_WIDGET(item->my_variable), pfd);

We must gradually replace all widgets that must be accessed, and that therefore should be included in the structure, until there are no more of this error messages from the compiler. The files affected are "main.c" and "callbacks.c".

In relation to "add_pixmap_directory()", we can avoid using this function, because relationship to image files are defined in the file ".glade". Either way, if you need to refer to any particular image, we can use "glade_xml_get_widget()" function.


With a little luck, our compiler will not show any more compilation errors on missing variables or functions. We will probably see some time execution errors still or maybe the aesthetic of our program will look awful. This is because we didn't fix the file ".glade" yet.





Adapting the interface file to the new version of Glade

There are some differences between versions of glade 2 and 3, in relation to xml marks that define the different widgets. Here are some of them:


  • mark <glade-interface> becomes <interface>, and </glade-interface> becomes </interface>
  • mark <widget class=... becomes <object class=..., and </widget> becomes </object>
  • In addition, widgets like "GtkVBox" and "GtkVPane" must be modified. We must add a property: <property name="orientation">vertical</property> that way those widgets don't look in a landscaped form.
  • Widgets of type "GtkComboBox" that shows a simple list of text are different in newer versions of GTK +. There is a widget of type "GtkComboBoxText" that behaves similar to the previous one, but it is not recognized by glade 3.12.1. If we still want to use glade, we must use a list approach and integrate to the GtkComboBox. It can be done like this:

  1. From glade, you must associate the "GtkComboBox" widget to a ComboBox "model". This "model" is defined separately: it defines how many columns that the model will have, and what kind of data the model will include in each column . Each column is identified by a distinctive name. Also, some static values ​​can be preloaded in the model. For a simple text list, the number of columns has to be 1, and the type must be "gchararray". Once the model list is created, must click right mouse button on the ComboBox widget, access "Edit ..." option -> tab "hierarchy" -> "add" a new hierarchy, defining name,, type, and a related attribute (this attribute correspond to the only column defined in the model).
  • As for the source code of the program, you must do the following replacements:
          // Loop that completes ComboBox widget
          while ( ... ) {
              gtk_combo_box_append_text(GTK_COMBO_BOX(comboBox), item);
          }


new version:

          GtkListStore *listaModelo;
          GtkTreeIter iter;


          // Loop that completes ComboBox widget
          while ( ... ) {
              gtk_list_store_append (listaModelo, &iter);
              gtk_list_store_set (listaModelo, &iter, 0, item, -1);
          }
          // links the "list" to the ComboBox widget
          gtk_combo_box_set_model(comboBox, GTK_TREE_MODEL(listaModelo));
          // "elementoLista" is the widget created when ComboBox hierarchy was
          // defined
          gtk_cell_layout_add_attribute (comboBox, elementoLista, "text", 0);


You can still see some error messages at runtime, such as "could not find signal handler ...". This message indicates that the associated functions to a widget signal (typically defined in the file "callbacks.c") were not found. To fix this error, we must compile the project with the additional flag "-export-dynamic". This can be defined in the menu option "Build -> Configure the project ... -> Configuration Options", adding 'CFLAGS=-export-dynamic' to "./configure" options.

References:
http://www.micahcarrick.com/gnome-anjuta-programming-tutorial.html
http://www.micahcarrick.com/libglade-anjuta-tutorial.html
http://www.micahcarrick.com/gtk-glade-tutorial-part-1.html
http://www.micahcarrick.com/gtk-glade-tutorial-part-2.html

http://www.micahcarrick.com/gtk-glade-tutorial-part-3.html
http://www.micahcarrick.com/gtk-glade-tutorial-part-3.html
http://www.micahcarrick.com/gtk-glade-tutorial-part-4.html
http://old.nabble.com/please-help:-bug:-glade2-file-conversion-to-glade3-failed-td26467085.html
http://ubuntuforums.org/showthread.php?t=1377005


"Creating GtkComboBox using Glade":
http://www.youtube.com/watch?v=Z5_F-rW2cL8

"gtk_combo_box_append_text() problem":
http://permalink.gmane.org/gmane.comp.gnome.glade.user/3774


jueves, 28 de marzo de 2013

Adding custom tasks to Ubiquiti cameras


(versión en español)

After using Axis network cameras for several years (with no such satisfactory results in a long term), I began to consider alternatives. That's how my hands went through different brands and models that did not cover the minimum expectations that I expected to completely abandon Axis. Sounds like a bad joke that most of these cameras whose firmware is usually based on Linux, have mangement interfaces particulary oriented to browsers like Internet Explorer, while inspecting the camera from a browser in Linux usually look bad, with distorted screens, unusable menu options, among other defects.

Eventually I started doing some tests with Ubiquiti AirCam mini camera. From the beginning seemed to possess the characteristics necessary to meet the intended use. Also I became interested in some extra features, such as having an SNMP agent, and even has an SSH server, which allows me to access the camera in console mode.

When setting up the dynamic DNS service I encountered a problem: apparently is prepared to work with dyndns.org only, which unfortunately doest not provide for free accounts anymore.  The camera manual says otherwise, about supporting a wide range of dynamic DNS services, but the fact is that the log file information invalidates this claim. When configuring the dynamic DNS service with an no-ip.com user, the camera log shows that dynamic dns client is trying to connect to the site http://members.dyndns.org. Inquiring about it, I found a way to add tasks or services to the camera without redoing the firmware (although the latter is also an option, as Ubiquiti provided source code for that).

Getting familiar with the environment

While accessing the camera usign ssh, we find a typical file system for a Linux distribution on an embedded system (the reference to busybox from binaries, for instance). Digging a bit in the /etc/rc.d, we find that rc.sysinit script sets kernel parameters for the camera, and rc script starts services and plugins.

rc script uses functions defined in the file /usr/etc/rc.d/rc.funcs, particularly rc_start() function, where we can find references to some external scripts called /etc/persistent/rc.prestart and /etc/persistent/rc.poststart. Such as the name implies, these scripts are executed (if any) before and after starting the services, respectively. The /etc/persistent directory allows to save scripts and files: all we have to do is save the configuration after making changes, and the changes are not going to be lost on the next reboot.

crond daemon is one of the services supported by the camera that are not started from the beginning. I'm going to use that service to run a script periodically. This script determines if the public IP has changed on our connection, and then update this information in no-ip.com.


Generating additional files and scripts

As mentioned above, we will generate a file called /etc/persistent/rc.poststart. The content of this file is showing next:


#!/usr/bin/sh
mkdir /etc/crontabs
cp -a /etc/persistent/no-ip.crontab /etc/crontabs/root
echo "root" > /etc/crontabs/cron.update
crond


This script creates directory /etc/crontabs (used by crond), copy a file (whose contents are shown in the next paragraph) to that directory with the name of the user who will run the task periodically (root in this case), and generates a file named cron.update whose content is the name of the user that has been modified his cron entry (as a flag to crond daemon to reread his configuration files). As a last step, the script starts crond daemon. rc.poststart must have execute permissions.

File /etc/persistent/no-ip.crontab has a single line, and is shown next:

*/5 * * * * /etc/persistent/no-ip.client >/dev/null 2>&1

This line tells crond to run /etc/persistent/no-ip.client  script (whose contents are shown in the next paragraph) every 5 minutes, discarding screen output.

Finally, /etc/persistent/no-ip.client script contains the following:


#!/usr/bin/sh

DDNSUSER="UsuarioNOIP"
DDNSPASS="PasswordNOIP"
DDNSHOST="HostnameNOIP.no-ip.org"

IPADDRESS=`wget http://ipecho.net/plain -O - -q ; echo`
if [ "${IPADDRESS}" = "" ]; then
exit 1
fi

IPSAVED=""
if [ -f /tmp/ip.publico ]; then
IPSAVED=`cat /tmp/ip.publico`
fi

if [ "${IPADDRESS}" != "${IPSAVED}" ]; then
echo ${IPADDRESS} > /tmp/ip.publico
logger Se detecta IP publico ${IPADDRESS}
wget -q -O /dev/null "http://${DDNSUSER}:${DDNSPASS}@dynupdate.no-ip.com/nic/update?hostname=${DDNSHOST}&myip=${IPADDRESS}" true
fi


This script has defined three variables:

DDNSUSER: our user for no-ip.com
DDNSPASS: our password  for no-ip.com
DDNSHOST: hostname as defined in no-ip.com


The script gets the public IP address of our connection, using http://ipecho.net site, and stores it in the IPADDRESS variable. It also compares this value with the IP address stored in the file /tmp/ip.publico, saved from previous executions of the script (after the camera starts running, this file does not exist so the update of non-ip.com runs at least once). It also generates an entry in the log, informing the public IP address change detected. This script also must have execute permissions.


Once we generated the required files, you can save these changes executing the command:

cfgmtd -w -p /etc/

This command saves changes to flash memory.

We are now ready to restart the camera (using reboot command is an option), and check if the changes are maintained.


Controlling changes

If changes were successful, we see that:
  1. Files added in /etc/persistent are still there
  2. crond process is running.
  3. Now there are messages from crond in the system log, such as:
Mar 28 14:27:19 192.168.1.20 crond[292]: time disparity of 586527 minutes detected
Mar 28 14:30:01 192.168.1.20 crond[292]: crond: USER root pid 314 cmd /etc/persistent/no-ip.client >/dev/null 2>&1
Mar 28 14:30:02 192.168.1.20 root: Se detecta IP publico 1.2.3.4
Mar 28 14:35:01 192.168.1.20 crond[292]: crond: USER root pid 323 cmd /etc/persistent/no-ip.client >/dev/null 2>&1

The first message is a notice from crond, about which there was a significant shift of time. That is because the camera has no clock, and starts with an incorrect date and time until the clocks synchronizes through the Internet using NTP.

In the first run of the script, we see that the IP address was correctly detected, implying that the update to no-ip was succesfull. The value of this IP address should be correctly stored in the /tmp/ip.publico file.

It is left to the curious, the revision of cgi scripts for camera management, to detect the reason of this "bug" (assuming there is a bug). Anyway, if the apparent bug was due to a misunderstanding on my part, still is very useful to know some mechanisms for modifying or adding features to the camera.