It allows you to monitor and control your devices from a browser.
Features
architecture
nodes
Hardware
In its simplest form a
Node is an ESP32 development board running
Node/Device firmware.
It can be powered by a 5V A/C adapter (wall wart) or a battery system.
The Espressif ESP32-S3-DevKitC-1 board has an RGB LED onboard. It is used as a status indicator at
power up. When the
Node first powers up and begins initializing, it will be RED. When
the
Node establishes communications with the
Relayer Module, it will turn GREEN to indicate
the
Node is ready. (More about the
Relayer below)
Your sensors and devices will use the GPIO pins of the
Node's ESP32 board. You can operate
up to 100 devices but you're more likely to run out of GPIO pins well before that. As you develop
your gizmo, you may want to enclose your electronics and provide connectors to external devices.
(If you use an ESP32 with a PCB antenna, it is not recommended to use a metal enclosure.)
Connection to your equipment and devices is up to you, but here's an example of a
Node enclosure using an
RJ-45 Breakout Board
and a barrel jack for the 5V power adapter.
Firmware
The
Node/Device firmware is written in C++ and includes "Node" and "Device"
base classes.
The
Node object creates the
Devices it will use.
It is rare that you will need to derive a child class from
Node but your custom
Devices
should always be derived from the
Device base class.
Node
Device
│
├── MyCustomDevice1
├── MyCustomDevice2
:
You create your
Node in the setup of your .ino (or main.cpp) file.
After constructing a
Node with a name and unique ID (0-19), you "add"
Devices using the
Node's
AddDevice() method. All
Devices added to your
Node will be assigned a unique ID (0-99).
The Device ID is assigned automatically when your
Device is added to the
Node.
void setup ()
{
:
:
ThisNode = new Node ("My First Node", 0);
ThisNode->AddDevice (new MyCustomDevice1 (...));
ThisNode->AddDevice (new MyCustomDevice2 (...));
:
:
}
Then you "Run" the
Node in the main loop.
void loop ()
{
ThisNode->Run ();
}
The
Node's
Run() method continually calls the
DoImmediate() and
DoPeriodic() methods
for all
Devices. It keeps your SMAC System running. More on that in the Devices section below ...
Nodes can handle "Commands" sent from the
SMAC Interface, although most commands are intended for
Devices.
Commands are four characters with optional parameters and are usually targeted for a specific
Device.
The
Node base class handles the following built-in commands but a derived
Node can handle
custom commands by overriding the
Node's
ExecuteCommand() method.
SNNA = Set Node Name
GNOI = Get Node Info : DataPacket.value = name|version|macAddress|numDevices
GDEI = Get Device Info : DataPacket.value = name1|rate1|name2|rate2|...
PING = Check if still alive and connected; responds with "PONG"
BLIN = Quickly blink the Node's status LED to indicate communication or location
RSET = Reset this Node's processor using esp_restart()
Your custom
Node class should first call the base class's
ExecuteCommand() method to handle the
built-in
Node commands:
Node::ExecuteCommand (...);
The
ExecuteCommand() method returns a "ProcessStatus" and may return a value of "NOT_HANDLED".
If the command is not handled by the base class, you can handle the command in your derived class.
More about "ProcessStatus" below.
devices
Your custom
Device should be derived from the
Device base class.
Devices can perform two kinds of operation: an "Immediate Process" and a "Periodic Process".
An Immediate Process is a quick operation you want your
Device to do continuously with
as little delay as possible between operations. For example, checking if a physical button
is being pressed on a control panel.
A Periodic Process is an operation to be performed at a periodic rate such as reading a
sensor once per second.
You define your Immediate Process by overriding the
DoImmediate() method.
You define your Periodic Process by overriding the
DoPeriodic() method.
The rate of calls to
DoPeriodic() is set in "calls per hour" using the
Device's
SetRate() method.
It defaults to 3600 (one call per second).
1 = one call per hour (the slowest data rate)
60 = one call per minute (for example, a temperature plot for a day)
3600 = one call per second (default)
72000 = 20 calls per second (recommended fastest data rate)
All data is sent to the
Relayer (and on to the
SMAC Interface) by populating the
global <DataPacket> structure and returning an appropriate "ProcessStatus".
The <DataPacket> structure has three fields:
deviceID : 2-char string (00-99)
timestamp : timestamp of when the data was acquired - usually millis()
value : variable length string (including NULL terminating char)
(this can be a numerical value or a text message up to 240 chars)
For the
DoPeriodic() method, we only need to populate the timestamp
and value fields. The deviceID is filled for us by the
Node base class.
DataPacket.value must be a NULL terminated string!
Both the
DoImmediate() and
DoPeriodic() methods return a "ProcessStatus" enum value.
It can be one of four values:
SUCCESS_DATA - Process performed successfully, send DataPacket to Relayer (such as a sensor reading)
SUCCESS_NODATA - Process performed successfully, no data to send to Relayer
FAIL_DATA - Process failed, send DataPacket to Relayer (such as error code or message)
FAIL_NODATA - Process failed, no data to send to Relayer
Here is a template for returning data in your
DoPeriodic() method:
ProcessStatus LightSensor::DoPeriodic ()
{
int sample = 4095 - analogRead (sensorPin);
DataPacket.timestamp = millis ();
itoa (sample, DataPacket.value, 10);
return SUCCESS_DATA;
}
Like
Nodes,
Devices can execute commands. Your
Device can execute custom commands
by overriding the
Device's
ExecuteCommand() method.
Node/Device Commands are sent from the
SMAC Interface as a text string. The command string
has four fields separated with the '|' char for clarity and has the following format:
┌─────────────── 2-char nodeID (00-19)
│ ┌──────────── 2-char deviceID (00-99)
│ │ ┌──────── 4-char command (usually capital letters)
│ │ │ ┌── Optional variable length parameter string
│ │ │ │
nn|dd|CCCC|params
The Device base class handles the following built-in commands:
GDNA = Get Device Name : Returns this device's name
SDNA = Set Device Name : Sets this device's name (a User-Friendly name for the SMAC Interface)
ENIP = Enable Immediate Processing : Start executing the immediate process for this device
DIIP = Disable Immediate Processing : Stop executing the immediate process for this device
DOIP = Do Immediate Process : Perform the immediate process one time
ENPP = Enable Periodic Processing : Start executing the periodic process for this device (start sending data) default
DIPP = Disable Periodic Processing : Stop executing the periodic process for this device (stop sending data)
DOPP = Do Periodic Process : Perform the periodic process one time, returns true or false
GRAT = Get Rate : Get the current periodic process rate for this device in calls per hour
SRAT = Set Rate : Set the periodic process rate for this device in procs per hour
Your custom
Device class can override ExecuteCommand() to handle custom commands,
for example, CALI for a calibrate function. It should first call the base class's
ExecuteCommand() method to handle the built-in
Device commands. More on this in the examples.
The
ExecuteCommand() method returns a "ProcessStatus" and may return a value of "NOT_HANDLED".
If the command is not handled by the base class, you can handle the command in your derived class.
Here is a template for handling custom commands in your
ExecuteCommand() method:
ProcessStatus LED::ExecuteCommand ()
{
pStatus = Device::ExecuteCommand ();
if (pStatus == NOT_HANDLED)
{
if (strcmp (CommandPacket.command, "LEON") == 0)
{
digitalWrite (ledPin, HIGH);
pStatus = SUCCESS_NODATA;
}
else if (strcmp (CommandPacket.command, "LEOF") == 0)
{
digitalWrite (ledPin, LOW);
pStatus = SUCCESS_NODATA;
}
}
return pStatus;
}
the relayer
The
SMAC Relayer is just an empty ESP32-S3 board with the
Relayer firmware flashed to it.
The board should have its USB connector exposed so it can be cabled directly to a PC or laptop.
The
Relayer could even be an ESP32 USB Dongle!
Other than possibly needing to change the LED pin number there is no custom code needed for the
Relayer.
Just flash the Relayer firmware to an ESP32 board and you'll have a working
Relayer ready to go.
the smac interface
The
SMAC Interface is a web app that lets you monitor data and control equipment using the
Chrome or
Edge web browser.
It receives DataPackets and sends CommandStrings from/to all your
Nodes and
Devices.
The
SMAC Interface can host multiple pages, or "sections", of your interface using
a tabbed control in the top header. Included is a
Diagnostics tab that displays
all the
Nodes and
Devices connected to your system as well as a
Data Monitor
for each of the
Nodes.
Some Examples
Weather Station Example
Diagnostics
examples
From here, you will see how all this comes together by going through some examples.
You will need at least two ESP32 Dev Boards for a SMAC System. One for a Relayer
and at least one for a Node.
Start by creating a project folder on your local drive.
Then go to the
SMAC GitHub Repo.
In the Code section you should see two folders: Firmware and Interface.
Download a ZIP file of these two folders into your project folder:
∙ Under the Code Tab look for the green
<> Code ▼ pulldown button
∙ Use the [Download ZIP] option to download into your project folder, then extract the zip file.
Your folder structure should look like this:
MyProject
│
├── Firmware
│ │
│ ├── Node_Example1
│ ├── Node_Example2
│ └── Relayer
│
└── Interface (web app)
│
├── Fonts
├── Images
├── Scripts
├── diagnostics.html
├── dsStyles.css
├── example.html
├── favIcon.ico
├── index.html
├── setMAC.html
└── smacStyles.css
The Firmware folder holds the ESP32 code for
Node/Device and the
Relayer.
The Interface folder holds the
SMAC User Interface web app.
Our first example will demo simple telemetry by displaying a reading from a light sensor (photoresistor).
Building/Flashing
I use the Arduino framework in VS Code, but you're welcome to use
PlatformIO or the Arduino IDE. In VS Code you will notice that
the Arduino support by Microsoft is deprecated, but there is support
from the Arduino Community. Install the
Arduino Community Edition
extension.
To use PlatformIO, move the header files into the include
folder and all other files into the src folder. Also, rename the
Node_Example1.ino file to main.cpp.
Let's turn one of your ESP32's into a
SMAC Relayer.
Open the Firmware's Relayer folder and compile/flash to one of your ESP32's.
You should now have a working
Relayer and can be reused for all
SMAC projects. The onboard RGB LED should be green. If not, you
may have to change the pin for the LED. Look near the top of the
common.h file for the #define STATUS_LED_PIN.
Now lets make your other ESP32 board a
SMAC Node.
Close the Relayer folder and open the Node_Example1 folder.
Compile/flash to the other ESP32 board.
For Nodes, the onboard LED stays red until it connects with the Relayer,
then it goes green.
Example1
The hardware hookup for Node_Example1 is a voltage divider with a
5K resistor and a photoresistor (LDR).
(You can use any type of resistive sensor here, even just a pot.)
~180Ω-1MΩ
5KΩ ↘↘
┌────█████────┬─────(▥)────┐
│ │ │
│ │ │
▲ ▼ ▼
3.3V GPIO GND
pin 6
The firmware for Node_Example1 has a custom LightSensor class derived from the
Device base class.
The constructor saves and initializes the GPIO pin that our photoresistor will use. And it
sets a default sampling rate.
Then the class overrides the
DoPeriodic() method to return a reading. Data is returned
by populating the "timestamp" and "value" fields of the global <DataPacket> structure.
And a "ProcessStatus" is returned.
viewing local webpages
From here we'll be working in the Interface folder and the Chrome or Edge browser.
The easiest way I know of to view a website from your local drive
is to use Python's built-in web server:
- Install the latest version of Python onto your computer.
- Open a terminal window.
- Change directory to the home folder of your local website (the one with index.html).
-
Run the following command:
python -m http.server
This will start a server on port 8000
- Open Chrome or Edge and bring up localhost:8000
connecting your nodes to the relayer
For your
Nodes to wirelessly connect to the
Relayer, they will need to know the
Relayer's MAC address.
We will use the
SMAC's
Set MAC Tool to do this.
This tool grabs the MAC Address of the
Relayer and stores it in the
Node permanently.
You only have to do this once for each of your
Nodes of your system.
Use your new Chrome Local or Edge Local shortcut to open a browser.
Press [Ctrl-O] to open the file
setMAC.html in your project's
Interface folder. (You can bookmark this page as the Set MAC Tool).
- Use a USB cable to connect your Relayer board to your PC and click the [Connect to Relayer] button.
- Choose the serial port your board is on.
- It will read the MAC address of the board.
- Then disconnect the Relayer and connect the Node board.
- Choose the serial port of the Node.
- It will read the current Relayer's MAC address stored on the Node board and ask to keep it or replace it.
- Submit the new MAC address if they don't match.
using the smac interface
Press [Ctrl-O] to open the file
index.html in your project's
Interface folder. (You can bookmark this page as the SMAC Interface).
Connect your
Relayer to your PC.
Click on the [Connect to the Relayer] button and choose the serial port.
Once you connect to the
Relayer the
SMAC Interface for Example1 shows up.
Now power up your
Node board with a USB power supply or battery.
The
Node should connect to the Relayer and its LED should go green.
There is only one tab and one
Device for this
SMAC System.
It shows a "growbar" for the light sensor that is being updated 10 times per second.
(Wave your hand or flashlight over the light sensor)
The
Diagnostics tab shows details about the system:
Notice the "Device List" for the
Node. You can turn on/off both
Immediate Processing and Periodic Processing and even set the data
rate for Devices. You can also look at the data coming in by selecting "[] Show
Data" in the Node Monitor title bar. You can also manually send
commands directly to Devices in the "Node Command" box. (more on that later)
SMAC Interface Files
Since the SMAC Interface uses a tabbed control for your pages, you will need a single HTML file
for each of your tab sections. There is no need for the <html>, <head> or <body>
tags in your pages. You can simply have SMAC Widgets laid out as you wish.
In this first example there is only one tab page with a filename of example.html.
Interface (web app)
│
├── Fonts
├── Images
├── Scripts
├── diagnostics.html
├── dsStyles.css
├── example.html ◀────── added
├── favIcon.ico
├── index.html ◀──────── edit
├── setMAC.html
└── smacStyles.css
You then need to edit the index.html file to include your tab pages:
Open the index.html file and look for the "navButtons" section.
You will need a navButton for each of your tab pages:
Then scroll down a bit to this section.
You will need a content "div" for each page:
That's it. The
SMAC Interface can now display your page by clicking on your tab button.
smac interface style
The UI style adopted by the
SMAC Interface is called
Skeuomorphism.
This is to mimic some-what realistic control panels.
(Maybe I'm old fashioned but in my mind, a button should look like a button, and a switch should look like a switch.)
You can read more about Skeuomorphic design
here,
here, and
here and see some examples
here.
Your tab pages in the
SMAC Interface are written in HTML, CSS and Javascript/jQuery.
To visualize equipment components and data the
Interface includes some built-in styles and
SMAC Widgets. More about Widgets below ...
Some common CSS styles included are:
dsLabelL/dsLabelM/dsLabelS | - blockish panel labels (large, medium, small) |
dsDividerH/dsDividerV | - horizontal/vertical beveled seperator line |
dsBlock/dsBlockRuled/dsBlockLowered/dsBlockRaised | - rounded corner section areas |
dsGridForm | - form with labeled input fields |
dsButton/dsButtonRound | - momentary buttons |
dsInput | - all input types |
dsSwitch | - sliding switch |
dsSelect | - drop-down item selection |
dsTable | - table with styled header |
dsGroupbox/dsGroupBoxTitle/dsGroupBoxContent | - titled box area |
dsTabControl/dsTabBar/dsTabButton/dsTabContent | - tab control with styled tab buttons |
Some SMAC Interface CSS styles included are:
smac-ledOff/Red/Green/Blue/Yellow/Orange/Purple/White | - LED's |
smac-panellightOff/Red/Green/Blue/Yellow/Orange/Purple/White | - panel lights |
smac-eStop | - emergency stop button with reset |
smac-digital | - seven-segment digital display |
smac-display | - text area |
smac-growbar | - vertical or horizontal "value" bar |
smac-dial ☀ | - a rotary knob with printed values |
smac-joystick ☀ | - a 2D dragging control |
☀ not yet implemented (I'm working on it ...)
... more coming soon ...
smac interface widgets
We display controls and visualize device data using
SMAC UI Widgets.
SMAC Widgets are
Custom HTML Web Components
included with the
SMAC Framework.
Some are implemented but many more are planned and coming soon.
SMAC Interface Widgets are added to your tabpage like any other HTML element.
The are two types of UI
Widgets -
Device Control Widgets such as a Switch
and
Data Display Widgets such as a Gauge.
Some Data Display Widgets can show data from more than one Device, such as a Graph.
Some Device Control Elements: (These HTML elements control devices)
<smac-panelbutton> | - momentary push button |
<smac-switch> | - on/off (binary) switch with any set of on/off images |
<smac-eStop> | - emergency stop button with reset required |
<smac-slider> ☀ | - single knob range selector |
<smac-dualslider> ☀ | - dual knob low/high range selector |
<smac-dial> ☀ | - rotary selection dial |
<smac-spinner> ☀ | - value field using mouse wheel to adjust |
<smac-joystick> ☀ | - 2D joystick |
☀ not yet implemented (I'm working on it ...)
... more coming soon ...
Some Data Display Elements: (These HTML elements
react to device data)
<smac-led...> | - colored LED's: Off, Red, Green, Blue, Yellow, Orange, Purple, White |
<smac-panellight...> | - colored panel lights: Off, Red, Green, Blue, Yellow, Orange, Purple, White |
<smac-flasher> ☀ | - flashing panel light with on/off time attributes |
<smac-digital> | - single-device digital display (can use any font, e.g. 7-segment led) |
<smac-odometer> ☀ | - spinning wheels with digits 0-9 |
<smac-display> | - multiline display screen |
<smac-growbar> | - single-device horizontal or vertical bar whose length represents the data value |
<smac-gauge> | - single-device needle, filled pie slice or donut gauge |
<smac-bargraph> ☀ | - multi-device bar graph |
<smac-timegraph> | - multi-device cartesian graph with panning option (value vs. time) |
<smac-devicegraph> ☀ | - device value vs. device value - such as voltage vs. temperature |
<smac-polargraph> ☀ | - spider or radar Plot |
<smac-piechart> ☀ | - a dynamic pie chart that updates its sections on the fly |
<smac-donutchart> ☀ | - same as above |
<smac-chartgraph> ☀ | - scrolling chart recorder |
<smac-compass> | - a 360° nautical-type compass |
<smac-bubblefield> ☀ | - variable sized semi-transparent circles |
<smac-venndiagram> ☀ | - semi-transparent overlapping areas |
<smac-bloomfield> ☀ | - sunburst? |
<smac-camera> ☀ | - a digital camera web server streaming video or single photos |
☀ not yet implemented (I'm working on it ...)
... more coming soon ...
Each Widget has their own set of attributes for visual appearance, however, they require a
"device"
attribute to
tie them to a specific
Node and
Device of your SMAC system.
The "growbar" in the example.html file is displayed using the <smac-growbar>
widget with the following attributes:
<smac-growbar
device = "0,0"
orientation = "vertical"
width = "3"
height = "30"
minValue = "0"
maxValue = "4095"
backColor = "#303030"
fillColor = "#FF8040">
</smac-growbar>
Nearly all
Widgets require the 'device' attribute declaring where the
data is coming from (nodeID,deviceID).
You can also add CSS styling to
Widgets.
For example, let's display a light sensor reading in a digital field. Suppose our light sensor is physically
on
Node 0 and is
Device 0. (The Node ID is zero and the Device ID is zero.) The HTML
for this would be:
<smac-digital
device = "0,0">
</smac-digital>
(default style)
The device attribute takes two ID's separated with a comma - The Node ID and the Device ID.
(Attribute values are always in double quotes)
This simple HTML block will connect and display readings from Node 0/Device 0 in your tab page.
You could style this element using a normal CSS style attribute. For example:
<smac-digital style="padding:10px; border:2px solid blue; font-family:'Lucida Sans'; font-size:40px; color:aqua; background-color:#303030"
device = "0,0">
</smac-digital>
Suppose you want to turn a motor on/off with a switch.
We could have a motor driver on Node 0 as Device 1.
The HTML to control this motor would be:
<smac-switch
device = "0,1"
onImage = "Images/ui_Switch_Lever2_On.png"
offImage = "Images/ui_Switch_Lever2_Off.png"
onAction = "motorOnFunction()"
offAction = "motorOffFunction()">
</smac-switch>
Each time you toggle the switch, the function in onAction or offAction is called. Those functions
could send a command to the motor driver as well as manipulate other components on your page, if necessary.
Sending Commands from the Interface
The Javascript SMAC function for sending commands to Nodes and Devices is
Send_UItoRelayer (nodeIndex, deviceIndex, command, params)
This is an asynchronous function so it should be used with the "await" keyword in front of it.
This function takes three or four parameters:
nodeIndex - a number from 0 to 19
deviceIndex - a number from 0 to 99
command - 4-char command string (usually caps)
params - optional parameter string
The command is sent to the
Relayer and forwarded to the targeted
Node and
Device
where it is picked up by the firmware's
ExecuteCommand() method. We'll see how this is
done in the second example.
Example2
Here we've added a momentary pushbutton and a yellow LED to the first example.
Hookup should look like the following:
470Ω
┌─────█████─────┐
│ │
│ │
▼ │
GPIO (▼) LED
pin 4 (─)↘
│ ↘
button │
┌────┬▀▀▀┬────┐ │
│ └───┘ │ │
│ │ │
│ ▼ │
│ GPIO input │
│ pin 5 (pulldown) │
│ │
│ │
│ ~180Ω-1MΩ │
│ 5KΩ ↘↘ │
├────█████────┬─────(▥)──────┤
│ │ │
│ │ │
▲ ▼ ▼
3.3V GPIO GND
pin 6
In the Node_Example2 folder we've added Button and LED
Device classes.
Since we need to continually check if the button is being pressed, the Button class overrides
the
DoImmediate() method. And since we don't need to do anything periodically
there's no need to override the
DoPeriodic() method.
Button::Button (const char *inName, int inButtonPin)
:Device (inName)
{
buttonPin = inButtonPin;
pinMode (buttonPin, INPUT_PULLDOWN);
periodicEnabled = false;
}
ProcessStatus Button::DoImmediate ()
{
newState = digitalRead (buttonPin);
if (newState != currentState)
{
DataPacket.timestamp = millis ();
strcpy (DataPacket.value, (newState==0 ? "0" : "1"));
currentState = newState;
return SUCCESS_DATA;
}
return SUCCESS_NODATA;
}
For the LED class we do not need to do any processing but we will need to turn the LED on or off
when commanded by the
Interface.
So we override the
ExecuteCommand() method.
LED::LED (const char *inName, int inLEDPin)
:Device (inName)
{
ledPin = inLEDPin;
pinMode (ledPin, OUTPUT);
immediateEnabled = periodicEnabled = false;
}
ProcessStatus LED::ExecuteCommand ()
{
pStatus = Device::ExecuteCommand ();
if (pStatus == NOT_HANDLED)
{
if (strcmp (CommandPacket.command, "LEON") == 0)
{
digitalWrite (ledPin, HIGH);
pStatus = SUCCESS_NODATA;
}
else if (strcmp (CommandPacket.command, "LEOF") == 0)
{
digitalWrite (ledPin, LOW);
pStatus = SUCCESS_NODATA;
}
}
return pStatus;
}
And in the
SMAC Interface we've added a graphical LED to indicate if the button is being pressed,
and a simple switch to turn on/off the yellow LED.
Also notice there are three Devices for our
Node in the Diagnostics page now.
The HTML code in the example.html file now looks like this:
<br>
<div class="demoTitle">Example Tab Section</div>
<br>
<br>
<div style="font-size:2vw">
<div class="dsBlock" style="margin:0 2vw; text-align:center">
Light Sensor:<br>
<smac-growbar style="margin-top:0.5vw"
device = "0,0"
orientation = "vertical"
width = "3"
height = "30"
minValue = "0"
maxValue = "4095"
backColor = "#303030"
fillColor = "#FF8040">
</smac-growbar>
</div>
<div class="dsBlock" style="margin:0 2vw; text-align:center">
Little Button:<br>
<smac-led style="margin-top:0.5vw; font-size:3vw"
device="0,1">
</smac-led><br>
<br>
</div>
<div class="dsBlock" style="margin:0 2vw; text-align:center">
Yellow LED:<br>
<input id="yellowLEDSwitch" type="checkbox" class="dsSwitch" style="margin-top:0.5vw; font-size:3vw" onchange="UpdateYellowLED()" />
</div><br>
<br>
</div>
<script>
async function UpdateYellowLED ()
{
try
{
const switchState = $("#yellowLEDSwitch").is(':checked');
if (switchState)
await Send_UItoRelayer (0, 2, 'LEON');
else
await Send_UItoRelayer (0, 2, 'LEOF');
}
catch (ex)
{
ShowException (ex);
}
}
</script>
Notice we've added a function to send a command to the LED
Device; "LEON" for on and "LEOF" for off.
Since this function calls Send_UItoRelayer(), it should be an async function.
smac widget reference
A reference to each of the implemented SMAC Widgets is coming soon ...
For now, you can see what attributes are available in the SMAC_Widgets.js file of the Interface/Scripts folder.
●
●
●