Building Your Graphics with GLADE

Intro

Let’s continue our discussion on Raspi hardware and programming in C, using my model railroad application as an example. Last time, after burning out a couple of IO Pi Plus expansion boards, I replaced them and got them running, then hooked up a number of relay boards and tested them.

I recognized that my next step needed to be a decent interface for the model railroad. Having retired from chemical engineering in the polymer and petrochemical industry since the late 1960’s, I had certain expectations for what the control interface ought to look like. Specifically, I wanted a graphical interface to interact with the hardware, but I also wanted at least one status screen to tell me how the controls were configured and to point out problem areas. Here’s my formalized plan. I hope to present it to you here, as long as I don’t wear out my welcome with Andrew & friends.

Topic List

  • Overall structure discussion.

  • Intro to GLADE.

  • How the C program incorporates the GLADE file.

  • How "messaging" works and is handled.

  • How arrays of pointers are used to access various user-specified input information, and how the information is displayed.

  • Two types of buffers: a non-time-sensitive one for the information that is displayed directly, and checks to see that power & direction relays are set up properly, using information from user inputs via the graphical display interface (which is fairly static). Also, a time-sensitive buffer for the dynamic information from the position detector cards (which must be "de-bounced").

  • How to build a "Status" screen which checks that power and direction controls are set properly, and shows block occupancy. This is independent (for the most part) of the earlier code which displays relay setting information showing whether it is safe or not to cross the gap from one track block to the next.

There's code buried in here to implement some of the checking, but this will be revised later in the "Artificial Intelligence" section.

RasPi Graphical Interface:

As noted, I wanted two parts to the interface, (1) a graphical overview of the model railroad layout with both input and output, and (2) a status screen. As a first step, I brainstormed what it would take to bring about the two screens. A couple of ideas popped up, not necessarily in this order:

      • A need to periodically and quickly read and buffer time-sensitive layout status, rapidly changing items and items that have noisy signals, particularly track block occupancy detectors. I’ve had problems in the past with such things as magnetic reed switch contact bouncing, so these items will require buffering, and possibly, interrupt-driven code (We’ll see about that later.)

      • A need to periodically but not as frequently update one or both status displays, and accept control signals such as power pack selection, track block on/off and power pack selection, direction, and whether or not these settings are matched on adjacent blocks. A train should not cross from one track block to another unless all the aforementioned settings match.

So, for this section of computer code development, we’ll need to write code to support such items as

      • Color-coded status display of track block power, direction, occupancy, gaps between track blocks, turnout positions, etc.

      • Receiving commands from the computer (a classical AI application, to be discussed later) or directly from you, the modeler, to change state of the aforementioned settings, as well as storage of what those states are.

The bare layout diagram was displayed above. Let's summarize what we need to control need to control. You can easily change this sort of thing to meet your own project requirements, seeing as how the guts of the thing are relays for output and detectors (to be visited later) for input. There will also be a section later in this writeup which discusses motor control.

The following is a partial implementation of the control screen, showing color-coded feedback for electrical assignments. Most of the text can be clicked on to toggle direction, power assignment, and the like.

Blocks:

Yes, I know, there’s a system known as DCC which allows you to feed alternating current power to the tracks and uses electronics onboard each locomotive to receive commands to regulate speed, direction, power pack selection, and operate lights, horns, et cetera. I have a number of locomotives and to use DCC would necessitate investing a considerable amount of money. Instead, I elected to go with good ol’ DC power and use a computer to operate the components that control power and direction. For what I want to do, anything more than four or five locomotives would be cost-prohibitive. Besides, I’m used to block wiring. In the old days, that approach required lots of single-pole double-throw (SPDT) center-off electrical switches, plus lots of double-pole double-throw electrical switches to control direction. In general, a single electrical block of track required one of each type. (Note here that I refer to electrical switches to avoid confusing what the prototype railroads call track switches, which model railroaders call turnouts.)

So, take your layout diagram and divide it up into as many electrical blocks as you feel are necessary. A good place for a block boundary is just past a turnout location, as can be seen in the earlier diagram. In the case where two tracks are parallel and there is a crossover, a pair of turnouts such as blocks 01 and 02, etc, in my diagram, you’ll need a block boundary there. This avoids “sneak” path short circuits. Think about it: the train s on tracks 01 and 02 run in opposite directions, so there’ll be a short circuit between them if there isn’t a break in the rails. To use the crossover, you’ll have to match directions, i.e. set block 01 forward and block 02 reverse, before you can drive a locomotive over the crossover from one block to the other.

…clear as mud, huh? What is normally done is to designate one rail as “north” and the other as “south,” then trace ‘em to see if they ever connect. If they do, you’ll have problems. (Follow this approach through block 00 to see the problem.)

Now for a control scheme plan. I’d like the following:

/** modelRR MAIN FILE* Interface controls:* (1) (Main Control Board) Computer interface active, or manual?* (relay energized or not)status_display_area* (2) (Block) track block direction relays (assumes right-running).* (3) (Block) power pack selection: main or local.* (4) (Siding only) electrical connection to adjacent block.* Interface reads:* (1) All turnout positions normal/reverse (via current sensing?).* (2) All occupancy detectors (De-bounced? IR, or Hall effect?).* (3) All power packs voltage and (smoothed) current.* (4) All power packs on/off.* Interface displays* (1) Layout tracks.* (2) Track occupancy (via color: black=empty, blue=occupied).* (3) Track direction and power pack selections.* (4) Power packs voltage/current/on-or-off.* Smart displays:* (1) Possible derailment (flashing yellow track block).* (2) Possible short circuit (flashing red track block).* (3) Possible crossover misalignment (only one turnout of two)* (4) General alarm conditions.**/

Having established blocks and with control scheme in mind, you’ll need to describe the track layout and supporting electronics to the computer so you can display such important information as what power pack a block is connected to, what the direction is, and whether or not the block is occupied. We do that by using arrays in our code and filling them in so we can use them to refer to the actual physical layout. Here’s mine. Note the extra hardware information associated with each block. Yes, I'd love to store this information dynamically and read it from a file into a linked list. I used arrays because they're faster to implement. Were I to be building something for commercial sale, it would be different. Too bad- - this thing is illustrative, but good for my needs only. Be tough- - you have to enter your own layout information anyway.

First, some definitions, to make the code a little easier:

// MAX_SIDINGS includes only single-ended sidings. They are the last// entries at the top of most data arrays.// sidings can be single-ended or multiple sections, but each counts as one. #define MAX_SIDINGS 6// MAX_REGULAR_BLOCKS includes only regular blocks,// not single-ended sidings or crossovers #define MAX_REGULAR_BLOCKS 8// MAX_BLOCKS includes regular blocks, single-ended sidings, but not crossovers #define MAX_BLOCKS MAX_REGULAR_BLOCKS+MAX_SIDINGS// MAX_CROSSOVERS is added into MAX_GAPS only. No separate entry. #define MAX_CROSSOVERS 3// MAX_GAPS includes regular blocks, single-ended sidings, PLUS crossovers #define MAX_GAPS MAX_BLOCKS+MAX_CROSSOVERS// #define MAX_GAPS MAX_BLOCKS+3// typically, using blocks 0 thru 7 and siding blocks 8,9,10 (total 11)// plus 3 crossovers// MAX_DETECTORS is the number of detectors, 8/card x 2 cards #define MAX_DETECTORS 16// HDWE Requirements:// each block: spdt (pack #1/#2, ), spdt (off/on), dpdt (fwd/rev)// (future: 1 beefy switch machine driver?) [3 outputs (4 later)]// each siding: spdt(off/connect) (future: 1 beefy switch machine driver?)// [1 output (2 later)]// each crossover: none at present (future: 1 beefy switch machine driver?)// [ 0 now, 1 later]// each detector: one per gap [now & later]// TOTAL OUTPUTS: 2*8 blocks spdt + 1*8 blocks dpdt// +1*3 sidings spdt + 0 crossovers [+ 3 switch machine outputs]// TOTAL INPUTS: 1*14 detector signals// = 27 (30,future) outputs + 14 inputs = 41 (44, future) pins

With those definitions in mind, go ahead and specify your layout. I have used the array to record not only track details but also hardware details. With apologies, I have made arbitrary decisions about array dimensions. A better solution would be to use dynamic run-time dimensioning, and better yet would be to use a linked list built on the fly. Sorry. I didn’t want to make this example overly complicated. Mine is as follows:

