The result of my desire to integrate live TV into my own custom HTML dashboards, and the need for a disconnected webapp that can run on any browser. This is not complete, but it was was enough to accomplish what I needed.

Updates

11/28/14 - Added api:server/softwareUpdateDetails, api:server/startSoftwareUpdate and api:server/softwareUpdateProgress.  A new firmware section was added.

11/30/14 - Added Hardware Section. 

Tablo's Ports

Name Port Proto Desc
SlipPort 8887 TCP WebSocket (Non-Secure) - WAMP Messaging
HTTP 80 TCP Provides specific resources over http. Like Video Streaming, Playlists, Images
HTTP 18080 TCP Third Party APP Interface. The Plex Media Center Channel for Tablo uses this port.
HTTPS 443 TCP Unknown
Discovery Protocol 8881 UDP This is the port the Tablo listens for clients to discover it.

Tablo Discovery Protocol (aka ‎Bonjour discovery)

The client app does a local UDP subnet broadcast (for example, 192.168.1.255) on port 8881 with a payload of four ASCII characters, "BnGr". The client app must contain the payload or the Tablo will not respond. The tablo will respond to the broadcast on UDP port 8882 with the following payload (total size is 140 bytes):

Discovery Response Packet

0000   62 4e 67 52 74 61 62 6c 6f 2d 71 75 61 64 00 00  bNgRtablo-quad..
0010   00 00 00 00 00 00 00 00 14 0b 01 00 00 00 00 00  ................
0020   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0030   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0040   00 00 00 00 31 39 32 2e 31 36 38 2e 30 2e 31 30  ....192.168.4.10
0050   38 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  8...............
0060   00 00 00 00 53 49 44 5f 35 30 38 37 42 38 30 30  ....SID_XXXXXXXX
0070   33 36 44 30 00 00 00 00 74 61 62 6c 6f 00 00 00  XXXX....tablo...
0080   2a 00 71 75 61 64 00 54 00 00 00 00              *.quad.T....

Discovery Response Struct

struct tabloDiscoveryResp {  /* the tablo app will stop parsing a field once it hits a NULL (0x00).  */

char key[4]; /* Offset [0-3] - This is a reverse Capitalization of the BnGr that is sent from the requester */
char host[32]; /* offset [4-35] - Name of unit? /* 
char ip[32]; /* offset [68-99] - Ip address of unit */
char sid[20]; /* offset[100-119] - System ID */
char type[10]; /* offset [120-129] -  System Type?  */

}; 130 bytes total
		

Tablo Client Verifier (Client Auth Server)

Whenever the client makes the "api:session/connect" call the Tablo unit will try to contact the client via the TCP port specified by the "localPort" key value that is in the "api-session/connect" call. This port can be any value. Setup a TCP listener on any port that will respond to any connection with the String, “Nuvyyo”. This needs to initialized before issuing the "api-session/connect" call.

This applies to both the "Guest" and "Registered" connect mode. You will see it connect back to your client twice during normal connection setup. If this listener is not setup or the Tablo doesn't receive the proper response you get an "Authentication failure" in the form of a CALLERROR (4). Like below:

[4,"m1svrk3dida2l70z","http://api.slipstream.nuvyyo.com/error/session/connect/permissionDenied","Authentication failure\n"]

WebSocket Protocol

The primary control protocol for the Tablo is WebSockets which communicate over port 8887. The WebSocket connection is not secured over SSL and therefor all traffic is visible over the network. Mostly likely this is due to hardware processing limitations which is evident by their chosen client software architecture.

WebSocket Application Messaging Protocol (WAMP)

Tablo messaging API uses version 1.0 of the WebSocket Application Messaging Protocol (WAMP). All messages are transmitted as WebSocket messages of payload type text, and hence UTF-8 encoded, with the payload being valid JSON.

Connection Establishment

The entire sequence of events to establish a connection to the Tablo can be found in this diagram.  The next section breaks down the process into logical steps.

1) Setup the "Client Auth Server"

The main purpose of Init phase is to establish the WebSocket connection to the Tablo.  It also contains the first request the client will send to the Tablo for the purpose of shorting the URI's that required for each call message.  The interaction looks like this:

Init Conntection

When initiating the WebSocket you must include the HTTP header "Sec-WebSocket-Protocol: slipstream". Once the WebSocket connection is successfully established you will receive the WAMP WELCOME message from the Tablo unit.  The client does not respond to the WELCOME message.  The first WAMP message that the client send is the WAMP PREFIX message.  The server does not send any response to this message.

3) Register Client ("Guest Connect")

After the init phase is completed the connection from the tablo point view is in a non-authorized mode.   The next sequence of events registers your client so you can access the full API.

register phase

There are two modes of "api:session/connect".  The first and the one used in this sequence is called guest mode.  This is indicated by Key\value of "guest:true". Before the Tablo will respond successfully to your "api:session/connect" it will initiate the "Client Verifier" connection back to your client on the port specified by the Key\Value "localPort".  If that goes well then and only then will the Tablo respond to the "api:session/connect" with a success response.  In the response packet there's an important key\value pair is "serverID":SID_999999999999" .  This is the System Identifier that is used later in other calls and for client response needs.

Next request the client sends is the "api:clients/register".  This initiates the client's request to be registered.   Notice that you need to include the serverID within the request that you received from the "api:session/connect".  If everything goes properly the Tablo will respond to the request with the following key\vales.

clientID: This is a random identifier for recognizing the client for a non-guest mode connect.

clientKey:  I have not seen a use for this yet.

DeviceID:  This identifier is unique to your Tablo unit.  This is used in creating the key\value "signature" as part of the non-guest mode connect.

The last message that the client sends is a "api:session/disconnect".   This request closes the previous session established with the "api:session/connect".  This needs to be done in order to reestablish a session as a registered client.  Note that this request ends the session, it does not close the WebSocket.  

4) Connect as a Registered Client

After successfully registering and disconnecting the previous session you can now connect as a registered client using the same "api:session/connect" call as was used in the guest mode.  Notice in the sequence below that there are two additional Key/Value pairs that were not present in the guest mode connect.  They are "clientID" and "signature".   

register conn est

The "clientID" was received from the previous "api:clients/register" request.  This is a unique identifier that Tablo can verify as a registered client.  The next new Key/Value is "signature".  The client needs to generate this signature.  See "Generating the "signature" for the Registered Client Connect".  This is additional check verifying that the client has the "deviceKey" that was sent to client during the "api:clients/register" request. If successful the response will include the Key/Value "guest:false", which means your now a registered connected client and make any API calls.  

Syncing TV data to Client

While the Tablo has a web server it does not serve pages to clients.  Rather it offers web services.  E.g. WebSocket based API calls.   This forces the client to download all TV metadata from the Tablo so it can render the UI.   This is the cause behind the notorious "Syncing" complaints for the official Tablo apps.

