Skip to content

Commit b6b46e4

Browse files
authored
Integrate mll-lab/microplate and mll-lab/liquid-handling-robotics
1 parent b49763e commit b6b46e4

File tree

112 files changed

+5421
-26
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

112 files changed

+5421
-26
lines changed

.github/workflows/validate.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ jobs:
4343
runs-on: ubuntu-latest
4444

4545
strategy:
46+
fail-fast: false
4647
matrix:
4748
php-version:
4849
- "7.4"
@@ -74,6 +75,9 @@ jobs:
7475
extensions: mbstring
7576
php-version: "${{ matrix.php-version }}"
7677

78+
- if: "! startsWith(matrix.php-version, 8)"
79+
run: composer remove --dev --no-update mll-lab/graphql-php-scalars
80+
7781
- run: composer require "illuminate/support:${{ matrix.illuminate }}" --no-interaction --no-update
7882

7983
- if: matrix.dependencies == 'lowest'

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ See [GitHub releases](https://github.com/mll-lab/php-utils/releases).
99

1010
## Unreleased
1111

12+
## v1.13.0
13+
14+
### Added
15+
16+
- Integrate `mll-lab/microplate` and `mll-lab/liquid-handling-robotics`
17+
1218
## v1.12.0
1319

1420
### Added

composer.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@
1818
"php": "^7.4 || ^8",
1919
"ext-calendar": "*",
2020
"illuminate/support": "^8.73 || ^9 || ^10",
21-
"mll-lab/microplate": "^6",
2221
"mll-lab/str_putcsv": "^1",
2322
"nesbot/carbon": "^2.62.1",
2423
"thecodingmachine/safe": "^1 || ^2"
2524
},
2625
"require-dev": {
2726
"ergebnis/composer-normalize": "^2",
27+
"illuminate/database": "^8.73 || ^9 || ^10",
2828
"infection/infection": "^0.26 || ^0.27",
2929
"jangregor/phpstan-prophecy": "^1",
30+
"mll-lab/graphql-php-scalars": "^6",
3031
"mll-lab/php-cs-fixer-config": "^5",
32+
"nunomaduro/larastan": "^1 || ^2",
33+
"orchestra/testbench": "^5 || ^6 || ^7 || ^8",
3134
"phpstan/extension-installer": "^1",
3235
"phpstan/phpstan": "^1",
3336
"phpstan/phpstan-deprecation-rules": "^1",
@@ -37,6 +40,9 @@
3740
"rector/rector": "^0.17",
3841
"thecodingmachine/phpstan-safe-rule": "^1.2"
3942
},
43+
"suggest": {
44+
"mll-lab/graphql-php-scalars": "To use the provided scalar types for GraphQL servers, requires version ^6"
45+
},
4046
"autoload": {
4147
"psr-4": {
4248
"MLL\\Utils\\": "src/"

src/CSVArray.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44

55
use Illuminate\Support\Arr;
66

7-
/**
8-
* @phpstan-type CSVPrimitive bool|float|int|string|\Stringable|null
9-
*/
10-
final class CSVArray
7+
/** @phpstan-type CSVPrimitive bool|float|int|string|\Stringable|null */
8+
class CSVArray
119
{
1210
/**
1311
* TODO: fix parsing multiline-content in csv.

src/FluidXPlate/FluidXPlate.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace MLL\Utils\FluidXPlate;
4+
5+
use Illuminate\Support\Collection;
6+
use MLL\Utils\Microplate\Coordinates;
7+
use MLL\Utils\Microplate\CoordinateSystem96Well;
8+
use MLL\Utils\Microplate\Enums\FlowDirection;
9+
use MLL\Utils\Microplate\Microplate;
10+
11+
class FluidXPlate
12+
{
13+
public const FLUIDX_BARCODE_REGEX = /* @lang RegExp */ '/' . self::FLUIDX_BARCODE_REGEX_WITHOUT_DELIMITER . '/';
14+
public const FLUIDX_BARCODE_REGEX_WITHOUT_DELIMITER = '[A-Z]{2}(\d){8}';
15+
16+
public string $rackID;
17+
18+
/** @var Microplate<string, CoordinateSystem96Well> */
19+
private Microplate $microplate;
20+
21+
public function __construct(string $rackID)
22+
{
23+
if (\Safe\preg_match(self::FLUIDX_BARCODE_REGEX, $rackID) === 0) {
24+
throw new InvalidRackIDException($rackID);
25+
}
26+
$this->rackID = $rackID;
27+
$this->microplate = new Microplate(self::coordinateSystem());
28+
}
29+
30+
public static function coordinateSystem(): CoordinateSystem96Well
31+
{
32+
return new CoordinateSystem96Well();
33+
}
34+
35+
/** @param Coordinates<CoordinateSystem96Well> $coordinates */
36+
public function addWell(Coordinates $coordinates, string $barcode): void
37+
{
38+
if (\Safe\preg_match(self::FLUIDX_BARCODE_REGEX, $barcode) === 0) {
39+
throw new InvalidTubeBarcodeException($barcode);
40+
}
41+
42+
$this->microplate->addWell($coordinates, $barcode);
43+
}
44+
45+
/** @return Coordinates<CoordinateSystem96Well> */
46+
public function addToNextFreeWell(string $content, FlowDirection $flowDirection): Coordinates
47+
{
48+
return $this->microplate->addToNextFreeWell($content, $flowDirection);
49+
}
50+
51+
/** @return Collection<string, string|null> */
52+
public function wells(): Collection
53+
{
54+
return $this->microplate->wells();
55+
}
56+
57+
/** @return Collection<string, null> */
58+
public function freeWells(): Collection
59+
{
60+
return $this->microplate->freeWells();
61+
}
62+
63+
/** @return Collection<string, string> */
64+
public function filledWells(): Collection
65+
{
66+
return $this->microplate->filledWells();
67+
}
68+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace MLL\Utils\FluidXPlate;
4+
5+
abstract class FluidXPlateException extends \Exception {}

src/FluidXPlate/FluidXScanner.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace MLL\Utils\FluidXPlate;
4+
5+
use Illuminate\Support\Str;
6+
use MLL\Utils\Microplate\Coordinates;
7+
use MLL\Utils\Microplate\CoordinateSystem96Well;
8+
use MLL\Utils\StringUtil;
9+
10+
/** Communicates with a FluidX scanner device and fetches results from it. */
11+
class FluidXScanner
12+
{
13+
private const READING = 'Reading...';
14+
private const XTR_96_CONNECTED = 'xtr-96 Connected';
15+
private const NO_READ = 'NO READ';
16+
private const NO_TUBE = 'NO TUBE';
17+
public const LOCALHOST = '127.0.0.1';
18+
19+
public function scanPlate(string $ip): FluidXPlate
20+
{
21+
if ($ip === self::LOCALHOST) {
22+
return $this->returnTestPlate();
23+
}
24+
25+
if ($ip === '') {
26+
throw new ScanFluidXPlateException('Cannot start scan request without an IP address.');
27+
}
28+
29+
try {
30+
$socket = \Safe\fsockopen($ip, 8001, $errno, $errstr, 30);
31+
} catch (\Throwable $e) {
32+
throw new ScanFluidXPlateException("Cannot reach FluidX Scanner {$ip}: {$e->getMessage()}. Verify that the FluidX Scanner is turned on and the FluidX software is started.", 0, $e);
33+
}
34+
35+
\Safe\fwrite($socket, "get\r\n");
36+
37+
$answer = '';
38+
do {
39+
$content = fgets($socket);
40+
$answer .= $content;
41+
} while (is_string($content) && ! Str::contains($content, 'H12'));
42+
43+
\Safe\fclose($socket);
44+
45+
return self::parseRawContent($answer);
46+
}
47+
48+
public static function parseRawContent(string $rawContent): FluidXPlate
49+
{
50+
if ($rawContent === '') {
51+
throw new ScanFluidXPlateException('Der Scanner lieferte ein leeres Ergebnis zurück.');
52+
}
53+
54+
$lines = StringUtil::splitLines($rawContent);
55+
$barcodes = [];
56+
$id = null;
57+
foreach ($lines as $line) {
58+
if ($line === '' || $line === self::READING || $line === self::XTR_96_CONNECTED) {
59+
continue;
60+
}
61+
$content = explode(', ', $line);
62+
if (count($content) <= 3) {
63+
continue;
64+
}
65+
66+
// All valid lines contain the same plate barcode
67+
$id = $content[3];
68+
if ($id === FluidXScanner::NO_READ && isset($content[4])) {
69+
$id = $content[4];
70+
}
71+
72+
$barcodeScanResult = $content[1];
73+
$coordinatesString = $content[0];
74+
if ($barcodeScanResult !== self::NO_READ && $barcodeScanResult !== self::NO_TUBE) {
75+
$barcodes[$coordinatesString] = $barcodeScanResult;
76+
}
77+
}
78+
79+
if (is_null($id)) {
80+
throw new ScanFluidXPlateException('Der Scanner lieferte keinen Plattenbarcode zurück.');
81+
}
82+
83+
if ($id === FluidXScanner::NO_READ) {
84+
throw new ScanFluidXPlateException($barcodes === []
85+
? 'Weder Platten-Barcode noch Tube-Barcodes konnten gescannt werden. Bitte überprüfen Sie, dass die Platte korrekt in den FluidX-Scanner eingelegt wurde.'
86+
: 'Platten-Barcode konnte nicht gescannt werden. Bitte überprüfen Sie, dass die Platte mit der korrekten Orientierung in den FluidX-Scanner eingelegt wurde.');
87+
}
88+
89+
$plate = new FluidXPlate($id);
90+
foreach ($barcodes as $coordinates => $barcode) {
91+
$plate->addWell(Coordinates::fromString($coordinates, new CoordinateSystem96Well()), $barcode);
92+
}
93+
94+
return $plate;
95+
}
96+
97+
private function returnTestPlate(): FluidXPlate
98+
{
99+
return self::parseRawContent(\Safe\file_get_contents(__DIR__ . '/TestPlate.txt'));
100+
}
101+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace MLL\Utils\FluidXPlate;
4+
5+
class InvalidRackIDException extends FluidXPlateException
6+
{
7+
public function __construct(string $rackID)
8+
{
9+
parent::__construct("Invalid rack ID: {$rackID}");
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace MLL\Utils\FluidXPlate;
4+
5+
class InvalidTubeBarcodeException extends FluidXPlateException
6+
{
7+
public function __construct(string $tubeBarcode)
8+
{
9+
parent::__construct("Invalid tube barcode: {$tubeBarcode}");
10+
}
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace MLL\Utils\FluidXPlate\Scalars;
4+
5+
use MLL\GraphQLScalars\Regex;
6+
use MLL\Utils\FluidXPlate\FluidXPlate;
7+
8+
class FluidXBarcode extends Regex
9+
{
10+
public ?string $description = 'A valid barcode for FluidX-Tubes or FluidX-Plates represented as a string, e.g. `XR12345678`.';
11+
12+
public static function regex(): string
13+
{
14+
return FluidXPlate::FLUIDX_BARCODE_REGEX;
15+
}
16+
}

0 commit comments

Comments
 (0)