1.3.0 โ€ข Published 6 months ago

@jaarnio/tripplite-pdu-sdk v1.3.0

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

Tripplite PDU SDK with WebSocket Server

A comprehensive Node.js SDK for interacting with Tripplite PDU devices, featuring a real-time WebSocket server for building monitoring dashboards and applications.

โœจ Features

  • ๐Ÿ”Œ Direct PDU Integration - Connect to Tripplite PDU devices via HTTP/HTTPS
  • ๐ŸŒ Real-time WebSocket Server - Live monitoring with up to 16 concurrent clients
  • โšก Load Control - Turn loads on/off/cycle with instant feedback
  • ๐Ÿ”„ Auto-reconnection - Handles authentication token expiration automatically
  • ๐Ÿ“Š Load-specific Subscriptions - Efficient updates only for loads you care about
  • ๐Ÿ›ก๏ธ Robust Error Handling - Comprehensive retry logic and graceful failures
  • ๐Ÿ”ง Easy Configuration - Environment variables for seamless deployment

๐Ÿš€ Quick Start

Server-Side Setup (Deploy the WebSocket Server)

1. Install the package:

npm install @jaarnio/tripplite-pdu-sdk

2. Create environment configuration:

# .env file
TRIPPLITE_PDU_HOST=192.168.1.100
TRIPPLITE_PDU_PORT=443
TRIPPLITE_PDU_USERNAME=your_username
TRIPPLITE_PDU_PASSWORD=your_password
TRIPPLITE_PDU_DEVICE_ID=your_device_id

# WebSocket server settings (optional)
WS_PORT=8081
WS_MAX_CLIENTS=16
PDU_POLL_INTERVAL=5000

3. Start the WebSocket server:

# Using npm script
npm run server

# Or directly
npx tripplite-server

# Development mode with debug logging
npm run server:dev

The server will:

  • โœ… Connect to your PDU
  • โœ… Start polling for state changes every 5 seconds
  • โœ… Accept WebSocket connections on port 8081
  • โœ… Handle up to 16 concurrent clients

Client-Side Integration (Build Dashboards/Apps)

1. Install the client SDK:

npm install @jaarnio/tripplite-pdu-sdk

2. Create a simple monitoring client:

const TripplitePDUClient = require('@jaarnio/tripplite-pdu-sdk/websocket-server/client-sdk');

const client = new TripplitePDUClient({
    url: 'ws://your-server:8081',
    onConnect: () => console.log('Connected to PDU server!'),
    onStateChange: (change) => {
        console.log(`Load ${change.loadId}: ${change.previousState} โ†’ ${change.currentState}`);
        updateDashboard(change);
    },
    onActionResult: (result) => {
        console.log(`Action ${result.action} on Load ${result.loadId}: ${result.success ? 'SUCCESS' : 'FAILED'}`);
    }
});

// Connect and subscribe to specific loads
async function start() {
    await client.connect();
    
    // Monitor loads 1, 2, and 3
    client.subscribe([1, 2, 3]);
    
    // Control a load
    client.sendAction(1, 'on');   // Turn load 1 ON
    client.sendAction(2, 'off');  // Turn load 2 OFF
    client.sendAction(3, 'cycle'); // Cycle load 3 (if currently ON)
}

start();

๐Ÿ“‹ Usage Examples

Building a Web Dashboard

HTML:

