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.
| 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 |
| 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 |
Before adding downstream interfaces, ensure you have:
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})");
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 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}");
// 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}");
}
/// <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}");
}
}
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
}'
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
}'
curl -X GET \
"https://api.us.acresecurity.cloud/api/f/${INSTANCE_KEY}/controllers/${CONTROLLER_KEY}/downstream" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
| 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 |
| 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 |