RoonAmpSwitcher

Hi. I have a little project, basically, to detect when a device starts playing and send a 12V trigger to an amp. The device is a raspberry pi and from what I’ve read there are 2 ways to do this. First is to monitor pin 40 on the RPi header which goes high when playing music and the second is Roon Trigger. The second option option interests me more and that is what I’m trying to implement. Is this a good place to post questions, as I’m fairly new to this and have many questions :slight_smile:

Peter,
Ilike to integrate it in rooDial ( rooDial a Wireless Volume Knob for Roon with Microsoft Surface Dial - Audio Products - Roon Labs Community)

At the moment I can set a Roon enabled device to standby. My idea was to offer an option to turn on- and off the USB power of the PI. So you can connect a cable to the USB 5V power and trigger the amp with it. Most amps are fine with 5v instead of 12V.

I already proposed it here rooDial a Wireless Volume Knob for Roon with Microsoft Surface Dial - Audio Products - Roon Labs Community but there was not so much feedback on my idea so I did not implement it yet.

Probably over complex for what you need, but Home Assistant with the Roon Integration can control a wide variety of IOT switches.

At one point I had a 12v transformer plugged into an IOT mains switch (I’ve used WeMo and Z-wave, but there are many) which triggered a dac.

You’d need a dedicated Pi to run Home Assistant.

On the positive side you may find you do other things with Roon and Home Assistant (for example I have a wireless Philips Hue switch in my shower room with buttons that control volume, and play my favourite radio station). I use a IR blasters, zwave switches of various kinds, and led lights, all controlled by roon zones starting and stopping playing.

You can find out more about home assistant here:-

Thanks for the suggestions but i’ve now got the “bit between the teeth”
I’m trying to run the roon code on a windows laptop. I’ve installed node and git and am trying to run the following code in powershell:-

“use strict”;

// add timestamps in front of log messages

require(‘console-stamp’)(console, {

  • label: false,*
  • labelPrefix: “”,*
  • labelSuffix: “”,*
  • datePrefix: “”,*
  • dateSuffix: “”*
    });

const triggerHost = “192.168.0.127”
const triggerPort = 9090

const turnOnPause = 35 * 1000 // 35 seconds
const zoneName = “Bedroom”

let RoonApi = require(“node-roon-api”);
let RoonApiStatus = require(“node-roon-api-status”);
let RoonApiTransport = require(“node-roon-api-transport”);
let ontime = require(‘ontime’)
let net = require(‘net’);

let trigger_state = “UNKNOWN”
let last_trigger_write = new Date();

let roon = new RoonApi({

  • extension_id: ‘com.superfell.roon.amp’,*

  • display_name: “Roon Power Amp Switcher”,*

  • display_version: “1.0.1”,*

  • publisher: ‘Simon Fell’,*

  • email: ‘fellforce@.gmail.com’,*

  • website: ‘superfell (Simon Fell) · GitHub’,*

  • log_level: ‘none’,*

  • core_paired: function(core) {*

  •        let transport = core.services.RoonApiTransport;*
    
  •        let tracker = new_tracker(core, transport);*
    
  •        transport.subscribe_zones(tracker.zone_event);*
    
  •   },*
    
  • core_unpaired: function(core) {*
    
  •        console.log("-", "LOST");*
    
  •    }*
    

});

// get current state at startup, ensure we have the state before we start
// processing events from Roon.
amp_trigger_msg(‘STAT’, function(data) {

  • trigger_callback(data);*
  • roon.start_discovery();*
    })

let svc_status = new RoonApiStatus(roon);

roon.init_services({

  • required_services: [ RoonApiTransport ],*
  • provided_services: [ svc_status ],*
    });

svc_status.set_status(“All is good”, false);

function new_tracker(core, transport) {

  • let t = {*

  •    zone: null,*
    
  •    zone_id: "",*
    
  •    last_state : "",*
    
  • }*

  • t.on_state_changed = function() {*

  •    console.log("zone state now " + t.last_state)*
    
  •    if (!((t.last_state != "playing") && (t.last_state != "loading"))) {*
    
  •        if (trigger_state == "OFF") {*
    
  •            transport.control(t.zone, "pause", (x) => setTimeout(() => transport.control(t.zone, "play"), turnOnPause))*
    
  •        }*
    
  •        set_trigger(true)*
    
  •    }*
    
  • }*

  • t.zone_event = function(cmd, data) {*

  •    if (cmd == "Subscribed") {*
    
  •        data.zones.forEach( z => { *
    
  •            if (z.display_name == zoneName) {*
    
  •                t.zone = z;*
    
  •                t.zone_id = z.zone_id;*
    
  •                t.last_state = z.state;*
    
  •            }*
    
  •        })*
    
  •        console.log("zones", t.zone_id, t.last_state);*
    
  •        t.on_state_changed();*
    
  •    } else if (cmd == "Changed") {*
    
  •        if ("zones_changed" in data) {*
    
  •            data.zones_changed.forEach( z => {*
    
  •                if ((z.zone_id == t.zone_id) && (z.state != t.last_state)) {*
    
  •                    t.last_state = z.state;*
    
  •                    t.on_state_changed();*
    
  •                }*
    
  •            })*
    
  •        } else if ("zones_seek_changed" in data) {*
    
  •            // skip*
    
  •        } else {*
    
  •            console.log(cmd, data);*
    
  •        }*
    
  •    }*
    
  • }*

  • return t*
    }

