Skip to content
Permalink
main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
const net = require('net');
let fusionClient = null;
const clientData = {
"meta": {},
"log": {
"fusion": {},
},
"devices": [],
"liveevents": [],
"eventhistory": [],
}
const updateClientData = () => {
if (process.send) {
process.send(clientData);
} else {
console.log("[SRV] Error: No process to send to")
}
};
const fusionDeviceTypes = {
1: "System Faults",
2: "Nurse Call System",
3: "Cell Call System",
4: "General Alarm System",
5: "Fire Alarm System",
6: "Medical Gas System",
7: "Attack Alarm System",
8: "Cell Call IP File Server / System",
9: "Cell Misting System",
10: "SSG Phone Management Server (Lon)",
11: "SSG RTLS Gateway Server",
12: "3rd Party Fire Alarm System",
14: "Ultima+ Nurse Call System",
20: "iButton Registration Point",
21: "Popup Client",
22: "Smart Client",
128: "SSG Data Management Server",
129: "SSG Master Display",
130: "SSG Messaging Server",
131: "SSG Centra Server",
132: "SSG BMS Integration Server",
133: "SSG Phone Management Server (IP)",
134: "SSG Nurse Call Gateway Server",
135: "SSG Fusion Data-Link",
254: "SSG Backup Fusion-IP Router",
}
const fusionDeviceIcons = {
1: "triangle-exclamation",
2: "hospital",
3: "handcuffs",
4: "siren-on",
5: "fire",
6: "cloud",
7: "user-ninja",
8: "server",
9: "smog",
10: "phone-alt",
11: "router",
12: "sensor-fire",
14: "heart-circle-plus",
20: "fingerprint",
21: "envelope",
22: "mobile-screen",
128: "database",
129: "map",
130: "envelope",
131: "wrench",
132: "buildings",
133: "mobile-signal-out",
134: "link-horizontal",
135: "tower-cell",
254: "network-wired"
}
for (let i = 192; i < 250; i++) {
fusionDeviceTypes[i] = "3rd Party System";
}
for (let i = 192; i < 250; i++) {
fusionDeviceTypes[i] = "3rd Party System";
}
const messageTypes = {
0: "System Identification Request",
1: "System Identification Response",
2: "Health Check Request",
3: "Active Events Request",
4: "Disconnected Headend",
5: "Health Check Reply",
6: "Time + Date Sync",
7: "Routing ID Table",
8: "Extra Data Message",
9: "System ID Change",
10: "Routing ID Table Request",
33: "Event Activated",
34: "Event Deactivated",
35: "Device Info Request",
36: "Device Info Response",
37: "Special Message",
38: "Event Cleardown",
40: "Global Message",
41: "Control Message - Event",
42: "Acknowledged Message",
43: "Not Acknowledged Message",
44: "Control Message - Data",
50: "BACnet Device Status Request",
51: "BACnet Device Status",
52: "DS BACnet DB Request",
53: "DS BACnet DB Response",
54: "DS BACnet Device Status Request",
55: "DS BACnet Device Status",
56: "BMS Heartbeat",
57: "Reset Event",
200: "Unified Message",
201: "Unified Message Response",
};
const fusionMsgQueue = [];
let initalBuffer = false;
const connectToFusion = () => {
console.log("[FUS] Connecting to Fusion protocol...");
fusionClient = new net.Socket();
fusionClient.on('data', function (rawData) {
fusionMsgQueue.push(rawData);
if (!initalBuffer && rawData[1] == 0) {
parseFusionMsg(rawData);
} else if (initalBuffer) {
newFusionMsg();
}
});
console.log("MSG Receiver defined")
fusionClient.on('error', function (err) {
if (err.code === 'ECONNREFUSED') {
console.log('[FUS] Fusion is not running - waiting 60 seconds...');
setTimeout(connectToFusion, 60000);
} else {
console.log('[FUS] Fusion error: ' + err);
setTimeout(connectToFusion, 5000);
}
});
fusionClient.on('close', function () {
fusionMsgQueue.length = 0;
});
fusionClient.connect(30000, '127.0.0.1', function () {
console.log('[FUS] Connected to Fusion server - handshake should be soon...');
initalBuffer = true;
});
}
const newFusionMsg = () => {
const msgcount = fusionMsgQueue.length;
while (fusionMsgQueue.length > 0) {
// connectToFusion.pause();
parseFusionMsg(fusionMsgQueue.shift());
}
// connectToFusion.resume();
return msgcount;
}
parseFusionMsg = (rawData) => {
// Parse data to readable bytes
strData = Buffer.from(rawData).toString('hex');
strData = strData.replace(/(.{2})/g, "$1 ").trim();
// console.log(strData);
dataLength = rawData[0];
dataHeader = rawData[1];
dataData = rawData.subarray(2, dataLength + 2);
// datetime string as key dd/mm hh:mm:ss
// remove year from date
dt = new Date();
date = dt.toLocaleString();
epoch = dt.getTime();
dtime = date.split(" ")[1];
date = date.split(" ")[0].slice(0, -6);
datetime = date + " " + dtime;
if (messageTypes[dataHeader] == undefined) {
clientData.log.fusion[epoch] = { "msg": "Unknown Message Type", dt: datetime };
} else if ([2, 5].includes(dataHeader)) {
// skip healthchecks from log
} else {
clientData.log.fusion[epoch] = { "msg": messageTypes[dataHeader], dt: datetime }
}
updateClientData();
switch (dataHeader) {
// HANDSHAKE
case 0x00:
console.log("[FUS] Handshake started...");
clientID = dataData[0];
clientData.meta.psuedoID = clientID;
updateClientData();
fusionClient.write(Buffer.from([0x0B, 0x01, 0x00, clientID, 0x02, 0x31, 0x2E, 0x33, 0x30, 0x00, 0x00, 0x00]));
console.log("[FUS] Handshake finished w/ client ID: " + clientID);
break;
// HEADEND CLIENT
case 0x01: // Connect
switch (dataData[2]) {
case 2: // Ultima or Codemlon
clientData.meta.clientType = 0;
clientData.meta.clientStr = "Nurse Call";
updateClientData();
case 14: // Acelo
clientData.meta.clientType = 1;
clientData.meta.clientStr = "Acelo";
updateClientData();
}
console.log("[FUS] Device connected: " + fusionDeviceTypes[dataData[2]]);
// split remaining data by 0x00 delimiter
// first chunk is version, second and third is system specific
chunks = dataData.subarray(3).toString().split("\0")
// remove last chunk as is blank
chunks.pop();
updated = false;
for (i = 0; i < clientData.devices.length; i++) {
if (clientData.devices[i].sysID == dataData[1]) {
clientData.devices[i].connected = true;
clientData.devices[i].lastConn = epoch;
updated = true;
}
}
if (!updated) {
clientData.devices.push({
"sysID": dataData[1],
"sysType": dataData[2],
"sysTypeStr": fusionDeviceTypes[dataData[2]],
"sysVersion": chunks[0],
"icon": fusionDeviceIcons[dataData[2]],
"data1": chunks[1],
"data2": chunks[2],
"connected": true,
"lastConn": epoch
});
}
updateClientData();
// fusionClient.write(Buffer.from([3, 10, clientData.meta.psuedoID, dataData[1]]));
break;
case 0x08: // Extra Data
console.log("[FUS] Headend client extra data");
break;
case 0x04: // Disconnect
console.log("[FUS] Headend client disconnected:");
console.log(dataData[1]);
for (i = 0; i < clientData.devices.length; i++) {
if (clientData.devices[i].sysID == dataData[1]) {
clientData.devices[i].connected = false;
updateClientData();
}
}
break;
// HEALTHCHECKS
case 0x02:
console.log("[FUS] Healthcheck request");
break;
case 0x05:
console.log("[FUS] Healthcheck response");
break;
// CALLS
case 0x21: // Event Activated
console.log("[FUS] Event Activated");
labels = dataData.subarray(21);
// labels split by 0x00 delimiter
labels = labels.toString().split("\0");
eventData = {
"clientID": dataData[1],
"serverID": dataData[2],
"eventtime": dataData.subarray(3, 11),
"sysType": dataData[11],
"sysString": fusionDeviceTypes[dataData[11]],
"params": dataData.subarray(12, 20),
"label1": labels[0],
"label2": labels[1],
"label3": labels[2],
"label4": labels[3],
"label5": labels[4],
"active": true,
"evtIcon": fusionDeviceIcons[dataData[11]],
"time": epoch,
// readable time in hh:mm
"readtime": dtime.slice(0, -3)
}
clientData.liveevents.push(eventData);
clientData.eventhistory.push(eventData);
updateClientData();
break;
case 0x22: // Event Deactivated
console.log("[FUS] Event Deactivated");
// find event with matching clientID, remove from live, change active on history
clientData.liveevents.forEach((event, index) => {
if (event.clientID == dataData[1]) {
clientData.liveevents.splice(index, 1);
clientData.eventhistory.forEach((event, index) => {
if (event.clientID == dataData[1]) {
clientData.eventhistory[index].active = false;
}
});
}
});
updateClientData();
break;
// TIME + DATE
case 0x06:
const year = parseInt(strData.split(" ")[9] + strData.split(" ")[10], 16);
const date = new Date(year, dataData[6] - 1, dataData[5], dataData[2], dataData[3], dataData[4]);
clientData.meta.fusionDT = date;
updateClientData();
console.log("[FUS] Time and Date Sync: " + date);
break;
// TODO
// IP TABLES
case 0x07:
console.log(dataData);
deviceID = dataData[0];
sysType = dataData[1];
sysTypeStr = fusionDeviceTypes[sysType];
wardNo = dataData[2];
ipaddress = dataData.subarray(3).toString();
clientData.devices.forEach((device, index) => {
if (device.sysID == deviceID) {
console.log("[FUS] IP Table found for device " + deviceID);
clientData.devices[index].ipaddress = ipaddress;
clientData.devices[index].wardNo = wardNo;
}
});
updateClientData();
break;
default:
// Remaining Codes:
// 0x03 - Request Active Events
// 0x07 - Routing ID Table - Ignore
// 0x09 - System ID Change
// 0x0A - Routing ID Table Request - Ignore
// 0x(35) - Device Info Request
// 0x(36) - Device Info
// 0x(37) - Special Message
// 0x(38) - Cleardown Message - Removes data station events
// 0x(40) - Global Message
// 0x(41) - Control (Event) Message
// 0x(42) - Ack / Nack Message
// 0x(43) - Control (Data) Message
// 0x(52) - 0x(56) - BACNet - Can be ignored
console.log("[FUS] Unhandled message: " + dataHeader)
console.log(strData)
break;
}
}
connectToFusion();