There are two different methods for acquiring the metadata, a full and an incremental synchronization. Full synchronization is used when the client has never synced its data before or the client wants a fresh new copy of all the metadata. Incremental synchronization is used when the client only wants the metadata that is new, changed or deleted.  The Tablo keeps track of all changes to its internal database through the use of a sequence number.  Every time the Tablo database is updated it increments the sequence number.  Your client must track this sequence number to know where your last synchronization ended.

Both methods have the same header response to requests for synchronization as shown below:

[3, "H9WL2rUNSLmTxCyB", {"changes":{"sinceSequenceNumber":0,"untilSequenceNumber":1227,"objectCount":250, "OPERATIONAL_TYPE": {


The "sinceSequenceNumber" Key/Value specifies what sequence number to stop syncing at and the "untilSequenceNumber" specifies within the confines of the response where the next synchronization request should begin at.  The response also contains the Key/Value "objectCount" which holds a counter to the number objects contained within the response. When you setup a synchronization operation you specify the maximum number of metadata objects you want per sync operation. This entire process will become clearer in the following sections.

All the different types objects (The TV metadata) can be found in the Object Type Categories section.

Full Synchronization

As stated previously the full sync is used when your client has no metadata.  The first operation you perform is a "api:sync/sequenceNumber" request to get the current sequence number of the database in the Tablo.   The response you will receive will contain the key/value of "sequenceNumber" which the current sequence number of the Tablo database.  This will become the value that you will use for the "sinceSequenceNumber" in subsequent requests.

Below we request the current sequence number and get the response:

Request:


[2,"6p52xrazjnu7f9hb","api:sync/sequenceNumber"]

Response:

[3,"stv6w9yc3z40cksu",{"sequenceNumber":101348}]

We now know based on the response the Tablo's current sequence number is 101348.  Since we are doing a full synchronization we need to get all the metadata objects up that sequence number.  So the next step is to peform a "api:sync/startSeed" as shown below: (Note I set the "maxObjects" key/value to 1 to keep the example simple. The default is 250.

Request:

[2,"fuv0b4k9fhctpye1","api:sync/startSeed",{"maxObjects":1,"sinceSequenceNumber":0}]

Response:

[3,"fuv0b4k9fhctpye1",{"seedUntilSequenceNumber":101348,"totalObjectCount":12402}]


We set the "sinceSequenceNumber" to 0 because we are requesting a full sync.   You can also see in the response "seedUntilSequenceNumber" is the same as the current sequence number that was seen in the "api:sync/sequenceNumber" request.  That means for this seeding session we are going to sync from sequence number 0 till 101348, one object at a time(E.g. maxObjects:1" . Again you would normally set the maxObject to 250, but in this example we are request one object per request.  The response also tells use that we will be syncing a total of 12402 objects.

The next step is to start pulling the objects at the rate dictated by the "maxObject" value specified above by performing a "api:sync/seed".

Request:

[2,"x3w1vel292kg4ivz","api:sync/seed",{"untilSequenceNumber":101348,"sinceSequenceNumber":0}]


Response:

[3,"x3w1vel292kg4ivz",{"changes":{"sinceSequenceNumber":0,"untilSequenceNumber":3,"objectCount":1,"added":{"channel":[{"objectID":81,
"type":"channel","dataAvailable":true,"callSign":"CW18",
"affiliateCallSign":"CW","channelNumberMajor":18,
"channelNumberMinor":1,"audioChannels":6,"resolution":{"title":"1080i","width":1920,"height":1080}}]}}}]


The request contains both the "untilSequenceNumber" and the "sinceSequenceNumber".  After the request is sent the Tablo will respond and deliver a blob of metadata.  In this case it only contains one metdata object, "channels".  The format of the metadata objects are discussed later.  The important field in the response for this discussion is the "sinceSequenceNumber".  As you can see the value of this field is set to 3.  Which means the next "api:sync/seed" request we send will set the "sinceSequenceNumber" equal to the "untilSequenceNumber".  Since we are only pulling one object at time and our "sinceSequenceNumber" is still less then our "untilSequenceNumber" we need to perform another "api:sync/seed".

Request:

[2,"lqlsn7d81li45spv","api:sync/seed",{"untilSequenceNumber":101348,"sinceSequenceNumber":3}]


Response:

[3,"lqlsn7d81li45spv",{"changes":{"sinceSequenceNumber":3,"untilSequenceNumber":4,"objectCount":1,"added":{"channel":[{"objectID":82,"type":"channel","dataAvailable":true,
"callSign":"getTV","channelNumberMajor":18,"channelNumberMinor":2,
"audioChannels":2,"resolution":{"title":"480i","width":704,"height":480}}]}}}]


Note how we updated the "sinceSequenceNumber" in the request to reflect the next sync point to start from.  This process is repeated over and over until "sinceSequenceNumber" equals the "untilSequenceNumber".  Once that condition is meet there are no more objects left to sync.  The last step is the "close" the sync by issuing the following request.

Request:

[2,"jkhkj3454jhkj4h5","api:sync/finishedIncremental",{"untilSequenceNumber":101348}]

Response:

[3,"jkhkj3454jhkj4h5",null]

The reason you need to "close" the sync is if you ever need to sync again in the same session you will receive an a error that "seeding" has already been initiated.  Note: When doing a full sync you will only receive Synchronization Operational Types of "added" type.

Incremental Synchronization

Incremental Synchronization is very similar to the full sync except for the following conditions:


Steps:

  1.     Get the current Tablo sequence number via "api:sync/sequenceNumber".
  2.     Set the starting point of your sync via "api:sync/startIncremental".
  3.     Start retrieving the objects via "api:sync/incremental".  Keep doing this recursively unitl "sinceSequenceNumber" equals the "untilSequenceNumber".
  4.     Close the sync via "api:sync/finishedIncremental".

Playing Video

Both Live TV and Recorded TV streams are accessed via playlists and are HTTP streamed via port 80.

See api:player/watchLive and api:player/playRecording.

API Calls

The are all the WAMP calls made by the Tablo.

WAMP WELCOME Message

Description: After the WebSocket connection is established the first message the client will receive from the Tablo unit will be WELCOME message.

WAMP Format:

[ TYPE_ID_WELCOME , sessionId , protocolVersion, serverIdent ]

Tablo Sends:

[0, "cee1046c-35dc-4364-9848-7a49642450e1", 1, "Tablo/0.0.1"]

Response:

Client does not respond to this message.

WAMP PREFIX Message

Description: Whenever a URI is used, full identification of the procedure/topic is provided by this URI. However, URIs can get long, which means tedious to input for developers, and also resulting in considerable volume on wire, when many small messages are exchanged. To counter that, URIs MAY be abbreviated using the CURIE (Compact URI Expression) syntax. In other words this is a shortcut so you don't have to specify the full URI with each CALL message.  This message needs to be sent to the Tablo before any WAMP CALL messages are sent.  The prefix:"api" and URI:"http://api.slipstream.nuvyyo.com/" need to be set

WAMP Format:

[ TYPE_ID_PREFIX , prefix , URI ]

Request:

[1,"api","http://api.slipstream.nuvyyo.com/"]

Response:

Tablo doesn't respond. 

WAMP CALL Message's by URI

WAMP Format:

[ TYPE_ID_CALL , callID , procURI , ... ]


The TYPE_ID_CALL = 2

callID = This is randomly generated by the client for the purpose of routing the response to the appropriate requester.

URI = the API call.

api:session/connect

This API call is only successful if the Client Auth Server is running. The "signature" for a registered client connect is generated via the "Generating the "signature" for the Registered Client Connect" process. 

Guest Mode Request:

[2,"i1dkxzi1hdlpirmd","api:session/connect",{"localPort":38111,"localAddress":"10.90.22.33", "clientType":"Android-tablet","guest":true,"deviceName":"SAMSUNG-TAB-10","clientVersion":"1.1.2.0.0","clientBuild":49}]

Guest Mode Response:

[3,"i1dkxzi1hdlpirmd",{"compatibleClient":true,"forceCacheReload":false,"guest":true,"localConnection":true,"name":"Tablo",
"playbackPort":80,"serverID":"SID_AAAAAAAAAAAAA"}]

Registered Client Request:

[2,"thefx3xz8pzq18z9",
"api:session/connect",{"localAddress":"10.90.22.33","clientType":"Android-tablet","signature":"05f5e12d6b091b5bd29a1ab98634492b",
"clientVersion":"1.1.2.0.0","localPort":38111,
"deviceName":"SAMSUNG-TAB-10","clientBuild":49,"clientID":"20a446f1-e861-4c3d-bf1e-09efe5304e31"}]

Registered Client Response:

[2,"thefx3xz8pzq18z9","api:session/connect",
{"localAddress":"10.90.22.33","clientType":"Android-tablet",
"signature":"05f5e12d6b091b5bd29a1ab98634492b",
"clientVersion":"1.1.2.0.0",
"localPort":38111,"deviceName":"SAMSUNG-TAB-10",
"clientBuild":49,
"clientID":"20a446f1-e861-4c3d-bf1e-09efe5304e31"}]

api:clients/register

Request:

[2,"ha00wuo88yadtszs","api:clients/register",{"deviceName":"AAAAAAAAAAAAAAAA","deviceID":"AAAAAAAAAAAAAAAA"}]

Response:

[3,"ha00wuo88yadtszs",{"clientID":"20a446f1-e861-4c3d-bf1e-09efe5304e31","clientKey":"e69b2b9e-ce32-4cfb-986b-0c9f7b3ef97f","deviceKey":"aaaaaaaa-aaaa-aaaaaaaaa-aaaaaaaaaaaa"}]

api:session/disconnect


Request:

[2,"h6tpois9y1hxuche","api:session/disconnect"]

Response:

[3,"h6tpois9y1hxuche",null]

api:channels/committedScanResult

Returns a list of all the channels that are available. E.g. All the TV channels that were successfully tuned.

Request:

[2,"0.fvp3ugyj5mr96bt9","api:channels/committedScanResult"]

Response:

[3,"0.fvp3ugyj5mr96bt9",{"completed":true,"resultID":2,"postalCode":"77777", "countryCode":"USA","timeZone":"-02:00","tzName":"America/Newyork", CHANNEL_ CATAGORY_OBJECT

api:subscription/status

Returns the date of when your subscription will expire.

Request:

[2,"0.6ymaikx8dtl6jemi","api:subscription/status"]

Response:

[3,"0.6ymaikx8dtl6jemi",{"type":"subscriptionStatus","state":"trial","expires":"2015-01-02T06:22Z"}]

There are four values for the key, "state": "grandfather", "none", "trial" and "subscribed". 

api:server/status

Returns the status of the Tablo unit.

Request:

[2,"0.n2dz4bifx02dfgvi","api:server/status"]

Response:

[3,"0.n2dz4bifx02dfgvi",{"type":"serverStatus","name":"Tablo","serverID":"SID_999999999999", "localAddress":"10.90.7.33","serverBuild":1426515,
"serverVersion":"2.1.16","utcOffset":"-02:00", "updateAvailable":false,"lastGuideUpdate":"2015-11-20T08:11:11Z",
"tzName":"America/Newyork", "processing":{"downloadingGuide":false,importingRecordings":false,
"scanningChannels":false, "updating":false},"setup":{"channelsScanned":true,channelsCommitted":true,"guideSeeded":true}, "model":{"wifi":true,"tuners":4},"reachability":{"internet":true, "remoteAccess":null,"unavailable":[]},"hardDrive":{"connected":true,"name":"Maxtor C00 AAS GGG (500GB)","size":496132957184,"formatState":"authorized",
"usage":92485367392,"busyState":"ready"}}]

api:server/tuner

Tells you what the tuner is doing.  Recording, how live TV connections, etc.

Request:

[2,"w8Ip5aCeKii8RSv0","api:server/tuner"]

Response:

[3,"w8Ip5aCeKii8RSv0",{"type":"tunerStatus","tunerCount":4,"tunedChannels":[],"recordings":{"channels":[],"airings":[],"recordings":[]}}]

api:server/setName

Set the name of Tablo unit.

Request:

[2,"0.cfc9banm0qhyqfr","api:server/setName",{"name":"Tabloo"}]

Response:

[3,"0.n2dz4bifx02dfgvi",{"type":"serverStatus","name":"Tabloo","serverID":"SID_999999999999", "localAddress":"10.90.7.33","serverBuild":1426515,
"serverVersion":"2.1.16","utcOffset":"-02:00", "updateAvailable":false,"lastGuideUpdate":"2015-11-20T08:11:11Z",
"tzName":"America/Newyork", "processing":{"downloadingGuide":false,"importingRecordings":false,
"scanningChannels":false, "updating":false},"setup":{"channelsScanned":true,"channelsCommitted":true,"guideSeeded":true}, "model":{"wifi":true,"tuners":4},"reachability":{"internet":true,"remoteAccess":null, "unavailable":[]},"hardDrive":{"connected":true,"name":"Maxtor C00 AAS GGG (500 GB)","size":496132957184,"formatState":"authorized",
"usage":92485367392,"busyState":"ready"}}]

api:sync/sequenceNumber

Gets the current sequence number of the metadata in the Tablo. Every time the Tablo gets new data (TV metadata) this number is incremented. This is represented by the Key/Value of "sequenceNumber" in the response.

Request:

[2,"hogy24gpq29tje0d","api:sync/sequenceNumber"]

Response:

[3,"hogy24gpq29tje0d",{"sequenceNumber":94421}]

api:sync/startSeed

This call is used to sync the client database with the metadata stored on the Tablo. This call is normally only used the first time your syncing data with the Tablo or you want a complete fresh copy. For any syncing after the first, use the incremental sync. The "sinceSequenceNumber" indicates where to start syncing from till the "sequenceNumber" value returned in the "api:sync/sequenceNumber" is equal. Normally you only execute this when your database is empty thus "sinceSequenceNumber" is set to 0.  But if your connection was interrupted during a full sync and you knew where you left off then obviously you could set the "sinceSequenceNumber" from where you left off.

maxObjects = This is max number of records to pull per "api:sync/seed" request.

Request:

[2,"62IiTW3z9YJffXuN","api:sync/startSeed",{"maxObjects":250,"sinceSequenceNumber":0}]

Response:

[3,"grbt8a9xdex7y1l7",{"seedUntilSequenceNumber":94423,"totalObjectCount":1034}]

api:sync/seed


Initiates the transmission of meta data to the client.

Request:

[2,"CALLBACKID","api:sync/seed",{"untilSequenceNumber":94421,"sinceSequenceNumber":0}];

Response:  See Syncing data.

api:sync/finishedSeed


Request:

[2,"uHgzYA84XHwCX65Z","api:sync/finishedSeed",{"untilSequenceNumber":76364}]

Response: See Syncing data

api:sync/startIncremental

See Syncing data

api:sync/incremental


Request:

[2,"EwNlYZMB0cXcwYzD","api:sync/incremental",{"sinceSequenceNumber":76366}]

Response: See Syncing data

api:sync/finishedIncremental

Request:

[2,"SWaUyEZxlUTpSrFt","api:sync/finishedIncremental",{"untilSequenceNumber":76366}]

Response: See Syncing data

api:settings/settings

Request:

[2,"w8Ip5aCeKii8RSv0","api:settings/settings"]

Response:

[3,"w8Ip5aCeKii8RSv0",{"type":"settings","recordingQuality":"medium","led":true,
"selectedRecordingQuality":{"name":"HD 720p","value":"medium","recommended":true,"hourlyBytes":2322000000},
"remoteAccess":{"enabled":false,"config":"none",
"hideAutoBandwidth":true}, "extendLiveRecordings":true,"autoDelete":true,"excludeDuplicates":{"enabled":true,"strategy":"scheduleOne"}}]

api:settings/availableRecordingQualities

Request:

[2,"w8Ip5aCeKii8RSv0","api:settings/availableRecordingQualities"]

Response:

[3,"w8Ip5aCeKii8RSv0",{"availableRecordingQualities":[{"name":"HD 720p","value":"medium","recommended":true,"hourlyBytes":2322000000},{"name":"HD 720p — Roku Chromecast","value":"medlow","recommended":false,
"hourlyBytes":1422000000},{"name":"HD 1080p","value":"high","recommended":false,"hourlyBytes":4572000000},{"name":"Standard Definition","value":"low","recommended":false,"hourlyBytes":972000000}]}]

api:settings/setRecordingQuality


Request:

[2,"0.lns6f6d54xzxs9k9","api:settings/setRecordingQuality",{"recordingQuality":"high"}]

Response:

[3, "0.lns6f6d54xzxs9k9", {"settings":{"type":"settings","recordingQuality":"high","led":true,
"selectedRecordingQuality":{"name":"HD 1080p", "value":"high", "recommended":false, "hourlyBytes":4572000000},"remoteAccess":{"enabled":false,"config":"none","hideAutoBandwidth":true},
"extendLiveRecordings":true, "autoDelete":true,"excludeDuplicates":{"enabled":true,"strategy":"scheduleOne"}}}]

api:player/watchLive

Request:

[2,"0.813jg0ody1puv7vi","api:player/watchLive",{"channel":87,"remoteBandwidth":1000}]

Response:

[3, "0.813jg0ody1puv7vi", {"relativePlaylistURL":"stream/pl.m3u8?cFA-KD7ALFwohcjp-mauOQ"}]

api:player/playRecording


Request:

[2,"0.995sxb1a5x2vgqfr","api:player/playRecording",{"recordingType":"recEpisode","objectID":19649,"remoteBandwidth":1000}]

Response:

[3,"0.995sxb1a5x2vgqfr",{"relativePlaylistURL":"stream/pl.m3u8?NqRSmXR7rf0C9ptko0AFTA",
"transcoding":false}

api:settings/setLED

Request:

[2, "w8Ip5aCeKii8RSv0", "api:settings/setLED", {"enabled":true}]]

Response:

[3,"w8Ip5aCeKii8RSv0",{"settings":{"type":"settings","recordingQuality":"medium",
"led":true,"selectedRecordingQuality":{"name":"HD 720p","value":"medium","recommended":true,"hourlyBytes":2322000000},
"remoteAccess":{"enabled":false,"config":"none",
"hideAutoBandwidth":true},
"extendLiveRecordings":true,"autoDelete":true,"excludeDuplicates":{"enabled":true,"strategy":"scheduleOne"}}}]

api:server/name


Request:

[2,"OXl7SkbbIAmMGic9","api:server/name"]

Response:

[3,"OXl7SkbbIAmMGic9",{"name":"Tablo"}]

api:channels/updateGuide

Manually update the guide information.

Request:

[2,"0.c2itbyixb7w4s4i","api:channels/updateGuide"]


Response:

[3, "0.c2itbyixb7w4s4i", {"serverStatus":{"type":"serverStatus", "name":"Tablo", "serverID":"SID_AAAAAAAAAAAA", "localAddress":"10.90.33.14","serverBuild":1426515,
"serverVersion":"2.1.16", "utcOffset":"-02:00",
"updateAvailable": false, "lastGuideUpdate": "2014-10-22T02:14:10Z","tzName":"America/Newyork","processing":{"downloadingGuide":true, "importingRecordings":false, "scanningChannels":false, "updating":false},"setup":{"channelsScanned":true,"channelsCommitted":true,
"guideSeeded":true},"model":{"wifi":true,"tuners":4 },"reachability":{"internet":true,"remoteAccess":null,"unavailable":[]},"hardDrive":{"connected":true,"name":"Maxtor JJJS KJSS (500 GB)", "size":442132957184,"formatState":"authorized","usage":5345873984,
"busyState":"ready"}},"guideDownloadProgress":{"progress":1.000000}}]

api:schedules/scheduleGuideEpisode

Schedule a single episode. Sends back the modified objects.

Request:

[2,"0.vmaxw1y4yisy8pvi","api:schedules/scheduleGuideEpisode",{"guideEpisode":48264,"schedule":true}]

Response:

[3, "0.vmaxw1y4yisy8pvi", {"changes":{"sinceSequenceNumber":110872,"untilSequenceNumber":110874,
"objectCount":2,"modified" : {"guideEpisode" : [{"airDate":"2014-11-08T03:00Z","description":"Frank is taken by surprise during an on-air interview; Danny and his new boss clash as they investigate a drive-by shooing.","duration":3600.0,"episodeNumber":7,
"originalAirDate":"2014-11-07","seasonNumber":5,"title":"Shoot the Messenger","type":"guideEpisode","qualifiers":["new","cc"],"relationships":{"guideSeason":7406,"channel":108},"schedule":{"scheduleType":"episode","state":"scheduled"},"objectID":48264} ], "guideSeries" : [{"description":"Tom Selleck stars as Frank Reagan, the chief of police in New York and patriarch of the Reagan clan, a multigenerational family of cops. Frank's oldest son is Danny, a seasoned detective and Iraqi War veteran who occasionally uses dubious tactics to solve cases. Daughter Erin, the lone female, is an assistant district attorney. Fresh out of Harvard Law, Jamie is the youngest member and \"golden boy\" of the family. Jamie gave up a lucrative future in law to continue the family's tradition in police work, and is asked to participate in a secretive investigation that even his father does not know about.","duration":3600.0,"originalAirDate":"2010-09-24","title":"Blue Bloods","cast":["Tom Selleck","Donnie Wahlberg","Bridget Moynahan","Will Estes","Len Cariou","Marisa Ramirez","Amy Carlson","Vanessa Ray"],"relationships":{"genres":[346]},"schedule":{"state":"none"},"objectID":3146,"type":"guideSeries","images":[{"type":"image","imageID":3147,"imageType":"series_3x4_small",
"imageStyle":"thumbnail"},{"type":"image","imageID":3148,"imageType":"series_4x3_large",
"imageStyle":"cover"},{"type":"image","imageID":3149,"imageType":"iconic_4x3_large",
"imageStyle":"background"}]} ]}}}]

To unschedule:

Request:

[2,"0.4o935x0wzh0k9","api:schedules/scheduleGuideEpisode",{"guideEpisode":48264,"schedule":false}]

api:schedules/scheduleGuideSeries

Schedule the recording of all "new" episodes in a series.

Request:

[2,"0.705ozqfqpzld6lxr","api:schedules/scheduleGuideSeries",{"guideSeries":3146,"schedule":true,"scheduleType":"new"}]


Response:

[3, "0.705ozqfqpzld6lxr", {"changes":{"sinceSequenceNumber":110879,"untilSequenceNumber":110912,
"objectCount":31,"modified" : {"guideEpisode" : [{"airDate":"2014-11-06T19:00Z","description":"When a family friend is murdered, the Reagans band together to take down the leader of the gang responsible; a second crisis sends Frank reeling.","duration":3600.0,"episodeNumber":23,
"originalAirDate":"2013-05-10","seasonNumber":3,"title":"This Way Out","type":"guideEpisode","qualifiers":["cc"],"relationships":{"guideSeason":31114,"channel":97},"schedule":{"scheduleType":"series",
"skipReason":"episodeNotNew","state":"skipped"},"objectID":45607}, {"airDate":"2014-11-06T20:00Z",
"description":"Famous movie star Russell Berke (Marc Blucas), who researched a role by shadowing Danny, becomes the victim of a stabbing.","duration":3600.0,"episodeNumber":2,
"originalAirDate":"2013-10-04","seasonNumber":4,"title":"The City That Never Sleeps","type":"guideEpisode","qualifiers":["cc"],"relationships":{"guideSeason":45608,"channel":97},"schedule":{"scheduleType":"series","skipReason":"episodeNotNew",
"state":"skipped"},"objectID":45609}, {"airDate":"2014-11-06T21:00Z",
"description":"Danny serves as lead hostage negotiator when Erin is held at gunpoint inside the courtroom; when Jamie is suspended for disobeying orders, Frank must determine a proper punishment.","duration":3600.0,"episodeNumber":3,
"originalAirDate":"2013-10-11","seasonNumber":4,"title":"To Protect and Serve","type":"guideEpisode","qualifiers":["cc"],"relationships":{"guideSeason":45608,"channel":97},"schedule":{"scheduleType":"series","skipReason":"episodeNotNew",
"state":"skipped"},"objectID":45610}.......

To unschedule:

Request:

[2,"0.705ozqfqpzld6lxr","api:schedules/scheduleGuideSeries", {"guideSeries":3146,"schedule":false,"scheduleType":"new"}]

Record "all" episodes:

Request:

[2,"0.ak5erztwoaltmx6r","api:schedules/scheduleGuideSeries",{"guideSeries":3146,"schedule":true,"scheduleType":"all"}]

To unschedule:

Request:

[2,"0.ak5erztwoaltmx6r","api:schedules/scheduleGuideSeries",{"guideSeries":3146,"schedule":false,"scheduleType":"all"}]

api:schedules/setManualProgram


Set a manual one time recording.

Request:

[2,"0.hp73f5uls8wr8uxr","api:schedules/setManualProgram",{"title":"Test","kind":"once","channel":89,"duration":1800,"once":{"year":2014,"month":11,"day":6,"hour":20,"minute":0}}]

Response:

[3, "0.hp73f5uls8wr8uxr", {"manualProgram": 58642,"changes":{"sinceSequenceNumber":111078,"untilSequenceNumber":111084,
"objectCount":2,"added" : {"manualProgram" : [{"title":"Test","type":"manualProgram","objectID":58642,"schedule":{"state":"scheduled"},"config":{"kind":"once","channel":89,"duration":1800.0,"once":{"year":2014,"month":11,"day":6,"hour":20,"minute":0,
"tzName":"America/Chicago"}}} ], "manualProgramAiring" : [{"airDate":"2014-11-07T02:00Z","duration":1800.0,"schedule":{"scheduleType":"program","state":"scheduled"},"relationships":{"manualProgram":58642,"channel":89},"type":"manualProgramAiring",
"objectID":58643} ]} }}]

Modify an existing manual recording (add manualProgram key/value with the object id of the existing recording):

Request:

[2,"0.88wn0yy96tsdobt9","api:schedules/setManualProgram",{"title":"test","kind":"once","channel":88,"duration":1800,"once":{"year":2014,"month":11,"day":12,"hour":20,"minute":0},
"manualProgram":58647}]

Schedule a reoccurring manual recording.

Request:

[2,"0.sguo281lwj1exw29","api:schedules/setManualProgram",{"title":"Repeating","kind":"recurring","channel":89,"duration":1800,
"recurring":{"repeat":["monday","wednesday","friday"],"hour":20,"minute":30}}]

Response:

[3, "0.sguo281lwj1exw29", {"manualProgram": 58655,"changes":{"sinceSequenceNumber":111137,"untilSequenceNumber":111157,
"objectCount":7,"added" : {"manualProgram" : [{"title":"Repeating","type":"manualProgram","objectID":58655,
"schedule":{"state":"scheduled"},"config":{"kind":"recurring","channel":89,"duration":1800.0,
"recurring":{"repeat":["monday","wednesday","friday"],"hour":20,"minute":30,
"tzName":"America/Chicago"}}} ], "manualProgramAiring" : [{"airDate":"2014-11-08T02:30Z","duration":1800.0,"schedule":{"scheduleType":"program","state":"scheduled"},"relationships":{"manualProgram":58655,"channel":89},"type":"manualProgramAiring",
"objectID":58656}, {"airDate":"2014-11-11T02:30Z","duration":1800.0,"schedule":{"scheduleType":"program","state":"scheduled"},"relationships":{"manualProgram":58655,"channel":89},"type":"manualProgramAiring",
"objectID":58659}, {"airDate":"2014-11-13T02:30Z","duration":1800.0,"schedule":{"scheduleType":"program","state":"scheduled"},"relationships":{"manualProgram":58655,"channel":89},"type":"manualProgramAiring",
"objectID":58661}, {"airDate":"2014-11-15T02:30Z","duration":1800.0,"schedule":{"scheduleType":"program","state":"scheduled"},"relationships":{"manualProgram":58655,"channel":89},"type":"manualProgramAiring",
"objectID":58663}, {"airDate":"2014-11-18T02:30Z","duration":1800.0,"schedule":{"scheduleType":"program","state":"scheduled"},"relationships":{"manualProgram":58655,"channel":89},"type":"manualProgramAiring",
"objectID":58665}, {"airDate":"2014-11-20T02:30Z","duration":1800.0,"schedule":{"scheduleType":"program","state":"scheduled"},"relationships":{"manualProgram":58655,"channel":89},"type":"manualProgramAiring",
"objectID":58667} ]} }}]

Delete reoccurring.

See deleteManualProgram....

api:schedules/deleteManualProgram


Request:

[2,"0.q21t7h1gv4h1tt9","api:schedules/deleteManualProgram",{"manualProgram":58642}]

Response:

[3, "0.q21t7h1gv4h1tt9", {"changes":{"sinceSequenceNumber":111085,"untilSequenceNumber":111091,
"objectCount":2,"deleted" : {"manualProgram" : [58642 ], "manualProgramAiring" : [58643 ]} }}]

api:server/softwareUpdateDetails

Retrieves information on the latest available firmware.   This call is executed when the api:server/status response key/value pair of "updateAvailable" equals true.

Request:

[2,"0.q21t7h1gv4h1tt9","api:server/softwareUpdateDetails"]

Response:

[3, "0.f6qzyqdwboxswcdi", {"version":"2.1.18", "build":18, "ignore":{"date":"2014-11-26 20:40:32+00:00","file":"","size":26224853,"url":"https://api.tablotv.com/sf/builds/quad/2.1.18_141126.11_quad.flash"},"releaseNotes":"Release Notes for 2.1.18:\n\n NEW  Resume recording feature\n After a power interruption, Tablo will resume any recordings still in progress\n Two recordings will be created for the interrupted airing  one from before and one from after the interruption event\n\n Improvements to handling multiple broadcasters in one area using the same channel numbers\n\n Significant speed improvements to sync processes between Tablo and connected devices\n\n Improvements to database cleanup processes for users with large channel lineups\n\n Speed improvements to Live TV tuning\n\n Speed improvements to Roku accessing recordings list\n\n Overall stability improvements"}]

api:server/startSoftwareUpdate

Starts the firmware upgrade process.

Request:

[2,"0.vzvgpzjtf586ko6r","api:server/startSoftwareUpdate"]

Response:

[3, "0.vzvgpzjtf586ko6r", {"serverStatus":{"type":"serverStatus", "name":"Tablo", "serverID":"SID_XXXXXXXXXXXXXXX", "localAddress":"10.90.33.44","serverBuild":1426515,"serverVersion":"2.1.16", "utcOffset":"-03:00", "updateAvailable": true, "lastGuideUpdate": "2014-11-21T13:03:10Z","tzName":"America/NewYork","processing":{"downloadingGuide":false, "importingRecordings":false, "scanningChannels":false, "updating":true},"setup":{"channelsScanned":true,"channelsCommitted":true,"guideSeeded":true},"model":{"wifi":true,"tuners":4 },"reachability":{"internet":true,"remoteAccess":null,"unavailable":[]},"hardDrive":{"connected":true,"name":"Maxtor JDJDJD (500 GB)", "size":234234234234,"formatState":"authorized","usage":34865757575,
"busyState":"ready"}},"updateProgress":{"step":"downloading", "download":{"progress":0.00},"error":null}}]

api:server/softwareUpdateProgress

Progress of update.

Request:

[2,"0.nmda8vdexp4d9529","api:server/softwareUpdateProgress"]

Response:

[3, "0.nmda8vdexp4d9529", {"step":"downloading", "download":{"progress":0.00},"error":null}]

WAMP SUBSCRIBE Messages

WAMP Format:

[ TYPE_ID_SUBSCRIBE , topicURI ]

Name Topic URI Response
server/status http://api.slipstream.nuvyyo.com/server/status/notifications [8, "http://api.slipstream.nuvyyo.com/server/status/notifications",
 {"type":"serverStatus",
"name":"Tablo",
"serverID":"SID_AAAAAAAAAAAA",
"localAddress":"10.90.33.12",
"serverBuild":1426515,"serverVersion":"2.1.16",
 "utcOffset":"-02:00", "updateAvailable": false,
"lastGuideUpdate": "2014-10-14T02:14:10Z",
"tzName":"America/Newyork",
"processing":{"downloadingGuide":false,
"importingRecordings":false,
 "scanningChannels":false, "updating":false},
"setup":{"channelsScanned":true,"channelsCommitted":true,"guideSeeded":true},
"model":{"wifi":true,"tuners":4 },
"reachability":{"internet":true,"remoteAccess":null,
"unavailable":[]},
"hardDrive":{"connected":true,"name":"MAXTOR JJJD DKKD 500 GB)", "size":434742992,"formatState":"authorized",
"usage":45344534544,"busyState":"ready"}}]
server/tuner http://api.slipstream.nuvyyo.com/server/tuner/notifications
subscription/status http://api.slipstream.nuvyyo.com/subscription/status/notifications
sync/sequenceNumber http://api.slipstream.nuvyyo.com/sync/sequenceNumber/notifications [8,"http://api.slipstream.nuvyyo.com/sync/sequenceNumber/notifications",
{"sequenceNumber":100985}]
channels/scanProgress api:channels/scanProgress/notifications
channels/latestScanResult "api:channels/latestScanResult/notifications
channels/committedScanResult api:channels/committedScanResult/notifications"
channels/guideDownloadProgress api:channels/guideDownloadProgress/notifications [8, "http://api.slipstream.nuvyyo.com/channels/guideDownloadProgress/notifications",
{"progress":0.001895}]
clients/clientList api:clients/clientList/notifications
server/hardDriveFormattingProgress api:server/hardDriveFormattingProgress/notifications
api:settings/settings api:settings/settings/notifications [8, "http://api.slipstream.nuvyyo.com/settings/settings/notifications",
{"type":"settings","recordingQuality":"high",
"led":true,
"selectedRecordingQuality":{"name":"HD 1080p",
"value":"high", "recommended":false, "hourlyBytes":4572000000},"remoteAccess":{"enabled":false,"config":"none","hideAutoBandwidth":true},
"extendLiveRecordings":true,"autoDelete":true,"excludeDuplicates":{"enabled":true,"strategy":"scheduleOne"}}]
api:server/softwareUpdateProgress api:server/softwareUpdateProgress/notifications [8, "http://api.slipstream.nuvyyo.com/server/softwareUpdateProgress/notifications", {"step":"installing", "download":{"progress":1.00},"error":null}]
 
[8, "http://api.slipstream.nuvyyo.com/server/softwareUpdateProgress/notifications", {"step":"installing", "download":{"progress":1.00},"error":null}]
 
[8, "http://api.slipstream.nuvyyo.com/server/softwareUpdateProgress/notifications", {"step":"rebooting", "download":{"progress":1.00},"error":null}]

Generating the "signature" for the Registered Client Connect

In the non-guest or registered connect for the  "api:session/connect" the “signature” key/value is based on generating an MD5 HMAC (128bit) digest using the “deviceKey” Key/value that the Tablo sent when registering your client.

The digest is calculated based on the following input at the client: {serverID}+”/”+{DATE}

The date is in the ISO 8601 format.  Example Input: “SID_999999999999/2014-10-09T08:27Z”.  The client then uses the “deviceKey” to generate the MD5 HMAC digest from the input.  Note that currently the Tablo unit doesn’t verify the signature.  E.g. you can put whatever you want in the “signature” key/value and the Tablo will accept your request to connect.

Pseudo code:

HMAC Signature Generation:
String Input = SID+'/'+DATE
DATE = yyyy-MM-dd'T'HH:mm'Z', E.g. "2014-10-24T15:12Z"
String privateKey = deviceKey;  "See connect as registered client "
String algorithm = HmacMD5
Returns = HEX encoded MD5 hash  of Input

HTTP URL Requests

These are available via port 80 using HTTP.

Function URL
Retrieve images by "imageID". The id is contained within the metadata for numerous objects. http://{Tablo IP}/stream/thumb?id={imageID}
Retrieve Playlist http://{Tablo_IP}/stream/pl.{playlist_id}

Synchronization Operational Types

These are the types that are included in the responses to synchronization requests.

[3, "H9WL2rUNSLmTxCyB", {"changes":{"sinceSequenceNumber":0,"untilSequenceNumber":1227,"objectCount":250, "OPERATIONAL_TYPE" : {

Name Desc
added Object needs to be added
deleted Object needs to be removed
modified Object needs to me replaced.  Note you get a full copy of the entire object, and not just what changed.

Object Type Categories (MetaTypes of TV guide data)

These are the objects broken down by type that are retrieved when you sync with the Tablo.

[3, "H9WL2rUNSLmTxCyB", {"changes":{"sinceSequenceNumber":0,"untilSequenceNumber":1227,"objectCount":250,
"added" : {OBJECT_TYPE_CATEGORY}

Name JSON
channel {"channel":
{"objectID":81,
"type":"channel",
"dataAvailable":true,
"callSign":"CW18",
"affiliateCallSign":"CW",
"channelNumberMajor":18,
"channelNumberMinor":1,
"audioChannels":6,
"resolution":{"title":"1080i","width":1920,"height":1080}}
genre {"genre":
{"type":"genre","objectID":116,"title":"News"}
guideSeason {"guideSeason:
{"seasonNumber":0,
"relationships":{"guideSeries":117},
"objectID":121,
"type":"guideSeason"}
guideSeries {"guideSeries":
{"duration":1800,
"originalAirDate":"1999-08-30",
"title":"Fox 6 News at 5",
"relationships":{"genres":[116]},
"schedule":{"state":"none"},
"objectID":117,
"type":"guideSeries",
"images":[{"type":"image","imageID":118,"imageType":"series_3x4_small","imageStyle":"thumbnail"},{"type":"image","imageID":119,"imageType":"series_4x3_large","imageStyle":"cover"},{"type":"image","imageID":120,"imageType":"series_4x3_large","imageStyle":"background"}]}
guideSportOrganization {"guideSportOrganization":
{"title":"College Football",
"type":"guideSportOrganization",
"objectID":803,
"schedule":{"state":"none"},
"relationships":{"genres":[524]},
"images":[{"type":"image","imageID":804,"imageType":"sport_4x3_small","imageStyle":"thumbnail"},{"type":"image","imageID":805,"imageType":"sport_4x3_large","imageStyle":"cover"},{"type":"image","imageID":806,"imageType":"sport_4x3_large","imageStyle":"background"}]}
guideMovie {"guideMovie":
{"mpaaRating":"pg13",
"plot":"Mammoth hunter D'Leh (Steven Strait) has long been in love with a beautiful, blue-eyed tribeswoman named Evolet (Camilla Belle). After horseback-riding raiders kidnap most of his D'Leh's fellow tribesmen as well as Evolet, he sets out on a dangerous trek to rescue her from her captors.",
"releaseYear":2008,
"runtime":6540,
"title":"10,000 B.C.",
"cast":["Steven Strait","Camilla Belle","Cliff Curtis","Joel Virgel","Ben Badra","Mo Zainal","Nathanael Baring","Mona Hammond","Marco Khan","Reece Ritchie","Joel Fry","Omar Sharif","Kristian Beazley","Junior Oliphant","Louise Tu'u"],
"directors":["Roland Emmerich"],
"qualityRating":0.625,
"relationships":{"genres":[152,143,144]},
"type":"guideMovie",
"objectID":3634,
"images":[{"type":"image","imageID":3635,"imageType":"movie_2x3_small","imageStyle":"thumbnail"},{"type":"image","imageID":3636,"imageType":"iconic_4x3_large","imageStyle":"cover"},{"type":"image","imageID":3637,"imageType":"iconic_4x3_large","imageStyle":"background"}]}
recChannel {"recChannel":
{"objectID":19241,
"type":"recChannel",
"callSign":"WISN-TV",
"affiliateCallSign":"ABC",
"channelNumberMajor":12,
"channelNumberMinor":1,
"audioChannels":6,
"resolution":{"title":"1080i","width":1920,"height":1080}}
recEpisode {"recEpisode":
{"airDate":"2014-10-07T01:00Z",
"description":"Mr. Drummond hires a tutor for Willis and Arnold who does everything but teach.",
"episodeNumber":15,
"originalAirDate":"1979-02-09",
"scheduleDuration":1800,
"seasonNumber":1,
"title":"The Tutor",
"type":"recEpisode",
"relationships":{"recSeason":26345,
"recChannel":26346},"video":{"state":"finished",
"size":451203072,
"width":720,
"height":480,
"duration":2119,
"scheduleOffsetStart":-15,
"scheduleOffsetEnd":304},
"user":{"type":"recordingUserInfo","watched":false,"protected":false,"position":299},
"objectID":26343,
"images":[{"type":"image","imageID":26408,"imageType":"snapshot","imageStyle":"snapshot"}]}
recSeason {"recSeason":
{"seasonNumber":1,
"relationships":{"recSeries":26344},
"objectID":26345,
"type":"recSeason"}
recSeries {"recSeries":
{"description":"Two black kids from Harlem, Arnold Jackson and older brother Willis, are welcomed into the family of wealthy New York businessman Philip Drummond when their mother, his housekeeper, passes away. The two brothers become part of the Drummond family and learn various lessons about life.",
"duration":1800,
"originalAirDate":"1978-11-03",
"title":"Diff'rent Strokes",
"cast":["Conrad Bain","Gary Coleman","Todd Bridges","Dana Plato","Charlotte Rae","Mary Jo Catlett","Danny Cooksey","Mary Ann Mobley"],
"relationships":{"genres":[171]},
"objectID":26344,
"type":"recSeries",
"images":[{"type":"image","imageID":7841,"imageType":"series_3x4_small","imageStyle":"thumbnail"},{"type":"image","imageID":7842,"imageType":"series_4x3_large","imageStyle":"cover"},{"type":"image","imageID":7843,"imageType":"iconic_4x3_large","imageStyle":"background"}]}
guideEpisode {"guideEpisode":
{"airDate":"2014-11-01T00:00Z",
"description":"The pioneers continue working to build their own civilization.",
"duration":3600,
"episodeNumber":13,
"originalAirDate":"2014-10-31",
"seasonNumber":1,
"title":"Weeks Seven and Eight in Utopia",
"type":"guideEpisode",
"qualifiers":["new","cc"],
"relationships":{"guideSeason":39476,"channel":90},
"schedule":{"state":"none"},
"objectID":39477}
guideMovieAiring {"guideMovieAiring":
{"type":"guideMovieAiring",
"objectID":38648,
"airDate":"2014-10-31T22:00Z",
"duration":10800,
"schedule":{"state":"none"},
"relationships":{"guideMovie":3829,"channel":97}}
guideSportEvent {"guideSportEvent":
{"eventTitle":"ACC Game of the Week: Teams TBA",
"airDate":"2014-11-01T16:30Z",
"duration":10800,
"qualifiers":["live"],
"schedule":{"state":"none"},
"relationships":{"guideSportOrganization":803,"channel":85},
"type":"guideSportEvent",
"objectID":39587}
recMovie {recMovie:
{"plot":"Thomas spots giant footprints in some clay, and Percy thinks a monster is loose on the island.",
"releaseYear":2014,
"runtime":3780,
"title":"Thomas & Friends: Tale of the Brave",
"cast":["Mark Moraghan","Olivia Colman","Clive Mantle","Mike Grady","Tim Whitnall","Jonathan Broadbent"],
"directors":["Rob Silvestri"],
"relationships":{"genres":[152,305]},
"type":"recMovie","objectID":55579,"images":[{"type":"image","imageID":42194,"imageType":"movie_2x3_small","imageStyle":"thumbnail"},{"type":"image","imageID":42195,"imageType":"movie_2x3_large","imageStyle":"cover"},{"type":"image","imageID":42196,"imageType":"movie_2x3_large","imageStyle":"background"}
recMovieAiring {recMovingAiring:
{"type":"recMovieAiring",
"objectID":55578,
"airDate":"2014-11-03T18:00Z",
"scheduleDuration":3600,
"relationships":{"recMovie":55579,"recChannel":21878},
"video":{"state":"finished",
"size":928292864,
"width":704,
"height":480,
"duration":3920,
"scheduleOffsetStart":-15,
"scheduleOffsetEnd":305},
"user":{"type":"recordingUserInfo",
"watched":false,"protected":false,"position":0},
"qualifiers":["premiere"],"images":[{"type":"image","imageID":55590,"imageType":"snapshot","imageStyle":"snapshot"}
manualProgram { manualProgram:
{"title":"Test",
"type":"manualProgram",
"objectID":58670,
"schedule":{"state":"scheduled"},
"config":{"kind":"once",
"channel":87,
"duration":1800,
"once":{"year":2014,"month":11,"day":13,"hour":21,"minute":0,"tzName":"America/Chicago"}}}
manualProgramAiring { manualProgramAiring:
{"airDate":"2014-11-14T03:00Z",
"duration":1800,
"schedule":{"scheduleType":"program","state":"scheduled"},
"relationships":{"manualProgram":58670,"channel":87},
"type":"manualProgramAiring",
"objectID":58671}

Object Relationship Category Correlations

These diagrams are depict the relationships between objects:

Firmware

The firmware is encrypted with AESCrypt. While the client initiates the software update the Tablo unit actually downloads and installs the firmware independently.

Hardware

There are currently two different Tablo units.  One is a dual turner and the other is a quad model.

Model Description SOC
SPVR2-01-NA Tablo Dual Tuner
SPVR4-01-NA Tablo Quad Tuner ViXS Xcode 5190

Quad Unit (SPV4-01-NA)

The heart of the the quad unit is a ViXS Xcode 5190 SOC and is supported with 512M of ram.  There is also 8GB of NVS.

Key Components

Quantity Part # Desc
1 213XC5BEC12 ViXs XCODE 5190
1 K9K8G08U0D-SIB0 Samsung 4GB NAND (Datasheet)
4 NT5CB128M16FP-DI Nanya 128MB DRAM Chip
1 AR5B22 Atheros WiFi module

PCB Pictures

SPV4-01-NA-TOP-FULL SPV4-01-NA-TOP-ANGLE-FULL
SPV4-01-NA-BOTTON-FULL SPV4-01-NA-BOTTON-FULL2
SPV4-01-NA-SPI-INTERFACE