Go Down

Topic: [Feature Request] Editor "Plugins/Tools" additional functions (Read 2227 times) previous topic - next topic

microsan84

Hi,

I'm developing some functions in an external tool that auto-generates code and uses Arduino IDE for compilation/upload.

It creates a WebServer that uses the "tool functionality in Arduino IDE"
that receives the code by POST and commands by GET requests.

Because some methods/fields where not public.
I had to add some methods and change Editor.java

It should be nice if the Arduino team can make this available in the next Arduino version.
Maybe others could use this new functionality as well.

Here is the changes I made:

at beginning of Editor-class add
private ArrayList<Tool> tools = new ArrayList<>();


in  private void addTools(JMenu menu, File sourceFolder)

replaced tool.init(Editor.this);
with     tools.add(tool);

at end of Editor-class constructor add
Code: [Select]
// this makes sure that GUI is loaded before any "Plugins" is initialized
// so that when a "Plugin" loads it can use the LOG window to show info.
for (int i = 0; i < tools.size(); i++)
{
 tools.get(i).init(Editor.this);
}


add five functions/methods to Editor-class

Code: [Select]
public void verifyCompile()
  {
 handleRun(false, presentHandler, runHandler);
  }
  public void upload()
  {
 handleExport(false);
  }
  public boolean addNewFile(String fileName, String contents) // for the API
  {
 File newFile = new File(sketch.getFolder(), fileName);
 int fileIndex = sketch.findFileIndex(newFile);
 if (fileIndex >= 0) { // file allready exist, just change the contents.
  tabs.get(fileIndex).setText(contents);
  return true;
 }
 SketchFile sketchFile;
 try {
  sketchFile = sketch.addFile(fileName);
  addTab(sketchFile, contents);
 } catch (IOException e) {
  // This does not pass on e, to prevent showing a backtrace for
  // "normal" errors.
  Base.showWarning(tr("Error"), e.getMessage(), null);
  return false;
 }
 selectTab(findTabIndex(sketchFile));
 return true;
  }
  public boolean removeFile(String fileName) // for the API, so that files could be removed
  {
 File newFile = new File(sketch.getFolder(), fileName);
 int fileIndex = sketch.findFileIndex(newFile);
 if (fileIndex >= 0) { // file exist
  SketchFile sketchFile = sketch.getFile(fileIndex);
  sketch.removeFile(sketchFile);
  try {
 removeTab(sketchFile);
 // just set current tab to the main tab
 selectTab(0);

 // update the tabs
 header.repaint();
 return true;
  } catch (IOException e) {
 // This does not pass on e, to prevent showing a backtrace for
 // "normal" errors.
 Base.showWarning(tr("Error"), e.getMessage(), null);
  }      
 }
 return false;
  }
  public boolean renameFile(String oldFileName, String newFileName) // for the API, so that it can rename files
  {
 File newFile = new File(sketch.getFolder(), oldFileName);
 int fileIndex = sketch.findFileIndex(newFile);
 if (fileIndex >= 0) { // file exist
  SketchFile sketchFile = sketch.getFile(fileIndex);
  try {
 sketchFile.renameTo(newFileName);
 // update the tabs
 header.rebuild();
 return true;
  } catch (IOException e) {
 // This does not pass on e, to prevent showing a backtrace for
 // "normal" errors.
 Base.showWarning(tr("Error"), e.getMessage(), null);
  }
 }
 return false;
  }


i have attached the Webserver tool/plugin with the source file so that you can see how I use the new functionality . It uses json-20200518.jar from https://repo1.maven.org/maven2/org/json/json/20200518/ for the JSON parser.
this zip could be extracted in tools folder of your Arduino installation.

And yes i know about the CLI but with that there need to be an external program that takes care of the WebServer part.

I wanted to use the GUI so that it's easier for users to view, understand and verify the generated code.

And last a question about how/if I can use the word Arduino in the code generation tool.

microsan84

I fixed it by using reflection so with that I can access protected and private members

and by using:
Code: [Select]
public void init(Editor editor) { // required by tool loader
    this.editor = editor;
    editor.addWindowListener(new WindowAdapter() {
      public void windowOpened(WindowEvent e) {
          init(); // this is the autostart function
      }
   });
}

to make sure the editor is loaded before I try access anything.

pert

