diff --git a/CHANGELOG.md b/CHANGELOG.md
index f03c658..e6cef4d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## Unreleased
 
+## v5.1.0
+
+### Added
+
+- Add `FluidXScanner`-class
+
 ## v5.0.0
 
 ### Added
diff --git a/composer.lock b/composer.lock
index 3834ec4..42f67e6 100644
--- a/composer.lock
+++ b/composer.lock
@@ -8253,26 +8253,26 @@
         },
         {
             "name": "orchestra/testbench",
-            "version": "v7.22.1",
+            "version": "v7.22.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/orchestral/testbench.git",
-                "reference": "987f5383f597a54f8d9868f98411899398348c02"
+                "reference": "2b46f51b61404313156f180a0b3ebda7233a309b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/orchestral/testbench/zipball/987f5383f597a54f8d9868f98411899398348c02",
-                "reference": "987f5383f597a54f8d9868f98411899398348c02",
+                "url": "https://api.github.com/repos/orchestral/testbench/zipball/2b46f51b61404313156f180a0b3ebda7233a309b",
+                "reference": "2b46f51b61404313156f180a0b3ebda7233a309b",
                 "shasum": ""
             },
             "require": {
                 "fakerphp/faker": "^1.21",
                 "laravel/framework": "^9.52.4",
                 "mockery/mockery": "^1.5.1",
-                "orchestra/testbench-core": "^7.22.1",
+                "orchestra/testbench-core": "^7.22.2",
                 "php": "^8.0",
                 "phpunit/phpunit": "^9.5.10",
-                "spatie/laravel-ray": "^1.28",
+                "spatie/laravel-ray": "^1.32.4",
                 "symfony/process": "^6.0.9",
                 "symfony/yaml": "^6.0.9",
                 "vlucas/phpdotenv": "^5.4.1"
@@ -8306,22 +8306,22 @@
             ],
             "support": {
                 "issues": "https://github.com/orchestral/testbench/issues",
-                "source": "https://github.com/orchestral/testbench/tree/v7.22.1"
+                "source": "https://github.com/orchestral/testbench/tree/v7.22.2"
             },
-            "time": "2023-02-24T01:07:22+00:00"
+            "time": "2023-03-23T09:07:28+00:00"
         },
         {
             "name": "orchestra/testbench-core",
-            "version": "v7.22.1",
+            "version": "v7.22.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/orchestral/testbench-core.git",
-                "reference": "75b42f9130e7903ffa0ef8dbb466962ca6635261"
+                "reference": "c34bcaf4888cb680f76eaca11ec9c4a67c019a0b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/75b42f9130e7903ffa0ef8dbb466962ca6635261",
-                "reference": "75b42f9130e7903ffa0ef8dbb466962ca6635261",
+                "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/c34bcaf4888cb680f76eaca11ec9c4a67c019a0b",
+                "reference": "c34bcaf4888cb680f76eaca11ec9c4a67c019a0b",
                 "shasum": ""
             },
             "require": {
@@ -8333,9 +8333,9 @@
                 "laravel/pint": "^1.4",
                 "mockery/mockery": "^1.5.1",
                 "orchestra/canvas": "^7.0",
-                "phpstan/phpstan": "^1.9.14",
+                "phpstan/phpstan": "^1.10.7",
                 "phpunit/phpunit": "^9.5.10",
-                "spatie/laravel-ray": "^1.28",
+                "spatie/laravel-ray": "^1.32.4",
                 "symfony/process": "^6.0.9",
                 "symfony/yaml": "^6.0.9",
                 "vlucas/phpdotenv": "^5.4.1"
@@ -8394,7 +8394,7 @@
                 "issues": "https://github.com/orchestral/testbench/issues",
                 "source": "https://github.com/orchestral/testbench-core"
             },
