In Slow-Dash projects, user Python module can be used for:
slowdash_project:
name: ...
module:
file: FILE_PATH
parameters:
KEY1: VALUE1
...
data_suffix: SUFFIX
cgi_enabled: False
[TODO] implement SUFFIX
By default, user modules are not enabled if the server program is launched by CGI. To enable this, set the cgi_enabled
parameter True
. Be careful for all the side effects, including performance overhead and security issues. As multiple user modules can be loaded in parallel, splitting functions to a CGI-enabled module and disabled one might be a good strategy.
slowdash_project:
name: ...
module:
file: ./mymodule.py
mymodule.py
at the user project directory will be loaded to slow-dash.mudule.py
will be called for various context.### Called when this module is loaded. The params are the parameters in the configuration file.
def initialize(params):
pass
### Called during termination of slow-dash.
def finalize():
pass
### Called when web clients need a list of available channels.
# Return a list of channel struct, e.g., [ { "name": NAME1, "type": TYPE1 }, ... ]
def get_channels():
return []
### Called when web clients request data.
# If the channel is not known, return None
# else return a JSON object of the data, in the format described in the Data Model document.
def get_data(channel):
return None
### Called when web clients send a command.
# If command is not recognized, return None
# elif command is executed successfully, return True
# else return False or { "status": "error", "message": ... }
def process_command(doc):
return None
[TODO] implement the full data-source interface
slowdash_project:
name: WorldClock
module:
file: worldclock.py
parameters:
timeoffset: -9
data_suffix: worldclock
# worldclock.py
import time, datetime
= 0
timeoffset
def initialize(params):
global timeoffset
= params.get('timeoffset', 0)
timeoffset
def get_channels():
return [
'name': 'WorldClock', 'type': 'tree'},
{
]
def get_data(channel):
if channel == 'WorldClock':
= time.time()
t = datetime.datetime.fromtimestamp(t)
dt return { 'tree': {
'UnixWorldClock': t,
'UTC': dt.astimezone(datetime.timezone.utc).isoformat(),
'Local': dt.astimezone().isoformat(),
'%+dh'%timeoffset: dt.astimezone(tz).isoformat()
}}
return None
# for testing
if __name__ == '__main__':
print(get_data(get_channels()[0]['name']))
Running the slowdash
command without a port number option shows the query result to screen. The query string is given as the first argument.
Two queries are useful to test the module:
channel
: query for a channel listdata/CHANNEL
: query for data for the channel$ slowdash channels
[{ "name": "WorldClock", "type": "tree" }]
$ slowdash data/WorldClock
{ "WorldClock": { "start": 1678801863.0, "length": 3600.0, "t": 1678805463.0, "x": { "tree": {
"UnixTime": 1678805463.7652955,
"UTC": "2023-03-14T14:51:03.765296+00:00",
"Local": "2023-03-14T15:51:03.765296+01:00",
"-9h": "2023-03-14T05:51:03.765296-09:00" }}}}
(the output above is reformatted for better readability)
To the example user data source above, add the following function:
def process_command(doc):
global timeoffset
try:
if doc.get('set_time_offset', False):
= int(doc.get('time_offset', None))
timeoffset return True
except Exception as e:
return { "status": "error", "message": str(e) }
return False
Make a HTML form to send commands from Web browser:
<form>
<input type="number" name="time_offset" value="0">
Time Offset (hours): <input type="submit" name="set_time_offset" value="Set">
</form>
Save the file at the config
directory under the user project direcotry. Add a new HTML panel with HTML file WorldClock
.
When the Set
button is clicked, the form data is sent to the user module as a JSON document. On the terminal screen where the slowdash command is running, you can see a message like:
POST: /slowdash.cgi/control
23-03-14 16:37:46 INFO: DISPATCH: {'set_time_offset': True, 'time_offset': '3'}
import logging
= logging.getLogger(name)
logger
logger.addHandler(logging.StreamHandler(sys.stderr))
logger.setLevel(logging.INFO)
def process_command(doc):
logger.info(doc) ...
import threading
= None
current_thread = False
stop_requested
def run():
while not stop_requested:
do_my_task()
def initialize(params):
global current_thread
= threading.Thread(target=run)
current_thread
current_thread.start()
def finalize():
global current_thread, stop_requested
if current_thread is not None:
= True
stop_requested current_thread.join()
class MyTask:
....
= MyTask()
my_task
def run():
my_task.start()while not stop_requested:
my_task.do_one()
my_task.stop()
def process_command(doc):
return my_task.process_command(doc)
def stop(signum, frame):
finalize()
if __name__ == '__main__':
import signal
signal.signal(signal.SIGINT, stop) initialize({})