TMRemote SDK

Services API & Plugin Development Guide

TMRemote SDK - Build plugins, integrate via REST API, or extend with custom devices.

Triggers initiate workflows · Actions execute operations · Events notify of changes

REST API

Control TMRemote via HTTP from any device on your network.

Endpoints

EndpointMethodDescription
/api/statusGETGet current system status
/api/profilesGETList all monitor profiles
/api/profiles/{name}/loadPOSTLoad a monitor profile
/api/scenariosGETList all scenarios
/api/scenarios/{name}/runPOSTExecute a scenario
/api/matrix/statusGETGet matrix routing status
/api/matrix/routePOSTSet video routing (input, output)
/api/displaysGETList all displays with info
/api/displays/topologyPOSTSet display topology (extend/clone/internal/external)
/api/plugin/**Plugin-registered routes

Example: Run a Scenario

curl -X POST http://localhost:8080/api/scenarios/Gaming/run

Example: Route Matrix

curl -X POST http://localhost:8080/api/matrix/route \
  -H "Content-Type: application/json" \
  -d '{"input": 1, "output": 3}'

Plugin Development

Create plugins to add new devices, automation, and API endpoints.

Quick Start

  1. Create a class implementing IDevicePlugin
  2. Add your plugin DLL to the Plugins folder
  3. Enable via Settings → Plugins tab

Minimal Plugin

public class MyPlugin : IDevicePlugin
{
    public string PluginId => "my-plugin";
    public string DisplayName => "My Custom Plugin";
    public Version Version => new Version(1, 0, 0);
    public string Description => "My plugin description";
    public string Author => "Your Name";
    public int MinHostVersion => 1;
    public bool IsEnabled { get; set; }
    public IEnumerable<IDevice> ConnectedDevices => Array.Empty<IDevice>();

    public Task InitializeAsync(IPluginHost host)
    {
        // Register custom API route (path, HTTP method, handler)
        host.RegisterApiRoute("myplugin/status", "GET", async req =>
            new ApiResponse { Success = true, Data = "OK" });

        // Use logger
        host.Logger.Info("Plugin initialized");

        return Task.CompletedTask;
    }

    // Required interface members
    public Task ShutdownAsync(PluginCloseReason reason) => Task.CompletedTask;
    public void ShowConfigDialog(IntPtr parentWindow) { }
    public Task<IEnumerable<IDiscoveredDevice>> DiscoverDevicesAsync()
        => Task.FromResult<IEnumerable<IDiscoveredDevice>>(Array.Empty<IDiscoveredDevice>());
    public Task<IDevice> ConnectDeviceAsync(DeviceConfig config) => Task.FromResult<IDevice>(null);
    public IEnumerable<IActionDefinition> GetActionDefinitions() => Array.Empty<IActionDefinition>();
    public UserControl CreateSettingsPanel() => null;
    public PluginRequirements GetRequirements() => new PluginRequirements();
    public void Dispose() { }
}

Key IPluginHost Services

Property/MethodDescription
host.LoggerIPluginLogger for diagnostic output
host.GetPluginDataPath(id)Get plugin's data storage folder
host.LoadSettings<T>(id)Load plugin settings from storage
host.SaveSettings<T>(id, settings)Save plugin settings to storage
host.ShowNotification(title, msg, icon)Show tray notification
host.GetDevice(deviceId)Get device from any plugin
host.GetDevicesByType<T>()Get devices by interface type
host.RegisterApiRoute(path, method, handler)Add custom HTTP endpoint
host.RegisterHotkey(id, name, callback)Register global hotkey
host.Subscribe<TEvent>(handler)Subscribe to plugin events

Device Interfaces

Plugins expose devices through interfaces. Use IMatrixDevice for matrix switchers with labeled I/O ports.

IDevice (Base Interface)

Property/MethodDescription
string DeviceIdUnique device identifier
string DisplayNameHuman-readable name
string PluginIdOwning plugin ID
string AddressDevice address (IP/hostname)
bool IsConnectedConnection state
ConnectAsync()Connect to device
DisconnectAsync()Disconnect from device
ConnectionStateChangedFired on connect/disconnect
ErrorOccurredFired on device errors

IMatrixDevice (extends IDevice)

For matrix switching devices with input/output ports. Labels are used in routing grid tooltips.

PropertyDescription
string[] InputLabelsLabels for input ports (8 elements)
string[] OutputLabelsLabels for output ports (8 elements)

Example: Matrix Device

public class MyMatrixDevice : IMatrixDevice
{
    public string DeviceId => _config.DeviceId;
    public string DisplayName => _config.DisplayName;
    public string PluginId => "my-matrix";
    public string Address => _config.IpAddress;
    public bool IsConnected => _client.IsConnected;

    // IMatrixDevice - labels shown in UI tooltips
    public string[] InputLabels => _config.InputLabels;
    public string[] OutputLabels => _config.OutputLabels;

    // ... connection methods, events
}

Services Reference

Core service interfaces for advanced integration.


IMatrixService

Interface for HDMI matrix control operations.

Connection

Property/MethodDescription
bool IsConnectedWhether matrix is connected
string IpAddressCurrent matrix IP address
ConnectAsync(string ip)Connect to matrix at IP
Disconnect()Disconnect from matrix
DiscoverAsync(int timeout)Discover devices on network

Routing

MethodDescription
SetRouteAsync(int input, int output)Route input to single output
SetAllRoutesAsync(int[] routes)Set all 8 output routes
SetAllRoutesVerifiedAsync(int[] routes)Set routes with verification
GetVideoStatusAsync()Get current routing status

Power & Audio

MethodDescription
SetPowerAsync(bool on)Set power state
GetPowerStatusAsync()Get power status
SetExternalAudioAsync(int input)Set external audio (0=off)
GetAudioStatusAsync()Get audio status

CEC Commands

MethodDescription
CecPowerOnOutputAsync(int port)Power on output display
CecPowerOffOutputAsync(int port)Power off output display
CecPowerOnInputAsync(int port)Power on input source
CecPowerOffInputAsync(int port)Power off input source

Presets

MethodDescription
RecallPresetAsync(int index)Recall hardware preset (1-8)
SavePresetAsync(int index)Save to hardware preset (1-8)

Events

EventDescription
RoutingChangedFired when video routing changes
PowerChangedFired when power state changes
AudioChangedFired when audio routing changes
StateChangedFired on any state change
ErrorOccurredFired on communication errors

Example Usage

// Connect and route
await matrix.ConnectAsync("192.168.1.100");

// Route input 1 to outputs 1-4
await matrix.SetAllRoutesAsync(new[] { 1, 1, 1, 1, 2, 2, 3, 4 });

// Power on all TVs
for (int i = 1; i <= 8; i++)
    await matrix.CecPowerOnOutputAsync(i);

// Set external audio to input 3
await matrix.SetExternalAudioAsync(3);

IProfileService

Interface for monitor profiles and scenarios.

Monitor Profiles

MethodDescription
GetMonitorProfiles()Get list of profile names
ApplyMonitorProfileAsync(name)Apply a monitor profile
SaveMonitorProfileAsync(name)Save current display config
DeleteMonitorProfileAsync(name)Delete a profile

Scenarios

MethodDescription
GetScenarios()Get list of scenario names
LoadScenarioAsync(name)Load scenario details
ExecuteScenarioAsync(name, progress)Execute a scenario
SaveScenarioAsync(scenario)Save scenario
DeleteScenarioAsync(name)Delete scenario

ScenarioInfo Properties

PropertyTypeDescription
NamestringScenario name
DescriptionstringDescription text
VideoRoutingint[8]Input for each output
MonitorProfileNamestringMonitor profile to apply
ExternalAudioInputintExternal audio input (0=off)
PowerOnbool?Power on matrix first
EnabledboolWhether scenario is active

Example Usage

// Execute a scenario with progress
var progress = new Progress<ScenarioProgress>(p => {
    Console.WriteLine($"[{p.StepNumber}/{p.TotalSteps}] {p.CurrentStep}");
});

var result = await profiles.ExecuteScenarioAsync("Gaming", progress);

if (result.Success)
    Console.WriteLine($"Completed in {result.Duration.TotalSeconds}s");
else
    Console.WriteLine($"Failed: {result.Message}");

IPluginService

Interface for plugin management and API routing.

Plugin Information

MethodDescription
GetPlugins()Get all plugin info
GetPlugin(pluginId)Get specific plugin info
PluginCountNumber of loaded plugins

Plugin State

MethodDescription
IsPluginEnabled(pluginId)Check if plugin is enabled
EnablePlugin(pluginId)Enable a plugin
DisablePlugin(pluginId)Disable a plugin
ReloadPluginAsync(pluginId)Reload a plugin

Plugin Configuration

MethodDescription
ShowPluginSettings(pluginId, hwnd)Show plugin config dialog
GetPluginRequirements(pluginId)Get plugin requirements
AreRequirementsMet(pluginId)Check if requirements met

API Routing

MethodDescription
GetRegisteredApiRoutes()List all API routes
HandleApiRequestAsync(request)Route API request to plugin

Plugin Development

Create plugins by implementing the IDevicePlugin interface.

IDevicePlugin Interface

public interface IDevicePlugin : IDisposable
{
    // Identity
    string PluginId { get; }
    string DisplayName { get; }
    string Author { get; }
    Version Version { get; }
    string Description { get; }
    int MinHostVersion { get; }

    // Lifecycle
    Task InitializeAsync(IPluginHost host);
    Task ShutdownAsync(PluginCloseReason reason);
    void ShowConfigDialog(IntPtr parentWindow);
    bool IsEnabled { get; set; }

    // Device Management
    Task<IEnumerable<IDiscoveredDevice>> DiscoverDevicesAsync();
    Task<IDevice> ConnectDeviceAsync(DeviceConfig config);
    IEnumerable<IDevice> ConnectedDevices { get; }

    // Actions
    IEnumerable<IActionDefinition> GetActionDefinitions();

    // UI
    UserControl CreateSettingsPanel();

    // Requirements
    PluginRequirements GetRequirements();
}

IPluginHost Interface

The IPluginHost provides access to TMRemote services:

public interface IPluginHost
{
    // Logging
    IPluginLogger Logger { get; }

    // Configuration & Storage
    string GetPluginDataPath(string pluginId);
    T LoadSettings<T>(string pluginId) where T : class, new();
    void SaveSettings<T>(string pluginId, T settings) where T : class;

    // UI Integration
    void ShowNotification(string title, string message, NotificationIcon icon);
    void RequestSettingsRefresh();
    void NavigateToSettingsTab(string tabName);

    // Firewall Management
    bool RequestFirewallAccess(PortRequirement port);
    bool RevokeFirewallAccess(PortRequirement port);
    bool IsFirewallPortOpen(int port);

    // System Enumerations
    IReadOnlyList<DisplayInfo> GetDisplays();
    IReadOnlyList<AudioEndpointInfo> GetAudioEndpoints(AudioFlow flow);
    IReadOnlyList<string> GetMonitorProfiles();
    IReadOnlyList<string> GetScenarios();
    IReadOnlyList<PluginInfo> GetPlugins();

    // Device Enumeration (Cross-Plugin)
    IReadOnlyList<IDevice> GetAllDevices();
    IReadOnlyList<IDevice> GetDevicesByPlugin(string pluginId);
    IReadOnlyList<T> GetDevicesByType<T>() where T : class, IDevice;
    void RegisterDevice(IDevice device);
    void UnregisterDevice(string deviceId);
    IDevice GetDevice(string deviceId);

    // REST API Extension
    void RegisterApiRoute(string path, string method, Func<ApiRequest, Task<ApiResponse>> handler);
    void UnregisterApiRoute(string path, string method);

    // Event Subscription
    void Subscribe<TEvent>(Action<TEvent> handler) where TEvent : IPluginEvent;
    void Unsubscribe<TEvent>(Action<TEvent> handler) where TEvent : IPluginEvent;
    void Publish<TEvent>(TEvent evt) where TEvent : IPluginEvent;

    // Hotkey Registration
    void RegisterHotkey(string hotkeyId, string displayName, Action callback);
    void UnregisterHotkey(string hotkeyId);

    // Services
    T GetService<T>() where T : class;
    INavigationPanelService NavigationPanels { get; }

    // Application Info
    Version HostVersion { get; }
    string ApplicationPath { get; }
    int InterfaceVersion { get; }
    int ApiRevision { get; }
}

Example Plugin

public class MyPlugin : IDevicePlugin
{
    private IPluginHost _host;

    public string PluginId => "my-plugin";
    public string DisplayName => "My Custom Plugin";
    public string Description => "Does amazing things";
    public Version Version => new Version(1, 0, 0);
    public string Author => "Your Name";
    public int MinHostVersion => 1;
    public bool IsEnabled { get; set; }

    public IEnumerable<IDevice> ConnectedDevices => _devices;
    private List<IDevice> _devices = new();

    public async Task InitializeAsync(IPluginHost host)
    {
        _host = host;
        _host.Logger.Info("MyPlugin initializing...");

        // Register API route (requires path AND HTTP method)
        _host.RegisterApiRoute("myplugin/status", "GET", HandleStatusRequest);

        // Subscribe to events
        _host.Subscribe<RoutingChangedEvent>(OnRoutingChanged);
    }

    private Task<ApiResponse> HandleStatusRequest(ApiRequest request)
    {
        return Task.FromResult(new ApiResponse
        {
            Success = true,
            Data = new { Status = "OK", Version = Version.ToString() }
        });
    }

    private void OnRoutingChanged(RoutingChangedEvent e)
    {
        _host.Logger.Info($"Routing changed: {string.Join(",", e.Routes)}");
    }

    public Task ShutdownAsync(PluginCloseReason reason)
    {
        _host.Unsubscribe<RoutingChangedEvent>(OnRoutingChanged);
        return Task.CompletedTask;
    }

    public void ShowConfigDialog(IntPtr parentWindow) { }
    public Task<IEnumerable<IDiscoveredDevice>> DiscoverDevicesAsync()
        => Task.FromResult<IEnumerable<IDiscoveredDevice>>(Array.Empty<IDiscoveredDevice>());
    public Task<IDevice> ConnectDeviceAsync(DeviceConfig config) => Task.FromResult<IDevice>(null);
    public IEnumerable<IActionDefinition> GetActionDefinitions() => Array.Empty<IActionDefinition>();
    public UserControl CreateSettingsPanel() => null;
    public PluginRequirements GetRequirements() => new PluginRequirements();
    public void Dispose() { }
}

Navigation Panel Provider

Plugins that want to provide custom control panels implement INavigationPanelProvider:

public interface INavigationPanelProvider
{
    // Synchronous - returns immediately with current state (may be stale)
    NavigationPanelDefinition GetPanelDefinition(IDevice device = null);

    // Asynchronous - waits for device state to load before returning
    Task<NavigationPanelDefinition> GetPanelDefinitionAsync(IDevice device = null);

    // Execute control actions
    Task<bool> ExecuteControlAsync(IDevice device, string controlId);
    Task<bool> ExecuteControlAsync(IDevice device, string controlId, object value);

    // Get current control state
    Task<object> GetControlStateAsync(IDevice device, string controlId);
}

Sync vs Async Panel Definition

MethodWhen to UseBehavior
GetPanelDefinition Quick checks, device list population Returns immediately with cached/default state
GetPanelDefinitionAsync Initial panel display, after device connection Fetches current device state before building panel

Why Async Matters

Device state (patterns, controls, volume, etc.) is fetched asynchronously. If you build the panel before state is loaded, it will show stale or empty data. GetPanelDefinitionAsync ensures state is loaded first:

public async Task<NavigationPanelDefinition> GetPanelDefinitionAsync(IDevice device = null)
{
    var myDevice = device as MyDevice ?? _connectedDevices.FirstOrDefault();

    // Load current state before building panel
    if (myDevice != null && myDevice.IsConnected)
    {
        await myDevice.RefreshStateAsync();  // Fetch current state from device
    }

    // Now build panel with up-to-date state
    return BuildPanelDefinition(myDevice);
}

Panel Definition Structure

var panel = new NavigationPanelDefinition
{
    PanelId = "my-panel",
    DisplayName = "My Device",
    PluginId = PluginId,
    PollIntervalMs = 5000  // Refresh state every 5 seconds
};

// Add control sections
var section = new ControlSection
{
    SectionId = "controls",
    DisplayName = "Device Controls",
    Layout = SectionLayout.Row
};

section.Controls.Add(new ControlDefinition
{
    ControlId = "power",
    DisplayName = "Power",
    Type = ControlType.Toggle,
    StandardControl = StandardControl.Power
});

panel.Sections.Add(section);

CLI Integration

The ServicesCLI class provides programmatic CLI access:

// Create CLI with services
var cli = new ServicesCLI(
    matrix: AppServices.Instance.Matrix,
    profiles: AppServices.Instance.Profiles,
    plugins: AppServices.Instance.Plugins
);

// Execute commands
int exitCode = await cli.ExecuteAsync(new[] { "route", "1", "3" });
await cli.ExecuteAsync(new[] { "scenario", "run", "Gaming" });
await cli.ExecuteAsync(new[] { "plugins" });

Available Commands

CategoryCommands
Generalhelp, status
Matrixconnect, disconnect, discover, route, routes, power, audio, preset, cec
Profilesprofiles, profile (apply/save/delete)
Scenariosscenarios, scenario (run/show/delete)
Pluginsplugins, plugin (enable/disable/reload/info), api-routes

See Also


TMRemote SDK 2.73.26.5

Top