Page cover

Writing a plugin

Let's write a trivial plugin in go

Note that this document is written pre-release. Details, particularly around the SDK, are subject to change.

One of the key features of rtd.pub is the ability to use any language to implement your integration with Excel. Perhaps you have an existing system that you want to extend, or simply prefer writing code in a particular language.

We really want to avoid boilerplate and make it as easy as possible, so that you can focus on adding value... as a PHB may say.

To show how easy it is, we're going to build a trivial plugin that counts from 0 to 60. Amazing, right? Not really, but hopefully this will give you a taste for how easy it is to do far more meaningful things.

Implementation

Create a go module and in main.go import some modules. (Note that these will be available when rtd.pub is generally available.)

Create a struct with an embedded PluginServer which handles the communication between your plugin and rtd.pub.

package main

import 	
(
	"github.com/acornsoftuk/plugin"
	pb "github.com/acornsoftuk/plugin/protos"
	"github.com/sirupsen/logrus"
)

type CountingPlugin struct {
	plugin.PluginBase
}

Next, some hooks to handle plugin lifecycle.

func (p *CountingPlugin) Run(ctx context.Context) error {
	logrus.Infof("CountingPlugin started")
	return nil
}

func (p *CountingPlugin) Stop(context.Context) {
	logrus.Infof("CountingPlugin stopped")
}

We want our counter to start when a user adds a rtdPub.Sub call to a formula cell. Implementing Subscribe starts the subscription to a given piece of data, which will ultimately land back into that cell.

The Subscribe func expects the topic ID (assigned by Excel), subject and additional parameters. Most important is the setResult callback that ensures a new value is written to the correct topic.

Plugins must respect the context ctx passed by the runtime, and terminate when it is cancelled. This ensures that values cease to be produced when the subscribe cells are de

func (p *CountingPlugin) Subscribe(ctx context.Context, tid int32, subj string, params []string,
	setResult plugin.SendFunc) error {

	go func() {
		// extract/parse from []params slice using type of default
		// params, index, default
		maxTicks := plugin.TryParam(params, 0, 60)
		delay := plugin.TryParam(params, 1, 1)

		ticker := time.NewTicker(time.Duration(delay) * time.Second)
		defer ticker.Stop()

		for i := 0; i <= maxTicks; i++ {
			select {
			case <-ctx.Done():
				log.Infof("context cancelled, stopping counter goroutine.")
				return
			case <-ticker.C:
				if err := setResult(i); err != nil {
					log.Error(err)
				}
			}
		}

		log.Infof("counter reached %v, stopping goroutine for topic %v.", maxTicks, tid)
	}()

	log.Infof("counting plugin got subscribe: %v", tid)
	return nil
}

Unsubscribe is called when there are no more cells referencing the topic. Implementing this is optional, but it is a good place to tidy up any external subscriptions.

func (p *CountingPlugin) Unsubscribe(topicId int32) error {
	logrus.Infof("got unsubscribe: %v", req)
}

A plugin is an executable. Not much more to do now, other than add a main func tp start the thing up.

func main() {
	// starts a gRPC server
	plugin.Start(&CountingPlugin{})
}

And build it (for Windows)

GOOS=windows go build -o rtdpi-demo.exe

This should yield rtdpi-demo.exe

Installation

Move or copy the .exe to the plugins folder where you have installed rtd.pub.

Edit rtdpub.yaml to look similar to the below. This creates a connector, which is a reified instance of a plugin. This plugin does not have any configuration values, but if it did, these could be added to the properties map.

Also of note is the optional streams: section. This allows you to alias the rtdPub.Sub function to something more user friendly and discoverable by end users, complete with documentation.

rtdpub:
  plugins:
    path: ./plugins
    connectors:
      tutorial:
        uses: demo
        properties: {}
        streams:
        - name: Counter
          params:
            - name: max
              description: Number to count up to

Try it out

Start Excel and create a blank workbook.

Add a formula in any cell like =rtdPub.Sub("tutorial", "counter")

Stand back in amazement as the value updates.

While this is happening add another, =rtdPub.Sub("tutorial", "counter2") and watch the two cells update in unison. You can fill up or down to reference the same data point in multiple places through the same subscription.

You can see it running in the below video.

Trivial counter RTD function, with content coming from a go plugin.

Wrap up

Easy! Python support is coming soon.

If you have any questions along the way, we are here to help. Drop us an email. You can also register for early access.

Last updated