Finally I got it. The cover of the Xbox games currently playing on the Dashboard

Well, after a lot of sweat, frustration, work and learning a lot along the way, I finally have the cover of the Xbox game I am currently playing on my Dashboard. It has been complicated for me since I have been using Home Assistant for a few months but I finally have a first working version. I still want to improve it but I feel satisfied with the work done.

All it’s done with a few sensors, some api calls, an automation and a custom button card. Maybe I will do a step by step in the future with the final version.



8 Likes

This is great! I’ve been struggling with getting the current game and mapping it to a cover, any chance you could share the details on what your card is doing?

Hi, is a little hard to explain mostly because are a lot of step but basically I used the current Xbox live integration sensors (one is disable by default). One of them show that if you are in a game and the other the name of the game. I take the name and search with the API of http://mobygames.com/. With the api I get the id of the game and them with the id and the same api I get the cover and console. All of this with a bunch of sensors and an automation. The card are two custom button cards. I’m trying to improve it every day

2 Likes

This looks really good, any plans on releasing the code for us to replicate?

Great job! :smiley:

The Xbox was the only one of the current three major consoles without a way to get the cover of the current game into HA. I’m trying to replicate what you did, and I managed to get it working, but I’ve found that some of the names reported by the Xbox integration don’t match directly with those in the MobyGames API.

For example, “Burnout™ Paradise Remastered” as reported by the Xbox integration, isn’t found through the MobyGames API. However, if I search for “Burnout: Paradise - Remastered” I can find the game. A similar issue occurred with “Red Dead Redemption 2” which is listed as “Red Dead Redemption II” on MobyGames.

So far, I’ve been getting around this by adding text replacements as I encounter these odd cases. I was wondering if you faced the same issue and if you approached it differently.

Hi, yes I found same problems. I use text replacement for some cases too, removing special characters but not always works. For the cases no cover is found I show the Xbox logo. No much we can do, the api is limited but is better than nothing :smiling_face:

Yeah, I couldn’t find anything that worked 100% of the time. So, I took a different approach and ditched the MobyGames API altogether. Instead, I created a Python script to scrape www.xbox.com to get all US releases and their cover art URLs, and then dumped that into a CSV file. Since it’s the same source as the integration, I figure it will have a higher match rate than the MobyGames API, and the images are high quality too.

I’m sure it can be done on demand, but the site is mostly JavaScript, and I couldn’t figure out the API calls, so browser scraping did the trick.

Then I use the CSV file as a lookup table in the automation to get the cover URL.

I’ll leave it here in case anyone wants to follow a similar approach: GitHub - paulorichards/xbox-game-scraper. It takes about a minute or so to run and pulls in around 3000 games.

2 Likes

Nice, I suppose you need to run the program from time to time to get new releases.

I a lot to ask that you share the automation or at least the part that deals with the csv?

Thanks in advance

Yes, you are correct.

I did the automation in Node-RED. I’m not sure if it can be done directly in Home Assistant. Perhaps with a REST sensor and some templating logic, but I haven’t tried that yet.

After running the script, I place the CSV file in the ‘www’ folder within Home Assistant and then access it from there using an HTTP Request node in Node-RED.

Here is the flow:

[
  {
    "id": "31deacb256799b20",
    "type": "tab",
    "label": "Update Xbox Current Game",
    "disabled": false,
    "info": "",
    "env": []
  },
  {
    "id": "c560093b6b2d638e",
    "type": "function",
    "z": "31deacb256799b20",
    "name": "Extract game name",
    "func": "return {query: msg.payload.split(' - ')[0]};",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 610,
    "y": 140,
    "wires": [
      [
        "1032799666cdaf6a"
      ]
    ]
  },
  {
    "id": "30b7f9319586fc7b",
    "type": "server-state-changed",
    "z": "31deacb256799b20",
    "name": "Current Game Changed",
    "server": "b55816b8.c497b8",
    "version": 5,
    "outputs": 1,
    "exposeAsEntityConfig": "",
    "entityId": "sensor.paulorichards_status",
    "entityIdType": "exact",
    "outputInitially": false,
    "stateType": "str",
    "ifState": "",
    "ifStateType": "str",
    "ifStateOperator": "is",
    "outputOnlyOnStateChange": true,
    "for": "0",
    "forType": "num",
    "forUnits": "minutes",
    "ignorePrevStateNull": false,
    "ignorePrevStateUnknown": false,
    "ignorePrevStateUnavailable": false,
    "ignoreCurrentStateUnknown": false,
    "ignoreCurrentStateUnavailable": false,
    "outputProperties": [
      {
        "property": "payload",
        "propertyType": "msg",
        "value": "",
        "valueType": "entityState"
      },
      {
        "property": "data",
        "propertyType": "msg",
        "value": "",
        "valueType": "eventData"
      },
      {
        "property": "topic",
        "propertyType": "msg",
        "value": "",
        "valueType": "triggerId"
      }
    ],
    "x": 140,
    "y": 140,
    "wires": [
      [
        "86ab7b20afacf703"
      ]
    ]
  },
  {
    "id": "86ab7b20afacf703",
    "type": "switch",
    "z": "31deacb256799b20",
    "name": "Filter Actual games",
    "property": "payload",
    "propertyType": "msg",
    "rules": [
      {
        "t": "eq",
        "v": "Home",
        "vt": "str"
      },
      {
        "t": "cont",
        "v": "Last seen",
        "vt": "str"
      },
      {
        "t": "else"
      }
    ],
    "checkall": "true",
    "repair": false,
    "outputs": 3,
    "x": 370,
    "y": 140,
    "wires": [
      [
        "4186958e7c16e93f"
      ],
      [
        "4186958e7c16e93f"
      ],
      [
        "c560093b6b2d638e"
      ]
    ]
  },
  {
    "id": "4186958e7c16e93f",
    "type": "ha-sensor",
    "z": "31deacb256799b20",
    "name": "Update Sensor",
    "entityConfig": "8643d05c555130c3",
    "version": 0,
    "state": "Inactive",
    "stateType": "str",
    "attributes": [
      {
        "property": "entity_picture",
        "value": "",
        "valueType": "str"
      }
    ],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 620,
    "y": 40,
    "wires": [
      []
    ]
  },
  {
    "id": "37aaba4f72b8d684",
    "type": "csv",
    "z": "31deacb256799b20",
    "name": "Parse CSV",
    "spec": "rfc",
    "sep": ",",
    "hdrin": true,
    "hdrout": "none",
    "multi": "one",
    "ret": "\\n",
    "temp": "",
    "skip": "0",
    "strings": false,
    "include_empty_strings": "",
    "include_null_values": "",
    "x": 1210,
    "y": 140,
    "wires": [
      [
        "fd250c8d70ace24e"
      ]
    ]
  },
  {
    "id": "21486a04579c8159",
    "type": "http request",
    "z": "31deacb256799b20",
    "name": "Get CSV",
    "method": "GET",
    "ret": "txt",
    "paytoqs": "ignore",
    "url": "",
    "tls": "",
    "persist": false,
    "proxy": "",
    "insecureHTTPParser": false,
    "authType": "",
    "senderr": false,
    "headers": [],
    "x": 1020,
    "y": 140,
    "wires": [
      [
        "37aaba4f72b8d684"
      ]
    ]
  },
  {
    "id": "fd250c8d70ace24e",
    "type": "switch",
    "z": "31deacb256799b20",
    "name": "Match Game Title",
    "property": "payload[\"Game Title\"]",
    "propertyType": "msg",
    "rules": [
      {
        "t": "eq",
        "v": "query",
        "vt": "msg"
      }
    ],
    "checkall": "true",
    "repair": false,
    "outputs": 1,
    "x": 1430,
    "y": 140,
    "wires": [
      [
        "762729495184c0b4"
      ]
    ]
  },
  {
    "id": "762729495184c0b4",
    "type": "ha-sensor",
    "z": "31deacb256799b20",
    "name": "Update Sensor",
    "entityConfig": "8643d05c555130c3",
    "version": 0,
    "state": "Active",
    "stateType": "str",
    "attributes": [
      {
        "property": "entity_picture",
        "value": "payload[\"Cover URL\"]",
        "valueType": "msg"
      }
    ],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 1660,
    "y": 140,
    "wires": [
      []
    ]
  },
  {
    "id": "1032799666cdaf6a",
    "type": "template",
    "z": "31deacb256799b20",
    "name": "Setup URL",
    "field": "url",
    "fieldType": "msg",
    "format": "handlebars",
    "syntax": "mustache",
    "template": "https://{{env.HOMEASSISTANT_HOST}}/local/xbox_game_titles.csv",
    "output": "str",
    "x": 830,
    "y": 140,
    "wires": [
      [
        "21486a04579c8159"
      ]
    ]
  },
  {
    "id": "b55816b8.c497b8",
    "type": "server",
    "name": "Home Assistant",
    "addon": true
  },
  {
    "id": "8643d05c555130c3",
    "type": "ha-entity-config",
    "server": "b55816b8.c497b8",
    "deviceConfig": "e0d4f3aa6af52387",
    "name": "xbox-cover",
    "version": "6",
    "entityType": "sensor",
    "haConfig": [
      {
        "property": "name",
        "value": "Xbox Current Game Cover"
      },
      {
        "property": "icon",
        "value": ""
      },
      {
        "property": "entity_picture",
        "value": ""
      },
      {
        "property": "entity_category",
        "value": ""
      },
      {
        "property": "device_class",
        "value": ""
      },
      {
        "property": "unit_of_measurement",
        "value": ""
      },
      {
        "property": "state_class",
        "value": ""
      }
    ],
    "resend": false,
    "debugEnabled": false
  },
  {
    "id": "e0d4f3aa6af52387",
    "type": "ha-device-config",
    "name": "Xbox",
    "hwVersion": "",
    "manufacturer": "Node-RED",
    "model": "Xbox Series X",
    "swVersion": ""
  }
]
1 Like

