Plugin development, newbie questions

Hi, I hope this is the correct forum section for this.

I’ve recently started developing a volumio plugin, and have a couple of questions that I’ve been unable to answer myself through digging around. I have a development background, though not in really this kind.

Some background on the plugin… it’s intended to allow control of an Onkyo receiver that my volumio raspberry pi is connected to. Rather than having to turn the receiver on through it’s own app or manually, I wanted to create a plugin that does this automatically. I’ve had no problem getting this to work, but I’ve run into some issues with some of the more standard plugin features.

If it helps the code can be found here:

github.com/orderoftheflame/volu … yo_control

It’s incomplete… but it’s a proof of concept I’ll be adding to as and when I find time.

Whilst these may be the first of a few questions I end up having… the first two issues I’ve been unable to resolve are:

  1. When attempting to save on the config page, I get an error because the following file doesn’t exist:
/data/configuration/miscellanea/onkyo_control/config.json

The specific error is:

Mar 09 21:38:26 volumiozero volumio[4859]: info: ONKYO-CONTROL: saveConnectionConfig() data: {"autolocate":true,"receiverIP":"","receiverPort":"60128"} Mar 09 21:38:27 volumiozero volumio[4859]: fs.js:641 Mar 09 21:38:27 volumiozero volumio[4859]: return binding.open(pathModule._makeLong(path), stringToFlags(flags), mode); Mar 09 21:38:27 volumiozero volumio[4859]: ^ Mar 09 21:38:27 volumiozero volumio[4859]: Error: ENOENT: no such file or directory, open '/data/configuration/miscellanea/onkyo_control/config.json' Mar 09 21:38:27 volumiozero volumio[4859]: at Error (native) Mar 09 21:38:27 volumiozero volumio[4859]: at Object.fs.openSync (fs.js:641:18) Mar 09 21:38:27 volumiozero volumio[4859]: at Object.fs.writeFileSync (fs.js:1347:33) Mar 09 21:38:27 volumiozero volumio[4859]: at Object.writeFileSync (/data/plugins/miscellanea/onkyo_control/node_modules/jsonfile/index.js:115:13) Mar 09 21:38:27 volumiozero volumio[4859]: at Config.save (/data/plugins/miscellanea/onkyo_control/node_modules/v-conf/index.js:209:11) Mar 09 21:38:27 volumiozero volumio[4859]: at Timeout._onTimeout (/data/plugins/miscellanea/onkyo_control/node_modules/v-conf/index.js:184:18) Mar 09 21:38:27 volumiozero volumio[4859]: at ontimeout (timers.js:386:14) Mar 09 21:38:27 volumiozero volumio[4859]: at tryOnTimeout (timers.js:250:5) Mar 09 21:38:27 volumiozero volumio[4859]: at Timer.listOnTimeout (timers.js:214:5)

Looking at other plugins, I can’t find any reference to this file being created in the code. While I could create it manually, I’d prefer my plugin to be done properly, and while I could create it with the correct permissions in the install.sh file… I get the feeling that this isn’t the correct way this should happen.

How should the file be created? By me somewhere in code? Or automatically, but I’m missing something?

  1. Can toast messages be internationalised? The following code produces a toast, but the text stays the same? Other plugins seem to have these strings hard coded, which means they can’t be internationalised. Is there any existing way to replace these strings so far?

e.g:

self.commandRouter.pushToastMessage('success', "TRANSLATE.SETTINGS_SAVED", "TRANSLATE.SETTINGS_SAVED_ACTION");

Many thanks in advance for any help, I’m quite new to this, but really enjoying working with Volumio after using it for a few years. :slight_smile:

Hi, for the configuration , I don’t see anything wrong. The conf files are created upon installation of plugin.
How did you initialize it?
With the plugin helper?

As for the internazionalization, you can see a clever example in this plugin:
github.com/volumio/volumio-plug … o/index.js

Keep up the good work and let me know if you need more help!

Thanks for getting back to me. :slight_smile: That’s perfect for the toast messages!

I managed to figure out my config problem after comparing with some of the other plugins. It looks like I was missing the following function:

onkyoControl.prototype.getConfigurationFiles = function() { return ['config.json']; }

Adding this seemed to fix the problem and the “/data/configuration/miscellanea/onkyo_control/config.json” is now created fine. :smiley:

I did create the plugin using the helper with “volumio plugin init”, and I’ve just created another test plugin. It doesn’t look like this method exists in the default template index.js file, though it is mentioned in the docs here: volumio.github.io/docs/Plugin_S … ex.js.html

I’m not sure how the plugin helper creates this default file exactly. Does it just copy from the example_plugin folder in volumio_plugins? It looks like that function is missing from there as well.

github.com/volumio/volumio-plug … n/index.js

I’m not sure if this function is deliberately not there by default, or if it’s a bug though. :slight_smile: I could just be misunderstanding, though it does seem that all the plugins that I checked have it. If it helps, the index.js file that I generated with “volumio plugin init” is:

[code]‘use strict’;

var libQ = require(‘kew’);
var fs=require(‘fs-extra’);
var config = new (require(‘v-conf’))();
var exec = require(‘child_process’).exec;
var execSync = require(‘child_process’).execSync;

module.exports = initTest;
function initTest(context) {
var self = this;

    this.context = context;
    this.commandRouter = this.context.coreCommand;
    this.logger = this.context.logger;
    this.configManager = this.context.configManager;

}

initTest.prototype.onVolumioStart = function()
{
var self = this;
var configFile=this.commandRouter.pluginManager.getConfigurationFile(this.context,‘config.json’);
this.config = new (require(‘v-conf’))();
this.config.loadFile(configFile);

return libQ.resolve();

}

