smithtek-nodered-sms
Version:
A Node-RED node for sending SMS via ZTE USB modem.
221 lines (191 loc) • 9.35 kB
JavaScript
// zte-sms.js - Main Node-RED Node
const fetch = require("node-fetch");
module.exports = function(RED) {
function ZteSmsNode(config) {
RED.nodes.createNode(this, config);
let node = this;
// Configurable properties
node.phone = config.phone || "";
node.password = config.password || "Admin";
node.modemIp = config.modemIp || "192.168.0.1";
node.smsInterval = config.smsInterval || 10; // Default SMS interval in seconds
// Start periodic polling
startPolling(node);
node.on('input', async function(msg) {
let phoneNumber = node.phone || msg.phone;
let password = node.password;
let modemIp = node.modemIp;
let message = msg.payload;
if (!phoneNumber || !message) {
node.error("Phone number and message are required.", msg);
return;
}
try {
await loginToModem(password, modemIp);
let response = await sendSms(phoneNumber, message, modemIp);
msg.payload = response;
node.send([null, null, msg]); // Only send to the 3rd output (status pin)
} catch (error) {
node.error(error, msg);
}
});
}
function startPolling(node) {
if (node.smsIntervalId) clearInterval(node.smsIntervalId);
if (node.modemIntervalId) clearInterval(node.modemIntervalId);
// SMS Polling (User-configurable)
node.smsIntervalId = setInterval(async () => {
try {
let lastSms = await getLastSms(node.modemIp);
if (lastSms.message === "No message content") {
node.warn("No new messages found. Triggering login.");
await loginToModem(node.password, node.modemIp); // Trigger login process
} else {
// Only forward if there is a valid message
node.send([
{ payload: { from: lastSms.from || "Unknown", time: lastSms.time || "Unknown" } }, // Pin 1 (Sender + Time)
{ payload: lastSms.message }, // Pin 2 (Message)
null // Pin 3 (Not used in this part)
]);
}
} catch (error) {
node.warn("Failed to fetch SMS: " + error);
}
}, node.smsInterval * 1000);
// Modem Status Polling (Fixed at 10s)
node.modemIntervalId = setInterval(async () => {
try {
let modemStatus = await getModemStatus(node.modemIp);
if (modemStatus) {
node.send([null, null, { payload: modemStatus }]); // Pin 3 (Modem Status)
}
} catch (error) {
node.warn("Failed to fetch modem status: " + error);
}
}, 10000);
}
async function loginToModem(password, modemIp) {
let encodedPassword = Buffer.from(password).toString('base64');
let loginPayload = `isTest=false&goformId=LOGIN&password=${encodedPassword}`;
await fetch(`http://${modemIp}/goform/goform_set_cmd_process`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With": "XMLHttpRequest",
"Referer": `http://${modemIp}/index.html`
},
body: loginPayload
});
}
async function sendSms(number, text, modemIp) {
function textToHex(str) {
return Array.from(str).map(c => ('000' + c.charCodeAt(0).toString(16)).slice(-4)).join('');
}
function getSmsTime() {
let now = new Date();
return `${now.getFullYear().toString().substr(-2)};${('0' + (now.getMonth() + 1)).slice(-2)};${('0' + now.getDate()).slice(-2)};${('0' + now.getHours()).slice(-2)};${('0' + now.getMinutes()).slice(-2)};${('0' + now.getSeconds()).slice(-2)};+8`;
}
let payload = `isTest=false&goformId=SEND_SMS¬Callback=true&Number=${encodeURIComponent(number)}&sms_time=${encodeURIComponent(getSmsTime())}&MessageBody=${textToHex(text)}&ID=-1&encode_type=UNICODE`;
let response = await fetch(`http://${modemIp}/goform/goform_set_cmd_process`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With": "XMLHttpRequest",
"Referer": `http://${modemIp}/index.html`
},
body: payload
});
return await response.text();
}
async function getLastSms(modemIp) {
try {
let response = await fetch(`http://${modemIp}/goform/goform_get_cmd_process?isTest=false&cmd=sms_data_total&page=0&data_per_page=500&mem_store=1&tags=10&order_by=order+by+id+desc`, {
method: "GET",
headers: {
"Referer": `http://${modemIp}/index.html`,
"Accept": "application/json, text/javascript, */*; q=0.01"
}
});
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
let data = await response.json();
// Check if we received a valid messages array
if (!data || typeof data !== "object" || !Array.isArray(data.messages) || data.messages.length === 0) {
console.warn("No received messages found.");
return { from: "Unknown", time: "Unknown", message: "No message content" };
}
// Filter messages to only include received ones (tag: 1)
let receivedMessages = data.messages.filter(msg => msg.tag === "1");
if (receivedMessages.length === 0) {
console.warn("No received messages found.");
return { from: "Unknown", time: "Unknown", message: "No received messages found" };
}
// Get the most recent received SMS
let lastSms = receivedMessages[0];
function decodeUCS2(hex) {
let str = '';
for (let i = 0; i < hex.length; i += 4) {
let charCode = parseInt(hex.substr(i, 4), 16);
if (!isNaN(charCode)) {
str += String.fromCharCode(charCode);
}
}
return str;
}
return {
from: lastSms.number || "Unknown",
time: lastSms.date || "Unknown",
message: lastSms.content ? decodeUCS2(lastSms.content) : "No message content"
};
} catch (error) {
console.error("Error fetching last SMS:", error);
return { from: "Error", time: "Error", message: "Failed to fetch SMS" };
}
}
async function getModemStatus(modemIp) {
try {
let response = await fetch(`http://${modemIp}/goform/goform_get_cmd_process?multi_data=1&isTest=false&sms_received_flag_flag=0&sts_received_flag_flag=0&cmd=network_type,network_provider,ppp_status,realtime_tx_bytes,realtime_rx_bytes,opms_wan_auto_mode,signalbar,battery_temp,lan_ipaddr,roam_setting_option&_=` + Date.now(), {
method: "GET",
headers: {
"Referer": `http://${modemIp}/index.html`,
"Accept": "application/json, text/javascript, */*; q=0.01",
"X-Requested-With": "XMLHttpRequest"
}
});
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
let data = await response.json();
return {
network_type: data.network_type || "Unknown",
network_provider: data.network_provider || "Unknown",
ppp_status: data.ppp_status || "Unknown",
realtime_tx_bytes: data.realtime_tx_bytes || "0",
realtime_rx_bytes: data.realtime_rx_bytes || "0",
opms_wan_auto_mode: data.opms_wan_auto_mode || "Unknown",
signalbar: data.signalbar || "0",
battery_temp: data.battery_temp || "Unknown",
lan_ipaddr: data.lan_ipaddr || "Unknown",
roam_setting_option: data.roam_setting_option || "Unknown"
};
} catch (error) {
console.error("Error fetching modem status:", error);
return { network_type: "Error", network_provider: "Error", ppp_status: "Error" };
}
}
RED.nodes.registerType("zte-sms", ZteSmsNode, {
defaults: {
phone: { value: "" },
password: { value: "Admin" },
modemIp: { value: "192.168.0.1" },
smsInterval: { value: 10 }
},
inputs: 1,
outputs: 3,
icon: "font-awesome/fa-comment",
label: function() {
return "ZTE SMS";
}
});
};