[WD19] Safescan TA-855, Connexion et Lecture

Safescan est une société hollandaise spécialisée dans les appareils de détection de faux billets mais également dans les pointeuses (avec ou sans lecteur d’empreinte digitale). Un logiciel sous licence est fourni avec chaque pointeuse mais ne propose pas de gérer une base de données sur un serveur en temps réel.

En effet le logiciel Time Attandance génère une base de données Firebird SQL, au format fichier. Celle-ci est sauvegardée lorsqu’on ferme le programme, et n’est jamais alimentée puisqu’il n’y a pas de service qui s’en occupe. L’idée ici est donc d’aller lire directement les données sur la pointeuse, en téléchargeant le journal des événements.

La société ne fournit aucun SDK pour effectuer cette opération, mais après de nombreuses recherches sur internet, il s’avère que la technologie utilisée est la même que celle utilisée par ZKTeco. La machine ici présente utilise par défaut le port UDP 4370 pour communiquer, et dispose d’un serveur web sur lequel on peut effectuer quelques opérations de base. Les machines fournies par ZKTeco fonctionnent de la même manière. C’est l’article sur Infobytesec – « Perverting Embedded Devices » – qui a permis de faire le lien entre les deux marques (rem : n’hésitez pas à le lire car il est plutôt intéressant).

Une API pour Windows

Il existe donc bien une API pour Windows qui permet d’interagir avec les appareils disposant d’un écran en noir et blanc mais également ceux équipés d’un écran TFT couleur ou d’une caméra (iFace). Les bibliothèques sont disponibles en 32 et 64 bits. Rendez-vous sur la page de téléchargements puis choisissez SDK dans la liste déroulante « Type ». Pour nos développements nous avions pris la version 6.2.5.7 en 32 bits mais la version répertoriée au 17/4/2020 est la 6.3.1.

Pour installer et enregistrer le kit de développement, décompressez l’archive puis allez dans le dossier du même nom lancez le script « Auto Install » (qui correspond bien entendu à la version de votre système d’exploitation). Il copie toutes les DLL dans le bon dossier de Windows puis les enregistre à l’aide de la commande « regsvr32« .

Utilisation dans Windev

Lorsqu’on dispose de bibliothèques de ce type, elles peuvent être distribuées sous forme d’assemblage .NET, mais peuvent aussi reprendre des fonctions qui auraient été écrites en C ou C++. Dans ce cas il faut soit appeler les fonctions avec AppelDLL32, ou utiliser un objet de type Automation. La deuxième solution est celle qui fonctionne : merci à Ada Atchinard qui a posté l’info sur le forum des développeurs de PC SOFT.

Connexion via IP et affichage du numéro de série

La première opération consiste à se connecter à la machine et à récupérer l’identifiant ainsi que le numéro de série. La bibliothèque « zkemkeeper.dll » permet d’utiliser un objet de type ActiveX / Automation dans Windev. L’utilitaire OLE/COM Object Viewer (disponible dans le SDK Windows) permet d’avoir l’information sur tous les paramètres passés à chaque fonction. La documentation au format PDF vous permet également d’obtenir tout ce qu’il vous faut.

  • Il faut créer un objet Automation de type « zkemkeeper.ZKEM« .
  • Pour la connexion on appelle « Connect_Net()« .
  • En guise de 1er paramètre, l’adresse IP (chaine).
  • Le second paramètre est le port (chaine/entier).
  • La réponse est un booléen.
  • Récupérer le n° d’appareil (propriété MachineNumber).
  • Récupérer le n° de série (fonction GetSerialNumber).

Voici un exemple de code qui permet de réaliser une connexion à la pointeuse puis de sauvegarder dans des variables locales le n° d’appareil ainsi que le S/N. Attention, la connexion peut prendre un certain temps.

Si la connexion aboutit, le booléen « bStatus » va avoir pour valeur « True« . C’est uniquement dans ce cas qu’on récupère les informations désirées. Si la connexion échoue on remet simplement les variables à zéro (leur définition n’a pas été reprise dans l’image ci-dessus pour ne laisser que le code considéré comme important).