-            "time": "2023-02-23T12:15:23+00:00"
+            "time": "2023-03-23T08:52:15+00:00"
         },
         {
             "name": "phar-io/manifest",
@@ -9525,16 +9525,16 @@
         },
         {
             "name": "rector/rector",
-            "version": "0.15.21",
+            "version": "0.15.23",
             "source": {
                 "type": "git",
                 "url": "https://github.com/rectorphp/rector.git",
-                "reference": "1cee8cc5d6d836e1bf9a3006d7b062adde3a6022"
+                "reference": "f4984ebd62b3613002869b0ddd6868261d62819e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/rectorphp/rector/zipball/1cee8cc5d6d836e1bf9a3006d7b062adde3a6022",
-                "reference": "1cee8cc5d6d836e1bf9a3006d7b062adde3a6022",
+                "url": "https://api.github.com/repos/rectorphp/rector/zipball/f4984ebd62b3613002869b0ddd6868261d62819e",
+                "reference": "f4984ebd62b3613002869b0ddd6868261d62819e",
                 "shasum": ""
             },
             "require": {
@@ -9574,7 +9574,7 @@
             ],
             "support": {
                 "issues": "https://github.com/rectorphp/rector/issues",
-                "source": "https://github.com/rectorphp/rector/tree/0.15.21"
+                "source": "https://github.com/rectorphp/rector/tree/0.15.23"
             },
             "funding": [
                 {
@@ -9582,7 +9582,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-03-06T11:44:29+00:00"
+            "time": "2023-03-22T15:22:45+00:00"
         },
         {
             "name": "sanmai/later",
@@ -10735,16 +10735,16 @@
         },
         {
             "name": "spatie/laravel-ray",
-            "version": "1.32.3",
+            "version": "1.32.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/spatie/laravel-ray.git",
-                "reference": "8c7ea86c8092bcfe7a046f640b6ac9e5d7ec98cd"
+                "reference": "2274653f0a90dd87fbb887437be1c1ea1388a47c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/8c7ea86c8092bcfe7a046f640b6ac9e5d7ec98cd",
-                "reference": "8c7ea86c8092bcfe7a046f640b6ac9e5d7ec98cd",
+                "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/2274653f0a90dd87fbb887437be1c1ea1388a47c",
+                "reference": "2274653f0a90dd87fbb887437be1c1ea1388a47c",
                 "shasum": ""
             },
             "require": {
@@ -10804,7 +10804,7 @@
             ],
             "support": {
                 "issues": "https://github.com/spatie/laravel-ray/issues",
-                "source": "https://github.com/spatie/laravel-ray/tree/1.32.3"
+                "source": "https://github.com/spatie/laravel-ray/tree/1.32.4"
             },
             "funding": [
                 {
@@ -10816,7 +10816,7 @@
                     "type": "other"
                 }
             ],
-            "time": "2023-03-03T13:37:21+00:00"
+            "time": "2023-03-23T08:04:54+00:00"
         },
         {
             "name": "spatie/macroable",
diff --git a/rector.php b/rector.php
index a88fbd1..881e15c 100644
--- a/rector.php
+++ b/rector.php
@@ -2,6 +2,7 @@
 
 use Rector\Config\RectorConfig;
 use Rector\Core\ValueObject\PhpVersion;
+use Rector\Privatization\Rector\Class_\FinalizeClassesWithoutChildrenRector;
 use Rector\Set\ValueObject\SetList;
 use Rector\TypeDeclaration\Rector\Closure\AddClosureReturnTypeRector;
 
@@ -18,4 +19,10 @@
     ]);
     $config->paths([__DIR__ . '/src', __DIR__ . '/tests']);
     $config->phpVersion(PhpVersion::PHP_74);
+
+    $config->skip([
+        FinalizeClassesWithoutChildrenRector::class => [
+            __DIR__ . '/src/FluidXPlate/FluidXScanner.php', // enabled for mocking
+        ],
+    ]);
 };