Very interesting. Thanks so much for sharing! I always like to see what people do with the plugin functionality of the Arduino IDE. I think it doesn't get as much use as it might because a lot of people don't even know the IDE has this capability.

With your new approach, are you able to use the tool with the unmodified Arduino IDE?

microsan84

With your new approach, are you able to use the tool with the unmodified Arduino IDE?
Yes it's unmodified, is working, even tried it on ubuntu 20.04
except that the seperator line in the tools menu 
dissapear after some time,
when adding additional items to this menu, in this case a config menu item that loads a form (made by the very simple "non bloatware" GUI editor GuiGenie <1MB)

I could do a submenu that replaces the "API Web Server Start" tool menu item, but at the moment it was easier to just add it like this.

microsan84

adding items to the menu seems buggy, don't work as expected , so I did a submenu instead that replaces the standard tool menu item

microsan84

Latest update contains:
With help of the Arduino source code, I have created a Custom ConsoleOutputStream named ConsoleOutputStream2
that is initialized with:
Code: [Select]
EditorConsole editorConsole;
private ConsoleOutputStream2 out;
private ConsoleOutputStream2 err;
private SimpleAttributeSet console_stdOutStyle;
private SimpleAttributeSet console_stdErrStyle;

public void setCurrentEditorConsole() {
    if (out == null) {
       out = new ConsoleOutputStream2(console_stdOutStyle, System.out, cs);
       System.setOut(new PrintStream(out, true));
    
       err = new ConsoleOutputStream2(console_stdErrStyle, System.err, cs);
       System.setErr(new PrintStream(err, true));
    }
    out.setCurrentEditorConsole(editorConsole);
    err.setCurrentEditorConsole(editorConsole);
}

private void fetchPrivateConsoleFields(){
    Field f;
    f = Editor.class.getDeclaredField("console");
    f.setAccessible(true);
    editorConsole = (EditorConsole) f.get(this.editor); // editor is provided by the normal tool interface

    f = EditorConsole.class.getDeclaredField("stdOutStyle");
    f.setAccessible(true);
    console_stdOutStyle = (SimpleAttributeSet) f.get(this.editorConsole);

    f = EditorConsole.class.getDeclaredField("stdErrStyle");
    f.setAccessible(true);
    console_stdErrStyle = (SimpleAttributeSet) f.get(this.editorConsole);
}


WebSocket server from https://github.com/TooTallNate/Java-WebSocket
This WebSocket is used to send above terminal data to the client,

the complete source is available at
https://github.com/manicken/manicken.github.io/blob/master/API_WebServer/src/API_WebServer.java
all source is in that file

microsan84

I have made some rework of the source code,
and made it a own repository on github that is now regularly updated.
https://github.com/manicken/arduinoAPIwebserver
there is also a README that explains the structure of the POST JSON
and the other functionality.


source code changes:
*each class have it's own file.
There is some work to do on the "main" API_Webserver.java
because it have 700 lines of code and I personally think that is too much.
I will probably make a separate class for the settings and the part that communicate directly with Arduino IDE.

New functionality:

* now there is the possibility to have a optional keywords.txt in the sketch folder (the project) so  that custom keywords for own created classes can be made highlighted.

* @ HTTP POST each JSON item is now optional, but removeOtherFiles and files is used together in that order.

* @ HTTP POST there are now an keywords "object" that contains a list of additional keywords used by the POST:ed files, that keywords is stored in a keywords_temp.txt in sketch folder so that is available direct after the sketch/IDE is loaded.
 
* Autocomplete (because Arduino IDE uses RSyntaxTextArea) I found    this https://github.com/bobbylight/AutoComplete
that is using RSyntaxTextArea

this autocomplete function is activated on an empty line with ctrl+space
"live" autocomplete is not working at the moment.

note that the line can also contain some letters but then the words provided
must begin with that.

The autocomplete function uses a file located in the tool folder of this "Plugin/Extension" "c.xml"