<!DOCTYPE html>
<html>
<head>
    <title>PDU Dashboard</title>
    <style>
        .load { margin: 10px; padding: 15px; border: 1px solid #ccc; }
        .load.on { background-color: #d4edda; }
        .load.off { background-color: #f8d7da; }
        button { margin: 5px; padding: 5px 10px; }
    </style>
</head>
<body>
    <h1>PDU Load Dashboard</h1>
    <div id="loads"></div>
    
    <script src="https://cdn.jsdelivr.net/npm/ws@8/browser.js"></script>
    <script>
        class PDUDashboard {
            constructor() {
                this.ws = new WebSocket('ws://localhost:8081');
                this.loads = new Map();
                this.setupEventHandlers();
            }

            setupEventHandlers() {
                this.ws.onmessage = (event) => {
                    const message = JSON.parse(event.data);
                    
                    if (message.type === 'welcome') {
                        this.subscribe([1, 2, 3, 4, 5, 6, 7, 8]);
                    } else if (message.type === 'subscribed') {
                        this.renderLoads(message.currentStates);
                    } else if (message.type === 'stateChange') {
                        this.updateLoad(message);
                    }
                };
            }

            subscribe(loadIds) {
                this.ws.send(JSON.stringify({
                    type: 'subscribe',
                    loadIds: loadIds
                }));
            }

            sendAction(loadId, action) {
                this.ws.send(JSON.stringify({
                    type: 'action',
                    loadId: loadId,
                    action: action
                }));
            }

            renderLoads(states) {
                const container = document.getElementById('loads');
                container.innerHTML = '';
                
                states.forEach(load => {
                    this.loads.set(load.loadId, load);
                    const div = this.createLoadElement(load);
                    container.appendChild(div);
                });
            }

            createLoadElement(load) {
                const div = document.createElement('div');
                div.className = `load ${load.state === 'LOAD_STATE_ON' ? 'on' : 'off'}`;
                div.id = `load-${load.loadId}`;
                
                const isOn = load.state === 'LOAD_STATE_ON';
                div.innerHTML = `
                    <h3>Load ${load.loadId} - ${load.name}</h3>
                    <p>Status: <strong>${isOn ? 'ON' : 'OFF'}</strong></p>
                    <button onclick="dashboard.sendAction('${load.loadId}', 'on')">Turn ON</button>
                    <button onclick="dashboard.sendAction('${load.loadId}', 'off')">Turn OFF</button>
                    ${isOn ? `<button onclick="dashboard.sendAction('${load.loadId}', 'cycle')">CYCLE</button>` : ''}
                `;
                
                return div;
            }

            updateLoad(change) {
                const element = document.getElementById(`load-${change.loadId}`);
                if (element) {
                    const load = this.loads.get(change.loadId);
                    load.state = change.currentState;
                    element.outerHTML = this.createLoadElement(load).outerHTML;
                }
            }
        }

        const dashboard = new PDUDashboard();
    </script>
</body>
</html>

Node.js Monitoring Service

const TripplitePDUClient = require('@jaarnio/tripplite-pdu-sdk/websocket-server/client-sdk');

class PDUMonitoringService {
    constructor() {
        this.client = new TripplitePDUClient({
            url: 'ws://pdu-server:8081',
            onConnect: () => this.onConnect(),
            onStateChange: (change) => this.handleStateChange(change),
            onPDUStatus: (status) => this.handlePDUStatus(status),
            onError: (error) => this.handleError(error)
        });
        
        this.alertThresholds = new Map();
        this.setupAlerts();
    }

    async start() {
        await this.client.connect();
        
        // Monitor critical loads
        this.client.subscribe([1, 2, 3, 4, 5, 6, 7, 8]);
        
        console.log('PDU Monitoring Service started');
    }

    setupAlerts() {
        // Set up alert conditions
        this.alertThresholds.set('1', { critical: true, name: 'Main Server' });
        this.alertThresholds.set('2', { critical: true, name: 'Network Switch' });
        this.alertThresholds.set('3', { critical: false, name: 'Backup System' });
    }

    handleStateChange(change) {
        const alert = this.alertThresholds.get(change.loadId);
        
        if (alert && change.currentState === 'LOAD_STATE_OFF') {
            this.sendAlert({
                severity: alert.critical ? 'CRITICAL' : 'WARNING',
                message: `${alert.name} (Load ${change.loadId}) has turned OFF`,
                timestamp: change.timestamp
            });
        }
        
        // Log all changes
        console.log(`[${change.timestamp}] Load ${change.loadId} (${change.name}): ${change.previousState} โ†’ ${change.currentState}`);
    }

    handlePDUStatus(status) {
        if (status.status === 'disconnected') {
            this.sendAlert({
                severity: 'CRITICAL',
                message: `PDU connection lost: ${status.error}`,
                timestamp: status.timestamp
            });
        } else if (status.status === 'connected') {
            console.log(`PDU reconnected: ${status.message}`);
        }
    }

    sendAlert(alert) {
        console.error(`๐Ÿšจ ${alert.severity}: ${alert.message}`);
        
        // Here you could integrate with:
        // - Email notifications
        // - Slack/Teams webhooks
        // - SMS alerts
        // - Monitoring systems (Prometheus, etc.)
    }

    handleError(error) {
        console.error('PDU Monitoring Error:', error.message);
    }
}

// Start the monitoring service
const monitor = new PDUMonitoringService();
monitor.start().catch(console.error);

๐Ÿ”ง Configuration Options

Environment Variables

VariableDefaultDescription
TRIPPLITE_PDU_HOSTRequiredPDU IP address or hostname
TRIPPLITE_PDU_PORT443PDU HTTPS port
TRIPPLITE_PDU_USERNAMERequiredPDU login username
TRIPPLITE_PDU_PASSWORDRequiredPDU login password
TRIPPLITE_PDU_DEVICE_IDRequiredPDU device identifier
WS_PORT8081WebSocket server port
WS_MAX_CLIENTS16Maximum concurrent clients
PDU_POLL_INTERVAL5000PDU polling interval (ms)
PDU_MAX_RETRIES3Max retry attempts
PDU_RETRY_DELAY5000Retry delay (ms)
WS_HEARTBEAT_INTERVAL30000Client heartbeat interval (ms)
WS_CLIENT_TIMEOUT60000Client timeout (ms)
WS_DEBUGfalseEnable debug logging

Client SDK Options

const client = new TripplitePDUClient({
    url: 'ws://localhost:8081',        // WebSocket server URL
    autoReconnect: true,               // Auto-reconnect on disconnect
    reconnectInterval: 5000,           // Reconnect delay (ms)
    name: 'MyDashboard',              // Client name for logging
    
    // Event handlers
    onConnect: () => {},               // Connected to server
    onDisconnect: (code, reason) => {},// Disconnected from server
    onStateChange: (change) => {},     // Load state changed
    onActionResult: (result) => {},    // Action completed
    onPDUStatus: (status) => {},       // PDU connection status
    onError: (error) => {}             // Error occurred
});

๐Ÿ“ก WebSocket Protocol

Client โ†’ Server Messages

Subscribe to loads:

{
    "type": "subscribe",
    "loadIds": ["1", "2", "3"]
}

Send action:

{
    "type": "action",
    "loadId": "1",
    "action": "on",
    "byName": false
}

Ping server:

{
    "type": "ping",
    "timestamp": "2025-01-01T12:00:00Z"
}

Server โ†’ Client Messages

State change notification:

{
    "type": "stateChange",
    "loadId": "1",
    "name": "Load01",
    "previousState": "LOAD_STATE_OFF",
    "currentState": "LOAD_STATE_ON",
    "timestamp": "2025-01-01T12:00:00Z"
}

Action result:

{
    "type": "actionResult",
    "loadId": "1",
    "action": "on",
    "success": true,
    "timestamp": "2025-01-01T12:00:00Z"
}

๐Ÿš€ Deployment

Production Deployment

1. Using PM2 (Recommended):

# Install PM2
npm install -g pm2

# Create ecosystem file
cat > ecosystem.config.js << EOF
module.exports = {
  apps: [{
    name: 'tripplite-pdu-server',
    script: 'node_modules/@jaarnio/tripplite-pdu-sdk/websocket-server/start-server.js',
    instances: 1,
    autorestart: true,
    watch: false,
    max_memory_restart: '1G',
    env: {
      NODE_ENV: 'production'
    }
  }]
}
EOF

# Start with PM2
pm2 start ecosystem.config.js
pm2 save
pm2 startup

2. Using Docker:

FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY .env ./
EXPOSE 8081

CMD ["npm", "run", "server"]

3. Using systemd service:

[Unit]
Description=Tripplite PDU WebSocket Server
After=network.target

[Service]
Type=simple
User=nodejs
WorkingDirectory=/opt/tripplite-pdu
ExecStart=/usr/bin/node node_modules/@jaarnio/tripplite-pdu-sdk/websocket-server/start-server.js
Restart=always
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

๐Ÿงช Testing

Test client connection:

npm run client:test

Test action commands:

npm run client:actions

Debug mode:

npm run server:dev

๐Ÿค Integration Examples

Express.js REST API Bridge

const express = require('express');
const TripplitePDUClient = require('@jaarnio/tripplite-pdu-sdk/websocket-server/client-sdk');

const app = express();
app.use(express.json());

const pduClient = new TripplitePDUClient({
    url: 'ws://localhost:8081'
});

// REST endpoints
app.get('/api/loads', (req, res) => {
    res.json(pduClient.getAllLoadStates());
});

app.post('/api/loads/:id/action', async (req, res) => {
    const { id } = req.params;
    const { action } = req.body;
    
    const success = pduClient.sendAction(id, action);
    res.json({ success, loadId: id, action });
});

pduClient.connect().then(() => {
    pduClient.subscribe([1, 2, 3, 4, 5, 6, 7, 8]);
    app.listen(3000, () => console.log('REST API listening on port 3000'));
});

React Dashboard Component

import React, { useState, useEffect } from 'react';

const PDUDashboard = () => {
    const [loads, setLoads] = useState({});
    const [connected, setConnected] = useState(false);
    const [ws, setWs] = useState(null);

    useEffect(() => {
        const websocket = new WebSocket('ws://localhost:8081');
        
        websocket.onopen = () => {
            setConnected(true);
            websocket.send(JSON.stringify({
                type: 'subscribe',
                loadIds: ['1', '2', '3', '4']
            }));
        };

        websocket.onmessage = (event) => {
            const message = JSON.parse(event.data);
            
            if (message.type === 'subscribed') {
                const loadMap = {};
                message.currentStates.forEach(load => {
                    loadMap[load.loadId] = load;
                });
                setLoads(loadMap);
            } else if (message.type === 'stateChange') {
                setLoads(prev => ({
                    ...prev,
                    [message.loadId]: {
                        ...prev[message.loadId],
                        state: message.currentState
                    }
                }));
            }
        };

        websocket.onclose = () => setConnected(false);
        setWs(websocket);

        return () => websocket.close();
    }, []);

    const sendAction = (loadId, action) => {
        if (ws && connected) {
            ws.send(JSON.stringify({
                type: 'action',
                loadId,
                action
            }));
        }
    };

    return (
        <div className="pdu-dashboard">
            <h2>PDU Dashboard {connected ? '๐ŸŸข' : '๐Ÿ”ด'}</h2>
            <div className="loads-grid">
                {Object.values(loads).map(load => (
                    <div key={load.id} className={`load-card ${load.state === 'LOAD_STATE_ON' ? 'on' : 'off'}`}>
                        <h3>Load {load.id}</h3>
                        <p>{load.name}</p>
                        <p>Status: <strong>{load.state === 'LOAD_STATE_ON' ? 'ON' : 'OFF'}</strong></p>
                        <div className="actions">
                            <button onClick={() => sendAction(load.id, 'on')}>ON</button>
                            <button onClick={() => sendAction(load.id, 'off')}>OFF</button>
                            {load.state === 'LOAD_STATE_ON' && (
                                <button onClick={() => sendAction(load.id, 'cycle')}>CYCLE</button>
                            )}
                        </div>
                    </div>
                ))}
            </div>
        </div>
    );
};

export default PDUDashboard;

๐Ÿ“š API Reference

Core SDK (Direct PDU Access)

const TripplitePDU = require('@jaarnio/tripplite-pdu-sdk');

// Basic usage
const pdu = new TripplitePDU({
    host: '192.168.1.100',
    port: 443,
    username: 'admin',
    password: 'password',
    deviceId: 'PDU001'
});

// Or use environment variables
const pdu = new TripplitePDU(); // Reads from .env

WebSocket Client SDK

const TripplitePDUClient = require('@jaarnio/tripplite-pdu-sdk/websocket-server/client-sdk');

const client = new TripplitePDUClient(options);
await client.connect();
client.subscribe([1, 2, 3]);
client.sendAction(1, 'on');
const state = client.getLoadState(1);
client.disconnect();

โš ๏ธ Troubleshooting

Common Issues:

  1. "Connection failed" - Check PDU IP, credentials, and network connectivity
  2. "Token expired" - Server automatically handles re-authentication
  3. "WebSocket connection refused" - Ensure server is running on correct port
  4. "No state updates" - Check if client is properly subscribed to loads

Debug Mode:

WS_DEBUG=true npm run server

๐Ÿ“ License

MIT License - see LICENSE file for details.


๐Ÿค Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Commit your changes
  4. Push to the branch
  5. Create a Pull Request

๐Ÿ“ž Support

  • GitHub Issues: Report bugs or request features
  • Documentation: Full API documentation available in /docs
  • Examples: Additional examples in /examples directory
1.3.0

6 months ago

1.1.6

8 months ago

1.1.5

8 months ago

1.1.4

8 months ago

1.1.3

8 months ago

1.1.2

8 months ago

1.1.1

8 months ago

1.1.0

8 months ago