A4A : Changement de licence

Bonjour,

« Ada for Automation » était jusqu’à présent distribué sous la licence « GMGPL : GNAT Modified General Public License « .

Les bibliothèques mises en œuvre dans « Ada for Automation », GtkAda et Gnoga, étant passées à la GPL V3 + GCC RUNTIME LIBRARY EXCEPTION, j’ai également procédé au changement de licence dans les mêmes termes car cela me semble cohérent.

Bien sûr, il vous appartient de juger si cette licence convient à votre utilisation.

Pour le côté pratique, d’habitude, quand je veux modifier une ligne dans une série de fichiers, j’invoque ce bon vieux « sed » comme ceci par exemple :

cd Ada/A4A
find . -name "*.ad*" -exec sed -i 's/Copyright (C) 2012-2014/Copyright (C) 2012-2015/g' {} \;

Cela fonctionne bien avec une ligne à remplacer mais c’est plus laborieux avec plusieurs…

Comme je suis un peu fainéant et que « sed » n’avait pas l’air d’être conçu pour le cas, j’ai préféré apprendre un peu de Python que de me taper tous les fichiers un par un et ça a donné le script « ChangeLicense.py » :

#!/usr/bin/env python
# ChangeLicense.py
# cd Ada/A4A
# find . -name "*.ad*" -exec sed -i 's/Copyright (C) 2012-2014/Copyright (C) 2012-2015/g' {} \;
# find . -name "*.ad*" -execdir python ~/Ada/ChangeLicense.py {} \;

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("file", help="the file to operate on")
args = parser.parse_args()
print(args.file)

old = """
-----------------------------------------------------------------------
--                       Ada for Automation                          --
--                                                                   --
--              Copyright (C) 2012-2015, Stephane LOS                --
--                                                                   --
-- This library is free software; you can redistribute it and/or     --
-- modify it under the terms of the GNU General Public               --
-- License as published by the Free Software Foundation; either      --
-- version 2 of the License, or (at your option) any later version.  --
--                                                                   --
-- This library is distributed in the hope that it will be useful,   --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of    --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU --
-- General Public License for more details.                          --
--                                                                   --
-- You should have received a copy of the GNU General Public         --
-- License along with this library; if not, write to the             --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330,      --
-- Boston, MA 02111-1307, USA.                                       --
--                                                                   --
-- As a special exception, if other files instantiate generics from  --
-- this unit, or you link this unit with other files to produce an   --
-- executable, this  unit  does not  by itself cause  the resulting  --
-- executable to be covered by the GNU General Public License. This  --
-- exception does not however invalidate any other reasons why the   --
-- executable file  might be covered by the  GNU Public License.     --
-----------------------------------------------------------------------
"""


new = """
------------------------------------------------------------------------------
--                            Ada for Automation                            --
--                                                                          --
--                   Copyright (C) 2012-2016, Stephane LOS                  --
--                                                                          --
-- This library is free software;  you can redistribute it and/or modify it --
-- under terms of the  GNU General Public License  as published by the Free --
-- Software  Foundation;  either version 3,  or (at your  option) any later --
-- version. This library is distributed in the hope that it will be useful, --
-- but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN- --
-- TABILITY or FITNESS FOR A PARTICULAR PURPOSE.                            --
--                                                                          --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception,   --
-- version 3.1, as published by the Free Software Foundation.               --
--                                                                          --
-- You should have received a copy of the GNU General Public License and    --
-- a copy of the GCC Runtime Library Exception along with this program;     --
-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
-- <http://www.gnu.org/licenses/>.                                          --
--                                                                          --
------------------------------------------------------------------------------
"""


file_content = ''
with open(args.file) as infile:
    for line in infile:
        file_content = file_content + line

file_content = file_content.replace(old, new)

with open(args.file, 'w') as outfile:
    outfile.write(file_content)

Et ça s’utilise presque pareil :

find . -name "*.ad*" -execdir python ~/Ada/ChangeLicense.py {} \;

Bon, je ne suis pas devenu un pro de Python avec ceci mais j’ai bien aimé l’expérience.

Ce script peut sans doute être utilisé pour d’autres cas en adaptant les chaînes.
Si ça peut vous servir je vous l’offre.

Cordialement,
Stéphane

A4A : Modbus TCP Server + Web HMI = A4A_Piano

Bonjour,

J’évoquais ce tantôt en fin d’article une application mêlant un serveur Modbus TCP et une IHM Web pour constituer un « piano » pour mes essais et démonstrations.

Cette application s’appuie donc sur le framework Gnoga qui permet de réaliser des IHM Web entre autres. Gnoga, de Monsieur David Botton, intègre également les Simple Components de Monsieur Dmitry Kasakov qui fournissent entre autres le serveur web embarqué dans l’application.

Gnoga permet la création dynamique de contenu web et l’animation des pages depuis une application écrite en Ada.

En ce moment je joue beaucoup avec Inkscape qui permet plein de fantaisie dans l’édition au format SVG de graphiques vectoriels.
C’est bien sûr intéressant pour dessiner des synoptiques que l’on pourra animer relativement facilement et cela permet aussi de faire un beau schéma :

Structure de l'application A4A_Piano
Structure de l’application A4A_Piano

L’application « A4A_Piano » met en œuvre le noyau « kernel0 » qui intègre seulement un serveur Modbus TCP, fourni par libmodbus, et une tâche principale ainsi qu’une tâche périodique.
Le noyau gère les échanges entre les tâches et le serveur Modbus TCP, la tâche principale appelle la fonction principale du programme utilisateur, la tâche périodique appelle la fonction périodique du programme utilisateur.
Le programme utilisateur est en charge de la gestion des échanges entre les zones mémoire E/S et les objets utilisateur ainsi que du traitement de ces objets.
C’est de l’histoire ancienne.

L’IHM Web est composée d’une part des vues affichées dans le / les navigateurs, ces vues étant constituées naturellement de technologies Web telles HTML 5, CSS, SVG et JavaScript, et d’une contrepartie en Ada fondée sur le patron de conception Modèle-Vue-Contrôleur un peu bricolé.

Tel que réalisé dans « A4A_Piano », le code HTML est juste une amorce, le SVG et le CSS ont été écrits à la main, je vous conseille la lecture des tutoriels de Monsieur Jenkov, courts et efficaces.
Avec un peu d’habitude on arrive à faire de belles choses avec SVG et Inkscape et on peut alors penser plus grand.

En fait, plutôt que de tout piloter d’un côté ou de l’autre, j’ai pris le parti de laisser l’aspect graphique côté technologies Web et de n’agir que sur un attribut d’état. En fonction de l’état, le CSS fait le reste pour l’animation.

Chaque objet de synoptique HTML ou SVG que l’on souhaite animer dispose d’un identifiant dont on pourra se servir pour le connecter à un pendant « View », un objet dérivé des objets fournis par Gnoga.
Pour chaque connexion on va instancier une collections d’objets « View » ainsi qu’une tâche gérant le rafraîchissement.

Ces objets « View » gèrent la mise à jour des objets HTML ou SVG auxquels on les a connectés à la création de la connexion ainsi que l’interaction avec l’utilisateur, c’est à dire les « Cliques » souris par exemple.
D’un autre côté on les a également connectés avec un objet « Controller » dont le rôle est d’une part de servir de proxy aux objets « View » et de gérer l’accès en lecture / écriture des données utilisateur.
La tâche « Scanner » va piloter elle le rafraîchissement des « Controllers ».

Le code n’est sans doute pas optimal mais il est quand même disponible dans le dépôt comme d’habitude.

Pour information, j’espère bien vous voir à ces 17èmes Journées du Logiciel Libre…
D’autant plus que les Gentils Organisateurs ont bien voulu m’accorder la faveur insigne d’un stand pour la présentation du projet « Ada for Automation » !

JDLL2016

Cordialement,
Stéphane

A4A : Exemple d’application 6 : Modbus TCP Client / Serveur + deux canaux Hilscher

Bonjour,

Comme je l’indiquais récemment l’application « app6_gui » offre :

  • un serveur Modbus TCP pour y connecter par exemple un SCADA du marché,
  • une fonction IO Scanning avec une tâche client Modbus TCP par serveur d’E/S,
  • deux canaux cifX Hilscher, pour gérer deux cartes, par exemple une PROFIBUS DP Maitre et une EtherCAT Maitre, ou pour gérer une carte à deux canaux comme par exemple un PROFIBUS DP Maitre et un CANopen Maitre,
  • une interface graphique avec GtkAda, permettant de consulter l’état des communications et de l’application et de démarrer ou arrêter les programmes utilisateur,
  • l’intégration du « cifX TCP Server » permettant la configuration et le diagnostic des cartes par SYCON.net via une connexion TCP/IP.

