Interoperability with PhET Simulations
PhET Interactive Simulations are engaging, playful educational tools that make it easy for students to learn science and math content. PhET-iO augments PhET Simulations with interoperability features making them a much more flexible, powerful tool. Simulations can be customized for classrooms or individual learners, provide adaptive feedback, and provide data on student usage and progress.
Features
Customization
Configure the simulation to launch in a particular state or with customizations using the Query Parameter API. Simulation can be customized for classrooms or individiual learners, or for usage with a particular activity or question. Setting the start state, hiding controls or fixing values, changing text, choosing just one screen, etc.
Communication
Send commands to the simulation and receive messages from the simulation. A versatile API - which enables the surrounding technology to "talk" with the simulation, get data, freeze interaction, set values, take screenshots, or capture or set state.
Streaming Data
An event stream (data log) provides detailed messages about how the student used the simulation and how the simulation responded, this can be used for research purposes. The states of the simulation can also be streamed for analysis or playback.
Save/Load
the state of the simulation can be stored and restored
Record/Playback
Using states + deltas, the simulation can be recorded and played back like a movie
Documentation
Object Identifiers and Types
Each object in a simulation is associated with an identifier and a type. The identifier (a) appears in messages produced by the object in the data log (b) is used to configure the object and (c) is used to interact with the object dynamically. The type describes the nature of the object as well as methods for interacting with the object. For instance, a user interface component commonly found in PhET Simulations is a "reset all" button, which restores the simulation to its initial state. The identifier and type for a reset all button in a simulation may be given like this:// The Reset All button, which appears in the bottom right of the screen
resetAllButton: TResetAllButton
The identifier can be used to refer globally to the instance after it has been created, and the type shows what
kinds of
events and methods are provided by the object. In this case, the TResetAllButton provides an event to the data
stream
when the button is pressed.
// Names of events emitted by the button
events: [ 'fired' ]
Here is a larger example of the identifiers and names for PhET's Faraday's Law Simulation:
{
// The root for cross-cutting interoperable features
phetio: TPhETIO,
// Simulation-specific content
faradaysLaw: {
// The sim itself
sim: PhETIOCommon.getSimAPI(),
// The navigation bar, including the PhET Menu and menu items.
navigationBar: PhETIOCommon.getNavigationBarAPI(),
// Content in the screen
faradaysLawScreen: {
// Physical values in the model and settings
model: {
showSecondCoilProperty: TProperty( TBoolean ),
showMagnetArrowsProperty: TProperty( TBoolean ),
magnetModel: {
positionProperty: TProperty( TVector2 ),
flippedProperty: TProperty( TBoolean ),
showFieldLinesProperty: TProperty( TBoolean )
},
voltmeterModel: {
thetaProperty: TProperty( TNumber ) // the angle of the needle, in radians
}
},
// Visible objects
view: {
controlPanel: {
resetAllButton: TResetAllButton,
flipMagnetButton: TButton,
showFieldCheckBox: TCheckBox,
singleCoilRadioButton: TRadioButton( TBoolean ),
doubleCoilRadioButton: TRadioButton( TBoolean )
},
magnet: TNode.extend( {
dragHandler: TTandemDragHandler
} )
}
}
}
}
The full identifier for an object is given by the full path to reach the object. For example, the magnet's
position is 'faradaysLaw.faradaysLawScreen.magnet.position'. Noted that all
identifiers are prefixed by the name of the simulation and the name of the screen within the simulation to
facilitate
cross-simulation studies.
Query Parameters
Several query parameters govern the behavior of an instrumented PhET simulation:- phet-io.emitStates: Outputs the full state at the end of every frame.
- phet-io.emitDeltas: Outputs state keyframes every 10 seconds and deltas every frame
- phet-io.emitEmptyDeltas: When emitting states using phetio.js, emit deltas that are empty, to simplify playback in some systems.
- phet-io.log: If set to console, will stream phetioEvents to console. This is useful for understanding or debugging the phetioEvent log output in real-time. If using Chrome or Firefox, using phet-io.log=lines provides a colorized single-line output.
- phet-io.docs: Will output type documentation to the console, see https://github.com/phetsims/phet-io/issues/218
- phet-io.expressions: Evaluate expressions on phetio wrapper objects, like: http://localhost/faradays-law/faradays-law_en.html?ea&brand=phet-io&phet-io.log=console&phet-io.expressions=[["beaker.beakerScreen.soluteSelector","setVisible",[true]]] Described in more detail below.
Query Parameter API for instrumented instances
Instrumented objects in a simulation can be customized through a query parameter named phet-io.expressions. Here is an example of that query parameter:?phet-io.expressions=phet-io.expressions=[["beaker.beakerScreen.soluteSelector","setVisible",[true]]]
The list of available methods and types are provided below in the types section. A complete
an example of this is provided in customization.html. Please be aware that different
browsers have different limits on the length of query strings. If a query string becomes too long, it will simply
be truncated, leading to errors during parsing. Internet Explorer has the lowest maximum query string length at
approximately 2000 characters. This limit can be reached easily if setting many state variables through the query
string.
An alternative to configuration with query strings is to configure the simulation after it launches using the
iframe API.
Data Stream
Here is (the beginning of) a sample data log from the Color Vision simulation:{
"messageIndex": 0,
"eventType": "model",
"phetioID": "sim",
"componentType": "TSim",
"event": "simStarted",
"time": 1447285103220,
"parameters": {
"sessionID": null,
"simName": "Color Vision",
"simVersion": "1.1.0-phet-io.3",
"url": "http://www.colorado.edu/physics/phet/dev/html/phet-io/newschools/color-vision_en.html",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"provider": "PhET Interactive Simulations, University of Colorado Boulder"
}
}
{
"messageIndex": 1,
"eventType": "user",
"phetioID": "homeScreen.singleBulbScreenLargeButton",
"componentType": "TButton",
"event": "fired",
"time": 1447285111392,
"children": [
{
"messageIndex": 2,
"eventType": "model",
"phetioID": "sim.showHomeScreen",
"componentType": "TProperty",
"event": "changed",
"time": 1447285111393,
"parameters": {
"oldValue": true,
"newValue": false
}
}
]
}
{
"messageIndex": 3,
"eventType": "user",
"phetioID": "colorVision.singleBulbScreen.photonRadioButton",
"componentType": "TRadioButton",
"event": "fired",
"time": 1447285114104,
"parameters": {
"value": "photon"
},
"children": [
{
"messageIndex": 4,
"eventType": "model",
"phetioID": "colorVision.singleBulbScreen.beamType",
"componentType": "TProperty",
"event": "changed",
"time": 1447285114104,
"parameters": {
"oldValue": "beam",
"newValue": "photon"
}
}
]
}
Here is a brief description of each of the fields in a message:
Attribute | Description |
---|---|
messageIndex | The number of the message, starting with 0. Can be used to verify that the correct number of messages was received. |
eventType | {user|model} whether the message was originated by a user action or by the simulation itself. For instance, a button press is a user event while a state change in the simulation is a model event (even if it was triggered by a user event) |
phetioID | The unique identifier for the object sending the message. It usually starts with ${simulation-name}.${screen-name} except for globals outside of screens, or objects in the navigation bar. |
componentType | The name of the component type of the object that sent the message. Component type names begin with a capital letter T because they refer to Type wrappers. |
event | The name of the event that was fired by the object. |
time | The number of milliseconds since the epoch (in 1960) as reported by the client machine. No effort is made to verify that this matches any other clock. |
parameters | (optional) Key value pairs that describe the details of the event. |
children | (optional) Events that were triggered while this event was being processed. |
iframe API
The iframe API can be used to communicate with a running simulation. Methods can be called on any of the simulation instances. Commands can be sent to them, or values retrieved from them. Here's a full example of how to use the sim iframe API to toggle whether a simulation is active (running) or inactive (frozen), which also demonstrates how to send commands to the simulation and receive messages back from the simulation.
var sim = SimWrapperUtils.getSim( 'concentration' );
// Get a reference to the iframe for the sim
var simFrame = document.getElementById( 'sim' );
// This sim does not need any listeners to be added before the sim launches, so it can be phet-io.standalone
simFrame.src = sim.URL + '&ea&phet-io.standalone';
// Construct the sim iframe client that can be used to send messages to the sim.
var simIFrameClient = new SimIFrameClient( simFrame );
// Keep track of whether the sim is active or not
var lastActiveValue = true;
// When the user presses the "Toggle Active" button, send a command to the iframe
document.getElementById( 'toggleActiveButton' ).addEventListener( 'click', function() {
// Send a message using the simIFrameClient to invoke a method on an instance
simIFrameClient.invoke( sim.camelCaseName + '.sim.active', 'setValue', [ !lastActiveValue ] );
} );
// When the sim launches, wire up a listener to the active Property, so we can report its value and
// use the correct value when toggling
simIFrameClient.onSimInitialized( function() {
// Send a message using the simIFrameClient to link up to the active Property
simIFrameClient.invoke( sim.camelCaseName + '.sim.active', 'link', [ function( active ) {
lastActiveValue = active;
document.getElementById( 'readout' ).innerHTML = 'Active: ' + active;
} ]
);
} );
There are two ways to pass a function across the API, one which runs the function in the parent frame and one which
runs the function in the sim frame. For instance, in the above example the args has a function wrapped in an array.
This will run in the parent frame. If passing an object literal with {phetioID, method, args}, it will be invoked
synchronously in the simulation frame, see the source for state.html for an example.
Startup Sequence
In order to prevent missing any events sent from the simulation to the wrapper (in the case of multi-threaded browser support for multiple frames), PhET-iO provides a startup sequence with a step where the wrapper can register any listeners with phet-io, before the simulation launches. Here is an overview of the startup sequence:- the wrapper starts up and initializes its state
- the wrapper creates a listener with `window.addEventListener( 'message',...)`
- the wrapper sets the source of the simulation iframe
- the simulation preload files initialize (including phetio.js and related files)
- the simulation creates a listener with `window.addEventListener( 'message',...)`
- the simulation does postMessage indicating that it is ready for phet-io messages
- the wrapper receives the postMessage and sends a composite command to the simulation frame, containing adding any desired listeners, setting any desired startup configuration, etc and ending with the directive to start launching the requirejs portion of the simulation. Alternatively, this could be a sequence of individual messages, but the wrapper would have to wait for the response message for each before sending the final go-ahead to launch the requirejs portion of the simulation.
- the simulation receives the message to launch the requirejs portion and finishes launching normally.
// Wait for the phase where we can add listeners
simIFrameClient.onPhETiOInitialized( function() {
// Add any desired listeners to phet-io (cannot communicate with sim instances at this
// point since they have not been created yet
simIFrameClient.invoke( 'phetio', 'addPhETIOEventsListener', [ function( message ) {
textarea.innerHTML += '\n' + message;
textarea.scrollTop = textarea.scrollHeight;
} ], function() {
// after that is complete, launch the simulation
simIFrameClient.invoke( 'phetio', 'launchSimulation', [] );
}
);
} );
Types
Each instrumented instance in a PhET-iO simulation has a type which provides a set of methods which can be called from query parameters or across the iframe api. The events field declares which event names can be emitted by the type in the phetioEvents log. The "T" prefix in front of each type indicates that it is a thin "phet-io" wrapper type instead of the actual implementation simulation type. This section provides documentation for all of the common types, and was generated by running an instrumented simulation with the query parameter phet-io.docs. The list of all common types is given below:TArray
- setValue: () ➔ TVoid
- Sets the value of all elements in the array
TBoolean
TButton
events: fired
- fire: () ➔ TVoid
- Fire the button's action, as if the button has been pressed and released
TCheckBox
events: toggled
- isChecked: () ➔ TBoolean
- Returns true if the checkbox is checked, false otherwise
- link: (TFunctionWrapper) ➔ TVoid
- Link a listener to the underlying checked TProperty. The listener receives an immediate callback with the current value (true/false)
- setChecked: (TBoolean) ➔ TVoid
- Sets whether the checkbox is checked or not
TColor
TComboBox
events: fired,popupShown,popupHidden
TDerivedProperty
events: changed
- getValue: () ➔ TObject
- Gets the current value
- link: (TFunctionWrapper) ➔ TVoid
- Adds a listener which will receive notifications when the value changes and an immediate callback with the current value upon linking.
- unlink: (TFunctionWrapper) ➔ TVoid
- Removes a listener that was added with link
TEvents
events: documentation,typeName,methods,supertype,getMethodDeclaration,extend
- addListener: (TString,TFunctionWrapper) ➔ TVoid
- Adds a listener to the specified event channel
- removeListener: (TString,TFunctionWrapper) ➔ TVoid
- Removes a listener that was added with addListener
TFaucet
events: dragStarted,dragged,dragEnded,startTapToDispense,endTapToDispense
TMenuItem
events: fired
TMomentaryButton
events: pressed,released,releasedDisabled
TNode
- setRotation: (TNumber) ➔ TVoid
- Set the rotation of the node, in radians
- setVisible: (TBoolean) ➔ TVoid
- Set whether the node will be visible (and interactive)
TNumber
TObject
TObservableArray
events: itemAdded
- setValue: (TObject) ➔ TVoid
- Set all of the elements simultaneously
TOnOffSwitch
events: toggled
TPanel
TPhetButton
events: fired
TGroup(TObject)
TProperty(TObject)
events: changed
- getValue: () ➔ TObject
- Gets the current value.
- link: (TFunctionWrapper) ➔ TVoid
- Add a listener which will be called when the value changes. The listener also gets an immediate callback with the current value.
- setValue: (TObject) ➔ TVoid
- Sets the value of the property, and triggers notifications if the value is different
- unlink: (TFunctionWrapper) ➔ TVoid
- Removes a listener
TRadioButton
events: fired
TResetAllButton
events: fired
TSim
events: simStarted,state,stateDelta,frameCompleted,stepSimulation,inputEvent
- addEventListener: (TString,TFunctionWrapper) ➔ TVoid
- Add an event listener to the sim instance
- disableRequestAnimationFrame: () ➔ TVoid
- Prevents the simulation from animating/updating
- getScreenshotDataURL: () ➔ TString
- Gets a base64 representation of a screenshot of the simulation as a data url
THSlider
events: dragStarted,dragged,dragEnded
- setMajorTicksVisible: (TBoolean) ➔ TVoid
- Set whether the major tick marks should be shown
- setMinorTicksVisible: (TBoolean) ➔ TVoid
- Set whether the minor tick marks should be shown
TString
TPhETIO
- addInstanceAddedListener: (TFunctionWrapper) ➔ TVoid
- Adds a listener that receives a callback whenever a new sim instance has been prepared for interoperability
- addInstanceRemovedListener: (TFunctionWrapper) ➔ TVoid
- Removes a listener that was added with addInstanceAddedListener
- addPhETIOEventsListener: (TFunctionWrapper) ➔ TVoid
- Adds a listener to the phetioEvents event channel, for data analysis
- getState: () ➔ TObject
- Gets the full state of the simulation
- getPhETIOIDs: () ➔ TArray
- Gets a list of all of the wrapped instances which are available for interoperability.
- getValues: (TArray) ➔ TObject
- Get the current values for multiple Property/DerivedProperty at the same time. Useful for collecting data to be plotted, so values will be consistent.
- invokeInputEvent: (TString) ➔ TVoid
- Plays back a recorded input event into the simulation.
- setState: (TObject) ➔ TVoid
- Sets the full state of the simulation
- stepSimulation: (TNumber) ➔ TVoid
- Steps one frame of the simulation.
TToggleButton
events: toggled
TVector2
License
All PhET-iO files, including but not limited to: simulations instrumented for interoperability, API files, phet-io & tandem files, SimIFrameAPI.js and example HTML, JS and CSS files are Copyright 2014-2016, University of Colorado Boulder and can only be used with a license and express consent. For licensing, please contact phethelp@colorado.edu
.