Thank you for sharing. I think it’s time to learn node red :sweat_smile:

Sorry to bother you, I created the csv with your python program and upload the file to my www forder. Afther thay I installed node red and added your flow but I get an error, do you have any idea what it could be? Thanks in advance

My guess is that the HTTP Request to download the CSV is failing due to an incorrect URL. Double-check that it’s accurate. I’ve configured an environment variable with the Home Assistant host. Make sure to replace that with your own value.

Also, you can add debug nodes to the output of the “Get CSV” node to get more detailed information about what’s going wrong.

1 Like

I have finally been able to implement your method but like mine it has problems with some games (Mafia I: Definitive edition, the one I,m currently playing). If can’t find itshows the cover of the last game played. I have to see how to show a generic image when you can’t find the game since now on my dashboard it shows the name of the current game and the cover of the previous one if can’t find it. I have to see which of the two methods is more accurate.

I also found a few mismatches. So, I’ve updated the Node-RED flow to query MobyGames if there’s no match in the Xbox.com data.

This worked well for me with “DOOM Eternal,” which wasn’t found in the Xbox.com extract (it has two entries: “DOOM Eternal Standard Edition” and “DOOM Eternal Deluxe Edition,” but no plain “DOOM Eternal” as it is reported from the Xbox integration). However, MobyGames did have a match.

Unfortunately, “Mafia I: Definitive Edition” isn’t found in either source.

I’ve added a connection point in the flow for cases where there’s no match. You can use this to update the state in Home Assistant to set a generic image if you like.

I’ve also included some manually injected values for testing different scenarios, along with an MBG_API_KEY environment variable for setting up your API key. I’ll keep working on improving it to handle more edge cases.

Here is the updated flow in case you want to try it.