Je voulais faire un article à part pour cette application, avec des images. J’ai donc monté la manipulation suivante :

Manip-2016-01-08

On a donc un PC, sous Debian Jessie et un noyau Linux 64 bit standard, avec deux cartes Hilscher PCI, une cifX 50-DP configurée en Maître PROFIBUS DP et une cifX 50-RE configurée en Maître EtherCAT :

PC+2cifX50

Comme Esclave PROFIBUS DP j’ai une carte Hilscher CB-AB32-DPS, un « piano » avec deux octets en entrée et autant en sortie qui permet de monter une manipulation en deux secondes et demie :

CB-AB32-DPS

Pour EtherCAT, j’ai également un « piano », le NXIO 500-RE, qui reçoit sa fonctionnalité en insérant la carte MMC qui convient, ici EtherCAT Esclave, avec toujours deux octets d’E/S :

NXIO-500-RE

Le client Modbus TCP qui interroge le serveur de l’application 6 est Modbus Poll dont j’ai déjà fait mention il y a longtemps.

Quant au client Modbus TCP, ce que certains appellent IO Scanning, il est configuré pour exécuter deux requêtes sur un serveur d’un genre particulier sur lequel je reviendrai plus tard.

Je vous épargne la trace laissée par l’application dans le terminal depuis lequel elle est lancée, qui est certes informative, voire même didactique, mais un peu indigeste.

La fenêtre principale s’ouvre et l’on y trouve les onglets suivants.

La vue « Identité », (remarquez la version !) :

A4A-App6-Identity

La vue « Etat général » en deux bouts :

A4A-App6-GeneralStatus1

A4A-App6-GeneralStatus2

On notera la présence des informations d’état des deux tâches « Fieldbus ».

On trouve ensuite la vue d’état du serveur Modbus TCP qui affiche les compteurs de requêtes, et bien évidemment celui qui bouge est celui de la fonction 3 configurée côté Modbus Poll :

A4A-App6-ModbusTCPServer

La vue « Client Modbus TCP » affiche les informations d’état concernant les deux requêtes configurées pour le serveur secret pour le moment, le second client étant désactivé :

A4A-App6-ModbusTCPClients

Notez que lorsque l’on rajoute des requêtes au niveau de la configuration des clients, la vue d’état est bien sûr adaptée automatiquement.

Puis viennent les deux vues d’état des canaux Hilscher cifX.

L’un est donc PROFIBUS DP Maître :

A4A-App6-ProfibusDPMaster

Et l’autre est EtherCAT Maître :

A4A-App6-EtherCATMaster

Le programme utilisateur à l’œuvre dans les trois tâches est très sensiblement identique :

   procedure Process_IO is

      Elapsed_TON_1 : Ada.Real_Time.Time_Span;

   begin

      if First_Cycle then

         Output_Byte := Pattern_Byte;

         First_Cycle := False;

      end if;

      Tempo_TON_1.Cyclic (Start   => not TON_1_Q,
                          Preset  => Ada.Real_Time.Milliseconds (500),
                          Elapsed => Elapsed_TON_1,
                          Q       => TON_1_Q);

      if TON_1_Q then

         case Cmd_Byte is

         when 0 =>
            Output_Byte := ROR (Value => Output_Byte, Amount => 1);

         when 1 =>
            Output_Byte := ROL (Value => Output_Byte, Amount => 1);

         when others => Output_Byte := Pattern_Byte;

         end case;

      end if;

   end Process_IO;

On trouve aussi dans « Ada for Automation » une application exemple 5, « app5 », identique à « app6 » mais qui ne gère elle qu’un seul canal Hilscher cifX.

Mais quel est donc ce serveur Modbus TCP secret dont je vous entretiens depuis le début ?
La NXIO 500-RE ne pourrait-elle faire l’affaire avec une MMC ad hoc ? Sans doute, mais pour des raisons qui m’échappent ce firmware n’existe pas…

Aussi, je me suis dit que je n’avais qu’à utiliser le « kernel 0 », issu de « app1simu », et qui fournit un serveur Modbus TCP.
Oui mais, et les boutons et LEDs ? GtkAda aurait bien sûr pu convenir mais comme je jouais avec Gnoga…