Lecture du journal global

Une fois que l’on est connecté on peut lire les informations du journal afin de récupérer les entrées/sortie et les pauses, pour chaque jour et chaque utilisateur. Le procédé est le suivant : on doit d’abord « lire » l’entièreté du contenu de ce journal. Le tout est alors stocké en mémoire. On doit alors appeler une fonction qui va lire chaque enregistrement.

  • On doit appeler la fonction ReadGeneralLogData().
  • L’unique paramètre est le numéro de machine (récupéré plus haut).
  • On appelle une première fois GetGeneralLogData().
  • Le 1er paramètre est le numéro de machine (format entier 4 octets).
  • Les paramètres suivants sont des entiers système permettant de stocker chaque information. Il y en a 10. Dans l’ordre : n° de l’appareil qui a enregistré le pointage, l’ID utilisateur, de nouveau le n° de la pointeuse, le mode de vérification (ex : par carte), le type (entrée, sortie, pause), l’année, le mois, le jour, l’heure et les minutes. Voir « Conversions de type C« 
  • La fonction renvoie un booléen sinon une valeur négative en cas d’erreur. Nous ne traiterons pas les erreurs dans l’exemple ci-dessous.
  • Elle peut être appelée tant que la valeur est vraie ; cela lira à chaque fois l’enregistrement suivant en mémoire. Une fois qu’il n’y en a plus, la fonction devrait retourner 0.

L’exemple ci-dessous va donc récupérer dans un champ Table les informations du journal de la pointeuse. Nous sommes donc en direct sur la pointeuse, les informations sont lues directement dans sa mémoire interne.

Nous traitons chaque enregistrement en prenant bien soin de vérifier la valeur de chaque entier retourné, surtout en ce qui concerne le type (pour distinguer l’entrée de la sortie) ou bien le mode de vérification (par empreinte digitale, mot de passe ou carte RFID). Voici ce qu’on obtient alors comme résultat dans une fenêtre.

Nous avons également prévu un bouton « Déconnexion » dans la fenêtre. Cela permet juste d’appeler la fonction Disconnect() de notre objet Automation. Elle ne reçoit aucun paramètre en entrée et ne retourne aucune valeur.

Et après?

Nous pouvons donc constater qu’il y a bel et bien un moyen d’interagir avec la pointeuse en direct tout en se passant du logiciel fourni par Safescan. Par exemple, nous avons vu qu’il existe une fonction pour nettoyer le journal des événements global. Il existe également tout un tas de fonctions pour récupérer les informations comme l’adresse MAC ou l’état de l’appareil, ou pour gérer différents événements. Au fil de nos essais nous verrons donc ce qu’il est possible de réaliser.

