jbeard4 / SCION

SCXML/Statecharts in JavaScript, moved to gitlab: https://gitlab.com/scion-scxml/scion
https://scion.scxml.io
Apache License 2.0
149 stars 29 forks source link

improve documentation concerning interface to statemachine #394

Closed pkoevesdi closed 6 years ago

pkoevesdi commented 6 years ago

Would be nice to have an example, how a scml has to look like, to get the model in scion syntax as explained here: https://github.com/jbeard4/SCION-CORE#entry-exit-and-transition-actions Or, how can I view a model in scion syntax that has been generated by urlToModel or similar functions?

I'd like to know, how I can put callback functions inside scxml to make them be executed by the statemachine? At the moment, I'm trying to figure out by reverse engineering. :-( Would be easier to get it explained. And the reverse engineering would be easier if I could check the model, that urlToModel gives out.

jbeard4 commented 6 years ago

Follow the quickstart:

scxml.urlToModel("drag-and-drop.xml",function(err,model){

  if(err) throw err;

  model.prepare(function(err, fnModel) {

    console.log(fnModel.toString())
    //OR
    console.log(fnModel())

  });
})
jbeard4 commented 6 years ago

Regarding callback functions, can you elaborate on your use case?

pkoevesdi commented 6 years ago

Thank You, the console.log(fnModel.toString()) is already illuminating. Still I don't find the stuff I put into the <onexit></onexit> tags in my scxml. Is that compiled too? Anyway, I don't need to see what's coming out the compiler, in case You could help me: I have a hardware board with buttons and leds. I want to determine the behaviour of the leds and the buttons with a scxml statechart. I want to edit this completely with a visual scxml editor. (To be able to make changes easily, also by other people, who understand a state machine, but not nessecarily Javascript.) My program maps the button presses to events I send with sc.gen. So far so good. But now I want to map the state or transition events to certain LED reactions. I can to this with the registerListener method. But two problems:

Here's a mockup of how I'd like to do it: hui.scxml:

<scxml initial="inactive" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
   <state id="state1">
  <onentry>setLED(1,true);</onentry>
  <onexit>setLED(1,false);</onexit>
<transition event="changestate"/>
<script>setLED(2,true);</script>
 </state>
</scxml>

Inside hui.js:

function setLED(number,state){
//do whatever to make LED <number> light up or extinguish
}

Is there any way to do it?

jbeard4 commented 6 years ago

You need to put the content of your onentry and onexit tags inside of a script tag.

jbeard4 commented 6 years ago

I'm working on an article that addresses the problem you are describing. I'll let you know when it's published. Thanks.

pkoevesdi commented 6 years ago

Hey,

can I ask You another question: I try to react on events sent from inside the statemachine by having <send event="testevent"/> inside the onEntry of a scxml file. I try to execute a function on this event:

sc.on('testevent', function (data) {
        console.log('Received event from sc! data:', data)
    });

but my Javascript doesn't receive the event. Is it supposed to work like this? It receives well on standard events like:

sc.on('onEntry', function (data) {
        console.log('Received event from sc! data:', data)
    });

By the way thank You very much for Your help! Would You have me rather make a new issue for each topic like this?

jbeard4 commented 6 years ago

The list of events emitted by the machine is described here: https://github.com/jbeard4/SCION-CORE#event-emitter

One way to "listen" for arbitrary events processed by the machine is by setting a listener on the onSmallStepBegin event.

pkoevesdi commented 6 years ago

Thanks.

The point is: I don't want to listen for any arbitary events (as I am able to do with onEntry as well), and then parse the event data to figure out, what to do. I rather want do define INSIDE the scxml what to to and when. So, outside I want to listen only for special events, that come intentionally out of the state machine only from certain states where I defined them to be sent (can be many states or transistions throughout the whole state machine, where I want to send the same certain event).

Otherwise I end up with having to maintain an event parser, which looks for the event data, before knowing what to do. So I'd have to keep a dictionary of the states, under which condition the action is to be taken. Sounds like mirroring parts of the state machine outside. And if the state machine changes, i.e. the name of a state, I'd have to go back into my source code of the program to reflect that change there.

Do You have an idea / solution for my problem? From the SCXML specification I understood: "<send> is used to send events and data to external systems, including external SCXML Interpreters, or to raise events in the current SCXML session." Is that first part not implemented yet? If so, can You confirm, that in SCION, there is no difference to <raise> at the moment? (From the spec: "The <raise> element raises an event in the current SCXML session.)

A backup solution could be, that instead of throwing events, I execute commands from inside the <script> area. But I thought, it's smarter to just use the <send> element. To be language independet and just use clean, standardized scxml elements.

pkoevesdi commented 6 years ago

Ok, temporaily I solved it with a interface node inside the statemachine which only passes the events it gets from somewhere inside to the outside:

<state id="sendEvents">
    <transition event="fireEvent" target="sendEvents">
     <script>sendOutside(machineId,_event.data);</script>
   </state>

Whereever in the statemachine I want to fire an action outside I put:

<send event="fireEvent">
      <param>name="data" expr="whatever"</param>
</send>

So, the machine I can keep quite clean of javaScript, only the one "sendEvents" state communicates to the outside.

Now I have a new question: How can I write data into the data model of the machine? I know, I can instantiate the machine with a snaphot. But on the running machine, can I write data into it?

jbeard4 commented 6 years ago

The only way you can manipulate the datamodel inside of the state machine is through sending events.

pkoevesdi commented 6 years ago

Ok, I saw that in scxml specs too now. Is there a way to overwrite some or all of the data from the <datamodel> on initialising time of the state machine or must I construct a whole snapshot?

How can I retrieve the _sessionid variable?

How about the <send>? Is it supposed to send events outside the scxml session in Scion? How can I listen for them? Or can I set callbacks somewhere?

jbeard4 commented 6 years ago

You can set data in the datamodel during initialization by using the snapshot property of initArgs. See: https://github.com/jbeard4/SCION-CORE#scgetsnapshot--snapshot

jbeard4 commented 6 years ago

The _sessionid variable is user-specified, and also passed in on initArgs as property "sessionid". See: https://github.com/jbeard4/SCION-CORE/blob/master/lib/scion.js#L695

jbeard4 commented 6 years ago

Send can be used for inter-session communication. Please refer to the specification for the syntax of send.

SCION's built-in send implementation only sends events to the outer queue of the statechart. The next version of SCION will support send for inter-session communication.

To implement your own inter-session send logic, you can implement your own custom send function by specifying "customSend" function on initArgs: https://github.com/jbeard4/SCION-CORE/blob/master/lib/scion.js#L1470

pkoevesdi commented 6 years ago

Hey. Thanks again for the input.

In https://github.com/jbeard4/SCION-CORE/blob/d7673342b18a91a4bf743301e0fcc42d1ce4415c/lib/scion.js#L602 you call the object sessionid, but elsewhere it's sessionId (camelCase). With that corrected, it works!

pkoevesdi commented 6 years ago

So, now I try to implement my own send function. Aim is to filter the events, that have actions outside (i.e. a function in myObj by the event name), and pass the others back in.

  const customSend = function (event) {
    if (!!(myObj[event.name])) myObj[event.name](event.data);
    else console.log(event.name,'passed back in, new state:',sc[event.data.channel].gen(event));
  }

which delivers Error: Cannot call gen during a big-step when calling sc.gen(). I understand that: inside a customSend I'm not allowed to change the state of the machine. But how can I solve this? How can I distinguish between internal events that should be raised inside and events I want to give out? I'd try it with <raise>, but there's no delay attribute, which I would be missing.

jbeard4 commented 6 years ago

You do this by inspecting the target of the send event. Please refer to the specification for how to interpret send/@target.

To avoid the error you encountered, wrap the call to gen in a setTimeout with 0 delay.

pkoevesdi commented 6 years ago

What has to go into target, if I want to receive the sent event in my node.js script, which has instantiated the scxml session? I tried around a bit, but As soon as I put something inside the target attribute, my customSend function doesn't get executed. Tried it with a simple test.scxml and repl.js (without customSend), and there it's the same: <send> is not executed, when target is (probably wrongly) specified. Also, there's the type attribute of <send>. In the scion Source, I find a "publish" type. What does that do?

The way I tried like above, getting all <send> events in my customSend, I cannot pass the events back into the state machine. setTimeout doesn't help, still get the 'Cannot call gen during a big-step' error. Tried it also with setTimeout of 1000ms.

So, my simplest, cooked down customSend function looks like:

const customSend = function (event) {
    setTimeout(sc.gen(event), 0);
  }

and I expect it to pass on the events back and evaluate them inside the scxml session. Isn't it supposed to work that way?

pkoevesdi commented 6 years ago

Ok, for the first I give up with an own customSend. I made a different approach, i have a looping transition somewhere in my model (always active), which looks like this:

<transition type="internal" event="myFunction">
            <script>device[_event.data.command](_sessionid,_event.data);</script>
        </transition>

Whenever i want to call home, I send for instance <send event="myFunction"><param name="command" expr="'setLED'"/><param .../></send>

jbeard4 commented 6 years ago

Great