Taa taan ! Voilà un « piano » piloté depuis le navigateur !

A4A-App6-A4A-Piano-Local

Comme c’est du SVG, donc du vectoriel, même depuis un mobile ça se pilote.

Et en plus l’application supporte les connexions multiples ! On peut jouer à plusieurs ! J’adore…
Promis, dès que mon code est un peu plus propre je l’envoie sur le dépôt.

Cordialement,
Stéphane

A4A : Applications et Noyaux

Bonjour,

Le framework « Ada for Automation » est fourni avec un certain nombre d’applications exemples, simple applications consoles ou avec interface graphique en GtkAda, et très bientôt je l’espère avec interface web grâce à Gnoga, un autre framework que je vous avais présenté très brièvement ici, et avec lequel je m’instruis beaucoup tout en m’amusant.

Ainsi par exemple le projet de base A4A est proposé en version console, dont le fichier projet est « a4a.gpr », et en version interface graphique avec « a4a_gui.gpr ».
Cette application est la plus simple possible et joue avec les deux octets d’entrée et sortie à sa disposition, ne mettant en œuvre que des fonctions de rotation et d’affectation.

Ce projet repose sur un noyau, partagé par les deux versions, qui gère le programme utilisateur, un serveur Modbus TCP et des clients Modbus TCP, un pour chaque serveur d’E/S.
La tâche principale collabore avec une tâche périodique annexe via la nouvelle DPM générique dont il a été question précédemment, chacune des tâches disposant d’un programme utilisateur à exécuter à l’état RUN.
Le serveur Modbus TCP comme les clients échangent leurs données avec la tâche principale également via des DPM spécifiques dont le rôle est d’assurer la cohérence des données échangées.

Via le mécanisme d’extension des projets supporté par la suite d’outils fournis par AdaCore, on peut construire des projets héritant des fichiers du projet parent et ajoutant de nouveaux fichiers ou en les substituant s’ils portent le même nom.

C’est ce mécanisme qui est utilisé par exemple avec l’application 1 présentée ici, et encore ici.

Aussi, comme cette application hérite de tout A4A, son dossier source ne contient que ce qui lui est propre, son identification, sa configuration et le programme utilisateur.

Pour cette application 1, ne disposant pas de la partie opérative on a développé une application de simulation, nommée « app1simu ».
Cette application ne nécessitant que la fonction serveur Modbus TCP on a créé un nouveau noyau issu du noyau de A4A auquel on a ôté la gestion des clients Modbus TCP.
Ce noyau logeait dans le dossier de l’application « app1simu » puisqu’il lui était spécifique.

Il en fut de même pour l’application 2 qui met en œuvre une carte Hilscher cifX en lieu et place des clients Modbus TCP.

Et encore de même avec l’application 3 qui remplace les clients Modbus TCP par un maitre Modbus RTU.

Le problème dans cette situation c’est que pour réutiliser un noyau de « app1simu », de « app2 » ou de « app3 », il eu fallut hériter de ces projets ou lister les sources que l’on souhaitait réutiliser, c’est possible, ou pire dupliquer les sources, beurk… Bref, ce n’était pas satisfaisant d’autant que je comptais multiplier les noyaux… On multiplie ce qu’on peut.

J’ai donc réorganisé le framework de sorte que les noyaux se retrouvent dans leurs propres dossiers.
Piloté par une variable externe ou un scénario depuis GPS, le dossier sélectionné est ajouté au projet en cours de traitement.

Ainsi le noyau de « app1simu » est maintenant le « kernel0 », celui de A4A est le « kernel1 », celui de « app3 » est devenu « kernel2″…

C’est beau et ça fonctionne à peu près.
La variable est définie dans un fichier de projet partagé par les autres fichiers de projets, le projet « shared.gpr », et reçoit une valeur initiale par défaut qui est « kernel1 » si elle n’est pas définie par ailleurs.
Attention cependant de laisser GPS terminer ses opérations de mise à jour des références croisées avant de changer de noyau au moyen de la liste déroulante du scénario sous peine de corruption de sa base de données, du moins avec la version Debian.
Ce n’est pas très grave puisqu’il suffit de supprimer celle-ci pour qu’elle soit reconstituée mais c’est un peu pénible.

