Simubaron

 Programmation SIOC

Accueil Interfaces Logiciels Pedestal et throttle Tableau de bord Savoir faire Programmation SIOC Téléchargements Contact et liens Les auteurs
 

Remonter


Quelques trucs et astuces SIOC

Sommaire

Introduction et programme SIOC

Je ne vais pas ici faire une introduction à ce langage. Si vous ne le connaissez pas, je vous renvoie pour l'excellent et incontournable site de Claude Kieffer, www.simucockpit.fr, et particulièrement à la page d'initiation à SIOC. Je désire simplement exposer les points un peu obscurs ou les solutions que j'ai trouvées à certains problèmes.

C'est moi (Pierre, dit Kelt760) qui ai écrit notre programme SIOC : j'en ai conçu la plus grande partie, je me suis inspiré d'exemples glanés ici ou là pour d'autres. Mais je ne suis pas un programmeur confirmé, et je suis preneur de tous commentaires, suggestions d'amélioration et de simplification (via la page Contacts).

Dans tous les exemples ci-dessous, je conserverai les numéros de variables, d'entrées et de sortie de notre programme SIOC ; à vous de les adapter à votre câblage et à vos variables déjà existantes.

Vous pouvez afficher le programme ou le télécharger entièrement (mais n'oubliez pas qu'il toujours en développement, pas entièrement commenté, qu'il contient certainement des bugs, et qu'on peut sûrement faire plus simple à de nombreux endroits. Donc utilisez-le "à vos risques et périls" !).

Comprendre les événements

Voici un aspect de SIOC qui peut paraître obscur aux débutants, même  après la lecture de l'initiation de Simucockpit (pardon Claude !).

Je veux parler du fait qu'un programme SIOC n'a pas véritablement de début ni de fin ; il n'est pas exécuté linéairement, une ligne après l'autre, comme on pourrait le penser.

