Read json value from Arcam AVR web interface

Hi There,

Have spent a chunk of time today googling around and trying things, @Troon seemed to be fairly knowledgable on this subject and helped others with a similar issue.

The Arcam AVR41 device hosts a web interface, 1 of the pages contains a value named “Incoming format” which I would like to create a sensor for and have it shown on a dashboard, picture of web page with value below:

Using the Dev Tools it appears when going to the “General setup” tab on the main page of the AV41 there is a HTML and .js url that is called, I recall @Troon explaining that the HTML is called first, then .js is called to retrieve the values and feeds them back into the HTML. The two URL’s it calls are below

http://192.168.0.71/setup/general-setup.html?_=1737077536545
http://192.168.0.71/setup/public/js/general-setup.js?_=1737077536546

I know the “arcam_fmj” integration exists however that is coded to only read the “Decode Mode” which is not the information I am after, I wonder if @elupus may be open to adding in the ability to read the “Incoming format” value ?

Both HTML/.js content below, I did manage to create a scrape sensor however that only calls/reads the static html which has a dummy value inside it which is simply “—”, would it be possible to get any help with this?

<form class="pt-5">
  <div class="form-group row">
    <label for="source-input" class="col-3 col-form-label">Source Input</label>
    <div class="col-8">
      <input id="source-input" name="source-input" type="text" class="form-control here" value="CD" disabled>
    </div>
  </div>
  <div class="form-group row">
    <label for="incoming-format" class="col-3 col-form-label">Incoming format</label>
    <div class="col-8">
      <input id="incoming-format" name="incoming-format" type="text" class="form-control here" value="---" disabled>
    </div>
  </div>
  <div class="form-group row">
    <label for="incoming-sample-rate" class="col-3 col-form-label">Incoming sample rate</label>
    <div class="col-8">
      <input id="incoming-sample-rate" name="incoming-sample-rate" type="text" class="form-control here" value="---" disabled>
    </div>
  </div>
  <div class="form-group row">
    <label for="incoming-bitrate" class="col-3 col-form-label">Incoming bitrate</label>
    <div class="col-8">
      <input id="incoming-bitrate" name="incoming-bitrate" type="text" class="form-control here" value="---" disabled>
    </div>
  </div>
  <div class="form-group row">
    <label for="dialnorm" class="col-3 col-form-label">Dialnorm</label>
    <div class="col-8">
      <input id="dialnorm" name="dialnorm" type="text" class="form-control here" value="---" disabled>
    </div>
  </div>
  <div class="form-group row">
    <label for="incoming-video-resolution" class="col-3 col-form-label">Incoming video format</label>
    <div class="col-8">
      <input id="incoming-video-resolution" name="incoming-video-resolution" type="text" class="form-control here" value="---" disabled>
    </div>
  </div>
  <div class="form-group row">
    <label for="audio-compression" class="col-3 col-form-label">Audio compression</label>
    <div class="col-8">
      <select id="audio-compression" name="audio-compression" class="custom-select">
        <option value="0" selected>Off</option>
        <option value="1">Medium</option>
        <option value="2">High</option>
      </select>
    </div>
  </div>
  <div class="form-group row">
    <label for="balance" class="col-3 col-form-label">Balance</label>
    <div class="col-8">
      <div class="row">
        <div class="col">
          <input id="balance" name="balance" type="range" min="-6" max="6" step="1" class="custom-range form-control here d-none d-md-block" value="0">
          <div class="row h-100">
            <div class="col text-secondary d-none d-md-block">Left +6dB</div>
            <output name="balance" id="balance" class="col text-left text-md-center my-auto my-md-0">0dB</output>
            <div class="col text-right text-secondary d-none d-md-block">Right +6dB</div>
          </div>
        </div>
        <div class="col-auto">
          <button id="decrease-balance" aria-label="balance decrease" type="button" class="btn btn-secondary stepper" aria-label="Decrease Balance">
            <i class="fas fa-minus"></I>
          </button>
          <button id="increase-balance" aria-label="balance increase" type="button" class="btn btn-secondary stepper" aria-label="Increase Balance">
            <i class="fas fa-plus"></I>
          </button>
        </div>
      </div>
    </div>
  </div>
  <div class="form-group row">
    <label for="dts-dialogue-control" class="col-3 col-form-label">DTS Dialogue Control</label>
    <div class="col-8">
      <div class="row">
        <div class="col">
          <input id="dts-dialogue-control" name="dts-dialogue-control" type="range" min="0" max="6" step="1" class="custom-range form-control here d-none d-md-block" value="0">
          <div class="row h-100">
            <div class="col text-secondary d-none d-md-block">0dB</div>
            <output name="dts-dialogue-control" id="dts-dialogue-control" class="col text-left text-md-center my-auto my-md-0">0dB</output>
            <div class="col text-right text-secondary d-none d-md-block">+6dB</div>
          </div>
        </div>
        <div class="col-auto">
          <button id="decrease-dts-dialogue-control" aria-label="DTS dialogue decrease" type="button" class="btn btn-secondary stepper" aria-label="Decrease DTS Dialogue Control">
            <i class="fas fa-minus"></I>
          </button>
          <button id="increase-dts-dialogue-control" aria-label="DTS dialogue increase" type="button" class="btn btn-secondary stepper" aria-label="Increase DTS Dialogue Control">
            <i class="fas fa-plus"></I>
          </button>
        </div>
      </div>
    </div>
  </div>
  <div class="form-group row">
    <label for="maximum-volume" class="col-3 col-form-label">Maximum volume</label>
    <div class="col-8">
      <div class="row">
        <div class="col">
          <input id="maximum-volume" name="maximum-volume" type="range" min="20" max="99" step="1" class="custom-range form-control here d-none d-md-block" value="99">
          <div class="row h-100">
            <div class="col text-secondary d-none d-md-block">20</div>
            <output name="maximum-volume" id="maximum-volume" class="col text-left text-md-center my-auto my-md-0">99</output>
            <div class="col text-right text-secondary d-none d-md-block">99</div>
          </div>
        </div>
        <div class="col-auto">
          <button id="decrease-maximum-volume" aria-label="maximum volume decrease" type="button" class="btn btn-secondary stepper" aria-label="Decrease Maximum Volume">
            <i class="fas fa-minus"></I>
          </button>
          <button id="increase-maximum-volume" aria-label="maximum volume increase" type="button" class="btn btn-secondary stepper" aria-label="Increase Maximum Volume">
            <i class="fas fa-plus"></I>
          </button>
        </div>
      </div>
    </div>
  </div>
  <div class="form-group row">
    <label for="maximum-on-volume" class="col-3 col-form-label">Maximum On Volume</label>
    <div class="col-8">
      <div class="row">
        <div class="col">
          <input id="maximum-on-volume" name="maximum-on-volume" type="range" min="20" max="99" step="1" class="custom-range form-control here d-none d-md-block" value="50">
          <div class="row h-100">
            <div class="col text-secondary d-none d-md-block">20</div>
            <output name="maximum-on-volume" id="maximum-on-volume" class="col text-left text-md-center my-auto my-md-0">50</output>
            <div class="col text-right text-secondary d-none d-md-block">99</div>
          </div>
        </div>
        <div class="col-auto">
          <button id="decrease-maximum-on-volume" aria-label="maximum on volume decrease" type="button" class="btn btn-secondary stepper" aria-label="Decrease Maximum On Volume">
            <i class="fas fa-minus"></I>
          </button>
          <button id="increase-maximum-on-volume" aria-label="maximum on volume increase" type="button" class="btn btn-secondary stepper" aria-label="Increase Maximum On Volume">
            <i class="fas fa-plus"></I>
          </button>
        </div>
      </div>
    </div>
  </div>
  <div class="form-group row">
    <label for="display-on-time" class="col-3 col-form-label">Display on time</label>
    <div class="col-8">
      <select id="display-on-time" name="display-on-time" class="custom-select">
        <option value="0">5 seconds</option>
        <option value="1">10 seconds</option>
        <option value="2">30 seconds</option>
        <option value="3">1 minute</option>
        <option value="4" selected>Always On</option>
      </select>
    </div>
  </div>
  <div class="form-group row">
    <label for="control-options" class="col-3 col-form-label">Control options</label>
    <div class="col-8">
      <select id="control-options" name="control-options" class="custom-select" disabled>
        <option value="0">Off</option>
        <option value="1">RS232</option>
        <option value="2" selected>IP</option>
      </select>
    </div>
  </div>
  <div class="form-group row">
    <label for="power-on" class="col-3 col-form-label">Power on</label>
    <div class="col-8">
      <select id="power-on" name="power-on" class="custom-select">
        <option value="0" selected>Last State</option>
        <option value="1">Standby</option>
        <option value="2">On</option>
      </select>
    </div>
  </div>
  <div class="form-group row">
    <label for="language" class="col-3 col-form-label">Language</label>
    <div class="col-8">
      <select id="language" name="language" class="custom-select">
        <option value="0" selected>English</option>
        <option value="1">Francais</option>
        <option value="2">Deutsch</option>
        <option value="3">Español</option>
        <option value="4">Nederlands</option>
        <option value="5">Pусский</option>
        <option value="6">中文</option>
      </select>
    </div>
  </div>