that file contains all the words that is currently supported (it's contents is from the example provided by the author of AutoComplete)

there is not any words from the Arduino library,
but I plan to do a automatic lockup from the files located in
%arduinoInstallDir%\reference\www.arduino.cc\en\Reference
and auto generate a c.xml that can be put into the Arduino "Sketchbook location" (in preferences)

Also by looking at the source and did some experiments
I also found out that you can have a tools folder in above "Sketchbook location" there "plugins" like this can be installed,
that makes it much easier to install on every OS including mac.

Also I have read some threads here about missing functionality,
all those requested functionality
like
"theme editor"
"boards alphabetic/custom/mostUused order"
"store current processor in sketch(folder)"
and so on

everything that can be manipulated by Reflect can also be done with this plugin functionalty.

What is needed are some functionality like the library have to download and install plugins
It could off course also be made as an plugin, by anyone.

/Jannik

pert

Quote
What is needed are some functionality like the library have to download and install plugins
It could off course also be made as an plugin, by anyone.
A "plugin manager" plugin. Very nice!

That could even be useful for Arduino's official WiFi101/NINA firmware updater plugin because currently the NINA firmware releases are coming out at a much shorter interval than the IDE releases, but the primary method for distributing NINA firmware releases is via the WiFi101/NINA firmware updater plugin bundled with the Arduino IDE. I've been recommending people to download the hourly build of the Arduino IDE just to get the latest firmware version.

microsan84

Why can't you just use some kind of firmware downloader inside your plugin to fetch the updates automatically from a server (they are just files stored in a folder)?
Seems very unnecessary to download a full copy of the Arduino IDE every time a little file of ~1MB is updated/added.



microsan84

I have done a additional plugin + a template for anyone to use
https://github.com/manicken/arduinoPrefsSaver
https://github.com/manicken/arduinoIDEpluginTemplate


The arduinoPrefsSaver plugin is used to save the current board settings (only)
to a preferences.txt located in the sketch folder.
This pref. file is then loaded when a sketch is opened and merged(all values replaced by new) into the current PreferencesData and then applied with code I found in the Arduino Source.

There are some twerks as I explained in the Teensy forum:
read from post #12
https://forum.pjrc.com/threads/64848-Compiler-directive-to-select-board?p=261694&viewfull=1#post261694


here is a copy of the posts (if that forum change the link system)


post #12
I have make a kind of working prototype as a (Arduino IDE Plugin/Extension/Tool) so it works on any OS.
It works like you want, by having certain preferences saved to current sketch folder

available at:
https://github.com/manicken/arduinoPrefsSaver

*How to Install
download this repository by either Code-Download Zip or
by git clone https://github.com/manicken/arduinoPrefsSaver.git
then extract/open the repository

global (into sketchbook folder (defined in Arduino IDE - Preferenses):
make a new folder in the above defined sketchbook folder
called tools
then copy the manickenPrefSaver from the repository into this new "tools" folder.

new menu items:
Click image for larger version.
here you can "activate/save current board settings" and deactivate the plugin functionality.

right now there is not any possibility to select what to save
but the items I have selected work for the moment.

right now it messes up the tools menu a little:
Click image for larger version.
that is after I have used teensy 4.0
and opened a project that uses Arduino Uno

There is the possibility to change the setting without any mess,
because you can select boards the normal way without mess,
but I have not figured it out yet.

the following items are saved:
Code:
board=teensy40
custom_CrystalFreq=generic_40
custom_FlashFreq=generic_40
custom_FlashMode=generic_dout
custom_ResetMethod=generic_nodemcu
custom_baud=generic_115200
custom_bmcsketchconfig=teensy40_no
custom_cpu=nano_atmega328
custom_dbg=generic_Serial
custom_eesz=generic_16M15M
custom_exception=generic_legacy
custom_ip=generic_lm2f
custom_keys=teensy40_en-us
custom_led=generic_2
custom_lvl=generic_SSLTLS_MEMHTTP_CLIENTHTTP_SERVERCOREWIFIHTTP_UPDATEUPDATEROTAOOMMDNS
custom_opt=teensy40_o2std
custom_sdk=generic_nonosdk_190703
custom_speed=teensy40_600
custom_ssl=generic_all
custom_usb=teensy40_serialmidiaudio
custom_vt=generic_flash
custom_wipe=generic_none
custom_xtal=generic_160
serial.databits=8
serial.debug_rate=9600
serial.line_ending=1
serial.parity=N
serial.port=COM8
serial.port.file=COM8
serial.port.iserial=null
serial.port.label=COM8
serial.port.protocol=serial
serial.stopbits=1
target_package=teensy
target_platform=avr
[/pre][/color]
i.e. all items beginning with:
serial.
custom_
and equals board


/Jannik



post #13
It works this way

When its "Activated" by the user, it takes those listed items above
and save them into a preferences.txt at the sketch folder (alias the project)

When this sketch is loaded again, while had selected another sketch.
Then it checks for preferences.txt and if that file is found it merges that contents into the "global"
preferences, and then apply that changes (this is where the menu gets a little messy I will fix that tomorrow).

If preferences.txt is not found then nothing will happen.

The Deactivate just renames preferences.txt to preferences.inactive.txt.

This can be used in a future release (of the plugin)


If you want you can compile the plugin from source, just change the arduino install dir of the respective compile scripts, (also java sdk 8 (1.8) is needed with the correct PATH variables set.

I have also made a plugin template project https://github.com/manicken/arduinoIDEpluginTemplate

that anyone can use to create their own plugins,
it contains helper methods
for example Refect
if you want more there is also the API_webserver that contains more functionality



post #14
First a little how Arduino IDE works with preferences (settings)
(Instance=when the IDE is started from the OS)

When the IDE starts it loads the pref. file
Then directly after it saves it back

Every time a new/(existing sketch) window is opened (from the File-menu)
it saves to this pref. file
(verified by looking at the file to see it's "Date modified" change)

When the last window of the instance is closed it also saves to pref. file.

When having multiple windows open in the same instance
they all share the same current Pref. (because it's static global)

The only way of having multiple pref. (multiple selected boards)
at the same time is to have multiple instances loaded.
(you don't need to have multiple installations for this)


Now I have done some more testing of my plugin.

The mess I was seeing is some kind of feature the IDE have.

Actually it works correctly:

When a (existing sketch) with a previous saved pref. is opened.
This according to above (IDE working stuff) loads this to the instance pref.
and all open windows of that same instance is gonna use this new setting.

But as I have stated before the sketch pref. is only saved when using the Extension-Menu commands
so the previous sketch don't loose the (sketch pref. settings) by load of this new window.

The mess in the menu disappears when closing the previous window.
(don't really know the inner working of that)
the board selection menu code is a little messy.


Also a little note. right now when open a existing sketch
and the sketch pref. is loaded it also rebuild the File-Examples menu
this takes little time to load, just so that you know what is happening.
When the extensions menu is visible it's done loading.
I will make this a optional by a setting in the next release.


sceniclife

Hey, great work. Have they implemented your editor changes yet? I am making a XY plotter as a tool but also ran into protected/private/default methods/variables from the Editor.

Could you help me out and explain what you did/meant by fixed by using reflection?

In my case, the editor's handlePlotter() will check if serialMonitor and serialPlotter is open. I essentially copied handlePlotter in my tool but cannot check if serialMonitor/serialPlotter is open from editor. (they are default package visible only)

I'll probably just open a PR on the arduino github: https://github.com/arduino/Arduino
But if you found a way around it, lmk!

I fixed it by using reflection so with that I can access protected and private members

and by using:
Code: [Select]
public void init(Editor editor) { // required by tool loader
    this.editor = editor;
    editor.addWindowListener(new WindowAdapter() {
      public void windowOpened(WindowEvent e) {
          init(); // this is the autostart function
      }
   });
}

to make sure the editor is loaded before I try access anything.


microsan84

By using reflection you can access those non public members.
An by this way the Arduino team don't needs to make any changes to the source code.

Except in some special cases (I actually did not get the local pref. to work as I wanted and that is still a work in progress)

I have made a helper class for the reflection stuff.
https://github.com/manicken/arduinoAPIwebserver/blob/main/API_WebServer/src/Reflect.java

use these imports:
Code: [Select]
import processing.app.AbstractMonitor;
import com.manicken;


then the example code:
Code: [Select]
Editor editor;
AbstractMonitor serialMonitor;
AbstractMonitor serialPlotter;

public void init(Editor editor) { // required by tool loader
    this.editor = editor;
    // using the following ensure that the editor is completely loaded
    // before accessing anything
    editor.addWindowListener(new WindowAdapter() {
        public void windowOpened(WindowEvent e) {
            serialMonitor = (AbstractMonitor)Reflect.GetField("serialMonitor", editor);
            serialPlotter = (AbstractMonitor)Reflect.GetField("serialPlotter", editor);
            // other init stuff
            // ......
            // here I can check if serial monitor is closed
            if (serialMonitor.isClosed())
            {
            }
        }
    });
}






microsan84

I have some problems with the current implementation of the tool loader.

The started webserver and websocket servers should be able to be shared between the different editors.

It can be solved by only allowing one server to be active at any time.

But what it's needed is a list in the editor where all loaded "tools" can be accessed.

So by going down to the base class I can go thought all loaded editor-instances and then in each editor go though the loaded "tools" to find my tool and then close the connections on all other "servers"
then start the servers on the last loaded "tool".

Right now I think I can utilize the menu to create a custom JMenuItem in which the current instance of the tool can be stored at a tag element, but the best way should be to have a array as stated above.


edit.
It will be a custom JMenu with a Tool item tag.

microsan84

Now I reached the limits  :smiley-confuse:


Now I Have tried the above:

By creating a Custom class
ToolJMenu extends javax.swing.JMenu


that have a public reference to the Tool:
public Tool tool = null;


But had some casting problems.
That was later verified by
http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html
Code: [Select]
If the myClassReloadingFactory object factory reloads the MyObject class using a different class loader than the class the above code resides in, you cannot cast the instance of the reloaded MyObject class to the MyObject type of the object variable. Since the two MyObject classes were loaded with different class loaders, the are regarded as different classes, even if they have the same fully qualified class name. Trying to cast an object of the one class to a reference of the other will result in a ClassCastException.

found  internalToolCache but that only contains the internal tools.
the only way of resolving this is by having a:
public List<Tool> tools;
in Editor.java
which contains all loaded tools.


also now getting

  [javac] Compiling 1 source file to C:\_githubClones\Arduino-1.8.13\app\bin
  [javac] C:\_githubClones\Arduino-1.8.13\app\src\processing\app\Editor.java:25: error: cannot access I18n
  [javac] import static processing.app.I18n.tr;
  [javac]                             ^
  [javac]   bad class file: C:\_githubClones\Arduino-1.8.13\arduino-core\arduino-core.jar(processing/app/I18n.class)
  [javac]     class file has wrong version 55.0, should be 52.0
  [javac]     Please remove or make sure it appears in the correct subdirectory of the classpath.


out of the blue
was prev. able to compile but now it's just stuck

microsan84

The compile error was fixed by removing all compiled class files in both
arduino-core/bin
and
app/bin
then do a complete recompile at both "arduino-core" and "app".

And now after some additional experiments,
the only way of fixing my problem:
"is that I have 3 servers and 1 midi devices in a "open" state
and when a new sketch is opened "from the same Arduino IDE instance"
and the previous is closed (either manually or automatically)
then there is no way of closing the previous "open" servers and devices,
because with the current implementation of the "Tool" interface there is no
unload function"

Here comes the working fix (that must be applied to the Arduino IDE source code):
1. Add additional Tool interface (to make old "extension" tools backwards compatible)
 public interface ToolExt extends Tool {  
  that have an void unload(); 

2. Add  "public List<Tool> tools;" into Editor.java
  @Editor.java private void addTools(JMenu menu, File sourceFolder) {

after  "tool.init(Editor.this);"
add    if (tools == null) tools = new ArrayList<Tool>();
         tools.add(tool);


3. then in my "Extension"
use it like this

Code: [Select]
public void CloseOtherEditors()
{
    List<Editor> editors = base.getEditors();
    boolean anyStopped = false;
    for (int ei = 0; ei < editors.size(); ei++)
    {
        Editor _editor = editors.get(ei);
        if (this.editor == _editor) // don't unload current editor
            continue;

        // first execute the unload on similar extensions
        for (int ti = 0; ti < _editor.tools.size(); ti++)
        {
            if (!_editor.tools.get(ti).getClass().getName().equals(API_WebServer.class.getName()))
                continue;

            try {
                ToolExt tool = (ToolExt)_editor.tools.get(ti);
                tool.unload();
                System.out.println("tool unload:" + _editor.getSketch().getName());
                //API_WebServer apiws = (API_WebServer)tool;
                //System.out.println("other extension: " + apiws.thisToolMenuTitle);
            }catch (Exception e) {System.err.println("tool type:" + _editor.tools.get(ti).getClass().getName());}
        }
        base.handleClose(_editor); // close other
    }
}


my github fork with the above changes
https://github.com/manicken/Arduino


these are small changes that only affects extensions and could be merged into the main branch of Arduino without any problems.

Go Up