Il s'agit d'un langage fonctionnant à base d'événements. On définit des variables ; chaque variable peut être

  • liée à une entrée (un interrupteur, par exemple),
  • liée à une sortie (une led...),
  • liée à un "offset" de FSUIPC, c'est à dire un état ou une commande de FS,
  • liée à à d'autres éléments, tels que IOCP,
  • n'être liée à rien du tout,
  • ou encore déclarée "subrutine" (c'est-à-dire sous-routine).

Tout cela est très bien décrit dans le tuto de Simucockpit.

Chaque variable peut (mais ce n'est pas obligé) contenir des commandes, comme la modification d'une autre variable, de sa propre valeur, de calculs sur la valeur d'une variable, ou encore l'appel d'une sous-routine.

Le point important, c'est que le contenu d'une variable n'est exécuté que si la valeur de cette variable est modifiée. La seule exception est la sous-routine, dont le contenu est exécutée lorsque la variable est appelée par la commande CALL. C'est la modification d'une variable qui constitue un événement.

Tant qu'une variable n'est pas modifiée, les commandes qu'elle contient ne sont jamais exécutées. En revanche dès qu'un événement survient et modifie une variable, le code de celle-ci est exécuté. Ce code peut alors modifier d'autres variables ou appeler une ou des sous-routines, ce qui produit autant de nouveaux événements. Et bien sûr le code de chaque variable ainsi modifiée sera alors aussi exécuté.

Le tout est de bien structurer la cascade d'événements pour déclencher ce qu'il faut quand il le faut.

Haut de page

L'initialisation des variables

Vous avez donc compris qu'un programme SIOC n'a ni début, ni fin. En réalité, pour être tout à fait exact, il possède quand même une sorte de début : c'est ce qu'on appelle l'initialisation, ce qui se passe quand SIOC démarre ou est relancé.

Le cas particulier de la variable 0000

D'abord, il existe une variable particulière, la variable numérotée 0. En effet, c'est la seule variable dont le contenu (qu'on appelle aussi ses instructions ou son script) est exécuté au démarrage de SIOC. C'est donc l'endroit idéal pour effectuer toute sortes d'actions de départ, sans oublier que toute variable modifiée au cours de cette exécution verra son script exécuté aussi. Il est donc très simple de déclencher une cascade d'actions au démarrage de SIOC (voir la section sur l'utilisation des sous-routines). Par exemple, voici le code actuel de notre variable 0000 :

Var 0000, name init, Value 0 // initialisation
{
&fs_bat = &sw_batt 
&fs_park_brake = &sw_park_brake 
CALL &init_flaps
CALL &blink // lance le timer pour cligontement voyant 
CALL &affich_Leds
}

Les trois instructions CALL appellent des sous-routines, dont nous expliquerons le principe dans la section Utiliser une sous-routine.

Les deux premières commandes assurent que la batterie et le frein de parking prendront l'état dans lequel se trouvent leurs inters sur le panneau ;  c'est utile, car on ne peut pas être sûr de leur état au démarrage de SIOC (on peut avoir basculé un inter entre deux vols), mais qu'en même temps seul un changement d'état des inters déclenche l'exécution de leurs scripts. Forcer la batterie de FS au départ de SIOC assure donc un plus grand réalisme.

En poussant le raisonnement à fond, il faudrait faire de même pour tous les autres inters du panneau. En réalité, nous avons fait autrement, car le démarrage n'est pas le seul cas où il faut assurer que la cohérence entre FS et le panneau : même avec un panneau physique, rien n'empêche de cliquer les inters de l'écran. Voir la solution adoptée plus bas dans la section Assurer la cohérence entre le panneau physique et FS. Les deux premières commandes de la variable &init sont donc finalement inutiles...

Remarque : FS envoie tous ses offsets au lancement (ou réinitialisation) d'un vol. Donc, si SIOC est lancé avant, les scripts de toutes les variables liées à FSUIPC seront exécutés. En revanche, si SIOC est lancé après le vol de FS, seul le script de la variable 0 est exécuté (et ceux que cette variable déclenche).

La commande Value

Il existe une autre façon de conner une valeur initiale à toute variable, à l'aide de la commande Value, que l'on rajoute à la fin de la déclaration de la variable :

Var 0001, name ma_variable, value 10

Quand on lance SIOC, &ma_variable prend la valeur 10. Il y a cependant deux choses à savoir. Au lancement de SIOC :

  • si la variable est modifiée par la variable 0000, cette dernière prend le pas sur la commande Value (qui n'a alors guère de sens). Dans ce cas, le script de la variable est exécuté, comme indiqué ci-dessus.
  • sinon, la variable prend la valeur indiquée, mais son script n'est pas exécuté.

Haut de page

Nommer les variables

En SIOC, les variables sont numérotées. On peut choisir leurs numéros librement, mais il est préférable, pour s'y retrouver dans un grand programme, de choisir un schéma de numérotation, et de laisser de grands "trous" entre les blocs de numéros pour pouvoir facilement rajouter des variables par la suite en cas de modification du programme.

Cependant, SIOC donne aussi la possibilité de nommer les variables. Bien sûr, cela demande aussi un peu de discipline, car le nombre de caractères est limité, les espaces interdits. Il faut utiliser des noms cohérents et parlants, pour qu'ils décrivent d'eux-mêmes à quoi sert la variable.

Un nom de variable SIOC peut comporter 14 caractères, sans espace, par exemple fs_batt. Pour référencer une variable dans le reste du programme, il faut faire précéder son nom du signe &. ConfigSioc le fait automatiquement, mais si vous programmez dans un logiciel de texte (voir ci-dessous), c'est une erreur de syntaxe fréquente que de l'oublier.

Le gain apporté par les noms est très important : le programme reste lisible, même longtemps après son écriture. Sans noms, il sera presque incompréhensible par quelqu'un d'autre que son auteur. Avec des noms, il devient facile à explorer. Et surtout, la maintenance est grandement facilitée. C'est pourquoi j'ai nommé (pratiquement) toutes mes variables.

Dernier avantage : ConfigSioc donne la possibilité de combiner plusieurs programmes (en txt) en un seul ; dans ce cas, il renumérote les variables en cas de conflit de numéros entre les parties, mais il conserve les noms.

Haut de page

Programmer SIOC dans un logiciel de texte

ConfigSioc est très pratique quand on débute, car il interdit les erreurs de syntaxe, et propose toutes les options dans des menus locaux déroulants.

Malheureusement, il ne permet pas de copier/coller facilement des morceaux de programme déjà faits. Pour ma part, je préfère écrire mon programme dans un fichier texte, puis l'importer dans ConfigSioc. Cela demande un peu de rigueur pour éviter les erreurs de syntaxe, mais cela permet d'aller beaucoup plus vite.

Nouvelle méthode

  1. J'indique dans le fichier de configuration sioc.ini le nom du fichier texte de mon programme en cours d'écriture, par exemple monsioc.txt.
  2. Je lance Sioc. Ce dernier compile automatiquement le fichier texte et en fait un fichier .ssi, qu'il enregistre et exécute aussitôt  (pas d'avertissement d'écrasement du fichier précédent).
  3. Si une erreur survient à la compilation, Sioc le signale. Il suffit de la corriger dans le fichier monsioc.txt, et de relancer Sioc.
  4. Pour faire une nouvelle version et garder l'ancienne, je modifie le nom de monsioc.txt, par exemple en monsiocv2.txt, et je le mentionne dans sioc.ini.

Difficile de faire plus simple ! 

Ancienne méthode

Voici comment je procèdais précédemment :

  1. Dans Sioc.ini, j'indique à SIOC le nom du fichier ssi que je vais écrire (en cas de modification d'une version déjà fonctionnelle, je lui donne un autre nom, pour être sûr de conserver celle-ci). Pour l'exemple, appelons-le monsioc.ssi.
  2. Je lance SIOC (si le fichier monsioc.ssi n'existe pas encore, il indiquera une erreur, pas grave).
  3. Je vais dans Notepad ou un autre éditeur de texte, je commence à écrire le programme (pas trop de lignes en une seule fois, pour limiter les fautes de frappe et de syntaxe).
  4. Je sauve ce fichier, sous le nom de (par exemple) monscioc.txt.
  5. Je l'importe dans ConfigSioc (commande File -> Import Text) ; s'il y a des erreurs de compilation, je corrige et réenregistre monsioc.txt, puis je l'importe à nouveau.
  6. Quand le fichier est importé, je le sauve sous le nom monsioc.ssi.
  7. J'active la fenêtre de SIOC, je clique Reload (recharger).
  8. Je teste, avec FS et/ou IOCPConsole. Dans IOCPConsole, ne pas oublier de cliquer CONNECT, car il se déconnecte à chaque RELOAD de SIOC.
  9. Et je continue en reprenant à l'étape 3, pour compléter ou corriger le programme jusqu'à obtenir le résultat voulu.

Attention, à chaque importation dans Configsioc, ce dernier produit un fichier sans nom ; il faut donc l'enregistrer chaque fois sous monsioc.ssi, en écrasant la version précédente (penser à sauvegarder sous un autre nom chaque version intermédiaire qui fonctionne correctement pour pouvoir facilement revenir en arrière en cas d'erreur par la suite).

Avec un peu d'habitude, le procédé est très rapide. Si on part d'un programme existant, il suffit de l'exporter en txt (commande File -> Export Text dans ConfigSioc).

Haut de page

Simplifier l'écriture avec NotePad++

NotePad++ est un éditeur de texte gratuit, spécialement conçu pour faciliter la programmation. En effet, il est capable d'appliquer différentes couleurs aux divers éléments : variables et leurs attributs, fonctions, opérateurs, valeurs, etc. Et comme il est entièrement configurable, il peut être adapté à SIOC.

Vous pouvez le trouver ici. Si vous le désirez, vous pouvez également télécharger le fichier de configuration que j'utilise pour programmer en SIOC. Pour l'utiliser, il suffit de le décompresser, puis de le placer dans le dossier C:\Documents and Settings\NomUtilisateur\Application Data\Notepad++. Si vous n'avez pas encore personnalisé de langage dans NotePad++, vous pouvez écraser le fichier existant s'il existe. En revanche, si vous utilisez déjà NotePad++ avec d'autres langages, il faut copier le contenu de notre fichier dans l'existant.

Voici à quoi cela ressemble :

Cool, non ?

Haut de page

Découper un programme SIOC en plusieurs modules

Tant que le programme SIOC du cockpit n'est pas trop long, ou qu'une seule personne travaille dessus, on peut le conserver "en un seul morceau".

En ravanche, quand il commence à grossir, ou si on désire travailler à plusieurs (par exemple l'un s'occupe des radios, l'autre du PA, un troisième du trim), il devient beaucoup plus simple de scinder le programme en plusieurs morceaux ou modules. On utilisera pour cela la très intéressante fonction Group de Config_sioc.exe, ou un fichier .LST directement dans SIOC. Voici les détails (testés avec la version 3.7 de sioc).

Ecriture des modules

On écrit les modules normalement, indépendamment les uns des autres, avec comme simple règles :

  • Ne vous préoccupez pas des numéros de variable : le compilateur les renumérotera lors de la réunion des modules.
  • Si vous voulez imposer un numéro de variable (et donc empêcher que cette variable soit renumérotée à la compilation, utiliser le mot réservé Static après le nom de la variable. Exemple :
Var 003, name out_init_mc_1, Static, Link IOCARD_OUT, Output 49 // out re-initialise Master 1
  • Ne pas donner le même nom à des variables différentes dans les divers modules (instruction name). C'est la contrainte la plus forte, surtout si l'on travaille à plusieurs. Mais cela peut facilement être surmonté en utilisant des conventions de nommage ou des préfixes dans les noms.
  • Vous pouvez réutiliser, modifier, appeler toute variable définit dans un autre module, comme si elle était définie dans le module en cours. Toutefois, cela imposera lors de la compilation, que tous les modules contenant la définition de toutes les variables utilisées soient inclus dans liste des fichiers à compiler. La conséquence de cela est qu'il est difficile de tester un module seul, s'il fait appel à des variables "externes".
  • Les sous-routines peuvent être mises dans n'importe quel module, qui devra être inclus dans liste des fichiers à compiler. On peut envisager plusieurs manières de faire, mais il semble logique de regrouper les sous-routines générales (c'est-à-dire appelées par plusieurs variables de différents modules) dans un même module, ce qui simplifiera leur maintenance.. En revanche, les sous-routines "locales" (appelées une seulement pour une tâche unique et précise) peuvent rester dans leur module, car la lecture en sera simplifée.
  • Chaque fichier reçoit l'extension .txt.
  • Il est plus simple de mettre ces modules dans le dossier de SIOC.

Réunion des modules

Pour réunir les modules et obtenir le fichier .ssi exécutable, il y a deux méthodes. L'une utilise les commandes Group et Run de Config_sioc.exe, l'autre un fichier .LST (liste) directement dans sioc.ini.

Avec Config_sioc.exe

Voici la procédure pas à pas :

  1. Si vous désirez travailler dans un dossier ne contenant que vos modules, vous devez y recopier Config_sioc.exe et tous ses fichiers (Config_sioc_spa.lng, Config_sioc_eng.lng, Config_sioc_eng.cat, Config_sioc_spa.cat, Config_sioc_spa.lng, SIOCGEN.HLP et SIOCGEN.GID). Sinon, mettez les modules dans le dossier de SIOC (plus simple).
  2. Lancez Config_sioc.exe.
  3. Si les menus sont en espagnol, demandez English dans le menu Language.
  4. Déroulez le menu Group et choisissez Files. Une petite fenêtre s'ouvre, sans aucun bouton ni contrôle.
  5. La première fois, si elle n'est pas vide, supprimez tout le texte qu'elle contient.
  6. Tapez le nom de chaque module, avec son extension .txt, chacun sur une nouvelle ligne.


     
  7. Fermez la fenêtre. Bien qu'il n'y ait aucune confirmation, cela enregistre la liste des fichiers au sein de Config_sioc.exe (comme vous pouvez le voir en rouvrant cette fenêtre).
  8. Déroulez le menu Group et choisissez cette fois Run. Si une fenêtre affiche File not found, cela veut dire que l'un au moins des fichiers de la liste n'a pas été trouvé (faute de frappe...).
  9. La compilation s'effectue. Si tout se passe bien, une fenêtre avec indique OK en vert. Sinon, elle affiche STOP en rouge. Il vous faut alors localiser l'erreur (ce qui n'est pas toujours facile, car le numéro de ligne indiqué correspond au nouveau fichier qui n'est pas encore créé !). L'erreur la plus fréquente est variable already exists, une variable est définie deux fois. Bien sûr, dans la mesure du possible, il vaut mieux avoir testé les modules individuellement avant, pour limiter les erreurs.

     
    Erreur de compilation                              Compilation réussie
     
  10. Dans les deux cas (erreur ou non), la fenêtre de Config_sioc s'affiche. Si une erreur est survenue, corrigez-la et recommencez à l'étape 8, sans enregistrer le fichier noname.ssi qui est forcément incomplet.
  11. Quand la compilation s'est bien passée, enregistrez le fichier noname.ssi sous le nom de votre choix (par exemple monavion.ssi) de préférence dans le dossier de SIOC.
  12. Ouvrez le fichier sioc.ini et indiquez le nom du fichiers .ssi obtenu (ici monavion.ssi) dans la ligne CONFIG_FILE=. Enregistrez ensuite sioc.ini .
CONFIG_FILE=.\monavion.ssi

Voilà. Reste plus qu'à lancer Sioc.exe pour faire fonctionner le programme.

Si vous modifiez un module, repartez à l'étape 8. Si vous en ajoutez un, repartez à l'étape 4.

Directement dans sioc.ini

Autre possibilité, sans doute plus simple :

  1. Rassemblez tous les modules dans le dossier de SIOC (cette fois je crois que c'est impératif).
  2. Créez un fichier de texte, par exemple avec NotePad++, et ecrivez le nom de chacun des fichiers de modules (chacun sur une nouvelle ligne).
  3. Enregistrez ce fichier dans le dossier de SIOC, en lui donnant l'extension .LST (pour liste). Par exemple, monavion.lst.
  4. Ouvrez le fichier sioc.ini et indiquez le nom du fichiers .LST que vous venez de créer (ici monavion.lst) dans la ligne CONFIG_FILE=. Enregistrez ensuite sioc.ini .
  5. Lancez Sioc.exe. Il effectue automatiquement la compilation, créé et enregistre le fichier .ssi correspondant (ici monavion.ssi).

A priori, c'est donc plus facile, mais vous avez moins de contrôle sur les fichiers : à chaque modification d'un module, il suffit de relancer SIOC, mais la version précédente de monavion.ssi est écrasée.

Assurer la cohérence entre le panneau physique et FS

Quand on désire programmer par exemple un interrupteur, il faut évidemment placer l'action de cet inter dans sa propre variable. Par exemple, pour l'inter de batterie, il faut deux variables ici &fs_bat, la batterie (qu'il faut commander) et &sw_batt (l'inter de commande) :

Var 0002, name fs_bat, Link FSUIPC_INOUT, Offset $3102, Length 1 // batterie FS
Var 0003, name sw_batt, Link IOCARD_SW, Input 1 // Inter_batterie

Tel quel, rien ne marche ; il faut basculer la batterie de FS selon l'état de l'inter :

Var 0002, name fs_bat, Link FSUIPC_INOUT, Offset $3102, Length 1 // batterie FS
Var 0003, name sw_batt, Link IOCARD_SW, Input 1 // Inter_batterie
{
&fs_bat = &sw_batt 
}

Là, c'est mieux : quand on bascule l'inter, sa variable change (de 0 à 1 ou l'inverse), son code est exécuté et la batterie de FS est modifiée en conséquence.

Mais il reste un problème : si on clique sur l'inter batterie sur l'écran de FS, la batterie réagit, et l'inter physique n'est plus "d'accord" avec celui de l'écran. Pour corriger ça, il suffit de remettre la commande dans la variable de la batterie :

Var 0002, name fs_bat, Link FSUIPC_INOUT, Offset $3102, Length 1 // batterie FS
{
&fs_bat = &sw_batt 
}

Var 0003, name sw_batt, Link IOCARD_SW, Input 1 // Inter_batterie
{
&fs_bat = &sw_batt 
}

Maintenant, si on clique l'inter à l'écran, ou si la batterie de FS est modifiée par une action quelconque (la réinitialisation du vol - vous ne vous crashez jamais, vous ? -, le changement d'avion ou autre), sa variable est modifiée, donc son code est exécuté, et elle revient toujours dans la même position que l'inter physique.

En généralisant cette méthode à tous les inters et commandes, on assure que FS sera toujours d'accord avec le cockpit. Bien sûr, cela augmente le nombre de lignes du programme, comme ici pour le commutateur rotatif d'une magnéto :

Var 0501, name fs_Magneto1, Link FSUIPC_INOUT, Offset $0892, Length 2 // Magentos moteur 1 forcées selon panneau
{
IF &mot1_off = 1
{
&fs_Magneto1 = 0 
}
ELSE
{
IF &mot1_R = 1
{
&fs_Magneto1 = 1 
}
ELSE
{
IF &mot1_L = 1
{
&fs_Magneto1 = 2 
}
ELSE
{
IF &mot1_B = 1
{
&fs_Magneto1 = 3 
}
ELSE
{
IF &mot1_S = 1
{
&fs_Magneto1 = 4 
}
}
}
}
}
}

Var 0502, name mot1_off, Link IOCARD_SW, Input 10 // Inter magnétos moteur 1
{
IF &mot1_off = 1
{
&fs_Magneto1 = 0 
}
}

Var 0503, name mot1_R, Link IOCARD_SW, Input 11
{
IF &mot1_R = 1
{
&fs_Magneto1 = 1 
}
}

Var 0504, name mot1_L, Link IOCARD_SW, Input 13
{
IF &mot1_L = 1
{
&fs_Magneto1 = 2 
}
}

Var 0505, name mot1_B, Link IOCARD_SW, Input 12
{
IF &mot1_B = 1
{
&fs_Magneto1 = 3 
}
}

Var 0506, name mot1_S, Link IOCARD_SW, Input 17
{
IF &mot1_S = 1
{
&fs_Magneto1 = 4 
}
}

Mais quel plaisir de ne plus avoir à se soucier de rien : le panneau commandera toujours FS.

A ce propos, Pierre01 a trouvé comment modifier un contacteur 12 positions avec un ressort pour simuler la position Start de la clef.

Le gros problème des inters programmés

Tout ce qui vient d'être dit fonctionne parfaitement, à la condition que l'inter soit associé à un offset de FS. Mais il peut arriver que ce ne soit pas le cas : par exemple, on voudra simuler une fonction qu n'existe pas dans FS, comme l'extinction d'une radio, ou une prise de park, qui assurera du courant au sol aussi longtemps qu'on voudra sans vider la batterie.

Ces fonctions sont faciles à prgrammer en SIOC, mais on recontre un gros "hic". Il existe à l'heure où nous écrivons un bug dans la MasterCard d'Opencockpits, qui l'empêche de lire correctement l'état de certaines entrées au démarrage (ou redémarrage) de SIOC.

Cela a pour conséquence que le programme n'initialise pas correctement ces entrées, car elles ne subissent aucun changement d'état au démarrage de SIOC. Dès qu'on a manipulé UNE fois UNE DES ENTREES du même groupe de la Master, tout revient dans l'ordre. L'ennui est qu'il ne suffit pas de les manipuler dans le programme SIOC (ce qui pourrait se faire dans la variable 000), il faut physiquement basculer une entrée.

Nous aons trouvé plusieurs solutions . La première consiste à consacrer une entrée par groupe (soit 8 par MasterCard) à un bouton poussoir (utile par ailleurs ou non). Un relai connecté à l'interrupteur de batterie permettrait "d'appuyer" une fois sur chacun de ces poussoirs, par autant de circuitsd indépendants. Cela forcerait la Master à lire toutes les entrées et donc à initialiser correctement les inters programmés.

Cette solution est un peu lourde et perd 8 entrées par Master (mais on peut utiliser des poussoirs utilisés par ailleurs, comme ceux de test, par exemple).

L'autre solution est constituée d'une (petite) modification sur la MasterCard (ajout d'un fil au dos) et de deux lignes de programmation. Vous en trouverez la description complète à la page Amélioration de la MasterCard.

Haut de page

Eviter les IF dans les conditions simples

Voici une astuce qui permet de simplifier l'écriture des conditions,(instruction IF) dans le cas où il n'y a pas besoin de ELSE. Merci à Stevelep, alias Bob, qui a donné ce truc sur le très actif forum français des constructeurs de cockpits. Je cite :

Petit truc SIOC pour éviter les IF THEN ELSE

Var 1388, Link IOCARD_SW, Input 96 // Sélect G Aux
{
IF V1388 = 1
{
V1385 = 5
}

devient:

Var 1388, Link IOCARD_SW, Input 96 // Sélect G Aux
{
V1385 = V1388 * 5
}

Autre exemple :

Var 1389, Link IOCARD_SW, Input 97 // Sélect G Croisé
{
IF V1389 = 1
{
V1385 = 2
}
}

devient:

Var 1389, Link IOCARD_SW, Input 97 // Sélect G Croisé
{
V1385 = V1389 * 2
}

Tout simple, mais il fallait y penser : comme la variable testée vaut 0 ou 1, on évite le IF en la multipliant par la valeur à donner à la variable résultat.

Haut de page

Entrées des connecteurs J3 et J4 de la MasterCARD IOCARD
vues depuis l'avant et l'arrière

Section complétée et déplacée vers la page MasterCard.

Utiliser une sous-routine

Les routines (ou sous-routines, c'est la même chose) font souvent peur aux débutants ; elles sont pourtant essentielles dans quasiment tous les programmes, quel que soit le langage. Et SIOC ne fait pas exception.

Pourquoi une sous-routine ? Certaines tâches doivent être exécutées dans de nombreux cas. Plutôt que de la réécrire chaque fois, le principe de la sous-routine consiste à ne l'écrire qu'une seule fois, puis de l'appeler aussi souvent que cela sera nécessaire. Elle permet aussi souvent de simplifier l'écriture et de rendre le programme plus lisible.

Mais un exemple vaut mieux qu'un long discours. Prenons une led : 

Var 000&, name Ma_Led, Link IOCARD_OUT, Output 77 

Supposons que nous voulions l'allumer quand une certaine variable, disons &cond1_led est à 1, et l'éteindre sinon. Il faut allumer ou éteindre la led dans le script de la variable &cond1_led :

Var 0002, name cond1_Led, value 0
{
&Ma_led = &cond1_led
} 

Au départ, &cond1_led vaut 0 et la led est éteinte. Si ensuite &cond1_led passe de 0 à 1 ou inversement, la led s'allume ou s'éteint. Jusque là, tout va bien (j'ai volontairement évité le IF, voir la section Eviter les IF dans les conditions simples).

Mais supposons maintenant que la led dépende aussi d'une seconde condition &cond2_led. On peut écrire de la même façon :

Var 0003, name cond2_Led, value 0 
{
&Ma_led = &cond2_led
} 

Si on en reste là, dès que l'une des deux variables &cond1_led ou &cond2_led passe à 1, la led s'allume. Et elle s'éteint dès que l'une des deux repasse à 0. Mais ce n'est pas forcément ce que l'on désire. On peut vouloir la laisser allumée si l'une des deux conditions est à 1 (opération OU -OR en anglais- entre les conditions), ou au contraire ne l'allumer que si les deux conditions sont à 1(opération ET -AND en anglais).

Sans sous_routine,  on est obligé de programmer tout cela dans les scripts des deux variables &cond1_led et &cond1_led puisque l'action doit avoir lieu quand n'importe laquelle des condition est modifiée. Et sans se tromper dans le sens des conditions...

La sous-routine va tout simplifier. On va lui faire calculer l'allumage de la led selon la valeur des 2 conditions. Supposons que nous voulions que les 2 conditions soient à 1 pour que la led s'allume. On écrira :

Var 0002, name cond1_Led, value 0
{
CALL &calc_led
} 
Var 0003, name cond2_Led, value 0
{
CALL &calc_led
} 
Var 0004, name calc_Led, Link subrutine
{
&Ma_led = &cond1_led * &cond2_led
} 

On a modifié le script des deux variables conditions, afin qu'elles appellent la sous-routine (ce que chacune fera si elle est modifiée, d'après le principe de base de SIOC). On utilise pour cela la fonction CALL, qui se contente de forcer l'exécution du code de la variable sous-routine, comme si cette dernière avait été modifiée. Notez d'ailleurs qu'ici, on n'utilise pas la valeur de la variable sous-routine elle même (&calc_led). Mais rien n'empêcherait de le faire, comme avec toute autre variable, en affectation comme en lecture. On verra dans la section Passer un paramètre à une sous-routine qu'on peut appeler la routine en lui donnant une valeur, ce qui est très puissant.

Dans la sous-routine, j'évite encore les IF - parce que les conditions valent toujours 0 ou 1 par hypothèse -, mais ce n'est pas du tout obligé.

Les avantages de la sous-routine sont multiples :

  • les variables de condition sont extrêmement simples, et de plus identiques, ce qui est logique puisqu'elles sont symétriques ;
  • le programme est bien lisible ;
  • plus fort : on peut modifier facilement la sous-routine. Si, par exemple, on s'est trompé dans la logique et que la led doive maintenant  s'allumer si l'une OU l'autre condition vaut 1, il suffit de modifier la sous-routine :
Var 0004, name calc_Led, Link subrutine
{
&Ma_led = &cond1_led + &cond2_led // la led s'allume pour une valeur supérieure à 0, donc si l'une des 2 condition au moins vaut 1
} 
  • encore plus fort : si une troisième condition venait à s'ajouter, il suffirait de modifier &calc_led pour tenir compte de cette troisième condition et d'écrire cette variable condition :
Var 0005, name cond3_Led, value 0
{
CALL &calc_led
} 

Vous voyez, il n'y a rien de plus simple !

Mais bien sûr, rien n'empêche une sous-routine d'en appeler une autre, et ainsi de suite... C'est ce qui donne toute sa puissance à SIOC, mais ça peut se compliquer rapidement. C'est pourquoi il est utile de faire un organigramme (c'est-à-dire un dessin de la logique) du programme, ou du moins de la fonction que l'on désire implémenter, avant de commencer à l'écrire.

Notre programme SIOC comporte de nombreuses sous-routines (mais n'oubliez pas qu'il n'est pas finalisé).

Haut de page 

Comment faire clignoter un voyant avec la fonction TIMER

Faire clignoter un voyant (une led) est un problème courant. Voici une façon de faire ; elle est inspirée de l'excellent site (en anglais)de Nico Kaan (http://www.lekseecon.nl/).

Var 0002, name Ma_Led, Link IOCARD_OUT, Output 77 // Led à faire clignoter

Var 0000, value 0 // initialisation ; variable à modifier pour lancer le clignotement
{
&Ma_Led = 0
&Clignot = 10 // valeur initiale 
&Clignot = TIMER 0, -1, 50 
// 0 = valeur finale, -1 = incrément (ou decrément), 50 = intervalle ( * 10 msec)
// On compte donc ici de 10 à 0 par pas de 1, toutes les 0.5 secondes 

// On pourrait compter des valeurs croissantes avec un incrément positif.
}

Var 0001, name Clignot
{
L0 = MOD &Clignot, 2
&Ma_Led = L0 // allume ou éteint la led
}

Explication : la variable 0000 sert à lancer le clignotement (en lui assignant une valeur ; on peut aussi en faire une sous-routine, voir ci-dessous). Elle éteint la led, initialise la variable &Clignot au nombre de clignotements voulus (ici 10), lance le timer, qui va décompter (incrément  = -1) jusqu'à 0, toutes les 0,5 secondes.

Cela modifie chaque fois la valeur de &Clignot ; son code est donc exécuté toutes les 0,5 secondes : la fonction MOD renvoie le reste de la division par l'argument. Si l'argument est 2, comme ici, le reste sera alternativement 0 ou 1.

Il reste à affecter cette valeur 0 ou 1 à la led, selon ce reste.

Clignoter régulièrement malgré plusieurs conditions

Je modifié le code ci-dessus pour faire clignoter le voyant Gear up (alarme du train) quand il le faut. Ici le problème est que son déclenchement dépend de plusieurs conditions (train rentré, volets full, au moins un moteur gaz réduits), c'est pourquoi je l'ai implémenté dans une sous-routine.

Au départ, j'ai appelé cette sous-routine dans chacune des variables représentant ces conditions ; mais comme chacune modification de ces variables relançait le timer, j'obtenais un clignotement irrégulier. De plus je ne savais pas pendant combien de temps faire clignoter le voyant.

J'ai résolu le problème en appelant la sous-routine dans la variable 000 d'initialisation de SIOC et en mettant la valeur finale à 9999. De cette façon, le timer est lancé ou relancé en même temps que SIOC, et ne s'arrête que s'il atteignait la valeur 9999, soit au bout d'1h25 environ (généralement, je suis posé avant !). Pas d'inquiétude, cela n'a pas d'influence sur les FPS !

La routine &calc_voy_gear (non montrée ici), est la résultante des conditions (voir le programme) ; je m'en sers dans la variable blinking pour faire clignoter le voyant quand il le faut. Et cette fois, comme le timer est constant, plus de problème d'irrégularité. Si le vole peut dépasser 1h25, on peut relancer le timer dans une routine quelconque, en s'assurant toutefois de ne faire que si le voyant n'est pas en marche, pour éviter toute irrégularité dans son clignotement.

Notez pour finir que je n'allume pas la led directement, mais via une variable intermédiaire &temp_led_gear ; celle-ci commandera la led au travers de la routine &Affich_Leds (non montrée ici), qui prend en compte les conditions de courant et d'appui du bouton Test.

Var 0000, name init, Value 0 // initialisation
{
CALL &blink // lance le timer pour clignotement voyant 
}

//
// clignotement voyant Gear up
//

Var 0072, name blink, Link SUBRUTINE // timer
{
&blink_count = 0 
&blink_count = TIMER 9999 ,1 ,50
// La valeur finale maximale du timer est 9999, ce qui donne environ 1h 25 de clignotement.
// Rien n'empêche de relancer le timer s'il arrive à expiration trop tôt. 
}

Var 0073, name blink_count, Value 0 // met blinking à 1 une fois sur deux, génè
{
&blinking = MOD &blink_count ,2
}

Var 0074, name blinking
{
IF &calc_voy_gear = 1
{
&temp_led_gear = &blinking 
}
ELSE
{
&temp_led_gear = 0 
}
}

On peut écrire toute la dernière variable comme ceci (en utilisant l'astuce citée dans la section Eviter les IF dans les conditions simples) :

Var 0074, name blinking
{
&temp_led_gear = &calc_voy_gear * &blinking 
}

C'est plus court, plus élégant, mais un peu moins lisible.

Haut de page 

Passer un paramètre à une sous-routine

Pour compléter ce qui a été dit plus haut sur les sous-routine, il faut savoir qu'on peut passer un paramètre à une sous-routine avec la commande CALL : la valeur indiquée après le CALL sera donnée à la variable sous-routine elle-même lors de l'appel. Cela veut dire que si on écrit (pour reprendre l'exemple ci-dessus) :

Var 0000, name init, Value 0 // initialisation
{
CALL &blink 1000 
}

la variable &blink (qui doit être Link SUBRUTINE), prendra la valeur 1000 avant d'être exécutée.

On peut ensuite utiliser cette valeur à l'intérieur même de la routine, ici par exemple pour déterminer la valeur finale du timer, donc le temps de clignotement :

Var 0072, name blink, Link SUBRUTINE // timer
{
&blink_count = TIMER &blink ,1 ,50
}

Cela veut dire que l'on peut utiliser la même routine de clignotement pour des temps différents : il suffit de lui passer la valeur correspondante. Ce principe peut évidemment être généralisé à toutes les sous-routines, qui deviennent ainsi plus générales.

Haut de page 

Asservir les voyants à la présence de courant et au bouton Test

Quoi de moins réaliste que des afficheurs ou des voyants (leds) qui s'allument dès la mise sous tension du cockpit, sans tenir compte de la présence de courant dans l'avion ?

En fait, il est assez facile d'asservir tous ces éléments à la présence de courant. L'astuce consiste à ne pas commander la led directement, mais au travers d'une variable intermédiaire, que j'ai préféré appeler "temporaire" (le préfixe temp ne risque pas de faire confusion avec les inter... rupteurs).

Donc, pour chaque élément (led, afficheur), on définit deux variables.

  • L'une est directement liée à l'élément, par exemple pour la led volets bas :
Var 0404, name led_flaps_down, Link IOCARD_OUT, Output 16 // Led volets bas
  • L'autre est la variable "temporaire", qui n'est liée à rien :
    Var 0454, name temp_flap_B, Value 0 // init led volets bas

La première déclenche réellement l'allumage de la led ; la seconde figure dans le calcul qui détermine si elle doit être allumée ou non. Ici, cela donne (j'ai rajouté les deux autres leds, transit (&temp_flap_t) et volets APR (&temp_flap_APR):

Var 0430, name calc_led_flaps, Link SUBRUTINE // gère les leds des volets
{
IF &fs_pos_flaps = 0
{
&temp_flap_APR = 0 
&temp_flap_B = 0 
&temp_flap_T = 0 
}
ELSE
{
IF &fs_pos_flaps = 16383
{
&temp_flap_APR = 0 
&temp_flap_B = 1 
&temp_flap_T = 0 
}
ELSE
{
IF &fs_pos_flaps = 8191
{
&temp_flap_APR = 1 
&temp_flap_B = 0 
&temp_flap_T = 0 
}
ELSE
{
&temp_flap_APR = 0 
&temp_flap_B = 0 
&temp_flap_T = 1 
}
}
}
}

Selon la position des volets dans FS (&fs_pos_flap), on allume la led bas, APR ou transit (si les volets ne sont ni à 0, ni APR, ni Bas, c'est qu'ils sont en mouvement, et on allume Transit). Notez que ce calcul est fait dans une sous-routine, ce qui permet de l'appeler non seulement quand l'inter de volets est actionné, mais aussi quand la position des volets change dans FS (clic à l'écran), ou encore à l'initialisation (comme les volets sont commandés par un inter 3 positions,la position APR donne 0 sur les 2 entrées).

Ensuite, on une autre routine va se charger d'allumer effectivement les leds, selon la présence de courant (variable &courant à 1),

Var 0340, name affich_Leds, Link SUBRUTINE // allume ou éteint les leds selon courant 
{
IF &courant = 0 // pas de courant
{
&led_flaps_APH = 0 
&led_flaps_down = 0 
&led_trans_flap = 0 
}
ELSE
{
&led_flaps_APH = &temp_flap_APR 
&led_flaps_down = &temp_flap_B 
&led_trans_flap = &temp_flap_T 
}
}

Il est également facile de rajouter l'action du bouton poussoir AnnunTest, qui doit tout allumer, s'il y a du courant, pendant qu'on le presse :

Var 0340, name affich_Leds, Link SUBRUTINE // allume ou éteint les leds selon courant 
{
IF &courant = 0 // pas de courant
{
&led_flaps_APH = 0 
&led_flaps_down = 0 
&led_trans_flap = 0 
}
ELSE
{
&led_flaps_APH = &temp_flap_APR 
&led_flaps_down = &temp_flap_B 
&led_trans_flap = &temp_flap_T 
IF &sw_test = 1 // bouton Test pressé, ne fonctionne que si courant et à 1
{
&led_flaps_APH = 1 // tous les voyants sont allumés
&led_flaps_down = 1 
&led_trans_flap = 1 
}
ELSE // Bouton Test relâché, on remet les voyants à leur état calculé avant le test
{
&led_flaps_APH = &temp_flap_APR 
&led_flaps_down = &temp_flap_B 
&led_trans_flap = &temp_flap_T 
}
}
}

Quand on relâche ce bouton, les leds sont remises aux valeurs temporaires, c'est-à-dire à ce qu'elles étaient avant l'appui du bouton.

Si vous regardez le programme entier, vous verrez que ce principe est étendu à toutes les leds. Les afficheurs des radios et transpondeurs ont des conditions supplémentaires : inter Avionics sur ON pour les deux, bouton ON encore en plus pour le transpondeur. Je n'ai pas implémenté de bouton ON OFF pour la radio, mais il serait très simple de le faire.

Haut de page 

Utiliser les afficheurs 7-segments avec SIOC

Pour allumer des chiffres sur un afficheur avec SIOC, il faut définir des variables comme ceci :

Var 2103, name aff_c1ac_1, Link IOCARD_DISPLAY, Digit 9, Numbers 1 

Var 2104, name aff_c1ac, Link IOCARD_DISPLAY, Digit 5, Numbers 4

Le digit indique le numéro de l'afficheur (sachant que le premier est numéroté 0), et Numbers indique le nombre de chiffres du nombre à afficher.

En plus des chiffres, on peut utiliser les valeurs suivantes :

  • Valeur - 999999 = éteint l'afficheur
  • Valeur - 999998 = affiche le signe "-"
  • Valeur - 999997 = affiche "6"
  • Valeur - 999996 = affiche "t"
  • Valeur - 999995 = affiche "d"
  • Valeur - 999994 = affiche "_"

Points importants (merci à Bob, voir ce sujet sur le forum Air-cockpit)

"Quand on envoie un nombre à un afficheur, il est très important que le nombre de chiffres de la variable corresponde au nombre de chiffres de l'afficheur (on peut utiliser la fonction LIMIT pour s'en assurer).

Exemple : de 0-999 pour un afficheur à 3 chiffres.

Si, à cause d'une faute dans le code, on envoie un nombre à 5 chiffres sur un afficheur à 3 chiffres, on va provoquer des bugs, apparemment parce qu'on va décaler une base de registre dans l'exécuteur de SIOC et perturber toutes les autres variables, ce qui a pour cause d'allumer ou d'éteindre des LED de façon aléatoire.

Ce n'est pas vraiment un bug de SIOC mais une absence de sécurité sur les variable display si on dépasse leur longueur.

On peut aussi provoquer cet effet en envoyant une variable négative (la VS p.ex.) si on oublie de déclarer son Type=1 qui la force à prendre un valeur négative. La variable va alors prendre une valeur dans les 65000 et donc dépassera le nombre de chiffres de l'afficheur".

Haut de page