...
   type Kernel_Type is
      ("kernel0",   --  Modbus TCP Server
       "kernel1",   --  Modbus TCP Server + Modbus TCP IO Scanning
       "kernel2",   --  Modbus TCP Server + Modbus RTU IO Scanning
       "kernel3",   --  Modbus TCP Server + one Hilscher cifX channel
       "kernel4",   --  Modbus TCP Server + Modbus TCP IO Scanning
                    --  + one Hilscher cifX channel
       "kernel5"    --  Modbus TCP Server + Modbus TCP IO Scanning
      );            --  + two Hilscher cifX channels

   Kernel : Kernel_Type := external ("Kernel", "kernel1");
...

Et l’on voit que le noyau de « app2 » est devenu le « kernel3 », que le « kernel4 » fusionne le « kernel1 » et le « kernel3 » et que le « kernel5 » ajoute un canal Hilscher au « kernel4 ».

J’ai bien sûr créé les applications exemples « app5 » qui roule avec le « kernel4 » et « app6 » motorisé par le « kernel5 », le tout en version console ou GUI.

Comme chaque canal Hilscher est susceptible de gérer un bus de terrain différent, classique ou sur Ethernet Temps réel, chaque canal est géré par une tâche indépendante qui exécute son propre programme utilisateur avec une période que l’on peut configurer.
Les tâches « fieldbus » communiquent avec la tâche principale via les DPM génériques déjà évoquées.

Ainsi l’application « app6_gui », mon top model ;-), offre :

  • un serveur Modbus TCP pour y connecter par exemple un SCADA du marché,
  • une fonction IO Scanning avec une tâche client Modbus TCP par serveur d’E/S,
  • deux canaux cifX Hilscher, pour gérer deux cartes, par exemple une PROFIBUS DP Maitre et une EtherCAT Maitre, ou pour gérer une carte à deux canaux comme par exemple un PROFIBUS DP Maitre et un CANopen Maitre,
  • une interface graphique avec GtkAda, permettant de consulter l’état des communications et de l’application et de démarrer ou arrêter les programmes utilisateur,
  • l’intégration du « cifX TCP Server » permettant la configuration et le diagnostic des cartes par SYCON.net via une connexion TCP/IP.

Je vous remercie pour votre attention et vous souhaite une très bonne et heureuse année 2016 pleine de projets, personnels comme professionnels, et de réussite.

N’hésitez pas à nous solliciter.

Cordialement,
Stéphane

A4A : « Dual Port Memory » générique en Ada

Bonjour,

Dans un système constitué de multiples tâches collaborant il est nécessaire de mettre en œuvre une communication garantissant la cohérence des données échangées entre les tâches.

L’un des mécanismes préconisés en Ada pour fournir une solution est fondé sur les types protégés.
On en trouve quelques explications par exemple ici mais le livre de Monsieur John Barnes est certainement plus didactique.

Ce mécanisme est déjà utilisé dans « Ada for Automation » notamment par exemple pour les interfaces entre la tâche principale et les tâches clients Modbus TCP ou entre la tâche principale et la tâche serveur Modbus TCP.

Pour faire court, un type protégé encapsule les données privées et protégées et doit fournir les fonctions et procédures permettant la manipulation des données. Les fonctions permettent uniquement la lecture des données protégées et comme elles ne peuvent modifier celles-ci elles peuvent être appelées de façon concurrente sans problème tandis que les procédures permettent la lecture et l’écriture des données protégées et bloquent le temps de leur exécution les appels des fonctions et procédures concurrentes.

Cependant, il se trouve que le besoin était jusque là considéré comme spécifique et cela a donc conduit à des mises en œuvre spécifiques.
Mais si l’on considère le besoin d’une façon générale on peut aboutir à une solution plus générique qui peut convenir dans une grande majorité des cas d’utilisation.
Ainsi, dans « Ada for Automation », la tâche principale est par défaut associée à une tâche périodique. Ces tâches jusque là tournaient indépendamment l’une de l’autre et j’avais remis aux calendes de les faire communiquer entre elles autrement que par variables partagées, sans gestion de la cohérence donc.

En cette fin d’année les calendes sont donc arrivées !

J’ai nommé le mécanisme « Dual Port Memory » générique par déformation professionnelle, en référence à la fameuse Dual Port Memory Hilscher dont il est souvent question dans les pages de ce site. Et aussi bien sûr parce que la similitude est bien réelle.

Vous voudrez bien vous référer au schéma ci-après pour les explications qui suivent.

