Light History
Table of Contents
Overview
The Light History API allows you to retrieve the command history for light devices in the RCAMS system. This provides a chronological record of all commands sent to lights, including the type of command, timestamp, and the result of the command. This historical data is valuable for auditing purposes, troubleshooting, and verifying that commands were successfully executed during emergency situations or drills.
Note: The light history endpoint requires basic authentication. The user must have appropriate permissions for the requested operations.
Light History API Endpoint
| Endpoint | Description |
|---|---|
/lighthistory.php |
Retrieves command history for lights based on specified filtering criteria |
Get Light Command History
Retrieves the command history for lights based on specified filtering criteria.
POST/lighthistory.php
Returns the command history for lights, organized hierarchically by school, group, and light name.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
light_mac |
String | No | MAC address of a specific light to filter by |
school_id |
String | No | ID of a specific school to filter by |
group_id |
String | No | ID of a specific group to filter by |
Filtering Options
The Light History API provides several filtering options that can be used individually or in combination to narrow down the results to specific lights, groups, or schools.
Filter Combinations
| Filter Combination | Result |
|---|---|
light_mac only |
Returns command history for the specific light, regardless of its group or school |
group_id only |
Returns command history for all lights in the specified group |
school_id only |
Returns command history for all lights in the specified school, across all groups |
school_id and group_id |
Returns command history for all lights in the specified group within the specified school |
| No filters | Returns command history for all lights the user has access to |
Note: When using multiple filters, the results are filtered using an AND relationship. For example, if both school_id and group_id are provided, only lights that belong to both the specified school AND the specified group will be included in the results.
Response Structure
The response from the Light History API is organized in a hierarchical structure, with schools at the top level, followed by groups, then individual lights, and finally the command history for each light.
{
"satus": "OK",
"message": "Light history retrieved successfully.",
"data": {
"Grand Prairie High School": {
"Gymnasium": {
"Light2014": {
"id": 21,
"mac": "1B2C3D4F3B3E",
"topic": "rcams_topic_1B2C3D4F3B3E",
"history": [
{
"id": 48,
"Timestamp": "2025-04-08 11:53:23",
"message": "MODE: 4",
"result": "message published"
},
{
"id": 47,
"Timestamp": "2025-04-08 11:52:53",
"message": "MODE: 1",
"result": "message published"
},
{
"id": 43,
"Timestamp": "2025-04-08 11:31:25",
"message": "MODE: 0",
"result": "message published"
},
{
"id": 31,
"Timestamp": "2025-04-08 10:57:07",
"message": "MODE: 6",
"result": "message published"
},
{
"id": 21,
"Timestamp": "2025-04-08 10:36:36",
"message": "MODE: 3",
"result": "35"
}
]
}
},
"Main Building": {
"Light2015": {
"id": 22,
"mac": "1B2C3D4F4B5E",
"topic": "rcams_topic_1B2C3D4F4B5E",
"history": [
{
"id": 52,
"Timestamp": "2025-04-08 11:53:23",
"message": "MODE: 4",
"result": "missed_required_server_params"
},
{
"id": 45,
"Timestamp": "2025-04-08 11:38:01",
"message": "MODE: 1",
"result": "missed_required_server_params"
},
{
"id": 44,
"Timestamp": "2025-04-08 11:34:04",
"message": "MODE: 1",
"result": "message published"
}
]
}
}
}
}
}
Response Structure Explanation
The response contains a nested structure with the following levels:
- Schools - The top level contains school names as keys
- Groups - Within each school, group names are keys
- Lights - Within each group, light names are keys
- Light Details - For each light, basic details (ID, MAC address, topic) are provided
- History - The "history" array contains the command history for each light, with the most recent commands first
Command History Details
Each entry in the history array contains the following information:
| Field | Description |
|---|---|
id |
Unique identifier for the command history entry |
Timestamp |
Date and time when the command was sent |
message |
The command that was sent (e.g., "MODE: 1" for lockdown) |
result |
The result of the command execution (success or error message) |
Possible Result Values
| Result Value | Description |
|---|---|
message published |
The command was successfully published to the light |
missed_required_server_params |
The light is missing required server configuration parameters |
Failed to connect to MQTT broker |
Could not connect to the MQTT broker to send the command |
light_missed_params |
The light is missing required configuration parameters |
Numeric value (e.g., 35) |
A status code from the MQTT broker or light device |
Code Examples
// Set up the request
const jsonData = JSON.stringify({
"school_id": "1",
"group_id": "1"
});
// Make the API call
fetch('https://rcamsapi.spheronomics.com/api/v2/lighthistory.php', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('api_username:api_password')
},
body: jsonData
})
.then(response => response.json())
.then(data => {
if (data.status === 'OK') {
console.log('Light history retrieved successfully');
// Process the hierarchical data
for (const schoolName in data.data) {
console.log(`School: ${schoolName}`);
const school = data.data[schoolName];
for (const groupName in school) {
console.log(` Group: ${groupName}`);
const group = school[groupName];
for (const lightName in group) {
const light = group[lightName];
console.log(` Light: ${lightName} (${light.mac})`);
console.log(' Command History:');
light.history.forEach(entry => {
console.log(` ${entry.Timestamp}: ${entry.message} (${entry.result})`);
});
}
}
}
} else {
console.error('Error:', data.message);
}
})
.catch(error => console.error('Error:', error));
Light History Analyzer Example
The following example demonstrates a utility class for retrieving and analyzing light command history. It includes functions for getting history, filtering by various criteria, and generating summary reports.
// Light History Analyzer for RCAMS
class LightHistoryAnalyzer {
constructor(apiUsername, apiPassword) {
this.apiUsername = apiUsername;
this.apiPassword = apiPassword;
this.baseUrl = 'https://rcamsapi.spheronomics.com/api/v1';
this.historyData = null;
}
// Helper method for making API requests
async makeRequest(endpoint, params) {
// Create form data from params
const json = {};
for (const [key, value] of Object.entries(params)) {
json[key] = value;
}
const jsonString = JSON.stringify(json);
try {
const response = await fetch(`${this.baseUrl}/${endpoint}`, {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa(`${this.apiUsername}:${this.apiPassword}`)
},
body: jsonString
});
return await response.json();
} catch (error) {
console.error(`API request to ${endpoint} failed:`, error);
throw error;
}
}
// Login
async login(username, password) {
const response = await this.makeRequest('login.php', {
user: username,
pass: password
});
if (response.status === 'OK') {
console.log('Login successful.');
return true;
} else {
console.error('Login failed:', response.message);
return false;
}
}
// Check if logged in
isLoggedIn() {
return this.status !== "OK";
}
// Fetch light history
async fetchHistory(filters = {}) {
if (!this.isLoggedIn()) {
throw new Error('Not logged in. Call login() first.');
}
const params = {};
// Apply filters if provided
if (filters.lightMac) params.light_mac = filters.lightMac;
if (filters.schoolId) params.school_id = filters.schoolId;
if (filters.groupId) params.group_id = filters.groupId;
const response = await this.makeRequest('lighthistory.php', params);
if (response.status === 'OK' || response.satus === 'OK') { // Handle both spelling variations
this.historyData = response.data;
return this.historyData;
} else {
throw new Error(`Failed to fetch history: ${response.message}`);
}
}
// Process the hierarchical history data into a flat array
getFlatHistoryArray() {
if (!this.historyData) {
throw new Error('No history data available. Call fetchHistory() first.');
}
const flatHistory = [];
for (const schoolName in this.historyData) {
const school = this.historyData[schoolName];
for (const groupName in school) {
const group = school[groupName];
for (const lightName in group) {
const light = group[lightName];
light.history.forEach(entry => {
flatHistory.push({
school: schoolName,
group: groupName,
light: lightName,
lightId: light.id,
lightMac: light.mac,
lightTopic: light.topic,
commandId: entry.id,
timestamp: new Date(entry.Timestamp),
message: entry.message,
result: entry.result,
successful: entry.result === 'message published'
});
});
}
}
}
// Sort by timestamp, newest first
return flatHistory.sort((a, b) => b.timestamp - a.timestamp);
}
// Generate a summary report
generateSummaryReport() {
if (!this.historyData) {
throw new Error('No history data available. Call fetchHistory() first.');
}
const flatHistory = this.getFlatHistoryArray();
const report = {
totalCommands: flatHistory.length,
successfulCommands: flatHistory.filter(entry => entry.successful).length,
failedCommands: flatHistory.filter(entry => !entry.successful).length,
commandTypes: {},
schoolStats: {},
timeDistribution: {
lastHour: 0,
lastDay: 0,
lastWeek: 0,
lastMonth: 0,
older: 0
}
};
// Calculate success rate
report.successRate = report.totalCommands > 0
? (report.successfulCommands / report.totalCommands * 100).toFixed(2) + '%'
: 'N/A';
// Process command types
flatHistory.forEach(entry => {
// Extract command type (e.g., "MODE: 1" -> "MODE 1")
const commandType = entry.message.replace(':', '');
if (!report.commandTypes[commandType]) {
report.commandTypes[commandType] = {
count: 0,
successful: 0,
failed: 0
};
}
report.commandTypes[commandType].count++;
if (entry.successful) {
report.commandTypes[commandType].successful++;
} else {
report.commandTypes[commandType].failed++;
}
// Process school stats
if (!report.schoolStats[entry.school]) {
report.schoolStats[entry.school] = {
totalCommands: 0,
successful: 0,
failed: 0,
groups: {}
};
}
report.schoolStats[entry.school].totalCommands++;
if (entry.successful) {
report.schoolStats[entry.school].successful++;
} else {
report.schoolStats[entry.school].failed++;
}
// Process group stats within schools
if (!report.schoolStats[entry.school].groups[entry.group]) {
report.schoolStats[entry.school].groups[entry.group] = {
totalCommands: 0,
successful: 0,
failed: 0
};
}
report.schoolStats[entry.school].groups[entry.group].totalCommands++;
if (entry.successful) {
report.schoolStats[entry.school].groups[entry.group].successful++;
} else {
report.schoolStats[entry.school].groups[entry.group].failed++;
}
// Process time distribution
const now = new Date();
const commandTime = entry.timestamp;
const diffInHours = (now - commandTime) / (1000 * 60 * 60);
if (diffInHours <= 1) {
report.timeDistribution.lastHour++;
} else if (diffInHours <= 24) {
report.timeDistribution.lastDay++;
} else if (diffInHours <= 168) { // 7 days
report.timeDistribution.lastWeek++;
} else if (diffInHours <= 720) { // 30 days
report.timeDistribution.lastMonth++;
} else {
report.timeDistribution.older++;
}
});
return report;
}
// Get history for a specific light
getLightHistory(lightMac) {
const flatHistory = this.getFlatHistoryArray();
return flatHistory.filter(entry => entry.lightMac === lightMac);
}
// Get recent commands (within last X hours)
getRecentCommands(hours = 24) {
const flatHistory = this.getFlatHistoryArray();
const now = new Date();
return flatHistory.filter(entry => {
const diffInHours = (now - entry.timestamp) / (1000 * 60 * 60);
return diffInHours <= hours;
});
}
// Get commands of a specific type
getCommandsByType(commandType) {
const flatHistory = this.getFlatHistoryArray();
return flatHistory.filter(entry => entry.message.includes(commandType));
}
// Get failed commands
getFailedCommands() {
const flatHistory = this.getFlatHistoryArray();
return flatHistory.filter(entry => !entry.successful);
}
}
// Example usage:
/*
async function analyzeHistory() {
// Initialize the analyzer with API credentials
const analyzer = new LightHistoryAnalyzer('api_username', 'api_password');
try {
// Login
const loginSuccess = await analyzer.login('user@example.com', 'password123');
if (!loginSuccess) return;
// Fetch history for a specific school
await analyzer.fetchHistory({ schoolId: '1' });
// Generate and display summary report
const report = analyzer.generateSummaryReport();
console.log('Summary Report:', JSON.stringify(report, null, 2));
// Get recent commands (last 24 hours)
const recentCommands = analyzer.getRecentCommands(24);
console.log(`Commands in the last 24 hours: ${recentCommands.length}`);
// Get lockdown commands
const lockdownCommands = analyzer.getCommandsByType('MODE: 1');
console.log(`Lockdown commands: ${lockdownCommands.length}`);
// Get failed commands
const failedCommands = analyzer.getFailedCommands();
console.log(`Failed commands: ${failedCommands.length}`);
console.log('Analysis completed successfully.');
} catch (error) {
console.error('Error during analysis:', error);
}
}
// Run the analysis
// analyzeHistory();
*/