initTest.prototype.onStart = function() {
var self = this;
var defer=libQ.defer();

    // Once the Plugin has successfull started resolve the promise
    defer.resolve();

return defer.promise;

};

initTest.prototype.onStop = function() {
var self = this;
var defer=libQ.defer();

// Once the Plugin has successfull stopped resolve the promise
defer.resolve();

return libQ.resolve();

};

initTest.prototype.onRestart = function() {
var self = this;
// Optional, use if you need it
};

// Configuration Methods -----------------------------------------------------------------------------

initTest.prototype.getUIConfig = function() {
var defer = libQ.defer();
var self = this;

var lang_code = this.commandRouter.sharedVars.get('language_code');

self.commandRouter.i18nJson(__dirname+'/i18n/strings_'+lang_code+'.json',
    __dirname+'/i18n/strings_en.json',
    __dirname + '/UIConfig.json')
    .then(function(uiconf)
    {


        defer.resolve(uiconf);
    })
    .fail(function()
    {
        defer.reject(new Error());
    });

return defer.promise;

};

initTest.prototype.setUIConfig = function(data) {
var self = this;
//Perform your installation tasks here
};

initTest.prototype.getConf = function(varName) {
var self = this;
//Perform your installation tasks here
};

initTest.prototype.setConf = function(varName, varValue) {
var self = this;
//Perform your installation tasks here
};

// Playback Controls ---------------------------------------------------------------------------------------
// If your plugin is not a music_sevice don’t use this part and delete it

initTest.prototype.addToBrowseSources = function () {

    // Use this function to add your music service plugin to music sources
//var data = {name: 'Spotify', uri: 'spotify',plugin_type:'music_service',plugin_name:'spop'};
this.commandRouter.volumioAddToBrowseSources(data);

};

initTest.prototype.handleBrowseUri = function (curUri) {
var self = this;

//self.commandRouter.logger.info(curUri);
var response;


return response;

};

// Define a method to clear, add, and play an array of tracks
initTest.prototype.clearAddPlayTrack = function(track) {
var self = this;
self.commandRouter.pushConsoleMessage(’[’ + Date.now() + '] ’ + ‘initTest::clearAddPlayTrack’);

    self.commandRouter.logger.info(JSON.stringify(track));

    return self.sendSpopCommand('uplay', [track.uri]);

};

initTest.prototype.seek = function (timepos) {
this.commandRouter.pushConsoleMessage(’[’ + Date.now() + '] ’ + 'initTest::seek to ’ + timepos);

return this.sendSpopCommand('seek '+timepos, []);

};

// Stop
initTest.prototype.stop = function() {
var self = this;
self.commandRouter.pushConsoleMessage(’[’ + Date.now() + '] ’ + ‘initTest::stop’);

};

// Spop pause
initTest.prototype.pause = function() {
var self = this;
self.commandRouter.pushConsoleMessage(’[’ + Date.now() + '] ’ + ‘initTest::pause’);

};

// Get state
initTest.prototype.getState = function() {
var self = this;
self.commandRouter.pushConsoleMessage(’[’ + Date.now() + '] ’ + ‘initTest::getState’);

};

//Parse state
initTest.prototype.parseState = function(sState) {
var self = this;
self.commandRouter.pushConsoleMessage(’[’ + Date.now() + '] ’ + ‘initTest::parseState’);

    //Use this method to parse the state and eventually send it with the following function

};

// Announce updated State
initTest.prototype.pushState = function(state) {
var self = this;
self.commandRouter.pushConsoleMessage(’[’ + Date.now() + '] ’ + ‘initTest::pushState’);

    return self.commandRouter.servicePushState(state, self.servicename);

};

initTest.prototype.explodeUri = function(uri) {
var self = this;
var defer=libQ.defer();

    // Mandatory: retrieve all info for a given URI

    return defer.promise;

};

initTest.prototype.getAlbumArt = function (data, path) {

    var artist, album;

    if (data != undefined && data.path != undefined) {
            path = data.path;
    }

    var web;

    if (data != undefined && data.artist != undefined) {
            artist = data.artist;
            if (data.album != undefined)
                    album = data.album;
            else album = data.artist;

            web = '?web=' + nodetools.urlEncode(artist) + '/' + nodetools.urlEncode(album) + '/large'
    }

    var url = '/albumart';

    if (web != undefined)
            url = url + web;

    if (web != undefined && path != undefined)
            url = url + '&';
    else if (path != undefined)
            url = url + '?';

    if (path != undefined)
            url = url + 'path=' + nodetools.urlEncode(path);

    return url;

};

initTest.prototype.search = function (query) {
var self=this;
var defer=libQ.defer();

    // Mandatory, search. You can divide the search in sections using following functions

    return defer.promise;

};

initTest.prototype._searchArtists = function (results) {

};

initTest.prototype._searchAlbums = function (results) {

};

initTest.prototype._searchPlaylists = function (results) {

};

initTest.prototype._searchTracks = function (results) {

};
[/code]

Many thanks again for helping!

orderoftheflame ,
Thanks! It’s been over a month since your post and the “getConfigurationFiles” function is still missing when generating a base plug-in with “volumio plugin init”. Your post was of great help.

Randy

orderoftheflame,
Did some more digging. “getConfigurationFiles” function isn’t needed. The config file is read in the “onVolumioStart” function.

Volumio created their own module for reading config files, “v-conf”. See https://www.npmjs.com/package/v-conf

Running Volumio in DEV mode, “volumio dev” from the command line, lets you see “console.log()” output. With your post and some debugging lines I finally figure it out.

Thanks!
Randy

Thanks very much. :slight_smile:

I’ve not had a lot of time to finish the bits I want to on my plugin off, when I do I’ll try to get it merged in if anyone else with an Onkyo receiver wants to use it.