Si le mécanisme est générique, les données échangées sont structurées dans des types de données propres à l’application.
Le paquetage implémentant le mécanisme générique est instancié en lui fournissant les types de données Entrées et Sorties et figure dans la partie privée du paquetage « Interface ». Entrée et Sortie sont vues ainsi par l’une des tâches, l’autre voyant bien sûr l’inverse.

Ce paquetage contient également les zones d’échanges pour les programmes utilisateur de chacune des tâches, de façon similaire aux mémoires images process, ces zones étant aussi des instances des mêmes types Entrées et Sorties.
Il contient enfin les fonctions et procédures assurant la mise à jour des zones d’E/S, ces fonctions et procédures étant appelées par les tâches en amont et aval des traitements utilisateur.

A Generic DPM in Ada
A Generic DPM in Ada

Voici la spécification du paquetage générique DPM :

-----------------------------------------------------------------------
--                       Ada for Automation                          --
--                                                                   --
--              Copyright (C) 2012-2015, Stephane LOS                --
--                                                                   --
-- This library is free software; you can redistribute it and/or     --
-- modify it under the terms of the GNU General Public               --
-- License as published by the Free Software Foundation; either      --
-- version 2 of the License, or (at your option) any later version.  --
--                                                                   --
-- This library is distributed in the hope that it will be useful,   --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of    --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU --
-- General Public License for more details.                          --
--                                                                   --
-- You should have received a copy of the GNU General Public         --
-- License along with this library; if not, write to the             --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330,      --
-- Boston, MA 02111-1307, USA.                                       --
--                                                                   --
-- As a special exception, if other files instantiate generics from  --
-- this unit, or you link this unit with other files to produce an   --
-- executable, this  unit  does not  by itself cause  the resulting  --
-- executable to be covered by the GNU General Public License. This  --
-- exception does not however invalidate any other reasons why the   --
-- executable file  might be covered by the  GNU Public License.     --
-----------------------------------------------------------------------

generic
   type Inputs_Type is private;
   type Outputs_Type is private;

package A4A.Generic_Dual_Port_Memory is

   protected type Inputs_Dual_Port_Memory_Area is

      procedure Set_Data (Data_In : in Inputs_Type);

      function Get_Data return Inputs_Type;

   private
      Data : Inputs_Type;
   end Inputs_Dual_Port_Memory_Area;

   protected type Outputs_Dual_Port_Memory_Area is

      procedure Set_Data (Data_In : in Outputs_Type);

      function Get_Data return Outputs_Type;

   private
      Data : Outputs_Type;
   end Outputs_Dual_Port_Memory_Area;

   type Dual_Port_Memory is
      record
         Inputs : Inputs_Dual_Port_Memory_Area;

         Outputs : Outputs_Dual_Port_Memory_Area;
      end record;

   type Dual_Port_Memory_Access
     is access all Dual_Port_Memory;

end A4A.Generic_Dual_Port_Memory;

Et le corps :

-----------------------------------------------------------------------
--                       Ada for Automation                          --
--                                                                   --
--              Copyright (C) 2012-2015, Stephane LOS                --
--                                                                   --
-- This library is free software; you can redistribute it and/or     --
-- modify it under the terms of the GNU General Public               --
-- License as published by the Free Software Foundation; either      --
-- version 2 of the License, or (at your option) any later version.  --
--                                                                   --
-- This library is distributed in the hope that it will be useful,   --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of    --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU --
-- General Public License for more details.                          --
--                                                                   --
-- You should have received a copy of the GNU General Public         --
-- License along with this library; if not, write to the             --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330,      --
-- Boston, MA 02111-1307, USA.                                       --
--                                                                   --
-- As a special exception, if other files instantiate generics from  --
-- this unit, or you link this unit with other files to produce an   --
-- executable, this  unit  does not  by itself cause  the resulting  --
-- executable to be covered by the GNU General Public License. This  --
-- exception does not however invalidate any other reasons why the   --
-- executable file  might be covered by the  GNU Public License.     --
-----------------------------------------------------------------------

package body A4A.Generic_Dual_Port_Memory is

   protected body Inputs_Dual_Port_Memory_Area is

      procedure Set_Data  (Data_In : in Inputs_Type) is
      begin
         Data := Data_In;
      end Set_Data;

      function Get_Data return Inputs_Type is
      begin
         return Data;
      end Get_Data;

   end Inputs_Dual_Port_Memory_Area;

   protected body Outputs_Dual_Port_Memory_Area is

      procedure Set_Data  (Data_In : in Outputs_Type) is
      begin
         Data := Data_In;
      end Set_Data;

      function Get_Data return Outputs_Type is
      begin
         return Data;
      end Get_Data;

   end Outputs_Dual_Port_Memory_Area;

