├── 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 | [![Build status](https://ci.appveyor.com/api/projects/status/61tiduyk2eo8g7r9/branch/master?svg=true)](https://ci.appveyor.com/project/geoperez/sparkfunfingerprint/branch/master) 2 | [![Analytics](https://ga-beacon.appspot.com/UA-8535255-2/unosquare/wsfingerprint/)](https://github.com/igrigorik/ga-beacon) 3 | 4 | # ![Fingerprint](https://github.com/unosquare/sparkfunfingerprint/raw/master/logos/sffp-logo-32.png "Unosquare SparkFun Fingerprint Reader") 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 | ![GT-521Fxx](https://github.com/unosquare/sparkfunfingerprint/raw/master/logos/sffp-image.jpg "GT-521Fxx") 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: [![NuGet version](https://badge.fury.io/nu/Unosquare.Sparkfun.FingerprintReader.svg)](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 | } --------------------------------------------------------------------------------