Skip to content

Commit

Permalink
Fixes #132 - Hopefully a permanent fix for invalid part labels, and p…
Browse files Browse the repository at this point in the history
…assing barcode scans to the part info endpoint
  • Loading branch information
replaysMike committed Apr 1, 2023
1 parent b528741 commit f7da2bf
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 24 deletions.
14 changes: 14 additions & 0 deletions Binner/Binner.Web/ClientApp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Binner/Binner.Web/ClientApp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"react-router-dom": "^6.9.0",
"react-scripts": "^5.0.1",
"react-spinners": "^0.13.8",
"react-string-replace": "^1.1.0",
"react-toastify": "^9.1.2",
"reactstrap": "^9.1.6",
"rimraf": "^4.4.1",
Expand Down
60 changes: 46 additions & 14 deletions Binner/Binner.Web/ClientApp/src/components/BarcodeScannerInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,22 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
const processBarcodeInformation = (e, value) => {
let barcodeType = "code128";
let parsedValue = {};
let correctedValue = value;
let gsDetected = false;
let rsDetected = false;
let eotDetected = false;
if (value.startsWith("[)>") || value.startsWith("[)\u25B2")) {
let invalidBarcodeDetected = false;
if (value.startsWith("[)>")) {
// 2D DotMatrix barcode. Process into value.
barcodeType = "datamatrix";
const parseResult = parseDataMatrix(value);
console.log('parseResult', parseResult);
parsedValue = parseResult.value;
gsDetected = parseResult.gsDetected;
rsDetected = parseResult.rsDetected;
eotDetected = parseResult.eotDetected;
invalidBarcodeDetected = parseResult.invalidBarcodeDetected;
correctedValue = parseResult.correctedValue;
} else {
// 1D barcode
parsedValue = value.replace("\n", "").replace("\r", "");
Expand All @@ -105,18 +110,20 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
return {
type: barcodeType,
value: parsedValue,
correctedValue: correctedValue,
rawValue: value,
rsDetected,
gsDetected,
eotDetected
eotDetected,
invalidBarcodeDetected
};
};

const parseDataMatrix = (value) => {
let parsedValue = {};
const gsCharCodes = [29, 93];
const rsCharCodes = [30, 94];
const eotCharCodes = [4, 68];
const gsCharCodes = ["\u001d", "\u005d", "\u241d"];
const rsCharCodes = ["\u001e", "\u005e", "\u241e"];
const eotCharCodes = ["\u0004", "^\u0044", "\u2404"];
const header = "[)>";
const expectedFormatNumber = 6; /** 22z22 barcode */
const controlChars = ["P", "1P", "P1", "K", "1K", "10K", "11K", "4L", "Q", "11Z", "12Z", "13Z", "20Z"];
Expand All @@ -128,12 +135,18 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
let buffer = "";
let i;
let formatNumberIndex = 0;
let correctedValue = value.toString();

gsCodePresent = gsCharCodes.some(v => value.includes(v));
rsCodePresent = rsCharCodes.some(v => value.includes(v));
eotCodePresent = eotCharCodes.some(v => value.includes(v));

// read in the format number first. For Digikey 2d barcodes, this should be 6 (expectedFormatNumber)
for (i = 0; i < value.length; i++) {
buffer += value[i];
if (buffer === header) {
if (rsCharCodes.includes(value.charCodeAt(i + 1))) {
if (rsCharCodes.includes(value[i + 1])) {
// read the character after the RS token (sometimes not present)
rsCodePresent = true;
formatNumberIndex = i + 2;
} else {
formatNumberIndex = i + 1;
Expand All @@ -143,6 +156,7 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
break;
}
}
// assert expected barcode format number
if (formatNumber !== expectedFormatNumber) {
// error
console.error(`Expected the 2D barcode format number of ${expectedFormatNumber} but was ${formatNumber}`);
Expand All @@ -152,27 +166,41 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
let lastPosition = i;
const gsLines = [];
let gsLine = '';
// break each group separator into an array
for (i = lastPosition; i < value.length; i++) {
const ch = value[i];
if (gsCharCodes.includes(ch.charCodeAt(0))) {
gsCodePresent = true;
if (gsCharCodes.includes(ch)) {
// start of a new line. read until next gsCharCode or EOT
if (gsLine.length > 0)
gsLines.push(gsLine);
gsLine = '';
} else {
gsLine += ch;
}
if (eotCharCodes.includes(ch.charCodeAt(0))) {
eotCodePresent = true;
}
}
if (gsLine.length > 0)
gsLines.push(gsLine);

for (i = 0; i < gsLines.length; i++) {
let readLength = gsLines.length;
let invalidBarcodeDetected = false;
// some older DigiKey barcodes are encoded incorrectly, and have a blank GSRS at the end. Filter them out.
// https://github.com/replaysMike/Binner/issues/132
if (correctedValue.endsWith("\u005e\u0044\r") || correctedValue.endsWith("\u005d\u005e\u0044")) {
invalidBarcodeDetected = true;
readLength--;
// apply correction to the raw value
if (correctedValue.endsWith("\r")){
correctedValue = correctedValue.substring(0, correctedValue.length - 4) + "\r";
} else {
correctedValue = correctedValue.substring(0, correctedValue.length - 3);
}
}
const filteredGsLines = [];
// read each group separator
for (i = 0; i < readLength; i++) {
// read until we see a control char
const line = gsLines[i];
filteredGsLines.push(line);
let readCommandType = "";
let readValue = "";
let readControlChars = true;
Expand Down Expand Up @@ -236,10 +264,14 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
}
}
return {
rawValue: value,
value: parsedValue,
correctedValue: correctedValue,
gsDetected: gsCodePresent,
rsDetected: rsCodePresent,
eotDetected: eotCodePresent
eotDetected: eotCodePresent,
gsLines: filteredGsLines,
invalidBarcodeDetected
};
};

Expand Down
4 changes: 2 additions & 2 deletions Binner/Binner.Web/ClientApp/src/pages/Inventory.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ export function Inventory(props) {
binNumber2: (lastPart && lastPart.binNumber2) || "",
origin: (input.value.countryOfOrigin && input.value.countryOfOrigin.toLowerCase()) || "",
description: input.value.description || "",
barcode: input.rawValue
barcode: input.correctedValue
};
const existingPartNumber = _.find(scannedPartsRef.current, { partNumber: cleanPartNumber });
if (existingPartNumber) {
Expand Down Expand Up @@ -483,7 +483,7 @@ export function Inventory(props) {
// fetch metadata on the barcode, don't await, do a background update
const scannedPart = {
partNumber: cleanPartNumber,
barcode: input.rawValue
barcode: input.correctedValue
};
fetchBarcodeMetadata(e, scannedPart, (partInfo) => {
// barcode found
Expand Down
21 changes: 21 additions & 0 deletions Binner/Binner.Web/ClientApp/src/pages/tools/BarcodeScanner.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,25 @@ pre {
background-color: rgb(0, 100, 128);
color: #fff;
font-weight: 600;
}

.block-container .block.active.red {
background-color: rgb(128, 0, 0);
}

.gs {
font-size: 1.5em;
font-weight: 700;
color:rgb(255, 0, 0);
}
.rs {
font-size: 1.5em;
font-weight: 700;
color:rgb(50, 50, 150);
}

.eot {
font-size: 1.5em;
font-weight: 700;
color:rgb(0, 100, 0);
}
28 changes: 21 additions & 7 deletions Binner/Binner.Web/ClientApp/src/pages/tools/BarcodeScanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Form, Popup } from "semantic-ui-react";
import { toast } from "react-toastify";
import { BarcodeScannerInput } from "../../components/BarcodeScannerInput";
import reactStringReplace from "react-string-replace";
import "./BarcodeScanner.css";

export function BarcodeScanner(props) {
Expand All @@ -13,41 +14,50 @@ export function BarcodeScanner(props) {
const [rsDetected, setRsDetected] = useState(false);
const [gsDetected, setGsDetected] = useState(false);
const [eotDetected, setEotDetected] = useState(false);
const [invalidBarcodeDetected, setInvalidBarcodeDetected] = useState(false);

// debounced handler for processing barcode scanner input
const handleBarcodeInput = (e, input) => {
// ignore single keypresses
setRsDetected(false);
setGsDetected(false);
setEotDetected(false);
setInvalidBarcodeDetected(false);

if (input && input.rawValue) {
const rawValueFormatted = input.rawValue
.replaceAll("\u001e","<RS>")
.replaceAll("\u005e","<RS>")
.replaceAll("\u001d", "<GS>")
.replaceAll("\u005d", "<GS>")
.replaceAll("<RS>\u0004", "<RS><EOT>")
.replaceAll("<RS>\u0044", "<RS><EOT>");
.replaceAll("^\u0044", "\u241e\u2404") // ^EOT
.replaceAll("\u0004", "\u2404") // EOT
.replaceAll("\u001e","\u241e") // RS (30)
.replaceAll("\u005e","\u241e") // RS (94) ^
.replaceAll("\u001d", "\u241d") // GS (29)
.replaceAll("\u005d", "\u241d") // GS (93) ]
;
const json = {...input, rawValueFormatted: rawValueFormatted};
setBarcodeValue(JSON.stringify(json, null, 2));
setRsDetected(input.rsDetected);
setGsDetected(input.gsDetected);
setEotDetected(input.eotDetected);
setInvalidBarcodeDetected(input.invalidBarcodeDetected);
} else{
setBarcodeValue(JSON.stringify(input, null, 2));
}
toast.info(`Barcode type ${input.type} received`);
};

//let barcodeObject = barcodeValue;
let barcodeObject = reactStringReplace(barcodeValue, "\u241E", (match, i) => (<span key={i*2} className="rs">{match}</span>));
barcodeObject = reactStringReplace(barcodeObject, "\u241D", (match, i) => (<span key={i*3} className="gs">{match}</span>));
barcodeObject = reactStringReplace(barcodeObject, "\u2404", (match, i) => (<span key={i*4} className="eot">{match}</span>));

return (
<div>
<BarcodeScannerInput onReceived={handleBarcodeInput} listening={isKeyboardListening} minInputLength={4} swallowKeyEvent={false} />
<h1>Barcode Scanner</h1>
<p>Test your barcode scanner to see what values it outputs.</p>
<Form>
<div>
<code><pre>{barcodeValue}</pre></code>
<code><pre>{barcodeObject}</pre></code>
<div className="block-container">
<label>Detected:</label>
<Popup
Expand All @@ -64,6 +74,10 @@ export function BarcodeScanner(props) {
content={<p>EOT, or end of transmission (ASCII 04, unicode \u0004) separator is a hidden data code indicating the end of a barcode transmission. It is optional for barcodes.</p>}
trigger={<div className={`block ${eotDetected ? 'active' : ''}`}>EOT</div>}
/>
<Popup
content={<p>Some older DigiKey barcodes are encoded incorrectly. If an older part label that was encoded incorrectly is detected it will be indicated here, and Binner has corrected for it.</p>}
trigger={<div className={`block ${invalidBarcodeDetected ? 'active red' : ''}`}>Invalid</div>}
/>
</div>
</div>
</Form>
Expand Down
26 changes: 25 additions & 1 deletion Binner/Library/Binner.Common/Services/PartService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ public async Task<bool> DeletePartSupplierAsync(PartSupplier partSupplier)
{
const int maxImagesPerSupplier = 5;
const int maxImagesTotal = 10;
var response = new PartResults();
var totalImages = 0;
var user = _requestContext.GetUserContext();
//var context = await _contextFactory.CreateDbContextAsync();
Expand All @@ -616,9 +617,32 @@ public async Task<bool> DeletePartSupplierAsync(PartSupplier partSupplier)
var mouserApi = await _integrationApiFactory.CreateAsync<Integrations.MouserApi>(user?.UserId ?? 0);
var nexarApi = await _integrationApiFactory.CreateAsync<Integrations.NexarApi>(user?.UserId ?? 0);
var arrowApi = await _integrationApiFactory.CreateAsync<Integrations.ArrowApi>(user?.UserId ?? 0);
if (partNumber.StartsWith("[)>"))
{
if (digikeyApi.Configuration.IsConfigured)
{
// 2d barcode scan requires decode first
var barcodeInfo = await GetBarcodeInfoAsync(partNumber, ScannedBarcodeType.Product);
if (barcodeInfo.Response?.Parts.Any() == true)
{
var firstPartMatch = barcodeInfo.Response.Parts.First();
if(!string.IsNullOrEmpty(firstPartMatch.ManufacturerPartNumber))
partNumber = firstPartMatch.ManufacturerPartNumber;
else if(!string.IsNullOrEmpty(firstPartMatch.BasePartNumber))
partNumber = firstPartMatch.BasePartNumber;

}
}

// continue with lookup
}
if (string.IsNullOrEmpty(partNumber))
{
// return empty result, invalid request
return ServiceResult<PartResults>.Create(response);
}

var datasheets = new List<string>();
var response = new PartResults();
var swarmResponse = new SwarmApi.Response.SearchPartResponse();
var digikeyResponse = new KeywordSearchResponse();
var mouserResponse = new SearchResultsResponse();
Expand Down
12 changes: 12 additions & 0 deletions Scripts/appveyor-build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -84,28 +84,40 @@ Copy-Item -Force -Path .\Binner\scripts\windows\* -Destination .\Binner\Binner.W

$buildEnv = "linux-x64"
Move-Item -Force -Path .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\appsettings.Unix.Production.json -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\appsettings.json
if (!$?) { exit -1 }
Move-Item -Force -Path .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\nlog.Unix.config -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\nlog.config
if (!$?) { exit -1 }
Copy-Item -Force -Path .\Binner\scripts\unix\* -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish

$buildEnv = "linux-arm"
Move-Item -Force -Path .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\appsettings.Unix.Production.json -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\appsettings.json
if (!$?) { exit -1 }
Move-Item -Force -Path .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\nlog.Unix.config -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\nlog.config
if (!$?) { exit -1 }
Copy-Item -Force -Path .\Binner\scripts\unix\* -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish

$buildEnv = "linux-arm64"
Move-Item -Force -Path .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\appsettings.Unix.Production.json -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\appsettings.json
if (!$?) { exit -1 }
Move-Item -Force -Path .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\nlog.Unix.config -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\nlog.config
if (!$?) { exit -1 }
Copy-Item -Force -Path .\Binner\scripts\unix\* -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish

$buildEnv = "ubuntu.14.04-x64"
Move-Item -Force -Path .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\appsettings.Unix.Production.json -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\appsettings.json
if (!$?) { exit -1 }
Move-Item -Force -Path .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\nlog.Unix.config -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\nlog.config
if (!$?) { exit -1 }
Copy-Item -Force -Path .\Binner\scripts\unix\* -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish
if (!$?) { exit -1 }

$buildEnv = "osx.10.12-x64"
Move-Item -Force -Path .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\appsettings.Unix.Production.json -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\appsettings.json
if (!$?) { exit -1 }
Move-Item -Force -Path .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\nlog.Unix.config -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish\nlog.config
if (!$?) { exit -1 }
Copy-Item -Force -Path .\Binner\scripts\unix\* -Destination .\Binner\Binner.Web\bin\$releaseConfiguration\$framework\$buildEnv\publish
if (!$?) { exit -1 }

# build installers
Write-Host "Building Installers" -ForegroundColor green
Expand Down

0 comments on commit f7da2bf

Please sign in to comment.