Skip to content

Commit

Permalink
better error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Brandawg93 committed Dec 28, 2023
1 parent aafa3b5 commit 72d4795
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 52 deletions.
4 changes: 2 additions & 2 deletions __tests__/server/nut.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ END LIST VAR ups`
describe('Nut', () => {
it('should get devices', async () => {
const nut = new Nut('localhost', 3493)
const mockSocket = jest.spyOn(nut, 'getCommand')
const mockSocket = jest.spyOn(nut as any, 'getCommand')
mockSocket.mockImplementation(() => {
return Promise.resolve('BEGIN LIST UPS\nUPS ups "cyberpower"\nUPS ups2 "cyberpower"\nEND LIST UPS')
})
Expand All @@ -62,7 +62,7 @@ describe('Nut', () => {

it('should work with multiple ups devices on the same server', async () => {
const nut = new Nut('localhost', 3493)
const mockSocket = jest.spyOn(nut, 'getCommand')
const mockSocket = jest.spyOn(nut as any, 'getCommand')
mockSocket.mockImplementation(() => {
return Promise.resolve(listVarUps)
})
Expand Down
65 changes: 36 additions & 29 deletions src/app/api/v1/devices/[[...device]]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,53 @@ import { NextRequest, NextResponse } from 'next/server'
import { Nut } from '@/server/nut'

export async function GET(request: NextRequest, { params }: { params: any }) {
if (params?.device?.length > 2) {
return NextResponse.json('Only one device and/or parameter is supported', { status: 400 })
}

const nut = new Nut(
process.env.NUT_HOST || 'localhost',
parseInt(process.env.NUT_PORT || '3493'),
process.env.USERNAME,
process.env.PASSWORD
)
await nut.connect()
const devices = await nut.getDevices()

// api/v1/devices
if (!params || !params.device || params.device.length === 0) {
const promises = devices.map((device) => nut.getData(device.name))
const data = await Promise.all(promises)
await nut.close()
return NextResponse.json(data)
}

if (params.device.length > 2) {
return NextResponse.json('Only one device is supported', { status: 400 })
}

const device = params.device[0]
if (!devices.includes(device)) {
return NextResponse.json(`Device ${device} not found`, { status: 404 })
}
const data = await nut.getData(device)
await nut.close()

// api/v1/devices/[device]/[param]
if (params.device.length === 2) {
const paramString = params.device[1] as keyof typeof data
const value = data[paramString]
if (value === undefined) {
return NextResponse.json(`Parameter ${paramString} not found`, {
status: 404,
})
if (params?.device?.length === 2) {
const device = params.device[0]
const param = params.device[1]
const paramString = param as keyof typeof data
try {
const data = await nut.getVar(device, param)
await nut.close()
if (data === undefined) {
return NextResponse.json(`Parameter ${paramString.toString()} not found`, {
status: 404,
})
}
return NextResponse.json(data)
} catch (e) {
return NextResponse.json(`Parameter ${paramString.toString()} on device ${device} not found`, { status: 404 })
}
return NextResponse.json(value)
}

// api/v1/devices/[device]
// api/v1/devices/[device]
if (params?.device?.length === 1) {
const device = params.device[0]
try {
const data = await nut.getData(device)
await nut.close()
return NextResponse.json(data)
} catch (e) {
return NextResponse.json(`Device ${device} not found`, { status: 404 })
}
}

// api/v1/devices
const devices = await nut.getDevices()
const promises = devices.map((device) => nut.getData(device.name))
const data = await Promise.all(promises)
await nut.close()
return NextResponse.json(data)
}
74 changes: 54 additions & 20 deletions src/server/nut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ export class Nut {
this.socket = new PromiseSocket()
}

public async getCommand(command: string, until?: string) {
private async getCommand(command: string, until?: string, start = 'BEGIN') {
await this.socket.write(command)
const data = await this.socket.readAll(command, until)
if (data.startsWith('ERR') || !data.startsWith(start)) {
throw new Error('Invalid response')
}
return data
}

Expand Down Expand Up @@ -51,29 +54,60 @@ export class Nut {

public async getDevices(): Promise<Array<DEVICE_LIST>> {
const command = 'LIST UPS'
const devices: Array<DEVICE_LIST> = []
let data = await this.getCommand(command)
data = data.replace(`BEGIN ${command}`, '')
data = data.replace(`END ${command}`, '')
data = data.replace(/"/g, '')
const devices = data.trim().split('\n')
return devices.map((device) => ({ name: device.split(' ')[1], description: device.split(' ')[2] }))
for (const line of data.split('\n')) {
if (line.startsWith('UPS')) {
const name = line.split('"')[0].replace('UPS ', '').trim()
const description = line.split('"')[1].trim()
devices.push({ name, description })
}
}
return devices
}

public async getData(device = 'UPS', delimiter = '.'): Promise<DEVICE> {
public async getData(device = 'UPS'): Promise<DEVICE> {
const command = `LIST VAR ${device}`
const properties: any = {}
let data = await this.getCommand(command)
for (const line of data.split('\n')) {
if (line.startsWith('VAR')) {
const key = line.split('"')[0].replace(`VAR ${device} `, '').trim()
const value = line.split('"')[1]
properties[key] = value
}
}

return properties as DEVICE
}

public async getDescription(device = 'UPS'): Promise<string> {
const command = `GET UPSDESC ${device}`
const data = await this.getCommand(command)
return data.split('"')[1].trim()
}

public async getCommands(device = 'UPS'): Promise<Array<string>> {
const command = `LIST CMD ${device}`
const commands: Array<string> = []
let data = await this.getCommand(command)
data = data.replace(`BEGIN ${command}`, '')
data = data.replace(`END ${command}`, '')
const regex = new RegExp(`VAR ${device} `, 'g')
data = data.replace(regex, '')
data = data.replace(/"/g, '')
const props = data.trim().split('\n')
const values: any = {}
props.forEach((prop) => {
const key = prop.substring(0, prop.indexOf(' ')).replace(/\./g, delimiter)
const value = prop.substring(prop.indexOf(' ') + 1)
values[key] = value
})
return values as DEVICE
for (const line of data.split('\n')) {
if (line.startsWith('CMD')) {
const command = line.split('"')[0].replace(`CMD ${device} `, '').trim()
commands.push(command)
}
}

return commands
}

public async getCommandDescription(device = 'UPS', command: string): Promise<string> {
const data = await this.getCommand(`GET CMDDESC ${device} ${command}`, '\n', 'CMDDESC')
return data.split('"')[1].trim()
}

public async getVar(device = 'UPS', variable: string): Promise<string> {
const data = await this.getCommand(`GET VAR ${device} ${variable}`, '\n', 'VAR')
return data.split('"')[1].trim()
}
}
5 changes: 4 additions & 1 deletion src/server/promise-socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ export default class PromiseSocket {
})
}

async readAll(command: string, until: string = `END ${command}`) {
async readAll(command: string, until: string = `END ${command}`, timeout = 1000) {
return new Promise<string>((resolve, reject) => {
let buf = ''
setTimeout(() => {
reject(new Error('Timeout'))
}, timeout)
this.innerSok.on('data', (data) => {
buf += Buffer.from(data).toString()
if (buf.includes(until)) {
Expand Down

0 comments on commit 72d4795

Please sign in to comment.