Define iCbox with different types of buttons shown.
The layout of an IEC ID is: ID,<button_select_mask>,<reset_time>,<reset_mask>
At minimu, ID must be given.
Below are different flavours of the button definition given for an IEC of type IX.
Toggle button
This is the default behaviour.
When you click the button with the left mouse button (further referred to as LMB), the button goes to the "on" state.
Clicking again the LMB button brings the button back to the "off" state.
To achieve this, just define IX.
Example:
IX0
Button follows LMB
For this to happen, the <reset_time> parameter has to be set to -1.
As long as you press the LMB the button is put in the "on" state. Only when you release the LMB the button goes to the "off" state again.
Example: IX0,,-1
Note: since the first parameter <button_select_mask> is not given you have to use the ,, syntax before defining the second parameter <reset_time>.
Note: this cannot be applied to individual buttons. It's all buttons or none.
Limit number of buttons shown
By default all 8 buttons are shown for IX.
If you only want to show a limited amount of buttons, the first parameter <button_selection_mask> of the IX definition has to be used.
Example:
IX0,0b00001111
Only IX0.0 until IX0.3 will be shown in iCbox
Mix of buttons
Playing around with a mix of visble buttons, pushbuttons, timed buttons, mouse-following buttons,... can be found below.
Example:
IX0,0b00001111,100
Only IX0.0 until IX0.3 will be shown in iCbox and those buttons will behave as pushbuttons with an "on" time of 100ms, regardless of how long the LMB is held.
Example:
IX0,0b00001111,300
As before, but now the buttons act as pushbuttons with a fixed "on" duration of 300ms, regardless of how long the left mouse button is held.
This simulates a normal push button press done by a user (kind of...)
Example
IX0,0b10101010,500
Only IX0.1, IX0.3, IX0.5 and IX0.7 will be shown in iCbox and those buttons will behave as pushbuttons with an "on" limited to 500ms, regardless of how long the LMB is held.
Example:
IX0,0b10101010,-1
Same as above, only here the button will be "on" for as long as you press the leLMB. Only when you release the LMB, the button will go back to its "off" state.
Example:
IX0,0b00111111,-1,0b00001111
Only show the first 6 buttons (IX0.0 - IX0.5) of which button IX0.4 and IX0.5 behave as "normal" buttons (see Toggle button) and buttons IX0.0 until IX0.3 are following the LMB action (see Buttons follows LMB above).
Example:
IX0,0b00111111,400,0b00001111
Same as above, only the buttons IX0.0 until IX0.3 are automatically reset to the "off" state after 400ms, regardless of the state of the LMB (pressed or not).
iCbox with different flavours of button representation
The intention is that we can communicate between two Raspberry Pi's using immediateC. To achieve this, one Raspberry Pi has to be the master and another Raspberry Pi has to be the slave.
Should you introduce a third Raspberry Pi, then also that one has to be a slave.
For the master Raspberry Pi nothing changes. The setup as we know, remains as-is. That is:
Ports are defined as 0 for GPIO_A and 1 for GPIO_B
iCserver must be started in the startup script
No special namings have to be taken into account: the master is free to use whatever name he wants to define header names for, e.g., iCbox (using -n as name parameter) or iCmqtt (using -N as name parameter)
Use whatever -I and -Q parameter you want when generating the MQTT files for openHAB.
Use the same -I and -Q parameters in the setup file where you define the iCmqtt setup.
Currently, I've used -I20 and -Q20 on the master Raspberry Pi to generate the MQTT files.
If the master uses "internal" IX-properties (e.g. to control a switch also from an iCbox panel), he's free to use whatever number he wants.
Example:
In the setup file, we can have
-R iCbox -n Test \
IX0,,100 \
IX1,,100 \
IX2,0b10001111,100 \
In the .ic file we can have
imm bit Sw01 = IX0.0;
imm bit Sw02 = IX0.1;
imm bit Sw03 = IX0.2;
imm bit Sw04 = IX0.3;
imm bit Sw05 = IX0.4;
imm bit Sw06 = IX0.5;
imm bit Sw07 = IX0.6;
imm bit Sw08 = IX0.7;
Something similar for IX1 and IX2.
For the slave Raspberry Pi(s), the situation is different, much different! That is:
Ports for the first slave are defined as 2 for GPIO_A and 3 for GPIO_B
Ports for the second slave are defined as 4 for GPIO_A and 5 for GPIO_B
And so on...
We can go to a max. of 8 for GPIO_A and 9 for GPIO_B
iCserver must NOT be started on the slave(s): only one instance of iCserver shall run and that is on the master Raspberry Pi!
When launching a setup on the slave Raspberry Pi and the master is not running yet, the slave will sense and try to connect to the server until iCserver on the master is up and running (or until eternity minus one day...)
The option -o 2 shall be passed as one of the parameters to iCpiI2C to indicate all MCP ports are now mapped on 2 and 3 (see above)
In case yet another slave would be part of the game, then -o 4 shall be passed and so on.
The option -s <ip_address_master_raspberry_pi> shall be passed so that the slave can connect to the iCserver running on the master.
Example currently used:
iCpiI2C -s 192.168.1.40 -o 2 -f -d330 \
All names that are used (file names, header names for iCbox, iCmqtt) must be unique: I deploy the _o2 postfix to make them unique (in case the same filename would initially be used on the different Raspberry Pi's).
Example:
Name of the header of the iCbox on the master: MCP_IO
Name of the header of the iCbox on the slave: MCP_IO_o2
Name of the setup file on the master: setup
Name of the setup file on the slave: setup_o2
Name fo the .ic file on the master: piI2c.ic
Name of the .ic file on the slave: piI2C_o2.ic
And so on... (you get the picture, right?)
When generating MQTT variables you MUST use other -I and -Q numbers during the generation phase: it's not allowed to have the same ones!
A hint: when you generate the MQTT files on the master Raspberry Pi you will get some helpful feedback telling you which -I and -Q values could be used for the generation on the slave Raspberry Pi.
Currently, I've used -I50 and -Q50 for the slave Raspberry Pi to be sure they're not conflicing with the ones used on the master Raspberry Pi (-I20 and -Q20)
I have situations where I want to control a (pulse)relay from pusbuttons located on both floors of our house. This is also possible with immediateC running with a master and a slave Raspberry Pi.
Suppose the following use case:
One pusbutton controlled by the master Raspberry Pi - btn_01 - activates two relays - Rel1 and Rel21 - also controlled by the master Raspberry Pi. The corresponding LED for the pushbutton is led_01.
I want a pushbutton controlled by the slave Raspberry Pi - btn_47 - to also activate the two relays controlled by the master Raspberry Pi. The corresponding LED for the pushbutton is led_47.
To achieve this, a few things must be known:
What's the IEC value of the LED led_01 controlled by the master Raspberry Pi?
What's the IEC value of pushbutton btn_47 controlled by the slave Raspberry Pi?
What's the IEC value of the LED led_47 controlled by the slave Raspberry Pi?
When the above is known, one can start building the connections.
What has to be done on the slave Raspberry Pi?
Well, since LED led_47 - controlled by the slave Raspberry Pi - has to follow LED led_01 controlled by the master Raspberry Pi, one has to know the IEC value of LED led_01.
In my test setup, the IEC address of LED led_01 controlled by the master Raspberry Pi is QX101.3.
To be able to use the value of QX101.3 on the slave Raspberry Pi this IEC address has to be tagged as extern in the .ic file, like so:
extern QX101.3;
This informs the iCserver that QX101.3 is "received" by the slave Raspberry Pi.
Once this is done, you can connect the LED of the corresponding push button to the value of QX101.3, like so:
imm bit led_47 = QX101.3;
And then, led_47 itself is controlling an I/O pin of one of the MCP23017 I/O expanders controlled by the slave Raspberry Pi. In my case, the following connection is made:
QX153.5 = led_47;
From now on, whenever LED led_01 controlled by the master Raspberry Pi changes level, led_47 on the slave Raspberry Pi will follow. Since led_47 is following led_01, in the end the IEC output QX153.5 will also follow that same level change.
This will then control the hardware pin of the I/O expander and thus the physical LED connected to it, represented by IEC name QX153.1.
So, you could see the immediate variable led_47 as a "glue" between QX101.3 and QX153.2. Amazing, right?
What has to be done on the master Raspberry Pi?
Since we want Rel1 and Rel21 to be controlled not only by btn_01 controlled by the master Raspberry Pi but also by btn_47 controlled by the slave Raspberry Pi, we have to introduce the IEC address of btn_47 at the level of the master Raspberry Pi.
Since the addres of btn_47 is an IX address, there's nothing special that has to be done. Here, there's no need for extern since IX addresses can be received by any application.
The only thing we have to do, is to create an imm bit variable and assign it the debounced and filtered value of the IX IEC address of btn_47.
The IEC value of btn_47 is in my case IX152.1 and is defined in the .ic file on the slave Raspberry Pi as follows:
imm bit btn_47 = RISE(deBounce(IX152.1, t10ms, 50));
This means:
that button btn_47 is debounced with a debounce time of 500 ms (10ms x 50)
that button btn_47 is only active for a very short time: at the moment the level of the physical button rises. That means, the RISE function is "waiting" until the level of button btn_47 goes high.
This is needed to be able to have a clean and valid pushbutton value for the logic defined in the .ic file.
To be able to use the value of btn_47 from the slave Raspberry Pi on the level of the master Raspberry Pi, we also have to introduce the same code in the .ic file running on the master Raspberry Pi like so:
imm bit gf_btn_47 = RISE(deBounce(IX152.1, t10ms, 50));
I've given the variable a name that refers to the origin of the button. I added gf_ as prefix to indicate that btn_47 of the slave Raspberry Pi is referenced here through the IX152.1 IEC variable.
That's a personal choice, but is not necessary. We could have used btn_47 too.
Once gf_btn_47 is defined, there's not much to be done anymore to allow btn_47 of the slave Raspberry Pi to also control Rel1 and Rel21 of the master Raspberry Pi through that gf_btn_47 immediateC variable: wherever btn_01 of the master Raspberry Pi is used we have to "OR" it with the value of gf_btn_47:
This is how Rel1 is controlled:
imm bit Rel1 = fb_relay
(
btn_01 | gf_btn_47,
RelayGroup_Relay_01_ON | RISE(Sw01),
RelayGroup_Relay_01_OFF | RISE(Sw01)
);
And this is how Rel21 is contolled:
imm bit Rel21 = fb_relay
(
btn_01 | gf_btn_47,
RelayGroup_Relay_01_ON,
RelayGroup_Relay_01_OFF
);
Watch the "OR" construction for both btn_01 and gf_btn_47
And that's it! From now onwards, Rel1 and Rel21 can be controlled not only from 2 different places, but also from 2 different floors!
Yes, immediateC is really powerful. But unfortunately heavily underestimated.
When I recently tried to compile therm.ic on a Raspberry Pi with Trixie as OS I got the following compiler error:
1535 typedef __signed__ __int128 __s128 __attribute__((aligned(16)));
*** --------------------------------^ C syntax error 1 in therm.ic
1536 typedef unsigned __int128 __u128 __attribute__((aligned(16)));
*** ------------------------------^ C syntax error 2 in therm.ic
1577 __uint128_t vregs[32];
*** -----^ C syntax error 3 in therm.ic
2235 __uint128_t vregs[32];
*** ------^ C syntax error 4 in therm.ic
At first, I totally didn't understand what was going on. But after a while I discovered that it was because Trixie is (only) a 64 bit distro and it also has a brand new GCC version (14.2.0-19), although that was not the reason of the compilation error since also GCC version 10 (which I currently use as default GCC compiler) suffered from the same issue.
I could quickly figure out that, when compiling therm.ic, the file icg.h was included in the section of the file beginning with %{. This is a special marker John has introduced to be able to write "real" C-code (he calls it "literal C code") into an immediateC file (to close the C-code block, use %}).
Commenting out that line was solving the issue. However, I needed to know the real cause of the problem since John didn't just include icg.h without any reason in his therm.ic example file.
icg.h was used in therm.ic using the following syntax: #include <icg.h> (mind the < and > token).
Since < and > is used, it means it comes from a system path. Indeed, icg.h is located in /usr/local/include and is copied into that location when running the make install command while compiling with the overiall m -a command.
When looking to the content of icg.h I saw there were quite a few include files used:
#include <signal.h>
#if RASPBERRYPI >= 5010 /* GPIO V2 ABI for icoctl */
#include <linux/gpio.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#endif /* RASPBERRYPI >= 5010 - GPIO V2 ABI for icoctl */
#ifndef INT_MAX
#include <limits.h>
#endif /* INT_MAX */
I started to experiment and in the end I saw that the following two include files were causing the issue:
signal.h
linux/gpio.h
Then I started to look who was defining the failing typedefs.
To find the literal string, I used the command
sudo grep / -rn -e "typedef __signed__ __int128 __s128 __attribute__((aligned(16)));" 2> /dev/null
starting from the root of the RFS.
After a long search, I saw there were multiple places where this string was defined:
pi@trixie2:~ $ sudo grep / -rn -e "typedef __signed__ __int128 __s128 __attribute__((aligned(16)));" 2> /dev/null
/usr/src/linux-headers-6.12.47+rpt-common-rpi/include/uapi/linux/types.h:17:typedef __signed__ __int128 __s128 __attribute__((aligned(16)));
/usr/include/linux/types.h:12:typedef __signed__ __int128 __s128 __attribute__((aligned(16)));
At first, I thought it was the "long" Raspberry Pi path that was used (the one with rpt-common-rpi in it). However, using the trick described in the above paragraph revealed to me that it's the types.h file from /usr/include/linux that is defining this typedef like so:
#ifdef __SIZEOF_INT128__
typedef __signed__ __int128 __s128 __attribute__((aligned(16)));
typedef unsigned __int128 __u128 __attribute__((aligned(16)));
#endif
A 64 bit OS has __SIZEOF_INT128__ defined by default, so the typedef statements are taken into account.
Similar situation with the other offending line:
__uint128_t vregs[32];
This one is defined in /usr/lib/linux/uapi/arm64/asm/sigcontext.h.
So, how could I narrow down where the errors were coming from? Well, the good old debug tactics: adding prints.
At a certain moment, I came to know that the issue started in the method iC_c_compile(), located in gram.y. More specific, in the call to yyparse() on line 3247 (as of this writing: 04/01/2026).
yyparse() is a function that is generated by Bison/Flex and does the parsing of the given file accoring the grammar that it's been given.
To resolve the issue, I asked some help from CoPilot. And the solution is found: changing the grammar and lexer code for the immcc compiler.
To be clear: the issue is not related to the GCC compiler but to the immcc compiler that converts .ic files in .c files. Therefor, among other files, the files gram.y and lexc.l are used to feed the bison/flex generator that creates the necessary files for the immcc compiler.
To be honest, I have absolutely no experience with such kind of files but I did learn quite a bit.
First, the file gram.y:
Since INT128 was not known to the grammar file, it had to be added. To accomplish this, I added two things to the file:
a new token:
%token <tok> INT128
a new type specifier:
| INT128 {
$$.start = $1.start;
$$.end = $1.end;
$$.symbol = NULL;
}
Pls. don't ask me what this means/does, but it's necessary to resolve the compiling issue.
Second, the lexc.l file:
Since INT128 has been defined in gram.y we can now define new types (the missing ones) inside lexc.l. I added the following to the file:
"__int128" { count(&yylval); return INT128; }
"__uint128_t" { count(&yylval); return TYPE_NAME; }
"__int128_t" { count(&yylval); return TYPE_NAME; }
"__signed__" { count(&yylval); return SIGNED; }
After I've done those changes, I ran the following commands to have a fresh (re)build:
make distclean
./configure
m -a
If I then ran iCmake therm.ic again, the issue was resolved!!!
To recap, this was the initial compiling issue:
pi@trixie2:~/mystuff/immediatec/src $ iCmake therm.ic
therm.c
******* therm.ic ************************
1535 typedef __signed__ __int128 __s128 __attribute__((aligned(16)));
*** --------------------------------^ C syntax error 1 in therm.ic
1536 typedef unsigned __int128 __u128 __attribute__((aligned(16)));
*** ------------------------------^ C syntax error 2 in therm.ic
1577 __uint128_t vregs[32];
*** -----^ C syntax error 3 in therm.ic
2235 __uint128_t vregs[32];
*** ------^ C syntax error 4 in therm.ic
*** Error: 4 syntax errors - cannot execute. File therm.ic, line 266
immcc: syntax or generate errors
immcc compile errors in 'therm.ic' - no executable 'therm' generated
Failing occured since immcc did not know about __int128 (old '80 and '90 C-code).
I added the missing stuff in gram.y and lexc.l and recompiled everything with success.
immcc: syntax or generate errors is coming from icc.c, line 1202
immcc compile errors in 'therm.ic' - no executable 'therm' generated comes from from iCmake, line 401
lexc.o and lex.yy.c are compiled out of lexc.l
gram.o and gram.tab.c are compiled out of gram.y
comp.o and comp.tab.c are compiled out of comp.y
cons.o and cons.tab.c are compiled out of cons.y
icg.h is created from a perl script icg.pl that takes in icc.h as input parameter and createds icg.h
misc.c contains miscellanious functions that are shared by all modules. It is one of the dependencies of scid.c (this seems to be a generated file...)
Compiling of the above is accomplished by running the command m -c. If changes are detected, recompilation will occur.
To install all the above, run m -a.
I wanted to be able to also use bool as type when calling a C function. Back in the days, C didn't have a bool type but since C99 (I guess) there's an include file called <stdbool.h> that can be included to "fake" a bool type.
However, using this in a literal C block in immediateC fails since, again, immcc doesn't know the type bool because of its age.
I applied the same mechanism as I did to add the INT128 type: modifying gram.y and lexc.l.
In gram.y, I did the following:
Added %token <tok> BOOL (placed it just after INT128, my previous change)
Added
| BOOL {
$$.start = $1.start;
$$.end = $1.end;
$$.symbol = NULL;
}
In lexc.l, I did the following:
Added
_Bool" { count(&yylval); return BOOL; }
bool" { count(&yylval); return BOOL; }
so that both bool as well as _Bool can be used in C code (both are mapped onto BOOL which is defined in gram.y)
Recompiling using m -c resolves the issue: immcc now also understands the type bool!
To deploy this in an .ic file you can do the following:
Create a literal C code block:
%{
#include <stdbool.h>
.
.
void gvc(bool b)
{
if (b)
{
printf("True\n");
}
else
{
printf("False\n");
}
}
.
.
%}
This is really an idiotic example, but just for the fun of it...
Call the C-code from immediateC:
if (TX0.1) {
gvc(0);
gvc(1);
}
Note: you can't call gvc() like that, it has to be called from within an immediateC expression (here: if (TX0.1))
To make life easier (and to avoid calling 0 or 1 as parameter) you can use an enum in immediateC. To do this:
Create an enum in literal C code: %{ enum MYBOOL {FALSE, TRUE}; %}
This maps FALSE to 0 and TRUE to 1.
Call gvc() like this:
gvc(TRUE);
gvc(FALSE);
This makes reading the code more logic and easy.
In fact, creating an enum for this is not really necessary. Since #include <stdbool.h> is part of the party the terms true and false also exist and can be used as calling parameter.
So, gvc(TRUE) or gvc(true) (and the opposite, of course) are both possible. Depends on your flavour...