From da04e9d35d2aecbc9c4a930fe9a217409c35aef2 Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Thu, 13 Mar 2025 21:37:04 -0500 Subject: [PATCH] test(rfid): intial trials built --- database/schema/rfidReaders.ts | 24 +++++ database/schema/rfidTags.ts | 28 ++++++ server/index.ts | 3 + server/services/rfid/controller/readTags.ts | 51 ++++++++++ .../rfid/controller/shippedOutTags.ts | 3 + server/services/rfid/controller/station1.ts | 3 + server/services/rfid/controller/station2.ts | 4 + server/services/rfid/controller/station3.ts | 4 + server/services/rfid/controller/station4.ts | 4 + server/services/rfid/controller/tagData.ts | 48 ++++++++++ server/services/rfid/rfidService.ts | 18 ++++ server/services/rfid/route/mgtEvents.ts | 95 +++++++++++++++++++ server/services/rfid/route/tagInfo.ts | 91 ++++++++++++++++++ server/services/server/utils/serverData.ts | 8 +- server/services/sqlServer/prodSqlServer.ts | 3 + 15 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 database/schema/rfidReaders.ts create mode 100644 database/schema/rfidTags.ts create mode 100644 server/services/rfid/controller/readTags.ts create mode 100644 server/services/rfid/controller/shippedOutTags.ts create mode 100644 server/services/rfid/controller/station1.ts create mode 100644 server/services/rfid/controller/station2.ts create mode 100644 server/services/rfid/controller/station3.ts create mode 100644 server/services/rfid/controller/station4.ts create mode 100644 server/services/rfid/controller/tagData.ts create mode 100644 server/services/rfid/rfidService.ts create mode 100644 server/services/rfid/route/mgtEvents.ts create mode 100644 server/services/rfid/route/tagInfo.ts diff --git a/database/schema/rfidReaders.ts b/database/schema/rfidReaders.ts new file mode 100644 index 0000000..ce69290 --- /dev/null +++ b/database/schema/rfidReaders.ts @@ -0,0 +1,24 @@ +import {text, pgTable, numeric, index, timestamp, boolean, uuid, uniqueIndex, jsonb} from "drizzle-orm/pg-core"; +import {createInsertSchema, createSelectSchema} from "drizzle-zod"; +import {z} from "zod"; + +export const rfidReaders = pgTable( + "rfidReaders", + { + rfidReader_id: uuid("rfidReader_id").defaultRandom().primaryKey(), + reader: text("reader"), + readerIP: text("readerIP"), + lastHeartBeat: timestamp("lastHeartBeat").defaultNow(), + }, + (table) => [ + // uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`), + uniqueIndex("reader").on(table.reader), + ] +); + +// Schema for inserting a user - can be used to validate API requests +// export const insertRolesSchema = createInsertSchema(roles, { +// name: z.string().min(3, {message: "Role name must be more than 3 letters"}), +// }); +// Schema for selecting a Expenses - can be used to validate API responses +export const selectRolesSchema = createSelectSchema(rfidReaders); diff --git a/database/schema/rfidTags.ts b/database/schema/rfidTags.ts new file mode 100644 index 0000000..e311802 --- /dev/null +++ b/database/schema/rfidTags.ts @@ -0,0 +1,28 @@ +import {text, pgTable, numeric, index, timestamp, boolean, uuid, uniqueIndex, jsonb} from "drizzle-orm/pg-core"; +import {createInsertSchema, createSelectSchema} from "drizzle-zod"; +import {z} from "zod"; + +export const rfidTags = pgTable( + "rfidTags", + { + rfidTag_id: uuid("rfidTag_id").defaultRandom().primaryKey(), + tagHex: text("tagHex"), + tag: text("tag"), + lastRead: timestamp("timeStamp").defaultNow(), + counts: jsonb("counts").notNull(), //.default([{area: 1, timesHere: 5}]).notNull(), + lastareaIn: text("lastareaIn").notNull(), + runningNumber: numeric("runningNumber").notNull(), + created_at: timestamp("created_at").defaultNow(), + }, + (table) => [ + // uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`), + uniqueIndex("tagHex").on(table.tagHex), + ] +); + +// Schema for inserting a user - can be used to validate API requests +// export const insertRolesSchema = createInsertSchema(roles, { +// name: z.string().min(3, {message: "Role name must be more than 3 letters"}), +// }); +// Schema for selecting a Expenses - can be used to validate API responses +export const selectRolesSchema = createSelectSchema(rfidTags); diff --git a/server/index.ts b/server/index.ts index 39e20d4..f92d15d 100644 --- a/server/index.ts +++ b/server/index.ts @@ -15,6 +15,8 @@ import tcpServer from "./services/tcpServer/tcpServer.js"; import ocme from "./services/ocme/ocmeService.js"; import sqlService from "./services/sqlServer/sqlService.js"; import logistics from "./services/logistics/logisticsService.js"; +import rfid from "./services/rfid/rfidService.js"; + import {db} from "../database/dbclient.js"; import {settings} from "../database/schema/settings.js"; import {count, eq} from "drizzle-orm"; @@ -80,6 +82,7 @@ const routes = [ tcpServer, sqlService, logistics, + rfid, ] as const; const appRoutes = routes.forEach((route) => { diff --git a/server/services/rfid/controller/readTags.ts b/server/services/rfid/controller/readTags.ts new file mode 100644 index 0000000..5ed585d --- /dev/null +++ b/server/services/rfid/controller/readTags.ts @@ -0,0 +1,51 @@ +import axios from "axios"; +import {createLog} from "../../logger/logger.js"; + +export const readTags = async (reader: string) => { + /** + * Start the read for x seconds then auto stop it + */ + + let token: string; + + const readers = [{reader: "reader1", readerIP: "10.10.1.222", lastHeartBeat: new Date()}]; + // get the auth token + const ip = readers.find((r) => r.reader === reader)?.readerIP; + + try { + const res = await axios.get(`https://${ip}/cloud/localRestLogin`, { + headers: {Authorization: `Basic ${btoa("admin:Zebra123!")}`}, + }); + token = res.data.message; + // start the read + try { + const res = await axios.put( + `https://${ip}/cloud/start`, + {}, + { + headers: {Authorization: `Bearer ${token}`}, + } + ); + + // stop after 5 seconds + + try { + const res = await axios.put( + `https://${ip}/cloud/stop`, + {}, + { + headers: {Authorization: `Bearer ${token}`}, + } + ); + } catch (error) { + createLog("error", "rfid", "rfid", `There was an error Stopping the read ${error}`); + } + } catch (error) { + createLog("error", "rfid", "rfid", `There was an error Starting the read ${error}`); + } + } catch (error) { + createLog("error", "rfid", "rfid", `There was an error Getting the token the read ${error}`); + } + + // start the read +}; diff --git a/server/services/rfid/controller/shippedOutTags.ts b/server/services/rfid/controller/shippedOutTags.ts new file mode 100644 index 0000000..add552f --- /dev/null +++ b/server/services/rfid/controller/shippedOutTags.ts @@ -0,0 +1,3 @@ +/** + * we will monitor shipped out pallets every hour if they get shipped out + */ diff --git a/server/services/rfid/controller/station1.ts b/server/services/rfid/controller/station1.ts new file mode 100644 index 0000000..c5822a6 --- /dev/null +++ b/server/services/rfid/controller/station1.ts @@ -0,0 +1,3 @@ +/** + * station 1 will just check for 10 tags + */ diff --git a/server/services/rfid/controller/station2.ts b/server/services/rfid/controller/station2.ts new file mode 100644 index 0000000..b687a82 --- /dev/null +++ b/server/services/rfid/controller/station2.ts @@ -0,0 +1,4 @@ +/** + * we will have 3 reader points here station2.1 will require 10 tags, 2.2 and 2.3 will require 1 + * + */ diff --git a/server/services/rfid/controller/station3.ts b/server/services/rfid/controller/station3.ts new file mode 100644 index 0000000..7c02601 --- /dev/null +++ b/server/services/rfid/controller/station3.ts @@ -0,0 +1,4 @@ +/** + * Phase 1 we link the tag to the line only the line3.x where x is the line number + * Phase 2 we will generate a label to be reprinted at staion 4 + */ diff --git a/server/services/rfid/controller/station4.ts b/server/services/rfid/controller/station4.ts new file mode 100644 index 0000000..351928e --- /dev/null +++ b/server/services/rfid/controller/station4.ts @@ -0,0 +1,4 @@ +/** + * Phase 1 we will just follow the logic of printing a label when we are told requested to based on this tag. + * Phase 2 we will just reprint the tag that was generated at the line + */ diff --git a/server/services/rfid/controller/tagData.ts b/server/services/rfid/controller/tagData.ts new file mode 100644 index 0000000..443fd2a --- /dev/null +++ b/server/services/rfid/controller/tagData.ts @@ -0,0 +1,48 @@ +type TagData = {tagHex: string; reader: string; tag: string; timeStamp: Date}; +export const tagData = async (data: TagData[]) => { + /** + * We will always update a tag + */ + + // dyco wall makes sure we have 10 tags + const station1 = data.some((n) => n.reader.includes("reader1")); + + // by the dyco infeed + const station2 = data.some((n) => n.reader.includes("station2")); + + // at the end of each line + const station3 = data.some((n) => n.reader.includes("line3")); + + // at the wrapper + const station4 = data.some((n) => n.reader.includes("wrapper")); + + // station checks + if (station1 && data.length != 10) { + console.log(`There are only ${data.length}`); + } + + if (station2) { + console.log(`There are only ${data.length}`); + } + + if (station3) { + // make sure we only have one tag or dont update + if (data.length != 1) { + console.log(`There are ${data.length} tags, and ${data[0].reader} only allows 1 tag to create a label.`); + //throw new Error("There are more than 1 tag at this station and it is not allowed"); + } else { + console.log("Generate the tag and link it to the tag."); + } + } + + if (station4) { + if (data.length != 1) { + console.log( + `There are ${data.length} tags and this ${data[0].reader} only allows 1 tag to create a label.` + ); + //throw new Error("There are more than 1 tag at this station and it is not allowed"); + } else { + console.log("reprint the label linked to the tag."); + } + } +}; diff --git a/server/services/rfid/rfidService.ts b/server/services/rfid/rfidService.ts new file mode 100644 index 0000000..dd97f7d --- /dev/null +++ b/server/services/rfid/rfidService.ts @@ -0,0 +1,18 @@ +import {OpenAPIHono} from "@hono/zod-openapi"; + +import mgtEvents from "./route/mgtEvents.js"; +import tagInfo from "./route/tagInfo.js"; +const app = new OpenAPIHono(); + +const routes = [ + mgtEvents, + tagInfo, + // settings +] as const; + +// app.route("/server", modules); +const appRoutes = routes.forEach((route) => { + app.route("/rfid", route); +}); + +export default app; diff --git a/server/services/rfid/route/mgtEvents.ts b/server/services/rfid/route/mgtEvents.ts new file mode 100644 index 0000000..b8b74f9 --- /dev/null +++ b/server/services/rfid/route/mgtEvents.ts @@ -0,0 +1,95 @@ +//http://usday1vms006:4000/api/v1/zebra/wrapper1 +import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; +import {readTags} from "../controller/readTags.js"; +import {createLog} from "../../logger/logger.js"; + +// Define the response schema +const responseSchema = z.object({ + success: z.boolean().openapi({example: true}), + message: z.string().optional(), +}); + +const app = new OpenAPIHono(); +let lastGpiTimestamp = 0; + +const ParamsSchema = z.object({ + reader: z + .string() + .min(3) + .openapi({ + param: { + name: "id", + in: "path", + }, + example: "1212121", + }), +}); + +app.openapi( + createRoute({ + tags: ["server"], + summary: "Adds a new module", + method: "post", + path: "/mgtevents/{reader}", + request: { + params: ParamsSchema, + }, + responses: { + 200: { + content: { + "application/json": {schema: responseSchema}, + }, + description: "Response message", + }, + 400: { + content: { + "application/json": { + schema: z.object({message: z.string().optional().openapi({example: "Internal Server error"})}), + }, + }, + description: "Internal Server Error", + }, + 401: { + content: { + "application/json": { + schema: z.object({message: z.string().optional().openapi({example: "Unauthenticated"})}), + }, + }, + description: "Unauthorized", + }, + 500: { + content: { + "application/json": { + schema: z.object({message: z.string().optional().openapi({example: "Internal Server error"})}), + }, + }, + description: "Internal Server Error", + }, + }, + }), + async (c) => { + const {reader} = c.req.valid("param"); + const body = await c.req.json(); + + if (body.type === "heartbeat") { + console.log("Heartbeat"); + } + + if (body.type === "gpi" && body.data.state === "HIGH") { + const eventTimestamp = new Date(body.timestamp).getTime(); // Convert ISO timestamp to milliseconds + + if (eventTimestamp - lastGpiTimestamp > 10) { + // Check if it's been more than 2ms + lastGpiTimestamp = eventTimestamp; // Update last seen timestamp + createLog("info", "rfid", "rfid", `${reader} is reading a tag.`); + readTags(reader); + } else { + console.log("Duplicate GPI event ignored."); + } + } + + return c.json({success: true, message: `New info from ${reader}`}, 200); + } +); + +export default app; diff --git a/server/services/rfid/route/tagInfo.ts b/server/services/rfid/route/tagInfo.ts new file mode 100644 index 0000000..694677a --- /dev/null +++ b/server/services/rfid/route/tagInfo.ts @@ -0,0 +1,91 @@ +//http://usday1vms006:4000/api/v1/zebra/wrapper1 +import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; +import {ConsoleLogWriter} from "drizzle-orm"; +import {tagData} from "../controller/tagData.js"; + +// Define the response schema +const responseSchema = z.object({ + success: z.boolean().openapi({example: true}), + message: z.string().optional(), +}); + +const app = new OpenAPIHono(); + +const ParamsSchema = z.object({ + reader: z + .string() + .min(3) + .openapi({ + param: { + name: "id", + in: "path", + }, + example: "1212121", + }), +}); + +app.openapi( + createRoute({ + tags: ["server"], + summary: "Adds a new module", + method: "post", + path: "/taginfo/{reader}", + request: { + params: ParamsSchema, + }, + responses: { + 200: { + content: { + "application/json": {schema: responseSchema}, + }, + description: "Response message", + }, + 400: { + content: { + "application/json": { + schema: z.object({message: z.string().optional().openapi({example: "Internal Server error"})}), + }, + }, + description: "Internal Server Error", + }, + 401: { + content: { + "application/json": { + schema: z.object({message: z.string().optional().openapi({example: "Unauthenticated"})}), + }, + }, + description: "Unauthorized", + }, + 500: { + content: { + "application/json": { + schema: z.object({message: z.string().optional().openapi({example: "Internal Server error"})}), + }, + }, + description: "Internal Server Error", + }, + }, + }), + async (c) => { + const {reader} = c.req.valid("param"); + let tagdata: any = []; + const body = await c.req.json(); + //console.log(`Tag: ${Buffer.from(body.idHex, "hex").toString("utf-8")}, ${body[key].data.idHex}`); + + for (let i = 0; i < body.length; i++) { + const tag = Buffer.from(body[i].data.idHex, "hex").toString("utf-8"); + if (tag.includes("ALPLA")) { + tagdata = [ + ...tagdata, + {tagHex: body[i].data.idHex, reader: reader, tag: tag, timeStamp: body[i].timestamp}, + ]; + } + } + + tagData(tagdata); + + return c.json({success: true, message: `New info from ${reader}`}, 200); + } +); + +export default app; diff --git a/server/services/server/utils/serverData.ts b/server/services/server/utils/serverData.ts index 55ecc4e..bb083a8 100644 --- a/server/services/server/utils/serverData.ts +++ b/server/services/server/utils/serverData.ts @@ -8,7 +8,13 @@ import fs from "fs"; export const serversCheckPoint = async () => { let servers: any; - fs.readFile("./data.json", "utf8", (err, data) => { + let filePath: string; + if (process.env.NODE_ENV === "development") { + filePath = "./server/services/server/utils/serverData.json"; + } else { + filePath = "./dist/server/services/server/utils/serverData.json"; + } + fs.readFile(filePath, "utf8", (err, data) => { if (err) { console.error("Error reading JSON file:", err); return; diff --git a/server/services/sqlServer/prodSqlServer.ts b/server/services/sqlServer/prodSqlServer.ts index cd4d39d..d301b09 100644 --- a/server/services/sqlServer/prodSqlServer.ts +++ b/server/services/sqlServer/prodSqlServer.ts @@ -18,6 +18,8 @@ export const initializeProdPool = async () => { return {success: false, message: "The server is not installed."}; } + return; + // make sure the server is not set to localhost this will prevent some weird issues later but can be localhost on the dev const serverLoc = await db.select().from(settings).where(eq(settings.name, "dbServer")); if (serverLoc[0].value === "localhost" && process.env.NODE_ENV !== "development") { @@ -44,6 +46,7 @@ export const initializeProdPool = async () => { }; export const closePool = async () => { + return; try { await pool.close(); createLog("info", "lst", "sqlProd", "Connection pool closed");