Genesis
This commit is contained in:
168
daemon.js
Normal file
168
daemon.js
Normal file
@@ -0,0 +1,168 @@
|
||||
import 'dotenv/config';
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
// Configuration
|
||||
const BASE_URL = 'http://www.acinfinityserver.com';
|
||||
const USER_AGENT = 'ACController/1.9.7 (com.acinfinity.humiture; build:533; iOS 18.5.0) Alamofire/5.10.2';
|
||||
const POLL_INTERVAL_MS = 60000; // 60 seconds
|
||||
const DB_FILE = 'ac_data.db';
|
||||
|
||||
// Database Setup
|
||||
const db = new Database(DB_FILE);
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS readings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
dev_id TEXT,
|
||||
dev_name TEXT,
|
||||
temp_c REAL,
|
||||
humidity REAL,
|
||||
vpd REAL,
|
||||
fan_speed INTEGER,
|
||||
on_speed INTEGER,
|
||||
off_speed INTEGER
|
||||
)
|
||||
`);
|
||||
|
||||
const insertStmt = db.prepare(`
|
||||
INSERT INTO readings (dev_id, dev_name, temp_c, humidity, vpd, fan_speed, on_speed, off_speed)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
// State
|
||||
let token = null;
|
||||
|
||||
// Helper to check credentials
|
||||
if (!process.env.AC_EMAIL || !process.env.AC_PASSWORD) {
|
||||
console.error('Error: AC_EMAIL and AC_PASSWORD must be set in .env file');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Login to AC Infinity API
|
||||
*/
|
||||
async function login() {
|
||||
console.log('Logging in...');
|
||||
const params = new URLSearchParams();
|
||||
params.append('appEmail', process.env.AC_EMAIL);
|
||||
params.append('appPasswordl', process.env.AC_PASSWORD);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/api/user/appUserLogin`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'User-Agent': USER_AGENT
|
||||
},
|
||||
body: params
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.code === 200) {
|
||||
console.log('Login successful.');
|
||||
return data.data.appId;
|
||||
} else {
|
||||
throw new Error(`Login failed: ${data.msg} (${data.code})`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get All Devices
|
||||
*/
|
||||
async function getDeviceList(authToken) {
|
||||
const params = new URLSearchParams();
|
||||
params.append('userId', authToken);
|
||||
|
||||
const response = await fetch(`${BASE_URL}/api/user/devInfoListAll`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'User-Agent': USER_AGENT,
|
||||
'token': authToken,
|
||||
'phoneType': '1',
|
||||
'appVersion': '1.9.7'
|
||||
},
|
||||
body: params
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.code === 200) return data.data || [];
|
||||
throw new Error(`Get device list failed: ${data.msg}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Settings
|
||||
*/
|
||||
async function getDeviceModeSettings(authToken, devId, port) {
|
||||
const params = new URLSearchParams();
|
||||
params.append('devId', devId);
|
||||
params.append('port', port.toString());
|
||||
|
||||
const response = await fetch(`${BASE_URL}/api/dev/getdevModeSettingList`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'User-Agent': USER_AGENT,
|
||||
'token': authToken,
|
||||
'phoneType': '1',
|
||||
'appVersion': '1.9.7',
|
||||
'minversion': '3.5'
|
||||
},
|
||||
body: params
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.code === 200) return data.data;
|
||||
console.warn(`Failed to get settings for ${devId}: ${data.msg}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll Function
|
||||
*/
|
||||
async function poll() {
|
||||
try {
|
||||
if (!token) {
|
||||
token = await login();
|
||||
}
|
||||
|
||||
const devices = await getDeviceList(token);
|
||||
console.log(`[${new Date().toISOString()}] Found ${devices.length} devices.`);
|
||||
|
||||
for (const device of devices) {
|
||||
const settings = await getDeviceModeSettings(token, device.devId, device.externalPort || 1);
|
||||
|
||||
if (settings) {
|
||||
const tempC = settings.temperature ? settings.temperature / 100 : null;
|
||||
const hum = settings.humidity ? settings.humidity / 100 : null;
|
||||
const vpd = settings.vpdnums ? settings.vpdnums / 100 : null;
|
||||
|
||||
insertStmt.run(
|
||||
device.devId,
|
||||
device.devName,
|
||||
tempC,
|
||||
hum,
|
||||
vpd,
|
||||
settings.speak,
|
||||
settings.onSpead,
|
||||
settings.offSpead
|
||||
);
|
||||
|
||||
console.log(`Saved reading for ${device.devName || device.devId}: ${tempC}°C, ${hum}%, Fan: ${settings.speak}/10`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Polling error:', error.message);
|
||||
// Reset token on error to force re-login next time if needed
|
||||
token = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Start Daemon
|
||||
console.log(`Starting AC Infinity Data Daemon (Interval: ${POLL_INTERVAL_MS}ms)`);
|
||||
poll(); // Initial run
|
||||
setInterval(poll, POLL_INTERVAL_MS);
|
||||
Reference in New Issue
Block a user