-
• #2
First the hardware: Your connectivity is just fine with any Espruino board: Espruino talks and listens 3.3V and so does the Finger Print reader. Just make sure you connect the Finger Print Reader Ground to Espruino Ground and then the Finger Print reader power to 3.3 Volt or 5 Volt. I assume you run your Espruino for the beginning on USB as connected to the Espruino Web IDE on your computer.
Now that we have done the hardware, we look at the initialization of the serial on Espruino. There is not much to say other than selecting the pins, the baud rate - which is 9600 (default of the finger print reader), and the format. I assume the format is just plain 8 bit with 1 stop and 1 start bit and no parity bit.
To write to the device it is simple:
mySerial.print(...);
To read from the device, it is a bit more tricky - or better said - elaborate, and for sure different as you may have learned from other environments, such as Arduino. Let me fill you in with a bit background about Espruino.
A major goal of Espruino's creator - @Gordon Williams - was: tiny, power frugal, easy to use / program micro controller setup.
The easy part for programming is for one part JavaScript and for the other - painful - part, handle all the low level hardware stuff under the hood so that you have to act only when something happened that needs your attention - *** without you constantly checking, also called polling - in a program loop (as in Arduino) - every possible thing for which something may have happen ***. In Espruino, everything is Event Driven including your application. So you will never find a command like wait() or delay() or alike. In your case, when the finger print reader sends you - your application code - data, Espruino does it's magic and notifies you - your application code - with the data it received. In order that Espruino can notify - call - you, you have to 'leave your callback number' when you 'install' the communication. And this goes with a piece of code like this:
mySerial.on("data", function(data) { console.log(data); });
This tells the serial appliance built into Espruino, that on data reception it should call your function with the received data as parameter value. The (anonymous) function in above example prints the data in the console.
That is all there is!
...no kidding... or just a little bit.
Being very familiar with JavaScript, you will say "Sure, I can take it from here". Not so familiar, you may ask about some more details... Take a look at Espruino USART.
But before going into more programming, lets play a bit and see some action. Reading through AD-TECH GT-511C3 datasheet V2.1, we can quickly see wether the connectivity is up.
(1) Connect your Espruino and Finger Print reader. Pick Espruino's Serial1 for now. Which pins these are, check in the espruino.com/Reference and board. For Pico: B6(TX) and B7(RX), Ground goes to Ground and Power goes to 3.3V or Bat (Bat is the USB 5V through a diode and is located between Ground and 3.3V pin on the 0.1" pins next to the USB tab).
(2) Connect Espruino with USB cable to your computer you run the Web IDE in Chrome (which you already have started).
(3) Connect to Espruino (top left yellow/orange button (Espruino board shows in drop down... pick any when more than one shows).
(4) In left-hand pane - the console - you enter
Serial1.setup(9600, { tx:B6, rx:B7});
This initializes Espruino's serial appliance on port B6 and B7 with speed of 9600 baud.(5) Then you enter
Serial1.on("data", function(data) { console.log(data); });
(6) Now we send the command to turn the Finger Print reader LED on and we should get back an ACK, which shows as a '0' in Hex: 30 (0x30) in the console. For entering use copy paste from here:
(6.1) Open:
- (6.2) LED on:
Serial1.write([0x55,0xAA,0x01,0x00,0x01,0x00,0x00,0x00,0x12,0x00,0x13,0x01]);```
(6.3) LED off
- (6.4) Close:
Serial1.write([0x55,0xAA,0x01,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x02,0x01]);```
For every command sent you get something back: some crazy characters but also a "U" - (0x55) and "0" (0x30).
So much for now.
-
• #3
Thanks, really appreciated.
Couple of additional questions,the commands resulted on this response, which I believe is a proper acknowledgement.
U
ª
0
0
How do I get other commands using the format Serial1.write([0x55,0xAA,0x01,0x00,0x01,0x00,0x00,0x00,0x12,0x00,0x13,0x01]);
That is which of the hex numbers represent the command start code1, start code 2, device id, input parameter, command code and checksum.Finally, how do I display/get data packet in a readable format, like the serial_number and firmware_number in devinfo data packet which is returned by the open command
-
• #4
Great! It worked. It is quite an adventure to aim without having things at hand.
Something I missed is that you have an Espruino-Wifi (by the way: an excellent choice). But you managed to translate (to the same pins and 5V to VUSB). From now on I know.
So let's get started with building up some infrastructure / module that we then want to use this way
var FPR = require("GT522C3"); var fpr1 = new FPR(); // or: ...(1,"Enrollment Office"); var cb = function(err, data) { console.log(if (err) ? err : data); } // ...for now :( // for now, cb is even ignored... :( :( :( and the internal (tbd) is used function onInit() { Serial1.setup(9600, { tx:B6, rx:B7 }); fpr1.connect(Serial1,cb,1); // open with extra info (version,...,serial number) }
And this is some 'get going code' you paste into the right hand pane - the editor - and upload to the board. The last line is there only for development time (EDIT: code fixed based on post #8, thanks @user81574).
// begin module inline var C = // for now just command stuff... but it could include more and become sub structured. { OPEN: 0x01 , CLOSE: 0x02 , LED: 0x12 , EXTRA: 0x01 , LED_ON: 0x01 , LED_OFF: 0x00 }; // Constructor for a GT522C3 Device // id: optional device ID, an Integer 16 bit, default 1 0x0001 // name: optional device name / location var D = function(id,loc) { this.id = (typeof id === "undefined") ? 1 : id; // holds on to the device id this.name = name; // holds on to the optional name / location this.ser = undefined; // holds on to the serial the device is connected to this.open = false; // holds on to the state the device is in (open/closed) }, p = D.prototype; // .connect(serial, cb) - connect w/ optional initialize (open, etc...) // cb: a callback function(err, data) // err: undefined when ok, otherwise err object (tbd - to be defined) // data: response object when ok (tbd) // xtra: optional booly truy for extra info // (booly: something that evaluates to boolean true or false, therefore also called // booly: absent, false, undefined, null, "", and 0 are all falsy, otherwise truy) // Note: if cb absent, only serial is stored and initialization has to be called separately p.connect = function(serial, cb, xtra) { this.serial.on("data", function(data){console.log(data);}); if (cb) { this.open(cb, xtra); } }; // .open(cb, xtra) - open w/ optional extra information in response object // cb: a callback function(err, data) // err: undefined when ok, otherwise err object: { _: this, ... } // (for this fpr instance, and more tbd - to be defined) // data: response object when ok, w/ xtra: // falsy: { _: this } // truy: { _: this, ver: aNumber, mxSize: aNumber, sn: aString } // for this fpr instance, version number, isoAreaMaxSize, serial number // xtra: optional truy for extra info p.open = function (cb, xtra) { this.cb = cb; // (incorrectly) for now... this.wri(this.cmd0(C.OPEN, xtra), cb); } // .cmd0(cmd, xtra) - build command of structure 0 - only extra parm p.cmd0 = function(cmd, xtra) { var c = [0x55,0xAA,this.id&0xFF,this.id>>8,(xtra)?0x01:0x00,0x00,0x00,0x00]; return this.chk(c,1); }; // .chk(data, add) - add (append) or validate checksum byte // data: a command without checksum byte or a response with checksum byte // add: truy: data is a command and is returned with calculated checksum byte added // falsy: data is response and true is returned when checksum byte is ok, else false p.chk = function(data, add) { var s = 0, c = data.length, i = c - ((add) ? 0 : 2); while (i-- > 0) s += data[i]; s&=0xFF; if (add) { data.push(s&xFF); data.push((s>>8)&xFF); return data; } else return ((data[c] == (s&0xFF)) && (data[c+1] == ((s>>8)&0xFF))); }; // .wri(data) - write data to device - (incorrectly) for now... p.wri = function(data) { this.cb(undefined, data); }; // exports = D; // will replace next line when stored as module Modules.addCached("GT522C3", function(){ modules = D; }); // end module (inline) var FPR = require("GT522C3"); var fpr1 = new FPR(); // or: ...(1,"Enrollment Office"); var cb = function(err, data) { console.log((err) ? err : data); }; // ...for now :( function onInit() { Serial1.setup(9600, { tx:B6, rx:B7 }); fpr1.connect(Serial1,cb,1); // open with extra info (version,...,serial number) } setTimeout(onInit,1000);
This is nowhere near basic... but it is basics... (showing how to implement commands).
When uploaded, it should print stuff - including the firmwareVersion, isoAreaMaxSize and device serialNumber in the console.
I'm sure you can take it from here and add the .led(on, cb) (w/ on truy/falsy for on and off) and play around.
To handle the response, it gets a little bit more tricky, since a response block has to be detected, analyzed, and the application callback be called. In other words the application passed in callback has to be feed then in a promise (fullfilled) based on response block becoming available event. In addition to the 'callback hell' that can be managed with promises - nicely available in Espruino - you have also to deal with timeout, since a response block may not show (corrtectly / completely) within (variably) expected time... The timeout would then have to kill the promise(s) in work.
You may for sure take a look at the node.js implementation for guidance - or even more. Just be aware that you deal with less resources than you have in a regular node.js environment.
PS: since I do not have a device - and not time to write an emulator for it (I anyway ran out of time already) - this code is not run-tested... so please provide me with feedback (...tested code).
-
• #5
Thanks! this is already a perfect start (considering I was running round the web and library codes/documentation and datasheets with close to nothing).
I'll start extending and run-testing ASAP.
-
• #6
I started a while ago a module for this but never got to the finishing touches.
I am sure it could be improved quite a lot but as a starter it should work.
As far as i remember i got stuck with memory issues while trying to handle the
fingerprint images.. i wanted them to display on an eink. I guess with the improvements in espruino 1v93 and 1v94 its possible now. Sadly i damaged my fingerprint sensor in an tee spill excitend so i cant finish the module.I attached the code as is.. let me know if you need help to make sense of the mess :)
1 Attachment
-
• #7
...oh these late nights, early early mornings lend many hands for a nice tee and coffee and nasty spills there of...
But foremost: @PaddeK, thanks for sharing!
Looked through the code and - even though not the way I'm used to - I like it and learned a lot from it... including a bug: I treated the checksum as 8 bit instead of 16... ouch. Using a buffer and put 8 and 16 bit views over it is a cool thing... One could even use a clipping one that makes the &0xFF obsolete...
@PaddeK, do you have some usage information?
From your comment and code I assume you got pretty far with implementing. Yes, images is not the coolest to do... If you would have some SRAM / FRAM / MRAM (able up to 40 MHz clock rate - enough to keep up with measly serial 9600, even with byte-wise addressing versus self-incrementing address) and stream response into those, on serial data receipt and process it form there... Regular information as blocks, and images streamed. Serial (flash) EPROM would work as well used as a Ring buffer, fast enough, cheap, and barely wearing down with the amount of data when going for a large one... The EPROM could then also be used as the 'local Database' in addition to communicating it over Wifi to a networked server (that's what I guess @user81574 has eventually in mind... ( Btw, do you know the size of a so-called profile? Because that is what I guess has to work as minimum to be really useful).
@user81574, I would give it a try with @PaddeK's code when given some usage samples.
-
• #8
So, run-tested the unedited code on the Espruino-Wifi,
here are errors encountered and how I fixed them.
Line 52 - .p.cmd0() to p.cmd0()Uncaught ReferenceError: "id" is not defined
at line 1 col 16
this.id = (id) ? id : 1; // holds on to the device idit turns out that the tenary expression ?: didn't work so I passed the Id directly
main issue:
Uncaught Error: Constructor should be a function, but is Object at line 1 col 16 var fpr1 = new FPR(); ^ =undefined Uncaught Error: Field or method "connect" does not already exist, and can't create it on undefined at line 70 col 7 fpr1.connect(Serial1,cb,1); ^ in function called from system
also, Line 21 and Line 34 has this.open as property and this.open as method respectively.
Anyway, just to get it running, this was my minor fix (as long it works am happy at the moment (: );// begin module inline var C = // for now just command stuff... but it could include more and become sub structured. { OPEN: 0x01 , CLOSE: 0x02 , LED: 0x12 , EXTRA: 0x01 , LED_ON: 0x01 , LED_OFF: 0x00 }; // Constructor for a GT522C3 Device // id: optional device ID, an Integer 16 bit, default 1 0x0001 // name: optional device name / location var D = function(id,loc) { this.id = 1; // holds on to the device id this.name = "Enrolment office"; // holds on to the optional name / location this.ser = undefined; // holds on to the serial the device is connected to this.opened = false; // holds on to the state the device is in (open/closed) }, p = D.prototype; // .connect(serial, cb) - connect w/ optional initialize (open, etc...) // cb: a callback function(err, data) // err: undefined when ok, otherwise err object (tbd - to be defined) // data: response object when ok (tbd) // xtra: optional booly truy for extra info // (booly: something that evaluates to boolean true or false, therefore also called // booly: absent, false, undefined, null, "", and 0 are all falsy, otherwise truy) // Note: if cb absent, only serial is stored and initialization has to be called separately p.connect = function(serial, cb, xtra) { serial.on("data", function(data){console.log(data);}); if (cb) { this.open(cb, xtra); } }; // .open(cb, xtra) - open w/ optional extra information in response object // cb: a callback function(err, data) // err: undefined when ok, otherwise err object: { _: this, ... } // (for this fpr instance, and more tbd - to be defined) // data: response object when ok, w/ xtra: // falsy: { _: this } // truy: { _: this, ver: aNumber, mxSize: aNumber, sn: aString } // for this fpr instance, version number, isoAreaMaxSize, serial number // xtra: optional truy for extra info p.open = function (cb, xtra) { this.cb = cb; // (incorrectly) for now... this.wri(this.cmd0(C.OPEN, xtra), cb); } // .cmd0(cmd, xtra) - build command of structure 0 - only extra parm p.cmd0 = function(cmd, xtra) { var c = [0x55,0xAA,this.id&0xFF,this.id>>8,(xtra)?0x01:0x00,0x00,0x00,0x00]; return this.chk(c,1); }; // .chk(data, add) - add (append) or validate checksum byte // data: a command without checksum byte or a response with checksum byte // add: truy: data is a command and is returned with calculated checksum byte added // falsy: data is response and true is returned when checksum byte is ok, else false p.chk = function(data, add) { var s = 0, c = data.length, i = c - ((add) ? 0 : 1); while (i-- > 0) s += data[i]; s&=0xFF; if (add) { data.push(s); return data; } else return (data[c] == s); }; // .wri(data) - write data to device - (incorrectly) for now... p.wri = function(data) { this.cb(undefined, data); }; //Modules.addCached("GT522C3",D); // later when as module: exports = D; // end module (inline) //var FPR = require("GT522C3"); var fpr1 = new D(); // or: ...(1,"Enrollment Office"); var cb = function(err, data) { console.log((err) ? err : data); }; // ...for now :( function onInit() { Serial1.setup(9600, { tx:B6, rx:B7 }); fpr1.connect(Serial1,cb,1); // open with extra info (version,...,serial number) } setTimeout(onInit,1000);
with printed on console 85, 170, 1, 0, 1, 0, 0, 0, 1
-
• #9
Awesome one @PaddeK! Still going through your code.
@allObjects would be nice to get those usage samples
Thanks for sharing @PaddeK -
• #10
I cant really test it.. but i looked a bit at the index.js and saw i was testing a function for complete enrollment. A few minor changes i think and you should get it to work.
Try this as a replacement for the index.js
'use strict'; let GT511CXX = require('./gt511cxx.js'); GT511CXX.prototype.enroll = function(id) { let me = this, open = () => me.open(), start = () => me.enrollStart(id), capture = () => me.captureFinger(true), waitFinger = () => me.waitFinger(10000, false), waitReleaseFinger = () => me.waitFinger(10000, true), ledOn = () => me.switchLED(true), ledOff = () => me.switchLED(false), enroll1 = () => me.enroll1(), enroll2 = () => me.enroll2(), enroll3 = () => me.enroll3(), enrollDelay = () => new Promise(resolve => setTimeout(resolve, 500)), blinkDelay = () => new Promise(resolve => setTimeout(resolve, 100)); return new Promise((resolve, reject) => { let errorHandler = (err) => { ledOff(); reject(err); }, successHandler = () => { console.log('enroll success'); resolve(); }; open() .then(ledOn) .then(() => console.log('press finger')) .then(waitFinger) .then(start) .then(capture) .then(enroll1) .then(() => console.log('enroll 1 done')) .then(ledOff) .then(blinkDelay) .then(ledOn) .then(() => console.log('release finger')) .then(waitReleaseFinger) .then(enrollDelay) .then(() => console.log('press finger')) .then(waitFinger) .then(capture) .then(enroll2) .then(() => console.log('enroll 2 done')) .then(ledOff) .then(blinkDelay) .then(ledOn) .then(() => console.log('release finger')) .then(waitReleaseFinger) .then(enrollDelay) .then(() => console.log('press finger')) .then(waitFinger) .then(capture) .then(enroll3) .then(() => console.log('enroll 3 done')) .then(ledOff) .then(successHandler) .catch(errorHandler); }); }; let fps = new GT511CXX(GT511CXX.Devices.GT511C1R, Serial1); fps.enroll(0); // ID 0 means first storage slot .. slot has to be empty
The enrollment function itself shows a lot of the usage i had in mind.
It is more a series of module calls then a module function itself.. but the
complete enrollment process was so tedious that i wanted to provide a solution with
the module i guess.Btw. pretty much all of the code is based on the documents i found here:
http://www.adh-tech.com.tw/files/GT-511C3_datasheet_V1%201_20131127.pdf
http://www.adh-tech.com.tw/files/GT-511C1R_datasheet_V2-2016-10-25.pdf--- Edit ---
About the profile size.. its 506 or 498 bytes depending on the sensor model.
As far as i can see i already can handle the profiles.. but the fingerprint images the sensor
can capture are in the order of 50kb.. and with that i had no luck. But before i could really dig in to it i destroyed my GT-511C1R.. but i was lucky.. my espruino wifi survived. -
• #11
Promise
really shines in this setup of sequencing. I'm glad it became available. I have only one little concern that the nesting may push the limit of memory usage. Of course, implementation of how data is handled can greatly mitigate that. -
• #12
I was glad too.. i had not much experience with promises at that time but this was my chance to get my feet wet so to speak :)
In terms of memory usage i have to admit that i have no clue. This was my first little project with microcontrollers and espruino i just hacked away and was hoping for the best. -
• #13
@user81574, I suggest you move on with PaddeK's code. Again, thanks @PaddeK for sharing. It does not need much to get it to completion for enroll and the other functions you would like to have at your (coding-)finger-tips. (Sticking on my path would over several serious transformation steps - such as adding the parser and promise - lead to a solution too... but I like to use invented wheels... in favor of saving the - non-recoverable - resource called Time for getting after the not yet invented ones...).
For example, for the enroll function, just add the application callback into the method signature (line 11):
...function(id, callback) {
and use it (in lines 21 and 22):
let errorHandler = (err) => { ledOff(); reject(err); callback(err, { _: me, id: id }); }, successHandler = () => { console.log('enroll success'); resolve(); callback(undefined, { _: me, id: id })};
If you want to have your callback execution to happen in its own Espruino JavaScript single-thread 'task', pack the callback invocation in
setTimeout(...
:let errorHandler = (err) => { ledOff(); reject(err); setTimeout(callback, 100, err, { _: me, id: id }); }, successHandler = () => { console.log('enroll success'); resolve(); setTimeout(callback, 100, undefined, { _: me, id: id })};
This will allow other pending - and may be more pressing - 'tasks' to get a chance to do their thing. Applied throughout the whole application makes your application behave like a TSO - (processor) time sharing option - environment for each of the tasks, where you run less into timely-ness contentions (buffer overflow, stacking up to many and/or 'losing' events,...) - from the single core /360 times...
For adding more to the module, such as getting the extra information on open, add a method
.open(...
following the same pattern as.enroll(...
with callback, and adjust thesuccessHandler
accordingly.To make operations very robust, always begin with an
open(
and end with aclose
(to be added in bothsuccessHandler
anderrorHandler
. I do not know the finger print reader's sequencing control, but having a clear begin and end always helps - like demarcation of a transaction: begin and end with commit or rollback (a-la successHandler and errorHandler, respectively).Putting all in one single module is no difficult either. Just add the code into the first piece of code from @PaddeK.
This conversation was great pleasure for me and makes me conclude: Old dog, new tricks? ...still works.
-
• #14
Big thanks to you guys @PaddeK and @allObjects. I highly appreciate your efforts!
-
• #15
Hello @PaddeK @allObjects, am having difficulties using the getTemplate() function. It always timesout whenever I call it.
How do I get the raw template data? (I want to store them in a remote database) -
• #16
...you mean raw image... 5.22 in .pdf doc? - Is that not what @PaddeK said he struggled with? It is about 20KB and I assume there is a buffer overflow or something else that is going on that it times out. It times out because it does never reach the condition of done... (done receiving the 4 + 19200 + 2 bytes (payload plus the extra 6 bytes for admin). Lookup the flags for buffer overflow when the timeout hits... If you want to fix that, you may need to pre-declare a new, fast, contiguous buffer and place the received data into that buffer rather than do string concatenation and/or reducing the baud rate (for just that particular function). I would see that a only possible solution, if there is a solution at all for this. I'm not sure if you can build a compile function to handle that (see "compiled" for making things faster).
-
• #17
The template is just a few bytes.. around 500. This should be no problem.. i guess it is a bug in my code. I am sure it worked at some point but i did some refactoring after that. I ordered a new
GT-511C1 so i can test it.. now i just have to find a place to hide my tee :D -
• #18
Please do hide that tee :D
I'll be waiting for your codes. It would be awesome if the data is a string. That would allow storage easy. -
• #19
I will give it a try a soon my new fingerprint sensor arrives. But i guess this will take a week or two. Why do you want to store the template in the first place? The C1 can hold up to 20 templates and the C3 even 200.
-
• #20
I need a way to store it to an external database because fingerprints data (template) might exceed over 200 and I also need it to make verification remotely. Am assuming the whole image file is converted to a template data. I just need a way to access and extract that data.
-
• #21
could you please paste a code that should retrieve a given template let me run, maybe my code was bad and didnt implement it way I should;
GT511CXX.prototype.getUserFingData = function(id, callback){ let me = this, open = () => me.open(1), getData = (id) => me.getTemplate(id), close = () => me.close(); return new Promise((resolve, reject) => { let errorHandler = (err) => { close(); reject(err); }, successHandler = (data) => { console.log('template gotten'); console.log(data); close(); resolve(); }; getData(id) .then(successHandler) .catch(errorHandler); }); }
I also tried this code below, and got a timeout exceeded
fps.getTemplate(1).then( (data) => { console.log("then called"); console.log(data); } ) .catch( (err) => console.log(err));
-
• #22
Something like this should do the trick.
let GT511CXX = require('./gt511cxx.js'), fps = new GT511CXX(GT511CXX.Devices.GT511C3, Serial1); fps.open().then(() => { fps.getTemplate(0).then(template => { console.log('template', template); }).catch(err => console.log(err)); }).catch(err => console.log(err));
This example should return you the template in the first slot (slot: 0).
It should return an error if there is no template in the given slot. -
• #23
thats all I get:
=undefined timeout exceeded >
I even increased the C.TIMEOUT.RECIEVE_DATA to 7s, yet it still times out
-
• #24
If you are sure you previously enrolled a fingerprint into the same slot you try to get the template from then i guess there is a bug. My new sensor is already shipped.. so it should arrive in a few days.
-
• #25
@user81574, did you check E.getErrorFlags()?
Hello, this is my first usage of the Espruino Wifi board.
I'm having trouble connecting/reading data from Fingerprint Scanner - TTL (GT-511C3).
I have already connected RX-TX, TX-RX pins from FPS to board, but am confused how to read/write data using the Serial class.
I know there is a Nodejs library https://github.com/the-AjK/GT-511C3/blob/master/lib/gt511c3.js for it but I dont know how to use it with the Serial class.
Please any help or comment will be appreciated.