Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugins fix/logs #403

Merged
merged 2 commits into from
Oct 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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