Overview of the Serial Communication Protocol
Serial on a Server
Any time you are connected to a server over SSH, you are using a serial protocol. Today, it is carried by TCP/IP, along with all of your other network traffic, rather than over a dedicated line, but the principle is the same. When you open a terminal window in your favorite Linux distro, you're actually emulating what used to be a hard-wired serial connection.
You can still set up a hardware serial terminal as a console to your Linux machine. The first step is to get it wired to the computer. You probably have a 9-pin serial port, so you'll need a 9-pin serial cable. Most hardware terminals use a 25-pin connector, so you also need a 9-pin to 25-pin adapter, which you can easily find on Amazon or at your local electronics shop.
Once everything is connected, look at the manual for your terminal to find out how to set up the baud rate and other connection parameters, which should be 115,200 baud, 8 data bits, no parity bit, and 1 stop bit.
Once you are wired up, you need software to present a shell to the terminal over the serial line. A program called agetty
should already be available in your distribution or available from the package manager. Open a terminal window on your desktop and type agetty. If it says not enough arguments you're good to go. The man page shows the command format:
agetty [options] port [baud_rate...]
See the "Finding Your Serial Port" box to determine your port name; then, enter something like:
Finding Your Serial Port
Linux reports all of its hardware, including serial ports, as files in /dev
. If your computer has a port on the back of the CPU, then it is probably found at /dev/ttyS0
. If you're trying to talk to an Arduino, it is probably /dev/ttyAMA0
, whereas if you're using a USB dongle, it is probably something like /dev/ttyUSB0
. You can find your port quickly and easily as follows:
- Make sure your USB serial port or Arduino is unplugged
- In a terminal, enter
ls -1 /dev/tty*>noUSB.txt
- Connect your USB serial port and give it a second or so to register
- In the terminal, enter
ls -1 /dev/tty*>USB.txt
- Finally, enter
diff noUSB.txt USB.txt
You'll see something like:
98a99 > /dev/ttyUSB0
In steps 2 and 4 above, you're using ls
to get a list of tty
devices found in /dev
. The -1
requests output of one entry per line instead of output in columns. The >noUSB.txt
redirects output to a text file. After connecting a USB device, step 4 makes a new list that will include the device that's just been connected, and step 5, the diff
command, compares the two output files.
The first line of output reports that after line 98 in the original file (noUSB.txt
) the second file (USB.txt
) has an additional (a
) line 99. The next line of output shows that line after the chevron. In this case, that's the name of the USB serial port that appeared when the device was connected.
agetty ttyUSB0 115200
If all goes well, a login prompt should appear on your hard-wired terminal (Figure 4). Once you log in, the system's default shell will appear. You can also configure agetty
to run a custom handler when something connects (e.g., any kind of service you've written), pass the connection off to another program to start a dial-up Internet connection, or anything along those lines.
Curses Library
Whether you're using a hard-wired terminal or just a virtual terminal in a window on your computer, this example will work just fine. Because so many different terminals are available with different sizes, capabilities, and options, the curses library [5] was developed to handle all of the details in the background, so the developer can just write code and not worry about the capabilities of the underlying hardware. If a particular feature isn't available on a terminal, curses will downgrade the features until it can be displayed. If necessary, the output will just be unstyled black and white text.
The Big Print Clock
A popular thing to do in a serial terminal is display big block text several lines tall. The bigPrint.py
Python script shows a clock in block text (Figure 5) and is a great example of an application of the curses
library.
In the Python program in Listing 4, import
brings in the curses, time, os, string, and datetime libraries. Note that line 5 is a little different: Rather than importing the entire library, it only imports the datetime
module. By default, Python imports entire libraries, so an import
in this format allows you to bring in only the parts you need.
Listing 4
clock.py
001 import curses 002 import time 003 import os 004 import string 005 from datetime import datetime 006 007 class console: 008 def __init__ ( self ): 009 self.screen = curses.initscr() 010 curses.noecho() 011 curses.cbreak() 012 self.screen.nodelay ( True ) 013 self.screen.keypad ( True ) 014 015 self.digits = dict() 016 self.digits [ '0' ] = """ 017 ### 018 # # 019 # # 020 # # 021 ### 022 """ 023 self.digits [ '1' ] = """ 024 # 025 ## 026 # 027 # 028 ##### 029 """ 030 self.digits [ '2' ] = """ 031 ### 032 # # 033 # 034 # 035 ##### 036 """ 037 self.digits [ '3' ] = """ 038 ### 039 # # 040 # 041 # # 042 ### 043 """ 044 self.digits [ '4' ] = """ 045 # # 046 # # 047 ##### 048 # 049 # 050 """ 051 self.digits [ '5' ] = """ 052 ##### 053 # 054 ### 055 # 056 #### 057 """ 058 self.digits [ '6' ] = """ 059 ### 060 # 061 #### 062 # # 063 ### 064 """ 065 self.digits [ '7' ] = """ 066 ##### 067 # 068 # 069 # 070 # 071 """ 072 self.digits [ '8' ] = """ 073 ### 074 # # 075 ### 076 # # 077 ### 078 """ 079 self.digits [ '9' ] = """ 080 ### 081 # # 082 #### 083 # 084 ### 085 """ 086 self.digits [ ':' ] = """ 087 088 # 089 090 # 091 092 """ 093 094 def bigPrint ( self , text , position ): 095 x = position [ 1 ] 096 y = position [ 0 ] 097 originalY = y 098 099 for char in text: 100 if char in self.digits: 101 firstLine = True 102 for line in self.digits [ char ].split ( "\n" ): 103 if firstLine == True: 104 firstLine = False 105 continue 106 outLine = "" 107 for char in line: 108 if char == "#": outLine += chr ( 97 ) 109 else: outLine += char 110 self.screen.addstr ( y , x , "{0:5s}".format ( outLine ) , curses.A_ALTCHARSET ) 111 y += 1 112 if y > ( originalY + 4 ): break 113 y = originalY 114 x += 7 115 116 def loop ( self ): 117 looping = True 118 startTime = time.time() 119 nextClock = startTime + 1 120 121 while looping: 122 key = None 123 try: 124 key = self.screen.getch() 125 except: 126 pass 127 128 if key == ord ( " " ): 129 looping = False 130 131 if time.time() > nextClock: 132 now = datetime.now() 133 self.bigPrint ( now.strftime ( "%H:%M:%S" ) , ( 1 , (80-56) / 2 ) ) 134 nextClock += 1 135 136 screen = console() 137 screen.loop()
In programming, a class
is a set of variables and functions that represent a bigger unit. You can create instances of classes that all have the same capabilities but operate independently. If a program were an orchestra, a class could represent each musician. Here, the console
class represents a set of functions to display block text on the screen.
The __init__
function is a special function in a class. It is called automatically whenever the class is instantiated (created for the first time). Similar to setup
in the Arduino code, it lets you get the class ready for whatever it needs to do. Here I use it to set up curses.
Line 9 initializes the curses library, and lines 10 and 11 set up how I want curses to function overall. The curses.noecho
function says not to echo (print) characters as they are typed; therefore, any characters drawn to the screen have to be put there explicitly. The curses.cbreak
line tells curses to return any key presses immediately, instead of waiting for the Enter key to be pressed.
Sometimes you also might want to check to see whether a key is pressed and continue whether it is or not. Curses calls this nodelay
. Here, I have to set this mode explicitly on self.screen
, the terminal display. In this program, I'm using the entire terminal as a single window, but curses supports splitting it into smaller windows and controlling them individually, so each can react to keys differently; therefore, I have to tell it explicitly which window I want to place in nodelay
mode.
The self.screen.keypad
line tells curses to interpret escape sequences coming from the terminal automatically. Special keys like the arrow keys, function keys, and Page Up/Down keys usually transmit an escape and then a key code. In keypad
mode, you get a constant instead, which is easier to compare and makes a program more readable.
The rest of the __init__
function defines the block characters that will be displayed in the Big Print clock. Line 15 creates a dictionary of definitions. The self.digits
key (which is
in line 16) specifies the character being stored. Triple quotes start a block string. Until Python encounters another set of triple quotes, everything will be considered a part of the string. Lines 17-21 are a block of lines and characters that define what should be filled in to draw the character identified in the key.
Each set of seven lines defines the numbers 0 through 9 and a colon. I chose the #
character for the blocks, but that choice is arbitrary. The bigPrint
method (lines 94-114) shows why.
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Gnome 47.2 Now Available
Gnome 47.2 is now available for general use but don't expect much in the way of newness, as this is all about improvements and bug fixes.
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.
-
New Steam Client Ups the Ante for Linux
The latest release from Steam has some pretty cool tricks up its sleeve.