├── toc.yml
├── logos
├── sffp-image.jpg
├── sffp-logo-256.png
└── sffp-logo-32.png
├── src
├── Unosquare.Sparkfun.FingerprintModule
│ ├── Resources
│ │ ├── libnserial.so.1.temp
│ │ └── EmbeddedResources.cs
│ ├── Utils
│ │ ├── OperatingSystem.cs
│ │ └── Runtime.cs
│ ├── PacketBase.cs
│ ├── Unosquare.Sparkfun.FingerprintModule.csproj
│ ├── MessageBase.cs
│ ├── SerialPort
│ │ ├── MsSerialPort.cs
│ │ ├── RjcpSerialPort.cs
│ │ └── ISerialPort.cs
│ ├── DataPacket.cs
│ ├── ResponseBase.cs
│ ├── Command.cs
│ ├── Extensions.cs
│ ├── Enumerations.cs
│ ├── Responses.cs
│ └── FingerprintReader.cs
└── Unosquare.Sparkfun.Playground
│ ├── Unosquare.Sparkfun.Playground.csproj
│ └── Program.cs
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── stale.yml
├── docfx.json
├── LICENSE
├── appveyor.yml
├── Unosquare.Sparkfun.FingerprintModule.sln
├── README.md
├── StyleCop.Analyzers.ruleset
└── .gitignore
/toc.yml:
--------------------------------------------------------------------------------
1 | - name: API Documentation
2 | href: obj/api/
--------------------------------------------------------------------------------
/logos/sffp-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unosquare/sparkfunfingerprint/HEAD/logos/sffp-image.jpg
--------------------------------------------------------------------------------
/logos/sffp-logo-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unosquare/sparkfunfingerprint/HEAD/logos/sffp-logo-256.png
--------------------------------------------------------------------------------
/logos/sffp-logo-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unosquare/sparkfunfingerprint/HEAD/logos/sffp-logo-32.png
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/Resources/libnserial.so.1.temp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unosquare/sparkfunfingerprint/HEAD/src/Unosquare.Sparkfun.FingerprintModule/Resources/libnserial.so.1.temp
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/Utils/OperatingSystem.cs:
--------------------------------------------------------------------------------
1 | #if !NET452
2 | namespace Unosquare.Sparkfun.FingerprintModule.Utils
3 | {
4 | ///
5 | /// Enumeration of Operating Systems.
6 | ///
7 | public enum OperatingSystem
8 | {
9 | ///
10 | /// Unknown OS
11 | ///
12 | Unknown,
13 |
14 | ///
15 | /// Windows
16 | ///
17 | Windows,
18 |
19 | ///
20 | /// UNIX/Linux
21 | ///
22 | Unix,
23 |
24 | ///
25 | /// macOS (OSX)
26 | ///
27 | Osx,
28 | }
29 | }
30 | #endif
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: wontfix
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **Expected behavior**
18 | A clear and concise description of what you expected to happen.
19 |
20 | **Screenshots**
21 | If applicable, add screenshots to help explain your problem.
22 |
23 | **Desktop (please complete the following information):**
24 | - OS: [e.g. iOS]
25 | - Browser [e.g. chrome, safari]
26 | - Version [e.g. 22]
27 |
28 | **Smartphone (please complete the following information):**
29 | - Device: [e.g. iPhone6]
30 | - OS: [e.g. iOS8.1]
31 | - Browser [e.g. stock browser, safari]
32 | - Version [e.g. 22]
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.Playground/Unosquare.Sparkfun.Playground.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | netcoreapp2.2;net461
5 | ..\..\StyleCop.Analyzers.ruleset
6 | 7.2
7 |
8 |
9 |
10 | all
11 | runtime; build; native; contentfiles; analyzers
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docfx.json:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": [
3 | {
4 | "src": [
5 | {
6 | "files": [ "src/Unosquare.Sparkfun.FingerprintModule/**/*.cs" ],
7 | "exclude": [ "**/bin/**", "**/obj/**" ]
8 | }
9 | ],
10 | "dest": "obj/api"
11 | }
12 | ],
13 | "build": {
14 | "template": [
15 | "best-practices/templates/default"
16 | ],
17 | "content": [
18 | {
19 | "files": [ "**/*.yml" ],
20 | "cwd": "obj/api",
21 | "dest": "api"
22 | },
23 | {
24 | "files": [ "*.md", "toc.yml" ]
25 | }
26 | ],
27 | "resource": [
28 | {
29 | "files": [ "best-practices/resources/**", "logos/sffp-logo-256.png"]
30 | }
31 | ],
32 | "globalMetadata": {
33 | "_appTitle": "Sparkfun Fingerprint Reader Interfacing Library for .NET",
34 | "_enableSearch": true,
35 | "_appLogoPath": "best-practices/resources/images/logo.png",
36 | "_docLogo": "logos/sffp-logo-256.png"
37 | },
38 | "dest": "_site"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Unosquare Labs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/PacketBase.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.Sparkfun.FingerprintModule
2 | {
3 | ///
4 | /// Base class for message packets (either command or response).
5 | ///
6 | ///
7 | public abstract class PacketBase : MessageBase
8 | {
9 | ///
10 | /// The base packet length.
11 | ///
12 | public const int BasePacketLength = 12;
13 |
14 | ///
15 | /// Gets or sets the parameter.
16 | ///
17 | public int Parameter { get; protected set; }
18 |
19 | ///
20 | /// Gets a value indicating whether this instance has data packet.
21 | ///
22 | ///
23 | /// true if this instance has data packet; otherwise, false.
24 | ///
25 | public bool HasDataPacket => DataPacket != null;
26 |
27 | ///
28 | /// Gets or sets the data packet.
29 | ///
30 | internal DataPacket DataPacket { get; set; }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/Utils/Runtime.cs:
--------------------------------------------------------------------------------
1 | #if !NET452
2 | namespace Unosquare.Sparkfun.FingerprintModule.Utils
3 | {
4 | using System;
5 | using System.IO;
6 |
7 | ///
8 | /// Provides utility methods to retrieve system information.
9 | ///
10 | internal static class Runtime
11 | {
12 | private static OperatingSystem? _oS;
13 |
14 | ///
15 | /// Gets the current Operating System.
16 | ///
17 | ///
18 | /// The os.
19 | ///
20 | public static OperatingSystem OS
21 | {
22 | get
23 | {
24 | if (_oS.HasValue == false)
25 | {
26 | var windowsDirectory = Environment.GetEnvironmentVariable("windir");
27 | if (string.IsNullOrEmpty(windowsDirectory) == false
28 | && windowsDirectory.Contains(@"\")
29 | && Directory.Exists(windowsDirectory))
30 | {
31 | _oS = OperatingSystem.Windows;
32 | }
33 | else
34 | {
35 | _oS = File.Exists(@"/proc/sys/kernel/ostype") ? OperatingSystem.Unix : OperatingSystem.Osx;
36 | }
37 | }
38 |
39 | return _oS ?? OperatingSystem.Unknown;
40 | }
41 | }
42 | }
43 | }
44 | #endif
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/Unosquare.Sparkfun.FingerprintModule.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Copyright (c) 2019 - Unosquare
4 | Unosquare
5 | Unosquare
6 | netstandard2.0;net452
7 | true
8 | ..\..\StyleCop.Analyzers.ruleset
9 | Unosquare.Sparkfun.FingerprintModule
10 | Unosquare.Sparkfun.FingerprintModule
11 | 7.3
12 |
13 |
14 |
15 |
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/MessageBase.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.Sparkfun.FingerprintModule
2 | {
3 | ///
4 | /// Base class for any kind of message (command, response or data packet).
5 | ///
6 | public abstract class MessageBase
7 | {
8 | internal const byte BaseStartCode1 = 0x55;
9 | internal const byte BaseStartCode2 = 0xAA;
10 | internal static readonly byte[] BaseDeviceId = { 01, 00 };
11 |
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | protected MessageBase()
16 | {
17 | DeviceId = BaseDeviceId;
18 | }
19 |
20 | ///
21 | /// Gets or sets the byte payload (the byte array representation of the message).
22 | ///
23 | protected internal byte[] Payload { get; set; }
24 |
25 | ///
26 | /// Gets or sets the first synchronization byte.
27 | ///
28 | protected byte StartCode1 { get; set; } = BaseStartCode1;
29 |
30 | ///
31 | /// Gets or sets the second synchronization byte.
32 | ///
33 | protected byte StartCode2 { get; set; } = BaseStartCode2;
34 |
35 | ///
36 | /// Gets the device identifier.
37 | ///
38 | /// For current devices, default DeviceId is 0x0001, always fixed.
39 | protected byte[] DeviceId { get; }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/SerialPort/MsSerialPort.cs:
--------------------------------------------------------------------------------
1 | #if NET452
2 | namespace Unosquare.Sparkfun.FingerprintModule.SerialPort
3 | {
4 | using System;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using System.IO.Ports;
8 |
9 | internal class MsSerialPort : ISerialPort
10 | {
11 | private readonly SerialPort _serialPort;
12 |
13 | public MsSerialPort(string portName, int baudRate)
14 | {
15 | _serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
16 | }
17 |
18 | public string PortName => _serialPort?.PortName;
19 |
20 | public bool IsOpen => _serialPort?.IsOpen == true;
21 |
22 | public int BytesToRead => _serialPort?.BytesToRead ?? throw new InvalidOperationException("Serial port is not open.");
23 |
24 | public static string[] GetPortNames() => SerialPort.GetPortNames();
25 |
26 | public void Open() => _serialPort?.Open();
27 |
28 | public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
29 | _serialPort?.BaseStream.WriteAsync(buffer, offset, count, cancellationToken);
30 |
31 | public Task FlushAsync(CancellationToken cancellationToken) =>
32 | _serialPort?.BaseStream.FlushAsync(cancellationToken);
33 |
34 | public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
35 | _serialPort?.BaseStream.ReadAsync(buffer, offset, count, cancellationToken);
36 |
37 | public void Close() => _serialPort?.Close();
38 |
39 | void IDisposable.Dispose() => _serialPort?.Dispose();
40 | }
41 | }
42 | #endif
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/SerialPort/RjcpSerialPort.cs:
--------------------------------------------------------------------------------
1 | #if !NET452
2 | namespace Unosquare.Sparkfun.FingerprintModule.SerialPort
3 | {
4 | using RJCP.IO.Ports;
5 | using Resources;
6 | using System;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | internal class RjcpSerialPort : ISerialPort
11 | {
12 | private readonly SerialPortStream _serialPort;
13 |
14 | static RjcpSerialPort()
15 | {
16 | if (Utils.Runtime.OS != Utils.OperatingSystem.Windows)
17 | EmbeddedResources.ExtractAll();
18 | }
19 |
20 | public RjcpSerialPort(string portName, int baudRate)
21 | {
22 | _serialPort = new SerialPortStream(portName, baudRate, 8, Parity.None, StopBits.One);
23 | }
24 |
25 | public string PortName => _serialPort?.PortName;
26 |
27 | public bool IsOpen => _serialPort?.IsOpen == true;
28 |
29 | public int BytesToRead => _serialPort?.BytesToRead ?? throw new InvalidOperationException("Serial port is not open.");
30 |
31 | public static string[] GetPortNames() => SerialPortStream.GetPortNames();
32 |
33 | public void Open() => _serialPort?.Open();
34 |
35 | public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
36 | _serialPort?.WriteAsync(buffer, offset, count, cancellationToken);
37 |
38 | public Task FlushAsync(CancellationToken cancellationToken) =>
39 | _serialPort?.FlushAsync(cancellationToken);
40 |
41 | public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
42 | _serialPort?.ReadAsync(buffer, offset, count, cancellationToken);
43 |
44 | public void Close() =>_serialPort?.Close();
45 |
46 | void IDisposable.Dispose() =>_serialPort?.Dispose();
47 | }
48 | }
49 | #endif
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '1.0.{build}'
2 | image: Visual Studio 2017
3 | configuration:
4 | - Release
5 | platform: Any CPU
6 | notifications:
7 | - provider: Slack
8 | auth_token:
9 | secure: Q+xg4/yU5OR9BVF14cw4yZ+3qlhMeYDsAhUQyOIszmF1mHvq44tIvQpWByBJCd/cgUIZk3SwBpk4hh1MrkQIk6rnaOZ2LNBTev4zrq36oXk=
10 | channel: '#builds'
11 | environment:
12 | # Don't report back to the mothership
13 | DOTNET_CLI_TELEMETRY_OPTOUT: 1
14 | COVERALLS_REPO_TOKEN:
15 | secure: dVpAqavd3jP7LMW+oswLBPfihwiGDZUtWDQWJadMO1Gj61SYegxNsGTZOT2BYN6+
16 | op_build_user: "Geo Perez"
17 | op_build_user_email: "geovanni.perez@gmail.com"
18 | access_token:
19 | secure: HzWdswNyfQbQ0vLk9IQyO+Ei9mxoPYp9rvv6HPhtC9J/Fm7EHRzyV953pbPRXI9I
20 | init:
21 | - ps: $Env:LABEL = "CI" + $Env:APPVEYOR_BUILD_NUMBER.PadLeft(5, "0")
22 | before_build:
23 | - ps: |
24 | if(-Not $env:APPVEYOR_PULL_REQUEST_TITLE)
25 | {
26 | git checkout $env:APPVEYOR_REPO_BRANCH -q
27 | cinst docfx -y
28 | }
29 | - appveyor-retry dotnet restore -v Minimal
30 | build_script:
31 | - msbuild /p:Configuration=%CONFIGURATION%
32 | after_build:
33 | - ps: |
34 | if(-Not $env:APPVEYOR_PULL_REQUEST_TITLE)
35 | {
36 | docfx docfx.json
37 | git config --global credential.helper store
38 | Add-Content "$env:USERPROFILE\.git-credentials" "https://$($env:access_token):x-oauth-basic@github.com`n"
39 | git config --global core.autocrlf false
40 | git config --global user.email $env:op_build_user_email
41 | git config --global user.name $env:op_build_user
42 |
43 | git clone https://github.com/unosquare/sparkfunfingerprint.git -b gh-pages origin_site -q
44 | git clone -b documentation https://github.com/unosquare/best-practices.git -q
45 | docfx docfx.json --logLevel Error
46 | Copy-Item origin_site/.git _site -recurse
47 | CD _site
48 | Copy-Item README.html index.html -force
49 | git add -A 2>&1
50 | git commit -m "Documentation update" -q
51 | git push origin gh-pages -q
52 | CD ..
53 | }
54 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/SerialPort/ISerialPort.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.Sparkfun.FingerprintModule.SerialPort
2 | {
3 | using System;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | ///
8 | ///
9 | /// Interface to wrap any Serial Port implementation.
10 | ///
11 | ///
12 | public interface ISerialPort : IDisposable
13 | {
14 | ///
15 | /// Gets the name of the port.
16 | ///
17 | ///
18 | /// The name of the port.
19 | ///
20 | string PortName { get; }
21 |
22 | ///
23 | /// Gets a value indicating whether this instance is open.
24 | ///
25 | ///
26 | /// true if this instance is open; otherwise, false.
27 | ///
28 | bool IsOpen { get; }
29 |
30 | ///
31 | /// Gets the bytes to read.
32 | ///
33 | ///
34 | /// The bytes to read.
35 | ///
36 | int BytesToRead { get; }
37 |
38 | ///
39 | /// Opens this instance.
40 | ///
41 | void Open();
42 |
43 | ///
44 | /// Closes this instance.
45 | ///
46 | void Close();
47 |
48 | ///
49 | /// Writes the asynchronous.
50 | ///
51 | /// The buffer.
52 | /// The offset.
53 | /// The count.
54 | /// The cancellation token.
55 | /// A task representing the write action.
56 | Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
57 |
58 | ///
59 | /// Flushes the asynchronous.
60 | ///
61 | /// The cancellation token.
62 | /// A task representing the flush action.
63 | Task FlushAsync(CancellationToken cancellationToken);
64 |
65 | ///
66 | /// Reads the asynchronous.
67 | ///
68 | /// The buffer.
69 | /// The offset.
70 | /// The count.
71 | /// The cancellation token.
72 | /// A task representing the count of bytes from the read.
73 | Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Unosquare.Sparkfun.FingerprintModule.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27703.2026
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Unosquare.Sparkfun.FingerprintModule", "src\Unosquare.Sparkfun.FingerprintModule\Unosquare.Sparkfun.FingerprintModule.csproj", "{EE290DDE-55B8-467E-93BF-5C473A7BF652}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Unosquare.Sparkfun.Playground", "src\Unosquare.Sparkfun.Playground\Unosquare.Sparkfun.Playground.csproj", "{A6F75794-633A-4BEC-B468-23CCE0C411EE}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A7638602-6B6E-4384-8717-B89024EB76C0}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0F77D073-631C-42C1-9299-BAE5B83E3F45}"
13 | ProjectSection(SolutionItems) = preProject
14 | .travis.yml = .travis.yml
15 | appveyor.yml = appveyor.yml
16 | LICENSE = LICENSE
17 | README.md = README.md
18 | StyleCop.Analyzers.ruleset = StyleCop.Analyzers.ruleset
19 | EndProjectSection
20 | EndProject
21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logos", "logos", "{5D09002E-B80D-4292-8FBA-2DEE3A9FD682}"
22 | ProjectSection(SolutionItems) = preProject
23 | logos\sffp-image.jpg = logos\sffp-image.jpg
24 | logos\sffp-logo-256.png = logos\sffp-logo-256.png
25 | logos\sffp-logo-32.png = logos\sffp-logo-32.png
26 | EndProjectSection
27 | EndProject
28 | Global
29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
30 | Debug|Any CPU = Debug|Any CPU
31 | Release|Any CPU = Release|Any CPU
32 | EndGlobalSection
33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
34 | {EE290DDE-55B8-467E-93BF-5C473A7BF652}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {EE290DDE-55B8-467E-93BF-5C473A7BF652}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {EE290DDE-55B8-467E-93BF-5C473A7BF652}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {EE290DDE-55B8-467E-93BF-5C473A7BF652}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {A6F75794-633A-4BEC-B468-23CCE0C411EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {A6F75794-633A-4BEC-B468-23CCE0C411EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {A6F75794-633A-4BEC-B468-23CCE0C411EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {A6F75794-633A-4BEC-B468-23CCE0C411EE}.Release|Any CPU.Build.0 = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | GlobalSection(NestedProjects) = preSolution
47 | {EE290DDE-55B8-467E-93BF-5C473A7BF652} = {A7638602-6B6E-4384-8717-B89024EB76C0}
48 | {A6F75794-633A-4BEC-B468-23CCE0C411EE} = {A7638602-6B6E-4384-8717-B89024EB76C0}
49 | {5D09002E-B80D-4292-8FBA-2DEE3A9FD682} = {0F77D073-631C-42C1-9299-BAE5B83E3F45}
50 | EndGlobalSection
51 | GlobalSection(ExtensibilityGlobals) = postSolution
52 | SolutionGuid = {6FB98E8D-6F2D-4AE8-A2B0-8AA4C24F9F77}
53 | EndGlobalSection
54 | EndGlobal
55 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/DataPacket.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.Sparkfun.FingerprintModule
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | ///
7 | /// Base class for data packets.
8 | ///
9 | ///
10 | internal abstract class DataPacket
11 | : MessageBase
12 | {
13 | internal const byte DataPacketStartCode1 = 0x5A;
14 | internal const byte DataPacketStartCode2 = 0xA5;
15 |
16 | ///
17 | /// Initializes a new instance of the class.
18 | ///
19 | protected DataPacket()
20 | {
21 | StartCode1 = DataPacketStartCode1;
22 | StartCode2 = DataPacketStartCode2;
23 | }
24 |
25 | ///
26 | /// Gets or sets the byte array representing the real data the packet contains.
27 | ///
28 | public byte[] Data { get; protected set; }
29 | }
30 |
31 | ///
32 | /// Represents a command data packet.
33 | ///
34 | ///
35 | internal class CommandDataPacket
36 | : DataPacket
37 | {
38 | ///
39 | /// Initializes a new instance of the class.
40 | ///
41 | /// A byte array representing the real data the packet contains.
42 | public CommandDataPacket(byte[] data)
43 | {
44 | Data = data;
45 | GeneratePayload();
46 | }
47 |
48 | ///
49 | /// Generates the payload for the packet.
50 | ///
51 | private void GeneratePayload()
52 | {
53 | var payload = new List { StartCode1, StartCode2 };
54 | payload.AddRange(DeviceId);
55 | payload.AddRange(Data);
56 | var crc = payload.ComputeChecksum().ToLittleEndianArray();
57 | payload.AddRange(crc);
58 | Payload = payload.ToArray();
59 | }
60 | }
61 |
62 | ///
63 | /// Represents a response data packet.
64 | ///
65 | ///
66 | internal class ResponseDataPacket
67 | : DataPacket
68 | {
69 | ///
70 | /// Initializes a new instance of the class.
71 | ///
72 | /// A byte array representing the payload of the packet.
73 | public ResponseDataPacket(byte[] payload)
74 | {
75 | Payload = payload;
76 |
77 | // Extracting data
78 | var data = new byte[payload.Length - 6];
79 | Array.Copy(payload, 4, data, 0, data.Length);
80 | Data = data;
81 |
82 | IsCrcValid = payload.ValidateChecksum();
83 | }
84 |
85 | ///
86 | /// Gets a value indicating whether this instance has a valid CRC.
87 | ///
88 | ///
89 | /// true if this instance has a valid CRC; otherwise, false.
90 | ///
91 | public bool IsCrcValid { get; }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/Resources/EmbeddedResources.cs:
--------------------------------------------------------------------------------
1 | #if !NET452
2 | namespace Unosquare.Sparkfun.FingerprintModule.Resources
3 | {
4 | using System;
5 | using System.Collections.ObjectModel;
6 | using System.IO;
7 | using System.Reflection;
8 | using System.Runtime.InteropServices;
9 |
10 | ///
11 | /// Provides access to embedded assembly files.
12 | ///
13 | internal static class EmbeddedResources
14 | {
15 | internal const string LibCLibrary = "libc";
16 |
17 | ///
18 | /// Initializes static members of the class.
19 | ///
20 | static EmbeddedResources()
21 | {
22 | EntryAssembly = typeof(EmbeddedResources).GetTypeInfo().Assembly;
23 | ResourceNames = new ReadOnlyCollection(EntryAssembly.GetManifestResourceNames());
24 | var uri = new UriBuilder(EntryAssembly.CodeBase);
25 | var path = Uri.UnescapeDataString(uri.Path);
26 | EntryAssemblyDirectory = Path.GetDirectoryName(path);
27 | }
28 |
29 | ///
30 | /// Gets the resource names.
31 | ///
32 | ///
33 | /// The resource names.
34 | ///
35 | public static ReadOnlyCollection ResourceNames { get; }
36 |
37 | ///
38 | /// Gets the entry assembly directory.
39 | ///
40 | ///
41 | /// The entry assembly directory.
42 | ///
43 | public static string EntryAssemblyDirectory { get; }
44 |
45 | public static Assembly EntryAssembly { get; }
46 |
47 | ///
48 | /// Changes file permissions on a Unix file system.
49 | ///
50 | /// The filename.
51 | /// The mode.
52 | /// The result.
53 | [DllImport(LibCLibrary, EntryPoint = "chmod", SetLastError = true)]
54 | public static extern int Chmod(string filename, uint mode);
55 |
56 | ///
57 | /// Converts a string to a 32 bit integer. Use endpointer as IntPtr.Zero.
58 | ///
59 | /// The number string.
60 | /// The end pointer.
61 | /// The number base.
62 | /// The result.
63 | [DllImport(LibCLibrary, EntryPoint = "strtol", SetLastError = true)]
64 | public static extern int StringToInteger(string numberString, IntPtr endPointer, int numberBase);
65 |
66 | ///
67 | /// Extracts all the file resources to the specified base path.
68 | ///
69 | public static void ExtractAll()
70 | {
71 | var basePath = EntryAssemblyDirectory;
72 |
73 | foreach (var resourceName in ResourceNames)
74 | {
75 | var filename = resourceName
76 | .Substring($"{typeof(EmbeddedResources).Namespace}.".Length);
77 |
78 | var targetPath = Path.Combine(basePath, filename.Replace(".temp", string.Empty));
79 | if (File.Exists(targetPath)) return;
80 |
81 | using (var stream =
82 | EntryAssembly.GetManifestResourceStream($"{typeof(EmbeddedResources).Namespace}.{filename}"))
83 | {
84 | using (var outputStream = File.OpenWrite(targetPath))
85 | {
86 | stream?.CopyTo(outputStream);
87 | }
88 |
89 | try
90 | {
91 | Chmod(targetPath, (uint)StringToInteger("0777", IntPtr.Zero, 8));
92 | }
93 | catch
94 | {
95 | /* Ignore */
96 | }
97 | }
98 | }
99 | }
100 | }
101 | }
102 | #endif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://ci.appveyor.com/project/geoperez/sparkfunfingerprint/branch/master)
2 | [](https://github.com/igrigorik/ga-beacon)
3 |
4 | #  SparkFun Fingerprint Reader (GT-521Fxx)
5 |
6 | *:star: Please star this project if you find it useful!*
7 |
8 | Interfacing Library for .NET 4.5 (and Mono) and .NET Core!.
9 |
10 | * [Product Page](https://www.sparkfun.com/products/14585)
11 | * [Data sheet](https://cdn.sparkfun.com/assets/learn_tutorials/7/2/3/GT-521FX2_datasheet_V1.1__003_.pdf)
12 | * [Reference Manual](https://cdn.sparkfun.com/assets/learn_tutorials/7/2/3/GT-521F52_Programming_guide_V10_20161001.pdf)
13 | * [Wiki](https://learn.sparkfun.com/tutorials/fingerprint-scanner-gt-521fxx-hookup-guide)
14 |
15 | 
16 |
17 | ## Specifications
18 |
19 | | Technical Specs | GT-521F32 / GT-521F52 |
20 | | ---------------------------- | --------------------------------------------------- |
21 | | CPU | ARM Cortex M3 Cortex |
22 | | Sensor | optical |
23 | | Window | 16.9mm x 12.9mm |
24 | | Effective Area of the Sensor | 14mm x 12.5mm |
25 | | Image Size | 258x202 px |
26 | | Resolution | 450 dpi |
27 | | Max # of Fingerprints | 200 / 3000 |
28 | | Matching Mode | 1:1, 1:N |
29 | | Size of Template | 496 Bytes(template) + 2 Bytes (checksum) |
30 | | Serial Communication | UART (Default: 9600 baud) and USB v2.0 (Full Speed) |
31 | | False Acceptance Rate (FAR) | < 0.001% |
32 | | False Rejection Rate (FRR) | < 0.01% |
33 | | Enrollment Time | < 3 sec (3 fingerprints) |
34 | | Identification Time | < 1.5 |
35 | | Operating Voltage | 3.3V ~ 6Vdc |
36 | | Operating Current | < 130mA |
37 | | Touch Operating Voltage | 3.3Vdc |
38 | | Touch Operating Current | < 3mA |
39 | | Touch Standby Current | < μ5 |
40 |
41 | ## Library Features
42 | * All documented commands are implemented (2018-06-25)
43 | * Operations are all asynchronous
44 | * Nice sample application included for testing
45 | * MIT License
46 | * .Net Framework (and Mono)
47 | * No dependencies
48 | * .Net Standard
49 | * [SerialPortStream](https://github.com/jcurl/serialportstream): Independent implementation of System.IO.Ports.SerialPort and SerialStream for portability.
50 |
51 | ## NuGet Installation: [](https://badge.fury.io/nu/Unosquare.Sparkfun.FingerprintReader)
52 |
53 | ```
54 | PM> Install-Package Unosquare.Sparkfun.FingerprintReader
55 | ```
56 |
57 | ## Usage
58 |
59 | ```csharp
60 | using (var reader = new FingerprintReader(FingerprintReaderModel.GT521F52))
61 | {
62 | await reader.Open("COM4");
63 | Console.WriteLine($"Serial Number: {reader.SerialNumber}");
64 | Console.WriteLine($"Firmware Version: {reader.FirmwareVersion}");
65 | }
66 | ```
67 |
68 | ## Related fingerprint projects
69 |
70 | | Project | Description |
71 | |--------| ---|
72 | |[wsfingerprint](https://github.com/unosquare/wsfingerprint)|WaveShare Fingerprint Reader - Interfacing Library for .NET |
73 | |[libfprint-cs](https://github.com/unosquare/libfprint-cs)|The long-awaited C# (.NET/Mono) wrapper for the great fprint library|
74 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/ResponseBase.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.Sparkfun.FingerprintModule
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | ///
7 | /// Base class for response messages.
8 | ///
9 | ///
10 | public abstract class ResponseBase
11 | : PacketBase
12 | {
13 | internal static readonly Dictionary ResponseDataLength = new Dictionary
14 | {
15 | { CommandCode.Open, 24 },
16 | { CommandCode.Enroll3, 498 },
17 | { CommandCode.MakeTemplate, 498 },
18 | { CommandCode.GetImage, 52116 },
19 | { CommandCode.GetRawImage, 19200},
20 | { CommandCode.GetTemplate, 498 },
21 | };
22 |
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | /// A byte array representing the payload of the response.
27 | protected ResponseBase(byte[] payload)
28 | {
29 | Payload = payload;
30 | Parameter = payload.LittleEndianArrayToInt(4);
31 | Response = (ResponseCode)payload.LittleEndianArrayToUInt16(8);
32 | IsCrcValid = payload.ValidateChecksum(0, BasePacketLength - 1);
33 |
34 | if (payload.Length <= BasePacketLength) return;
35 | var dataPacketPayload = new byte[payload.Length - BasePacketLength];
36 | Array.Copy(payload, BasePacketLength, dataPacketPayload, 0, dataPacketPayload.Length);
37 |
38 | DataPacket = new ResponseDataPacket(dataPacketPayload);
39 | }
40 |
41 | ///
42 | /// Gets a value indicating whether this instance is successful.
43 | ///
44 | ///
45 | /// true if this instance is successful; otherwise, false.
46 | ///
47 | public virtual bool IsSuccessful => IsCrcValid && Response == ResponseCode.Ack && (!HasDataPacket || ResponseDataPacket.IsCrcValid);
48 |
49 | ///
50 | /// Gets the error code.
51 | ///
52 | public ErrorCode ErrorCode
53 | {
54 | get
55 | {
56 | if (IsSuccessful)
57 | return ErrorCode.NoError;
58 |
59 | if (!IsCrcValid || (HasDataPacket && !ResponseDataPacket.IsCrcValid))
60 | return ErrorCode.InvalidCheckSum;
61 |
62 | if (Parameter >= 0 && Parameter < 0x1001)
63 | return ErrorCode.DuplicateFingerprint;
64 |
65 | return (ErrorCode)Parameter;
66 | }
67 | }
68 |
69 | ///
70 | /// Gets the response data packet.
71 | ///
72 | internal ResponseDataPacket ResponseDataPacket => (ResponseDataPacket)DataPacket;
73 |
74 | ///
75 | /// Gets the response code.
76 | ///
77 | protected ResponseCode Response { get; }
78 |
79 | ///
80 | /// Gets a value indicating whether this instance has a valid CRC.
81 | ///
82 | ///
83 | /// true if this instance has a valid CRC; otherwise, false.
84 | ///
85 | protected bool IsCrcValid { get; }
86 |
87 | ///
88 | /// Gets a unsuccessful response.
89 | ///
90 | /// A final response type.
91 | /// The error code for the unsuccessful response.
92 | /// An unsuccessful response of type T.
93 | internal static T GetUnsuccessfulResponse(ErrorCode errorCode)
94 | where T : ResponseBase
95 | {
96 | // TODO: General payload generation code
97 | var payload = new List { BaseStartCode1, BaseStartCode2 };
98 | payload.AddRange(BaseDeviceId);
99 | payload.AddRange(((int)errorCode).ToLittleEndianArray());
100 | payload.AddRange(((ushort)ResponseCode.Nack).ToLittleEndianArray());
101 | var crc = payload.ComputeChecksum().ToLittleEndianArray();
102 | payload.AddRange(crc);
103 |
104 | return Activator.CreateInstance(typeof(T), payload.ToArray()) as T;
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/StyleCop.Analyzers.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/Command.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.Sparkfun.FingerprintModule
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | ///
7 | /// Represents a command or request for fingerprint device.
8 | ///
9 | ///
10 | public class Command
11 | : PacketBase
12 | {
13 | internal static readonly Dictionary CommandDataLength = new Dictionary
14 | {
15 | { CommandCode.VerifyTemplate, 498 },
16 | { CommandCode.IdentifyTemplate, 498 },
17 | { CommandCode.IdentifyTemplate2, 500 },
18 | { CommandCode.SetTemplate, 498 },
19 | };
20 |
21 | ///
22 | /// Initializes a new instance of the class.
23 | ///
24 | /// The command code.
25 | /// The parameter.
26 | protected Command(CommandCode commandCode, int parameter)
27 | {
28 | CommandCode = commandCode;
29 | Parameter = parameter;
30 | GeneratePayload();
31 | }
32 |
33 | ///
34 | /// Initializes a new instance of the class.
35 | ///
36 | /// The command code.
37 | /// The parameter.
38 | /// A byte array representing the data for a .
39 | protected Command(CommandCode commandCode, int parameter, byte[] data)
40 | : this(commandCode, parameter)
41 | {
42 | DataPacket = new CommandDataPacket(data);
43 | }
44 |
45 | ///
46 | /// Gets the command code.
47 | ///
48 | public CommandCode CommandCode { get; }
49 |
50 | ///
51 | /// Creates a object with the specified command code.
52 | ///
53 | /// The command code.
54 | /// A object.
55 | internal static Command Create(CommandCode commandCode) =>
56 | Create(commandCode, 0);
57 |
58 | ///
59 | /// Creates a object with the specified command code and parameter.
60 | ///
61 | /// The command code.
62 | /// The parameter.
63 | /// A object.
64 | internal static Command Create(CommandCode commandCode, int parameter) =>
65 | new Command(commandCode, parameter);
66 |
67 | ///
68 | /// Creates a object with the specified command code and parameter. Additionally creates a with the specific data.
69 | ///
70 | /// The command code.
71 | /// The parameter.
72 | /// A byte array representing the data for a .
73 | /// A object containing a .
74 | /// data.
75 | /// data - Current data length does not match expected data length for the command.
76 | internal static Command Create(CommandCode commandCode, int parameter, byte[] data)
77 | {
78 | if (data == null)
79 | throw new ArgumentNullException(nameof(data));
80 |
81 | if (CommandDataLength.ContainsKey(commandCode) && data.Length != CommandDataLength[commandCode])
82 | {
83 | throw new ArgumentOutOfRangeException(nameof(data), "Current data length does not match expected data length for the command.");
84 | }
85 |
86 | return new Command(commandCode, parameter, data);
87 | }
88 |
89 | ///
90 | /// Generates the payload for the command packet.
91 | ///
92 | private void GeneratePayload()
93 | {
94 | var payload = new List { BaseStartCode1, BaseStartCode2 };
95 | payload.AddRange(BaseDeviceId);
96 | payload.AddRange(Parameter.ToLittleEndianArray());
97 | payload.AddRange(((ushort)CommandCode).ToLittleEndianArray());
98 | var crc = payload.ComputeChecksum().ToLittleEndianArray();
99 | payload.AddRange(crc);
100 | Payload = payload.ToArray();
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/Extensions.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.Sparkfun.FingerprintModule
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | ///
7 | /// Extension methods.
8 | ///
9 | internal static class Extensions
10 | {
11 | ///
12 | /// Computes the checksum of the given payload.
13 | ///
14 | /// The payload.
15 | /// A value representing the computed CRC.
16 | /// payload.
17 | internal static ushort ComputeChecksum(this IList payload)
18 | {
19 | if (payload == null || payload.Count == 0)
20 | throw new ArgumentException($"'{nameof(payload)}' must not be empty.");
21 |
22 | return payload.ComputeChecksum(0, payload.Count - 1);
23 | }
24 |
25 | ///
26 | /// Computes the checksum of the given payload.
27 | ///
28 | /// The payload.
29 | /// The start index.
30 | /// The end index.
31 | /// A value representing the computed CRC.
32 | /// payload.
33 | internal static ushort ComputeChecksum(this IList payload, int startIndex, int endIndex)
34 | {
35 | if (payload == null || payload.Count < endIndex + 1)
36 | throw new ArgumentException($"'{nameof(payload)}' hast to be at least {endIndex + 1} bytes long.");
37 |
38 | ushort checksum = payload[startIndex];
39 | for (var i = startIndex + 1; i <= endIndex; i++)
40 | {
41 | checksum = (ushort)(checksum + payload[i]);
42 | }
43 |
44 | return checksum;
45 | }
46 |
47 | ///
48 | /// Validates the checksum for a byte array.
49 | ///
50 | /// The byte array.
51 | /// A indicating if the CRC is valid.
52 | /// true if the CRC is valid; otherwise, false.
53 | ///
54 | /// payload.
55 | internal static bool ValidateChecksum(this byte[] payload)
56 | {
57 | if (payload == null || payload.Length == 0)
58 | throw new ArgumentException($"'{nameof(payload)}' must not be empty.");
59 |
60 | return payload.ValidateChecksum(0, payload.Length - 1);
61 | }
62 |
63 | ///
64 | /// Validates the checksum for a byte array.
65 | ///
66 | /// The byte array.
67 | /// The start index.
68 | /// The end index.
69 | /// A indicating if the CRC is valid.
70 | /// true if the CRC is valid; otherwise, false.
71 | ///
72 | /// payload.
73 | internal static bool ValidateChecksum(this byte[] payload, int startIndex, int endIndex)
74 | {
75 | if (payload == null || payload.Length < endIndex + 1)
76 | throw new ArgumentException($"'{nameof(payload)}' hast to be at least {endIndex + 1} bytes long.");
77 |
78 | var checksum = payload.ComputeChecksum(startIndex, endIndex - 2);
79 | var currChecksum = payload.LittleEndianArrayToUInt16(endIndex - 1);
80 |
81 | return checksum == currChecksum;
82 | }
83 |
84 | ///
85 | /// Converts an value to a little endian byte array.
86 | ///
87 | /// The value.
88 | /// A little endian byte array with the converted value.
89 | internal static byte[] ToLittleEndianArray(this ushort value)
90 | {
91 | var result = BitConverter.GetBytes(value);
92 | if (!BitConverter.IsLittleEndian)
93 | Array.Reverse(result);
94 |
95 | return result;
96 | }
97 |
98 | ///
99 | /// Converts a little endian array to an .
100 | ///
101 | /// The byte array.
102 | /// The start index.
103 | /// An with the converted value.
104 | internal static ushort LittleEndianArrayToUInt16(this byte[] data, int startIndex)
105 | {
106 | var result = new byte[2];
107 | Array.Copy(data, startIndex, result, 0, 2);
108 |
109 | if (!BitConverter.IsLittleEndian)
110 | Array.Reverse(result);
111 |
112 | return BitConverter.ToUInt16(result, 0);
113 | }
114 |
115 | ///
116 | /// Converts an value to a little endian byte array.
117 | ///
118 | /// The value.
119 | /// A little endian byte array with the converted value.
120 | internal static byte[] ToLittleEndianArray(this int value)
121 | {
122 | var result = BitConverter.GetBytes(value);
123 | if (!BitConverter.IsLittleEndian)
124 | Array.Reverse(result);
125 |
126 | return result;
127 | }
128 |
129 | ///
130 | /// Converts a little endian array to an .
131 | ///
132 | /// The byte array.
133 | /// The start index.
134 | /// An with the converted value.
135 | internal static int LittleEndianArrayToInt(this byte[] data, int startIndex)
136 | {
137 | var result = new byte[4];
138 | Array.Copy(data, startIndex, result, 0, 4);
139 |
140 | if (!BitConverter.IsLittleEndian)
141 | Array.Reverse(result);
142 |
143 | return BitConverter.ToInt32(result, 0);
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.Playground/Program.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.Sparkfun.Playground
2 | {
3 | using System;
4 | using Swan;
5 | using System.Collections.Generic;
6 | using System.Threading.Tasks;
7 | using FingerprintModule;
8 | using Swan.Logging;
9 | #if NET461
10 | using System.Drawing;
11 | using System.Drawing.Imaging;
12 | using System.Runtime.InteropServices;
13 | #endif
14 |
15 | public static class Program
16 | {
17 | private const int InitialBaudRate = 9600;
18 |
19 | private static readonly Dictionary Options = new Dictionary
20 | {
21 | // Module Control Items
22 | {ConsoleKey.C, "Count enrolled users"},
23 | {ConsoleKey.M, "Match 1:N"},
24 | {ConsoleKey.I, "Get Image"},
25 | {ConsoleKey.R, "Get Raw Image"},
26 | {ConsoleKey.S, "Enter Standby Mode"},
27 | };
28 |
29 | public static async Task Main(string[] args)
30 | {
31 | try
32 | {
33 | "Getting ports...".Info();
34 |
35 | foreach (var p in FingerprintReader.GetPortNames())
36 | {
37 | $"Port: {p}".Info();
38 | }
39 |
40 | "Creating port...".Info();
41 | var reader = new FingerprintReader(FingerprintReaderModel.GT521F52);
42 |
43 | $"Opening port at {InitialBaudRate}...".Info();
44 | await reader.OpenAsync("COM4").ConfigureAwait(false);
45 |
46 | $"Serial Number: {reader.SerialNumber}".Info();
47 | $"Firmware Version: {reader.FirmwareVersion}".Info();
48 |
49 | while (true)
50 | {
51 | var option = Terminal.ReadPrompt("Select an option", Options, "Esc to quit");
52 | if (option.Key == ConsoleKey.C)
53 | {
54 | var countResponse = await reader.CountEnrolledFingerprintAsync().ConfigureAwait(false);
55 |
56 | if (countResponse.IsSuccessful)
57 | $"Users enrolled: {countResponse.EnrolledFingerprints}".Info();
58 | }
59 | else if (option.Key == ConsoleKey.M)
60 | {
61 | try
62 | {
63 | var matchResponse = await reader.MatchOneToN().ConfigureAwait(false);
64 |
65 | if (matchResponse.IsSuccessful)
66 | $"UserId: {matchResponse.UserId}".Info();
67 | else
68 | $"Error: {matchResponse.ErrorCode}".Error();
69 | }
70 | catch (OperationCanceledException ex)
71 | {
72 | $"Error: {ex.Message}".Error();
73 | }
74 | }
75 | else if (option.Key == ConsoleKey.I)
76 | {
77 | try
78 | {
79 | var imageResponse = await reader.GetImageAsync().ConfigureAwait(false);
80 | if (imageResponse.IsSuccessful)
81 | {
82 | $"Image size: {imageResponse.Image.Length}bytes".Info();
83 | #if NET461
84 | SaveImage(imageResponse.Image, 202, 258, "Image.bmp");
85 | #endif
86 | }
87 | else
88 | {
89 | $"Error: {imageResponse.ErrorCode}".Error();
90 | }
91 | }
92 | catch (OperationCanceledException ex)
93 | {
94 | $"Error: {ex.Message}".Error();
95 | }
96 | }
97 | else if (option.Key == ConsoleKey.R)
98 | {
99 | try
100 | {
101 | var imageResponse = await reader.GetRawImageAsync().ConfigureAwait(false);
102 | if (imageResponse.IsSuccessful)
103 | {
104 | $"Image size: {imageResponse.Image.Length}bytes".Info();
105 | #if NET461
106 | SaveImage(imageResponse.Image, 160, 120, "RawImage.bmp");
107 | #endif
108 | }
109 | else
110 | {
111 | $"Error: {imageResponse.ErrorCode}".Error();
112 | }
113 | }
114 | catch (OperationCanceledException ex)
115 | {
116 | $"Error: {ex.Message}".Error();
117 | }
118 | }
119 | else if (option.Key == ConsoleKey.S)
120 | {
121 | var standbyResponse = await reader.EnterStandByMode().ConfigureAwait(false);
122 |
123 | if (standbyResponse.IsSuccessful)
124 | "Standby Mode".Info();
125 | else
126 | $"Error: {standbyResponse.ErrorCode}".Error();
127 | }
128 | else if (option.Key == ConsoleKey.Escape)
129 | {
130 | break;
131 | }
132 | }
133 |
134 | "Closing port...".Info();
135 | reader.Dispose();
136 | Console.Clear();
137 | }
138 | catch (Exception ex)
139 | {
140 | ex.Log("Program.Main");
141 | Console.ReadLine();
142 | }
143 | }
144 |
145 | #if NET461
146 | private static void SaveImage(byte[] image, int width, int height, string fileName)
147 | {
148 | var newData = new byte[image.Length * 4];
149 |
150 | for (int x = 0; x < image.Length; x++)
151 | {
152 | newData[x * 4] = image[x];
153 | newData[(x * 4) + 1] = image[x];
154 | newData[(x * 4) + 2] = image[x];
155 | newData[(x * 4) + 3] = image[x];
156 | }
157 |
158 | using (var bmp = new Bitmap(width, height, PixelFormat.Format32bppRgb))
159 | {
160 | BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
161 | ImageLockMode.WriteOnly,
162 | bmp.PixelFormat);
163 |
164 | var pnative = bmpData.Scan0;
165 | Marshal.Copy(newData, 0, pnative, newData.Length);
166 |
167 | bmp.UnlockBits(bmpData);
168 |
169 | bmp.Save(fileName);
170 | }
171 | }
172 | #endif
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/Enumerations.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.Sparkfun.FingerprintModule
2 | {
3 | using System;
4 |
5 | ///
6 | /// Fingerprint reader model.
7 | ///
8 | /// Each model has different fingerprint hold capacity.
9 | public enum FingerprintReaderModel
10 | {
11 | ///
12 | /// Model GT-521F32
13 | ///
14 | /// Holds a maximum of 200 fingerprints.
15 | GT521F32 = 200,
16 |
17 | ///
18 | /// Model GT-521F52
19 | ///
20 | /// /// Holds a maximum of 3000 fingerprints.
21 | GT521F52 = 3000,
22 | }
23 |
24 | ///
25 | /// Response codes.
26 | ///
27 | public enum ResponseCode : ushort
28 | {
29 | ///
30 | /// Acknowledge
31 | ///
32 | Ack = 0x30,
33 |
34 | ///
35 | /// Non-acknowledge
36 | ///
37 | Nack = 0x31,
38 | }
39 |
40 | ///
41 | /// Command codes.
42 | ///
43 | public enum CommandCode : ushort
44 | {
45 | ///
46 | /// Initialization
47 | ///
48 | Open = 0x01,
49 |
50 | ///
51 | /// Termination
52 | ///
53 | Close = 0x02,
54 |
55 | ///
56 | /// Check if the connected USB device is valid
57 | ///
58 | UsbInternalCheck = 0x03,
59 |
60 | ///
61 | /// Change UART baud rate
62 | ///
63 | ChangeBaudRate = 0x04,
64 |
65 | ///
66 | /// Control CMOS led
67 | ///
68 | CmosLed = 0x12,
69 |
70 | ///
71 | /// Get enrolled fingerprint count
72 | ///
73 | GetEnrollCount = 0x20,
74 |
75 | ///
76 | /// Check whether a specified ID is already enrolled
77 | ///
78 | CheckEnrolled = 0x21,
79 |
80 | ///
81 | /// Start an enrollment
82 | ///
83 | EnrollStart = 0x22,
84 |
85 | ///
86 | /// Make first template for an enrollment
87 | ///
88 | Enroll1 = 0x23,
89 |
90 | ///
91 | /// Make second template for an enrollment
92 | ///
93 | Enroll2 = 0x24,
94 |
95 | ///
96 | /// Make third template for and enrollment,
97 | /// merge the three templates into one template,
98 | /// save marged template to the database
99 | ///
100 | Enroll3 = 0x25,
101 |
102 | ///
103 | /// Check if a finger is palced on the sensor
104 | ///
105 | IsPressFinger = 0x26,
106 |
107 | ///
108 | /// Delete a fingerprint with the specified ID
109 | ///
110 | DeleteID = 0x40,
111 |
112 | ///
113 | /// Delete all fingerprints from the device database
114 | ///
115 | DeleteAll = 0x41,
116 |
117 | ///
118 | /// 1:1 Verification on the capture fingerprint image with the specified ID
119 | ///
120 | Verify = 0x50,
121 |
122 | ///
123 | /// 1:N Identification on the capture fingerprint image with the device database
124 | ///
125 | Identify = 0x51,
126 |
127 | ///
128 | /// 1:1 Verification of a fingerprint template with the specified ID
129 | ///
130 | VerifyTemplate = 0x52,
131 |
132 | ///
133 | /// 1:N Identification of a fingerprint template with the device database
134 | ///
135 | IdentifyTemplate = 0x53,
136 |
137 | ///
138 | /// Capture a fingerprint image (256 x 256) from the sensor
139 | ///
140 | CaptureFinger = 0x60,
141 |
142 | ///
143 | /// Make a template for transmission
144 | ///
145 | MakeTemplate = 0x61,
146 |
147 | ///
148 | /// Download the capture fingerprint image (256 x 256)
149 | ///
150 | GetImage = 0x62,
151 |
152 | ///
153 | /// Capture and download a raw fingerprint image (320 x 240)
154 | ///
155 | GetRawImage = 0x63,
156 |
157 | ///
158 | /// Download the template of the specified ID
159 | ///
160 | GetTemplate = 0x70,
161 |
162 | ///
163 | /// Upload a template for the specified ID
164 | ///
165 | SetTemplate = 0x71,
166 |
167 | ///
168 | /// Start device database download
169 | ///
170 | [Obsolete("Command no longer supported.", true)]
171 | GetDatabaseStart = 0x72,
172 |
173 | ///
174 | /// End devoice database download
175 | ///
176 | [Obsolete("Command no longer supported.", true)]
177 | GetDatabaseEnd = 0x73,
178 |
179 | ///
180 | /// Set security level for a specified ID
181 | ///
182 | SetSecurityLevel = 0xF0,
183 |
184 | ///
185 | /// Get security level from a specified ID
186 | ///
187 | GetSecurityLevel = 0xF1,
188 |
189 | ///
190 | /// Identify of the capture fingerprint image with the specified template
191 | ///
192 | IdentifyTemplate2 = 0xF4,
193 |
194 | ///
195 | /// Enter standby mode (Low power mode)
196 | ///
197 | EnterStandbyMode = 0xF9,
198 | }
199 |
200 | ///
201 | /// Error codes.
202 | ///
203 | ///
204 | /// Error codes 0 to 2999 indicate the ID for a duplicate fingerprint while enrollment or setting template.
205 | ///
206 | public enum ErrorCode
207 | {
208 | ///
209 | /// Capture timeout
210 | ///
211 | [Obsolete]
212 | Timeout = 0x1001,
213 |
214 | ///
215 | /// Invalid serial baudrate
216 | ///
217 | [Obsolete]
218 | InvalidBaudrate = 0x1002,
219 |
220 | ///
221 | /// The specified ID is not between the valid range
222 | ///
223 | InvalidPos = 0x1003,
224 |
225 | ///
226 | /// The specified ID is not used
227 | ///
228 | NotUsed = 0x1004,
229 |
230 | ///
231 | /// The specified ID is already used
232 | ///
233 | AlreadyUse = 0x1005,
234 |
235 | ///
236 | /// Communication error
237 | ///
238 | CommErr = 0x1006,
239 |
240 | ///
241 | /// 1:1 verification error
242 | ///
243 | VerifyFail = 0x1007,
244 |
245 | ///
246 | /// 1:N identification error
247 | ///
248 | IdentifyFail = 0x1008,
249 |
250 | ///
251 | /// The device database is full
252 | ///
253 | DbFull = 0x1009,
254 |
255 | ///
256 | /// The device database is empty
257 | ///
258 | DbEmpty = 0x100A,
259 |
260 | ///
261 | /// Invalid order of the enrollment
262 | ///
263 | [Obsolete]
264 | TurnErr = 0x100B,
265 |
266 | ///
267 | /// Too bad finger
268 | ///
269 | BadFinger = 0x100C,
270 |
271 | ///
272 | /// Enrollment failure
273 | ///
274 | EnrollFailed = 0x100D,
275 |
276 | ///
277 | /// The specified command is not supported
278 | ///
279 | NotSupported = 0x100E,
280 |
281 | ///
282 | /// Device error
283 | ///
284 | DeviceErr = 0x100F,
285 |
286 | ///
287 | /// The capturing is cancelled
288 | ///
289 | [Obsolete]
290 | CaptureCanceled = 0x1010,
291 |
292 | ///
293 | /// Invalid parameter
294 | ///
295 | InvalidParam = 0x1011,
296 |
297 | ///
298 | /// Finger is not pressed
299 | ///
300 | FingerNotPressed = 0x1012,
301 |
302 | ///
303 | /// Invalid check sum. Possible data corruption.
304 | ///
305 | InvalidCheckSum = 0xFFFD,
306 |
307 | ///
308 | /// Duplicate fingerprint while enrollment or setting template
309 | ///
310 | DuplicateFingerprint = 0xFFFE,
311 |
312 | ///
313 | /// No error
314 | ///
315 | NoError = 0xFFFF,
316 | }
317 |
318 | ///
319 | /// CMOS led status.
320 | ///
321 | public enum LedStatus
322 | {
323 | ///
324 | /// Off
325 | ///
326 | Off,
327 |
328 | ///
329 | /// On
330 | ///
331 | On,
332 | }
333 |
334 | ///
335 | /// Finger actions.
336 | ///
337 | public enum FingerAction
338 | {
339 | ///
340 | /// Place fingerprint in sensor
341 | ///
342 | Place,
343 |
344 | ///
345 | /// Remove fingerprint from sensor
346 | ///
347 | Remove,
348 | }
349 | }
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/Responses.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.Sparkfun.FingerprintModule
2 | {
3 | using System;
4 | using System.Linq;
5 |
6 | ///
7 | /// Basic response packet, with no extra data.
8 | ///
9 | ///
10 | public sealed class BasicResponse : ResponseBase
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// A byte array representing the payload of the response.
16 | public BasicResponse(byte[] payload)
17 | : base(payload)
18 | {
19 | }
20 | }
21 |
22 | ///
23 | /// Initialization (open) response packet.
24 | ///
25 | ///
26 | /// Initialization response packet could have a data packet with device extra info.
27 | public sealed class InitializationResponse : ResponseBase
28 | {
29 | ///
30 | /// The no information label.
31 | ///
32 | public static string NoInfo = "No info available";
33 |
34 | ///
35 | /// Initializes a new instance of the class.
36 | ///
37 | /// A byte array representing the payload of the response.
38 | public InitializationResponse(byte[] payload)
39 | : base(payload)
40 | {
41 | if (!HasDataPacket) return;
42 |
43 | FirmwareVersion = $"{ResponseDataPacket.Data[3]:X2}" +
44 | $"{ResponseDataPacket.Data[2]:X2}" +
45 | $"{ResponseDataPacket.Data[1]:X2}" +
46 | $"{ResponseDataPacket.Data[0]:X2}";
47 | IsoAreaMaxSize = ResponseDataPacket.Data.LittleEndianArrayToInt(4);
48 | RawSerialNumber = new byte[16];
49 | Array.Copy(ResponseDataPacket.Data, 8, RawSerialNumber, 0, RawSerialNumber.Length);
50 |
51 | SerialNumber = string.Join(string.Empty, RawSerialNumber.Take(8).Select(x => x.ToString("X2"))) + "-" +
52 | string.Join(string.Empty, RawSerialNumber.Skip(8).Select(x => x.ToString("X2")));
53 | }
54 |
55 | ///
56 | /// Gets the device firmware version.
57 | ///
58 | public string FirmwareVersion { get; } = NoInfo;
59 |
60 | ///
61 | /// Gets the maximum size of the iso area.
62 | ///
63 | public int IsoAreaMaxSize { get; } = -1;
64 |
65 | ///
66 | /// Gets the device serial number.
67 | ///
68 | public string SerialNumber { get; } = NoInfo;
69 |
70 | ///
71 | /// Gets a value indicating whether this instance is successful.
72 | ///
73 | ///
74 | /// true if this instance is successful; otherwise, false.
75 | ///
76 | public override bool IsSuccessful => base.IsSuccessful && (RawSerialNumber?.Any(x => x != 0) ?? false);
77 |
78 | ///
79 | /// Gets the raw byte array of the device serial number.
80 | ///
81 | private byte[] RawSerialNumber { get; }
82 | }
83 |
84 | ///
85 | /// Fast searching response packet.
86 | ///
87 | ///
88 | /// The device operates as removable CD drive. If another removable CD drive exists in the system, connection time maybe will be long.
89 | /// To prevent this, command is used for fast searching of the device.
90 | ///
91 | public sealed class FastSearchingResponse : ResponseBase
92 | {
93 | ///
94 | /// Initializes a new instance of the class.
95 | ///
96 | /// A byte array representing the payload of the response.
97 | public FastSearchingResponse(byte[] payload)
98 | : base(payload)
99 | {
100 | }
101 |
102 | ///
103 | /// Gets a value indicating whether this instance is successful.
104 | ///
105 | ///
106 | /// true if this instance is successful; otherwise, false.
107 | ///
108 | public override bool IsSuccessful => base.IsSuccessful && Parameter == 0x55;
109 | }
110 |
111 | ///
112 | /// Count enrolled fingerprint response packet.
113 | ///
114 | ///
115 | public sealed class CountEnrolledFingerprintResponse : ResponseBase
116 | {
117 | ///
118 | /// Initializes a new instance of the class.
119 | ///
120 | /// A byte array representing the payload of the response.
121 | public CountEnrolledFingerprintResponse(byte[] payload)
122 | : base(payload)
123 | {
124 | }
125 |
126 | ///
127 | /// Gets the number of fingerprints registered in the device's database.
128 | ///
129 | public int EnrolledFingerprints => IsSuccessful ? Parameter : 0;
130 | }
131 |
132 | ///
133 | /// Check enrollment response packet.
134 | ///
135 | ///
136 | public sealed class CheckEnrollmentResponse : ResponseBase
137 | {
138 | ///
139 | /// Initializes a new instance of the class.
140 | ///
141 | /// A byte array representing the payload of the response.
142 | public CheckEnrollmentResponse(byte[] payload)
143 | : base(payload)
144 | {
145 | }
146 |
147 | ///
148 | /// Gets a value indicating whether an id is enrolled.
149 | ///
150 | ///
151 | /// true if the id is enrolled; otherwise, false.
152 | ///
153 | public bool IsEnrolled => IsSuccessful;
154 | }
155 |
156 | ///
157 | /// Enrollment response packet.
158 | ///
159 | ///
160 | /// Enrollment response packet could have a data packet with the generated fingerprint template.
161 | public sealed class EnrollmentResponse : ResponseBase
162 | {
163 | ///
164 | /// Initializes a new instance of the class.
165 | ///
166 | /// A byte array representing the payload of the response.
167 | public EnrollmentResponse(byte[] payload)
168 | : base(payload)
169 | {
170 | }
171 |
172 | ///
173 | /// Gets the fingerprint template.
174 | ///
175 | public byte[] Template => HasDataPacket ? (byte[])ResponseDataPacket.Data.Clone() : new byte[] { };
176 | }
177 |
178 | ///
179 | /// Check fingerprint pressing response packet.
180 | ///
181 | ///
182 | public sealed class CheckFingerPressingResponse : ResponseBase
183 | {
184 | ///
185 | /// Initializes a new instance of the class.
186 | ///
187 | /// A byte array representing the payload of the response.
188 | public CheckFingerPressingResponse(byte[] payload)
189 | : base(payload)
190 | {
191 | }
192 |
193 | ///
194 | /// Gets a value indicating whether there is a finger pressing in the sensor.
195 | ///
196 | ///
197 | /// true if there is a finger pressing in the sensor; otherwise, false.
198 | ///
199 | public bool IsPressed => Parameter == 0;
200 | }
201 |
202 | ///
203 | /// Match 1 to N response packet. Contains the User Id.
204 | ///
205 | ///
206 | public sealed class MatchOneToNResponse : ResponseBase
207 | {
208 | ///
209 | /// Initializes a new instance of the class.
210 | ///
211 | /// A byte array representing the payload of the response.
212 | public MatchOneToNResponse(byte[] payload)
213 | : base(payload)
214 | {
215 | }
216 |
217 | ///
218 | /// Gets the user identifier.
219 | ///
220 | public int UserId => IsSuccessful ? Parameter : -1;
221 | }
222 |
223 | ///
224 | /// Template response packet.
225 | ///
226 | ///
227 | /// is used for any command that returns a with a fingerprint template .
228 | public sealed class TemplateResponse : ResponseBase
229 | {
230 | ///
231 | /// Initializes a new instance of the class.
232 | ///
233 | /// A byte array representing the payload of the response.
234 | public TemplateResponse(byte[] payload)
235 | : base(payload)
236 | {
237 | }
238 |
239 | ///
240 | /// Gets the fingerprint template.
241 | ///
242 | public byte[] Template => (byte[])ResponseDataPacket.Data.Clone();
243 | }
244 |
245 | ///
246 | /// Get fingerprint image response packet.
247 | ///
248 | ///
249 | public sealed class GetFingerprintImageResponse : ResponseBase
250 | {
251 | ///
252 | /// Initializes a new instance of the class.
253 | ///
254 | /// A byte array representing the payload of the response.
255 | public GetFingerprintImageResponse(byte[] payload)
256 | : base(payload)
257 | {
258 | }
259 |
260 | ///
261 | /// Gets the byte array representing the fingerprint image.
262 | ///
263 | public byte[] Image => (byte[])ResponseDataPacket.Data.Clone();
264 | }
265 |
266 | ///
267 | /// Get raw image response packet.
268 | ///
269 | ///
270 | public sealed class GetRawImageResponse : ResponseBase
271 | {
272 | ///
273 | /// Initializes a new instance of the class.
274 | ///
275 | /// A byte array representing the payload of the response.
276 | public GetRawImageResponse(byte[] payload)
277 | : base(payload)
278 | {
279 | }
280 |
281 | ///
282 | /// Gets the byte array representing the fingerprint raw image.
283 | ///
284 | public byte[] Image => (byte[])ResponseDataPacket.Data.Clone();
285 | }
286 |
287 | ///
288 | /// Get security level response packet.
289 | ///
290 | ///
291 | public sealed class GetSecurityLevelResponse : ResponseBase
292 | {
293 | ///
294 | /// Initializes a new instance of the class.
295 | ///
296 | /// A byte array representing the payload of the response.
297 | public GetSecurityLevelResponse(byte[] payload)
298 | : base(payload)
299 | {
300 | }
301 |
302 | ///
303 | /// Gets the device's current security level.
304 | ///
305 | public int SecurityLevel => IsSuccessful ? Parameter : -1;
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/src/Unosquare.Sparkfun.FingerprintModule/FingerprintReader.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.Sparkfun.FingerprintModule
2 | {
3 | using SerialPort;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | ///
11 | ///
12 | /// The main class representing the Sparkfun fingerprint reader module GT521Fxx.
13 | /// Reference: https://cdn.sparkfun.com/assets/learn_tutorials/7/2/3/GT-521F52_Programming_guide_V10_20161001.pdf
14 | /// WIKI: https://learn.sparkfun.com/tutorials/fingerprint-scanner-gt-521fxx-hookup-guide.
15 | ///
16 | ///
17 | public class FingerprintReader : IDisposable
18 | {
19 | private const int InitialBaudRate = 9600;
20 | private const int TargetBaudRate = 115200;
21 |
22 | private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(1);
23 | private static readonly TimeSpan FingerActionTimeout = TimeSpan.FromSeconds(2);
24 | private static readonly TimeSpan GetRawImageTimeout = TimeSpan.FromSeconds(3);
25 | private static readonly TimeSpan EnrollTimeout = TimeSpan.FromSeconds(5);
26 | private static readonly TimeSpan GetImageTimeout = TimeSpan.FromSeconds(7);
27 |
28 | private ISerialPort _serialPort;
29 | private ManualResetEventSlim _serialPortDone = new ManualResetEventSlim(true);
30 | private InitializationResponse _deviceInfo;
31 | private bool _disposedValue; // To detect redundant calls
32 |
33 | ///
34 | /// Initializes a new instance of the class.
35 | ///
36 | /// The fingerprint reader model.
37 | /// The model determines the device capacity.
38 | public FingerprintReader(FingerprintReaderModel model)
39 | {
40 | FingerprintCapacity = (int) model;
41 | MaxValidId = FingerprintCapacity - 1;
42 | }
43 |
44 | #region Properties
45 |
46 | ///
47 | /// Gets the fingerprint capacity.
48 | ///
49 | public int FingerprintCapacity { get; }
50 |
51 | ///
52 | /// Gets the maximum valid identifier.
53 | ///
54 | ///
55 | /// The maximum valid identifier.
56 | ///
57 | public int MaxValidId { get; }
58 |
59 | ///
60 | /// Gets the device firmware version.
61 | ///
62 | public string FirmwareVersion => _deviceInfo?.FirmwareVersion ?? InitializationResponse.NoInfo;
63 |
64 | ///
65 | /// Gets the device serial number.
66 | ///
67 | public string SerialNumber => _deviceInfo?.SerialNumber ?? InitializationResponse.NoInfo;
68 |
69 | ///
70 | /// Gets the maximum size of the iso area.
71 | ///
72 | public int IsoAreaMaxSize => _deviceInfo?.IsoAreaMaxSize ?? -1;
73 |
74 | #endregion
75 |
76 | #region Open-Close
77 |
78 | ///
79 | /// Gets an array of serial port names for the current computer.
80 | ///
81 | /// This method is just a shortcut for Microsoft and RJCP libraries,
82 | /// you may use your SerialPort library to enumerate the available ports.
83 | ///
84 | /// An array of serial port names for the current computer.
85 | public static string[] GetPortNames() =>
86 | #if NET452
87 | MsSerialPort.GetPortNames();
88 | #else
89 | RjcpSerialPort.GetPortNames();
90 | #endif
91 |
92 | ///
93 | /// Opens and initialize the fingerprint device at the specified port name.
94 | ///
95 | /// The serial port.
96 | /// An instance of .
97 | /// A task that represents the asynchronous open operation.
98 | /// Device is already open. Call the close method first.
99 | public Task OpenAsync(ISerialPort serialPort, CancellationToken ct = default)
100 | {
101 | if (_serialPort != null)
102 | throw new InvalidOperationException("Device is already open. Call the close method first.");
103 |
104 | return OpenAsync(serialPort, InitialBaudRate, true, ct);
105 | }
106 |
107 | ///
108 | /// Opens and initialize the fingerprint device at the specified port name.
109 | ///
110 | /// Name of the port.
111 | /// An instance of .
112 | /// A task that represents the asynchronous open operation.
113 | /// Device is already open. Call the close method first.
114 | public Task OpenAsync(string portName, CancellationToken ct = default)
115 | {
116 | if (_serialPort != null)
117 | throw new InvalidOperationException("Device is already open. Call the close method first.");
118 |
119 | return OpenAsync(
120 | #if NET452
121 | new MsSerialPort(portName, InitialBaudRate),
122 | #else
123 | new RjcpSerialPort(portName, InitialBaudRate),
124 | #endif
125 | InitialBaudRate,
126 | false,
127 | ct);
128 | }
129 |
130 | ///
131 | /// Closes the fingerprint device if open.
132 | ///
133 | /// An instance of .
134 | /// A task that represents the asynchronous close operation.
135 | public async Task CloseAsync(CancellationToken ct = default)
136 | {
137 | if (_serialPort == null || !_serialPort.IsOpen)
138 | return;
139 |
140 | try
141 | {
142 | _deviceInfo = null;
143 | await TurnLedOffAsync(ct).ConfigureAwait(false);
144 | await CloseDeviceAsync(ct).ConfigureAwait(false);
145 | _serialPort.Close();
146 | await Task.Delay(100, ct).ConfigureAwait(false);
147 | }
148 | finally
149 | {
150 | _serialPort.Dispose();
151 | _serialPort = null;
152 | }
153 | }
154 |
155 | #endregion
156 |
157 | #region Commands
158 |
159 | ///
160 | /// Sets the let status asynchronous.
161 | ///
162 | /// The status.
163 | /// An instance of .
164 | /// A task that represents the asynchronous set led status operation.
165 | /// The result of the task contains an instance of .
166 | ///
167 | public Task SetLetStatusAsync(LedStatus status, CancellationToken ct = default) =>
168 | status == LedStatus.On ? TurnLedOnAsync(ct) : TurnLedOffAsync(ct);
169 |
170 | ///
171 | /// Turns the led on asynchronous.
172 | ///
173 | /// An instance of .
174 | /// A task that represents the asynchronous turn led on operation.
175 | /// The result of the task contains an instance of .
176 | ///
177 | public Task TurnLedOnAsync(CancellationToken ct = default) =>
178 | GetResponseAsync(Command.Create(CommandCode.CmosLed, 1), ct);
179 |
180 | ///
181 | /// Turns the led off asynchronous.
182 | ///
183 | /// An instance of .
184 | /// A task that represents the asynchronous turn led off operation.
185 | /// The result of the task contains an instance of .
186 | ///
187 | public Task TurnLedOffAsync(CancellationToken ct = default) =>
188 | GetResponseAsync(Command.Create(CommandCode.CmosLed), ct);
189 |
190 | ///
191 | /// Fasts device searching asynchronous.
192 | ///
193 | /// An instance of .
194 | /// A task that represents the asynchronous set fast device searching operation.
195 | /// The result of the task contains an instance of .
196 | ///
197 | /// The device operates as removable CD drive. If another removable CD drive exists in the system, connection time maybe will be long.
198 | /// To prevent this, command is used for fast searching of the device.
199 | ///
200 | public Task FastDeviceSearching(CancellationToken ct = default) =>
201 | GetResponseAsync(Command.Create(CommandCode.UsbInternalCheck), ct);
202 |
203 | ///
204 | /// Counts the enrolled fingerprint asynchronous.
205 | ///
206 | /// An instance of .
207 | /// A task that represents the asynchronous count enrolled fingerprint operation.
208 | /// The result of the task contains an instance of .
209 | ///
210 | public Task CountEnrolledFingerprintAsync(CancellationToken ct = default) =>
211 | GetResponseAsync(Command.Create(CommandCode.GetEnrollCount), ct);
212 |
213 | ///
214 | /// Checks the enrollment status asynchronous.
215 | ///
216 | /// The user identifier to check.
217 | /// An instance of .
218 | /// A task that represents the asynchronous check enrolled status operation.
219 | /// The result of the task contains an instance of .
220 | ///
221 | public Task CheckEnrollmentStatusAsync(int userId, CancellationToken ct = default) =>
222 | GetResponseAsync(Command.Create(CommandCode.CheckEnrolled, userId), ct);
223 |
224 | ///
225 | /// Enrolls a new user asynchronous.
226 | ///
227 | /// The iteration.
228 | /// The user identifier.
229 | /// An instance of .
230 | /// A task that represents the asynchronous enroll user operation.
231 | /// The result of the task contains an instance of .
232 | ///
233 | ///
234 | /// iteration
235 | /// or
236 | /// userId.
237 | ///
238 | public async Task EnrollUserAsync(int iteration, int userId, CancellationToken ct = default)
239 | {
240 | if (iteration <= 0 || iteration > 3)
241 | throw new ArgumentOutOfRangeException($"{nameof(iteration)} must be a number between 1 and 3");
242 |
243 | if (userId < -1 || userId > MaxValidId)
244 | throw new ArgumentOutOfRangeException(
245 | $"{nameof(userId)} must be a number between -1 and {MaxValidId}.");
246 |
247 | if (iteration == 1)
248 | {
249 | // Start enrollment
250 | var startResult =
251 | await GetResponseAsync(Command.Create(CommandCode.EnrollStart, userId), ct)
252 | .ConfigureAwait(false);
253 |
254 | if (!startResult.IsSuccessful)
255 | return ResponseBase.GetUnsuccessfulResponse(startResult.ErrorCode);
256 | }
257 |
258 | return await EnrollAsync(iteration, userId, ct).ConfigureAwait(false);
259 | }
260 |
261 | ///
262 | /// Waits a finger action asynchronous.
263 | ///
264 | /// The action to wait for.
265 | /// An instance of .
266 | /// A task that represents the asynchronous wait finger action operation.
267 | /// The result of the task contains a indicating if the action was performed.
268 | /// true if the action was performed; otherwise, false.
269 | ///
270 | public Task WaitFingerActionAsync(FingerAction action, CancellationToken ct = default) =>
271 | WaitFingerActionAsync(action, FingerActionTimeout, ct);
272 |
273 | ///
274 | /// Waits a finger action for a specified time period asynchronous.
275 | ///
276 | /// The action to wait for.
277 | /// The timeout.
278 | /// An instance of .
279 | /// A task that represents the asynchronous wait finger action operation.
280 | /// The result of the task contains a indicating if the action was performed.
281 | /// true if the action was performed; otherwise, false.
282 | ///
283 | public async Task WaitFingerActionAsync(
284 | FingerAction action,
285 | TimeSpan timeout,
286 | CancellationToken ct = default)
287 | {
288 | var startTime = DateTime.Now;
289 | while (true)
290 | {
291 | var result = await CheckFingerPressingStatusAsync(ct).ConfigureAwait(false);
292 |
293 | if (!result.IsSuccessful)
294 | return false;
295 |
296 | if ((action == FingerAction.Place && result.IsPressed) ||
297 | (action == FingerAction.Remove && !result.IsPressed))
298 | return true;
299 |
300 | if (DateTime.Now.Subtract(startTime) > timeout)
301 | return false;
302 |
303 | await Task.Delay(10, ct).ConfigureAwait(false);
304 | }
305 | }
306 |
307 | ///
308 | /// Checks the finger pressing status asynchronous.
309 | ///
310 | /// An instance of .
311 | /// A task that represents the asynchronous check finger pressing status operation.
312 | /// The result of the task contains an instance of .
313 | ///
314 | public Task CheckFingerPressingStatusAsync(CancellationToken ct = default) =>
315 | GetResponseAsync(Command.Create(CommandCode.IsPressFinger), ct);
316 |
317 | ///
318 | /// Deletes all users from device's database asynchronous.
319 | ///
320 | /// An instance of .
321 | /// A task that represents the asynchronous delete all users operation.
322 | /// The result of the task contains an instance of .
323 | ///
324 | public Task DeleteAllUsersAsync(CancellationToken ct = default) =>
325 | GetResponseAsync(Command.Create(CommandCode.DeleteAll), ct);
326 |
327 | ///
328 | /// Deletes a specific user asynchronous.
329 | ///
330 | /// The user identifier.
331 | /// An instance of .
332 | /// A task that represents the asynchronous delete user operation.
333 | /// The result of the task contains an instance of .
334 | ///
335 | /// userId.
336 | public Task DeleteUserAsync(int userId, CancellationToken ct = default)
337 | {
338 | if (userId < 0 || userId > MaxValidId)
339 | throw new ArgumentOutOfRangeException($"{nameof(userId)} must be a number between 0 and {MaxValidId}.");
340 |
341 | return GetResponseAsync(Command.Create(CommandCode.DeleteID, userId), ct);
342 | }
343 |
344 | ///
345 | /// Match 1:1 asynchronous. Acquires an image from the device and verify if it matches the supplied user id.
346 | ///
347 | /// The user identifier.
348 | /// An instance of .
349 | /// A task that represents the asynchronous match one to one operation.
350 | /// The result of the task contains an instance of .
351 | ///
352 | /// userId.
353 | public async Task MatchOneToOneAsync(int userId, CancellationToken ct = default)
354 | {
355 | if (userId < 0 || userId > MaxValidId)
356 | throw new ArgumentOutOfRangeException($"{nameof(userId)} must be a number between 0 and {MaxValidId}.");
357 |
358 | var captureResult = await CaptureFingerprintPatternAsync(ct).ConfigureAwait(false);
359 | if (!captureResult.IsSuccessful)
360 | return captureResult;
361 |
362 | return await GetResponseAsync(Command.Create(CommandCode.Verify, userId), ct)
363 | .ConfigureAwait(false);
364 | }
365 |
366 | ///
367 | /// Match 1:1 asynchronous. Verify if a provided fingerprint template matches the supplied user id.
368 | ///
369 | /// The user identifier.
370 | /// The fingerprint template.
371 | /// An instance of .
372 | /// A task that represents the asynchronous match one to one operation.
373 | /// The result of the task contains an instance of .
374 | ///
375 | /// userId.
376 | public Task MatchOneToOneAsync(int userId, byte[] template, CancellationToken ct = default)
377 | {
378 | if (userId < 0 || userId > MaxValidId)
379 | throw new ArgumentOutOfRangeException($"{nameof(userId)} must be a number between 0 and {MaxValidId}.");
380 |
381 | return GetResponseAsync(Command.Create(CommandCode.VerifyTemplate, userId, template), ct);
382 | }
383 |
384 | ///
385 | /// Match 1:N asynchronous. Acquires an image from the device and identifies the user id it belongs to.
386 | ///
387 | /// An instance of .
388 | /// A task that represents the asynchronous match one to n operation.
389 | /// The result of the task contains an instance of .
390 | ///
391 | public async Task MatchOneToN(CancellationToken ct = default)
392 | {
393 | var captureResult = await CaptureFingerprintPatternAsync(ct).ConfigureAwait(false);
394 | if (!captureResult.IsSuccessful)
395 | return captureResult;
396 |
397 | return await GetResponseAsync(Command.Create(CommandCode.Identify), ct)
398 | .ConfigureAwait(false);
399 | }
400 |
401 | ///
402 | /// Match 1:N asynchronous. Identifies the user id whom a provided fingerprint template belongs to.
403 | ///
404 | /// The fingerprint template.
405 | /// An instance of .
406 | /// A task that represents the asynchronous match one to n operation.
407 | /// The result of the task contains an instance of .
408 | ///
409 | public Task MatchOneToN(byte[] template, CancellationToken ct = default) =>
410 | GetResponseAsync(Command.Create(CommandCode.IdentifyTemplate, 0, template), ct);
411 |
412 | ///
413 | /// Match 1:N asynchronous. Identifies the user id whom a provided fingerprint template belongs to.
414 | ///
415 | /// The special fingerprint template.
416 | /// An instance of .
417 | /// A task that represents the asynchronous match one to n operation.
418 | /// The result of the task contains an instance of .
419 | ///
420 | /// uses a special fingerprint template with 2 extra bytes at the beginning of the byte array.
421 | public Task MatchOneToN2(byte[] template, CancellationToken ct = default) =>
422 | GetResponseAsync(Command.Create(CommandCode.IdentifyTemplate2, 500, template), ct);
423 |
424 | ///
425 | /// Makes a fingerprint template asynchronous. This template must be used only for transmission and not for user enrollment.
426 | ///
427 | /// An instance of .
428 | /// A task that represents the asynchronous make template operation.
429 | /// The result of the task contains an instance of .
430 | ///
431 | public async Task MakeTemplateAsync(CancellationToken ct = default)
432 | {
433 | var captureResult = await CaptureFingerprintPatternAsync(ct).ConfigureAwait(false);
434 | if (!captureResult.IsSuccessful)
435 | return captureResult;
436 |
437 | return await GetResponseAsync(Command.Create(CommandCode.MakeTemplate), ct)
438 | .ConfigureAwait(false);
439 | }
440 |
441 | ///
442 | /// Gets a fingerprint image asynchronous.
443 | ///
444 | /// An instance of .
445 | /// A task that represents the asynchronous get image operation.
446 | /// The result of the task contains an instance of .
447 | ///
448 | public async Task GetImageAsync(CancellationToken ct = default)
449 | {
450 | var captureResult =
451 | await CaptureFingerprintPatternAsync(ct).ConfigureAwait(false);
452 | if (!captureResult.IsSuccessful)
453 | return captureResult;
454 |
455 | return await GetResponseAsync(Command.Create(CommandCode.GetImage),
456 | GetImageTimeout, ct).ConfigureAwait(false);
457 | }
458 |
459 | ///
460 | /// Gets a raw image from the device asynchronous.
461 | ///
462 | /// An instance of .
463 | /// A task that represents the asynchronous get raw image operation.
464 | /// The result of the task contains an instance of .
465 | ///
466 | public async Task GetRawImageAsync(CancellationToken ct = default)
467 | {
468 | var captureResult = await CaptureFingerprintPatternAsync(ct).ConfigureAwait(false);
469 | if (!captureResult.IsSuccessful)
470 | return captureResult;
471 |
472 | return await GetResponseAsync(Command.Create(CommandCode.GetRawImage),
473 | GetRawImageTimeout, ct).ConfigureAwait(false);
474 | }
475 |
476 | ///
477 | /// Gets a fingerprint template asynchronous.
478 | ///
479 | /// The user identifier.
480 | /// An instance of .
481 | /// A task that represents the asynchronous get template operation.
482 | /// The result of the task contains an instance of .
483 | ///
484 | /// userId.
485 | public Task GetTemplateAsync(int userId, CancellationToken ct = default)
486 | {
487 | if (userId < 0 || userId > MaxValidId)
488 | throw new ArgumentOutOfRangeException($"{nameof(userId)} must be a number between 0 and {MaxValidId}.");
489 |
490 | return GetResponseAsync(Command.Create(CommandCode.GetTemplate, userId), ct);
491 | }
492 |
493 | ///
494 | /// Sets a fingerprint template asynchronous.
495 | ///
496 | /// The user identifier.
497 | /// The fingerprint template.
498 | /// An instance of .
499 | /// A task that represents the asynchronous set template operation.
500 | /// The result of the task contains an instance of .
501 | ///
502 | /// userId.
503 | public Task SetTemplateAsync(int userId, byte[] template, CancellationToken ct = default)
504 | {
505 | if (userId < 0 || userId > MaxValidId)
506 | throw new ArgumentOutOfRangeException($"{nameof(userId)} must be a number between 0 and {MaxValidId}.");
507 |
508 | return GetResponseAsync(Command.Create(CommandCode.SetTemplate, userId, template), ct);
509 | }
510 |
511 | ///
512 | /// Sets device to stand by mode (low power mode).
513 | ///
514 | /// An instance of .
515 | /// A task that represents the asynchronous enter standby operation.
516 | /// The result of the task contains an instance of .
517 | ///
518 | public Task EnterStandByMode(CancellationToken ct = default)
519 | {
520 | // TODO: Implement to wake up
521 | return GetResponseAsync(Command.Create(CommandCode.EnterStandbyMode), ct);
522 | }
523 |
524 | ///
525 | /// Sets the device's security level asynchronous. 1 is the lowest security level, 5 is the highest security level.
526 | ///
527 | /// The security level.
528 | /// An instance of .
529 | /// A task that represents the asynchronous set security level operation.
530 | /// The result of the task contains an instance of .
531 | ///
532 | /// level.
533 | public Task SetSecurityLevelAsync(int level, CancellationToken ct = default)
534 | {
535 | if (level < 1 || level > 5)
536 | throw new ArgumentOutOfRangeException($"{nameof(level)} must be a number between 1 and 5.");
537 |
538 | return GetResponseAsync(Command.Create(CommandCode.SetSecurityLevel, level), ct);
539 | }
540 |
541 | ///
542 | /// Gets the device's security level asynchronous. 1 is the lowest security level, 5 is the highest security level.
543 | ///
544 | /// An instance of .
545 | /// A task that represents the asynchronous get security level operation.
546 | /// The result of the task contains an instance of .
547 | ///
548 | public Task GetSecurityLevelAsync(CancellationToken ct = default) =>
549 | GetResponseAsync(Command.Create(CommandCode.GetSecurityLevel), ct);
550 |
551 | #endregion
552 |
553 | #region Private Functions
554 |
555 | ///
556 | /// Opens and initialize the fingerprint device at the specified port name with the specified baud rate.
557 | ///
558 | /// The serial port.
559 | /// The baud rate.
560 | /// if set to true [skip baud rate].
561 | /// An instance of .
562 | ///
563 | /// A task that represents the asynchronous open operation.
564 | ///
565 | /// The device could not be initialized.
566 | private async Task OpenAsync(ISerialPort serialPort, int baudRate, bool skipBaudRate, CancellationToken ct)
567 | {
568 | _serialPort = serialPort;
569 |
570 | _serialPort.Open();
571 | await Task.Delay(100, ct).ConfigureAwait(false);
572 |
573 | if (!skipBaudRate && baudRate != TargetBaudRate)
574 | {
575 | // Change baud rate to target baud rate for better performance
576 | await SetBaudRateAsync(TargetBaudRate, ct).ConfigureAwait(false);
577 | }
578 | else
579 | {
580 | _deviceInfo = await OpenDeviceAsync(ct).ConfigureAwait(false);
581 | if (!_deviceInfo.IsSuccessful)
582 | throw new Exception("The device could not be initialized.");
583 |
584 | await TurnLedOnAsync(ct).ConfigureAwait(false);
585 | }
586 | }
587 |
588 | ///
589 | /// Opens and initializes the device asynchronous.
590 | ///
591 | /// An instance of .
592 | /// A task that represents the asynchronous open device operation.
593 | /// The result of the task contains an instance of .
594 | ///
595 | private Task OpenDeviceAsync(CancellationToken ct) =>
596 | GetResponseAsync(Command.Create(CommandCode.Open, 1), ct);
597 |
598 | ///
599 | /// Closes the device asynchronous.
600 | ///
601 | /// An instance of .
602 | /// A task that represents the asynchronous close device operation.
603 | /// The result of the task contains an instance of .
604 | ///
605 | private Task CloseDeviceAsync(CancellationToken ct) =>
606 | GetResponseAsync(Command.Create(CommandCode.Close), ct);
607 |
608 | ///
609 | /// Sets the baud rate asynchronous.
610 | /// This closes and re-opens the device.
611 | ///
612 | /// The baud rate.
613 | /// An instance of .
614 | /// A task that represents the asynchronous set baud rate operation.
615 | /// The result of the task contains an instance of .
616 | ///
617 | private async Task SetBaudRateAsync(int baudRate, CancellationToken ct)
618 | {
619 | var response =
620 | await GetResponseAsync(Command.Create(CommandCode.ChangeBaudRate, baudRate), ct)
621 | .ConfigureAwait(false);
622 |
623 | // It is possible that we don't have a response when changing baud rate
624 | // because we are still listening with the previous config.
625 | // If this happens we'll have a communication error response (CommErr)
626 | if (response.IsSuccessful || response.ErrorCode == ErrorCode.CommErr)
627 | {
628 | var portName = _serialPort.PortName;
629 | await CloseAsync(ct).ConfigureAwait(false);
630 | await OpenAsync(
631 | #if NET452
632 | new MsSerialPort(portName, baudRate),
633 | #else
634 | new RjcpSerialPort(portName, baudRate),
635 | #endif
636 | baudRate,
637 | false,
638 | ct).ConfigureAwait(false);
639 | }
640 |
641 | return response;
642 | }
643 |
644 | ///
645 | /// Enroll stage asynchronous.
646 | ///
647 | /// The iteration.
648 | /// The user identifier.
649 | /// An instance of .
650 | /// A task that represents the asynchronous enroll operation.
651 | /// The result of the task contains an instance of .
652 | ///
653 | private async Task EnrollAsync(int iteration, int userId, CancellationToken ct)
654 | {
655 | var enrollmentFingerActionTimeOut = TimeSpan.FromSeconds(10);
656 |
657 | var captureResult =
658 | await CaptureFingerprintPatternAsync(enrollmentFingerActionTimeOut, ct)
659 | .ConfigureAwait(false);
660 | if (!captureResult.IsSuccessful)
661 | return captureResult;
662 |
663 | var cmd = iteration == 1 ? CommandCode.Enroll1 :
664 | iteration == 2 ? CommandCode.Enroll2 :
665 | CommandCode.Enroll3;
666 |
667 | return await GetResponseAsync(Command.Create(cmd, userId), EnrollTimeout, ct)
668 | .ConfigureAwait(false);
669 | }
670 |
671 | ///
672 | /// Capture fingerprint pattern asynchronous. A special pattern for capture a fingerprint from the device.
673 | ///
674 | /// An instance of .
675 | /// A final response type.
676 | /// A task that represents the asynchronous capture fingerprint pattern operation.
677 | /// The result of the task contains an instance of a response of type T.
678 | ///
679 | private Task CaptureFingerprintPatternAsync(CancellationToken ct)
680 | where T : ResponseBase => CaptureFingerprintPatternAsync(FingerActionTimeout, ct);
681 |
682 | ///
683 | /// Capture fingerprint pattern asynchronous. A special pattern for capture a fingerprint from the device.
684 | ///
685 | /// A final response type.
686 | /// The finger action timeout.
687 | /// An instance of .
688 | /// A task that represents the asynchronous capture fingerprint pattern operation.
689 | /// The result of the task contains an instance of a response of type T.
690 | ///
691 | private async Task CaptureFingerprintPatternAsync(TimeSpan fingerActionTimeout, CancellationToken ct)
692 | where T : ResponseBase
693 | {
694 | var actionPerformed = await WaitFingerActionAsync(FingerAction.Place, fingerActionTimeout, ct)
695 | .ConfigureAwait(false);
696 |
697 | if (!actionPerformed)
698 | return ResponseBase.GetUnsuccessfulResponse(ErrorCode.FingerNotPressed);
699 |
700 | var captureResult = await CaptureFingerprintAsync(ct).ConfigureAwait(false);
701 |
702 | return typeof(T) == captureResult.GetType()
703 | ? captureResult as T
704 | : Activator.CreateInstance(typeof(T), captureResult.Payload) as T;
705 | }
706 |
707 | ///
708 | /// Captures a fingerprint from the device asynchronous.
709 | ///
710 | /// An instance of .
711 | /// A task that represents the asynchronous capture fingerprint operation.
712 | /// The result of the task contains an instance of .
713 | ///
714 | private Task CaptureFingerprintAsync(CancellationToken ct) =>
715 | GetResponseAsync(Command.Create(CommandCode.CaptureFinger), ct);
716 |
717 | #endregion
718 |
719 | #region Write-Read
720 |
721 | ///
722 | /// Sends a command to the device and gets the device's response asynchronous.
723 | ///
724 | /// A final response type.
725 | /// The command object to send.
726 | /// An instance of .
727 | /// A task that represents the asynchronous get response operation.
728 | /// The result of the task contains an instance of a response type T.
729 | ///
730 | private Task GetResponseAsync(Command command, CancellationToken ct)
731 | where T : ResponseBase => GetResponseAsync(command, DefaultTimeout, ct);
732 |
733 | ///
734 | /// Sends a command to the device and gets the device's response asynchronous.
735 | ///
736 | /// A final response type.
737 | /// The command object to send.
738 | /// The response timeout.
739 | /// An instance of .
740 | /// A task that represents the asynchronous get response operation.
741 | /// The result of the task contains an instance of a response type T.
742 | ///
743 | private async Task GetResponseAsync(Command command, TimeSpan responseTimeout, CancellationToken ct)
744 | where T : ResponseBase
745 | {
746 | var expectedResponseLength = PacketBase.BasePacketLength;
747 | if (ResponseBase.ResponseDataLength.ContainsKey(command.CommandCode))
748 | {
749 | expectedResponseLength += 6 + ResponseBase.ResponseDataLength[command.CommandCode];
750 |
751 | // Special cases
752 | if ((command.CommandCode == CommandCode.Open && command.Parameter == 0) ||
753 | (command.CommandCode == CommandCode.Enroll3 && command.Parameter != -1))
754 | expectedResponseLength = PacketBase.BasePacketLength;
755 | }
756 |
757 | _serialPortDone.Wait(ct);
758 | _serialPortDone.Reset();
759 | try
760 | {
761 | var responsePkt =
762 | await GetInternalResponseAsync(command.Payload, expectedResponseLength, responseTimeout, ct)
763 | .ConfigureAwait(false);
764 | if (command.HasDataPacket && responsePkt?.IsSuccessful == true)
765 | {
766 | responsePkt =
767 | await GetInternalResponseAsync(command.DataPacket.Payload, expectedResponseLength,
768 | responseTimeout, ct).ConfigureAwait(false);
769 | }
770 |
771 | return responsePkt;
772 | }
773 | finally
774 | {
775 | _serialPortDone.Set();
776 | }
777 | }
778 |
779 | ///
780 | /// Sends the command payload to the device and gets the device's response asynchronous.
781 | ///
782 | /// A final response type.
783 | /// The payload.
784 | /// Expected length of the response.
785 | /// The response timeout.
786 | /// The ct.
787 | /// A task that represents the asynchronous get response operation.
788 | /// The result of the task contains an instance of a response type T.
789 | private async Task GetInternalResponseAsync(
790 | byte[] payload,
791 | int expectedResponseLength,
792 | TimeSpan responseTimeout,
793 | CancellationToken ct)
794 | where T : ResponseBase
795 | {
796 | await WriteAsync(payload, ct).ConfigureAwait(false);
797 | var response = await ReadAsync(expectedResponseLength, responseTimeout, CancellationToken.None)
798 | .ConfigureAwait(false);
799 | if (response == null || response.Length == 0)
800 | return ResponseBase.GetUnsuccessfulResponse(ErrorCode.CommErr);
801 |
802 | return Activator.CreateInstance(typeof(T), response) as T;
803 | }
804 |
805 | ///
806 | /// Writes data to the serial port asynchronous.
807 | ///
808 | /// The payload.
809 | /// An instance of .
810 | /// A task that represents the asynchronous write operation.
811 | private async Task WriteAsync(byte[] payload, CancellationToken ct)
812 | {
813 | if (_serialPort == null || _serialPort.IsOpen == false)
814 | {
815 | throw new InvalidOperationException(
816 | $"Call the {nameof(OpenAsync)} method before attempting communication");
817 | }
818 |
819 | await _serialPort.WriteAsync(payload, 0, payload.Length, ct).ConfigureAwait(false);
820 | await _serialPort.FlushAsync(ct).ConfigureAwait(false);
821 | }
822 |
823 | ///
824 | /// Reads data from the serial port asynchronous.
825 | ///
826 | /// Expected length of the response.
827 | /// The timeout.
828 | /// An instance of .
829 | /// A task that represents the asynchronous read operation.
830 | private async Task ReadAsync(int expectedResponseLength, TimeSpan timeout, CancellationToken ct)
831 | {
832 | if (_serialPort == null || _serialPort.IsOpen == false)
833 | {
834 | throw new InvalidOperationException(
835 | $"Call the {nameof(OpenAsync)} method before attempting communication");
836 | }
837 |
838 | var data = new List();
839 | var read = new byte[1024 * 4];
840 | var startTime = DateTime.Now;
841 |
842 | while (data.Count < expectedResponseLength || _serialPort.BytesToRead > 0)
843 | {
844 | if (_serialPort.BytesToRead > 0)
845 | {
846 | var bytesRead = await _serialPort.ReadAsync(read, 0, read.Length, ct).ConfigureAwait(false);
847 | if (bytesRead > 0)
848 | data.AddRange(read.Take(bytesRead));
849 | }
850 |
851 | if (DateTime.Now.Subtract(startTime) > timeout)
852 | return null;
853 |
854 | await Task.Delay(10, ct).ConfigureAwait(false);
855 | }
856 |
857 | return data.ToArray();
858 | }
859 |
860 | #endregion
861 |
862 | #region IDisposable Support
863 |
864 | ///
865 | public void Dispose() => Dispose(true);
866 |
867 | ///
868 | /// Releases unmanaged and - optionally - managed resources.
869 | ///
870 | ///
871 | /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
872 | protected virtual void Dispose(bool disposing)
873 | {
874 | if (_disposedValue) return;
875 | if (disposing)
876 | {
877 | CloseAsync().Wait();
878 | _serialPortDone.Dispose();
879 | }
880 |
881 | _serialPortDone = null;
882 | _disposedValue = true;
883 | }
884 |
885 | #endregion
886 | }
887 | }
--------------------------------------------------------------------------------