Jae's Blog Creatures and blogs.

Making a Yealink contacts directory

Last post of the year, I hope everybody will have a wonderful 2026, even within those troubled times.

I was playing with my Yealink T42s IP phone last night, wanting to make some API endpoint that automatically generates a remote phone book for the phone.
The remote phone book uses XML formatted with custom elements that are described within their official “documentation” PDF, graciously archived here.

The PDF shows the following example (from “Yealink SIP Phones XML Browser Developer’s Guide”, page 49):

<?xml version="1.0" encoding="ISO-8859-1"?>
<YealinkIPPhoneDirectory
defaultIndex = "integer"
next = "URI"
previous = "URI"
Beep = "yes/no"
cancelAction="URI"
Timeout = "integer"
refresh=“refresh timeurl=“url“
LockIn = "yes/no">
    <Title wrap = "yes/no">Directory Title</Title>
    <URL>URL</URL>
    <InputField>
        <Parameter>name</Parameter>
        <preKey>key words of the contacts</preKey>
    </InputField>
    <MenuItem>
        <Prompt>Contact Name</Prompt>
        <URI>number</URI>
    </MenuItem>
    <!--Additional Menu Items may be added -->
    <!--Additional soft key items may be added -->
</YealinkIPPhoneDirectory>Code language: HTML, XML (xml)

However, if you try it yourself, you’ll find that when updating the phone book, no entries will show up.
This is because Yealink’s own documentation is faulty.

In reality, the phone book needs to look like this:

<?xml version="1.0" encoding="UTF-8"?>
<YealinkIPPhoneDirectory>
    <Title>PurplePBX</Title>
    <DirectoryEntry>
        <Name>Jae (desk)</Name>
        <Telephone>1911</Telephone>
    </DirectoryEntry>
</YealinkIPPhoneDirectory>Code language: HTML, XML (xml)

Replace the MenuItem by DirectoryEntry, Prompt by Name and URI by Telephone and you get a working directory.

Thank you, Yealink, for making confusing documentation, while completely removing old (and new) firmware downloads from your support site.

Porting Resonite’s Headless Server Software to ARM

I promised it a few months ago, and here it is: ARM support for the Resonite Headless Server Software is now generally available.
So, what took so long in doing the port? FrooxEngine itself runs perfectly on ARM CPUs, the issue arising when trying to hit native libraries.
Resonite itself relies on about 250 external dependencies, most of which being Open-Source, some of them even shipping ARM support natively.

The issue remained in the 12 libraries left not shipping or supporting ARM, namely, FreeImage, Opus, crunch, Assimp, MSDFGen, Rnnoise, Brotli, Compressonator, SoundPipe, FreeType, SteamAudio and SteamWorks.NET.

Most of those libraries were trivial to build to ARM, and with a streak of luck, GitHub made ARM runners available right about the time I started working on this issue.
Before that, you needed to cross-compile everything or spin up some kind of QEMU VM for it. With the new runners, it became as easy as defining an OS array and using it like so (partial snippets):

# Define matrix of OS versions to use
strategy:
      matrix:
        osver: [ubuntu-latest, ubuntu-24.04-arm]

# Use it
runs-on: ${{ matrix.osver }}

# Set artifact name depending on platform
      - name: Set dist name
        run: |
          if ${{ matrix.osver == 'ubuntu-24.04-arm' }}; then
            echo "distname=arm-dist" >> "$GITHUB_ENV"
          else
            echo "distname=linux-dist" >> "$GITHUB_ENV"
          fi

# Upload artifact with right name
      - uses: actions/upload-artifact@v4
        with:
          path: Dist/
          name: ${{ env.distname }}Code language: PHP (php)

Yup, it’s that easy. Most of the libraries were trivial, building a C or C++ program for ARM on GitHub Actions basically equates to running the same commands on the ARM runner.

All this work was also the opportunity to clean up some files shipped in the FrooxEngine repository by bundling libraries directly into NuGet packages instead. Usually, those native libraries end up in runtimes/linux-arm64/native.

Now, why did it take so long to put this together, since it sounds so trivial? Well, that question can be answered with a single name: Steamworks.
We use Steam integrations (as Resonite is published on Steam), so of course, we do need to ship the Steam library, which at the time had no support for ARM64, completely crashing the type computing mechanism in FrooxEngine.

Fast-forward to November, Valve announces their headset, the Steam Frame. I didn’t care much for the headset itself, only that with it being ARM, it meant that we would finally get official ARM support for a bunch of stuff from Valve. A week or two later, they finally delivered, updating the Steamworks API library to support officially ARM.
At first updating the library itself proved difficult as for some reason, Steamworks.NET builds every single architecture separately. Luckily, a PR opened on the official repository made quick work of it, and provided proper ARM builds.

Early testing of the headless was done on the Oracle Cloud Free tier ARM machine. With Steamworks patched, I finally watched the headless run flawlessly on that machine, the only error remaining being the Discord social SDK complaining about the architecture. Luckily, Discord isn’t as critical and can be ignored.

All in all, this was a fun issue to tackle, and it opens the door for more ARM stuff in the future. Maybe a native ARM build for the Steam Frame, or a mobile build? Who knows?

In any case, time for you all to finally put that Raspberry Pi you’ve had on your shelf for the past 5 years to good use again.

A small reminder that you need at least the Discoverer plan to get access to the Headless Server Software; however, any support of Resonite is greatly appreciated.
If you wish to support us, you can subscribe within the account website.

Special thanks to the people & creatures that made this possible through research, direct contributions, or testing:

  • Cyro
  • AdamK2003
  • Orion Moonclaw
  • Gamethecupdog
Jae 2012-2025, CC BY-SA 4.0 unless stated otherwise.