From 2ad5d7853b2a9c0ee948c2dc0cd1dc5200b48ef2 Mon Sep 17 00:00:00 2001 From: Craig Moksnes Date: Sun, 26 Jan 2020 14:34:15 -0600 Subject: [PATCH] Added Plex support - Hacked in UPnP/SSDP discovery response - Added automatic tuner count determination - Fixed lineup.json and discover.json services responding with the wrong listen IP --- ProxyFormUnit.fmx | 67 +++++- ProxyFormUnit.pas | 74 ++++--- ProxyServerModuleUnit.dfm | 4 +- ProxyServerModuleUnit.pas | 185 +++++++++++++++- ProxyServiceModuleUnit.pas | 12 +- ProxyWebModuleUnit.dfm | 10 + ProxyWebModuleUnit.pas | 419 ++++++++++++++++++++++++++++++++++++- ceton/Ceton.pas | 51 ++++- hdhr/HDHR.pas | 134 +++++++++--- 9 files changed, 879 insertions(+), 77 deletions(-) diff --git a/ProxyFormUnit.fmx b/ProxyFormUnit.fmx index 8989df1..258972a 100644 --- a/ProxyFormUnit.fmx +++ b/ProxyFormUnit.fmx @@ -2,7 +2,7 @@ object MainForm: TMainForm Left = 0 Top = 0 Caption = 'Ceton HDHomeRun Proxy' - ClientHeight = 748 + ClientHeight = 754 ClientWidth = 435 Fill.Kind = Solid Position = ScreenCenter @@ -17,11 +17,11 @@ object MainForm: TMainForm object VertScrollBox1: TVertScrollBox Align = Contents Size.Width = 435.000000000000000000 - Size.Height = 748.000000000000000000 + Size.Height = 754.000000000000000000 Size.PlatformDefault = False TabOrder = 3 Viewport.Width = 435.000000000000000000 - Viewport.Height = 748.000000000000000000 + Viewport.Height = 754.000000000000000000 object gbGroup: TGroupBox Align = Top Padding.Left = 15.000000000000000000 @@ -73,6 +73,14 @@ object MainForm: TMainForm Text = 'Request Channels' OnClick = btnRefreshChannelsClick end + object lblSelectedChannels: TLabel + Position.X = 125.000000000000000000 + Position.Y = 253.000000000000000000 + Size.Width = 180.000000000000000000 + Size.Height = 17.000000000000000000 + Size.PlatformDefault = False + TabOrder = 3 + end end object gbSettings: TGroupBox Align = Top @@ -81,7 +89,7 @@ object MainForm: TMainForm Size.PlatformDefault = False StyleLookup = 'gbGroupStyle1' Text = 'Settings' - TabOrder = 1 + TabOrder = 2 object eCetonTunerAddress: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] TabOrder = 3 @@ -165,7 +173,7 @@ object MainForm: TMainForm Size.PlatformDefault = False StyleLookup = 'gbGroupStyle1' Text = 'Statistics' - TabOrder = 2 + TabOrder = 3 object lbStats: TListView ItemAppearanceClassName = 'TCustomizeItemObjects' ItemEditAppearanceClassName = 'TCustomizeItemObjects' @@ -197,6 +205,55 @@ object MainForm: TMainForm Size.Height = 8.000000000000000000 Size.PlatformDefault = False end + object GroupBox1: TGroupBox + Align = Top + Position.Y = 748.000000000000000000 + Size.Width = 435.000000000000000000 + Size.Height = 141.000000000000000000 + Size.PlatformDefault = False + StyleLookup = 'gbGroupStyle1' + Text = 'Debug' + Visible = False + TabOrder = 1 + object eDebugIP: TEdit + Touch.InteractiveGestures = [LongTap, DoubleTap] + TabOrder = 0 + Position.X = 37.000000000000000000 + Position.Y = 24.000000000000000000 + Size.Width = 129.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + end + object Label5: TLabel + Position.X = 16.000000000000000000 + Position.Y = 27.000000000000000000 + Size.Width = 17.000000000000000000 + Size.Height = 17.000000000000000000 + Size.PlatformDefault = False + Text = 'IP:' + TabOrder = 1 + end + object btnDebugDiscoveryRequest: TButton + Position.X = 16.000000000000000000 + Position.Y = 56.000000000000000000 + Size.Width = 121.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + TabOrder = 2 + Text = 'Discovery Request' + OnClick = btnDebugDiscoveryRequestClick + end + end + object Splitter3: TSplitter + Align = Top + Cursor = crVSplit + MinSize = 20.000000000000000000 + Position.Y = 748.000000000000000000 + Size.Width = 435.000000000000000000 + Size.Height = 8.000000000000000000 + Size.PlatformDefault = False + Visible = False + end end object StyleBook1: TStyleBook Styles = < diff --git a/ProxyFormUnit.pas b/ProxyFormUnit.pas index b2b65dd..9df8d4c 100644 --- a/ProxyFormUnit.pas +++ b/ProxyFormUnit.pas @@ -33,6 +33,8 @@ interface REST.json, REST.Client, + IdGlobal, + HDHR, Ceton, SocketUtils, @@ -74,6 +76,12 @@ TMainForm = class(TForm, IServiceConfigEvents) eHDHRListenHTTPPort: TEdit; eHDHRListenIP: TEdit; Label4: TLabel; + GroupBox1: TGroupBox; + Splitter3: TSplitter; + eDebugIP: TEdit; + Label5: TLabel; + btnDebugDiscoveryRequest: TButton; + lblSelectedChannels: TLabel; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure lbChannelsChangeCheck(Sender: TObject); @@ -85,6 +93,7 @@ TMainForm = class(TForm, IServiceConfigEvents) procedure btnRefreshChannelsClick(Sender: TObject); procedure eHDHRListenHTTPPortChangeTracking(Sender: TObject); procedure eHDHRListenIPChangeTracking(Sender: TObject); + procedure btnDebugDiscoveryRequestClick(Sender: TObject); private { Private declarations } fConfigManager: IServiceConfigManager; @@ -102,6 +111,7 @@ TMainForm = class(TForm, IServiceConfigEvents) procedure Save(const aSections: TServiceConfigSections); procedure UpdateInterface; + procedure UpdateChannelCount; procedure FillChannels; procedure FillTunerStatistics; @@ -237,6 +247,8 @@ procedure TMainForm.FillChannels; end else lbChannels.Enabled := False; + + UpdateChannelCount; end; procedure TMainForm.FormDestroy(Sender: TObject); @@ -264,38 +276,12 @@ procedure TMainForm.lbChannelsChangeCheck(Sender: TObject); ConfigManager.UnlockConfig(lConfig); end; + updateChannelCount; + Save([TServiceConfigSection.Channels]); end; end; -{procedure TMainForm.GetConfig; -var - lOldConfig, lNewConfig: TServiceConfig; -begin - lOldConfig := TServiceConfig.Create; - lNewConfig := TServiceConfig.Create; - try - lOldConfig.Ceton.ChannelMap.Exclude := lOldConfig.Ceton.ChannelMap.Exclude + [TChannelMapSection.Items]; - lOldConfig.Assign(fConfig); - - lNewConfig.Ceton.ChannelMap.Exclude := lNewConfig.Ceton.ChannelMap.Exclude + [TChannelMapSection.Items]; - ProxyServiceModule.GetConfig(lNewConfig); - - if lNewConfig.ToJSON <> lOldConfig.ToJSON then - begin - // Do not reload channels on every change - lbChannels.Clear; - - fConfig.Assign(lNewConfig); - - UpdateInterface; - end; - finally - lNewConfig.Free; - lOldConfig.Free; - end; -end;} - procedure TMainForm.BeginInterfaceUpdate; begin Inc(fInterfaceUpdateCount); @@ -531,4 +517,36 @@ procedure TMainForm.eHDHRListenIPChangeTracking(Sender: TObject); end; end; +procedure TMainForm.btnDebugDiscoveryRequestClick(Sender: TObject); +var + lDiscovery: TDiscoveryData; + lBytes: TBytes; +begin + lDiscovery.DeviceType := HDHOMERUN_DEVICE_TYPE_TUNER; + lDiscovery.DeviceID := HDHOMERUN_DEVICE_ID_WILDCARD; + + lBytes := TPacket.FromDiscovery(True, lDiscovery).ToBytes; + + ProxyServerModule.DiscoveryServer.SendBuffer(eDebugIP.Text, HDHR_DISCOVERY_PORT, TIdBytes(lBytes)); +// ProxyServerModule.DiscoveryServer.Broadcast(TIdBytes(lBytes), HDHR_DISCOVERY_PORT); +end; + +procedure TMainForm.UpdateChannelCount; +var + i, lChannelCount: Integer; +begin + if fEditingChannels then + begin + lChannelCount := 0; + for i := 0 to lbChannels.Items.Count-1 do + begin + if (TChannelListBoxItem(lbChannels.ListItems[i]).IsChecked) then + Inc(lChannelCount); + end; + lblSelectedChannels.Text := Format('(%d selected)', [lChannelCount]); + end + else + lblSelectedChannels.Text := ''; +end; + end. diff --git a/ProxyServerModuleUnit.dfm b/ProxyServerModuleUnit.dfm index db7c147..528e6ac 100644 --- a/ProxyServerModuleUnit.dfm +++ b/ProxyServerModuleUnit.dfm @@ -9,8 +9,8 @@ object ProxyServerModule: TProxyServerModule Left = 88 Top = 56 end - object ConfigTimer: TTimer - OnTimer = ConfigTimerTimer + object ServiceTimer: TTimer + OnTimer = ServiceTimerTimer Left = 144 Top = 56 end diff --git a/ProxyServerModuleUnit.pas b/ProxyServerModuleUnit.pas index e6aa172..ae712c9 100644 --- a/ProxyServerModuleUnit.pas +++ b/ProxyServerModuleUnit.pas @@ -17,6 +17,9 @@ interface IdThread, IdContext, IdUDPServer, + IdIPMCastServer, + IdIPMCastClient, + IdTCPServer, IdSocketHandle, IdGlobal, IdStack, @@ -26,6 +29,9 @@ interface Ceton, HDHR; +const + SSDP_PORT = 1900; + type TIdCOMThread = class(TIdThreadWithTask) protected @@ -35,10 +41,10 @@ TIdCOMThread = class(TIdThreadWithTask) TProxyServerModule = class(TDataModule, IServiceConfigEvents) IdScheduler: TIdSchedulerOfThreadDefault; - ConfigTimer: TTimer; + ServiceTimer: TTimer; procedure DataModuleCreate(Sender: TObject); procedure DataModuleDestroy(Sender: TObject); - procedure ConfigTimerTimer(Sender: TObject); + procedure ServiceTimerTimer(Sender: TObject); private { Private declarations } fConfigManager: IServiceConfigManager; @@ -46,20 +52,28 @@ TProxyServerModule = class(TDataModule, IServiceConfigEvents) fServer: TIdHTTPWebBrokerBridge; fDiscoveryServer: TIdUDPServer; +// fSSDPServer: TIdIPMCastServer; + fSSDPClient: TIdIPMCastClient; +// fControlServer: TIdTCPServer; fRestartServers: Boolean; procedure DiscoveryUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle); + procedure SSDPClientRead(Sender: TObject; const AData: TIdBytes; ABinding: TIdSocketHandle); + + procedure ControlTCPConnect(aContext: TIdContext); + procedure ControlTCPExecute(aContext: TIdContext); + procedure ServerException(AContext: TIdContext; AException: Exception); + function CreateSSDPAlivePacket: String; + function GetActive: Boolean; function GetListenIP: String; property ConfigManager: IServiceConfigManager read fConfigManager; property Client: TCetonClient read fClient; - - property ListenIP: String read GetListenIP; protected // IServiceConfigEvents procedure Changed(const aSender: TObject; const aSections: TServiceConfigSections); @@ -69,7 +83,10 @@ TProxyServerModule = class(TDataModule, IServiceConfigEvents) procedure StartServer; procedure StopServer; + property ListenIP: String read GetListenIP; property Active: Boolean read GetActive; + + property DiscoveryServer: TIdUDPServer read fDiscoveryServer; end; var @@ -113,8 +130,20 @@ procedure TProxyServerModule.DataModuleCreate(Sender: TObject); fServer.OnException := ServerException; fDiscoveryServer := TIdUDPServer.Create(Self); - fDiscoveryServer.ThreadedEvent := False; + fDiscoveryServer.ThreadedEvent := True; fDiscoveryServer.OnUDPRead := DiscoveryUDPRead; + +// fSSDPServer := TIdIPMCastServer.Create(Self); +// fSSDPServer.MulticastGroup := '239.255.255.250'; + + fSSDPClient := TIdIPMCastClient.Create(Self); + fSSDPClient.MulticastGroup := '239.255.255.250'; + fSSDPClient.OnIPMCastRead := SSDPClientRead; + fSSDPClient.ThreadedEvent := True; + +// fControlServer := TIdTCPServer.Create(Self); +// fControlServer.OnConnect := ControlTCPConnect; +// fControlServer.OnExecute := ControlTCPExecute; end; procedure TProxyServerModule.DataModuleDestroy(Sender: TObject); @@ -172,6 +201,38 @@ procedure TProxyServerModule.StartServer; except Log.d('Unable to bind discovery listening port'); end; + +{ try + fControlServer.Bindings.Clear; + with fControlServer.Bindings.Add do + begin + IP := lListenIP; + Port := HDHR_DISCOVERY_PORT; + end; + fControlServer.Active := True; + except + Log.d('Unable to bind control listening port'); + end;} + + try + fSSDPClient.Bindings.Clear; + with fSSDPClient.Bindings.Add do + begin + IP := lListenIP; + Port := SSDP_PORT; + end; + fSSDPClient.Active := True; + except + Log.d('Unable to bind SSDP listening port'); + end; + +{ try + fSSDPServer.BoundIP := lListenIP; + fSSDPServer.BoundPort := SSDP_PORT; + fSSDPServer.Active := True; + except + Log.d('Unable to bind SSDP listening port'); + end;} end; end; @@ -190,6 +251,26 @@ procedure TProxyServerModule.StopServer; except // Ignore end; + +{ try + fControlServer.Active := False; + fControlServer.Bindings.Clear; + except + // Ignore + end;} + + try + fSSDPClient.Active := False; + fSSDPClient.Bindings.Clear; + except + // Ignore + end; + +{ try + fSSDPServer.Active := False; + except + // Ignore + end;} end; procedure TProxyServerModule.ServerException(AContext: TIdContext; @@ -207,6 +288,8 @@ procedure TProxyServerModule.DiscoveryUDPRead(AThread: TIdUDPListenerThread; lConfig: TServiceConfig; lBytes: TBytes; lListenIP: String; + lDeviceID: UInt32; + lTunerCount: Integer; begin if Length(AData) > 0 then begin @@ -223,14 +306,23 @@ procedure TProxyServerModule.DiscoveryUDPRead(AThread: TIdUDPListenerThread; lDiscovery := lPacket.ToDiscovery; Log.D('Received discovery request: Device type: %d, Device ID: %s', [lDiscovery.DeviceType, IntToHex(lDiscovery.DeviceID, 8)]); - if (lDiscovery.DeviceType = HDHOMERUN_DEVICE_TYPE_TUNER) and (lDiscovery.DeviceID = HDHOMERUN_DEVICE_ID_WILDCARD) then + ConfigManager.LockConfig(lConfig); + try + lDeviceID := lConfig.DeviceID; + finally + ConfigManager.UnlockConfig(lConfig); + end; + + if ((lDiscovery.DeviceType = HDHOMERUN_DEVICE_TYPE_TUNER) or (lDiscovery.DeviceType = HDHOMERUN_DEVICE_TYPE_WILDCARD)) and ((lDiscovery.DeviceID = HDHOMERUN_DEVICE_ID_WILDCARD) or (lDiscovery.DeviceID = lDeviceID)) then begin lListenIP := ListenIP; + lTunerCount := Client.TunerCount; ConfigManager.LockConfig(lConfig); try lDiscovery.DeviceID := lConfig.DeviceID; - lDiscovery.TunerCount := 6; + lDiscovery.TunerCount := lTunerCount; + lDiscovery.DeviceAuthStr := 'abcdefg'; lDiscovery.BaseURL := AnsiString(Format('http://%s:%d', [lListenIP, lConfig.HTTPPort])); lDiscovery.LineupURL := AnsiString(Format('%s/lineup.json', [lDiscovery.BaseURL])); finally @@ -245,6 +337,10 @@ procedure TProxyServerModule.DiscoveryUDPRead(AThread: TIdUDPListenerThread; AThread.Server.SendBuffer(ABinding.PeerIP, ABinding.PeerPort, TIdBytes(lBytes)); end; end; + HDHOMERUN_TYPE_DISCOVER_RPY: begin + lDiscovery := lPacket.ToDiscovery; + Log.D('Received discovery reply: Device type: %d, Device ID: %s', [lDiscovery.DeviceType, IntToHex(lDiscovery.DeviceID, 8)]); + end; end; end; end; @@ -264,7 +360,7 @@ procedure TProxyServerModule.Changed(const aSender: TObject; end); end; -procedure TProxyServerModule.ConfigTimerTimer(Sender: TObject); +procedure TProxyServerModule.ServiceTimerTimer(Sender: TObject); begin if fRestartServers then begin @@ -273,6 +369,8 @@ procedure TProxyServerModule.ConfigTimerTimer(Sender: TObject); StopServer; StartServer; end; + +// fSSDPServer.Send(CreateSSDPAlivePacket); end; function TProxyServerModule.GetListenIP: String; @@ -297,4 +395,75 @@ function TProxyServerModule.GetListenIP: String; end; end; +procedure TProxyServerModule.ControlTCPConnect(aContext: TIdContext); +begin + Log.D('Control connect'); +end; + +procedure TProxyServerModule.ControlTCPExecute(aContext: TIdContext); +begin + Log.D('Control execute'); +end; + +procedure TProxyServerModule.SSDPClientRead(Sender: TObject; + const AData: TIdBytes; ABinding: TIdSocketHandle); +var + lData: UTF8String; +begin + if Length(AData) > 0 then + begin + SetLength(lData, Length(AData)); + Move(AData[0], lData[Low(lData)], Length(AData)); + + // TODO: Make more robust + if String(lData).StartsWith('M-SEARCH', True) then + begin + if String(lData).Contains('ST: upnp:rootdevice') then + begin + Log.D('Received M-SEARCH for rootdevice from %s', [ABinding.PeerIP]); + fDiscoveryServer.Send(ABinding.PeerIP, ABinding.PeerPort, CreateSSDPAlivePacket); + end; + end; + end; +end; + +// Received SSDP data from 192.168.1.43: NOTIFY * HTTP/1.1 +// Host: 239.255.255.250:1900 +// NT: upnp:rootdevice +// NTS: ssdp:alive +// Server: HDHomeRun/1.0 UPnP/1.0 +// Location: http://192.168.1.43:80/dri/device.xml +// Cache-Control: max-age=1800 +// USN: uuid:473366D2-A765-3D61-B466-XXXXX::upnp:rootdevice + +function TProxyServerModule.CreateSSDPAlivePacket: String; +const + cSSDPAlive = + 'NOTIFY * HTTP/1.1'#13#10+ + 'Host: 239.255.255.250:1900'#13#10+ + 'NT: upnp:rootdevice'#13#10+ + 'NTS: ssdp:alive'#13#10+ + 'Server: HDHomeRun/1.0 UPnP/1.0'#13#10+ + 'Location: http://%s:%d/device.xml'#13#10+ + 'Cache-Control: max-age=1800'#13#10+ + 'USN: uuid:%s::upnp:rootdevice'#13#10#13#10; +var + lListenIP: String; + lPort: Integer; + lDeviceUUID: String; + lConfig: TServiceConfig; +begin + lListenIP := ListenIP; + + ConfigManager.LockConfig(lConfig); + try + lPort := lConfig.HTTPPort; + lDeviceUUID := lConfig.DeviceUUID; + finally + ConfigManager.UnlockConfig(lConfig); + end; + + Result := Format(cSSDPAlive, [ListenIP, lPort, lDeviceUUID]); +end; + end. diff --git a/ProxyServiceModuleUnit.pas b/ProxyServiceModuleUnit.pas index 75135ba..94619f7 100644 --- a/ProxyServiceModuleUnit.pas +++ b/ProxyServiceModuleUnit.pas @@ -30,8 +30,10 @@ TServiceConfig = class(TPersistent) fCeton: TCetonConfig; fListenIP: String; fHTTPPort: Integer; + fDeviceUUID: String; procedure CreateDeviceID; + procedure CreateDeviceUUID; public constructor Create; destructor Destroy; override; @@ -44,6 +46,7 @@ TServiceConfig = class(TPersistent) property Ceton: TCetonConfig read fCeton; property DeviceID: UInt32 read fDeviceID write fDeviceID; + property DeviceUUID: String read fDeviceUUID write fDeviceUUID; property ListenIP: String read fListenIP write fListenIP; property HTTPPort: Integer read fHTTPPort write fHTTPPort; end; @@ -116,7 +119,7 @@ implementation procedure TServiceConfig.CreateDeviceID; begin - fDeviceID := UInt32(Random(Integer($FFFFFFFF))+1); + fDeviceID := THDHRUtils.CreateDeviceID; end; function TServiceConfig.ToJSON: String; @@ -193,6 +196,7 @@ constructor TServiceConfig.Create; fHTTPPort := HDHR_HTTP_PORT; CreateDeviceID; + CreateDeviceUUID; end; destructor TServiceConfig.Destroy; @@ -212,6 +216,7 @@ procedure TServiceConfig.AssignTo(Dest: TPersistent); lDest.fCeton.Assign(fCeton); lDest.fDeviceID := fDeviceID; + lDest.fDeviceUUID := fDeviceUUID; lDest.fHTTPPort := fHTTPPort; lDest.fListenIP := fListenIP; end @@ -220,6 +225,11 @@ procedure TServiceConfig.AssignTo(Dest: TPersistent); end; +procedure TServiceConfig.CreateDeviceUUID; +begin + fDeviceUUID := TGUID.NewGuid.ToString.Substring(1, 36); +end; + { TServiceConfigManager } constructor TServiceConfigManager.Create; diff --git a/ProxyWebModuleUnit.dfm b/ProxyWebModuleUnit.dfm index ef02bba..756466a 100644 --- a/ProxyWebModuleUnit.dfm +++ b/ProxyWebModuleUnit.dfm @@ -32,6 +32,16 @@ object ProxyWebModule: TProxyWebModule Name = 'TunerAction' PathInfo = '/tuner[0-99]/v*' OnAction = ProxyWebModuleTunerActionAction + end + item + Name = 'LineupStatusAction' + PathInfo = '/lineup_status.json' + OnAction = ProxyWebModuleLineupStatusActionAction + end + item + Name = 'DeviceXMLAction' + PathInfo = '/device.xml' + OnAction = ProxyWebModuleDeviceXMLActionAction end> Height = 333 Width = 414 diff --git a/ProxyWebModuleUnit.pas b/ProxyWebModuleUnit.pas index 59c7142..4488cbf 100644 --- a/ProxyWebModuleUnit.pas +++ b/ProxyWebModuleUnit.pas @@ -11,8 +11,12 @@ interface IdHTTPWebBrokerBridge, IDGlobal, REST.JSON, + REST.Json.Types, + REST.JsonReflect, + System.JSON, ProxyServiceModuleUnit, + ProxyServerModuleUnit, HDHR, Ceton; @@ -41,6 +45,10 @@ TProxyWebModule = class(TWebModule) procedure ProxyWebModuleTunerActionAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); procedure WebModuleCreate(Sender: TObject); + procedure ProxyWebModuleLineupStatusActionAction(Sender: TObject; + Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); + procedure ProxyWebModuleDeviceXMLActionAction(Sender: TObject; + Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); private { Private declarations } fConfigManager: IServiceConfigManager; @@ -51,6 +59,8 @@ TProxyWebModule = class(TWebModule) procedure GetLineup(const aLineup: TLineup); procedure SendTuneResponse(const aTuner, aChannel: Integer; const Response: TWebResponse); + function CreateDeviceXML: String; + property ConfigManager: IServiceConfigManager read fConfigManager; property Client: TCetonClient read fClient; public @@ -120,17 +130,20 @@ procedure TProxyWebModule.ProxyWebModuleDiscoverActionAction(Sender: TObject; lResponse: TDiscoverResponse; lConfig: TServiceConfig; lListenIP: String; + lTunerCount: Integer; begin Handled := True; try lResponse := TDiscoverResponse.Create; try - lListenIP := Client.ListenIP; + lListenIP := ProxyServerModule.ListenIP; + lTunerCount := Client.TunerCount; ConfigManager.LockConfig(lConfig); try lResponse.BaseURL := Format('http://%s:%d', [lListenIP, lConfig.HTTPPort]); lResponse.DeviceID := IntToHex(lConfig.DeviceID); + lResponse.TunerCount := lTunerCount; finally ConfigManager.UnlockConfig(lConfig); end; @@ -277,7 +290,7 @@ procedure TProxyWebModule.GetLineup(const aLineup: TLineup); lConfig: TServiceConfig; lCetonConfig: TCetonConfig; lRequestChannels: Boolean; - lTunerAddress: String; + lListenIP: String; lChannelMap: TChannelMap; begin ConfigManager.LockConfig(lConfig); @@ -310,9 +323,10 @@ procedure TProxyWebModule.GetLineup(const aLineup: TLineup); lChannelMap := TChannelMap.create; try + lListenIP := ProxyServerModule.ListenIP; + ConfigManager.LockConfig(lConfig); try - lTunerAddress := lConfig.Ceton.TunerAddress; lChannelMap.Assign(lConfig.Ceton.ChannelMap, False); finally ConfigManager.UnlockConfig(lConfig); @@ -322,7 +336,7 @@ procedure TProxyWebModule.GetLineup(const aLineup: TLineup); begin lChannelMapItem := lChannelMap[i]; if lChannelMapItem.Visible then - aLineup.Add(IntToStr(lChannelMapItem.Channel), lChannelMapItem.Name, Format('http://%s/auto/v%d', [lTunerAddress, lChannelMapItem.Channel])); + aLineup.Add(IntToStr(lChannelMapItem.Channel), lChannelMapItem.Name, Format('http://%s/auto/v%d', [lListenIP, lChannelMapItem.Channel])); end; finally lChannelMap.Free; @@ -363,6 +377,403 @@ procedure TProxyWebModule.WebModuleCreate(Sender: TObject); fClient := ProxyServiceModule.Client; end; +procedure TProxyWebModule.ProxyWebModuleLineupStatusActionAction( + Sender: TObject; Request: TWebRequest; Response: TWebResponse; + var Handled: Boolean); +begin + Handled := True; + try + Log.d('Received lineup status request from %s: %s', [Request.RemoteAddr, Request.PathInfo]); + try + Response.ContentType := 'application/json'; + Response.Content := '{"ScanInProgress":0,"ScanPossible":1,"Source":"Cable","SourceList":["Cable"]}'; + finally + Log.d('Finished lineup status request from %s: %s', [Request.RemoteAddr, Request.PathInfo]); + end; + except + HandleException; + end; +end; + +procedure TProxyWebModule.ProxyWebModuleDeviceXMLActionAction(Sender: TObject; + Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); +begin + Handled := True; + try + Log.d('Received device.xml request from %s: %s', [Request.RemoteAddr, Request.PathInfo]); + try + Response.ContentType := 'application/xml'; + Response.Content := CreateDeviceXML; + finally + Log.d('Finished device.xml request from %s: %s', [Request.RemoteAddr, Request.PathInfo]); + end; + except + HandleException; + end; +end; + +{ + + + + + 1 + 0 + + + VEN_0115&DEV_1310&SUBSYS_0001&REV_0003 VEN_0115&DEV_1310&SUBSYS_0001 VEN_0115&DEV_1310 + MediaDevices + Multimedia + urn:schemas-dkeystone-com:device:SecureContainer:1 + HDHomeRun DRI Tuner XXXXXXXX + / + Silicondust + http://www.silicondust.com/ + HDHR3-CC HDHomeRun PRIME + HDHomeRun PRIME + HDHR3-CC + http://www.silicondust.com/ + XXXXXXXX + uuid:473366D2-A765-3D61-B466-E73B254C632B + + + urn:schemas-microsoft-com:service:OCTAMessage:1 + urn:microsoft-com:serviceId:OCTAMessage + /dri/OCTAMessage.xml + http://192.168.1.43:80/dri/OCTAMessage + http://192.168.1.43:80/dri/OCTAMessage + + + + + urn:schemas-upnp-org:device:MediaServer:1 + HDHomeRun Prime Tuner XXXXXXXX-0 + / + Silicondust + HDHomeRun PRIME + uuid:C94A4A19-9F09-3DC8-AF60-1E918231F33B + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:urn:schemas-upnp-org:service:ConnectionManager + /dri/ConnectionManager.xml + http://192.168.1.43:80/dri/tuner0/ConnectionManager + http://192.168.1.43:80/dri/tuner0/ConnectionManager + + + urn:schemas-upnp-org:service:AVTransport:1 + urn:upnp-org:serviceId:urn:schemas-upnp-org:service:AVTransport + /dri/AVTransport.xml + http://192.168.1.43:80/dri/tuner0/AVTransport + http://192.168.1.43:80/dri/tuner0/AVTransport + + + urn:schemas-microsoft-com:service:WMDRM:1 + urn:microsoft-com:serviceId:urn:schemas-microsoft-com:service:WMDRM + /dri/WMDRM.xml + http://192.168.1.43:80/dri/tuner0/WMDRM + http://192.168.1.43:80/dri/tuner0/WMDRM + + + urn:schemas-opencable-com:service:Security:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Security + /dri/Security.xml + http://192.168.1.43:80/dri/tuner0/Security + http://192.168.1.43:80/dri/tuner0/Security + + + urn:schemas-opencable-com:service:CAS:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:CAS + /dri/CAS.xml + http://192.168.1.43:80/dri/tuner0/CAS + http://192.168.1.43:80/dri/tuner0/CAS + + + urn:schemas-opencable-com:service:Encoder:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Encoder + /dri/Encoder.xml + http://192.168.1.43:80/dri/tuner0/Encoder + http://192.168.1.43:80/dri/tuner0/Encoder + + + urn:schemas-opencable-com:service:Tuner:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Tuner + /dri/Tuner.xml + http://192.168.1.43:80/dri/tuner0/Tuner + http://192.168.1.43:80/dri/tuner0/Tuner + + + urn:schemas-opencable-com:service:FDC:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:FDC + /dri/FDC.xml + http://192.168.1.43:80/dri/tuner0/FDC + http://192.168.1.43:80/dri/tuner0/FDC + + + urn:schemas-opencable-com:service:Mux:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Mux + /dri/Mux.xml + http://192.168.1.43:80/dri/tuner0/Mux + http://192.168.1.43:80/dri/tuner0/Mux + + + urn:schemas-opencable-com:service:Diag:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Diag + /dri/Diag.xml + http://192.168.1.43:80/dri/tuner0/Diag + http://192.168.1.43:80/dri/tuner0/Diag + + + urn:schemas-opencable-com:service:UserActivity:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:UserActivity + /dri/UserActivity.xml + http://192.168.1.43:80/dri/tuner0/UserActivity + http://192.168.1.43:80/dri/tuner0/UserActivity + + + urn:schemas-silicondust-com:service:EAS:1 + urn:silicondust-com:serviceId:urn:schemas-silicondust-com:service:EAS + /dri/EAS.xml + http://192.168.1.43:80/dri/tuner0/EAS + http://192.168.1.43:80/dri/tuner0/EAS + + + + + urn:schemas-upnp-org:device:MediaServer:1 + HDHomeRun Prime Tuner XXXXXXXX-1 + / + Silicondust + HDHomeRun PRIME + uuid:068CCDE0-37DB-3DBE-87F4-C48FDB88CA95 + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:urn:schemas-upnp-org:service:ConnectionManager + /dri/ConnectionManager.xml + http://192.168.1.43:80/dri/tuner1/ConnectionManager + http://192.168.1.43:80/dri/tuner1/ConnectionManager + + + urn:schemas-upnp-org:service:AVTransport:1 + urn:upnp-org:serviceId:urn:schemas-upnp-org:service:AVTransport + /dri/AVTransport.xml + http://192.168.1.43:80/dri/tuner1/AVTransport + http://192.168.1.43:80/dri/tuner1/AVTransport + + + urn:schemas-microsoft-com:service:WMDRM:1 + urn:microsoft-com:serviceId:urn:schemas-microsoft-com:service:WMDRM + /dri/WMDRM.xml + http://192.168.1.43:80/dri/tuner1/WMDRM + http://192.168.1.43:80/dri/tuner1/WMDRM + + + urn:schemas-opencable-com:service:Security:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Security + /dri/Security.xml + http://192.168.1.43:80/dri/tuner1/Security + http://192.168.1.43:80/dri/tuner1/Security + + + urn:schemas-opencable-com:service:CAS:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:CAS + /dri/CAS.xml + http://192.168.1.43:80/dri/tuner1/CAS + http://192.168.1.43:80/dri/tuner1/CAS + + + urn:schemas-opencable-com:service:Encoder:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Encoder + /dri/Encoder.xml + http://192.168.1.43:80/dri/tuner1/Encoder + http://192.168.1.43:80/dri/tuner1/Encoder + + + urn:schemas-opencable-com:service:Tuner:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Tuner + /dri/Tuner.xml + http://192.168.1.43:80/dri/tuner1/Tuner + http://192.168.1.43:80/dri/tuner1/Tuner + + + urn:schemas-opencable-com:service:FDC:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:FDC + /dri/FDC.xml + http://192.168.1.43:80/dri/tuner1/FDC + http://192.168.1.43:80/dri/tuner1/FDC + + + urn:schemas-opencable-com:service:Mux:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Mux + /dri/Mux.xml + http://192.168.1.43:80/dri/tuner1/Mux + http://192.168.1.43:80/dri/tuner1/Mux + + + urn:schemas-opencable-com:service:Diag:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Diag + /dri/Diag.xml + http://192.168.1.43:80/dri/tuner1/Diag + http://192.168.1.43:80/dri/tuner1/Diag + + + urn:schemas-opencable-com:service:UserActivity:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:UserActivity + /dri/UserActivity.xml + http://192.168.1.43:80/dri/tuner1/UserActivity + http://192.168.1.43:80/dri/tuner1/UserActivity + + + urn:schemas-silicondust-com:service:EAS:1 + urn:silicondust-com:serviceId:urn:schemas-silicondust-com:service:EAS + /dri/EAS.xml + http://192.168.1.43:80/dri/tuner1/EAS + http://192.168.1.43:80/dri/tuner1/EAS + + + + + urn:schemas-upnp-org:device:MediaServer:1 + HDHomeRun Prime Tuner XXXXXXXX-2 + / + Silicondust + HDHomeRun PRIME + uuid:9C66EFAF-3230-3896-8112-7FD8E6FE7591 + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:urn:schemas-upnp-org:service:ConnectionManager + /dri/ConnectionManager.xml + http://192.168.1.43:80/dri/tuner2/ConnectionManager + http://192.168.1.43:80/dri/tuner2/ConnectionManager + + + urn:schemas-upnp-org:service:AVTransport:1 + urn:upnp-org:serviceId:urn:schemas-upnp-org:service:AVTransport + /dri/AVTransport.xml + http://192.168.1.43:80/dri/tuner2/AVTransport + http://192.168.1.43:80/dri/tuner2/AVTransport + + + urn:schemas-microsoft-com:service:WMDRM:1 + urn:microsoft-com:serviceId:urn:schemas-microsoft-com:service:WMDRM + /dri/WMDRM.xml + http://192.168.1.43:80/dri/tuner2/WMDRM + http://192.168.1.43:80/dri/tuner2/WMDRM + + + urn:schemas-opencable-com:service:Security:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Security + /dri/Security.xml + http://192.168.1.43:80/dri/tuner2/Security + http://192.168.1.43:80/dri/tuner2/Security + + + urn:schemas-opencable-com:service:CAS:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:CAS + /dri/CAS.xml + http://192.168.1.43:80/dri/tuner2/CAS + http://192.168.1.43:80/dri/tuner2/CAS + + + urn:schemas-opencable-com:service:Encoder:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Encoder + /dri/Encoder.xml + http://192.168.1.43:80/dri/tuner2/Encoder + http://192.168.1.43:80/dri/tuner2/Encoder + + + urn:schemas-opencable-com:service:Tuner:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Tuner + /dri/Tuner.xml + http://192.168.1.43:80/dri/tuner2/Tuner + http://192.168.1.43:80/dri/tuner2/Tuner + + + urn:schemas-opencable-com:service:FDC:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:FDC + /dri/FDC.xml + http://192.168.1.43:80/dri/tuner2/FDC + http://192.168.1.43:80/dri/tuner2/FDC + + + urn:schemas-opencable-com:service:Mux:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Mux + /dri/Mux.xml + http://192.168.1.43:80/dri/tuner2/Mux + http://192.168.1.43:80/dri/tuner2/Mux + + + urn:schemas-opencable-com:service:Diag:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:Diag + /dri/Diag.xml + http://192.168.1.43:80/dri/tuner2/Diag + http://192.168.1.43:80/dri/tuner2/Diag + + + urn:schemas-opencable-com:service:UserActivity:1 + urn:opencable-com:serviceId:urn:schemas-opencable-com:service:UserActivity + /dri/UserActivity.xml + http://192.168.1.43:80/dri/tuner2/UserActivity + http://192.168.1.43:80/dri/tuner2/UserActivity + + + urn:schemas-silicondust-com:service:EAS:1 + urn:silicondust-com:serviceId:urn:schemas-silicondust-com:service:EAS + /dri/EAS.xml + http://192.168.1.43:80/dri/tuner2/EAS + http://192.168.1.43:80/dri/tuner2/EAS + + + + + + +} + +function TProxyWebModule.CreateDeviceXML: String; +const + cDeviceXML = + ''#13#10+ + ''#13#10+ + ' '#13#10+ + ' 1'#13#10+ + ' 0'#13#10+ + ' '#13#10+ + ' '#13#10+ + ' VEN_0115&DEV_1310&SUBSYS_0001&REV_0003 VEN_0115&DEV_1310&SUBSYS_0001 VEN_0115&DEV_1310'#13#10+ + ' MediaDevices'#13#10+ + ' Multimedia'#13#10+ + ' urn:schemas-dkeystone-com:device:SecureContainer:1'#13#10+ + ' HDHomeRun DRI Tuner %0:s'#13#10+ + ' /'#13#10+ + ' Silicondust'#13#10+ + ' http://www.silicondust.com/'#13#10+ + ' HDHR3-CC HDHomeRun PRIME'#13#10+ + ' HDHomeRun PRIME'#13#10+ + ' HDHR3-CC'#13#10+ + ' http://www.silicondust.com/'#13#10+ + ' %0:s'#13#10+ + ' uuid:%1:s'#13#10+ + ' '#13#10+ + ''; + +var + lConfig: TServiceConfig; + lDeviceID, lDeviceUUID: String; +begin + ConfigManager.LockConfig(lConfig); + try + lDeviceID := IntToHex(lConfig.DeviceID); + lDeviceUUID := lConfig.DeviceUUID; + finally + ConfigManager.UnlockConfig(lConfig); + end; + + Result := Format(cDeviceXML, [lDeviceID, lDeviceUUID]); +end; + initialization finalization Web.WebReq.FreeWebModules; diff --git a/ceton/Ceton.pas b/ceton/Ceton.pas index ef11475..308df0c 100644 --- a/ceton/Ceton.pas +++ b/ceton/Ceton.pas @@ -45,6 +45,7 @@ TCetonConfig = class(TPersistent) fChannelMap: TChannelMap; fTunerAddress: String; fListenIP: String; + fTunerCount: Integer; public constructor Create; destructor Destroy; override; @@ -202,6 +203,7 @@ TREST = class abstract class function WaitForVar(const aClient: TRestClient; const aTunerID: Integer; const aServiceName, aVarName: String; const aTries: Integer; const aWaitMs: Integer; const aValidateValueCallback: TValidateValueCallback): String; static; class function VarMatches(const aValue: String): TValidateValueCallback; static; class function VarContains(const aValue: String): TValidateValueCallback; static; + class function GetTunerCount(const aClient: TRestClient): Integer; static; end; TTunerStats = record @@ -282,6 +284,7 @@ TCetonClient = class procedure NeedClient; function GetListenIP: String; + function GetTunerCount: Integer; public constructor Create; destructor Destroy; override; @@ -300,6 +303,7 @@ TCetonClient = class function GetTunerStats: TTunerStatsArray; property ListenIP: String read GetListenIP; + property TunerCount: Integer read GetTunerCount; end; TCetonVideoStream = class(TStream) @@ -762,6 +766,33 @@ class function TREST.VarContains(const aValue: String): TValidateValueCallback; end; end; +class function TREST.GetTunerCount(const aClient: TRestClient): Integer; +var + lRequest: TRESTRequest; +begin + Log.d('Checking tuner count'); + + lRequest := TRESTRequest.Create(nil); + try + lRequest.Timeout := 1500; + + lRequest.Client := aClient; + lRequest.Method := TRESTRequestMethod.rmGet; + lRequest.Resource := 'Services/6/Status.html'; + + lRequest.Execute; + + if lRequest.Response.StatusCode = 404 then + Result := 4 + else + Result := 6; + finally + lRequest.Free; + end; + + Log.d('Determined tuner count: %d', [Result]); +end; + { TTunerList } constructor TTunerList.Create; @@ -1062,8 +1093,13 @@ procedure TCetonClient.AssignConfig(const aConfig: TCetonConfig; const aExcludeS fConfig.Assign(aConfig, aExcludeSections); fClient := TRESTClient.Create(Format('http://%s', [fConfig.TunerAddress])); - // TODO: Request tuner count from Ceton - fTunerList.Count := 6; + try + fTunerList.Count := TREST.GetTunerCount(fClient); + except + Log.d('Unable to reach tuner at %s', [fConfig.TunerAddress]); + fTunerList.Count := 0; + FreeAndNil(fClient); + end; end else fConfig.Assign(aConfig, aExcludeSections); @@ -1121,6 +1157,16 @@ procedure TCetonClient.NeedClient; raise ECetonError.Create('Tuner address has not been configured'); end; +function TCetonClient.GetTunerCount: Integer; +begin + Lock; + try + Result := fTunerList.Count; + finally + Unlock; + end; +end; + { TStartStopStreamRequest } procedure TStartStopStreamRequest.DoBeforeExecute; @@ -1433,6 +1479,7 @@ procedure TCetonConfig.AssignTo(Dest: TPersistent; lDest.fChannelMap.Assign(fChannelMap, TCetonConfigSection.Channels in aExcludeSections); lDest.fTunerAddress := fTunerAddress; lDest.fListenIP := fListenIP; + lDest.fTunerCount := fTunerCount; end else inherited AssignTo(Dest); diff --git a/hdhr/HDHR.pas b/hdhr/HDHR.pas index 581c133..024d3f4 100644 --- a/hdhr/HDHR.pas +++ b/hdhr/HDHR.pas @@ -10,6 +10,7 @@ interface System.Generics.Collections, System.JSON, REST.JsonReflect, + REST.Json.Types, Xml.XmlDoc, Xml.XmlIntf, SocketUtils, @@ -30,6 +31,7 @@ interface HDHOMERUN_TAG_TUNER_COUNT = $10; HDHOMERUN_DEVICE_TYPE_TUNER = 1; + HDHOMERUN_DEVICE_TYPE_WILDCARD = $FFFFFFFF; HDHOMERUN_DEVICE_ID_WILDCARD = $FFFFFFFF; @@ -154,36 +156,51 @@ TPacket = record TDiscoverResponse = class private - fManufacturor: String; - fLineupURL: String; - fTunerCount: Integer; - fBaseURL: String; - fDeviceAuth: String; - fFirmwareVersion: String; - fFirmwareName: String; + [JSONName('FriendlyName')] fFriendlyName: String; - fDeviceId: String; +// [JSONName('Manufacturor')] +// fManufacturor: String; + [JSONName('ModelNumber')] fModelNumber: String; + [JSONName('FirmwareName')] + fFirmwareName: String; + [JSONName('FirmwareVersion')] + fFirmwareVersion: String; + [JSONName('DeviceID')] + fDeviceID: String; + [JSONName('DeviceAuth')] + fDeviceAuth: String; + [JSONName('TunerCount')] + fTunerCount: Integer; + [JSONName('BaseURL')] + fBaseURL: String; + [JSONName('LineupURL')] + fLineupURL: String; public constructor Create; property FriendlyName: String read fFriendlyName write fFriendlyName; - property Manufacturer: String read fManufacturor write fManufacturor; +// property Manufacturer: String read fManufacturor write fManufacturor; property ModelNumber: String read fModelNumber write fModelNumber; property FirmwareName: String read fFirmwareName write fFirmwareName; - property TunerCount: Integer read fTunerCount write fTunerCount; property FirmwareVersion: String read fFirmwareVersion write fFirmwareVersion; property DeviceID: String read fDeviceId write fDeviceID; property DeviceAuth: String read fDeviceAuth write fDeviceAuth; + property TunerCount: Integer read fTunerCount write fTunerCount; property BaseURL: String read fBaseURL write fBaseURL; property LineupURL: String read fLineupURL write fLineupURL; end; + + TLineupItem = class private + [JSONName('GuideNumber')] + fGuideNumber: String; + [JSONName('GuideName')] fGuideName: String; + [JSONName('URL')] fURL: String; - fGuideNumber: String; public property GuideNumber: String read fGuideNumber write fGuideNumber; property GuideName: String read fGuideName write fGuideName; @@ -203,6 +220,16 @@ TLineup = class function ToXML: String; end; + THDHRUtils = class abstract + private + const + cDeviceIDLookupTable: array[0..15] of Byte = ($0A, $05, $0F, $06, $07, $0C, $01, $0B, $09, $02, $08, $0D, $04, $03, $0E, $00); + public + class function ValidateDeviceID(const aDeviceID: UInt32): Boolean; static; + class function CorrectDeviceID(const aDeviceID: UInt32): UInt32; static; + class function CreateDeviceID: UInt32; static; + end; + implementation { TDiscoverResponse } @@ -211,13 +238,13 @@ constructor TDiscoverResponse.Create; begin // {"FriendlyName":"HDHomeRun PRIME","ModelNumber":"HDHR3-CC","FirmwareName":"hdhomerun3_cablecard","FirmwareVersion":"20160630atest2","DeviceID":"FFFFFFFF","DeviceAuth":"FFFFFFFF","TunerCount":3,"ConditionalAccess":1,"BaseURL":"http://192.168.1.182:80","LineupURL":"http://192.168.1.182:80/lineup.json"} fFriendlyName := 'HDHomeRun PRIME'; - fManufacturor := 'Silicondust'; +// fManufacturor := 'Silicondust'; fModelNumber := 'HDHR3-CC'; fFirmwareName := 'hdhomerun3_cablecard'; - fTunerCount := 4; + fTunerCount := 3; fFirmwareVersion := '20160630atest2'; fDeviceID := 'FFFFFFFF'; - fDeviceAuth := 'FFFFFFFF'; + fDeviceAuth := 'abcdefg'; fBaseURL := 'http://192.168.1.116:80'; fLineupURL := 'http://192.168.1.116:80/lineup.json'; end; @@ -492,24 +519,36 @@ class function TPacket.FromDiscovery(const aRequest: Boolean; var lTags: TTagArray; begin - SetLength(lTags, 6); - lTags[0]._Type := HDHOMERUN_TAG_DEVICE_TYPE; - lTags[0].ValueAsUInt32 := aData.DeviceType; + if aRequest then + begin + SetLength(lTags, 2); + lTags[0]._Type := HDHOMERUN_TAG_DEVICE_TYPE; + lTags[0].ValueAsUInt32 := aData.DeviceType; + + lTags[1]._Type := HDHOMERUN_TAG_DEVICE_ID; + lTags[1].ValueAsUInt32 := aData.DeviceID; + end + else + begin + SetLength(lTags, 6); + lTags[0]._Type := HDHOMERUN_TAG_DEVICE_TYPE; + lTags[0].ValueAsUInt32 := aData.DeviceType; - lTags[1]._Type := HDHOMERUN_TAG_DEVICE_ID; - lTags[1].ValueAsUInt32 := aData.DeviceID; + lTags[1]._Type := HDHOMERUN_TAG_DEVICE_ID; + lTags[1].ValueAsUInt32 := aData.DeviceID; - lTags[2]._Type := HDHOMERUN_TAG_DEVICE_AUTH_STR; - lTags[2].ValueAsAnsiString := aData.DeviceAuthStr; + lTags[2]._Type := HDHOMERUN_TAG_DEVICE_AUTH_STR; + lTags[2].ValueAsAnsiString := aData.DeviceAuthStr; - lTags[3]._Type := HDHOMERUN_TAG_TUNER_COUNT; - lTags[3].ValueAsUInt8 := aData.TunerCount; + lTags[3]._Type := HDHOMERUN_TAG_TUNER_COUNT; + lTags[3].ValueAsUInt8 := aData.TunerCount; - lTags[4]._Type := HDHOMERUN_TAG_BASE_URL; - lTags[4].ValueAsAnsiString := aData.BaseURL; + lTags[4]._Type := HDHOMERUN_TAG_BASE_URL; + lTags[4].ValueAsAnsiString := aData.BaseURL; - lTags[5]._Type := HDHOMERUN_TAG_LINEUP_URL; - lTags[5].ValueAsAnsiString := aData.LineupURL; + lTags[5]._Type := HDHOMERUN_TAG_LINEUP_URL; + lTags[5].ValueAsAnsiString := aData.LineupURL; + end; Result := Default(TPacket); if aRequest then @@ -560,4 +599,45 @@ function TPacket.IsValid: Boolean; Result := fCRC = CalcCRC; end; +{ THDHRUtils } + +class function THDHRUtils.CorrectDeviceID(const aDeviceID: UInt32): UInt32; +begin + Result := 0; + Result := Result xor cDeviceIDLookupTable[(aDeviceID shr 28) and $0F]; + Result := Result xor ((aDeviceID shr 24) and $0F); + Result := Result xor cDeviceIDLookupTable[(aDeviceID shr 20) and $0F]; + Result := Result xor ((aDeviceID shr 16) and $0F); + Result := Result xor cDeviceIDLookupTable[(12 shr 20) and $0F]; + Result := Result xor ((aDeviceID shr 8) and $0F); + Result := Result xor cDeviceIDLookupTable[(aDeviceID shr 4) and $0F]; +// Result := Result xor ((aDeviceID shr 0) and $0F); + + Result := Result or (aDeviceID and $FFFFFFF0); +end; + +class function THDHRUtils.ValidateDeviceID(const aDeviceID: UInt32): Boolean; +var + lChecksum: UInt32; +begin + lChecksum := 0; + lChecksum := lChecksum xor cDeviceIDLookupTable[(aDeviceID shr 28) and $0F]; + lChecksum := lChecksum xor ((aDeviceID shr 24) and $0F); + lChecksum := lChecksum xor cDeviceIDLookupTable[(aDeviceID shr 20) and $0F]; + lChecksum := lChecksum xor ((aDeviceID shr 16) and $0F); + lChecksum := lChecksum xor cDeviceIDLookupTable[(12 shr 20) and $0F]; + lChecksum := lChecksum xor ((aDeviceID shr 8) and $0F); + lChecksum := lChecksum xor cDeviceIDLookupTable[(aDeviceID shr 4) and $0F]; + lChecksum := lChecksum xor ((aDeviceID shr 0) and $0F); + + Result := lChecksum = 0; +end; + +class function THDHRUtils.CreateDeviceID: UInt32; +begin + // $131 seems to be prefix for HDHomeRun PRIME + Result := UInt32(Random(Integer($FFFFF))+1) or $13100000; + Result := THDHRUtils.CorrectDeviceID(Result); +end; + end.