// time is in UTC
ontime({

  • cycle: ‘07:10:00’*
    }, function (ot) {
  • console.log(‘Scheduled: turning amp off’)*
  • set_trigger(false)*
  • ot.done()*
  • return*
    })

// update the trigger state to the new state (true/false for on/off)
function set_trigger(newStateBool) {

  • let newState = newStateBool ? “ON” : “OFF”;*
  • if (newState == trigger_state) {*
  •    // If we think we're not going to make a change, and we've talked to the trigger*
    
  •    // recently, skip doing this*
    
  •    if ((new Date().getTime() - last_trigger_write.getTime()) < 10000) {*
    
  •        console.log("skipping no-op trigger change of: " + newState + ", current state is: " + trigger_state);*
    
  •        return;*
    
  •    }*
    
  • }*
  • amp_trigger_msg(newStateBool ? 'UON ': ‘UOFF’, trigger_callback);*
    }

const trigger_on = Buffer.from('ON ')

function trigger_callback(data) {

  • let new_state = data.compare(trigger_on, 0,4,0,4) == 0 ? “ON” : “OFF”*
  • console.log("Trigger state was " + trigger_state + " now " + new_state);*
  • trigger_state = new_state;*
  • svc_status.set_status("Amp Power: " + new_state, false);*
    }

// amp_trigger_msg is a helper that will send ‘msg’ to the esp2866 controlling the amp trigger singal
// and pass the response to the callback.
function amp_trigger_msg(msg, callback) {

  • console.log(“starting amp_trigger_msg:” + msg)*

  • last_trigger_write = new Date();*

  • var client = new net.Socket(); *

  • client.connect(triggerPort, triggerHost, function() {*

  •    // Write a message to the socket as soon as the client is connected, the server will receive it as message from the client *
    
  •    client.write(msg);*
    
  • });*

  • // Add a ‘data’ event handler for the client socket*

  • // data is what the server sent to this socket*

  • client.on(‘data’, function(data) {*

  •    console.log('TRIGGER SAID: ' + data.slice(0,4));*
    
  •    // Close the client socket completely*
    
  •    client.destroy();*
    
  •    callback(data)*
    
  • });*
    }

It runs with no errors but doesn’t show up in roon as an extension and does nothing.

The following code does show up as an extension:

var RoonApi = require(“node-roon-api”);

var roon = new RoonApi({

  • extension_id: ‘com.elvis.test’,*
  • display_name: “Elvis’s First Roon API Test”,*
  • display_version: “1.0.0”,*
  • publisher: ‘Elvis Presley’,*
  • email: ‘elvis@presley.com’,*
  • website: ‘https://github.com/elvispresley/roon-extension-test’*
    });

roon.init_services({});

//roon.start_discovery();

Obviously it also does nothing :slight_smile:

I have an Uno R3 and ethernet shield up and running with the following code:

#include <SPI.h>
#include <Ethernet.h>
#include <EthernetBonjour.h>

// replace the MAC address below by the MAC address printed on a sticker on the Arduino Shield 2
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
EthernetServer server(9090); //server port arduino server will use
EthernetClient client;
// TODO: Declare something depending on your application

void setup() {
Serial.begin(9600);

// initialize the Ethernet shield using DHCP:
Serial.println(“Obtaining an IP address using DHCP”);
if (Ethernet.begin(mac) == 0) {
Serial.println(“Failed to obtaining an IP address”);

// check for Ethernet hardware present
if (Ethernet.hardwareStatus() == EthernetNoHardware)
  Serial.println("Ethernet shield was not found");

// check for Ethernet cable
if (Ethernet.linkStatus() == LinkOFF)
  Serial.println("Ethernet cable is not connected.");

while (true);

}
server.begin();

// print out Arduino’s IP address, subnet mask, gateway’s IP address, and DNS server’s IP address
Serial.print("- Arduino’s IP address : ");
Serial.println(Ethernet.localIP());

Serial.print("- Gateway’s IP address : ");
Serial.println(Ethernet.gatewayIP());

Serial.print("- Network’s subnet mask : ");
Serial.println(Ethernet.subnetMask());

Serial.print("- DNS server’s IP address: ");
Serial.println(Ethernet.dnsServerIP());
EthernetBonjour.begin(“arduino”);
EthernetBonjour.run();

// TODO: initialize something depending on your application
}

void loop() {
// put your main code here, to run repeatedly:
// Check if a client has connected
EthernetClient client = server.available();
if (!client) {
return;
}
while (client.available() < 4) {
delay(5);
}
uint8_t buff[4];
int x = client.read(buff, 4);
client.flush();
client.stop();
}

There is still code missing from this but don’t think is should stop the js code running.

Any idea what i’m doing wrong?

Got this working. Just had to put a roon.start_discover(); statement before the amp_trigger_msg call. Now I need to figure out how to switch off after 20 mins of inactivity.

1 Like