├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── elixirserial.iml ├── lib └── serial.ex ├── mix.exs ├── mix.lock ├── src ├── serial.c └── serial.h └── test └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | deps 3 | priv 4 | doc 5 | erl_crash.dump 6 | *.ez 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 1996, 1999 Johan Bevemyr 2 | Copyright (c) 2007, 2009 Tony Garnock-Jones 3 | Copyright (c) 2015, Bitgamma OÜ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Variables to override 2 | # 3 | # CC C compiler 4 | # CROSSCOMPILE crosscompiler prefix, if any 5 | # CFLAGS compiler flags for compiling all C files 6 | # LDFLAGS linker flags for linking all binaries 7 | # MIX path to mix 8 | 9 | 10 | LDFLAGS += 11 | CFLAGS ?= -O2 -Wall -Wextra -Wno-unused-parameter 12 | CC ?= $(CROSSCOMPILER)gcc 13 | MIX ?= mix 14 | 15 | .PHONY: all elixir-code clean 16 | 17 | all: elixir-code 18 | 19 | elixir-code: 20 | $(MIX) compile 21 | 22 | %.o: %.c 23 | $(CC) -c $(CFLAGS) -o $@ $< 24 | 25 | priv/serial: src/serial.c 26 | @mkdir -p priv 27 | $(CC) $^ $(LDFLAGS) -o $@ 28 | 29 | clean: 30 | $(MIX) clean 31 | rm -f priv/serial src/*.o 32 | 33 | realclean: 34 | rm -fr _build priv/serial src/*.o 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elixir_serial 2 | 3 | ### *** Attention: Use [Nerves UART](https://github.com/nerves-project/nerves_uart) for new projects *** 4 | 5 | This is a port program with elixir driver for serial communication. 6 | 7 | The C code was originally written by Johan Bevemyr in 1996 and 8 | sporadically maintained by Tony Garnock-Jones from 2007 onwards. 9 | 10 | ## Examples 11 | ### Opening a port 12 | ```elixir 13 | {:ok, serial} = Serial.start_link 14 | 15 | Serial.open(serial, "/dev/ttyS0") 16 | Serial.set_speed(serial, 57600) 17 | Serial.connect(serial) 18 | ``` 19 | 20 | ### Sending data 21 | ```elixir 22 | Serial.send_data(serial, <<0x01, 0x02, 0x03>>) 23 | ``` 24 | 25 | ### Receiving data 26 | ```elixir 27 | def handle_info({:elixir_serial, serial, data}, state) do 28 | # do something with the data 29 | end 30 | ``` 31 | 32 | ## License 33 | 34 | Copyright (c) 1996, 1999 Johan Bevemyr 35 | Copyright (c) 2007, 2009 Tony Garnock-Jones 36 | Copyright (c) 2015 Bitgamma OÜ 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a copy 39 | of this software and associated documentation files (the "Software"), to deal 40 | in the Software without restriction, including without limitation the rights 41 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 42 | copies of the Software, and to permit persons to whom the Software is 43 | furnished to do so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be included in 46 | all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 49 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 50 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 51 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 52 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 53 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 54 | THE SOFTWARE. 55 | -------------------------------------------------------------------------------- /elixirserial.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/serial.ex: -------------------------------------------------------------------------------- 1 | defmodule Serial do 2 | @moduledoc """ 3 | Serial communication through Elixir ports. 4 | """ 5 | 6 | use GenServer 7 | 8 | @send 0 9 | @connect 1 10 | @disconnect 2 11 | @open 3 12 | @close 4 13 | @speed 5 14 | @parity_odd 6 15 | @parity_even 7 16 | @break 8 17 | @flow_control 9 18 | 19 | @doc """ 20 | Starts a serial port. The process invoking this function will receive 21 | messages in the form of `{:elixir_serial, pid, data}`. 22 | """ 23 | def start_link(opts \\ []) do 24 | GenServer.start_link(__MODULE__, self(), opts) 25 | end 26 | 27 | @doc """ 28 | Opens a connection to the given tty. 29 | """ 30 | def open(pid, tty) do 31 | GenServer.call(pid, {:open, tty}) 32 | end 33 | 34 | @doc """ 35 | Close the currently open connection. 36 | """ 37 | def close(pid) do 38 | GenServer.call(pid, {:cmd, @close}) 39 | end 40 | 41 | @doc """ 42 | Reopens the connection to the current tty. 43 | """ 44 | def connect(pid) do 45 | GenServer.call(pid, {:cmd, @connect}) 46 | end 47 | 48 | @doc """ 49 | Disconnects from the current tty. 50 | """ 51 | def disconnect(pid) do 52 | GenServer.call(pid, {:cmd, @disconnect}) 53 | end 54 | 55 | @doc """ 56 | Sets the connection speed to the given value. 57 | """ 58 | def set_speed(pid, speed) do 59 | set_speed(pid, speed, speed) 60 | end 61 | 62 | @doc """ 63 | Sets the input and output connection speed to the given values. 64 | """ 65 | def set_speed(pid, in_speed, out_speed) do 66 | GenServer.call(pid, {:speed, in_speed, out_speed}) 67 | end 68 | 69 | @doc """ 70 | Sets the parity to either `:odd` and `:even`. 71 | """ 72 | def set_parity(pid, :odd) do 73 | GenServer.call(pid, {:cmd, @parity_odd}) 74 | end 75 | def set_parity(pid, :even) do 76 | GenServer.call(pid, {:cmd, @parity_even}) 77 | end 78 | 79 | @doc """ 80 | Sends a break to the open connection. 81 | """ 82 | def break(pid) do 83 | GenServer.call(pid, {:cmd, @break}) 84 | end 85 | 86 | @doc """ 87 | Enable or disable flow control. 88 | """ 89 | def set_flow_control(pid, enable) do 90 | GenServer.call(pid, {:flow_control, enable}) 91 | end 92 | 93 | @doc """ 94 | Sends data over the open connection. 95 | """ 96 | def send_data(pid, data) do 97 | GenServer.call(pid, {:send, data}) 98 | end 99 | 100 | def init(pid) do 101 | exec = :code.priv_dir(:serial) ++ '/serial' 102 | port = Port.open({:spawn_executable, exec}, [{:args, ['-erlang']}, :binary, {:packet, 2}, :exit_status]) 103 | {:ok, {pid, port}} 104 | end 105 | 106 | def handle_call({:open, tty}, _from, {_pid, port} = state) do 107 | Port.command(port, [@open, tty]) 108 | {:reply, :ok, state} 109 | end 110 | def handle_call({:speed, new_in_speed, new_out_speed}, _from, {_pid, port} = state) do 111 | Port.command(port, [@speed, Integer.to_char_list(new_in_speed), ' ', Integer.to_char_list(new_out_speed), 0]) 112 | {:reply, :ok, state} 113 | end 114 | def handle_call({:flow_control, new_enable}, _from, {_pid, port} = state) do 115 | enable_char = if new_enable, do: '1', else: '0' 116 | Port.command(port, [@flow_control, enable_char]) 117 | {:reply, :ok, state} 118 | end 119 | def handle_call({:cmd, cmd}, _from, {_pid, port} = state) do 120 | Port.command(port, [cmd]) 121 | {:reply, :ok, state} 122 | end 123 | def handle_call({:send, data}, _from, {_pid, port} = state) do 124 | Port.command(port, [@send, data]) 125 | {:reply, :ok, state} 126 | end 127 | 128 | def handle_info({port, {:data, data}}, {pid, port} = state) do 129 | send(pid, {:elixir_serial, self(), data}) 130 | {:noreply, state} 131 | end 132 | def handle_info({port, {:exit_status, status}}, {_pid, port} = _state) do 133 | exit({:port_exit, status}) 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Compile.Serial do 2 | @shortdoc "Compiles serial port binary" 3 | def run(_) do 4 | 0 = Mix.Shell.IO.cmd("make priv/serial") 5 | Mix.Project.build_structure 6 | :ok 7 | end 8 | end 9 | 10 | defmodule Serial.Mixfile do 11 | use Mix.Project 12 | 13 | def project do 14 | [app: :serial, 15 | version: "0.1.2", 16 | elixir: "~> 1.0", 17 | compilers: [:serial, :elixir, :app], 18 | build_embedded: Mix.env == :prod, 19 | start_permanent: Mix.env == :prod, 20 | deps: deps, 21 | package: package, 22 | description: description 23 | ] 24 | end 25 | 26 | def application, do: [] 27 | 28 | defp deps do 29 | [ 30 | {:earmark, "~> 0.1", only: :dev}, 31 | {:ex_doc, "~> 0.11", only: :dev} 32 | ] 33 | end 34 | 35 | defp description do 36 | "Serial communication through Elixir ports" 37 | end 38 | 39 | defp package do 40 | [ 41 | files: ["lib", "mix.exs", "README.md", "LICENSE", "src", "Makefile"], 42 | maintainers: ["Michele Balistreri"], 43 | licenses: ["ISC"], 44 | links: %{"GitHub" => "https://github.com/bitgamma/elixir_serial"} 45 | ] 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"earmark": {:hex, :earmark, "0.2.0"}, 2 | "ex_doc": {:hex, :ex_doc, "0.11.2"}} 3 | -------------------------------------------------------------------------------- /src/serial.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 1996, 1999 Johan Bevemyr 3 | Copyright (c) 2007, 2009 Tony Garnock-Jones 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | /* -*- C -*- 24 | * File: serial.c (~jb/serialport/serial.c) 25 | * Author: Johan Bevemyr 26 | * Created: Fri Oct 18 09:59:34 1996 27 | * Purpose: Provide Erlang with access to the serial port. 28 | */ 29 | 30 | /* This program communicates through the following protocoll when 31 | * run with -erlang: 32 | * 33 | * (all messages starts with a two byte header containing the 34 | * size of the message) 35 | * 36 | * SEND DATA 37 | * Transmits DATA on the currently open tty 38 | * 39 | * CONNECT 40 | * Reopens a disconnected connection. 41 | * 42 | * DISCONNECT 43 | * Hangs up an open serial line 44 | * 45 | * OPEN path 46 | * Opens a new tty, closing the old (but not disconnecting). It 47 | * is possible to alternate between two connections. 48 | * 49 | * SPEED inspeed outspeed 50 | * Sets the input and output speeds. New connections will 51 | * have these speeds. It is also possible to change speed 52 | * on the fly. 53 | * 54 | * PARITY ODD/EVEN 55 | * Sets the parity of the current connection. 56 | * 57 | * BREAK 58 | * Sends break. 59 | * 60 | * usage: serial [-debug] [-cbreak] [-erlang] [-speed ] [-tty ] 61 | * bit rate is one of 62 | * 50 75 110 63 | * 134 150 200 64 | * 300 600 1200 65 | * 1800 2400 4800 66 | * 9600 19200 38400 67 | * 57600 115200 230400 68 | */ 69 | 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | #include 83 | 84 | #include "serial.h" 85 | 86 | int errno; 87 | 88 | #define MAXSPEED 23 89 | bit_rate bitrate_table[MAXSPEED] = { 90 | {0 , B0 }, 91 | {50 , B50 }, 92 | {75 , B75 }, 93 | {110 , B110 }, 94 | {134 , B134 }, 95 | {150 , B150 }, 96 | {200 , B200 }, 97 | {300 , B300 }, 98 | {600 , B600 }, 99 | {1200 , B1200 }, 100 | {1800 , B1800 }, 101 | {2400 , B2400 }, 102 | {4800 , B4800 }, 103 | {9600 , B9600 }, 104 | {19200 , B19200 }, 105 | {38400 , B38400 }, 106 | {57600 , B57600 }, 107 | {115200 , B115200 }, 108 | {230400 , B230400 } 109 | }; 110 | 111 | /********************************************************************** 112 | * Name: get_speed 113 | * 114 | * Desc: Returns the speed_t value associated with a given bit_rate 115 | * according to the bitrate_table. B0 is returned if no matching entry 116 | * is found. 117 | */ 118 | 119 | speed_t get_speed(int speed) { 120 | int i; 121 | 122 | for(i=0 ; i < MAXSPEED ; i++) { 123 | if (speed == bitrate_table[i].rate) { 124 | break; 125 | } 126 | } 127 | 128 | if (i == MAXSPEED) { 129 | return B0; 130 | } else { 131 | return bitrate_table[i].speed; 132 | } 133 | } 134 | 135 | /********************************************************************** 136 | * Name: set_raw_tty_mode 137 | * 138 | * Desc: Configures the given tty for raw-mode. 139 | */ 140 | 141 | void set_raw_tty_mode(int fd) { 142 | struct termios ttymodes; 143 | 144 | /* Get ttymodes */ 145 | 146 | if (tcgetattr(fd,&ttymodes) < 0) { 147 | perror("tcgetattr"); 148 | exit(1); 149 | } 150 | 151 | /* Configure for raw mode (see man termios) */ 152 | ttymodes.c_cc[VMIN] = 1; /* at least one character */ 153 | ttymodes.c_cc[VTIME] = 0; /* do not wait to fill buffer */ 154 | 155 | ttymodes.c_iflag &= ~(ICRNL | /* disable CR-to-NL mapping */ 156 | INLCR | /* disable NL-to-CR mapping */ 157 | IGNCR | /* disable ignore CR */ 158 | ISTRIP | /* disable stripping of eighth bit */ 159 | IXON | /* disable output flow control */ 160 | BRKINT | /* disable generate SIGINT on brk */ 161 | IGNPAR | 162 | PARMRK | 163 | IGNBRK | 164 | INPCK); /* disable input parity detection */ 165 | 166 | ttymodes.c_lflag &= ~(ICANON | /* enable non-canonical mode */ 167 | ECHO | /* disable character echo */ 168 | ECHOE | /* disable visual erase */ 169 | ECHOK | /* disable echo newline after kill */ 170 | ECHOKE | /* disable visual kill with bs-sp-bs */ 171 | ECHONL | /* disable echo nl when echo off */ 172 | ISIG | /* disable tty-generated signals */ 173 | IEXTEN); /* disable extended input processing */ 174 | 175 | ttymodes.c_cflag |= CS8; /* enable eight bit chars */ 176 | ttymodes.c_cflag &= ~PARENB; /* disable input parity check */ 177 | 178 | ttymodes.c_oflag &= ~OPOST; /* disable output processing */ 179 | 180 | /* roland */ 181 | ttymodes.c_cflag |= CLOCAL; 182 | 183 | 184 | 185 | /* Apply changes */ 186 | 187 | if (tcsetattr(fd, TCSAFLUSH, &ttymodes) < 0) { 188 | perror("tcsetattr"); 189 | exit(1); 190 | } 191 | } 192 | 193 | /********************************************************************** 194 | * Name: set_tty_speed 195 | * 196 | * Desc: set input and output speeds of a given connection. 197 | */ 198 | 199 | void set_tty_speed(int fd, speed_t new_ispeed, speed_t new_ospeed) { 200 | struct termios ttymodes; 201 | 202 | /* Get ttymodes */ 203 | 204 | if (tcgetattr(fd,&ttymodes) < 0) { 205 | perror("tcgetattr"); 206 | exit(1); 207 | } 208 | 209 | if (cfsetispeed(&ttymodes,new_ispeed) < 0) { 210 | perror("cfsetispeed"); 211 | exit(1); 212 | } 213 | 214 | if (cfsetospeed(&ttymodes,new_ospeed) < 0) { 215 | perror("cfsetospeed"); 216 | exit(1); 217 | } 218 | 219 | /* Apply changes */ 220 | 221 | if (tcsetattr(fd, TCSAFLUSH, &ttymodes) < 0) { 222 | perror("tcsetattr"); 223 | exit(1); 224 | } 225 | } 226 | 227 | /********************************************************************** 228 | * Name: set_tty_flow_control 229 | * 230 | * Desc: enable input and output flow control. 231 | */ 232 | 233 | void set_tty_flow_control(int fd, boolean enable) { 234 | struct termios ttymodes; 235 | 236 | /* Get ttymodes */ 237 | 238 | if (tcgetattr(fd,&ttymodes) < 0) { 239 | perror("tcgetattr"); 240 | exit(1); 241 | } 242 | 243 | if (enable) { 244 | ttymodes.c_cflag |= CRTSCTS; 245 | } else { 246 | ttymodes.c_cflag &= ~CRTSCTS; 247 | } 248 | 249 | /* Apply changes */ 250 | 251 | if (tcsetattr(fd, TCSAFLUSH, &ttymodes) < 0) { 252 | perror("tcsetattr"); 253 | exit(1); 254 | } 255 | } 256 | 257 | /********************************************************************** 258 | * Name: get_tbh_size 259 | * Desc: returns the size of a two_byte_header message (from Erlang). 260 | */ 261 | 262 | int get_tbh_size(unsigned char buf[]) { 263 | return (((int) buf[0]) << 8) + ((int) buf[1]); 264 | } 265 | 266 | /********************************************************************** 267 | * Name: set_tbh_size 268 | * Desc: sets the first two bytes of the buffer to its size 269 | */ 270 | 271 | void set_tbh_size(unsigned char buf[], int size) { 272 | buf[1] = (unsigned char) (size & 0xff); 273 | buf[0] = (unsigned char) ((size >> 8) & 0xff); 274 | return; 275 | } 276 | 277 | /********************************************************************** 278 | * Name: tbh_write 279 | * Desc: writes the buffer to a file descriptor, adding size info 280 | * at the beginning. 281 | */ 282 | 283 | void tbh_write(int fd, unsigned char buf[], int buffsize) { 284 | unsigned char header_buf[TBHSIZE]; 285 | 286 | Debug1("tbh_write: send message of size %d\r\n", buffsize); 287 | 288 | /* First, write two byte header */ 289 | set_tbh_size(header_buf, buffsize); 290 | write(fd,header_buf,TBHSIZE); 291 | 292 | /* Second, write original buffer */ 293 | write(fd,buf,buffsize); 294 | 295 | return; 296 | } 297 | 298 | /********************************************************************** 299 | * Name: read_at_least(fd,buf,nr) 300 | * Desc: Read at least nr bytes and put them into buf. Return the number 301 | * of bytes read, i.e. nr. 302 | * Returns: The number of bytes read, or 0 if stream closed. 303 | */ 304 | 305 | int read_at_least(int fd, unsigned char buf[], int nr) { 306 | int remaining = nr; 307 | int nr_read = 0; 308 | 309 | while(remaining > 0) { 310 | int read_this_time; 311 | read_this_time = read(fd, &buf[nr_read], remaining); 312 | 313 | /* Input stream closed? */ 314 | if (read_this_time == 0) { 315 | return 0; 316 | } 317 | 318 | nr_read += read_this_time; 319 | remaining -= read_this_time; 320 | } 321 | 322 | return nr_read; 323 | } 324 | 325 | /********************************************************************** 326 | * Name: tbh_read 327 | * Desc: Reads one message with two-byte-header, filling buffer. 328 | * Returns the number of elements used in the buffer, or 0 329 | * if the input file has been closed. 330 | * 331 | */ 332 | 333 | int tbh_read(int fd, unsigned char buf[], int buffsize) { 334 | int remaining, msgsize; 335 | 336 | if (read_at_least(fd,buf,TBHSIZE) != TBHSIZE) { 337 | return 0; 338 | } 339 | 340 | remaining = get_tbh_size(buf); 341 | 342 | Debug1("tbh_read: got message of size %d\r\n",remaining); 343 | 344 | msgsize = read_at_least(fd, &buf[TBHSIZE], Min(remaining,(buffsize-TBHSIZE))); 345 | 346 | if (msgsize == 0) { 347 | return 0; 348 | } else { 349 | return msgsize + TBHSIZE; 350 | } 351 | } 352 | 353 | /********************************************************************** 354 | * Name: write_to_tty 355 | * Desc: write a number of bytes found in the buffer to the tty, 356 | * filling the buffer from the given fillfd if neccessary. 357 | * 358 | */ 359 | 360 | void write_to_tty(int ttyfd, int fillfd, int totalsize, int buffsize, unsigned char buf[], int buffmaxsize) { 361 | write(ttyfd,buf,buffsize); 362 | totalsize -= buffsize; 363 | 364 | while(totalsize > 0) { 365 | int readmax; 366 | 367 | readmax = Min(totalsize,buffmaxsize); 368 | buffsize = read(fillfd,buf,readmax); 369 | write(ttyfd,buf,buffsize); 370 | totalsize -= buffsize; 371 | } 372 | 373 | return; 374 | } 375 | 376 | /**********************************************************************/ 377 | 378 | int Debug_Enabled = FALSE; 379 | 380 | int main(int argc, char *argv[]) { 381 | int ttyfd = -1; /* terminal file descriptor */ 382 | int stdinfd; /* user file descriptor */ 383 | int stdoutfd; /* user out file descriptor */ 384 | boolean cbreak=FALSE; /* cbreak flag */ 385 | boolean erlang=FALSE; /* talking to erlang flag */ 386 | speed_t in_speed=B9600; /* default in speed */ 387 | speed_t out_speed=B9600; /* default out speed */ 388 | boolean flowcontrol=TRUE; /* default flow control */ 389 | char ttyname[MAXPATHLEN]; /* terminal name */ 390 | 391 | strcpy(ttyname,"/dev/ttyS0"); 392 | 393 | /**************************************** 394 | * Process command line arguments 395 | */ 396 | 397 | { 398 | int i; 399 | 400 | for(i = 1 ; i < argc ; i++) { 401 | if (strcmp(argv[i],"-cbreak") == 0) { 402 | cbreak = TRUE; 403 | } else if (strcmp(argv[i],"-debug") == 0) { 404 | Debug_Enabled = TRUE; 405 | } else if (strcmp(argv[i],"-speed") == 0) { 406 | i += 1; 407 | 408 | if (i < argc) { 409 | out_speed = in_speed = get_speed(atoi(argv[i])); 410 | if (in_speed == B0) { 411 | goto error_usage; 412 | } 413 | } else { 414 | goto error_usage; 415 | } 416 | } else if (strcmp(argv[i],"-tty") == 0) { 417 | i += 1; 418 | 419 | if (i < argc) { 420 | strncpy(ttyname,argv[i],MAXPATHLEN-1); 421 | } else { 422 | goto error_usage; 423 | } 424 | } else if (strcmp(argv[i],"-erlang") == 0) { 425 | erlang = TRUE; 426 | } else if (strcmp(argv[i],"-noflow") == 0) { 427 | flowcontrol = FALSE; 428 | } else { 429 | goto error_usage; 430 | } 431 | } 432 | } 433 | 434 | /**************************************** 435 | * Configure serial port (tty) 436 | */ 437 | 438 | if (!erlang) { 439 | ttyfd = open(ttyname,O_RDWR); 440 | 441 | if (!TtyOpen(ttyfd)) { 442 | fprintf(stderr,"Cannot open terminal %s for read and write\n", ttyname); 443 | exit(1); 444 | } 445 | 446 | set_raw_tty_mode(ttyfd); 447 | set_tty_speed(ttyfd,in_speed,out_speed); 448 | set_tty_flow_control(ttyfd,flowcontrol); 449 | } 450 | 451 | /**************************************** 452 | * Configure user port 453 | */ 454 | 455 | stdinfd = fileno(stdin); 456 | stdoutfd = fileno(stdout); 457 | 458 | if (cbreak) { 459 | /* Use non-cononical mode for input */ 460 | set_raw_tty_mode(stdinfd); 461 | fprintf(stderr,"Entering non-canonical mode, exit with ---\n"); 462 | } 463 | 464 | /**************************************** 465 | * Start processing loop 466 | */ 467 | 468 | { 469 | fd_set readfds; /* file descriptor bit field for select */ 470 | int maxfd; /* max file descriptor for select */ 471 | unsigned char buf[MAXLENGTH]; /* buffer for transfer between serial-user */ 472 | int escapes; /* number of consecutive escapes in cbreak */ 473 | 474 | /* Set up initial bit field for select */ 475 | maxfd = Max(stdinfd,ttyfd); 476 | FD_ZERO(&readfds); 477 | 478 | /* no escapes encountered yet */ 479 | escapes = 0; 480 | 481 | while (TRUE) { 482 | int i; 483 | 484 | if(TtyOpen(stdinfd)) { 485 | FD_SET(stdinfd, &readfds); 486 | } 487 | 488 | if(TtyOpen(ttyfd)) { 489 | FD_SET(ttyfd, &readfds); 490 | } 491 | 492 | i = select(maxfd+1, &readfds, NULLFDS, NULLFDS, NULLTV); 493 | 494 | if (i <= 0) { 495 | perror("select"); 496 | exit(1); 497 | } 498 | 499 | /****************************** 500 | * Data from TTY 501 | */ 502 | 503 | /* from serial port */ 504 | if (TtyOpen(ttyfd) && FD_ISSET(ttyfd,&readfds)) { 505 | int nr_read; 506 | Debug("receiving from TTY\r\n"); 507 | 508 | FD_CLR(ttyfd,&readfds); 509 | 510 | nr_read = read(ttyfd,buf,MAXLENGTH); 511 | 512 | if (nr_read <= 0) { 513 | fprintf(stderr,"problem reading from tty\n"); 514 | exit(1); 515 | } 516 | 517 | if (erlang) { 518 | tbh_write(stdoutfd,buf,nr_read); 519 | } else { 520 | write(stdoutfd,buf,nr_read); 521 | } 522 | } 523 | 524 | /****************************** 525 | * Data from controlling process 526 | */ 527 | 528 | /* from user */ 529 | if (TtyOpen(stdinfd) && FD_ISSET(stdinfd,&readfds)) { 530 | int nr_read; 531 | int i; 532 | 533 | FD_CLR(stdinfd,&readfds); 534 | 535 | if (cbreak) { 536 | nr_read = read(stdinfd,buf,MAXLENGTH); 537 | 538 | for(i=0 ; i] [-tty ]\n",argv[0]); 691 | fprintf(stderr,"\tbit rate is one of \n\t\t50\t75\t110\n\t\t"); 692 | fprintf(stderr,"134\t150\t200\n\t\t300\t"); 693 | fprintf(stderr,"600\t1200\n\t\t1800\t2400\t4800\n\t\t"); 694 | fprintf(stderr,"9600\t19200\t38400\n\t\t57600\t"); 695 | fprintf(stderr,"115200\t230400\n"); 696 | 697 | return 0; 698 | } -------------------------------------------------------------------------------- /src/serial.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 1996, 1999 Johan Bevemyr 3 | Copyright (c) 2007, 2009 Tony Garnock-Jones 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | /* -*- C -*- 24 | * File: serial.h (~jb/serialport/serial.h) 25 | * Author: Johan Bevemyr 26 | * Created: Sun Oct 20 01:36:03 1996 27 | * Purpose: 28 | */ 29 | 30 | #ifndef SERIAL_H 31 | #define SERIAL_H 32 | 33 | typedef enum { 34 | FALSE = 0, 35 | TRUE = 1 36 | } boolean; 37 | 38 | #define NULLFDS ((fd_set *) 0) 39 | #define NULLTV ((struct timeval *) 0) 40 | #define MAXLENGTH 1024 41 | 42 | typedef struct { 43 | int rate; 44 | speed_t speed; 45 | } bit_rate; 46 | 47 | #define Max(A,B) (((A) > (B)) ? (A) : (B)) 48 | #define Min(A,B) (((A) < (B)) ? (A) : (B)) 49 | 50 | #define TtyOpen(TTY) ((TTY) != -1) 51 | 52 | #define COMMANDPOS 2 53 | #define COMMANDSIZE 1 54 | #define HEADERSIZE 3 55 | #define TBHSIZE 2 56 | 57 | #define PacketType(MESSAGE) (MESSAGE[COMMANDPOS]) 58 | 59 | #define BREAKPERIOD 0 60 | 61 | /* roland */ 62 | typedef enum { 63 | SEND=0, 64 | CONNECT=1, 65 | DISCONNECT=2, 66 | OPEN=3, 67 | CLOSE=4, 68 | SPEED=5, 69 | PARITY_ODD=6, 70 | PARITY_EVEN=7, 71 | BREAK=8, 72 | FLOW_CONTROL=9 73 | } command; 74 | 75 | extern int Debug_Enabled; 76 | 77 | #define Debug(STRING) do { if (Debug_Enabled) { fprintf(stderr,STRING); } } while(0) 78 | #define Debug1(STRING,Arg) do { if (Debug_Enabled) { fprintf(stderr,STRING,Arg); } } while(0) 79 | #define Debug2(STRING,Arg1,Arg2) do { if (Debug_Enabled) { fprintf(stderr,STRING,Arg1,Arg2); } } while(0) 80 | 81 | #endif /* SERIAL_H */ -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------