Skip to content

Commit 841b5b6

Browse files
committed
refactor directory structure; add RTC wait on signal send
1 parent 6d52150 commit 841b5b6

File tree

8 files changed

+296
-96
lines changed

8 files changed

+296
-96
lines changed

README.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import logging
2222
import time
2323

2424
import scxml4py.helper
25-
from scxml4py.engine import SCXML_Engine
25+
from scxmlApp.application import Application
2626
from scxml4py.action import Action
2727
from scxml4py.activity import ThreadedActivity
2828
from scxml4py.event import Event
@@ -70,7 +70,7 @@ simpleSM_string = '''<?xml version="1.0" ?>
7070

7171
# SCXML source and data setup
7272
simple_sm_data = Data()
73-
simple_sm = SCXML_Engine(
73+
simple_sm = Application(
7474
simpleSM_string,
7575
actions=[ActionStatus],
7676
activities=[ActivityOnline],
@@ -161,27 +161,27 @@ Visual SCXML Editor for VsCode: https://marketplace.visualstudio.com/items?itemN
161161
APACHE 2.0 License © 2024 Luigi Andolfato, Robert Karban
162162

163163

164-
## 📚 SCXML_Engine API
164+
## 📚 Application API
165165

166-
The `SCXML_Engine` class provides a high-level API to load, execute, and interact with SCXML state machines.
166+
The `Application` class provides a high-level API to load, execute, and interact with SCXML state machines.
167167

168168
---
169169

170-
### 🔹 `SCXML_Engine(scxml_doc: str, actions, activities, data)`
170+
### 🔹 `Application(scxml_doc: str, actions, activities, data)`
171171

172172
#### **Constructor**
173173
Initializes the SCXML engine with a state machine definition and supporting elements.
174174

175175
| Parameter | Type | Description |
176176
|---------------|-----------|-------------|
177-
| `scxml_doc` | `str` | The SCXML document as a string. |
177+
| `theScxmlDoc` | `str` | The SCXML document as a string. |
178178
| `actions` | `list` | List of `Action` classes to be instantiated and injected. |
179179
| `activities` | `list` | List of `Activity` classes to be instantiated and injected. |
180180
| `data` | `Data` | Shared data object passed to actions and activities. |
181181

182182
#### **Example**
183183
```python
184-
engine = SCXML_Engine(
184+
engine = Application(
185185
scxml_doc=some_scxml_string,
186186
actions=[ActionStatus],
187187
activities=[ActivityOnline],
@@ -304,7 +304,7 @@ class ActivityOnline(ThreadedActivity):
304304
Register the activity:
305305

306306
```python
307-
engine = SCXML_Engine(scxml_doc, actions=[], activities=[ActivityOnline], data=my_data)
307+
engine = Application(scxml_doc, actions=[], activities=[ActivityOnline], data=my_data)
308308
```
309309

310310
---

src/scxml4py/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@
1111
from .state import *
1212
from .stateMachine import *
1313
from .transition import *
14-
from .engine import *

src/scxml4py/event.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ def getId(self):
4949
def getType(self, theType):
5050
self.mType = theType
5151

52-
def getStatus(self, theStatus):
53-
self.mStatus = theStatus
52+
def getStatus(self):
53+
return(self.mStatus)
5454

5555
def setId(self, theId):
5656
self.mId = theId

src/scxmlApp/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .application import Application
2+
from .actionmgr import ActionMgr

src/scxmlApp/actionmgr.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
class ActionMgr(object):
2+
# generated class
3+
def __init__(self):
4+
self.mActionList = list()
5+
self.mActivityList = list()
6+
7+
def getAction(self, theActionId):
8+
for a in self.mActionList:
9+
if a.getId() == theActionId:
10+
return a
11+
return None
12+
13+
def getActions(self):
14+
return self.mActionList
15+
16+
def getActivity(self, theActivityId):
17+
for a in self.mActivityList:
18+
if a.getId() == theActivityId:
19+
return a
20+
return None
21+
22+
def getActivities(self):
23+
return self.mActivityList
24+
25+
def createActions(self, theData, action_classes):
26+
self.mActionList = [cls(theData) for cls in action_classes]
27+
28+
def createActivities(self, theEventQueue, theData, activity_classes):
29+
self.mActivityList = [cls(theEventQueue, theData) for cls in activity_classes]

src/scxmlApp/application.py

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import logging
2+
import threading
3+
from queue import Queue
4+
5+
import scxml4py.helper
6+
from scxml4py.reader import Reader
7+
from scxml4py.context import Context
8+
from scxml4py.event import Event
9+
from scxml4py.executor import Executor
10+
from scxml4py.listeners import EventListener
11+
from scxml4py.action import Action
12+
from scxml4py.listeners import StatusListener
13+
14+
from scxmlApp.actionmgr import ActionMgr
15+
16+
# Installed as status listener, engine looks for "ActionStatus" action
17+
class _ActionStatus(Action, StatusListener):
18+
# implemented by the developer, stub can be generated
19+
def __init__(self, theData, theActionMgr):
20+
Action.__init__(self, "ActionStatus", None, theData)
21+
self.mStatus = None
22+
self.mActionMgr = theActionMgr
23+
24+
def notify(self, status):
25+
self.mStatus = status
26+
userActionStatus = self.mActionMgr.getAction("ActionStatusListener")
27+
if userActionStatus is not None:
28+
logging.getLogger("scxmlApp").debug (">>>>>>>>>>>>>>>>>>>>>>>>>>>userActionStatus found")
29+
userActionStatus.execute(status)
30+
logging.getLogger("scxmlApp").info(">>>>_ActionStatus::notify Status: <" + scxml4py.helper.formatStatus(self.mStatus) + ">")
31+
32+
# why do I need this?
33+
def execute(self, theCtx):
34+
logging.getLogger("scxmlApp").info(">>>>_ActionStatus::execute Status: <" + scxml4py.helper.formatStatus(self.mStatus) + ">")
35+
36+
37+
class _EventListener(EventListener):
38+
def __init__(self, theData, callback, theActionMgr):
39+
self.mEvent = None
40+
self.callback = callback
41+
self.theData = theData
42+
self.mActionMgr = theActionMgr
43+
44+
def notify(self, event):
45+
self.mEvent = event
46+
self.callback()
47+
userActionEvent = self.mActionMgr.getAction("ActionEventListener")
48+
if userActionEvent is not None:
49+
logging.getLogger("scxmlApp").debug (">>>>>>>>>>>>>>>>>>>>>>>>>>>userActionEvent found")
50+
userActionEvent.execute(event)
51+
logging.getLogger("scxmlApp").info(">>>>_EventListener::notify EventListener: <" + ">")
52+
logging.getLogger("scxmlApp").debug(event.getStatus())
53+
54+
55+
class _Application(threading.Thread):
56+
def __init__(self, theScxmlDoc:str, data, theEventQueue, actions, activities):
57+
threading.Thread.__init__(self)
58+
self.mRunning = False
59+
self.mEventQueue = theEventQueue
60+
self.mData = data
61+
self._event = threading.Event()
62+
63+
self.mActionMgr = ActionMgr()
64+
self.mActionMgr.createActions(theData=self.mData, action_classes=actions)
65+
self.mActionMgr.createActivities(theEventQueue=self.mEventQueue, theData=self.mData, activity_classes=activities)
66+
67+
self.mContext = Context()
68+
logging.getLogger("scxmlApp").info("_Application::Loading SCXML model")
69+
self.mModel = Reader().readString("modelName", theScxmlDoc, self.mActionMgr.getActions(), self.mActionMgr.getActivities())
70+
logging.getLogger("scxmlApp").debug("_Application::Loaded SCXML model: " + scxml4py.helper.formatModel(self.mModel))
71+
self.mExecutor = Executor(self.mModel, self.mContext)
72+
self.mActionStatus = _ActionStatus(theData=self.mData, theActionMgr = self.mActionMgr)
73+
self.mEventListener = _EventListener(theData=self.mData, callback=self.event_callback, theActionMgr = self.mActionMgr)
74+
self.mExecutor.addStatusListener(self.mActionStatus)
75+
self.mExecutor.addEventListener(self.mEventListener)
76+
logging.getLogger("scxmlApp").info("_Application::Status: <" + scxml4py.helper.formatStatus(self.mExecutor.getStatus()) + ">")
77+
78+
def run(self):
79+
logging.getLogger("scxmlApp").info("_Application::Starting execution of <" + self.mModel.getId() + ">")
80+
self.mRunning = True
81+
self.mExecutor.start()
82+
logging.getLogger("scxmlApp").info("_Application::Status: <" + scxml4py.helper.formatStatus(self.mExecutor.getStatus()) + ">")
83+
while self.mRunning == True:
84+
theEvent = self.mEventQueue.get(True, None)
85+
#Queue().get(block, timeout)
86+
# loop on the event queue send the event to the SM engine
87+
logging.getLogger("scxmlApp").debug("_Application::Application received event = <" + theEvent.__str__() + ">")
88+
self.mExecutor.processEvent(theEvent)
89+
if theEvent.getId() == "_EXIT":
90+
logging.getLogger("scxmlApp").debug("_Application::Application exiting...")
91+
self.mRunning = False
92+
logging.getLogger("scxmlApp").info("_Application::Stopping execution of <" + self.mModel.getId() + ">")
93+
self.mExecutor.stop()
94+
logging.getLogger("scxmlApp").info("_Application::Status: <" + scxml4py.helper.formatStatus(self.mExecutor.getStatus()) + ">")
95+
96+
def event_callback(self):
97+
self._event.set() # unblock the waiting method
98+
99+
def wait_for_event(self):
100+
self._event.wait() # this blocks until .set() is called
101+
102+
class Application:
103+
def __init__(self, scxmlDoc: str, actions, activities, data):
104+
self.doc = scxmlDoc
105+
self.event_queue = Queue()
106+
self.data = data
107+
108+
self.application = _Application(self.doc, self.data, self.event_queue, actions, activities)
109+
110+
def start(self):
111+
self.application.start()
112+
113+
def terminate(self):
114+
self.event_queue.put(Event("_EXIT"), True, 2)
115+
self.application.wait_for_event()
116+
self.application.join()
117+
118+
def get_current_status(self):
119+
return scxml4py.helper.formatStatus(self.application.mExecutor.getStatus())
120+
121+
def send_signal(self, signal_name:str, sync:bool=True, rtc_block:bool=True):
122+
self.event_queue.put(Event(signal_name), sync, 2)
123+
if rtc_block:
124+
self.application.wait_for_event()
125+

test/models/simpleSM.scxml

-10
This file was deleted.

0 commit comments

Comments
 (0)