Skip to content

Commit

Permalink
Merge pull request #403 from zomfg/feature/plugin-verbosity
Browse files Browse the repository at this point in the history
plugins fix/logs
  • Loading branch information
psieg authored Oct 31, 2020
2 parents 925d5f6 + b66762c commit 493c763
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 48 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Lightpack project with Prismatik flavour
&nbsp;&nbsp;[Short Description](#lightpack-project-with-prismatik-flavour) <br />
&nbsp;&nbsp;[Main Features](#main-features) <br />
&nbsp;&nbsp;[Supported Devices and Protocols](#supported-devices-and-protocols) <br />
&nbsp;&nbsp;[Making Plugins](#making-plugins) <br />
&nbsp;&nbsp;[Useful URLs](#useful-urls) <br />
&nbsp;&nbsp;[Build Prismatik with Windows](#prismatik-build-instructions-for-windows) <br />
&nbsp;&nbsp;[Build with Linux](#build-instructions-for-linux) <br />
Expand Down Expand Up @@ -68,6 +69,11 @@ handle other devices with Prismatik such as Adalight, Ardulight, or even Alienwa
* ESP8266/ESP32 ([WLED](https://github.com/Aircoookie/WLED) firmware highly recommended)


##### Making Plugins:
* [template](Software/res/plugin-template.ini)
* [code samples](Software/apiexamples)


##### Useful URLs:
* [Project mothership](https://github.com/psieg/Lightpack/)
* [Original project mothership](https://github.com/woodenshark/Lightpack/)
Expand Down
1 change: 1 addition & 0 deletions Software/res/LightpackResources.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@
<file>icons/Prismatik-pixmap.png</file>
<file>text/cast.html</file>
<file>icons/persist.png</file>
<file>plugin-template.ini</file>
</qresource>
</RCC>
80 changes: 80 additions & 0 deletions Software/res/plugin-template.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# This a template for Prismatik plugin configuration
# How to make a plugin:
# - create a folder in Prismatik/Plugins (in your user's home dir), for ex "myplugin"
# - copy this INI file into that folder with the same name as the folder, for ex "myplugin.ini"
# Prismatik will do this automatically on launch (or when you reload the plugin list from the UI)
# - edit the INI file to suit your needs
# - to make your code interact with Prismatik enable the web server and checkout Software/apiexamples
# - plugin output will be merged with Prismatik logs, make sure to enable those if needed

# [Main] is a required section
[Main]

# this will be displayed in the Plugins tab, defaults to plugin folder name
;Name=My custom plugin
Name=

# command to make your plugin run, REQUIRED
# - the process will start in plugin's folder so your scripts will be local
# - system env vars are passed to the process
# - if you need external/system commands (interpreters / shell...)
# either use full path
# Execute=/bin/sh myplugin.sh
# or make sure it's in the PATH env var
# Execute=node myplugin.js
# shebangs should work too
# - this can be pretty much any executable and not related to Prismatik
# it could trigger something on the system when Prismatik launches
# - AVOID SPACES IN PATHS AND NAMES
# this won't work
# Execute=C:/Program Files/python.exe "my custom plugin.py"
# if spaces in file names are required, wrap the command in a script
# - on WINDOWS use forward slash "/" as file separator
# Execute=D:/Some/Path/curl.exe -s http://example.com/api/foo
Execute=

# platform specific command if needed, overrides Execute= on a given platform
# REQUIRED if Execute= is not used
# use forward slash "/" as file separator on WINDOWS
;ExecuteOnWindows=python.exe myscript.py
;ExecuteOnOSX=/some/path/sh myscript.sh
;ExecuteOnNix=myscript.sh


# these will be displayed in Plugins tab

# icon file (bmp, gif, jpg, png) within your plugin folder, defaults to a built-in icon
;Icon=myicon.png
Icon=
Author=
# version can be a number/string/hash/date...
Version=0.1

# description can be plain text
;Description=My plugin that does this and that
# or basic HTML, see https://doc.qt.io/qt-5/richtext-html-subset.html
Description="
<html>
<head>
<style>
p {
font-weight:bold;
color:red;
}
</style>
</head>
<body>
<p>Make sure to update the INI file in the plugin folder</p>
</body>
</html>
"




# here you can define any custom sections and settings to use in your plugin
;[MyCustomSettings]
;foo=bar

;[SomeOtherSection]
;port=6969
133 changes: 93 additions & 40 deletions Software/src/Plugin.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#include "Plugin.hpp"
#include <QSettings>
#include <QIcon>
#include <QFileInfo>
#include <QFile>
#include <QDir>
#include <QImageReader>
#include "Settings.hpp"
#include "../common/defs.h"

Expand All @@ -23,42 +23,62 @@ Plugin::Plugin(QString name, QString path, QObject *parent) :
_pathPlugin = path;
QDir pluginPath(_pathPlugin);

QString fileName = path+"/"+name+".ini";
QSettings settings( fileName, QSettings::IniFormat );
settings.beginGroup("Main");
this->_name = settings.value( "Name", "Error").toString();
if (settings.contains(kOsSpecificExecuteKey)) {
this->_exec = settings.value( kOsSpecificExecuteKey, "").toString();
} else {
this->_exec = settings.value( "Execute", "").toString();
const QString fileName(path+"/"+name+".ini");
if (!QFile::exists(fileName)) {
qWarning() << Q_FUNC_INFO << name << fileName << "does not exist, generating defaults, make sure to edit the file!";
if (!QFile::copy(":/plugin-template.ini", fileName))
qWarning() << Q_FUNC_INFO << name << "failed to generate" << fileName;
else if (!QFile::setPermissions(fileName, QFile::permissions(fileName) | QFileDevice::WriteOwner))
qWarning() << Q_FUNC_INFO << name << "could not set write permissions to" << fileName;
}
this->_guid = settings.value( "Guid", "").toString();
this->_author = settings.value( "Author", "").toString();
this->_description = settings.value( "Description", "").toString();
this->_version = settings.value( "Version", "").toString();
this->_icon = pluginPath.absoluteFilePath(settings.value( "Icon", "").toString());
QSettings settings(fileName, QSettings::IniFormat);
settings.beginGroup("Main");
_name = settings.value("Name", "").toString();
if (_name.isEmpty())
_name = name;

if (settings.contains(kOsSpecificExecuteKey))
_arguments = settings.value(kOsSpecificExecuteKey, "").toString().split(' ');
else
_arguments = settings.value("Execute", "").toString().split(' ');

if (!_arguments.isEmpty())
_exec = _arguments.takeFirst();

if (_exec.isEmpty())
qWarning() << Q_FUNC_INFO << name << "no executable, check" << fileName;

_guid = settings.value("Guid", "").toString();
_author = settings.value("Author", "").toString();
_description = settings.value("Description", "").toString();
_version = settings.value("Version", "").toString();

const QString iconName = settings.value("Icon", "").toString();
const QString iconPath = pluginPath.absoluteFilePath(iconName);
if (!iconName.isEmpty() && QFile::exists(iconPath) && !QImageReader::imageFormat(iconPath).isEmpty())
_icon = QIcon(iconPath);
if (_icon.isNull())
_icon = QIcon(":/icons/plugins.png");

settings.endGroup();

process = new QProcess(this);

}

Plugin::~Plugin()
{
Stop();
}

QString Plugin::Name() const
{
QString Plugin::Name() const {
return _name;
}

QString Plugin::Guid() const
{
QString Plugin::Guid() const {
return _guid;
}

QString Plugin::Author() const {
QString Plugin::Author() const {
return _author;
}

Expand All @@ -71,54 +91,56 @@ QString Plugin::Version() const {
}


QIcon Plugin::Icon() const {
QFileInfo f(_icon);
if (f.exists())
return QIcon(_icon);
return QIcon(":/icons/plugins.png");
QIcon Plugin::Icon() const {
return _icon;
}


int Plugin::getPriority() const {
QString key = this->_name+"/Priority";
const QString key = _name+"/Priority";
return Settings::valueMain(key).toInt();
}

void Plugin::setPriority(int priority) {
QString key = this->_name+"/Priority";
const QString key = _name+"/Priority";
Settings::setValueMain(key,priority);
}

bool Plugin::isEnabled() const {
QString key = this->_name+"/Enable";
const QString key = _name+"/Enable";
return Settings::valueMain(key).toBool();
}

void Plugin::setEnabled(bool enable){
DEBUG_LOW_LEVEL << Q_FUNC_INFO << enable;
QString key = this->_name+"/Enable";
void Plugin::setEnabled(bool enable) {
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name << enable;
const QString key = _name+"/Enable";
Settings::setValueMain(key,enable);
if (!enable) this->Stop();
if (enable) this->Start();
if (!enable) Stop();
if (enable) Start();
}


void Plugin::Start()
{
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _exec;

//QStringList arguments;
//arguments << "-style" << "fusion";
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name << "starting" << _exec << _arguments;

QDir dir(_pathPlugin);
QDir::setCurrent(dir.absolutePath());

process->disconnect();
connect(process, SIGNAL(stateChanged(QProcess::ProcessState)), this, SIGNAL(stateChanged(QProcess::ProcessState)));

connect(process, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(stateChanged(QProcess::ProcessState)));
connect(process, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(errorOccurred(QProcess::ProcessError)));

connect(process, SIGNAL(started()), this, SLOT(started()));
connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));

connect(process, SIGNAL(readyReadStandardError()), this, SLOT(readyReadStandardError()));
connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(readyReadStandardOutput()));

process->setEnvironment(QProcess::systemEnvironment());
// process->setProcessChannelMode(QProcess::ForwardedChannels);
process->setProgram(_exec);
process->setArguments(_arguments);
process->start();
}

Expand All @@ -132,3 +154,34 @@ QProcess::ProcessState Plugin::state() const
return process->state();
}

// QProcess slots
void Plugin::stateChanged(QProcess::ProcessState newState)
{
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name << newState;
emit pluginStateChanged(newState);
}

void Plugin::errorOccurred(QProcess::ProcessError error)
{
qWarning() << Q_FUNC_INFO << _name << error << _exec << _arguments;
}

void Plugin::started()
{
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name;
}

void Plugin::finished(int exitCode, QProcess::ExitStatus exitStatus)
{
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name << "exitCode=" << exitCode << exitStatus;
}

void Plugin::readyReadStandardError()
{
qWarning() << Q_FUNC_INFO << _name << process->readAllStandardError();
}

void Plugin::readyReadStandardOutput()
{
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name << process->readAllStandardOutput();
}
18 changes: 11 additions & 7 deletions Software/src/Plugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <QObject>
#include <QProcess>
#include <QIcon>
#include "debug.h"

class Plugin : public QObject
Expand Down Expand Up @@ -30,24 +31,27 @@ class Plugin : public QObject


signals:
void pluginStateChanged(QProcess::ProcessState newState);

void stateChanged(QProcess::ProcessState);

public slots:

void stateChanged(QProcess::ProcessState newState);
void errorOccurred(QProcess::ProcessError error);
void started();
void finished(int exitCode, QProcess::ExitStatus exitStatus);
void readyReadStandardError();
void readyReadStandardOutput();

private:

QString _guid;
QString _name;
QString _description;
QString _author;
QString _version;
QString _icon;
QIcon _icon;
QString _exec;
QString _arguments;
QStringList _arguments;
QString _pathPlugin;
QProcess *process;

};


2 changes: 1 addition & 1 deletion Software/src/PluginsManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ void PluginsManager::StartPlugins()
p->disconnect();
if (p->isEnabled())
p->Start();
connect(p, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(onPluginStateChangedHandler()));
connect(p, SIGNAL(pluginStateChanged(QProcess::ProcessState)), this, SLOT(onPluginStateChangedHandler()));
}

}
Expand Down

0 comments on commit 493c763

Please sign in to comment.