[
  {
    "id": "31deacb256799b20",
    "type": "tab",
    "label": "Update Xbox Current Game",
    "disabled": false,
    "info": "",
    "env": []
  },
  {
    "id": "c560093b6b2d638e",
    "type": "function",
    "z": "31deacb256799b20",
    "name": "Extract game name",
    "func": "return {query: msg.payload.split(' - ')[0]};",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 610,
    "y": 200,
    "wires": [
      [
        "1032799666cdaf6a"
      ]
    ]
  },
  {
    "id": "30b7f9319586fc7b",
    "type": "server-state-changed",
    "z": "31deacb256799b20",
    "name": "Current Game Changed",
    "server": "b55816b8.c497b8",
    "version": 5,
    "outputs": 1,
    "exposeAsEntityConfig": "",
    "entityId": "sensor.paulorichards_status",
    "entityIdType": "exact",
    "outputInitially": false,
    "stateType": "str",
    "ifState": "",
    "ifStateType": "str",
    "ifStateOperator": "is",
    "outputOnlyOnStateChange": true,
    "for": "0",
    "forType": "num",
    "forUnits": "minutes",
    "ignorePrevStateNull": false,
    "ignorePrevStateUnknown": false,
    "ignorePrevStateUnavailable": false,
    "ignoreCurrentStateUnknown": false,
    "ignoreCurrentStateUnavailable": false,
    "outputProperties": [
      {
        "property": "payload",
        "propertyType": "msg",
        "value": "",
        "valueType": "entityState"
      },
      {
        "property": "data",
        "propertyType": "msg",
        "value": "",
        "valueType": "eventData"
      },
      {
        "property": "topic",
        "propertyType": "msg",
        "value": "",
        "valueType": "triggerId"
      }
    ],
    "x": 140,
    "y": 140,
    "wires": [
      [
        "86ab7b20afacf703"
      ]
    ]
  },
  {
    "id": "86ab7b20afacf703",
    "type": "switch",
    "z": "31deacb256799b20",
    "name": "Filter Actual games",
    "property": "payload",
    "propertyType": "msg",
    "rules": [
      {
        "t": "eq",
        "v": "Home",
        "vt": "str"
      },
      {
        "t": "cont",
        "v": "Last seen",
        "vt": "str"
      },
      {
        "t": "else"
      }
    ],
    "checkall": "true",
    "repair": false,
    "outputs": 3,
    "x": 370,
    "y": 140,
    "wires": [
      [
        "4186958e7c16e93f"
      ],
      [
        "4186958e7c16e93f"
      ],
      [
        "c560093b6b2d638e"
      ]
    ]
  },
  {
    "id": "4186958e7c16e93f",
    "type": "ha-sensor",
    "z": "31deacb256799b20",
    "name": "Update Sensor",
    "entityConfig": "8643d05c555130c3",
    "version": 0,
    "state": "Inactive",
    "stateType": "str",
    "attributes": [
      {
        "property": "entity_picture",
        "value": "",
        "valueType": "str"
      }
    ],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 600,
    "y": 80,
    "wires": [
      []
    ]
  },
  {
    "id": "37aaba4f72b8d684",
    "type": "csv",
    "z": "31deacb256799b20",
    "name": "Parse CSV",
    "spec": "rfc",
    "sep": ",",
    "hdrin": true,
    "hdrout": "none",
    "multi": "one",
    "ret": "\\n",
    "temp": "",
    "skip": "0",
    "strings": false,
    "include_empty_strings": "",
    "include_null_values": "",
    "x": 1190,
    "y": 200,
    "wires": [
      [
        "fd250c8d70ace24e"
      ]
    ]
  },
  {
    "id": "21486a04579c8159",
    "type": "http request",
    "z": "31deacb256799b20",
    "name": "Get CSV",
    "method": "GET",
    "ret": "txt",
    "paytoqs": "ignore",
    "url": "",
    "tls": "",
    "persist": false,
    "proxy": "",
    "insecureHTTPParser": false,
    "authType": "",
    "senderr": false,
    "headers": [],
    "x": 1020,
    "y": 200,
    "wires": [
      [
        "37aaba4f72b8d684"
      ]
    ]
  },
  {
    "id": "fd250c8d70ace24e",
    "type": "switch",
    "z": "31deacb256799b20",
    "name": "Match Game Title",
    "property": "payload[\"Game Title\"]",
    "propertyType": "msg",
    "rules": [
      {
        "t": "eq",
        "v": "query",
        "vt": "msg"
      },
      {
        "t": "else"
      }
    ],
    "checkall": "false",
    "repair": false,
    "outputs": 2,
    "x": 490,
    "y": 320,
    "wires": [
      [
        "762729495184c0b4"
      ],
      [
        "10729ce07ce73b95"
      ]
    ]
  },
  {
    "id": "762729495184c0b4",
    "type": "ha-sensor",
    "z": "31deacb256799b20",
    "name": "Update Sensor",
    "entityConfig": "8643d05c555130c3",
    "version": 0,
    "state": "Active",
    "stateType": "str",
    "attributes": [
      {
        "property": "entity_picture",
        "value": "payload[\"Cover URL\"]",
        "valueType": "msg"
      }
    ],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 800,
    "y": 320,
    "wires": [
      []
    ]
  },
  {
    "id": "1032799666cdaf6a",
    "type": "template",
    "z": "31deacb256799b20",
    "name": "Setup URL",
    "field": "url",
    "fieldType": "msg",
    "format": "handlebars",
    "syntax": "mustache",
    "template": "https://{{env.HOMEASSISTANT_HOST}}/local/xbox_game_titles.csv",
    "output": "str",
    "x": 830,
    "y": 200,
    "wires": [
      [
        "21486a04579c8159"
      ]
    ]
  },
  {
    "id": "d368b0cbb905dd44",
    "type": "inject",
    "z": "31deacb256799b20",
    "name": "Mafia I: Definitive edition",
    "props": [
      {
        "p": "payload"
      },
      {
        "p": "topic",
        "vt": "str"
      }
    ],
    "repeat": "",
    "crontab": "",
    "once": false,
    "onceDelay": 0.1,
    "topic": "",
    "payload": "Mafia I: Definitive edition",
    "payloadType": "str",
    "x": 170,
    "y": 300,
    "wires": [
      [
        "86ab7b20afacf703"
      ]
    ]
  },
  {
    "id": "10729ce07ce73b95",
    "type": "switch",
    "z": "31deacb256799b20",
    "name": "Nothing matched",
    "property": "parts.count",
    "propertyType": "msg",
    "rules": [
      {
        "t": "eq",
        "v": "$.parts.index + 1",
        "vt": "jsonata"
      }
    ],
    "checkall": "true",
    "repair": false,
    "outputs": 1,
    "x": 490,
    "y": 420,
    "wires": [
      [
        "430c943f33720c9c"
      ]
    ]
  },
  {
    "id": "dbf02c1302b5eb14",
    "type": "inject",
    "z": "31deacb256799b20",
    "name": "DOOM Eternal",
    "props": [
      {
        "p": "payload"
      },
      {
        "p": "topic",
        "vt": "str"
      }
    ],
    "repeat": "",
    "crontab": "",
    "once": false,
    "onceDelay": 0.1,
    "topic": "",
    "payload": "DOOM Eternal",
    "payloadType": "str",
    "x": 140,
    "y": 260,
    "wires": [
      [
        "86ab7b20afacf703"
      ]
    ]
  },
  {
    "id": "d85edff32493dbd0",
    "type": "inject",
    "z": "31deacb256799b20",
    "name": "Home",
    "props": [
      {
        "p": "payload"
      },
      {
        "p": "topic",
        "vt": "str"
      }
    ],
    "repeat": "",
    "crontab": "",
    "once": false,
    "onceDelay": 0.1,
    "topic": "",
    "payload": "Home",
    "payloadType": "str",
    "x": 110,
    "y": 220,
    "wires": [
      [
        "86ab7b20afacf703"
      ]
    ]
  },
  {
    "id": "430c943f33720c9c",
    "type": "template",
    "z": "31deacb256799b20",
    "name": "Setup Game URL",
    "field": "url",
    "fieldType": "msg",
    "format": "handlebars",
    "syntax": "mustache",
    "template": "https://api.mobygames.com/v1/games?api_key={{env.MBG_API_KEY}}&title={{query}}&format=id",
    "output": "str",
    "x": 790,
    "y": 420,
    "wires": [
      [
        "8cac63c18b5449f4"
      ]
    ]
  },
  {
    "id": "8cac63c18b5449f4",
    "type": "http request",
    "z": "31deacb256799b20",
    "name": "Find Game ID",
    "method": "GET",
    "ret": "txt",
    "paytoqs": "ignore",
    "url": "",
    "tls": "",
    "persist": false,
    "proxy": "",
    "insecureHTTPParser": false,
    "authType": "",
    "senderr": false,
    "headers": [],
    "x": 1040,
    "y": 420,
    "wires": [
      [
        "7d4f50950f1af073"
      ]
    ]
  },
  {
    "id": "7d4f50950f1af073",
    "type": "json",
    "z": "31deacb256799b20",
    "name": "Parse JSON",
    "property": "payload",
    "action": "",
    "pretty": false,
    "x": 1250,
    "y": 420,
    "wires": [
      [
        "c7dd1d621ac4864a"
      ]
    ]
  },
  {
    "id": "32275ada01c4f1b8",
    "type": "template",
    "z": "31deacb256799b20",
    "name": "Setup Cover URL",
    "field": "url",
    "fieldType": "msg",
    "format": "handlebars",
    "syntax": "mustache",
    "template": "https://api.mobygames.com/v1/games/{{payload.games.0}}/platforms/289/covers?api_key={{env.MBG_API_KEY}}",
    "output": "str",
    "x": 730,
    "y": 580,
    "wires": [
      [
        "d6cbd4e4e96995d7"
      ]
    ]
  },
  {
    "id": "7e89b8018a5cbd6d",
    "type": "template",
    "z": "31deacb256799b20",
    "name": "Setup Cover URL",
    "field": "url",
    "fieldType": "msg",
    "format": "handlebars",
    "syntax": "mustache",
    "template": "https://api.mobygames.com/v1/games/{{payload.games.0}}/platforms/142/covers?api_key={{env.MBG_API_KEY}}",
    "output": "str",
    "x": 730,
    "y": 660,
    "wires": [
      [
        "9d50f9ca3ee53f63"
      ]
    ]
  },
  {
    "id": "6eb9f6c6536428e1",
    "type": "http request",
    "z": "31deacb256799b20",
    "name": "",
    "method": "GET",
    "ret": "txt",
    "paytoqs": false,
    "url": "",
    "persist": false,
    "insecureHTTPParser": false,
    "authType": "",
    "senderr": false,
    "headers": [],
    "x": 1330,
    "y": 620,
    "wires": [
      [
        "02ddac9a770e46ad"
      ]
    ]
  },
  {
    "id": "10cd20aa7dfeae24",
    "type": "delay",
    "z": "31deacb256799b20",
    "name": "",
    "pauseType": "delay",
    "timeout": "1",
    "timeoutUnits": "seconds",
    "rate": "1",
    "nbRateUnits": "1",
    "rateUnits": "second",
    "randomFirst": "1",
    "randomLast": "5",
    "randomUnits": "seconds",
    "drop": false,
    "allowrate": false,
    "outputs": 1,
    "x": 1140,
    "y": 580,
    "wires": [
      [
        "6eb9f6c6536428e1"
      ]
    ]
  },
  {
    "id": "784540448c4fbb71",
    "type": "delay",
    "z": "31deacb256799b20",
    "name": "",
    "pauseType": "delay",
    "timeout": "3",
    "timeoutUnits": "seconds",
    "rate": "1",
    "nbRateUnits": "1",
    "rateUnits": "second",
    "randomFirst": "1",
    "randomLast": "5",
    "randomUnits": "seconds",
    "drop": false,
    "allowrate": false,
    "outputs": 1,
    "x": 1140,
    "y": 660,
    "wires": [
      [
        "6eb9f6c6536428e1"
      ]
    ]
  },
  {
    "id": "02ddac9a770e46ad",
    "type": "json",
    "z": "31deacb256799b20",
    "name": "Parse Json",
    "property": "payload",
    "action": "",
    "pretty": false,
    "x": 1530,
    "y": 620,
    "wires": [
      [
        "1252f98054ed1c8f"
      ]
    ]
  },
  {
    "id": "d6cbd4e4e96995d7",
    "type": "template",
    "z": "31deacb256799b20",
    "name": "Set platform",
    "field": "platform",
    "fieldType": "msg",
    "format": "handlebars",
    "syntax": "mustache",
    "template": "xbox-series",
    "output": "str",
    "x": 950,
    "y": 580,
    "wires": [
      [
        "10cd20aa7dfeae24"
      ]
    ]
  },
  {
    "id": "9d50f9ca3ee53f63",
    "type": "template",
    "z": "31deacb256799b20",
    "name": "Set platform",
    "field": "platform",
    "fieldType": "msg",
    "format": "handlebars",
    "syntax": "mustache",
    "template": "xbox-one",
    "output": "str",
    "x": 950,
    "y": 660,
    "wires": [
      [
        "784540448c4fbb71"
      ]
    ]
  },
  {
    "id": "1252f98054ed1c8f",
    "type": "join",
    "z": "31deacb256799b20",
    "name": "Merge Covers",
    "mode": "custom",
    "build": "object",
    "property": "payload",
    "propertyType": "msg",
    "key": "platform",
    "joiner": "\\n",
    "joinerType": "str",
    "useparts": false,
    "accumulate": true,
    "timeout": "",
    "count": "2",
    "reduceRight": false,
    "reduceExp": "",
    "reduceInit": "",
    "reduceInitType": "",
    "reduceFixup": "",
    "x": 510,
    "y": 760,
    "wires": [
      [
        "b578020877c4b3f0"
      ]
    ]
  },
  {
    "id": "b578020877c4b3f0",
    "type": "function",
    "z": "31deacb256799b20",
    "name": "Select Cover from plarform",
    "func": "if (msg.payload[\"xbox-series\"].cover_groups.length > 0)\n    return { payload: msg.payload[\"xbox-series\"].cover_groups[0].covers[0].image }\nelse if (msg.payload[\"xbox-one\"].cover_groups.length > 0)\n    return {payload: msg.payload[\"xbox-one\"].cover_groups[0].covers[0].image }\nelse\n    return { payload: null }",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 760,
    "y": 760,
    "wires": [
      [
        "c76999761bfe3859"
      ]
    ]
  },
  {
    "id": "c7dd1d621ac4864a",
    "type": "switch",
    "z": "31deacb256799b20",
    "name": "Game Found?",
    "property": "payload.games",
    "propertyType": "msg",
    "rules": [
      {
        "t": "else"
      },
      {
        "t": "nempty"
      }
    ],
    "checkall": "true",
    "repair": false,
    "outputs": 2,
    "x": 500,
    "y": 560,
    "wires": [
      [],
      [
        "32275ada01c4f1b8",
        "7e89b8018a5cbd6d"
      ]
    ]
  },
  {
    "id": "c76999761bfe3859",
    "type": "ha-sensor",
    "z": "31deacb256799b20",
    "name": "Update Sensor",
    "entityConfig": "8643d05c555130c3",
    "version": 0,
    "state": "Active",
    "stateType": "str",
    "attributes": [
      {
        "property": "entity_picture",
        "value": "payload",
        "valueType": "msg"
      }
    ],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 1020,
    "y": 760,
    "wires": [
      []
    ]
  },
  {
    "id": "b55816b8.c497b8",
    "type": "server",
    "name": "Home Assistant",
    "addon": true,
    "rejectUnauthorizedCerts": true,
    "ha_boolean": "",
    "connectionDelay": false,
    "cacheJson": false,
    "heartbeat": false,
    "heartbeatInterval": "",
    "statusSeparator": "",
    "enableGlobalContextStore": false
  },
  {
    "id": "8643d05c555130c3",
    "type": "ha-entity-config",
    "server": "b55816b8.c497b8",
    "deviceConfig": "e0d4f3aa6af52387",
    "name": "xbox-cover",
    "version": "6",
    "entityType": "sensor",
    "haConfig": [
      {
        "property": "name",
        "value": "Xbox Current Game Cover"
      },
      {
        "property": "icon",
        "value": ""
      },
      {
        "property": "entity_picture",
        "value": ""
      },
      {
        "property": "entity_category",
        "value": ""
      },
      {
        "property": "device_class",
        "value": ""
      },
      {
        "property": "unit_of_measurement",
        "value": ""
      },
      {
        "property": "state_class",
        "value": ""
      }
    ],
    "resend": false,
    "debugEnabled": false
  },
  {
    "id": "e0d4f3aa6af52387",
    "type": "ha-device-config",
    "name": "Xbox",
    "hwVersion": "",
    "manufacturer": "Node-RED",
    "model": "Xbox Series X",
    "swVersion": ""
  }
]
1 Like

