Nintendo Switch App Sensor - Track for example your Ring Fit Activity

I was looking for a way to track how long I use Ring Fit Adventure. Unfortunately the Nintendo Switch doesn’t have any integrations.

So I used a piece of homebrew on my homebrew enabled Switch.
It is originally made for Discord Rich Presence but with a small script it is also available in Home-Assistant as a sensor.

You’ll need a homebrew enabled switch and install a sysmodule called SwitchPresence-Rewritten.

I have a command_line sensor that runs the following (rough and ugly) script:

import socket
import struct

SWITCH_IP = "192.168.1.100"

TCP_PORT = 0xCAFE
PACKETMAGIC = 0xFFAADD23

#Defines a title packet
class Title:

    def __init__(self, raw_data):
        unpacker = struct.Struct('2L612s')
        enc_data = unpacker.unpack(raw_data)
        self.magic = int(enc_data[0])
        if int(enc_data[1]) == 0:
            self.pid = int(enc_data[1])
            self.name = 'Home Menu'
        else:
            self.pid = int(enc_data[1])
            self.name = enc_data[2].decode('utf-8', 'ignore').split('\x00')[0]

def main():
    switch_server_address = (SWITCH_IP, TCP_PORT)
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(6)
    try:
      sock.connect(switch_server_address)
      data = sock.recv(628)
      title = Title(data)
      print(title.name)
      sock.close()
    except:
       print("off")



if __name__ == '__main__':
    main()

Don’t forget to change the IP.

Use it like this in Home-Assistant:

sensor:
  - platform: command_line
    name: Switch App
    command: python3 /config/scripts/presence-client.py

And the bonus sensor for getting Ring Fit Activity:

sensor:
  - platform: history_stats
    name: Ring Fit Duration
    entity_id: sensor.switch_app
    state: "Ring Fit Adventure"
    type: time
    start: "{{ now().replace(hour=0, minute=0, second=0) }}"
    end: "{{ now() }}"

Result:
Screenshot from 2021-05-15 20.34.13

3 Likes

This is beyond cool. I had been using the Discord Presence sysmodule with the intended PC client to update the Discord status, then the Discord Integration to pull the values into Home Assistant. Your way is so much more elegant, gonna update mine ASAP.

EDIT: It doesn’t seem to work too well for me. If I run the script manually, it works once (but also prints “off” after the game title), then subsequent attempts just result in “off”. After the initial attempt from the script, the presence client on the PC also stops working. If I go to the presence client manager on the Switch and toggle the module, it “resets” and allows the PC client and an initial script attempt to work. However, after the reset, it stops working after the first script attempt and the cycle continues.

I’m not really familiar with Python, but I tried removing the try/except statement to see what exception is occurring. The first attempt works, the second takes a few seconds to error out with:

Traceback (most recent call last):
  File "switch-presence-noExcept.py", line 35, in <module>
    main()
  File "switch-presence-noExcept.py", line 26, in main
    data = sock.recv(628)
socket.timeout: timed out

While every subsequent attempt after that produces:

Traceback (most recent call last):
  File "switch-presence-noExcept.py", line 35, in <module>
    main()
  File "switch-presence-noExcept.py", line 26, in main
    data = sock.recv(628)
ConnectionResetError: [Errno 104] Connection reset by peer

I’ll keep trying to mess around with this and see what else I can find out.

I have updated the script. Could you try again? Running the second time works for me. But only after waiting for a few seconds. This is no problem for me since the command_line sensor updates every 60 seconds.

I got it working, similar to your change I took the exit() method out of the try/except statements (although moved it outside instead of removing it completely). I also included the game ID so it can be used for a nice-looking media player:


or
image

Nice looking is subjective :stuck_out_tongue: Here’s the python script I settled on:

import socket
import struct
import json

TCP_PORT = 0xCAFE
SWITCH_IP = '192.168.0.14'
PACKETMAGIC = 0xFFAADD23

