You have a solution using subprocess but for posterity, I thought I'd post an example using a web service.
"""An example Ignition SCADA Gateway tag change event script that sends a simple JSON message to a flask
server when the production line starts and stops. The server runs alongside a tkinter window that
will shrink to a small semi-transparent button when the line starts running. The computer this runs
on was there for a reason, so obscuring the existing software was not an option. If there is an
unsaved defect record active in the window, it will pop back up when they stop so that they can update
it when finished.
This is quite quick. The line start has a ~5 second delay while a line-starting signal goes off. So, the
fraction of a second delay is nothing.
Ignition SCADA version == 8.1.17
"""
if not initialChange:
src_path = event.getTagPath().toString() # tag path that triggered this event
lam_num = Shared.L.get_num_from_path(src_path) # get the production line number from the path
# ip address for the industrial PC with the flask server depends on the line number
flask_server_url = {1: '10.155.0.102', 2: '10.155.0.201'}[lam_num] # static IP addresses!
defects_url = 'http://{}:5000/popup'.format(flask_server_url) # the address for for the flask endpoint
hc = system.net.httpClient() # this class can be used for POST and GET requests
# if the lam has started running, hide the popup
if newValue.getValue(): # boolean tags, so if the line is running, this is True
param_dict = {"action": "shrink"}
else:
param_dict = {"action": "show"}
result = hc.post(defects_url, params=param_dict).json
gist
You can see the Flask server and all the related mess here: https://github.com/HelloMorrisMoss/mahlo_popup
When this was set up we hadn't gotten the WebDev module yet, so when the operator creates a new defect record, by pushing the button, the available tag data is read directly from the Postgres/Timescale
database with the tag history using the psycopg2 library. (Timescale simplifies this since it "looks" like a single table.)
An instance of this runs on two production lines and one on a server where it provides the defect record data for a Dash defect lookup web interface and an automated report converter that includes the records in the final reports.
But, it's not a good place to start. So here's a much-pared down all-in-one file version using Flask and Flask-RESTful. Probably only use Python 3.8 if you're stuck on Windows 7 Compact Embedded edition, too.
"""Example flask endpoint for receiving http requests from Ignition (or whatever).
Based on https://github.com/HelloMorrisMoss/mahlo_popup
Python version == 3.8.4
Flask==2.0.2
Flask-RESTful==0.3.9
"""
from flask import Flask
from flask_restful import reqparse, Resource, Api
action_dict = { # shortened actions dictionary
'shrink':
{'debug_message': 'shrinking popup', # this is used in logging
'action_params': {'action': 'shrink'}, # this gets passed to the window
'return_result': ({'popup_result': 'Shrinking popup window.'}, 200), # sent back to post request
'description': 'Shrink the window to button size, no change if already button.', # just documentation atm
},
'show':
{'debug_message': 'showing popup',
'action_params': {'action': 'show'},
'return_result': ({'popup_result': 'Showing popup window.'}, 200),
'description': 'Show the full window if there are defects active, no change if already full.',
}
}
def action_function_dummy(action_data: dict):
"""The actual thing done here is to add the json->dict to a collections.deque for the tkinter
window to check and act on."""
print(f"I don't do anything! Here's my action data: {action_data=}")
class Popup(Resource):
"""This is used by flask-restful to provide end-point functionality."""
def post(self):
parser = reqparse.RequestParser()
parser.add_argument('action', type=str, required=True, help='You must provide a command action.')
data = parser.parse_args()
action_to_take = action_dict.get(data['action']) # get the action info from the dictionary above
if action_to_take is not None: # if the action data matches an action in the dict, do it
action_function_dummy(action_to_take['action_params'])
return action_to_take['return_result']
else:
return {'popup_result': 'No valid request.'}, 400 # or the action requested doesn't match, return that
def get(self):
"""Can also have other methods on the same endpoint."""
raise NotImplementedError('Build something here!')
# instantiate the flask app
app = Flask(__name__)
# add the restful endpoints
api = Api(app)
api.add_resource(Popup, '/popup')
if __name__ == '__main__':
app.run(debug=True)
gist