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.
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 !
Pas d'erreur ou d'exception ?
Est-ce le même modèle de pointeuse et avec Windev 19 ?
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
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 ?
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
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… :/
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 !!
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 🙂
Oui peut etre !
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 !!
Salue, merci j'ai eu la solution !!
J'ai fait un article à ce propos.
Qu'avez-vous fait pour que ça ne plante plus ? 🙂
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 !!
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 !!!
Il faudrait tester avec une ancienne version du SDK mais j'ai comme un doute que l'on en trouve sur le net…
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
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
– 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
Bonjour je souhaiterais avoir le code windev pour ce connecter à la poiteuse zkt IN01
merci pour votre collaboration
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.
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
Effectivement le site d'origine ne fonctionne plus correctement.
Aller sur : zkteco.eu/index.php/downloads/software-downloads
Catégorie SDK -> Standalone SDK
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.
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
Je ne sais pas vous dire désolé 😉
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
Ne développant plus en WinDev je ne peux malheureusement tester… 🙁
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?
Did you check if the port is not blocked by a firewall ?
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 !
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.
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.
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
Une différence de timezone peut-être?
Un grand merci pour ce tuto, qui m’a permis de connecter une badgeuse à mon logiciel de paie !(développé pour l’Afrique de l’ouest)
Avec plaisir (même si ce n’est pas un tuto très à jour)
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