Month: July 2021

  • Fixing Analog Stick Input in PPSSPP

    Fixing Analog Stick Input in PPSSPP

    PPSSPP is one of my favorite emulators. First introduced to me when I was still in high school, it is my go-to (or, only?) choice if I want to revisit some old PSP games. Though I ran into an issue that I was not able to run, only walk, diagonally in Metal Gear Solid: Peace Walker, while using a controller on Android.

    After some investigation I found out the root cause: PSP analog stick input uses max-norm (shown as p=∞ below), not Euclidean-norm (shown as p=2 below), which most other controller uses.

    In a simpler sentence: When you draw a circle using a regular controller analog stick, you get a circular input; when you draw a circle using a PSP analog stick, you get a square.

    Illustration of the unit circles of some p-norms. By QuartlOwn work, CC BY-SA 3.0, Link

    Apparently, the Android controller backend in PPSSPP did not perform this circle to square conversion, thus causing this issue. This is evidenced in the GIF below, using the PPSSPP analog stick viewer.

    Now, the easiest way of fixing this is to implement the circle to square mapping in the Android analog input backend, but that’s not the best solution. For historical reasons, the analog stick input is architected as the following

    PPSSPP 1.11 Design for Analog Input

    Analog stick mapping is done by each backend separately, with the XInput implementation being the more feature-complete one. The other ones usually have fewer features or are somewhat broken (e.g. lacking the necessary circle to square mapping). What we need is to refactor this into something more reasonable.

    A Better Analog Input Design

    This ensures that no matter which input backend you are using, you use the same, unified input mapper implementation. This ensures feature parity across all input backends. We just need to implement a correct, feature-complete unified input mapper.

    After a few hours, I have the refactor done, and it was eventually merged into master. The fix will most likely be available in the future 1.12 release of PPSSPP, and people can now have their game characters run diagonally.

  • Home Automation – Kubernetes Style

    Home Automation – Kubernetes Style

    I’ve been using Smartthings as my smart home hub for a while. And I’d like to migrate to something that’s open source (or more importantly, self-hosted). There’s been a few horror stories out there that make me worry about the potential “hostage” situation. (e.g. manufacturers charging a monthly price for the service, or flat-out discontinuing the service).

    Hardware

    Image 31 - Dell Wyse N03D Thin Client Celeron CPU N2807 1.58GHz 16GB SSD, 4GB DDR3 (NEW)
    Dell Wyse N03D
    Z-Wave + Zigbee Dongle

    I bought a Dell Wyse N03D Thin Client off eBay at $35 each, complete with power supply, keyboard and mouse. The thin client is definitely not a powerhouse, but it’s dual core x86-64 processor and 4GB of RAM should be plenty sufficient for my use case. To let it connect to Z-Wave and ZigBee devices, I also paired it with a dongle.

    Software

    OS

    The thin client came with Windows 7 Embedded pre-installed, which I absolutely don’t need. I replaced it with a fresh install of Ubuntu Server 20.04 LTS. As the thin client only has 16G of onboard storage, I modified the default partitioning scheme to get more space to the rootfs.

    Kubernetes

    I chose k3s for this project. It’s small size is a perfect fit. For the installation, running the following snippet is sufficient.

    curl -sfL https://get.k3s.io | sh -

    Running rancher is always a good idea. To have rancher installed on k3s, add the following manifest to /var/lib/rancher/k3s/server/manifests

    ---
    apiVersion: v1
    kind: Namespace
    metadata:
      name: cattle-system
    ---
    apiVersion: helm.cattle.io/v1
    kind: HelmChart
    metadata:
      name: rancher
      namespace: kube-system
    spec:
      chart: https://releases.rancher.com/server-charts/stable/rancher-2.5.8.tgz
      targetNamespace: cattle-system
      valuesContent: |-
        hostname: [REDACTED]
        tls: external
        replicas: 1

    This works because k3s comes with a helm CRD.

    Following that, apply the following manifest to get home-assistant and its friends installed.

    ---
    apiVersion: v1
    kind: Namespace
    metadata:
      name: home-assistant
    ---
    apiVersion: v1
    kind: Service
    metadata:
      namespace: home-assistant
      name: home-assistant
    spec:
      selector:
        app: home-assistant
      ports:
      - name: http
        protocol: TCP
        port: 80
        targetPort: 8123
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      namespace: home-assistant
      name: home-assistant
      labels:
        app: home-assistant
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: home-assistant
      template:
        metadata:
          labels:
            app: home-assistant
        spec:
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                - matchExpressions:
                  - key: kubernetes.io/hostname
                    operator: In
                    values:
                    - homelab-us-east-1-nano-1
          containers:
          - name: home-assistant
            image: homeassistant/home-assistant:stable
            ports:
            - containerPort: 8123
            volumeMounts:
            - mountPath: /config
              name: config
            - mountPath: /dev/ttyUSB1
              name: zigbee
            securityContext:
              privileged: true
          volumes:
          - name: config
            hostPath:
              path: /etc/home-assistant
              type: DirectoryOrCreate
          - name: zigbee
            hostPath:
              path: /dev/ttyUSB1
    ---
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      namespace: home-assistant
      name: home-assistant
    spec:
      rules:
      - host: [REDACTED]
        http:
          paths:
          - pathType: Prefix
            path: /
            backend:
              service:
                name: home-assistant
                port:
                  number: 80
    ---
    apiVersion: v1
    kind: Service
    metadata:
      namespace: home-assistant
      name: zwavejs2mqtt
    spec:
      selector:
        app: zwavejs2mqtt
      type: NodePort
      ports:
      - name: http
        protocol: TCP
        port: 8091
        targetPort: 8091
        nodePort: 30091
      - name: ws
        protocol: TCP
        port: 3000
        targetPort: 3000
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      namespace: home-assistant
      name: zwavejs2mqtt
      labels:
        app: zwavejs2mqtt
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: zwavejs2mqtt
      template:
        metadata:
          labels:
            app: zwavejs2mqtt
        spec:
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                - matchExpressions:
                  - key: kubernetes.io/hostname
                    operator: In
                    values:
                    - homelab-us-east-1-nano-1
          containers:
          - name: zwavejs2mqtt
            image: zwavejs/zwavejs2mqtt:4.5.1
            ports:
            - containerPort: 8091
            - containerPort: 3000
            volumeMounts:
            - mountPath: /usr/src/app/store
              name: config
            - mountPath: /dev/ttyUSB0
              name: zwave
            securityContext:
              privileged: true
          volumes:
          - name: config
            hostPath:
              path: /etc/zwavejs2mqtt
              type: DirectoryOrCreate
          - name: zwave
            hostPath:
              path: /dev/ttyUSB0

    Done

    I also used Cloudflare Argo tunnel to allow me to control my home automation outside of my house.