#Defines a title packet
class Title:

    def __init__(self, raw_data):
        unpacker = struct.Struct('2L612s')
        enc_data = unpacker.unpack(raw_data)
        self.magic = int(enc_data[0])
        if int(enc_data[1]) == 0:
            self.pid = int(enc_data[1])
            self.name = 'Home Menu'
        else:
            self.pid = int(enc_data[1])
            self.name = enc_data[2].decode('utf-8', 'ignore').split('\x00')[0]

def main():
    switch_server_address = (SWITCH_IP, TCP_PORT)
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(10)
    try:
      sock.connect(switch_server_address)
      data = sock.recv(628)
      title = Title(data)
      sensorJson = {
          "state": "on",
          "id": str(hex(title.pid)).replace("x",""),
          "title": title.name
      }
      sensorString = json.dumps(sensorJson)
      print(sensorString)
    except:
      sensorJson = {
          "state": "off",
          "id": -1,
          "title": "unknown"
      }
      sensorString = json.dumps(sensorJson)
      print(sensorString)
    exit()

if __name__ == '__main__':
    main()

Home Assistant package to configure the following command line and template sensors, and media player:

sensor:
- platform: command_line
  name: Switch Presence Python
  command: python3 /config/configs/commands/switch-presence.py
  json_attributes:
    - id
    - title
  value_template: '{{ value_json.state }}'
  scan_interval: 15
- platform: template
  sensors:
    nintendo_switch_status_python:
      friendly_name: "Nintendo Switch Status Python"
      value_template: '{{ states("sensor.switch_presence_python") }}'
      entity_picture_template: '{{ "/local/nintendo_switch_thumbnails/" ~ state_attr("sensor.switch_presence_python","id") ~ ".jpg" }}'
      attribute_templates:
        media_content_type: music
        media_title: '{{ state_attr("sensor.switch_presence_python","title") }}'

media_player:
- platform: universal
  name: Nintendo Switch Python
  children:
    - sensor.nintendo_switch_status_python

For the images to work, you’ll need to launch SwitchPresence-Rewritten-Manager.nro and dump the images, then transfer the contents of your Switch SDMC://Images folder to a HomeAssistant /config/www/nintendo_switch_thumbnails/ directory.

2 Likes

Nice work!

Cheers mate, ditto :slight_smile: I noticed today though that the code on my JSON couldn’t handle apostrophes in the game title and had to swap the replace ' with " function for json.dumps(). I updated the code in my previous post in case anyone is interested.

My Switch is modded, but I haven’t messed with it in a while. When does the homebrew send the data? Is it only while at the Switch dashboard, on startup and obviously not while in sleep mode?

Based on my novice understanding I believe the homebrew doesn’t “send” data, it runs constantly and responds to queries sent from the script or presence client at whatever interval it’s defined to.

Interestingly, it still responds when asleep, docked and using wired ethernet; if you put it to sleep while running a game, even if suspended and on the Home menu, it gives the same response as if you were actively playing the game. The only way I can get around this is letting it use wifi. Not a huge deal since I don’t wanna play online with my modded Switch.

I just came across this repo where someone was able to pull data from his switch account using the parental controls features…

THe code is 2yrs old, but Im going to see if i can get it working as-is… if I can, i may need some help turning it into an integration, as I have never done an integration before.

hi. any progress??

Hello.
Latest Home Assistant updates require using a new syntax for the command_line block to keep this code working:

command_line:
  - sensor:
      name: "Switch Presence Python"
      command: python3 /config/scripts/switch_presence.py
      json_attributes:
        - id
        - title
      value_template: '{{ value_json.state }}'
      scan_interval: 15

sensor:
- platform: template
  sensors:
    nintendo_switch_status_python:
      friendly_name: "Nintendo Switch Status Python"
      value_template: '{{ states("sensor.switch_presence_python") }}'
      entity_picture_template: '{{ "/local/nintendo_switch_thumbnails/" ~ state_attr("sensor.switch_presence_python","id") ~ ".jpg" }}'
      attribute_templates:
        media_content_type: music
        media_title: '{{ state_attr("sensor.switch_presence_python","title") }}'

media_player:
- platform: universal
  name: Nintendo Switch Python
  children:
    - sensor.nintendo_switch_status_python

I made a custom component for Nintendo Switch Online: Custom component: Nintendo Switch Online