end A4A.Generic_Dual_Port_Memory;

Pour l’exemple d’instanciation voici ci-après la spécification du paquetage réalisant celle nécessaire pour l’exemple d’interface entre la tâche principale et la tâche périodique 1.

On y trouve :

  • la définition des types Entrées et Sorties,
  • la définition des zones mémoire correspondantes pour la tâche principale et la tâche périodique 1,
  • les procédures gérant les échanges entre les zones mémoire et les données protégées,
  • et dans la partie privée, l’instance du paquetage générique.

Pour votre application, il est nécessaire d’adapter les types de données Entrées et Sorties comme vous l’avez sans doute compris.

-----------------------------------------------------------------------
--                       Ada for Automation                          --
--                                                                   --
--              Copyright (C) 2012-2015, Stephane LOS                --
--                                                                   --
-- This library is free software; you can redistribute it and/or     --
-- modify it under the terms of the GNU General Public               --
-- License as published by the Free Software Foundation; either      --
-- version 2 of the License, or (at your option) any later version.  --
--                                                                   --
-- This library is distributed in the hope that it will be useful,   --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of    --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU --
-- General Public License for more details.                          --
--                                                                   --
-- You should have received a copy of the GNU General Public         --
-- License along with this library; if not, write to the             --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330,      --
-- Boston, MA 02111-1307, USA.                                       --
--                                                                   --
-- As a special exception, if other files instantiate generics from  --
-- this unit, or you link this unit with other files to produce an   --
-- executable, this  unit  does not  by itself cause  the resulting  --
-- executable to be covered by the GNU General Public License. This  --
-- exception does not however invalidate any other reasons why the   --
-- executable file  might be covered by the  GNU Public License.     --
-----------------------------------------------------------------------

with A4A.Generic_Dual_Port_Memory; use A4A;

package A4A.Application.Main_Periodic1 is

   --------------------------------------------------------------------
   --  Main / Periodic1 Tasks interface
   --------------------------------------------------------------------

   type Inputs is
      record
         A : Boolean := False;
         B : Word    :=     0;
         C : Byte    :=     0;
      end record;

   type Outputs is
      record
         X : Boolean := False;
         Y : Word    :=     0;
         Z : Byte    :=     0;
      end record;

   --------------------------------------------------------------------
   --  Memory areas available to Main task
   --------------------------------------------------------------------

   Main_Inputs  : Inputs;
   --  These are the Inputs from task Periodic1 to Main

   Main_Outputs : Outputs;
   --  These are the Outputs from task Main to Periodic1

   procedure Get_Main_Inputs;
   --  This procedure updates Main_Inputs from DPM
   --  Main task should call it before calling Main_Cyclic

   procedure Set_Main_Outputs;
   --  This procedure updates DPM from Main_Outputs
   --  Main task should call it after calling Main_Cyclic

   --------------------------------------------------------------------
   --  Memory areas available to Periodic1 task
   --------------------------------------------------------------------

   Periodic1_Inputs  : Outputs;
   --  These are the Inputs from task Main to Periodic1

   Periodic1_Outputs : Inputs;
   --  These are the Outputs from task Periodic1 to Main

   procedure Get_Periodic1_Inputs;
   --  This procedure updates Periodic1_Inputs from DPM
   --  Periodic1 task should call it before calling Periodic1_Run

   procedure Set_Periodic1_Outputs;
   --  This procedure updates DPM from Periodic1_Outputs
   --  Periodic1 task should call it after calling Periodic1_Run

private

   package DPM is new
     A4A.Generic_Dual_Port_Memory
       (Inputs_Type  => Inputs,
        Outputs_Type => Outputs);

   The_DPM : DPM.Dual_Port_Memory;

end A4A.Application.Main_Periodic1;

En voici le corps :

