feat(scanner): more work on the scanner and can now scan to prod no lst right now
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m41s

This commit is contained in:
2026-04-25 18:13:07 -05:00
parent 83a542d1b7
commit 77b4533dea
17 changed files with 615 additions and 43 deletions

View File

@@ -0,0 +1,19 @@
import { Tabs } from "expo-router";
import { useAppStore } from "../../hooks/useAppStore";
export default function TabsLayout() {
const serverPort = useAppStore((s) => s.serverPort);
return (
<Tabs
screenOptions={{
headerShown: false, // Hides the header for all screens in this navigator
}}>
<Tabs.Screen name="scanner" options={{ title: "Scan" }} />
<Tabs.Screen name="config" options={{ title: "settings" }} />
<Tabs.Screen name="logs" options={{ title: "Logs",
href: parseInt(serverPort || "0", 10) >= 50000 ? null : "/(tabs)/logs",
}}
/>
</Tabs>
);
}

View File

@@ -0,0 +1,7 @@
import { Link } from "expo-router";
import { Text, View } from "react-native";
import Setup from "../setup";
export default function SettingsTab() {
return <Setup />
}

View File

@@ -0,0 +1,13 @@
import React from 'react'
import { Text, View } from 'react-native'
export default function Logs() {
return (
<View style={{
flex: 1,
//justifyContent: "center",
alignItems: "center",
marginTop: 50,
}}><Text>Logs</Text></View>
)
}

View File

@@ -0,0 +1,22 @@
import React from "react";
import { View } from "react-native";
import { useAppStore } from "../../hooks/useAppStore";
import ProdScanner from "../../components/ProdScanner";
import LSTScanner from "../../components/LSTScanner";
export default function scanner() {
const serverPort = useAppStore((s) => s.serverPort);
return (
<View
style={{
flex: 1,
//justifyContent: "center",
alignItems: "center",
marginTop: 50,
}}
>
{parseInt(serverPort || "0", 10) >= 50000 ? <ProdScanner /> : <LSTScanner />}
</View>
);
}

View File