// ............layout topology.......................struct Block_Hdwe_type // hdwe pin addresses (currently min 6, max 10){ int block_no; // block identifier number char block_name[30]; // descriptive name int nmbr_outputs; // max 3 = on/off + direction + pwrpack#; 1 for sidings// int nmbr_inputs; // max 7 = occupied detector + # of entrances + # exits int nmbr_entrances; // max 3 int nmbr_exits; // max 3 // all OUTPUT signals (may want to add on-layout signal LEDs later.) int OffOn_pinMode_nmbr; int FwdRev_pinMode__nmbr; int Pwr1_2_pinMode__nmbr; // Count entrances, exits from fwd direction main entrance // all INPUT signals (allowance for 3 entrances/exits, 0 thru 2) int occupied_pinMode_nmbr; // occupation detector: to be determined int enter_from_block_nmbr[3]; // entering from where? int enter_detect_pin_nmbr[3]; int exit_to_block_nmbr[3]; // exiting to where? int exit_detect_pin_nmbr[3];};// The array:struct Block_Hdwe_type block_hdwe[MAX_BLOCKS]; // computer access via the following pin numberstypedef struct block_structure // pin nmbrs are octal (char){ int block_no[14]; //except this one, which is int char pwr_select_pin[14]; char block_on_off_pin[14]; char block_fwd_rev_pin[14]; char siding_on_off_pin[14];} block_structure; // The array (initialized)(You’ll need layout-specific numbers here).block_structure block_relay = // pin nmbrs are octal (char){ .block_no ={0,1,2,3,4,5,6,7,8,9,10,11,12,13}, //except this one is int .pwr_select_pin = {01,03,05,07,011,013,015,017,00,00,00,00,00,00}, .block_on_off_pin = {00,02,04,06,010,012,014,016,01,00,05,03,02,07}, .block_fwd_rev_pin = {07,06,05,04,03,02,01,00,010,010,010,010,010,010},.siding_on_off_pin = {017,017,017,017,017,017,017,017,011,012,013,014,015,010}};

Next, a couple of definitions and global variables for the RasPi I2C channels:

// Specify which i2c addresses to use.// For two IO PI Plus 2.1 cards, the following four addresses are hard-wired.// They can be char (octal) numbers, although specified as hexadecimal.// For two IO PI Plus 2.1 cards, the following four addresses are// hard-wired.char ADDRESS20=0x20; // right (aux) two relay cards 17 - 0char ADDRESS21=0x21; // left (main block) two relay cards 17 - 0char ADDRESS22=0x22; // fwd/rev relays 7 thru 0 (invert required)char ADDRESS23=0x23; // detectors char on=0;char off=1; // global pin on/off zero or onechar on_invert=1;char off_invert=0; // global pin on/off one, zero

Power Controls:

A look ahead- - there are two power packs, #1 and #2, supplying power to all blocks via two power busses and a common return buss. The current from each of them is switched off or on with a single pole double throw (SPDT) relay. The layout trackage is divided into eight main electrical blocks numbered 00 through 07, each of which needs its own power controls. These controls consist of, for each block, an SPDT relay connection to either one of the two power supply packs, an SPDT off/on relay switch, and a double pole, double throw (DPDT) forward/reverse directional control relay. In addition there are three sidings, numbered 08 through 10. These sidings need only be connected to their adjacent blocks via an SPDT relay, which simplifies wiring considerably. (One rail is soldered to the main block, and the other is connected to its corresponding rail via this single SPDT relay.)

Graphical Interface

To easily implement a graphical interface to display track block status as well as to input your commands to change that status, you'll need some source code, which you are presumably using the "Geany" editor to generate, plus for display graphics you'll need the "Glade" editor, which can be found in the "Programming" drop-down menu.

You can start with a layout status drawing by using Glade, inserting a "GtkWindow", then inserting a "GtkDrawingArea" into it. Name each item as desired, using the "General" tab's "ID" field. I find it useful to copy the name to the "common" tab's "Widget name" field. You should have, at this point, a "GtkWindow" object containing a "GtkDrawingArea" object, both of which are named a unique name. You'll refer to these names later.

If you like, you can display a JPEG track diagram like the one above, which makes it easier to position the various objects you’ll be adding. Unfortunately, you can’t change the colors in that JPEG, so I recommend using it only to get started laying out various items, then use a drawing subroutine in your main program to draw the layout. This way, you can change track color, for example, to indicate if a track block is connected to power or not, which power pack, which direction, etc.