-----------------------------------------------------------------------
--                       Ada for Automation                          --
--                                                                   --
--              Copyright (C) 2012-2015, Stephane LOS                --
--                                                                   --
-- This library is free software; you can redistribute it and/or     --
-- modify it under the terms of the GNU General Public               --
-- License as published by the Free Software Foundation; either      --
-- version 2 of the License, or (at your option) any later version.  --
--                                                                   --
-- This library is distributed in the hope that it will be useful,   --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of    --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU --
-- General Public License for more details.                          --
--                                                                   --
-- You should have received a copy of the GNU General Public         --
-- License along with this library; if not, write to the             --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330,      --
-- Boston, MA 02111-1307, USA.                                       --
--                                                                   --
-- As a special exception, if other files instantiate generics from  --
-- this unit, or you link this unit with other files to produce an   --
-- executable, this  unit  does not  by itself cause  the resulting  --
-- executable to be covered by the GNU General Public License. This  --
-- exception does not however invalidate any other reasons why the   --
-- executable file  might be covered by the  GNU Public License.     --
-----------------------------------------------------------------------

package body A4A.Application.Main_Periodic1 is

   procedure Get_Main_Inputs is
   begin
      Main_Inputs := The_DPM.Inputs.Get_Data;
   end Get_Main_Inputs;

   procedure Set_Main_Outputs is
   begin
      The_DPM.Outputs.Set_Data (Main_Outputs);
   end Set_Main_Outputs;

   procedure Get_Periodic1_Inputs is
   begin
      Periodic1_Inputs := The_DPM.Outputs.Get_Data;
   end Get_Periodic1_Inputs;

   procedure Set_Periodic1_Outputs is
   begin
      The_DPM.Inputs.Set_Data (Periodic1_Outputs);
   end Set_Periodic1_Outputs;

end A4A.Application.Main_Periodic1;

Dans la tâche principale on rajoute les deux incantations autour de l’appel au programme utilisateur :

package body A4A.Kernel.Main is
...
            A4A.Application.Main_Periodic1.Get_Main_Inputs;
            A4A.Application.Main_Cyclic;
            A4A.Application.Main_Periodic1.Set_Main_Outputs;
...

Et on procède de même avec la tâche périodique 1 :

package body A4A.Kernel is
...

   procedure Periodic1_Run is
   begin
      A4A.Application.Main_Periodic1.Get_Periodic1_Inputs;
      A4A.Application.Periodic1_Cyclic;
      A4A.Application.Main_Periodic1.Set_Periodic1_Outputs;
   end Periodic1_Run;

...

Enfin, on peut jouer avec notre interface depuis la fonction principale… :

package body A4A.Application is
...

   procedure Main_Cyclic is
      My_Ident : constant String := "A4A.Application.Main_Cyclic";
   begin

      Map_Inputs;

      Map_HMI_Inputs;

      --  Playing with tasks interface
      Main_Outputs.X := Main_Inputs.A;
      Main_Outputs.Y := Main_Inputs.B;
      Main_Outputs.Z := Main_Inputs.C;

      Process_IO;

      Map_Outputs;

      Map_HMI_Outputs;

   exception

      when Error : others =>
         A4A.Log.Logger.Put (Who  => My_Ident,
                             What => Exception_Information (Error));

         Program_Fault_Flag := True;

   end Main_Cyclic;

...

… comme depuis la fonction périodique :

...

   procedure Periodic1_Cyclic is
      My_Ident : constant String := "A4A.Application.Periodic1_Cyclic";
   begin

      --  Playing with tasks interface
      Periodic1_Outputs.A := not Periodic1_Inputs.X;
      Periodic1_Outputs.B := Periodic1_Inputs.Y + 2;
      Periodic1_Outputs.C := Periodic1_Inputs.Z + 1;

   exception

      when Error : others =>
         A4A.Log.Logger.Put (Who  => My_Ident,
                             What => Exception_Information (Error));

         Program_Fault_Flag := True;

   end Periodic1_Cyclic;

...

Il y a également un programme de test qui permet de jouer avec cette DPM que vous trouverez dans le répertoire :

A4A/test/src/test_generic_dpm.adb

Comme ça fonctionne plutôt bien, je l’ai mis à l’épreuve un peu partout et j’ai donc pas mal bougrassé le code A4A en réorganisant les différents noyaux (kernels) afin d’en faciliter la réutilisation et l’évolution et en en rajoutant d’autres, ce dont je vous entretiendrai avec plaisir l’année prochaine.

Je vous remercie pour votre attention et vous souhaite de bonnes fêtes de fin d’année.

Cordialement,
Stéphane