</form>

The .js content is:

$(document).ready(function () {
  const maxVolId = "#maximum-volume";
  const maxOnVolId = "#maximum-on-volume";

  startup();

  function startup() {
    restorePreviousValues();
    $(maxVolId).on("input", maximumVolumeChanged);
    $(maxOnVolId).on("input", maximumOnVolumeChanged);

    window.socketMessageReceived = function(msg) {
      if (msg[2] == 0x1D) {
        window.webSocket.send(0x29, [0xF0]);
      } else if (msg[2] == 0x29) {
        updateValuesUsing(parseGeneralSetupBuffer(msg));
      } else if (msg[2] === 0x43) {
        updateAudioFormat(msg[5], msg[6]);
      } else if (msg[2] === 0x44) {
        updateSampleRate(msg[5]);
      }
    };

    window.webSocket.send(0x29, [0xF0]);
  }

  function updateAudioFormat(stream, channels) {
    const model = updateModel("incoming-format", Utils.audioFormat(stream, channels));
  }

  function updateSampleRate(rate) {
    const model = updateModel("incoming-sample-rate", Utils.parseSampleRate(rate));
    updateValuesUsing(model);
  }

  function parseGeneralSetupBuffer(buffer) {
    if (buffer.length != 38) {
      throw "Invalid buffer length: " + buffer.length;
    }

    if (!validateCommandCode(buffer, 0x29)) {
      throw "Invalid command code.";
    }

    var data = buffer.slice(5, buffer.length - 1);

    return {
      "source-input": Utils.asciiFromBuffer(data, { min: 0, max: 10 }),
      "incoming-format": Utils.audioFormat(data[10], data[11]),
      "incoming-sample-rate": Utils.parseSampleRate(data[12]),
      "incoming-bitrate": Utils.parseBitrate(data[13]),
      "dialnorm": Utils.parseDialNorm(data[14]),
      "incoming-video-resolution": Utils.parseIncomingVideoResolution(data.slice(17,21)),
      "audio-compression": data[23],
      "balance": Utils.parseSignedValue(data[24]),
      "dts-dialogue-control": data[25],
      "maximum-volume": data[26],
      "maximum-on-volume": data[27],
      "display-on-time": data[28],
      "control-options": data[29],
      "power-on": data[30],
      "language": data[31],
    };
  }

  window.encodeModel = function(model) {
    var data = new Array(32).fill(0);
    data[23] = model["audio-compression"];
    data[24] = Utils.toSignedValue(model["balance"]);
    data[25] = model["dts-dialogue-control"];
    data[26] = model["maximum-volume"];
    data[27] = model["maximum-on-volume"];
    data[28] = model["display-on-time"];
    data[29] = model["control-options"];
    data[30] = model["power-on"];
    data[31] = model["language"];
    return { code: 0x29, data: data };
  };

  function maximumVolumeChanged() {
    var maxVol = $(maxVolId).val();
    var maxOnVol = $(maxOnVolId).val();

    if (maxVol < maxOnVol) {
      $(maxOnVolId).val(maxVol);
    }
  }

  function maximumOnVolumeChanged() {
    var maxVol = $(maxVolId).val();
    var maxOnVol = $(maxOnVolId).val();

    if (maxOnVol > maxVol) {
      $(maxOnVolId).val(maxVol);
    }
  }

  window.shouldRespondToStepper = function(key, modifier) {
    if (key === "maximum-on-volume" && modifier === "increase") {
      return $(maxOnVolId).val() < $(maxVolId).val();
    }

    return true;
  };
});

Incoming format is fully known. If you provide a pull request to add a entity for that value im perfectly happy to review it.

Thank you for responding @elupus and appreciate you being happy to review it, I am unfamiliar with how to provide a pull request.

I have tried to go to the Home Assistant Arcam FMJ Receivers page and click on “View source on Github” I am taken directly to the arcam_fmj component files in homeassisant/core, then click on “Pull requests” and “New pull request” however unsure what to do after that as I am being asked to compare/review.

@elupus , any advice regarding what to do for a pull request? Googling around it appears a pull request is a review to merge a proposed set of changes, however I have no idea how to edit/write the code for the arcam_fmj plugin.

Not really. Either you have to try to learn python an how to add features to home assistant, or see it as a feature request and hope somebody has some time to pick it up and add the feature.

No guarantee I will have the time anytime soon.