From aa413511854553b5e8fd9983e9c67a3fb7105bea Mon Sep 17 00:00:00 2001 From: Douglas Barone Date: Mon, 15 May 2023 10:55:22 -0400 Subject: [PATCH] Initial commit --- dist/Printer.mjs | 86 ++++++++++++++++++ dist/index.js | 7 ++ dist/printers.mjs | 27 ++++++ dist/types.mjs | 1 + package-lock.json | 26 +++++- package.json | 6 +- src/Printer.mts | 219 ++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 29 +++++- src/printers.mts | 29 ++++++ src/types.mts | 78 +++++++++++++++++ tsconfig.json | 6 +- 11 files changed, 505 insertions(+), 9 deletions(-) create mode 100644 dist/Printer.mjs create mode 100644 dist/index.js create mode 100644 dist/printers.mjs create mode 100644 dist/types.mjs create mode 100644 src/Printer.mts create mode 100644 src/printers.mts create mode 100644 src/types.mts diff --git a/dist/Printer.mjs b/dist/Printer.mjs new file mode 100644 index 0000000..275f95c --- /dev/null +++ b/dist/Printer.mjs @@ -0,0 +1,86 @@ +const printerObjectIDConfigs = [ + { + model: 'm3655idn', + objectIds: { + model: '1.3.6.1.2.1.25.3.2.1.3.1', + serial: '1.3.6.1.2.1.43.5.1.1.17.1', + counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1', + toners: { + black: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.1', + max: '1.3.6.1.2.1.43.11.1.1.8.1.1', + model: '1.3.6.1.2.1.43.11.1.1.6.1.1' + } + } + } + }, + { + model: 'p6235cdn', + objectIds: { + model: '1.3.6.1.2.1.25.3.2.1.3.1', + serial: '1.3.6.1.2.1.43.5.1.1.17.1', + counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1', + toners: { + black: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.4', + max: '1.3.6.1.2.1.43.11.1.1.8.1.4', + model: '1.3.6.1.2.1.43.11.1.1.6.1.4' + }, + cyan: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.1', + max: '1.3.6.1.2.1.43.11.1.1.8.1.1', + model: '1.3.6.1.2.1.43.11.1.1.6.1.1' + }, + magenta: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.2', + max: '1.3.6.1.2.1.43.11.1.1.8.1.2', + model: '1.3.6.1.2.1.43.11.1.1.6.1.2' + }, + yellow: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.3', + max: '1.3.6.1.2.1.43.11.1.1.8.1.3', + model: '1.3.6.1.2.1.43.11.1.1.6.1.3' + } + } + } + }, + { + model: 'm2040dn', + objectIds: { + model: '1.3.6.1.2.1.25.3.2.1.3.1', + serial: '1.3.6.1.2.1.43.5.1.1.17.1', + counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1', + toners: { + black: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.1', + max: '1.3.6.1.2.1.43.11.1.1.8.1.1', + model: '1.3.6.1.2.1.43.11.1.1.6.1.1' + } + } + } + } +]; +export class Printer { + constructor(name, ip, model) { + this.name = name; + this.ip = ip; + this.model = model; + this.objectIds = printerObjectIDConfigs.find(x => x.model === model).objectIds; + } + oIDsArray() { + const oIDsArray = []; + function extractObjValues(obj) { + for (let key in obj) { + if (typeof obj[key] === 'object') { + extractObjValues(obj[key]); + } + else { + const oID = obj[key]; + oIDsArray.push(oID); + } + } + } + extractObjValues(this.objectIds); + return oIDsArray; + } +} diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..118be3c --- /dev/null +++ b/dist/index.js @@ -0,0 +1,7 @@ +import express from 'express'; +const app = express(); +const port = 3000; +app.get('/', (req, res) => { }); +app.listen(port, () => { + console.log(`http://localhost:${port}`); +}); diff --git a/dist/printers.mjs b/dist/printers.mjs new file mode 100644 index 0000000..9777107 --- /dev/null +++ b/dist/printers.mjs @@ -0,0 +1,27 @@ +export const printers = [ + { + name: 'p04', + ip: '10.7.0.134', + model: 'm3655idn' + }, + { + name: 'p05', + ip: '10.7.0.135', + model: 'm2040dn' + }, + { + name: 'p06', + ip: '10.7.0.136', + model: 'm2040dn' + }, + { + name: 'p07', + ip: '10.7.0.137', + model: 'm2040dn' + }, + { + name: 'p08', + ip: '10.7.0.138', + model: 'p6235cdn' + } +]; diff --git a/dist/types.mjs b/dist/types.mjs new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/types.mjs @@ -0,0 +1 @@ +export {}; diff --git a/package-lock.json b/package-lock.json index 0b59790..88e3443 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "ISC", "dependencies": { "body-parser": "^1.20.2", - "express": "^4.18.2" + "express": "^4.18.2", + "net-snmp": "^3.9.3" }, "devDependencies": { "@types/express": "^4.17.17", @@ -297,6 +298,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/asn1-ber": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/asn1-ber/-/asn1-ber-1.2.2.tgz", + "integrity": "sha512-CbNem/7hxrjSiOAOOTX4iZxu+0m3jiLqlsERQwwPM1IDR/22M8IPpA1VVndCLw5KtjRYyRODbvAEIfuTogNDng==" + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -1655,6 +1661,15 @@ "node": ">= 0.6" } }, + "node_modules/net-snmp": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/net-snmp/-/net-snmp-3.9.3.tgz", + "integrity": "sha512-d2QjmDu7qs7QrgI8WuzALoARpOhzX4dQX/E5XY/P8zQhBga3QCRZqoJ5Tt1yFxkNFL2D+NFPvbT4LrAur754yg==", + "dependencies": { + "asn1-ber": "^1.2.1", + "smart-buffer": "^4.1.0" + } + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -2208,6 +2223,15 @@ "semver": "bin/semver.js" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", diff --git a/package.json b/package.json index 4f61522..94d9f51 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,7 @@ "clean": "rimraf ./dist", "build": "npm run clean && tsc", "start": "node src", - "local": "ts-node-esm ./src/index.ts", - "dev": "nodemon src -e ts,json --exec 'npm run local'" + "dev": "nodemon --ext js,ts,mts,mjs ./src/index.ts --exec ts-node-esm" }, "keywords": [], "author": "", @@ -25,6 +24,7 @@ }, "dependencies": { "body-parser": "^1.20.2", - "express": "^4.18.2" + "express": "^4.18.2", + "net-snmp": "^3.9.3" } } diff --git a/src/Printer.mts b/src/Printer.mts new file mode 100644 index 0000000..6d5719a --- /dev/null +++ b/src/Printer.mts @@ -0,0 +1,219 @@ +import { + PrinterInfo, + PrinterModel, + PrinterObjectIDConfig, + PrinterObjectIDs, + Varbind, + VarbindString +} from './types.mjs' + +import snmp from 'net-snmp' + +const printerObjectIDConfigs: PrinterObjectIDConfig[] = [ + { + model: 'm3655idn', + objectIds: { + model: '1.3.6.1.2.1.25.3.2.1.3.1', + serial: '1.3.6.1.2.1.43.5.1.1.17.1', + counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1', + toners: { + black: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.1', + max: '1.3.6.1.2.1.43.11.1.1.8.1.1', + model: '1.3.6.1.2.1.43.11.1.1.6.1.1' + } + } + } + }, + { + model: 'p6235cdn', + objectIds: { + model: '1.3.6.1.2.1.25.3.2.1.3.1', + serial: '1.3.6.1.2.1.43.5.1.1.17.1', + counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1', + toners: { + black: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.4', + max: '1.3.6.1.2.1.43.11.1.1.8.1.4', + model: '1.3.6.1.2.1.43.11.1.1.6.1.4' + }, + cyan: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.1', + max: '1.3.6.1.2.1.43.11.1.1.8.1.1', + model: '1.3.6.1.2.1.43.11.1.1.6.1.1' + }, + magenta: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.2', + max: '1.3.6.1.2.1.43.11.1.1.8.1.2', + model: '1.3.6.1.2.1.43.11.1.1.6.1.2' + }, + yellow: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.3', + max: '1.3.6.1.2.1.43.11.1.1.8.1.3', + model: '1.3.6.1.2.1.43.11.1.1.6.1.3' + } + } + } + }, + { + model: 'm2040dn', + objectIds: { + model: '1.3.6.1.2.1.25.3.2.1.3.1', + serial: '1.3.6.1.2.1.43.5.1.1.17.1', + counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1', + toners: { + black: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.1', + max: '1.3.6.1.2.1.43.11.1.1.8.1.1', + model: '1.3.6.1.2.1.43.11.1.1.6.1.1' + } + } + } + } +] + +export class Printer { + constructor(name: string, ip: string, model: PrinterModel) { + this.name = name + this.ip = ip + this.model = model + + const objectIds = printerObjectIDConfigs.find( + x => x.model === model + )?.objectIds + + if (!objectIds) throw new Error(`No objectIds found for model ${model}`) + + this.objectIds = objectIds + + this.getPrinterInfo() + } + + name: string + ip: string + model: PrinterModel + objectIds: PrinterObjectIDs + + private oIDsArray(): string[] { + const oIDsArray: string[] = [] + function extractObjValues(obj: any) { + for (let key in obj) { + if (typeof obj[key] === 'object') { + extractObjValues(obj[key]) + } else { + const oID = obj[key] + oIDsArray.push(oID) + } + } + } + + extractObjValues(this.objectIds) + + return oIDsArray + } + + private deBufferizeVarbinds(varbinds: Varbind[]) { + const varbindsString: VarbindString[] = [] + + varbinds.forEach((varbind: Varbind) => { + if (varbind.value instanceof Buffer) + varbindsString.push({ ...varbind, value: varbind.value.toString() }) + else varbindsString.push({ ...varbind, value: varbind.value }) + }) + + return varbindsString + } + + private calcTonerLevelPercentage( + current: string | undefined, + max: string | undefined + ) { + if (typeof current === 'undefined' || typeof max === 'undefined') + throw new Error('current or max is undefined') + + return (+current! / +max!) * 100 + } + + private objectIDsToPrinterInfo(varbinds: Varbind[]) { + const snmpInfo = this.deBufferizeVarbinds(varbinds) + + const printerInfo: PrinterInfo = { + name: this.name, + ip: this.ip, + model: snmpInfo.find(x => x.oid === this.objectIds.model) + ?.value as string, + serial: snmpInfo.find(x => x.oid === this.objectIds.serial) + ?.value as string, + counter: snmpInfo.find(x => x.oid === this.objectIds.counter) + ?.value as string, + toners: { + black: { + level: this.calcTonerLevelPercentage( + snmpInfo.find(x => x.oid === this.objectIds.toners.black.current) + ?.value, + snmpInfo.find(x => x.oid === this.objectIds.toners.black.max)?.value + ), + model: snmpInfo.find(x => x.oid === this.objectIds.toners.black.model) + ?.value as string + }, + cyan: this.objectIds.toners.cyan + ? { + level: this.calcTonerLevelPercentage( + snmpInfo.find( + x => x.oid === this.objectIds.toners.cyan?.current + )?.value, + snmpInfo.find(x => x.oid === this.objectIds.toners.cyan?.max) + ?.value + ), + model: snmpInfo.find( + x => x.oid === this.objectIds.toners.cyan?.model + )?.value as string + } + : undefined, + magenta: this.objectIds.toners.magenta + ? { + level: this.calcTonerLevelPercentage( + snmpInfo.find( + x => x.oid === this.objectIds.toners.magenta?.current + )?.value, + snmpInfo.find(x => x.oid === this.objectIds.toners.magenta?.max) + ?.value + ), + model: snmpInfo.find( + x => x.oid === this.objectIds.toners.magenta?.model + )?.value as string + } + : undefined, + yellow: this.objectIds.toners.yellow + ? { + level: this.calcTonerLevelPercentage( + snmpInfo.find( + x => x.oid === this.objectIds.toners.yellow?.current + )?.value, + snmpInfo.find(x => x.oid === this.objectIds.toners.yellow?.max) + ?.value + ), + model: snmpInfo.find( + x => x.oid === this.objectIds.toners.yellow?.model + )?.value as string + } + : undefined + } + } + + return printerInfo + } + + async getPrinterInfo(): Promise { + const session = snmp.createSession(this.ip, 'public') + + return new Promise((resolve, reject) => { + session.get(this.oIDsArray(), (error, varbinds) => { + const varbindsString = this.deBufferizeVarbinds(varbinds) + const info = this.objectIDsToPrinterInfo(varbindsString) + + resolve(info) + }) + }) + } +} diff --git a/src/index.ts b/src/index.ts index d7b2278..5a26818 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,35 @@ import express from 'express' +import { printersDB } from './printers.mjs' +import { Printer } from './Printer.mjs' +import { PrinterInfo } from './types.mjs' const app = express() const port = 3000 -app.get('/', (req, res) => { - res.send('Hello World!') +const printers: Printer[] = printersDB.map( + printer => new Printer(printer.name, printer.ip, printer.model) +) + +app.get('/', async (req, res) => { + const printersInfo: PrinterInfo[] = await Promise.all( + printers.map(printer => printer.getPrinterInfo()) + ) + + res.send(printersInfo) +}) + +app.get('/:printerName', async (req, res) => { + const printerName = req.params.printerName + const printer = printers.find(printer => printer.name === printerName) + + if (!printer) { + res.status(404).send('Printer not found') + return + } + + const printerInfo: PrinterInfo = await printer.getPrinterInfo() + + res.send(printerInfo) }) app.listen(port, () => { diff --git a/src/printers.mts b/src/printers.mts new file mode 100644 index 0000000..f20105e --- /dev/null +++ b/src/printers.mts @@ -0,0 +1,29 @@ +import { Printer } from './types.mjs' + +export const printersDB: Printer[] = [ + { + name: 'p04', + ip: '10.7.0.134', + model: 'm3655idn' + }, + { + name: 'p05', + ip: '10.7.0.135', + model: 'm2040dn' + }, + { + name: 'p06', + ip: '10.7.0.136', + model: 'm2040dn' + }, + { + name: 'p07', + ip: '10.7.0.137', + model: 'm2040dn' + }, + { + name: 'p08', + ip: '10.7.0.138', + model: 'p6235cdn' + } +] diff --git a/src/types.mts b/src/types.mts new file mode 100644 index 0000000..1085ff1 --- /dev/null +++ b/src/types.mts @@ -0,0 +1,78 @@ +export type PrinterModel = 'm3655idn' | 'm2040dn' | 'p6235cdn' + +export type PrinterObjectIDs = { + model: string + serial: string + counter: string + toners: { + black: { + current: string + max: string + model: string + } + cyan?: { + current: string + max: string + model: string + } + magenta?: { + current: string + max: string + model: string + } + yellow?: { + current: string + max: string + model: string + } + } +} + +export type PrinterObjectIDConfig = { + model: PrinterModel + objectIds: PrinterObjectIDs +} + +export type PrinterInfo = { + name: string + ip: string + model: string + serial: string + counter: string + toners: { + black: { + level: number + model: string + } + cyan?: { + level: number + model: string + } + magenta?: { + level: number + model: string + } + yellow?: { + level: number + model: string + } + } +} + +export type Printer = { + name: string + ip: string + model: PrinterModel +} + +export type Varbind = { + oid: string + type: number + value: string | Buffer +} + +export type VarbindString = { + oid: string + type: number + value: string +} diff --git a/tsconfig.json b/tsconfig.json index 874a5d7..52ca3ca 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,7 +35,7 @@ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "allowImportingTsExtensions": true /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */, // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ @@ -83,7 +83,7 @@ /* Type Checking */ "strict": true /* Enable all strict type-checking options. */, - "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */, + "noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied 'any' type. */, // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ @@ -91,7 +91,7 @@ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + "noUnusedLocals": true /* Enable error reporting when local variables aren't read. */, // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */