Skip to content

Commit 493c763

Browse files
authored
Merge pull request #403 from zomfg/feature/plugin-verbosity
plugins fix/logs
2 parents 925d5f6 + b66762c commit 493c763

File tree

6 files changed

+192
-48
lines changed

6 files changed

+192
-48
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Lightpack project with Prismatik flavour
1414
&nbsp;&nbsp;[Short Description](#lightpack-project-with-prismatik-flavour) <br />
1515
&nbsp;&nbsp;[Main Features](#main-features) <br />
1616
&nbsp;&nbsp;[Supported Devices and Protocols](#supported-devices-and-protocols) <br />
17+
&nbsp;&nbsp;[Making Plugins](#making-plugins) <br />
1718
&nbsp;&nbsp;[Useful URLs](#useful-urls) <br />
1819
&nbsp;&nbsp;[Build Prismatik with Windows](#prismatik-build-instructions-for-windows) <br />
1920
&nbsp;&nbsp;[Build with Linux](#build-instructions-for-linux) <br />
@@ -68,6 +69,11 @@ handle other devices with Prismatik such as Adalight, Ardulight, or even Alienwa
6869
* ESP8266/ESP32 ([WLED](https://github.com/Aircoookie/WLED) firmware highly recommended)
6970

7071

72+
##### Making Plugins:
73+
* [template](Software/res/plugin-template.ini)
74+
* [code samples](Software/apiexamples)
75+
76+
7177
##### Useful URLs:
7278
* [Project mothership](https://github.com/psieg/Lightpack/)
7379
* [Original project mothership](https://github.com/woodenshark/Lightpack/)

Software/res/LightpackResources.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@
3939
<file>icons/Prismatik-pixmap.png</file>
4040
<file>text/cast.html</file>
4141
<file>icons/persist.png</file>
42+
<file>plugin-template.ini</file>
4243
</qresource>
4344
</RCC>

Software/res/plugin-template.ini

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# This a template for Prismatik plugin configuration
2+
# How to make a plugin:
3+
# - create a folder in Prismatik/Plugins (in your user's home dir), for ex "myplugin"
4+
# - copy this INI file into that folder with the same name as the folder, for ex "myplugin.ini"
5+
# Prismatik will do this automatically on launch (or when you reload the plugin list from the UI)
6+
# - edit the INI file to suit your needs
7+
# - to make your code interact with Prismatik enable the web server and checkout Software/apiexamples
8+
# - plugin output will be merged with Prismatik logs, make sure to enable those if needed
9+
10+
# [Main] is a required section
11+
[Main]
12+
13+
# this will be displayed in the Plugins tab, defaults to plugin folder name
14+
;Name=My custom plugin
15+
Name=
16+
17+
# command to make your plugin run, REQUIRED
18+
# - the process will start in plugin's folder so your scripts will be local
19+
# - system env vars are passed to the process
20+
# - if you need external/system commands (interpreters / shell...)
21+
# either use full path
22+
# Execute=/bin/sh myplugin.sh
23+
# or make sure it's in the PATH env var
24+
# Execute=node myplugin.js
25+
# shebangs should work too
26+
# - this can be pretty much any executable and not related to Prismatik
27+
# it could trigger something on the system when Prismatik launches
28+
# - AVOID SPACES IN PATHS AND NAMES
29+
# this won't work
30+
# Execute=C:/Program Files/python.exe "my custom plugin.py"
31+
# if spaces in file names are required, wrap the command in a script
32+
# - on WINDOWS use forward slash "/" as file separator
33+
# Execute=D:/Some/Path/curl.exe -s http://example.com/api/foo
34+
Execute=
35+
36+
# platform specific command if needed, overrides Execute= on a given platform
37+
# REQUIRED if Execute= is not used
38+
# use forward slash "/" as file separator on WINDOWS
39+
;ExecuteOnWindows=python.exe myscript.py
40+
;ExecuteOnOSX=/some/path/sh myscript.sh
41+
;ExecuteOnNix=myscript.sh
42+
43+
44+
# these will be displayed in Plugins tab
45+
46+
# icon file (bmp, gif, jpg, png) within your plugin folder, defaults to a built-in icon
47+
;Icon=myicon.png
48+
Icon=
49+
Author=
50+
# version can be a number/string/hash/date...
51+
Version=0.1
52+
53+
# description can be plain text
54+
;Description=My plugin that does this and that
55+
# or basic HTML, see https://doc.qt.io/qt-5/richtext-html-subset.html
56+
Description="
57+
<html>
58+
<head>
59+
<style>
60+
p {
61+
font-weight:bold;
62+
color:red;
63+
}
64+
</style>
65+
</head>
66+
<body>
67+
<p>Make sure to update the INI file in the plugin folder</p>
68+
</body>
69+
</html>
70+
"
71+
72+
73+
74+
75+
# here you can define any custom sections and settings to use in your plugin
76+
;[MyCustomSettings]
77+
;foo=bar
78+
79+
;[SomeOtherSection]
80+
;port=6969

Software/src/Plugin.cpp

Lines changed: 93 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#include "Plugin.hpp"
22
#include <QSettings>
3-
#include <QIcon>
4-
#include <QFileInfo>
3+
#include <QFile>
54
#include <QDir>
5+
#include <QImageReader>
66
#include "Settings.hpp"
77
#include "../common/defs.h"
88

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

26-
QString fileName = path+"/"+name+".ini";
27-
QSettings settings( fileName, QSettings::IniFormat );
28-
settings.beginGroup("Main");
29-
this->_name = settings.value( "Name", "Error").toString();
30-
if (settings.contains(kOsSpecificExecuteKey)) {
31-
this->_exec = settings.value( kOsSpecificExecuteKey, "").toString();
32-
} else {
33-
this->_exec = settings.value( "Execute", "").toString();
26+
const QString fileName(path+"/"+name+".ini");
27+
if (!QFile::exists(fileName)) {
28+
qWarning() << Q_FUNC_INFO << name << fileName << "does not exist, generating defaults, make sure to edit the file!";
29+
if (!QFile::copy(":/plugin-template.ini", fileName))
30+
qWarning() << Q_FUNC_INFO << name << "failed to generate" << fileName;
31+
else if (!QFile::setPermissions(fileName, QFile::permissions(fileName) | QFileDevice::WriteOwner))
32+
qWarning() << Q_FUNC_INFO << name << "could not set write permissions to" << fileName;
3433
}
35-
this->_guid = settings.value( "Guid", "").toString();
36-
this->_author = settings.value( "Author", "").toString();
37-
this->_description = settings.value( "Description", "").toString();
38-
this->_version = settings.value( "Version", "").toString();
39-
this->_icon = pluginPath.absoluteFilePath(settings.value( "Icon", "").toString());
34+
QSettings settings(fileName, QSettings::IniFormat);
35+
settings.beginGroup("Main");
36+
_name = settings.value("Name", "").toString();
37+
if (_name.isEmpty())
38+
_name = name;
39+
40+
if (settings.contains(kOsSpecificExecuteKey))
41+
_arguments = settings.value(kOsSpecificExecuteKey, "").toString().split(' ');
42+
else
43+
_arguments = settings.value("Execute", "").toString().split(' ');
44+
45+
if (!_arguments.isEmpty())
46+
_exec = _arguments.takeFirst();
47+
48+
if (_exec.isEmpty())
49+
qWarning() << Q_FUNC_INFO << name << "no executable, check" << fileName;
50+
51+
_guid = settings.value("Guid", "").toString();
52+
_author = settings.value("Author", "").toString();
53+
_description = settings.value("Description", "").toString();
54+
_version = settings.value("Version", "").toString();
55+
56+
const QString iconName = settings.value("Icon", "").toString();
57+
const QString iconPath = pluginPath.absoluteFilePath(iconName);
58+
if (!iconName.isEmpty() && QFile::exists(iconPath) && !QImageReader::imageFormat(iconPath).isEmpty())
59+
_icon = QIcon(iconPath);
60+
if (_icon.isNull())
61+
_icon = QIcon(":/icons/plugins.png");
62+
4063
settings.endGroup();
4164

4265
process = new QProcess(this);
43-
4466
}
4567

4668
Plugin::~Plugin()
4769
{
4870
Stop();
4971
}
5072

51-
QString Plugin::Name() const
52-
{
73+
QString Plugin::Name() const {
5374
return _name;
5475
}
5576

56-
QString Plugin::Guid() const
57-
{
77+
QString Plugin::Guid() const {
5878
return _guid;
5979
}
6080

61-
QString Plugin::Author() const {
81+
QString Plugin::Author() const {
6282
return _author;
6383
}
6484

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

7393

74-
QIcon Plugin::Icon() const {
75-
QFileInfo f(_icon);
76-
if (f.exists())
77-
return QIcon(_icon);
78-
return QIcon(":/icons/plugins.png");
94+
QIcon Plugin::Icon() const {
95+
return _icon;
7996
}
8097

8198

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

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

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

97-
void Plugin::setEnabled(bool enable){
98-
DEBUG_LOW_LEVEL << Q_FUNC_INFO << enable;
99-
QString key = this->_name+"/Enable";
114+
void Plugin::setEnabled(bool enable) {
115+
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name << enable;
116+
const QString key = _name+"/Enable";
100117
Settings::setValueMain(key,enable);
101-
if (!enable) this->Stop();
102-
if (enable) this->Start();
118+
if (!enable) Stop();
119+
if (enable) Start();
103120
}
104121

105122

106123
void Plugin::Start()
107124
{
108-
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _exec;
109-
110-
//QStringList arguments;
111-
//arguments << "-style" << "fusion";
125+
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name << "starting" << _exec << _arguments;
112126

113127
QDir dir(_pathPlugin);
114128
QDir::setCurrent(dir.absolutePath());
115129

116130
process->disconnect();
117-
connect(process, SIGNAL(stateChanged(QProcess::ProcessState)), this, SIGNAL(stateChanged(QProcess::ProcessState)));
131+
132+
connect(process, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(stateChanged(QProcess::ProcessState)));
133+
connect(process, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(errorOccurred(QProcess::ProcessError)));
134+
135+
connect(process, SIGNAL(started()), this, SLOT(started()));
136+
connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
137+
138+
connect(process, SIGNAL(readyReadStandardError()), this, SLOT(readyReadStandardError()));
139+
connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(readyReadStandardOutput()));
118140

119141
process->setEnvironment(QProcess::systemEnvironment());
120-
// process->setProcessChannelMode(QProcess::ForwardedChannels);
121142
process->setProgram(_exec);
143+
process->setArguments(_arguments);
122144
process->start();
123145
}
124146

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

157+
// QProcess slots
158+
void Plugin::stateChanged(QProcess::ProcessState newState)
159+
{
160+
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name << newState;
161+
emit pluginStateChanged(newState);
162+
}
163+
164+
void Plugin::errorOccurred(QProcess::ProcessError error)
165+
{
166+
qWarning() << Q_FUNC_INFO << _name << error << _exec << _arguments;
167+
}
168+
169+
void Plugin::started()
170+
{
171+
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name;
172+
}
173+
174+
void Plugin::finished(int exitCode, QProcess::ExitStatus exitStatus)
175+
{
176+
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name << "exitCode=" << exitCode << exitStatus;
177+
}
178+
179+
void Plugin::readyReadStandardError()
180+
{
181+
qWarning() << Q_FUNC_INFO << _name << process->readAllStandardError();
182+
}
183+
184+
void Plugin::readyReadStandardOutput()
185+
{
186+
DEBUG_LOW_LEVEL << Q_FUNC_INFO << _name << process->readAllStandardOutput();
187+
}

Software/src/Plugin.hpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <QObject>
44
#include <QProcess>
5+
#include <QIcon>
56
#include "debug.h"
67

78
class Plugin : public QObject
@@ -30,24 +31,27 @@ class Plugin : public QObject
3031

3132

3233
signals:
34+
void pluginStateChanged(QProcess::ProcessState newState);
3335

34-
void stateChanged(QProcess::ProcessState);
35-
3636
public slots:
37-
37+
void stateChanged(QProcess::ProcessState newState);
38+
void errorOccurred(QProcess::ProcessError error);
39+
void started();
40+
void finished(int exitCode, QProcess::ExitStatus exitStatus);
41+
void readyReadStandardError();
42+
void readyReadStandardOutput();
43+
3844
private:
3945

4046
QString _guid;
4147
QString _name;
4248
QString _description;
4349
QString _author;
4450
QString _version;
45-
QString _icon;
51+
QIcon _icon;
4652
QString _exec;
47-
QString _arguments;
53+
QStringList _arguments;
4854
QString _pathPlugin;
4955
QProcess *process;
5056

5157
};
52-
53-

Software/src/PluginsManager.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ void PluginsManager::StartPlugins()
9393
p->disconnect();
9494
if (p->isEnabled())
9595
p->Start();
96-
connect(p, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(onPluginStateChangedHandler()));
96+
connect(p, SIGNAL(pluginStateChanged(QProcess::ProcessState)), this, SLOT(onPluginStateChangedHandler()));
9797
}
9898

9999
}

0 commit comments

Comments
 (0)