Add Downstream to a Controller

Overview

Downstream interfaces enable communication between controllers and peripheral devices such as readers, inputs, and outputs. They define how the controller communicates with secondary boards (SIOs) and expansion modules. Understanding downstream configuration is essential for building a complete access control system.

Key Concepts

Concept Description
Downstream A communication interface that connects a controller to expansion boards or built-in I/O
SIO (Subcontroller I/O) Secondary board that provides additional reader and I/O capacity
Driver Number Port identifier on the controller for the downstream connection
Panel Address Address of the downstream device on the communication bus
On-Board Interface Built-in downstream on controllers like EP1501 for direct reader connection

Common Downstream Types

Type Model Description
EP1501OnBoardInfo Mercury EP1501 On-board interface for direct reader connections
MR16InInfo Mercury MR16-IN 16-input expansion board
MR16OutInfo Mercury MR16-OUT 16-output expansion board
MR52Info Mercury MR52 2-reader expansion board with inputs/outputs

Prerequisites

Before adding downstream interfaces, ensure you have:

  • ✅ A controller configured and online
  • ✅ Active communication port set on the controller
  • ✅ Knowledge of physical SIO addresses if using expansion boards

C# Examples

Add On-Board Interface (EP1501)

The EP1501 controller has a built-in downstream interface for direct reader connections.

using Feenics.Keep.WebApi.Wrapper;
using Feenics.Keep.WebApi.Model;

// First, get the controller
var controller = (await client.GetControllersAsync(currentInstance))
    .OfType<MercuryControllerInfo>()
    .First();

// Get communication ports and find the internal port
var ports = await client.GetCommunicationPortDefinitionsAsync(controller);
var internalPort = ports.Single(p => p.IsInternal);

// Add the on-board downstream interface
var downstream = await client.AddDownstreamToController(controller,
    new Ep1501OnBoardInfo
    {
        CommonName = "OnBoard Interface",
        DriverNumber = internalPort.DriverNumber,
        PanelAddress = 0  // On-board is always address 0
    });

Console.WriteLine($"Added downstream: {downstream.CommonName} (Key: {downstream.Key})");

Add an MR52 Expansion Board

The MR52 provides 2 reader connections with additional I/O.

// Add an MR52 on the RS-485 bus
var mr52 = await client.AddDownstreamToController(controller,
    new MR52Info
    {
        CommonName = "MR52 - Door 1 & 2",
        DriverNumber = port.DriverNumber,
        PanelAddress = 1  // Address set on the physical board
    });

Console.WriteLine($"Added MR52: {mr52.CommonName} at address {mr52.PanelAddress}");

Add Multiple MR16 Expansion Boards

// Add MR16-IN for additional inputs
var mr16In = await client.AddDownstreamToController(controller,
    new MR16InInfo
    {
        CommonName = "MR16-IN - Alarm Inputs",
        DriverNumber = port.DriverNumber,
        PanelAddress = 2
    });

// Add MR16-OUT for additional outputs  
var mr16Out = await client.AddDownstreamToController(controller,
    new MR16OutInfo
    {
        CommonName = "MR16-OUT - Auxiliary Outputs",
        DriverNumber = port.DriverNumber,
        PanelAddress = 3
    });

Console.WriteLine($"Added {mr16In.CommonName} and {mr16Out.CommonName}");

Get All Downstreams on a Controller

// Retrieve all downstream interfaces on a controller
var downstreams = await client.GetDownstreamForControllerAsync(controller);

Console.WriteLine($"Downstreams on {controller.CommonName}:");
foreach (var ds in downstreams)
{
    Console.WriteLine($"  - {ds.CommonName}");
    Console.WriteLine($"    Type: {ds.GetType().Name}");
    Console.WriteLine($"    Address: {ds.PanelAddress}");
    Console.WriteLine($"    Driver: {ds.DriverNumber}");
}

Complete Setup Example

