Light History

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.

Example Response
{
  "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:

  1. Schools - The top level contains school names as keys
  2. Groups - Within each school, group names are keys
  3. Lights - Within each group, light names are keys
  4. Light Details - For each light, basic details (ID, MAC address, topic) are provided
  5. 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

Example Request (JavaScript)
// 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 (JavaScript)
// 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();
*/