{"id":24510349,"url":"https://github.com/juliarobotics/functionalstatemachine.jl","last_synced_at":"2025-08-13T01:03:08.714Z","repository":{"id":54434174,"uuid":"188447549","full_name":"JuliaRobotics/FunctionalStateMachine.jl","owner":"JuliaRobotics","description":"Build a state machine in Julia based on functions along with stepping and visualization tools  ","archived":false,"fork":false,"pushed_at":"2024-12-17T08:33:34.000Z","size":123,"stargazers_count":14,"open_issues_count":6,"forks_count":2,"subscribers_count":3,"default_branch":"develop","last_synced_at":"2025-02-22T11:18:48.006Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JuliaRobotics.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-24T15:38:31.000Z","updated_at":"2024-06-04T06:12:20.000Z","dependencies_parsed_at":"2024-06-04T06:41:39.255Z","dependency_job_id":"309e99af-b2b8-488a-add8-b330d305c36c","html_url":"https://github.com/JuliaRobotics/FunctionalStateMachine.jl","commit_stats":{"total_commits":74,"total_committers":4,"mean_commits":18.5,"dds":0.3783783783783784,"last_synced_commit":"a534cb2bf1cd0571c5422034f8febca1b38fe4b9"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuliaRobotics%2FFunctionalStateMachine.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuliaRobotics%2FFunctionalStateMachine.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuliaRobotics%2FFunctionalStateMachine.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuliaRobotics%2FFunctionalStateMachine.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JuliaRobotics","download_url":"https://codeload.github.com/JuliaRobotics/FunctionalStateMachine.jl/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243713392,"owners_count":20335566,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-22T00:28:33.815Z","updated_at":"2025-03-15T09:40:38.855Z","avatar_url":"https://github.com/JuliaRobotics.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FunctionalStateMachine.jl\n\n[![CI](https://github.com/JuliaRobotics/FunctionalStateMachine.jl/actions/workflows/CI.yml/badge.svg)](https://github.com/JuliaRobotics/FunctionalStateMachine.jl/actions/workflows/CI.yml)\n[![codecov.io](https://codecov.io/github/JuliaRobotics/FunctionalStateMachine.jl/coverage.svg?branch=master)](https://codecov.io/github/JuliaRobotics/FunctionalStateMachine.jl?branch=master)\n\n[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/JuliaRobotics/FunctionalStateMachine.jl.svg)](https://github.com/JuliaRobotics/FunctionalStateMachine.jl/issues)\n[![Percentage of issues still open](https://isitmaintained.com/badge/open/JuliaRobotics/FunctionalStateMachine.jl.svg)](https://github.com/JuliaRobotics/FunctionalStateMachine.jl/issues)\n\nBuild a state machine in Julia based on functions along with stepping and visualization tools  \n\n\n## Video Animation Example\n\nClick the Vimeo image as link to a FSM generated video animation of six concurrent state machines (as used in [IncrementalInference.jl](http://www.github.com/JuliaRobotics/IncrementalInference.jl)).\n\n[![Clique State Machine Concurrent Animation](https://user-images.githubusercontent.com/6412556/92198487-87b10900-ee42-11ea-8674-4a3867a74b65.png)](https://vimeo.com/454616769 \"Clique State Machine Concurrent Animation - Click to Watch!\")\n\n# Installation\n## [OPTIONAL] System Dependencies\nVisualization tools require a system install of `graphviz`.  Do Ubuntu/Debian Linux equivalent of:\n```bash\nsudo apt-get install graphviz\n```\n\n## Install Julia Package\nJulia ≥ 0.7 add package\n```julia\njulia\u003e ]\n(v1.5) pkg\u003e add FunctionalStateMachine\n```\n\n# Example\n\n## Basic\n```julia\nusing FunctionalStateMachine\n\n## User state functions\nfunction bar!(usrdata)\n  println(\"do bar!\")\n  return FunctionalStateMachine.exitStateMachine\nend\n\nfunction foo!(usrdata)\n  println(\"do foo!\")\n  return bar!\nend\n\n# no user data struct defined, so just pass Nothing\nstatemachine = StateMachine{Nothing}(next=foo!)\nwhile statemachine(nothing, verbose=true); end\n\n# or maybe limit number of steps\nstatemachine = StateMachine{Nothing}(next=foo!)\nwhile statemachine(nothing, iterlimit=1); end\n```\n\n### Watchdog Timeout\n\nSometimes it is useful to know that an FSM process will exit, either as intended or by throwing an error on timeout (much like a [Watchdog Timer](https://en.wikipedia.org/wiki/Watchdog_timer)).  FSM uses Base.`InterruptException()` as a method of stopping a task that expires a `timeout::Real` [seconds].  Note, this functionality is not included by default in order to preserve a small memory footprint.  To use the timeout feature simply call the state machine with a timeout duration:\n```julia\nuserdata = nothing # any user data of type T\ntimeout = 3.0\nwhile statemachine(userdata, timeout, verbose=true); end\n```\n\n### Recording Verbose Output to File\n\nExperience has shown that when a state machine gets stuck, it is often useful to write the `verbose` steps out to file as a bare minimum guide of where a system might be failing.  This can be done by passing in a `::IOStream` handle into `verbosefid`:\n```julia\nfid = open(\"/tmp/verboseFSM_001.log\",\"w\")\nwhile statemachine(userdata, verbose=true, verbosefid=fid); end\nclose(fid)\n```\n\nThis particular structure is chosen so that `@async` or other multithreaded uses of FSM can still write to a common `fid` and also allow the user to `flush(fid)` and `close(fid)` regardless of whether the FSM has stalled.  Might seem \"boilerplate-esque\", but it's much easier for developers to snuff out bugs in highly complicted interdependent and multithreaded, multi-state-machine architectures.\n\n## With User Data and History\n\n```julia\n## Passing a data structure\nmutable struct ExampleUserData\n  x::Vector{Float64}\nend\n\n# or maybe record the state machine history\nstatemachine = StateMachine{ExampleUserData}(next=foo!)\neud = ExampleUserData(randn(10))\nwhile statemachine(eud, recordhistory=true); end\n\n# recover recorded state transition history, `::Vector{Tuple{DateTime,Int,Function,T}}`\nhist = statemachine.history\n\n# or maybe rerun a step on the data as it was at that time -- does not overwrite previous memory\nnew_eud_at_1 = sandboxStateMachineStep(hist, 1)\n```\n\n## Draw State Pictures with Graphviz\n\n```julia\n# ]add Graphs # in case the dependency is not installed yet\n\nusing Graphs\n\n# run the state machine\nstatemachine = StateMachine{ExampleUserData}(next=foo!)\neud = ExampleUserData(randn(10))\nwhile statemachine(eud, recordhistory=true); end\n\n# draw the state machine\nhist = statemachine.history\ndrawStateMachineHistory(hist, show=true)\n```\n\n## Multiple state machines can be visualized together\n```julia\nusing Graphs, FunctionalStateMachine\n\n#...\n\n# start multiple concurrent FSMs (this is only one)\n## they are likely interdependent\nstatemachine = StateMachine{Nothing}(next=foo!)\nwhile statemachine(nothing, recordhistory=true); end\n\n# add all histories to the `hists::Dict` as follows\n## ths example has userdata of type ::Nothing\nhists = Dict{Symbol,Vector{Tuple{DateTime,Int,Function,Nothing}}}(:first =\u003e statemachine.history)\n\n# generate all the images that will make up the video\nanimateStateMachineHistoryIntervalCompound(hists, interval=1)\n\n# and convert images to video with ffmpeg as shell command\nfps = 5\nrun(`ffmpeg -r 10 -i /tmp/caesar/csmCompound/csm_%d.png -c:v libtheora -vf fps=$fps -pix_fmt yuv420p -vf \"scale=trunc(iw/2)*2:trunc(ih/2)*2\" -q 10 /tmp/caesar/csmCompound/out.ogv`)\n@async run(`totem /tmp/caesar/csmCompound/out.ogv`)\n```\ncan combine multiple concurrent histories of the state machine execution into the same image frames.  See function for more details.\n\n\n# Lower Level Visualization tools\n\n## Animate Asyncronous State Machine Transitions\n\nThe following example function shows several state machines that were run asyncronously can be synchronously animated as separate frames (see below for single frame with multiple information):\n```julia\nusing Dates, DocStringExtensions\n\n\"\"\"\n    $SIGNATURES\n\nDraw many images in '/tmp/?/csm_%d.png' representing time synchronized state machine\nevents.\n\nNotes\n- State history must have previously been recorded.\n\"\"\"\nfunction animateStateMachines(histories::Vector{\u003c:Tuple}; frames::Int=100)\n\n  startT = Dates.now()\n  stopT = Dates.now()\n\n  # get start and stop times across all cliques\n  first = true\n  # hist = somestatemachine.history\n  for hist in histories\n    if hist[1][1] \u003c startT\n      startT = hist[1][1]\n    end\n    if first\n      stopT = hist[end][1]\n    end\n    if stopT \u003c hist[end][1]\n      stopT= hist[end][1]\n    end\n  end\n\n  # export all figures\n  folders = String[]\n  count = 0\n  for hist in histories\n    count += 1\n    retval = animateStateMachineHistoryByTime(hist, frames=frames, folder=\"sm$count\", title=\"SM-$count\", startT=startT, stopT=stopT)\n    push!(folders, \"sm$count\")\n  end\n\n  return folders\nend\n\n# animate the time via many png images in `/tmp`\nanimateCliqStateMachines([hist1; hist2], frames=100)\n```\n\nThis example will result in 100 images for both `hist1, hist` state machine history. Note the timestamps are used to synchronize animations images on concurrent state traversals, and can easily be made into a video with OpenShot or ffmpeg style tools.\n\n## Previous Linear Time Multi-FSM Animation\n\nA closely related function\n```julia\nanimateStateMachineHistoryByTime\n```\n\n\n# Contribute\n\nContributions and Issues welcome.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuliarobotics%2Ffunctionalstatemachine.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuliarobotics%2Ffunctionalstatemachine.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuliarobotics%2Ffunctionalstatemachine.jl/lists"}