For this window, I wanted both a graphic and some Gtk text objects. For my first version, I wanted a JPEG layout graphic, so I elected to include it by starting with another "GtkWindow" and inserting a "GtkFixed" object, which the documentation describes as "a container which allows you to position widgets at fixed coordinates." This helped to position a few starter objects.

I then removed the JPEG and did two rather laborious details: (1) I wrote a C subroutine to draw the layout and then (2) added various Gtk labels and whatnot on top of the drawing, via the Glade editor. Unfortunately, you can’t see the layout drawing while in the Glade editor, so positioning the Glade objects is a repetitive trial-and-error task. Fortunately, you can save the GLADE output and re-compile to a C program that loads it rapidly (much better than we had to do back in the old keypunch days!).

One thing that might help is running the C layout drawing subroutine, capturing the window (there is a utility program called scrot which tells you how), then temporarily adding it into the drawing area as a JPEG image. You can delete it later, after adjusting your various added items’ positions. …frustrating, but handy, as once you have these objects, you can access each on the fly to do things like mine: change color based on layout status, click on ‘em to change direction or power, etc. Adding the functionality is a bit of a pain, but well worth it.

Below are two output screens illustrating what my Glade code, utilized in my C code, produces. This is an annotated example showing status of the various blocks, various controls and whatnot, set for a case where there is a train supposedly occupying BLOCK 01. (I don’t have occupancy detectors set up yet, but there’s test code.) The track diagram is drawn using drawing code, whereas the gaps and such are done in GLADE and included by the C code. Sorry for the obfuscation. Let's look at the main control diagram page first, followed by the Status page (showing Block 02 occupied in a different test case; sorry about that!), then look at the GLADE-generated file that generates them both, then at some C code.

To draw both images, GLADE generates a file that starts out like the following:

<?xml version="1.0" encoding="UTF-8"?><!-- Generated with glade 3.20.0 --><interface> <requires lib="gtk+" version="3.20"/> <object class="GtkWindow" id="status_display_win"> <property name="name">status_display_win</property> <property name="width_request">400</property> <property name="height_request">300</property> <property name="can_focus">False</property> <property name="deletable">False</property> <property name="gravity">south-east</property> <child> <object class="GtkDrawingArea" id="status_display_area"> <property name="name">status_display_area</property> <property name="width_request">600</property> <property name="height_request">400</property> <property name="visible">True</property> <property name="can_focus">False</property> </object> </child> </object>

The above GLADE output generates a window screen containing a child object that you can write and draw on with C code, which is the second (Status) page. I'll show you some code a bit later. Do NOT attempt to copy this code! Instead, let GLADE build it for you. (Some of it can get pretty ugly, as you will see.)

The GLADE file continues with another object, the upper detailed Main Control Diagram shown first above, which contains a "fixed" container holding lots of child label objects whose value and color and all can be set programmatically, as well as can be drawn upon or text listed on.

<object class="GtkWindow" id="window_main"> <property name="name">win_main</property> <property name="width_request">600</property> <property name="height_request">360</property> <property name="can_focus">False</property> <property name="window_position">center</property> <property name="default_width">600</property> <property name="default_height">450</property> <signal name="destroy" handler="on_window_main_destroy" swapped="no"/> <child> <object class="GtkFixed" id="fixed1"> <property name="name">fixed1</property> <property name="width_request">600</property> <property name="height_request">360</property> <property name="visible">True</property> <property name="can_focus">False</property> <child> <object class="GtkImage" id="layout_widget"> <property name="name">layout_widget</property> <property name="width_request">620</property> <property name="height_request">450</property> <property name="visible">True</property> <property name="can_focus">False</property> <property name="margin_top">4</property> </object> <packing> <property name="y">125</property> </packing> </child>

The rest of the file consists of various "child" entries which add items to the "fixed" window. For example, here's one that adds the fixed-window-area menu. (Some entries have been deleted, for brevity.)