Thank you very much :blush:

Thankyou so much for this, finally got it working after a few hours of tinkering and remembering how to use Python. :+1: ( And first use of node red, steep learning curve today!)

This template is great, I’ve just been using Home Assistant for a short time but using REST with the IGDB API, a couple of sensors and media_player template to display on the panel I have managed to associate the name of the game that Xbox returns with its respective image :grin:.

Is there a Guide to replicate this?
Ideally I would like in a media player template.

I could not get your scraper to run successfully (looks like a timeout error?). Is it possible for you to post a link to the CSV file?

  File "/root/scrape-xbox/xbox-game-scraper/scrape-xbox.py", line 78, in <module>
    load_xbox_games_page()
  File "/root/scrape-xbox/xbox-game-scraper/scrape-xbox.py", line 19, in load_xbox_games_page
    xbox_series_link.click()
  File "/usr/local/lib/python3.11/dist-packages/playwright/sync_api/_generated.py", line 15507, in click
    self._sync(
  File "/usr/local/lib/python3.11/dist-packages/playwright/_impl/_sync_base.py", line 115, in _sync
    return task.result()
           ^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/playwright/_impl/_locator.py", line 160, in click
    return await self._frame.click(self._selector, strict=True, **params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/playwright/_impl/_frame.py", line 488, in click
    await self._channel.send("click", locals_to_params(locals()))
  File "/usr/local/lib/python3.11/dist-packages/playwright/_impl/_connection.py", line 61, in send
    return await self._connection.wrap_api_call(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/playwright/_impl/_connection.py", line 528, in wrap_api_call
    raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None
playwright._impl._errors.TimeoutError: Locator.click: Timeout 30000ms exceeded.
Call log:
  - waiting for locator("#platSelect").locator("a").filter(has=locator("span").filter(has_text="Xbox Series X|S"))

Hi all,
I was so inspired by the dashboard @d13g0m0nt3s initially shared, that I wanted to recreate the image entity showing the current playing Xbox game. I’m relatively new to HA, so my dashboard is not as pretty as the original by d13g0m0nt3s, but at least the image card is working :slight_smile:

I will briefly explain how I managed to get things working.

  • I expect you already have set up your Xbox integration. It’s necessary to enable the sensor sensor.your_gamertag.status. This sensor stores the game you’re currently playing and is disabled by default.
  • Create an account at GiantBomb and get your API key.
  • Create a virtual sensor in your configuration.yaml. This sensor will ask GiantBomb for the Game ID you’re currently playing via REST API and store it. Update the code with your own API and GamerTag Sensor you previously enabled. We will use this ID to ask for the image in the next step.
sensor:
  # Get Game ID
  - platform: rest
    name: "GiantBomb Game ID"
    resource_template: "https://www.giantbomb.com/api/search/?api_key=ENTER_YOUR_API_HERE&format=json&query={{ states('sensor.your_gamertag.status') | urlencode }}&resources=game"
    headers:
      User-Agent: "HomeAssistant"
    value_template: >
      {% if value_json.results is defined and value_json.results | length > 0 %}
        {{ value_json.results[0].id }} 
      {% else %}
        none
      {% endif %}
    scan_interval: 604800 
  • Create a virtual sensor in your configuration.yaml. Simply paste this code below the code from the previous step. This sensor will use the ID sensor and ask for the URL of the game’s artwork and store it. This URL sensor is later used in the dashboard. Update your API Key in this code as well.
    # Get Game Artwork
  - platform: rest
    name: "GiantBomb Game Artwork URL"
    resource_template: "https://www.giantbomb.com/api/game/{{ states('sensor.giantbomb_game_id' ) }}/?api_key=ENTER_YOUR_API_HERE&format=json"
    headers:
      User-Agent: "HomeAssistant"
    value_template: >
      {% if value_json.results is defined and value_json.results.image is defined and value_json.results.image.screen_url is defined %}
        {{ value_json.results.image.super_url }}
      {% else %}
        none
      {% endif %}
    scan_interval: 604800
  • The scan_interval is set to 604800 seconds which is equivalent to one week. So the sensor will update once a week. That’s no problem because we will now create the automation to update the sensors as soon as your GamerTag sensor changes its value.
  • Create a new automation and paste this YAML code into it. Update your GamerTag sensors so this automation will work for you as well. This automation is triggered by an update of your GamerTag status. Remember, this status stores the game you’re playing or how long you are offline, e.g. “Last seen 4h ago: Home”. As we don’t want hourly updates when you are not playing we use a condition to check if you are currently online. If yes, we will update the sensors that do the API calls for us.
alias: Xbox Game Artwork
description: ""
triggers:
  - entity_id:
      - sensor.your_gamertag.status
    trigger: state
conditions:
  - condition: and
    conditions:
      - condition: state
        entity_id: binary_sensor.your_gamertag
        state: "off"
actions:
  - target:
      entity_id: sensor.giantbomb_game_id
    action: homeassistant.update_entity
    data:
      entity_id:
        - sensor.giantbomb_game_id
  - delay: "00:00:02"
  - target:
      entity_id: sensor.giantbomb_game_artwork_url
    action: homeassistant.update_entity
    data: {}
  - delay: "00:00:02"
mode: single
  • Now it’s time to update your dashboard! I’m using a custom card from HACS because that works perfectly well with the URL from our sensor. The default HA card does not. Paste the YAML code into your dashboard whereever you want to have it.
type: custom:media-source-image-card
image: |
  {{ states('sensor.giantbomb_game_artwork_url') }}
forced_refresh_interval: 5
  • Optional:
    If you only want to display the image card when you are currently playing a game, use this code and paste it into the visibility option of the image card:
condition: state
entity: binary_sensor.gamertag # update here as well
state: "on"

So that’s it! I hope you could follow these steps and they helped you to recreate it for your own dashboard. Remember to update the code examples with your own API key and GamerTag. I’m not a native speaker so please excuse me if I have not expressed myself clearly enough. Feel free to ask in the comments!