sytabaresa / robot-python

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

robot-python

The Robot logo, with green background.

A small, blazing fast, functional zero-dependency and immutable Finite State Machine (StateCharts) library implemented in Python. Using state machines for your components brings the declarative programming approach to application state.

This is a python port of the popular JavaScript library robot with nearly identical API, still in optimization and with emphasis in general Python and MicroPython support. Tasks:

  • Python port, tested in MicroPython and python 3.6 as minimal version, older versions may don't work because ordered dicts requirement (see below for a workaround)
  • Same tests of JavaScript ported
  • Test passed
  • MicroPython support (RP2040 and Unix platform tested)
  • Used in a DIY Raspberry Pi Pico W project for a energy meter for a business ๐Ÿ˜‰
  • Extensive documentation (meanwhile check oficial robot documentation, has the same API)
  • General optimizations
  • MicroPython optimizations
  • More python tests
  • Create native machine code (.mpy) for MicroPython
  • (maybe) less dynamic, more performant API for constrained devices in MicroPython
  • ...

See thisrobot.life for documentation, but take in account that is in JavaScript.

Why Finite State Machines / StateCharts?

It is a robust paradigm for general purpose programming, but also recommended for high availability, performance and modeling sw/hw applications, is in use in so many applications such as software, embedded applications, hardware, electronics and many things that keep us alive. From an 8-bit microcontroller to a large application, the use of FSM/StateCharts can be useful to understand, model (and implement) solutions for complex logic and interactions environments.

Historically StateCharts were associated with a Graphical Modeling, but StateCharts don't limit to modeling and fancy drawings, libraries like this can be used to implement fsm/statechart as it in code! Even you donโ€™t need to draw something when you can start to program a FSM (see examples).

If only the Apollo 11 assembler programmers (1969) had known this paradigm (1984) before designing their electronic and user interface systems ๐Ÿฅฒ

Useful resources

Changes from original library

The API is nearly the same of the JS library, with some changes/gotchas:

  • JS objects are replaced with Python equivalents:
    • state definitions need to be dictionaries or objects with _ getitem _ method
    • events can be strings (equal as in the original library), objects with property type, dictionaries or objects with _ getitem _ method and type key
    • context doesn't has restrictions.
  • Some helpers were implemented as classes, more robust in type checking and with exact API that JS functions
  • JS Promises are implemented with async/await Python feature
  • Debug and logging helpers work as expected importing them
  • In MicroPython, you need to install typing stub package to support type annotations (zero runtime overhead)
  • In MicroPython or python version prior 3.6, you must provide initialState (first argument) in createMachine, because un-ordered dicts doesn't guarantee deduction of first state as initialState.

Examples

Minimal example:

from robot import createMachine, state, transition, interpret

machine = createMachine('off', {
    'off': state(
        transition('toggle', 'on')
    ),
    'on': state(
        transition('toggle', 'off')
    )
})

service = interpret(machine, lambda x: print(x))
print(service.machine.current)  # off
service.send('toggle')
print(service.machine.current)  # on

Nearly all features:

from robot import createMachine, guard, immediate, invoke, state, transition, reduce, action, state as final, interpret, Service
import robot.debug
import robot.logging


def titleIsValid(ctx, ev):
    return len(ctx['title']) > 5


async def saveTitle():
    id = await do_db_stuff()
    return id

childMachine = createMachine('idle', {
    'idle': state(transition('toggle', 'end', action(lambda: print('in child machine!')))),
    'end': final()
})

machine = createMachine('preview', {
    'preview': state(
        transition('edit', 'editMode',
                   # Save the current title as oldTitle so we can reset later.
                   reduce(lambda ctx: ctx | {'oldTitle': ctx['title']}),
                   action(lambda: print('side effect action'))
                   )
    ),
    'editMode': state(
        transition('input', 'editMode',
                   reduce(lambda ctx, ev: ctx | {'title': ev.target.value})
                   ),
        transition('cancel', 'cancel'),
        transition('child', 'child'),
        transition('save', 'validate')
    ),
    'cancel': state(
        immediate('preview',
                  # Reset the title back to oldTitle
                  reduce(lambda ctx: ctx | {'title': ctx['oldTitle']})
                  )
    ),
    'validate': state(
        # Check if the title is valid. If so go
        # to the save state, otherwise go back to editMode
        immediate('save', guard(titleIsValid), action(
            lambda ctx: print(ctx['title'], ' is in validation'))),
        immediate('editMode')
    ),
    'save': invoke(saveTitle,
                   transition('done', 'preview', action(
                       lambda: print('side effect action'))),
                   transition('error', 'error')
                   ),
    'child': invoke(childMachine,
                    transition('done', 'preview'),
                    ),
    'error': state(
        # Should we provide a retry or...?
    )
}, lambda ctx: {'title': 'example title'})


def service_log(service: Service):
    print('send event! current state: ', service.machine.current)


service = interpret(machine, service_log)
print(service.machine.current)
service.send('edit')
service.send('child')
service.child.send('toggle')

Testing

Tests are located in the tests/ folder, using unittest standard library. Run with this command or equivalent:

$ python -m unittest -v tests/*   

License

BSD-2-Clause, same of the original library :D

About


Languages

Language:Python 100.0%