37 commentaires sur « [WD19] Safescan TA-855, Connexion et Lecture »

  1. Bonnjour
    Merci beacoup d'avoir fait ce tuto.
    Mon probleme:
    -j'arrive à me connecter mais juste apres la connexion l'application s'arrete.
    – J'ai tout fait mais en vain !

  2. J'ai un ZKTeco UA300 et je travail avec windev 18.
    SDK utilisé: SDK(64bit Ver6.2.4.11)
    la connexion s'effectue bien, mais après ça je reçois le message suivant: Windows a rencontré un
    problème et l'appli s'arrête.
    l'exception ne renvoie rien.
    code utilisé:

    zKemKeeper est un objet automation "zkemkeeper.ZKEM"
    INumeroMachine = zKemKeeper>>Connect_Net(IP,xPort)
    QUAND EXCEPTION DANS
    SI INumeroMachine = Vrai ALORS
    Info("Connexion établie")
    SINON
    Info("Connexion échouée")
    FIN
    FAIRE
    ExceptionInfo(errMessage)
    FIN

  3. 1) Dans mon cas, j'ai passé l'adresse IP et le port en chaine de caractères à la fonction Connect_Net().

    2) "La connexion s'effectue bien" => le message d'information s'affiche ?

    3) Avez-vous essayé d'utiliser le SDK 32 bits dans un projet "Application 32 bits" pour voir si le phénomène est le même ?

  4. Merci d'avoir répondu.
    1) la déclaration pas de problème:
    IP est une chaîne UNICODE = "192.xxx.xxx.xxx"
    . xPort est un numérique = 4370
    2)Oui j'ai bien le message de connexion établie.
    . j'arrive même à récupérer les données du équipement (j'ai enlevé cette partie car le problême ne vient pas de la).
    Le code s'exécute pas à pas jusqu'à la fin et c'est là le programme s'arrête.
    3) Oui j'ai essayé le SDK 32 avec un projet 32 mais rien.
    SOS SOS SOS SOS SOS !!
    Merci

  5. Alors là je n'ai aucune idée ! Il faudrait voir le message d'erreur dans l'observateur d'événements mais pour l'analyser dur dur… Peut-être envoyer ces informations à PC SOFT pour voir ce qu'ils en pensent ? J'avoue que sur le coup je n'ai pas d'idée… :/

  6. Merci bcp
    j'ai tout fait avec un projet 32 bit et SDK 32 ça ne marche pas.
    j'ai crée un projet 64 bit, ça marche, mais moi je veux un 32.
    Pas trop grave je vais continuer à développer en attendant de trouver une solution au 32 bit.
    Merci beaucoup pour ton aide !!

  7. Etrange, car je travaille uniquement avec le SDK 32 bits et un projet 32 bits également. Un problème avec Windev 18 peut-être… Qui sait 🙂

  8. Salut !!
    Tout Marche maintenant !!
    Comment je doit faire pour recuperer en temps réel lorsqu'un pointage est validé ..!
    C'est ma première fois avec les biométrie, et je intermédiaire en Windev !!
    Merci d'avance !!

  9. Salut !!
    * Sur un système 32: Appli 32 bits + SDK 32 bits !

    * Sur un système 64: Appli 64 bits + SDK 64 bits !
    Je développe sous un système 64, à la fin du dev je génère les deux versions et le tour est joué !!
    Normalement sous le système 64 bits les deux config devaient marcher, mais bon !!

  10. J'ai un autre probleme avec le modele U260 connexion impossible, pourtant ce sont les memes fonctions de connexion.
    Le probleme est doit etre du au SDK, j'ai testé avec plusieurs versions de SDK mais en vain !!!

  11. Bonjours Vincent
    Voici Le Code Écrit mais ce ne marche pas j'ai installer SDK 32 toujours pas de connexion

    PROCEDURE Test03()
    iMachineNum est un entier
    sSerialNumber est une chaîne
    sCh est une chaîne sur 100
    bStatus est un booléen
    zKemKeeper est un objet Automation "zkemkeeper.ZKEM"
    bStatus =zKemKeeper>>Connect_Net(SansEspace(iAdresseIp),SansEspace(iPort))
    Info(iAdresseIp,iPort,bStatus)

    SI bStatus=Vrai ALORS
    iMachineNum=zKemKeeper>>MachineNumber
    zKemKeeper>>GetSerialNumber(iMachineNum,sch)
    sSerialNumber=SansEspace(sch)
    Info(sSerialNumber)
    SINON
    sSerialNumber=""
    iMachineNum=0
    Info("Pointeuse Non Connectée")
    FIN

  12. Bonjours
    Je vient juste d'établir la connexion avec la pointeuse, j'ai utiliser la fonction GetGeneralLogData(iMachineNum,col1,col2,col3,col4,col5,col6,col7,col8,col9,col10) et la réponse c'est des chiffres qui ne correspondent pas a ce que j'attend je n'arrive pas a maitriser les procédures déscrites
    j'attend un cou de pousse
    Merci d'avance

  13. – GetGeneralLogData renvoie un entier qui détermine si la lecture a abouti (voir l'image ; on fait une boucle tant que cette valeur = 1).
    – Le premier paramètre de la fonction est de type entier sur 4 octets (exemple : param1 est entier). Dans la documentation ce paramètre est en entrée uniquement (voir conversion type C).
    – Les autres paramètres sont en entrée/sortie (in/out) et donc ici doivent être déclarés comme entiers système (exemple : param2 est un entier système). De nouveau il faut consulter la documentation PC Soft pour la conversion d'entiers.
    – Pour certains paramètres retournés la documentation indique la valeur, on peut donc tester celles-ci avec une instruction de type SELON valeur … pour afficher un texte adéquat.
    – Les variables année mois jour peuvent être concaténées pour former une date au format AAAAMMJJ. Idem pour les heures (ne pas oublier de les compléter par des 0 dans ce cas).
    Vincent

  14. Normalement le code est équivalent pour les autres types de pointeuses. Il ne faut pas hésiter à consulter la documentation de l'API qui détaille les différentes fonctions Connect. Connect_Net() permet de se connecter à l'aide de l'adresse IP et du port.

  15. Bonjour Vincent, je n'arrive pas à télécharger le fichier SDK sur le site que tu as donné. Apparemment, il a été supprimé…
    Si c'est possible de l'avoir avec toi, ce serait génial. Merci bien.
    Mon Mail: saintleo06[at]gmail.com

  16. Effectivement le site d'origine ne fonctionne plus correctement.
    Aller sur : zkteco.eu/index.php/downloads/software-downloads
    Catégorie SDK -> Standalone SDK

  17. Merci bien Vincent, c'est fait.
    Mais dis moi, je tente de sauvegarder les empreintes des utilisateurs sur le lecteur dans mon application windev mais ça ne marche pas, alors as tu une idée?
    Merci bien d'avance.

  18. Bonjour,

    Avez vous une idée du payload network, quelqu'un l'a-t-il essayé de le capturer avec wireshark ?

    Merci D'avance.

    Bien à vous

  19. Bonjour,
    la méthode ReadGeneralLogData me renvoie le code d'erreur -101 (malloc memory failed)
    qu'est ce que je peux faire SVP.
    merci d'avance

  20. My machine I can ping on it with its IP address but I can not connect to it. I even did one code to search on the port without any result :
    The code is :

    I is int
    Status is boolean = False
    zKemKeeper is object Automation "zkemkeeper.ZKEM"

    LOOP
    Status = zKemKeeper>>Connect_Net(NoSpace("192.168.1.250"),NoSpace(I))
    I = I + 1
    TO DO WHILE Status = False
    info("The port number is : " + I)

    I have arrived till I = 465000 without any success.

    so how can I connect to this materiel?

  21. Bonjour,

    merci beaucoup pour cet article qui me permet de me connecter à un périphérique safescan, malgré la difficulté à récupérer le sdk. Etant novice dans l’utilisation des activeX et des objets automation, j’ai du mal à typer les paramètres retournés par la fonction ‘GetGeneralLogData’
    (malgré la doc du sdk et votre lien vers les ‘conversions de type C’) et je n’arrive du coup pas à lire correctement les valeurs retournées.
    Voici ce que j’ai trouvé :
    BOOL GetGeneralLogData(
    long dwMachineNumber ,
    long FAR* dwTMachineNumber ,
    long FAR* dwEnrollNumber ,
    long FAR* dwEMachineNumber ,
    long FAR* dwVerifyMode ,
    long FAR* dwInOutMode ,
    long FAR* dwYear,
    long FAR* dwMonth,
    long FAR* dwDay,
    long FAR* dwHour,
    long FAR* dwMinute);

    Une piste pour la conversion svp?

    D’avance merci !

    1. En revoyant le code dans les captures d’écran, je vois que j’avais préfixé les variables par « i » et j’ai précisé que le numéro de machine était un entier sur 4 octets (ce qui est la valeur par défaut quand on déclare en tant qu’entier sans spécifier le nombre d’octets). J’imagine que c’était donc pareil pour tous les autres.

      1. Bonjour,

        en fait la méthode GetGeneralLogData() ne renvoie pas (plus) les bons éléments, ce n’est pas un problème de typage de paramètres. En utilisant la méthode SSR_GetGeneralLogData()
        ça va bien…
        Merci pour la réponse et encore merci pour cet article.
        Bonne continuation.

  22. Bonjour ,
    Merci pour ce tuto cependant j’arrive à récupérer des données qui ne sont pas pas exact
    ID de l’utilisateur , la date l’heure et la minute ne concordent pas.

    merci de me revenir svp

  23. veuillez me corriger ce code ne marche pas
    // Déclarations des variables
    MachineNum est un entier
    MachNum est un entier
    EnrollNum est un entier
    ENumber est un entier
    VRFMode est un entier
    InOut est un entier
    Year est un entier
    Month est un entier
    Day est un entier
    Hour est un entier
    Min est un entier
    //iLectureOK est un booléen
    occur est un entier

    // Supprime toutes les données existantes dans la table avant de récupérer les nouvelles données
    TableSupprimeTout(fen_PRINCIPALE.Table_users)

    // Lecture des données de la pointeuse
    zKemKeeper>>ReadGeneralLogData(MachineNum)

    // Variable pour stocker le résultat de la lecture
    iLectureOK = zKemKeeper>>SSR_GetGeneralLogData(MachineNum, MachNum, EnrollNum, ENumber, VRFMode, InOut, Year, Month, Day, Hour, Min)

    // Tant que la lecture est réussie (iLectureOK = 1)
    TANTQUE iLectureOK = 1
    // Ajoute une ligne à la table des utilisateurs
    TableAjoute(fen_PRINCIPALE.Table_users)

    // Obtient l’occurrence actuelle dans la table
    occur est un entier = TableOccurrence(fen_PRINCIPALE.Table_users)

    // Remplit les colonnes de la table avec les données lues
    fen_PRINCIPALE.Table_users[occur].col_IDusers = EnrollNum
    fen_PRINCIPALE.Table_users[occur].col_Annee = Year
    fen_PRINCIPALE.Table_users[occur].col_Mois = Month
    fen_PRINCIPALE.Table_users[occur].col_jour = Day
    fen_PRINCIPALE.Table_users[occur].col_Heure = Hour
    fen_PRINCIPALE.Table_users[occur].col_Minute = Min

    // Utilise un SELON pour déterminer le VRFMode
    SELON VRFMode
    CAS 0 : fen_PRINCIPALE.Table_users[occur].col_VFM = « PWR »
    CAS 1 : fen_PRINCIPALE.Table_users[occur].col_VFM = « FP »
    CAS 2 : fen_PRINCIPALE.Table_users[occur].col_VFM = « CARD »
    AUTRE CAS : fen_PRINCIPALE.Table_users[occur].col_VFM = « Autre » // Remplacez « Autre » par la valeur par défaut si nécessaire
    FIN

    // Utilise un SELON pour déterminer le InOut
    SELON InOut
    CAS 0 : fen_PRINCIPALE.Table_users[occur].col_CICO = « Entrée »
    CAS 1 : fen_PRINCIPALE.Table_users[occur].col_CICO = « Sortie »
    CAS 2 : fen_PRINCIPALE.Table_users[occur].col_CICO = « Pause (Entrée) »
    CAS 3 : fen_PRINCIPALE.Table_users[occur].col_CICO = « Pause (Sortie) »
    AUTRE CAS : fen_PRINCIPALE.Table_users[occur].col_CICO = « Autre » // Remplacez « Autre » par la valeur par défaut si nécessaire
    FIN

    // Effectue la prochaine lecture
    iLectureOK = zKemKeeper>>SSR_GetGeneralLogData(MachineNum, MachNum, EnrollNum, ENumber, VRFMode, InOut, Year, Month, Day, Hour, Min)
    FIN

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *