{"id":24528900,"url":"https://github.com/elixir-circuits/circuits_uart","last_synced_at":"2025-12-12T00:31:03.032Z","repository":{"id":8229628,"uuid":"57340823","full_name":"elixir-circuits/circuits_uart","owner":"elixir-circuits","description":"Discover and use UARTs and serial ports in Elixir","archived":false,"fork":false,"pushed_at":"2025-04-17T21:46:12.000Z","size":400,"stargazers_count":197,"open_issues_count":22,"forks_count":46,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-04-30T06:07:26.228Z","etag":null,"topics":["elixir","serial-ports","uart"],"latest_commit_sha":null,"homepage":null,"language":"C","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/elixir-circuits.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSES/Apache-2.0.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2016-04-28T23:43:01.000Z","updated_at":"2025-04-28T04:25:22.000Z","dependencies_parsed_at":"2023-12-17T17:41:58.758Z","dependency_job_id":"0d961915-0d11-4321-b0f2-340f79756563","html_url":"https://github.com/elixir-circuits/circuits_uart","commit_stats":{"total_commits":264,"total_committers":34,"mean_commits":7.764705882352941,"dds":"0.24242424242424243","last_synced_commit":"abf114b0567b5fba044f4589891f4c858b0d200c"},"previous_names":["nerves-project/nerves_uart"],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-circuits%2Fcircuits_uart","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-circuits%2Fcircuits_uart/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-circuits%2Fcircuits_uart/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-circuits%2Fcircuits_uart/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elixir-circuits","download_url":"https://codeload.github.com/elixir-circuits/circuits_uart/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254254042,"owners_count":22039792,"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":["elixir","serial-ports","uart"],"created_at":"2025-01-22T07:24:58.635Z","updated_at":"2025-12-12T00:31:02.968Z","avatar_url":"https://github.com/elixir-circuits.png","language":"C","readme":"# Circuits.UART\n\n[![Hex version](https://img.shields.io/hexpm/v/circuits_uart.svg \"Hex version\")](https://hex.pm/packages/circuits_uart)\n[![API docs](https://img.shields.io/hexpm/v/circuits_uart.svg?label=hexdocs \"API docs\")](https://hexdocs.pm/circuits_uart/Circuits.UART.html)\n[![CI](https://github.com/elixir-circuits/circuits_uart/actions/workflows/ci.yml/badge.svg)](https://github.com/elixir-circuits/circuits_uart/actions/workflows/ci.yml)\n[![REUSE status](https://api.reuse.software/badge/github.com/elixir-circuits/circuits_uart)](https://api.reuse.software/info/github.com/elixir-circuits/circuits_uart)\n\n`Circuits.UART` allows you to use UARTs, serial ports, Bluetooth virtual serial\nport connections and more in Elixir. Some highlights:\n\n* Mac, Windows, Linux, and Nerves\n* Enumerate serial ports\n* Receive input via messages or by polling (active and passive modes)\n* Add and remove framing on serial data - line-based framing included for use\n  with GPS, cellular, satellite and other modules\n* Unit tests (uses the [tty0tty](https://github.com/freemed/tty0tty) virtual\n  null modem)\n\nLooking for `Nerves.UART`? `Circuits.UART` is the new name. Everything else is\nthe same. Update your project by replacing all references to `nerves_uart` and\n`Nerves.UART` to `circuits_uart` and `Circuits.UART` and you should be good.\n\nSomething doesn't work for you? Check out below and the\n[docs](https://hexdocs.pm/circuits_uart/). Post a question on the [Elixir\nForum](https://elixirforum.com/) or file an issue or PR.\n\n## Example use\n\nDiscover what serial ports are attached:\n\n```elixir\niex\u003e Circuits.UART.enumerate\n%{\"COM14\" =\u003e %{description: \"USB Serial Port\", manufacturer: \"FTDI\", product_id: 24577,\n    vendor_id: 1027},\n  \"COM5\" =\u003e %{description: \"Prolific USB-to-Serial Comm Port\",\n    manufacturer: \"Prolific\", product_id: 8963, vendor_id: 1659},\n  \"COM16\" =\u003e %{description: \"Arduino Uno\",\n    manufacturer: \"Arduino LLC (www.arduino.cc)\", product_id: 67, vendor_id: 9025}}\n```\n\nStart the UART GenServer:\n\n```elixir\niex\u003e {:ok, pid} = Circuits.UART.start_link\n{:ok, #PID\u003c0.132.0\u003e}\n```\n\nThe GenServer doesn't open a port automatically, so open up a serial port or\nUART now. See the results from your call to `Circuits.UART.enumerate/0` for what's\navailable on your system.\n\n```elixir\niex\u003e Circuits.UART.open(pid, \"COM14\", speed: 115200, active: false)\n:ok\n```\n\nThis opens the serial port up at 115200 baud and turns off active mode. This\nmeans that you'll have to manually call `Circuits.UART.read` to receive input. In\nactive mode, input from the serial port will be sent as messages. See the docs\nfor all options.\n\nWrite something to the serial port:\n\n```elixir\niex\u003e Circuits.UART.write(pid, \"Hello there\\r\\n\")\n:ok\n```\n\nSee if anyone responds in the next 60 seconds:\n\n```elixir\niex\u003e Circuits.UART.read(pid, 60000)\n{:ok, \"Hi\"}\n```\n\nInput is reported as soon as it is received, so you may need multiple calls to\n`read/2` to get everything you want. If you have flow control enabled and stop\ncalling `read/2`, the port will push back to the sender when its buffers fill\nup.\n\nEnough with passive mode, let's switch to active mode:\n\n```elixir\niex\u003e Circuits.UART.configure(pid, active: true)\n:ok\n\niex\u003e flush\n{:circuits_uart, \"COM14\", \"a\"}\n{:circuits_uart, \"COM14\", \"b\"}\n{:circuits_uart, \"COM14\", \"c\"}\n{:circuits_uart, \"COM14\", \"\\r\"}\n{:circuits_uart, \"COM14\", \"\\n\"}\n:ok\n```\n\nIt turns out that `COM14` is a USB to serial port. Let's unplug it and see what\nhappens:\n\n```elixir\niex\u003e flush\n{:circuits_uart, \"COM14\", {:error, :eio}}\n```\n\nOops. Well, when it appears again, it can be reopened. In passive mode, errors\nget reported on the calls to `Circuits.UART.read/2` and `Circuits.UART.write/3`\n\nBack to receiving data, it's a little annoying that characters arrive one by\none.  That's because our computer is really fast compared to the serial port,\nbut if something slows it down, we could receive two or more characters at a\ntime. Rather than reassemble the characters into lines, we can ask `circuits_uart`\nto do it for us:\n\n```elixir\niex\u003e Circuits.UART.configure(pid, framing: {Circuits.UART.Framing.Line, separator: \"\\r\\n\"})\n:ok\n```\n\nThis tells `circuits_uart` to append a `\\r\\n` to each call to `write/2` and to\nreport each line separately in active and passive mode. You can set this\nconfiguration in the call to `open/3` as well. Here's what we get now:\n\n```elixir\niex\u003e flush\n{:circuits_uart, \"COM14\", \"abc\"}   # Note that the \"\\r\\n\" is trimmed\n:ok\n```\n\nIf your serial data is framed differently, check out the `Circuits.UART.Framing`\nbehaviour and implement your own. `Circuits.UART.Framing.FourByte` is a\nparticularly simple example of a framer.\n\nYou can also set a timeout so that a partial line doesn't hang around in the\nreceive buffer forever:\n\n```elixir\niex\u003e Circuits.UART.configure(pid, rx_framing_timeout: 500)\n:ok\n\n# Assume that the sender sent the letter \"A\" without sending anything else\n# for 500 ms.\n\niex\u003e flush\n{:circuits_uart, \"COM14\", {:partial, \"A\"}}\n```\n\nSometimes it's easier to operate with the `pid` of the UART GenServer rather\nthan using the name of the port in active mode. An example of this is when you\nwant to send an acknowledgment back after a receive and you are using more than\none serial port at a time. You can do this with the `id: :pid` option to\n`open/1` or `configure/1`.\n\n```elixir\niex\u003e Circuits.UART.configure(pid, id: :pid)\n:ok\n\n# Assume some data was received\n\niex\u003e receive do\n...\u003e   {:circuits_uart, pid, _} -\u003e\n...\u003e     Circuits.UART.write(pid, \"ack\")\n...\u003e end\n:ok\n```\n\n## Installation\n\nTo install `circuits_uart`:\n\n  1. Add `circuits_uart` to your list of dependencies in `mix.exs`:\n\n  ```elixir\n  def deps do\n    [{:circuits_uart, \"~\u003e 1.5\"}]\n  end\n  ```\n\n  1. Check that the C compiler dependencies are satisified (see below)\n\n  1. Run `mix deps.get` and `mix compile`\n\n### C compiler dependencies\n\nSince this library includes C code, `make`, `gcc`, and Erlang header and\ndevelopment libraries are required.\n\nOn Linux systems, this usually requires you to install the `build-essential` and\n`erlang-dev` packages. For example:\n\n```sh\nsudo apt-get install build-essential erlang-dev\n```\n\nOn Macs, run `gcc --version` or `make --version`. If they're not installed, you\nwill be given instructions.\n\nOn Windows, if you're obtaining `circuits_uart` from `hex.pm`, you'll need MinGW\nto compile the C code. I use [Chocolatey](https://chocolatey.org/) and install\nMinGW by running the following in an administrative command prompt:\n\n```sh\nchoco install mingw\n```\n\nOn Nerves, you're set - just add `circuits_uart` to your `mix.exs`. Nerves\ncontains everything needed by default. If you do use Nerves, though, keep in\nmind that the C code is crosscompiled for your target hardware and will not work\non your host (the port will crash when you call `start_link` or `enumerate`. If\nyou want to try out `circuits_uart` on your host machine, the easiest way is to\neither clone the source or add `circuits_uart` as a dependency to a regular\n(non-Nerves) Elixir project.\n\n## Building and running the unit tests\n\nThe standard Elixir build process applies. Clone `circuits_uart` or download a\nsource release and run:\n\n```sh\nmix deps.get\nmix compile\n```\n\nThe unit tests require two serial ports connected via a NULL modem cable to run.\nDefine the names of the serial ports in the environment before running the\ntests. For example,\n\n```sh\nexport CIRCUITS_UART_PORT1=ttyS0\nexport CIRCUITS_UART_PORT2=ttyS1\n```\n\nIf you're on Windows or Linux, you don't need real serial ports. For linux,\ndownload and install [tty0tty](https://github.com/freemed/tty0tty). Load the\nkernel module and specify `tnt0` and `tnt1` for the serial ports. Check the\n`tty0tty` README.md, but this should looks something like:\n\n```sh\ncd tty0tty/module\nmake\nsudo cp tty0tty.ko /lib/modules/$(uname -r)/kernel/drivers/misc/\nsudo depmod\nsudo modprobe tty0tty\nsudo chmod 666 /dev/tnt*\n\nexport CIRCUITS_UART_PORT1=tnt0\nexport CIRCUITS_UART_PORT2=tnt1\n```\n\nOn Windows, download and install\n[com0com](https://sourceforge.net/projects/com0com/) (Look for version 2.2.2.0\nif the latest hasn't been signed). The ports on Windows are `CNCA0` and `CNCB0`.\n\nThen run:\n\n```sh\nmix test\n```\n\nIf you're using `tty0tty`, the tests will run at full speed. Real serial ports\nseem to take a fraction of a second to close and re-open. I added a gratuitous\ndelay to each test to work around this. It likely can be much shorter.\n\n\nOn MacOS, download and install [socat](http://www.dest-unreach.org/socat/). You can install it via Homebrew. Once you have it installed and ready to go, run the following command. You will need to change `\u003cUSERNAME\u003e` to your current system username\n\n```sh\nsudo socat -d -d -d -d -lf /tmp/socat pty,link=/dev/dummy1,raw,echo=0,user=\u003cUSERNAME\u003e,group=staff link=/dev/dummy2,raw,echo=0,user=\u003cUSERNAME\u003e,group=staff\n```\n\nOnce that opens, in a separate terminal emulator, set the Circuits ENVars, and go about your testing\n\n```sh\nexport CIRCUITS_UART_PORT1=/dev/dummy1\nexport CIRCUITS_UART_PORT2=/dev/dummy2\nmix test\n```\n\n## FAQ\n\n### Do I have to use Nerves?\n\nNo, this project doesn't have any dependencies on any Nerves components. The\ndesire for some serial port library features on Nerves drove us to create it,\nbut we also have host-based use cases. To be useful for us, the library must\nremain crossplatform and have few dependencies. We're just developing it under\nthe Nerves umbrella.\n\n### How can I use the serial port on Linux without sudo?\n\nSerial port files are almost always owned by the `dialout` group. Add yourself\nto the `dialout` group by running `sudo adduser yourusername dialout`. Then log\nout and back in again, and you should be able to access the serial port.\n\n### Debugging tips\n\nIf you're having trouble and suspect the C code, edit the `Makefile` to enable\ndebug logging. See the `Makefile` for instructions on how to do this. Debug\nlogging is appended to a file by default, but can be sent to `stderr` or another\nlocation by editing `src/circuits_uart.c`.\n\nIf you're on Linux, the `tty0tty` emulated null modem removes the flakiness of\nreal serial port drivers if that's the problem. The serial port monitor\n[jpnevulator](https://jpnevulator.snarl.nl/) is useful for monitoring the\nhardware signals and dumping data as hex byte values.\n\nOn OSX and Windows, I've found that PL2303-based serial ports can be flakey.\nFirst, make sure that you don't have a counterfeit PL2303. On Windows, they show\nup in device manager with a warning symbol. On OSX, they seem to hang when\nclosing the port. Non-counterfeit PL2303-based serial ports can pass the unit\ntests on Windows 10, but I have not been able to get them to pass on OSX.\nFTDI-based serial ports appear to work better on both operating systesm.\n\n### ei_copy why????\n\nYou may have noticed Erlang's `erl_interface` code copy/pasted into\n`src/ei_copy`.  This is *only* used on Windows to work around issues linking to\nthe distributed version of `erl_interface`. That was compiled with Visual\nStudio. This project uses MinGW, and even though the C ABIs are the same between\nthe compilers, Visual Studio adds stack protection calls that I couldn't figure\nout how to work around.\n\n### How does Circuits.UART communicate with the serial port?\n\nCircuits.UART uses a [Port](https://hexdocs.pm/elixir/Port.html) and C code.\nElixir/Erlang ports have nothing to do with the serial ports of the operating\nsystem.  They share the same name but are different concepts.\n\n### I see weird things happening on my UART using nerves\n\nBy default nerves is configured so Linux and the Elixir console is redirected\nto the serial0 interface. As a result, while using this interface, the buffer might\nbe full of debug logs from your application, which could cause the port to timeout\nwhen you are writing to it, or attempting to drain it `:port_timed_out`.\n\nTo disable this \"pollution\" you will have to edit:\n- `erlinit.config` and comment `-c ttyAMA0`\n- `cmdline.txt` and comment `console=serial0,115200`\n\nTo learn how to edit those files in your nerves setup you can check the advanced\nconfiguration documentation of nerves:\nhttps://hexdocs.pm/nerves/advanced-configuration.html#overwriting-files-in-the-root-filesystem\n\n## Acknowledgments\n\nWhen building this library,\n[node-serialport](https://github.com/voodootikigod/node-serialport) and\n[QtSerialPort](http://doc.qt.io/qt-5/qserialport.html) where incredibly helpful\nin helping to define APIs and point out subtleties with platform-specific serial\nport code. Sadly, I couldn't reuse their code, but I feel indebted to the\nauthors and maintainers of these libraries, since they undoubtedly saved me\nhours of time debugging corner cases.  I have tried to acknowledge them in the\ncomments where I have used strategies that I learned from them.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixir-circuits%2Fcircuits_uart","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felixir-circuits%2Fcircuits_uart","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixir-circuits%2Fcircuits_uart/lists"}