“Life is a theatre set in which there are but few practicable entrances.”
― Victor Hugo, Les Misérables
MagicMirror module to change screen scenes by time and order with ANIMATION EFFECT.
Click it to see the DEMO.
Its configuration file is in /examples/config.js.example
Since MM 2.25, a new feature, animateCSS is introduced into the MagicMirror.
With this update, my previous MMM-Scenes would be obsoleted. So I remade a new module for MM 2.25
- The update can provide more effects now. ( without my effort. :D )
- custom animation is rarely used, and this update would cover most use cases. So I decided to drop it.
- I redesigned the structure more simply and intuitively. (
roleis introduced.)
The scenario of the MM screen is made up of a series of scenes. Each module has its role in its appearance scenes to enter and exit by design.
When a scene begins, all modules whose roles end will be expelled, and all modules with the parts in that scene will be admitted.
As described in the scenario, your MM screen will play a drama with modules.
- control show/hide modules by assigning role names to the module's class
- various animations for modules exit/enter
- control scenes by notification and WebURL endpoints.
- Loop control
- custom indicators
The recommended way to install the module is by cloning the repository into your MagicMirror modules directory:
cd ~/MagicMirror/modules
git clone https://github.com/MMRIZE/MMM-Scenes2To update the module, navigate to your MagicMirror modules directory and pull the latest changes from the repository:
cd ~/MagicMirror/modules/MMM-Scenes2
git pullDon't worry, it's not as difficult as it looks. You can find a real-world example in the
examplesdirectory.
{
module: "clock",
position: "top_left",
hiddenOnStartup: true,
classes: "role1 role_final" // <-- assign role(s) to the module to control.
},
// ... other modules ...
{
module: "MMM-Scenes2",
position: 'bottom_bar', // Position of indicator
config: {
scenario: [ // `scenario` is REQUIRED
{ // First scene definition
exit: ["role1", "role2"],
enter: ["role3", "role4"],
},
{ // Second scene definition
exit: ["role3"],
enter: ["role_final"],
},
]
}
},This
scenariohas 2 scenes. At the first scene,"role1"and"role2"module(s) will exit from the scene with default animation. Then"role3"and"role4"module(s) will enter into the scene. After some lifetime, the second scene will start."role3"module(s) will be disappeared and"role_final"scene will be revealed. ("role4"will remain at the second scene.) And the whole scenario will repeat.
In other words, the
clockmodule will exit from the first scene as"role1"and will enter into the second scene as"role_final".
config: {
scenario: [ ... ], // Array of scene objects. This is the only option MUST-REQUIRED. You should fulfil this option in your configuration.
autoStart: true, // start the first scene automatically
//Below are omittable. You don't have to describe all these options in your config.
life: 1000 * 60, // default life of each scene
activeIndicator: '■', // default indicator of current scene
inactiveIndicator: '□', // default indicator of other scenes inactive
// You can ignore the belows if you are not an expert.
lockString: 'mmm-scenes2', // lockString for hide mechanism
defaultEnter: { animation, duration, gap }, // convenient definition of default options for `enter`
defaultExit: { animation, duration, gap }, // convenient definition of default options for `exit`
}| property | default | description |
|---|---|---|
scenario |
[] | REQUIRED The order-set of scenes. You SHOULD set the scene definition (object) as the items of this property. |
autoStart |
true |
Start the first scene automatically when the module starts. Set to false to start scenes only through external control. |
life |
1000 * 60 | (ms) The life of each scene after all roles are appeared. After this time, the next scene would start. If set as 0, the scene would be paused unless external control(notification, telegram, ...) happens. |
activeIndicator |
'■' | Default indicator of current active scene. You can reassign it in each scene object. |
inactiveIndicator |
'□' | Default indicator of other inactive scenes. This could also be reassigned in each scene object. |
lockString |
'mmm-scenes2' | Just leave it if you don't know what this is for. |
defaultEnter |
{ animation, duration, gap } | Convenient definition of default options for enter. I'll explain later. |
defaultExit |
{ animation, duration, gap } | Convenient definition of default options for exit. I'll explain later. |
There is no
defaultNextordefaultPreviousbecausenextandpreviousshould differ according to the scene.
scenario would have some series of scene objects. Each object would have these structures.
scenario: [
{
enter: [ ... ],
exit: [ ... ],
name: 'first_scene',
life: 1000 * 30,
activeIndicator: '■',
inactiveIndicator: '□',
next: null, // Since 1.1.0
previous: null, // Since 1.1.0
},
// next scenes.
]- When you don't assign
nameby yourself,scene_N(scene_1, scene_2, ...) is assigned automatically. This name is used for external control, so it would be better to avoidprev,next,pause,resume,playas a scene name. life,activeIndicator,inactiveIndicatorare defined in global configuration, but they could be reassigned in the specificsceneobject by your needs.- When
lifeis set as0, this scene would stop until an external command arrives. (e.g. TelegramBot command). You can set this value as0on the last scene to play the scenario only once. - (new)
nextandpreviousis introduced since 1.1.0. The 2 fields would be used for control the order of scenes. It'll be explained later. enterandexitare the most important fields onsceneobject. See below.
scenario: [
{
enter: [ "role1", "role2", ... ],
// OR
enter: [
{
role: "role1",
animation: "bounceIn",
duration: 1500,
gap: 100,
},
{
role: "role2",
animation: "rotateIn",
}
],
// OR
enter: [
"role1",
{
role: "role2",
animation: "flipInY",
duration: 3000,
}
]
// ... other fields
}
// ... more
],Each enter and exit could have a list of roles. role could be the name which you assigned in classes of modules, or the object which has a definition of the role, or a mix of names and objects.
When you don't need to order different behaviours to the specific roles in the scene, the names are enough to direct which module will enter/exit.
role: the name of role-player module(s).animation: the name of animation. Currently, the possible animations are defined here. Or see thisduration: Speed of animationgap: Each role module transitions sequentially with this delay. If set as 0, all modules of this role start their transition simultaneously.
For your convenience, You can define defaultEnter and defaultExit for the common setting of all roles unless each value is reassigned in the specific scene.
config: {
defaultEnter: {
animation: 'fadeIn',
duration: 1000,
gap: 0,
},
defaultExit: {
animation: 'fadeOut',
duration: 1000,
gap: 0,
},
scenario: [ ... ],
...
},By default, the order of the scenes is linearly executed in the order listed in scenario:[...]. For example, The third scene is executed after the second scene, and so on.
However, there are cases where you may want to arbitrarily adjust the order of the scenes.
previous/nextis used to force the previous and next scenes in each scene, respectively. The possible kind of values are(sceneIndex),(sceneName),null,false, orthe callback functionwhich will return one of those values.
scenario: [
...
{
name: "scene_003",
exit: ["role1", "role2"],
enter: ["role3", "role4"],
next: "scene_005", // sceneName
previous: 2, // Or sceneIndex
},
...This example means the next scene of this scene would be "scene_005". When SCENES_PREV is called, the previous scene would be the 3rd scene in the scenario. (2 means 3rd because the index is zero-based.)
-
If you want to follow the original order in the scenario, just omit
next/previousor set them tonull(the default behavior). -
If you set it to
false, the flow would be blocked.next: falsemeans, you cannot forward anywhere from this scene.
next: false,
previous: false,This example means SCENES_PREV and SCENES_NEXT will not work once you enter this scene. You can still escape with SCENES_PLAY.
- Finally, instead of a static value, you can use a callback function to provide a value that changes dynamically depending on a condition. This can be useful when branching of the scenario is required.
next: ({ scene, scenario }) => {
// A Parameter `scene` would have the info of current scene.
// A parameter `scenario` would have the whole scenario information.
// console.log(scene, scenario)
return (Math.random() > 0.5) ? "scene_1" : "scene_2"
},This example shows the next scene being randomly selected between the automatically assigned names scene_1 and scene_2. You can replace this with any branching logic you need, such as selecting a normal or party scenario based on the time.
You can keep a normal loop running and switch to a separate scenario when another module sends a notification. Set next: false on the final special scene to keep it there until another command arrives.
scenario: [
{ name: 'SceneA', previous: 'SceneC', /* ... */ },
{ name: 'SceneB', /* ... */ },
{ name: 'SceneC', next: 'SceneA', /* ... */ },
{ name: 'SceneD', previous: false, /* ... */ },
{ name: 'SceneE', next: false, /* ... */ },
]The normal flow is SceneA -> SceneB -> SceneC -> SceneA. Sending SCENES_PLAY with scene: 'SceneD' switches to the special flow, which ends at SceneE. Send SCENES_PLAY with scene: 'SceneA' to return to the normal flow.
this.sendNotification('SCENES_PLAY', {
scene: 'SceneD',
callback: (result) => console.log(result),
})next and previous can be callback functions. They receive the current scene and the complete scenario, and must return a scene name, a zero-based scene index, null, or false.
const isSpecialSchedule = () => {
const now = new Date()
const isWeekend = now.getDay() === 0 || now.getDay() === 6
const isSpecificHours = now.getHours() > 7 && now.getHours() < 9
return isWeekend && isSpecificHours
}
scenario: [
{
name: 'SceneA',
next: () => isSpecialSchedule() ? 'SceneD' : 'SceneB',
previous: () => isSpecialSchedule() ? 'SceneE' : 'SceneC',
/* ... */
},
{ name: 'SceneB', /* ... */ },
{ name: 'SceneC', next: 'SceneA', /* ... */ },
{ name: 'SceneD', /* ... */ },
{ name: 'SceneE', next: 'SceneA', /* ... */ },
]When SceneA finishes, the callback selects either SceneB or SceneD. This allows a normal and a special schedule without another scheduler module.
Some syntax was changed from
MMM-Scenes. Check it carefully if you are a user of the previous module.
- Each incoming notification could have a
callbackfunction as a member of the payload. It will be called when your notification request is done.
this.sendNotification('SCENES_NEXT', {
callback: (result) => { console.log(result.status) }
})
// Callback result example
{
status: true,
currentScene: { name, enter, exit, ... },
index: 0,
message: "Example..."
}Play the next scene.
Play the previous scene.
Pause at the current scene until another command arrives.
Resume the scene. The remaining life at pause would be applied with this command.
You can also resume with other commands(e.g. SCENES_NEXT). In that case, the remaining life would be ignored, and the scene would play instantly.
Get information on the current scene.
Play a specific scene.
scene could be a name or an index of a scene in the scenario. If omitted, the current scene would be applied.
This notification is emitted after the scene transition has completed. The payload has the same shape as the result of SCENES_CURRENT.
You can access MM URL to control this module from outside of MM. e.g.) IFTTT.
http://magicmirror.domain/scenes/pause
http://magicmirror.domain/scenes/resume
http://magicmirror.domain/scenes/next
http://magicmirror.domain/scenes/prev
http://magicmirror.domain/scenes/0
http://magicmirror.domain/scenes/scene_2
You can control MMM-Scenes2 using the Telegram app by installing the MMM-TelegramBot module.
/scene info/scene pause/scene resume/scene next/scene prev/scene index:0/scene name:scene_2
You can assign indicators globally or scene-specifically.
config: {
activeIndicator: '■',
inactiveIndicator: '□',
scenario: [ ... ],
...
},If you have 4 scenes in the scenario, the indicator will be shown as □ ■ □ □(the second scene is active).
config: {
...
scenario: [
{ // First scene
activeIndicator: '❶',
inactiveIndicator: '①',
...
},
...
]
},Like this, you can reassign indicators for specific scenes. In this case, you can see ❶ □ □ □ or ① ■ □ □.
You can decorate the indicators with CSS in your custom.css; The structure of HTML created will be like this
<div class="scenes_indicator">
<span class="scenes_indicator_scene index_0 inactive first">□</span>
<span class="scenes_indicator_scene index_1 active">■</span> <!-- current scene -->
<span class="scenes_indicator_scene index_2 inactive last">□</span>
</div>You can decorate its look like this:
/* custom.css */
.scenes_indicator_scene.inactive {
color: gray;
}
.scenes_indicator_scene.active {
color: red;
font-weight: bold;
font-size: 200%;
}One more thing: You can change the scene by clicking/touching the indicator if your MM supports click/touch.
- I dropped out some features of
MMM-Sceneslikecustomized animationor some things in this module. If you need to implement it again, feel free to tell me. I'll consider it. - If the
lifeof a scene is set as0, that scene will not be forwarded to the next scene. You can use this feature to make control looping or some hidden scenes for specific purposes. - RPI3 or older/weaker SBC doesn't have enough power to handle the animation. In that case, use animation default or avoid serious effects.
See CHANGELOG.md for the release history.
- Seongnoh Yi (eouia0819@gmail.com)
If you find any problems, bugs or have questions, please open a GitHub issue in this repository.
Pull requests are of course also very welcome 🙂
Please see the Code of Conduct. By contributing you agree to its terms.
This project is licensed under the MIT License - see the LICENSE file for details.
Click To Play