{"id":26353746,"url":"https://github.com/apertus-open-source-cinema/nctrl","last_synced_at":"2025-03-16T11:28:05.436Z","repository":{"id":37178694,"uuid":"197943912","full_name":"apertus-open-source-cinema/nctrl","owner":"apertus-open-source-cinema","description":"A centrarlized hardware abstraction layer for AXIOM cameras","archived":false,"fork":false,"pushed_at":"2023-01-20T22:39:02.000Z","size":932,"stargazers_count":7,"open_issues_count":6,"forks_count":7,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-03-20T03:32:47.790Z","etag":null,"topics":["axiom-cameras","hacktoberfest","lua","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/apertus-open-source-cinema.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSES/AGPL-3.0-only.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-20T14:50:37.000Z","updated_at":"2024-01-09T13:48:43.000Z","dependencies_parsed_at":"2023-01-22T20:01:12.537Z","dependency_job_id":null,"html_url":"https://github.com/apertus-open-source-cinema/nctrl","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apertus-open-source-cinema%2Fnctrl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apertus-open-source-cinema%2Fnctrl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apertus-open-source-cinema%2Fnctrl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apertus-open-source-cinema%2Fnctrl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/apertus-open-source-cinema","download_url":"https://codeload.github.com/apertus-open-source-cinema/nctrl/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243860247,"owners_count":20359692,"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":["axiom-cameras","hacktoberfest","lua","rust"],"created_at":"2025-03-16T11:28:04.943Z","updated_at":"2025-03-16T11:28:05.425Z","avatar_url":"https://github.com/apertus-open-source-cinema.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\nSPDX-FileCopyrightText: © 2019 Robin Ole Heinemann \u003crobin.ole.heinemann@gmail.com\u003e\nSPDX-License-Identifier: CC-BY-SA-4.0\n--\u003e\n\n# AXIOM ctrl\nA driver for controlling AXIOM cameras.\n\n## Getting started\nCurrently a somewhat recent stable [rust compiler](https://www.rust-lang.org/tools/install)  is required. \nFurthermore you need to install `libfuse` and the development headers for it (called `libfuse-dev` on debian derivatives). \n\nClone the repository using\n```bash\ngit clone --recursive https://github.com/apertus-open-source-cinema/nctrl\n```\n\nIn this directory you can use `cargo run` to start the control daemon. \nFor example:\n```bash\ncargo run -- -d nctrl_mountpoint -m camera_descriptions/beta/beta.yml\n```\nThis starts the control daemon with the `beta` registers and using `nctrl_mountpoint` as mountpoint for the fuse API.\n\n## Working Principle\nThe code in this repository takes care of all the low level communication to the hardware\nof the camera (ie. the image sensor). This is done with a variety of protocols (ie. `i2c`\nor memory access to shared memory regions with the FPGA).\n\nSimilar to a Linux kernel driver, a filesystem hierarchy is exposed, which represents the\ndifferent parameters of the hardware.\n\nExposing the parameters as a filesystem allows for simple solutions for a wide veriety\nof use cases:\n1) Write/ Read single parameters:\n    ```bash\n    $ cat /axiom_api/devices/cmv12000/cooked/pga_gain/value\n    1\n    $ echo -n \"2\" \u003e /axiom_api/devices/cmv12000/cooked/pga_gain/value # sets the analog gain to 2×\n    ```\n2) List available parameters:\n    ```bash\n    $ ls /axiom_api/devices/cmv12000/cooked/\n\tpga_gain pga_div ...\n    ```\n3) Get information about parameters:\n    ```bash\n    $ cat /axiom_api/devices/cmv12000/cooked/pga_gain/description\n\tanalog gain\n    ```\n\nThis simple abstraction allows to easily create powerful tools that build upon ctrl, like the register explorer of the [webui](https://github.com/axiom-micro/webui).\n\n![webui screenshot](img/webui_screenshot.png)\n\n## No Kernel Space Code\nNo kernel code is needed to expose the outlined functionality and `FUSE` is used\nto implement the filesystem. This gives better debuggability and allows us to code\nrust instead of kernel style C at the cost of some performance penalty and loosing the ability to handle\ninterrupts.\n\n\n## Developing locally\n\u003e **:warning: This Project uses submodules!**  \n\u003e **Either use `git clone --recursive` or run `git submodule update --init -- recursive` after cloning, otherwise the build will fail!**\n```bash\n$ mkdir ./axiom_api\n$ cargo run -- --mock --mountpoint ./axiom_api camera_descriptions/beta/beta.yml\n```\n\n\n## Concepts\nThe control daemon parses a `YAML` file that describes the camera setup, the available `devices`, globals / functions, lua `scripts` and initialization tasks.\n### devices\nThe `devices` block lists the available devices and their parameters. Each `device` consist of four parts:\n1) A communication channel, that specifies how registers are read and written. This can for example be a memory mapped region or a i2c device. The different communication channels are implemented in rust and the configuration file specifies the necessary parameters. For example\n   ```yaml\n   channel:\n   mode: \"i2c-cdev\"\n\t bus: 0\n\t address: 0x10\n   ``` \n2) Raw registers, that assign a address a name and potentially some metadata like the width of the register, a description, min and max values or a default. \n   ```yaml\n   temp_sensor:\n     address: 127\n     width: 2\n     default: 0\n     description: \u003e\n        Read-Only. Contains a value for calculating the sensor temperature.\n   ```\n3) Cooked registers, that assign a bit slice of a raw register or a address a name, metadata like the raw registers and potentially a value map. This map can map the raw register values to either floats, ints or strings. If such a map is present, reading a cooked register automatically returns the value assigned by the map and writing to such a register converts the given value to the raw value using this map. For example:\n   ```yaml\n   pga_gain:\n     address: pga[0:3]\n     description: analog gain\n     map:\n       0: 1\n       1: 2\n       3: 3\n       7: 4\n   ```\n   This assigns a raw value of 0 the *cooked* value 1, the raw value 1 the *cooked* value 2 and so forth. Writing 4 to this register would write 7 to the first three bits of the raw register `pga`. If the first three bits of the raw register `pga` contain the value 1 reading this register would return 2.\n4) Computed registers, that allow for arbitrary lua scripts to read and write a combination of registers. This could for example be used to provide a way to directly set a ISO value, which then sets a combination of digital gain, analog gain and potentially other registers. For example: \n   ```yaml\n   analog_gain:\n     description: \"Sets the analog gain\"\n     type: float\n     get: return cooked.coarse_gain * cooked.fine_gain\n     set: \u003e\n       local coarse = math.floor(value)\n       local fine = value / coarse\n       cooked.coarse_gain = coarse\n       cooked.fine_gain = fine\n   ```\n   This would provide a computed register for setting and reading the analog gain on the `ar0330` image sensor.\n\n### globals\nThe `globals` block provides a way to set globals like for example the name of the default bitstream to load, or the frequency of the clock that is provided by this bitstream to the image sensor.\n```yaml\nextclock: 24000000\ndefault_bitstream: no_patch.bit.bin\n```\nWhen using globals from `lua` it is additionally possible to write lua to represent a global value. This can for example be used to provide global helper functions for lua scripts or build one global parameter from other globals. For example:\n```yaml\ngain: |\n  function (reg_a, reg_b)\n      return reg_a * reg_b\n  end\ndefault_gain: gain(1, 2)\n```\nIn lua scripts the global variable `gain` would then be a function that takes two arguments and returns their product and the global variable `default_gain` would have the value of `2`.\n\n\n**NOTE**: rust scripts see these global constants written in lua as the string containing the `lua` code and not their actual evaluated value!\n### scripts\nThe `scripts` block allows to specify lua scripts. Lua scripts are snippets of `lua` code that interact with multiple `devices` at once. During the execution of a script, no other access to the devices used by the script is allowed. For example when starting up a image sensor a series of different registers writes of different devices is often necessary, which should not be interrupted by register accesses / writes by others.  \n\nEach `script` has a `description`, a list of devices it `uses` and optionally a list of argument names and types `args`. The `lua` code can access the `raw`, `cooked` and `computed` registers of the devices by reading from / writing to the `device_name.{raw, cooked, computed}` table. For example to read the `analog_gain` `raw` register of the `ar0330` device it can use `ar0330.raw.analog_gain`. To assign a value to this register: `ar0330.raw.analog_gain`. Scripts can access `globals` simply by their name. A complete script looks like this: \n``` yaml\ntest:\n  description: A simple test script\n  uses:\n    - ar0330\n  script: |\n    print(\"a\", a)\n    print(\"b\", b)\n    print(\"c\", c)\n    print(\"d\", d)\n    print(extclock)\n    scripts.test2(devices, { arg1 = 1.23, arg2 = \"test\"})\n    ar0330.raw.analog_gain = 3\n    return ar0330.computed.analog_gain\n  args:\n    a: int\n    b: float\n    c: string\n    d: binary\n```\nScripts can call other scripts by using the `scripts` table. A script has two arguments, a table containing devices, this table is automatically provided in the `devices` variable and a optional table with arguments. Finally scripts can also return a value.\n\n\nScripts can be run from `FUSE` by reading the `value` file in their directory. The value returned by the script is then received. Arguments of scripts can be assigned by writing to files in the `args` subfolder.\n### init\nFinally the `init` block contains a lua script that is executed after the control daemon is started and before any other access is allowed. This allows to initialize important devices, like loading a bitstream to a FPGA or initializing power supplies. Example:\n```yaml\ninit: |\n  ar0330.computed.analog_gain = 2\n```\n\n\n## Rust scripts \nSimilar to lua scripts it is also possible to write scripts in rust. These have the same interface as lua scripts and can even call each other. Rust scripts can for example be used to implement performance critical tasks. They are defined using the `script!` macro. For example:\n```rust\nscript! {\n    \"hard resets the sensor and brings it into standby\\n\"\n    Reset { test: u8 } =\u003e {\n        (self, devices = { ar0330, sensor_io }) {\n\t\t\tprintln!(\"test argument {}\", test);\n\n            sensor_io.write_raw(\"reset\", 1)?;\n\n            std::thread::sleep(std::time::Duration::from_millis(10));\n\n            sensor_io.write_raw(\"reset\", 0)?;\n            ar0330.write_cooked(\"software_reset\", 0)?;\n            ar0330.write_cooked(\"stream\", 1)?;\n\t\t\t\n\t\t\t\n            let _ret = run_script!(\"test\", devices, {\n                a: 123,\n                b: 1.23,\n                c: \"test\",\n                d: vec![0u8, 34u8]\n            })?;\n\n            ().to_bytes()\n        }\n    }\n}\n```\nThis defines a script with a single argument: `test` (with type `u8`). It uses the devices `ar0330` and `sensor_io` and also calls the lua script `test` from before.\nA longer script, that starts the `ar0330` image sensor in default settings on the `micro-r2` can be found [here](https://github.com/apertus-open-source-cinema/nctrl/blob/490a7469b4768ad82c6ebc37f24c080a94545492/src/scripts/micro_r2.rs#L26-L167).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapertus-open-source-cinema%2Fnctrl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapertus-open-source-cinema%2Fnctrl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapertus-open-source-cinema%2Fnctrl/lists"}