C# Sky Q Remote Control, electronic programme guide (EPG) and record

ParkSquare.SkyTv

BUILD STATUS Continuous Integration Build Status

INSTALLATION

Install-Package ParkSquare.SkyTv

DESCRIPTION

Control Sky Q set-top box over the network. Supports all remote control commands including power on/off, change channel, play/pause/stop/record, show program guide, box office and UI navigation. Retrieve box information such as device manufacturer, model and software version. View all Sky electronic programme guide (EPG) channel information and programme guide data, search for what's on now/next. Schedule recordings, list and delete recordings.

Documentation

There are essentially four components: DeviceDiscovery, ApiClient, RemoteControl and EpgService. Note that this package only works with SkyQ boxes, support for the old SkyHD and Sky+ boxes was removed from v4 onwards.

Device Discovery

This uses UPnP/SSDP to find devices on your network. The IP addresses of any that are identified as Sky boxes are returned. Devices will periodically advertise their existence, so you need to choose a timeframe long enough to receive them - usually a few seconds is enough.


    // First, get all IP addresses for the current device

    var clientIpAddresses = new List<IPAddress>();

    foreach (var netInterface in NetworkInterface.GetAllNetworkInterfaces())
    {
        if (netInterface.OperationalStatus != OperationalStatus.Up) continue;

        var ipProps = netInterface.GetIPProperties();

        foreach (var addr in ipProps.UnicastAddresses)
        {
            if (addr.Address.AddressFamily == AddressFamily.InterNetwork)
            {
                clientIpAddresses.Add(addr.Address);
            }
        }
    }

    IReadOnlyCollection<IPAddress> boxIpAddresses;

    // Find SkyQ boxes on the network

    using (var deviceDiscovery = new DeviceDiscovery())
    {
        Console.WriteLine("Looking for SkyQ boxes on network...");

        boxIpAddresses = await deviceDiscovery.FindSkyBoxesAsync(clientIpAddresses, TimeSpan.FromSeconds(3));
    }

    if (!boxIpAddresses.Any())
    {
        Console.WriteLine("No Sky Q boxes found!");
    }

Set Top Box C# API Client

An API running on the box exposes certain information and functions. An ApiClient instance can be created directly, or if using Dependency Injection, using the ApiClientFactory. The constructors of both objects require an IRestClient (basically just a convenience wrapper around HttpClient). Once you have an ApiClient, you can query details about the box, get, schedule and remove PVR recordings.

GetBoxDetailsAsync()

This call returns various bits of information about your Sky Q box, such as MAC address, software versions, make and model, and whether it is currently in standby mode or not.


    var restClient = new RestClient(new HttpClient());
    var apiClientFactory = new ApiClientFactory(restClient);

    foreach (var boxIpAddress in boxIpAddresses)
    {
        Console.WriteLine($"{boxIpAddress}");

        // Get box info

        var apiClient = apiClientFactory.CreateClient(boxIpAddress);
        var box = await apiClient.GetBoxDetailsAsync();

        Console.WriteLine($"{box.IpAddress} {box.MacAddress} {box.Manufacturer} {box.ModelNumber} " +
        $"Up time: {box.SystemUptime} 4K Enabled: {box.UhdCapable} {box.PowerStatus}");
    }

GetRecordingsAsync()

This call gets a list of all PVR recordings on the box, which includes ones that have been deleted but not yet erased. If the item has been deleted, the IsDeleted flag will be set, the DeletedOn property will be present, and the date/time at which the item will be permanently removed is in the ExpiresOn property. The PvrId is a unique identifier for that particular recording on your box, and can be used by other calls.


    var restClient = new RestClient(new HttpClient());
    var apiClientFactory = new ApiClientFactory(restClient);

    foreach (var boxIpAddress in boxIpAddresses)
    {
        Console.WriteLine($"{boxIpAddress}");

        var recordings = await apiClient.GetRecordingsAsync();

        foreach (var recording in recordings)
        {
            Console.WriteLine($"{recording.Title} Season {recording.SeasonNumber} Episode {recording.EpisodeNumber}");
        }
    }

RecordProgrammeAsync()

This method tells the PVR to record a programme, or 'events' as they are known in Sky parlance. The EventId passed in to identify the programme can be found in the EPG listings search discussed later. An optional flag called SeriesLink can be specified to tell the box to link all future episodes and record them automatically. This option is false by default, so only the individual programme will be recorded.

            
    var success = await apiClient.RecordProgrammeAsync("E708-7a0", true);

CancelRecordingAsync()

This method stops an in-progress recording, but does will not delete the partially recorded programme, instead leaving it on the box to watch.

            
    var success = await apiClient.CancelRecordingAsync("pvr-1234");

DeleteRecordingAsync()

This will delete the recording. By default, it will only soft delete the recording. There is an optional PermanentlyDelete parameter that will force the item to be erased from the disk immediately.

            
    var success = await apiClient.DeleteRecordingAsync("pvrid1234", true);

GetStorageStatusAsync()

This call returns the amount of disk space available and used.

            
    var storageStatus = await apiClient.GetStorageStatusAsync();
    Console.WriteLine($"{storageStatus.UsedMb} Mb used of {storageStatus.QuotaMb} Mb ({storageStatus.PercentageUsed} %)");

GetTimeAsync()

This call returns the current time according to the set-top box, in both UTC and local timezones.

            
    var time = await apiClient.GetTimeAsync();
    Console.WriteLine($"{time.Utc} {time.Local}");

Remote Control

This object allows remote control of the Sky Q box over your network by IP address. The available commands more or less mimic those found on the physical remote control, a list of which can be found in the RemoteCommand enum. Note that remote commands are ignored if the box is in standby. You can use the PowerStatus property from GetBoxDetailsAsync() above to determine if the box needs powering on before sending commands. The example below switches the box to Channel 409 (Sky Sports News), and brings the box out of standby if necessary using the result of GetBoxDetailsAsync() above.

            
	var remoteControl = new RemoteControl(boxIpAddress);

	if (box.PowerStatus == PowerStatus.Standby)
	{
		Console.WriteLine("Box is in standby, switching on....");
		await remoteControl.SendCommandAsync(RemoteCommand.Power, RemoteCommand.Backup);
	}

	await remoteControl.SendCommandAsync(
		RemoteCommand.Number4, 
		RemoteCommand.Number0,
		RemoteCommand.Number9);

Electronic Programme Guide (EPG) Service

The EpgService class needs an instance IRestClient injecting in.

GetEpgAsync()

This method provides access to a complete list of channels, genres, filters, and other static data.

            
	var epgService = new EpgService(new RestClient(new HttpClient()));
	var data = await epgService.GetEpgAsync();

	foreach (var channel in data.Channels)
	{
		Console.WriteLine($"{channel.Number} {channel.Title}");
	}

SearchListingsAsync()

This method allows searching for what is on one or more channels on a given date. Generally, you can get listings for today and up to a few weeks into the future. It is normally possible to get yesterday's listings also, but no further back than that. Channel Ids can be found from the static data returned by GetEpgAsync() above.


    // Get today's listings for various channels

    var searchCriteria = new SearchCriteria
    {
        Date = DateTime.Now,
        ChannelIds = new[] {1872, 2076, 1314, 1800}
    };

    var listings = await epgService.SearchListingsAsync(searchCriteria);

    foreach (var listing in listings)
    {
        Console.WriteLine($"{listing.ChannelId}");
        foreach (var programme in listing.Programmes)
        {
            Console.WriteLine($"{programme.StartTime} {programme.Title} {programme.Duration} {programme.PictureQuality} {programme.Audio}");
        }
    }