diff --git a/src/FluidXPlate/FluidXScanner.php b/src/FluidXPlate/FluidXScanner.php
new file mode 100644
index 0000000..2305c70
--- /dev/null
+++ b/src/FluidXPlate/FluidXScanner.php
@@ -0,0 +1,103 @@
+<?php declare(strict_types=1);
+
+namespace Mll\LiquidHandlingRobotics\FluidXPlate;
+
+use Illuminate\Support\Str;
+use Mll\Microplate\Coordinate;
+use Mll\Microplate\CoordinateSystem96Well;
+use MLL\Utils\StringUtil;
+
+/**
+ * Communicates with a FluidX scanner device and fetches results from it.
+ */
+class FluidXScanner
+{
+    private const READING = 'Reading...';
+    private const XTR_96_CONNECTED = 'xtr-96 Connected';
+    private const NO_READ = 'NO READ';
+    private const NO_TUBE = 'NO TUBE';
+    public const LOCALHOST = '127.0.0.1';
+
+    public function scanPlate(string $ip): FluidXPlate
+    {
+        if (self::LOCALHOST === $ip) {
+            return self::returnTestPlate();
+        }
+
+        if ('' === $ip) {
+            throw new ScanFluidXPlateException('Cannot start scan request without an IP address.');
+        }
+
+        try {
+            $socket = \Safe\fsockopen($ip, 8001, $errno, $errstr, 30);
+        } catch (\Throwable $e) {
+            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);
+        }
+
+        \Safe\fwrite($socket, "get\r\n");
+
+        $answer = '';
+        do {
+            $content = fgets($socket);
+            $answer .= $content;
+        } while (is_string($content) && ! Str::contains($content, 'H12'));
+
+        \Safe\fclose($socket);
+
+        return self::parseRawContent($answer);
+    }
+
+    public static function parseRawContent(string $rawContent): FluidXPlate
+    {
+        if ('' === $rawContent) {
+            throw new ScanFluidXPlateException('Der Scanner lieferte ein leeres Ergebnis zurück.');
+        }
+
+        $lines = StringUtil::splitLines($rawContent);
+        $barcodes = [];
+        $id = null;
+        foreach ($lines as $line) {
+            if ('' === $line || self::READING === $line || self::XTR_96_CONNECTED === $line) {
+                continue;
+            }
+            $content = explode(', ', $line);
+            if (count($content) <= 3) {
+                continue;
+            }
+
+            // All valid lines contain the same plate barcode
+            $id = $content[3];
+            if (FluidXScanner::NO_READ === $id && isset($content[4])) {
+                $id = $content[4];
+            }
+
+            $barcodeScanResult = $content[1];
+            $coordinateString = $content[0];
+            if (self::NO_READ !== $barcodeScanResult && self::NO_TUBE !== $barcodeScanResult) {
+                $barcodes[$coordinateString] = $barcodeScanResult;
+            }
+        }
+
+        if (is_null($id)) {
+            throw new ScanFluidXPlateException('Der Scanner lieferte keinen Plattenbarcode zurück.');
+        }
+
+        if (FluidXScanner::NO_READ === $id) {
+            throw new ScanFluidXPlateException([] === $barcodes
+                ? 'Weder Platten-Barcode noch Tube-Barcodes konnten gescannt werden. Bitte überprüfen Sie, dass die Platte korrekt in den FluidX-Scanner eingelegt wurde.'
+                : 'Platten-Barcode konnte nicht gescannt werden. Bitte überprüfen Sie, dass die Platte mit der korrekten Orientierung in den FluidX-Scanner eingelegt wurde.');
+        }
+
+        $plate = new FluidXPlate($id);
+        foreach ($barcodes as $coordinate => $barcode) {
+            $plate->addWell(Coordinate::fromString($coordinate, new CoordinateSystem96Well()), $barcode);
+        }
+
+        return $plate;
+    }
+
+    private static function returnTestPlate(): FluidXPlate
+    {
+        return self::parseRawContent(\Safe\file_get_contents(__DIR__ . '/TestPlate.txt'));
+    }
+}
diff --git a/src/FluidXPlate/ScanFluidXPlateException.php b/src/FluidXPlate/ScanFluidXPlateException.php
new file mode 100644
index 0000000..4399603
--- /dev/null
+++ b/src/FluidXPlate/ScanFluidXPlateException.php
@@ -0,0 +1,7 @@
+<?php declare(strict_types=1);
+
+namespace Mll\LiquidHandlingRobotics\FluidXPlate;
+
+final class ScanFluidXPlateException extends FluidXPlateException
+{
+}
diff --git a/src/FluidXPlate/TestPlate.txt b/src/FluidXPlate/TestPlate.txt
new file mode 100644
index 0000000..71b4edc
--- /dev/null
+++ b/src/FluidXPlate/TestPlate.txt
@@ -0,0 +1,96 @@
+A1, FD20024619, 1d_rackid_1, SA00826894
+A2, FD20024698, 1d_rackid_1, SA00826894
+A3, FD20024711, 1d_rackid_1, SA00826894
+A4, NO READ, 1d_rackid_1, SA00826894
+A5, NO READ, 1d_rackid_1, SA00826894
+A6, NO READ, 1d_rackid_1, SA00826894
+A7, NO READ, 1d_rackid_1, SA00826894
+A8, NO READ, 1d_rackid_1, SA00826894
+A9, NO READ, 1d_rackid_1, SA00826894
+A10, NO READ, 1d_rackid_1, SA00826894
+A11, NO READ, 1d_rackid_1, SA00826894
+A12, NO READ, 1d_rackid_1, SA00826894
+B1, NO READ, 1d_rackid_1, SA00826894
+B2, NO READ, 1d_rackid_1, SA00826894
+B3, NO READ, 1d_rackid_1, SA00826894
+B4, NO READ, 1d_rackid_1, SA00826894
+B5, NO READ, 1d_rackid_1, SA00826894
+B6, NO READ, 1d_rackid_1, SA00826894
+B7, NO READ, 1d_rackid_1, SA00826894
+B8, NO READ, 1d_rackid_1, SA00826894
+B9, NO READ, 1d_rackid_1, SA00826894
+B10, NO READ, 1d_rackid_1, SA00826894
+B11, NO READ, 1d_rackid_1, SA00826894
+B12, NO READ, 1d_rackid_1, SA00826894
+C1, NO READ, 1d_rackid_1, SA00826894
+C2, NO READ, 1d_rackid_1, SA00826894
+C3, NO READ, 1d_rackid_1, SA00826894
+C4, NO READ, 1d_rackid_1, SA00826894
+C5, NO READ, 1d_rackid_1, SA00826894
+C6, NO READ, 1d_rackid_1, SA00826894
+C7, NO READ, 1d_rackid_1, SA00826894
+C8, NO READ, 1d_rackid_1, SA00826894
+C9, NO READ, 1d_rackid_1, SA00826894
+C10, NO READ, 1d_rackid_1, SA00826894
+C11, NO READ, 1d_rackid_1, SA00826894
+C12, NO READ, 1d_rackid_1, SA00826894
+D1, NO READ, 1d_rackid_1, SA00826894
+D2, NO READ, 1d_rackid_1, SA00826894
+D3, NO READ, 1d_rackid_1, SA00826894
+D4, NO READ, 1d_rackid_1, SA00826894
+D5, NO READ, 1d_rackid_1, SA00826894
+D6, NO READ, 1d_rackid_1, SA00826894
+D7, NO READ, 1d_rackid_1, SA00826894
+D8, NO READ, 1d_rackid_1, SA00826894
+D9, NO READ, 1d_rackid_1, SA00826894
+D10, NO READ, 1d_rackid_1, SA00826894
+D11, NO READ, 1d_rackid_1, SA00826894
+D12, NO READ, 1d_rackid_1, SA00826894
+E1, NO READ, 1d_rackid_1, SA00826894
+E2, NO READ, 1d_rackid_1, SA00826894
+E3, NO READ, 1d_rackid_1, SA00826894
+E4, NO READ, 1d_rackid_1, SA00826894
+E5, NO READ, 1d_rackid_1, SA00826894
+E6, NO READ, 1d_rackid_1, SA00826894
+E7, NO READ, 1d_rackid_1, SA00826894
+E8, NO READ, 1d_rackid_1, SA00826894
+E9, NO READ, 1d_rackid_1, SA00826894
+E10, NO READ, 1d_rackid_1, SA00826894
+E11, NO READ, 1d_rackid_1, SA00826894
+E12, NO READ, 1d_rackid_1, SA00826894
+F1, NO READ, 1d_rackid_1, SA00826894
+F2, NO READ, 1d_rackid_1, SA00826894
+F3, NO READ, 1d_rackid_1, SA00826894
+F4, NO READ, 1d_rackid_1, SA00826894
+F5, NO READ, 1d_rackid_1, SA00826894
+F6, NO READ, 1d_rackid_1, SA00826894
+F7, NO READ, 1d_rackid_1, SA00826894
+F8, NO READ, 1d_rackid_1, SA00826894
+F9, NO READ, 1d_rackid_1, SA00826894
+F10, NO READ, 1d_rackid_1, SA00826894
+F11, NO READ, 1d_rackid_1, SA00826894
+F12, NO READ, 1d_rackid_1, SA00826894
+G1, NO READ, 1d_rackid_1, SA00826894
+G2, NO READ, 1d_rackid_1, SA00826894
+G3, NO READ, 1d_rackid_1, SA00826894
+G4, NO READ, 1d_rackid_1, SA00826894
+G5, NO READ, 1d_rackid_1, SA00826894
+G6, NO READ, 1d_rackid_1, SA00826894
+G7, NO READ, 1d_rackid_1, SA00826894
+G8, NO READ, 1d_rackid_1, SA00826894
+G9, NO READ, 1d_rackid_1, SA00826894
+G10, NO READ, 1d_rackid_1, SA00826894
+G11, NO READ, 1d_rackid_1, SA00826894
+G12, NO READ, 1d_rackid_1, SA00826894
+H1, NO READ, 1d_rackid_1, SA00826894
+H2, NO READ, 1d_rackid_1, SA00826894
+H3, NO READ, 1d_rackid_1, SA00826894
+H4, NO READ, 1d_rackid_1, SA00826894
+H5, NO READ, 1d_rackid_1, SA00826894
+H6, NO READ, 1d_rackid_1, SA00826894
+H7, NO READ, 1d_rackid_1, SA00826894
+H8, NO READ, 1d_rackid_1, SA00826894
+H9, NO READ, 1d_rackid_1, SA00826894
+H10, NO READ, 1d_rackid_1, SA00826894
+H11, NO READ, 1d_rackid_1, SA00826894
+H12, NO READ, 1d_rackid_1, SA00826894
diff --git a/tests/Unit/FluidXPlate/FluidXScannerTest.php b/tests/Unit/FluidXPlate/FluidXScannerTest.php
new file mode 100644
index 0000000..c6688aa
--- /dev/null
+++ b/tests/Unit/FluidXPlate/FluidXScannerTest.php
@@ -0,0 +1,22 @@
+<?php declare(strict_types=1);
+
+namespace FluidXPlate;
+
+use Mll\LiquidHandlingRobotics\FluidXPlate\FluidXScanner;
+use PHPUnit\Framework\TestCase;
+
+final class FluidXScannerTest extends TestCase
+{
+    public function testCreateFromStringEmpty(): void
+    {
+        $fluidXScanner = new FluidXScanner();
+        $fluidXPlate = $fluidXScanner->scanPlate(FluidXScanner::LOCALHOST);
+
+        self::assertSame('SA00826894', $fluidXPlate->rackId);
+        $filledWells = $fluidXPlate->filledWells();
+        self::assertCount(3, $filledWells);
+        self::assertSame('FD20024619', $filledWells->get('A1'));
+        self::assertSame('FD20024698', $filledWells->get('A2'));
+        self::assertSame('FD20024711', $filledWells->get('A3'));
+    }
+}