<child> <object class="GtkMenuBar" id="main_menu"> <property name="width_request">100</property> <property name="height_request">30</property> <property name="visible">True</property> <property name="can_focus">False</property> <child> <object class="GtkMenuItem" id="file_menu"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">_File</property> <property name="use_underline">True</property> <child type="submenu"> <object class="GtkMenu" id="menu1"> <property name="visible">True</property> <property name="can_focus">False</property> <child> <object class="GtkMenuItem" id="file_opn_init"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="tooltip_text" translatable="yes">set operation to initial status settings</property> <property name="label" translatable="yes">Load operation</property> <property name="use_underline">True</property> <signal name="activate" handler="on_file_opn_init_activate" swapped="no"/> </object> </child> <child> <object class="GtkMenuItem" id="file_opn_toggle_data_acq"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="tooltip_text" translatable="yes">refresh current operation (re-read all status items)</property> <property name="label" translatable="yes">Toggle data acquisition</property> <property name="use_underline">True</property> <signal name="activate" handler="on_file_opn_toggle_data_acq_activate" swapped="no"/> </object> </child> <child> <object class="GtkMenuItem" id="file_opn_save"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="tooltip_text" translatable="yes">Save current operation status</property> <property name="label" translatable="yes">Save operation status</property> <property name="use_underline">True</property> <signal name="activate" handler="on_file_opn_save_activate" swapped="no"/> </object> </child> <child> <object class="GtkSeparatorMenuItem"> <property name="visible">True</property> <property name="can_focus">False</property> </object> </child> <child> <object class="GtkMenuItem" id="file_exit"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="tooltip_text" translatable="yes">Exit</property> <property name="label" translatable="yes">Exit program</property> <property name="use_underline">True</property> <signal name="button-press-event" handler="on_file_exit_button_press_event" swapped="no"/> </object> </child> </object> </child> </object> </child> <child> <object class="GtkMenuItem" id="edit_menu">

...and so on.

There are a number of "widgets" which are fun to use, such as a progress bar and various label objects and buttons and whatnot. Where it really gets interesting, though, are when you start using labels as control buttons, sending "messages" to the C code. For example:

<child> <object class="GtkLabel" id="block_01_direction"> <property name="name">block_01_direction</property> <property name="width_request">35</property> <property name="height_request">24</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="label" translatable="yes">fwd&gt;</property> <property name="selectable">True</property> <attributes> <attribute name="font-desc" value="&lt;Enter Value&gt; 6"/> </attributes> <signal name="button-press-event" handler="on_block_01_direction_button_press_event" swapped="no"/> </object> <packing> <property name="x">251</property> <property name="y">144</property> </packing> </child> <child> <object class="GtkLabel" id="block_02_direction"> <property name="name">block_02_direction</property> <property name="width_request">42</property> <property name="height_request">21</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="label" translatable="yes">&lt;fwd</property> <property name="selectable">True</property> <attributes> <attribute name="font-desc" value="&lt;Enter Value&gt; 6"/> </attributes> <signal name="button-press-event" handler="on_block_02_direction_button_press_event" swapped="no"/> </object> <packing> <property name="x">253</property> <property name="y">188</property> </packing> </child>

Below here, we launch into a bunch of labels which list block names and controls. These labels can be clicked on to generate "message" signals which the program's loop can interpret as commands "on the fly." (Note the "button-press-event" signal and the name of the subroutine which will be called.) These get positioned on or adjacent to where the track lines will be drawn by C code. There appears to not be a way to insert straight lines and curves and such into GLADE, so lines have to be drawn with code. That's okay, though, because it is easy to use color settings to show information such as power connection. These two, above, are used to toggle the block 01 or block 02 forward/reverse direction relay.

The two main powerpack off/on switches, and in fact all of the relays, are toggled the same way. For brevity, I won't list them here. This writeup is getting far too long.

Within the code are means of indicating what power is selected for what track block, et cetera. Built into the screen shot are three warning types. One is a simple red/yellow/green indication hard-coded into the slow screen update code, which runs every 5 seconds, and checks to see if adjacent track blocks are powered by the same powerpack or not, are set in the same direction, and whether or not the next block is occupied by a train. In the second, which is hard-coded logic, similar information is used to indicate whether it is okay to cross a gap between one track block and another via color code. If everything is set correctly, normal track color is used, otherwise, a red indication is displayed. The third warning is drawn on a separate status screen which shows what is right or wrong. Each track block has a display of its status plus that of the previous and next blocks, showing occupation, power selection, and direction.

This is useful, but maybe overkill. You decide.

Click here to see the details of the next page, Adding C code to Utilize GLADE Output File