{"id":17246832,"url":"https://github.com/mrd0ll4r/kaleidoscope","last_synced_at":"2026-02-04T03:31:36.329Z","repository":{"id":114221293,"uuid":"251003431","full_name":"mrd0ll4r/kaleidoscope","owner":"mrd0ll4r","description":"An Environment to Run Lua Programs on top of Submarine","archived":false,"fork":false,"pushed_at":"2025-01-05T16:41:35.000Z","size":194,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-07T06:51:20.922Z","etag":null,"topics":["lighting","lua","raspberry-pi","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","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/mrd0ll4r.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":"2020-03-29T10:14:19.000Z","updated_at":"2025-01-05T16:41:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"6e68e4ef-2d91-4187-80d2-5350abb9833e","html_url":"https://github.com/mrd0ll4r/kaleidoscope","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mrd0ll4r/kaleidoscope","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrd0ll4r%2Fkaleidoscope","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrd0ll4r%2Fkaleidoscope/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrd0ll4r%2Fkaleidoscope/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrd0ll4r%2Fkaleidoscope/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrd0ll4r","download_url":"https://codeload.github.com/mrd0ll4r/kaleidoscope/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrd0ll4r%2Fkaleidoscope/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279018715,"owners_count":26086609,"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","status":"online","status_checked_at":"2025-10-14T02:00:06.444Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["lighting","lua","raspberry-pi","rust"],"created_at":"2024-10-15T06:35:15.472Z","updated_at":"2025-10-14T13:31:35.950Z","avatar_url":"https://github.com/mrd0ll4r.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kaleidoscope\n\nAn environment to run Lua programs on top of Submarine.\n\n## Description\n\nThis is a program that connects to Submarine and executes Lua programs to control Output Devices attached to\nthe Submarine instance.\n\n## Configuration\n\nConfiguration is done via YAML files.\nThe main configuration file specifies basic properties and a path to a directory containing configuration files for\nthe individual fixtures:\n```yaml\n# Address of the AMQP server used to publish status updates.\namqp_server_address: \"amqp://192.168.88.30:5672/%2f\"\n# Address of the Submarine instance to post outputs to.\nsubmarine_http_url: \"http://192.168.88.30:3069\"\n# The address to expose Prometheus metrics on.\nprometheus_listen_address: \"0.0.0.0:4343\"\n# The address to expose the HTTP API on.\nhttp_listen_address: \"0.0.0.0:3545\"\n# The path from which to load fixtures and programs.\nfixtures_path: \"./fixtures\"\n```\n\n## HTTP API\n\nKaleidoscope is controlled via a JSON-over-HTTP API.\nCurrently, these routes are exposed:\n```\nGET  /api/v1/fixtures                                                        List fixtures.\nGET  /api/v1/fixtures/:fixture                                               Get single fixture.\nGET  /api/v1/fixtures/:fixture/programs                                      List programs for fixture.\nPOST /api/v1/fixtures/:fixture/set_active_program                            Set active program by name, provide the name as text in the body.\nPOST /api/v1/fixtures/:fixture/cycle_active_program                          Cycle to the next program, skipping MANUAL and EXTERNAL.\nGET  /api/v1/fixtures/:fixture/programs/:program                             Get single program.\nGET  /api/v1/fixtures/:fixture/programs/:program/parameters                  List parameters for program.\nGET  /api/v1/fixtures/:fixture/programs/:program/parameters/:parameter       Get single parameter.\nPOST /api/v1/fixtures/:fixture/programs/:program/parameters/:parameter       Set parameter value, provide an alloy::program::ParameterSetRequest as JSON in the body.\nPOST /api/v1/fixtures/:fixture/programs/:program/parameters/:parameter/cycle Cycle discrete parameter value.\n```\n\n## The Lua Runtime\n\nWe use [mlua](https://crates.io/crates/mlua), which means that our programs are Lua 5.4.\n\nOn a high level, the `Runtime` manages a list of `Fixtures`.\nEach `Fixture` has a list of `Program`s, of which one is currently active and being executed.\nEach `Program` can have a set of discrete and/or continuous `Parameters`.\n\nThe Runtime evaluates all Fixtures in parallel.\nEach round of executions is called a tick, of which we target `200/s`.\n\nAt the end of each tick, all outputs are aggregated into one request and sent to Submarine.\nSubmarine then processes that request mostly-atomically.\n\n### Fixtures\n\nA Fixture is a set of output addresses controlled by one active Program.\nIt defines a list of outputs and Programs, and configures other Fixture-wide parameters.\nA Fixture definition is itself a valid Lua program.\nOne Fixture is defined per input file, with the `setup` function setting up the Fixture.\n\nHere's an annotated example:\n```lua\nSOURCE_VERSION=3\n\nfunction setup()\n    -- Set a name for the Fixture.\n    fixture_name(\"klo_rgbw\")\n\n    -- Add outputs.\n    add_output_alias('klo-r')\n    add_output_alias('klo-g')\n    add_output_alias('klo-b')\n    add_output_alias('klo-w')\n\n    -- Whether to disable the builtin MANUAL program.\n    --disable_manual_program(true)\n    \n    -- Whether to disable the builtin ON and OFF programs.\n    --disable_builtin_programs(true)\n    \n    -- (Optional) programs to load.\n    add_program(\"noise\", \"foo/noise.lua\")\nend\n```\n\nThe functions available for Fixture setup are listed in [src/runtime/lua/fixture_builtin.lua](src/runtime/lua/fixture_builtin.lua).\n\n### Builtin Programs\n\nBy default, each Fixture has three programs generated for it:\n- `OFF`, which sets all outputs of the fixture to `LOW`.\n- `ON`, which sets all outputs of the fixture to `ON`.\n- `MANUAL`, which generates a continuous parameter for each output of the fixture and sets them according to the\n  parameter values.\n\n### Programs\n\nEvery Program must contain a `setup` function and a `tick` function.\nThe `setup` function is called once, during Program initialization.\nThe `tick` function is called in regular intervals if the program is currently active.\n\nDuring `setup`, a Program defines Parameters, which are mutable through the HTTP API.\nParameter values can then be accessed during the `tick` function.\n\nIn the context of `setup()`, a bunch of special functions can be called, which are not available later.\nSee [src/runtime/lua/program_builtin.lua](src/runtime/lua/program_builtin.lua) for a list.\n\n#### The `tick` Function\n\nAt the heart of every program is the `tick(now: f64)` function.\nIt takes one parameter, the current time in `f64` seconds since an unspecified epoch available as `START`.\n\nFor enabled Programs, the Runtime usually calls this function on each tick.\nPrograms can elect to be handled in \"slow mode\", which can be useful for outputs that do not require short reaction\ntimes or frequent updates.\n\nBecause of this, the `tick` function __must not rely on it being called in a regular interval__.\nAs an example: Do not increment a counter on each tick and calculate outputs based on it -- use the provided timestamp\nto calculate outputs.\n\nThe `tick` function can call other functions and do whatever Lua can do, but it should run as fast as possible.\nThe Runtime keeps track of both the global tick duration and `tick` durations for each program, which might be useful\nfor debugging.\n\n#### Slow mode\n\nUsually programs are run at every tick.\nSlow mode programs are run every 1000 ticks.\nThe reason for this is that some programs can probably deal with the added\nlatency, which frees some performance for the programs that need to execute\nevery tick.\n\n#### Builtins\n\nThe Runtime provides a bunch of builtin functions and constants, of which some are written in Rust and some in Lua.\nSee [src/runtime/lua/program_builtin.lua](src/runtime/lua/program_builtin.lua) for a complete list.\nNotable mentions:\n\n- `KALEIDOSCOPE_VERSION: int`, which denotes the version of the Runtime.\n- `START: f64` and `NOW: f64` denote the program epoch and current timestamp, both as `f64` seconds.\n- `noise2d(f64, f64) -\u003e f64` computes 2D Perlin noise in `[-1,1]`.\n    This is implemented in Rust and relatively fast.\n- `noise3d(f64, f64, f64) -\u003e f64` computes 3D Perlin noise in `[-1,1]`.\n    This is implemented in Rust and slower than the 2D version.\n- `noise4d(f64, f64, f64, f64) -\u003e f64` computes 4D Perlin noise in `[-1,1]`.\n    This is implemented in Rust and slower than the 3D version.\n- `now() -\u003e f64` gets the time in seconds since the program epoch.\n- `get_parameter_value(name)` gets the current value of the named parameter.\n- `clamp(from: numer, to: number, x: number) -\u003e number` clamps `x` to `[from, to]`. \n- `lerp(from: number, to: number, x: number) -\u003e number` interpolates between `from` and `to`.\n- `map_range(a_lower: number, a_upper: number, b_lower: number, b_upper: number, x: number) -\u003e number` maps `x` from the\n    first range to the second.\n- `map_to_value(from: number, to: number, x: number) -\u003e u16` maps `x` from `[from,to]` to the 16-bit Submarine value\n    range.\n- `output_alias_to_address(alias: string) -\u003e u16` translates an alias to a numerical address, if it exists.\n    Raises an error otherwise.\n- `set_alias(alias: string, value: u16)` sets the output at `alias` to `value`.\n    Make sure to call this with integers, probably breaks with non-integers...\n\n## Example Programs\n\nHere's an example program that sets four channels of an `RGBW` output to a Perlin-noise color:\n\n```lua\nSOURCE_VERSION=3\n\n-- Constants\nlocal r = 0\nlocal g = 1\nlocal b = 2\nlocal w = 3\nlocal sine_speed = 0.07\nlocal noise_speed = 0.1\n\n-- Parameters\nlocal MODE_BRIGHTNESS_NAME = \"brightness\"\nlocal MODE_BRIGHTNESS_NIGHT = \"night\"\nlocal MODE_BRIGHTNESS_DAY = \"day\"\n\n-- Variables\nlocal current_brightness_mode = MODE_BRIGHTNESS_NIGHT\n\nfunction setup()\n    local p_brightness = new_discrete_parameter(MODE_BRIGHTNESS_NAME)\n    add_discrete_parameter_level(p_brightness, MODE_BRIGHTNESS_NIGHT, \"dunkel\")\n    add_discrete_parameter_level(p_brightness, MODE_BRIGHTNESS_DAY, \"hell\")\n    declare_discrete_parameter(p_brightness)\nend\n\nfunction compute_white(index, now)\n    local t = now * sine_speed\n\n    if current_brightness_mode == MODE_BRIGHTNESS_DAY then\n        return map_to_value(0, 1, map_range(-1, 1, 0.9, 1, math.sin(t + (math.pi / 4) * index)))\n    end\n    return map_to_value(0, 1, map_range(-1, 1, 0.7, 0.8, math.sin(t + (math.pi / 4) * index)))\nend\n\nfunction compute_color(index, now)\n    local t = now * noise_speed\n\n    if current_brightness_mode == MODE_BRIGHTNESS_DAY then\n        return map_to_value(0, 1, map_range(-1, 1, 0.8, 1, noise2d(index, t)))\n    end\n    return map_to_value(0, 1, map_range(-1, 1, 0.5, 0.9, noise2d(index, t)))\nend\n\nfunction tick(now)\n    current_brightness_mode = get_parameter_value(MODE_BRIGHTNESS_NAME)\n\n    set_alias('klo-w', compute_white(w, now))\n    set_alias('klo-r', compute_color(r, now))\n    set_alias('klo-g', compute_color(g, now))\n    set_alias('klo-b', compute_color(b, now))\nend\n```\n\n## Compilation \u0026 Running\n\nSee the [README of Submarine](../submarine/README.md), which explains setup and cross-compilation for Linux on a Raspberry Pi.\n\nIn general, while it is not required to run Kaleidoscope on the same machine as Submarine,\nwe have observed that this benefits lighting performance because of lower latency variance.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrd0ll4r%2Fkaleidoscope","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrd0ll4r%2Fkaleidoscope","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrd0ll4r%2Fkaleidoscope/lists"}