@@ -1,4 +1,4 @@
import { useRouter } from "expo-router";
import { Redirect, useRouter } from "expo-router";
import { useEffect, useState } from "react";
import { ActivityIndicator, Text, View } from "react-native";
import { useAppStore } from "../hooks/useAppStore";
@@ -7,6 +7,7 @@ import { devDelay } from "../lib/devMode";
export default function Index() {
const router = useRouter();
const [message, setMessage] = useState(<Text>Starting app...</Text>);
const [ready, setReady] = useState(false);
const hasHydrated = useAppStore((s) => s.hasHydrated);
const serverPort = useAppStore((s) => s.serverPort);
@@ -40,13 +41,18 @@ export default function Index() {
</Text>,
);
await devDelay(1500);
router.replace("/scanner");
//router.replace("/scanner");
setReady(true)
return;
}
setMessage(<Text>Checking for updates</Text>)
await devDelay(1500)
// TODO if theres an update go to update screen message :D
setMessage(<Text>Opening LST scan app</Text>);
await devDelay(3250);
router.replace("/scanner");
//router.replace("/scanner");
setReady(true)
} catch (error) {
console.log("Startup error", error);
setMessage(<Text>Something went wrong during startup.</Text>);
@@ -56,6 +62,9 @@ export default function Index() {
startup();
}, [hasHydrated, hasValidSetup, serverPort, router]);
if (ready) {
return <Redirect href="/(tabs)/scanner" />;
}
return (
<View
style={{

View File

@@ -1,32 +0,0 @@
import React from "react";
import { Text, View } from "react-native";
export default function scanner() {
return (
<View
style={{
flex: 1,
//justifyContent: "center",
alignItems: "center",
marginTop: 50,
}}
>
<View style={{ alignItems: "center", margin: 10 }}>
<Text style={{ fontSize: 20, fontWeight: "600" }}>LST Scanner</Text>
</View>
<View
style={{
marginTop: 50,
alignItems: "center",
}}
>
<Text>Relocate</Text>
<Text>0 / 4</Text>
</View>
{/* <View>
<Text>List of recent scanned pallets TBA</Text>
</View> */}
</View>
);
}

View File

@@ -4,7 +4,7 @@ import { useState } from "react";
import { Alert, Button, Text, TextInput, View } from "react-native";
import { useAppStore } from "../hooks/useAppStore";
export default function setup() {
export default function Setup() {
const router = useRouter();
const [auth, setAuth] = useState(false);
const [pin, setPin] = useState("");
@@ -40,6 +40,7 @@ export default function setup() {
updateAppState({
serverIp: serverIp.trim(),
serverPort: serverPort.trim(),
scannerId: scannerId?.trim(),
setupCompleted: true,
isRegistered: true,
});
@@ -80,7 +81,7 @@ export default function setup() {
borderRadius: 8,
}}
>
<Button title="Save Config" onPress={authCheck} />
<Button title="Submit" onPress={authCheck} />
</View>
</View>
) : (
@@ -136,7 +137,7 @@ export default function setup() {
<Button
title="Home"
onPress={() => {
router.push("/");
router.push("/(tabs)/scanner");
}}
/>
</View>

View File

@@ -0,0 +1,24 @@
import React from 'react'
import { View, Text } from 'react-native'
export default function LSTScanner() {
return (
<View><View style={{ alignItems: "center", margin: 10 }}>
<Text style={{ fontSize: 20, fontWeight: "600" }}>LST Scanner</Text>
</View>
<View
style={{
marginTop: 50,
alignItems: "center",
}}
>
<Text>Relocate</Text>
<Text>0 / 4</Text>
</View>
{/* <View>
<Text>List of recent scanned pallets TBA</Text>
</View> */}
</View>
)
}

View File

@@ -0,0 +1,31 @@
import React from 'react'
import { View, Text } from 'react-native'
import { ScannerTestScreen } from './ScannExample'
import { useAppStore } from '../hooks/useAppStore';
export default function ProdScanner() {
const scannerIdFromStore = useAppStore((s) => s.scannerId);
return (
<View>
<View style={{ alignItems: "center", margin: 10 }}>
<Text style={{ fontSize: 20, fontWeight: "600" }}>SScanner ID: {scannerIdFromStore}</Text>
</View>
<View
style={{
marginTop: 50,
alignItems: "center",
}}
>
<Text>Relocate</Text>
<Text>0 / 4</Text>
</View>
{/* <View>
<Text>List of recent scanned pallets TBA</Text>
</View> */}
<ScannerTestScreen />
</View>
)
}

View File

@@ -0,0 +1,58 @@
import { useEffect, useState } from "react";
import { Button, Text, View } from "react-native";
import { type ZebraScanResult, zebraScanner } from "../lib/ZebraScanner";
const STX = "\x02";
const ETX = "\x03";
export function ScannerTestScreen() {
const [lastResponse, setLastResponse] = useState("");
const handleScan = async (scan: ZebraScanResult) => {
console.log("Raw Zebra scan:", scan);
const scanned = scan.data;
// Hard-coded command example:
// <stx>98@{scanned}<etx>
const tcpMessage = `${STX}98@${scanned}${ETX}`;
console.log("TCP message to send:", tcpMessage);
console.log("TCP message visible:", `<stx>98@${scanned}<etx>`);
// Later this is where your TCP send goes.
// const response = await sendTcpMessage(tcpMessage);
const fakeResponse = `Would send TCP: <stx>98@${scanned}<etx>`;
console.log("TCP response:", fakeResponse);
setLastResponse(fakeResponse);
};
useEffect(() => {
zebraScanner.ensureProfile();
zebraScanner.startListening();
const sub = zebraScanner.addScanListener((scan) => {
console.log("SCAN:", scan);
handleScan(scan);
});
return () => {
sub.remove();
zebraScanner.stopListening();
};
}, []);
return (
<View style={{ padding: 20, gap: 12 }}>
<Button
title="Soft Trigger Scan"
onPress={() => zebraScanner.triggerScan()}
/>
<Text>Waiting for scan...</Text>
<Text>{lastResponse}</Text>
</View>
);
}

View File

@@ -0,0 +1,40 @@
import {
type EmitterSubscription,
NativeEventEmitter,
NativeModules,
} from "react-native";
const { ZebraScanner } = NativeModules;
const scannerEmitter = new NativeEventEmitter(ZebraScanner);
export type ZebraScanResult = {
data: string;
labelType?: string;
source?: string;
timestamp: number;
};
export const zebraScanner = {
startListening() {
ZebraScanner.startListening();
},
stopListening() {
ZebraScanner.stopListening();
},
triggerScan() {
ZebraScanner.triggerScan();
},
ensureProfile() {
ZebraScanner.ensureProfile();
},
addScanListener(
callback: (scan: ZebraScanResult) => void,
): EmitterSubscription {
return scannerEmitter.addListener("barcodeScanned", callback);
},
};

View File

@@ -0,0 +1,72 @@
import TcpSocket from "react-native-tcp-socket";
const STX = "\x02";
const ETX = "\x03";
type TcpResponse = {
success: boolean;
message: string;
data: string[];
};
/**
* Sends a Zebra-style TCP message:
* <STX>98@{scanned}<ETX>
*/
export async function sendTcpMessage(
scanned: string,
host: string,
port: number,
timeoutMs = 5000,
): Promise<TcpResponse> {
return new Promise((resolve) => {
const responses: string[] = [];
const client = TcpSocket.createConnection({ host, port }, () => {
const payload = `${STX}98@${scanned}${ETX}`;
console.log("Sending TCP (raw):", payload);
console.log("Sending TCP (visible):", `<stx>98@${scanned}<etx>`);
client.write(payload);
});
const timeout = setTimeout(() => {
client.destroy();
resolve({
success: false,
message: "TCP timeout",
data: responses,
});
}, timeoutMs);
client.on("data", (data) => {
const text = data.toString();
console.log("TCP received:", text);
responses.push(text);
});
client.on("error", (err) => {
clearTimeout(timeout);
client.destroy();
resolve({
success: false,
message: err.message,
data: responses,
});
});
client.on("close", () => {
clearTimeout(timeout);
resolve({
success: true,
message: "TCP complete",
data: responses,
});
});
});
}