/** * Legacy API Integration Service * * This service handles communication with the Legacy WellNuo API * at eluxnetworks.net for device management and deployments. */ const axios = require('axios'); const LEGACY_API_BASE = 'https://eluxnetworks.net/function/well-api/api'; /** * Room location codes mapping * These numeric codes are used by Legacy API to identify room types */ const ROOM_LOCATIONS = { 'Bedroom': 102, 'Living Room': 103, 'Kitchen': 104, 'Bathroom': 105, 'Hallway': 106, 'Office': 107, 'Garage': 108, 'Dining Room': 109, 'Basement': 110, 'Other': 200 }; const LOCATION_NAMES = Object.fromEntries( Object.entries(ROOM_LOCATIONS).map(([k, v]) => [v, k]) ); /** * Get authentication token from Legacy API * @param {string} username - Legacy API username * @param {string} password - Legacy API password * @returns {Promise} Access token */ async function getLegacyToken(username, password) { const formData = new URLSearchParams({ function: 'credentials', user_name: username, ps: password, clientId: '001', nonce: Date.now().toString() }); const response = await axios.post(LEGACY_API_BASE, formData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); if (response.data.status !== '200 OK') { throw new Error('Legacy API authentication failed'); } return response.data.access_token; } /** * Create deployment in Legacy API * @param {object} params - Deployment parameters * @param {string} params.username - Installer username * @param {string} params.token - Access token * @param {string} params.beneficiaryName - Full name * @param {string} params.beneficiaryEmail - Email * @param {string} params.beneficiaryUsername - Login username * @param {string} params.beneficiaryPassword - Password * @param {string} params.address - Address * @param {string} params.caretakerUsername - Caretaker username * @param {string} params.caretakerEmail - Caretaker email * @param {number} params.persons - Number of persons * @param {number} params.pets - Number of pets * @param {string} params.gender - Gender * @param {number} params.race - Race index * @param {number} params.born - Year born * @param {number} params.lat - GPS latitude * @param {number} params.lng - GPS longitude * @param {Array} params.wifis - WiFi credentials ["SSID|password", ...] * @param {Array} params.devices - Device well_ids [497, 523] * @returns {Promise} Created deployment_id */ async function createLegacyDeployment(params) { const formData = new URLSearchParams({ function: 'set_deployment', user_name: params.username, token: params.token, deployment: 'NEW', beneficiary_name: params.beneficiaryName, beneficiary_email: params.beneficiaryEmail, beneficiary_user_name: params.beneficiaryUsername, beneficiary_password: params.beneficiaryPassword, beneficiary_address: params.address || 'Unknown', // Legacy API requires non-empty address beneficiary_photo: params.beneficiaryPhoto || 'none', // Required by Legacy API, 'none' means no photo phone_number: params.phoneNumber || '0000000000', // Required by Legacy API for email sending (must be non-empty) caretaker_username: params.caretakerUsername || params.username, caretaker_email: params.caretakerEmail || params.beneficiaryEmail, persons: params.persons || 1, pets: params.pets || 0, gender: params.gender || 'Male', // Use 'Male' as default, 'Other' causes issues race: params.race || 0, born: params.born || new Date().getFullYear() - 65, lat: params.lat || 40.7128, // Default to NYC coordinates lng: params.lng || -74.0060, gps_age: params.gpsAge || 0, // Required by Legacy API wifis: JSON.stringify(params.wifis || []), devices: JSON.stringify(params.devices || []), reuse_existing_devices: params.devices && params.devices.length > 0 ? 1 : 0, signature: 'wellnuo-api', // Required to avoid None error in SendWelcomeBeneficiaryEmail skip_email: 1 // Skip welcome email to avoid Legacy API crash }); console.log('[LEGACY API] set_deployment request params:', formData.toString()); const response = await axios.post(LEGACY_API_BASE, formData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); console.log('[LEGACY API] set_deployment response:', JSON.stringify(response.data)); if (response.data.status !== '200 OK') { throw new Error(`Failed to create deployment in Legacy API: ${response.data.status || JSON.stringify(response.data)}`); } // Extract deployment_id from response // Response format varies, need to handle different cases // Note: Current Legacy API returns only {ok: 1, status: "200 OK"} without deployment_id const deploymentId = response.data.deployment_id || response.data.result || response.data.well_id; if (!deploymentId) { console.warn('[LEGACY API] Deployment created but no deployment_id returned. This is a known Legacy API limitation.'); } return deploymentId; } /** * Assign device to deployment (set well_id) * @param {string} username - Username * @param {string} token - Access token * @param {number} deviceId - Internal device ID * @param {number} wellId - Well ID to assign * @param {string} mac - Device MAC address * @returns {Promise} Success status */ async function assignDeviceToDeployment(username, token, deviceId, wellId, mac) { const formData = new URLSearchParams({ function: 'device_set_well_id', user_name: username, token: token, device_id: deviceId, well_id: wellId, mac: mac.toUpperCase().replace(/:/g, '') }); const response = await axios.post(LEGACY_API_BASE, formData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); return response.data.success === true; } /** * Update device location/room * @param {string} username - Username * @param {string} token - Access token * @param {number} wellId - Device well_id * @param {string} deviceMac - MAC address * @param {string} roomName - Room name (Bedroom, Kitchen, etc.) * @param {object} options - Additional options * @param {string} options.description - Device description * @param {string} options.closeTo - Position description * @param {number} options.radarThreshold - Radar sensitivity (0-100) * @param {number} options.group - Group ID * @returns {Promise} Success status */ async function updateDeviceLocation(username, token, wellId, deviceMac, roomName, options = {}) { const locationCode = ROOM_LOCATIONS[roomName] || ROOM_LOCATIONS['Other']; const formData = new URLSearchParams({ function: 'device_form', user_name: username, token: token, well_id: wellId, device_mac: deviceMac.toUpperCase().replace(/:/g, ''), location: locationCode.toString(), description: options.description || '', close_to: options.closeTo || '', radar_threshold: options.radarThreshold || 50, group: options.group || 0 }); const response = await axios.post(LEGACY_API_BASE, formData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); return response.data.status === '200 OK'; } /** * Get devices for deployment (only recently active) * @param {string} username - Username * @param {string} token - Access token * @param {number} deploymentId - Deployment ID * @param {boolean} onlineOnly - If true, return only online devices (fresh=true) * @returns {Promise} List of devices */ async function getDeploymentDevices(username, token, deploymentId, onlineOnly = true) { const formData = new URLSearchParams({ function: 'request_devices', user_name: username, token: token, deployment_id: deploymentId, group_id: 'All', location: 'All', fresh: onlineOnly ? 'true' : 'false' }); const response = await axios.post(LEGACY_API_BASE, formData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); if (response.data.status !== '200 OK') { return []; } // Parse device array format: // [device_id, well_id, MAC, timestamp, location, description, deployment_id] const devices = response.data.result_list || []; return devices.map(device => ({ deviceId: device[0], wellId: device[1], mac: device[2], lastSeen: device[3], locationCode: device[4], locationName: LOCATION_NAMES[device[4]] || 'Unknown', description: device[5], deploymentId: device[6], isOnline: true // If fresh=true was used, all returned devices are online })); } /** * Reboot device * @param {string} username - Username * @param {string} token - Access token * @param {number} deviceId - Device ID * @returns {Promise} Success status */ async function rebootDevice(username, token, deviceId) { const formData = new URLSearchParams({ function: 'device_reboot', user_name: username, token: token, device_id: deviceId }); const response = await axios.post(LEGACY_API_BASE, formData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); return response.data.status === '200 OK'; } /** * Find deployment ID by beneficiary username * Used to retrieve deployment_id after creation (since set_deployment doesn't return it) * @param {string} adminUsername - Admin username * @param {string} adminToken - Admin access token * @param {string} beneficiaryUsername - Username of the beneficiary * @returns {Promise} Deployment ID or null if not found */ async function findDeploymentByUsername(adminUsername, adminToken, beneficiaryUsername) { try { // Try logging in as the beneficiary user to get their deployment // Note: This requires knowing the beneficiary's password console.log('[LEGACY API] Attempting to find deployment for username:', beneficiaryUsername); // Alternative: Use get_user_deployments if available const formData = new URLSearchParams({ function: 'get_user_deployments', user_name: adminUsername, token: adminToken, target_username: beneficiaryUsername }); const response = await axios.post(LEGACY_API_BASE, formData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); console.log('[LEGACY API] get_user_deployments response:', JSON.stringify(response.data)); if (response.data.status === '200 OK' && response.data.result_list) { // Return the first deployment ID const deployments = response.data.result_list; if (deployments.length > 0) { return deployments[0].deployment_id || deployments[0][0]; // Handle both object and array format } } return null; } catch (error) { console.error('[LEGACY API] Error finding deployment:', error.message); return null; } } module.exports = { getLegacyToken, createLegacyDeployment, findDeploymentByUsername, assignDeviceToDeployment, updateDeviceLocation, getDeploymentDevices, rebootDevice, ROOM_LOCATIONS, LOCATION_NAMES };