/// <summary>
/// Sets up a complete downstream configuration for an EP4502 controller
/// </summary>
public async Task SetupControllerDownstreamsAsync(MercuryControllerInfo controller)
{
    // Get available communication ports
    var ports = await client.GetCommunicationPortDefinitionsAsync(controller);
    
    // Find the RS-485 port (typically port 1 or 2)
    var rs485Port = ports.FirstOrDefault(p => !p.IsInternal && !p.IsNetworkPort);
    
    if (rs485Port == null)
    {
        Console.WriteLine("No RS-485 port available");
        return;
    }
    
    // Set the port as active
    await client.SetActivePortAsync(controller, new ActivePortItem
    {
        DriverNumber = rs485Port.DriverNumber,
        BaudRate = 38400  // Standard baud rate; use 9600 for Allegion/Schlage
    });
    
    // Add downstream interfaces
    var downstreams = new List<(string name, int address)>
    {
        ("MR52 - Front Entry", 1),
        ("MR52 - Back Entry", 2),
        ("MR16-IN - Sensors", 3),
        ("MR16-OUT - Locks", 4)
    };
    
    foreach (var (name, address) in downstreams)
    {
        DownstreamInfo ds;
        
        if (name.StartsWith("MR52"))
        {
            ds = await client.AddDownstreamToController(controller,
                new MR52Info
                {
                    CommonName = name,
                    DriverNumber = rs485Port.DriverNumber,
                    PanelAddress = address
                });
        }
        else if (name.Contains("IN"))
        {
            ds = await client.AddDownstreamToController(controller,
                new MR16InInfo
                {
                    CommonName = name,
                    DriverNumber = rs485Port.DriverNumber,
                    PanelAddress = address
                });
        }
        else
        {
            ds = await client.AddDownstreamToController(controller,
                new MR16OutInfo
                {
                    CommonName = name,
                    DriverNumber = rs485Port.DriverNumber,
                    PanelAddress = address
                });
        }
        
        Console.WriteLine($"Added: {ds.CommonName} at address {address}");
    }
}

cURL Examples

Add On-Board Interface

ACCESS_TOKEN="your-access-token"
INSTANCE_KEY="your-instance-key"
CONTROLLER_KEY="your-controller-key"

curl -X POST \
  "https://api.us.acresecurity.cloud/api/f/${INSTANCE_KEY}/controllers/${CONTROLLER_KEY}/downstream" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "$type": "Feenics.Keep.WebApi.Model.Ep1501OnBoardInfo, Feenics.Keep.WebApi.Model",
    "CommonName": "OnBoard Interface",
    "DriverNumber": 0,
    "PanelAddress": 0
  }'

Add MR52 Expansion

curl -X POST \
  "https://api.us.acresecurity.cloud/api/f/${INSTANCE_KEY}/controllers/${CONTROLLER_KEY}/downstream" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "$type": "Feenics.Keep.WebApi.Model.MR52Info, Feenics.Keep.WebApi.Model",
    "CommonName": "MR52 - Main Entry",
    "DriverNumber": 1,
    "PanelAddress": 1
  }'

Get Downstreams on Controller

curl -X GET \
  "https://api.us.acresecurity.cloud/api/f/${INSTANCE_KEY}/controllers/${CONTROLLER_KEY}/downstream" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

Best Practices

Practice Description
Use unique addresses Each SIO on the same bus must have a unique panel address
Label physical boards Match CommonName to physical labels for easy identification
Document wiring Record which ports and addresses connect to which physical boards
Set active port first Always configure the active port before adding downstreams
Match baud rates Controller and SIO baud rates must match
Verify connectivity Check controller status after adding downstreams

Troubleshooting

Issue Possible Cause Solution
Downstream not found Wrong panel address Verify address on physical board DIP switches
Communication errors Baud rate mismatch Match baud rate to SIO configuration
Device offline Active port not set Configure active port before adding downstream
Duplicate address error Address conflict Ensure each SIO has unique address
Wrong driver number Port misconfiguration Retrieve ports and use correct DriverNumber
SIO not responding Wiring issue Check RS-485 A/B wiring and termination