메뉴
HN
Hacker News 95일 전

도트 매트릭스 프린터로 시작하는 아침: 2024

IMP
6/10
핵심 요약

스마트폰 중독과 멘탈 건강 악화를 줄이기 위해 80년대 도트 매트릭스 프린터와 라즈베리 파이를 활용해 나만의 맞춤형 '아침 신문' 출력 시스템을 구축한 프로젝트입니다. PHP 스크립트를 이용해 날씨, 주식, 뉴스, 레딧 게시물 등을 수집하고 프린터로 자동 출력하도록 설정했습니다. 오픈소스로 공개되어 있어 누구나 이 아이디어를 참고해 자신만의 디지털 디톡스 도구를 만들 수 있습니다.

번역된 본문

한동안 나는 하루를 시작할 때마다 스마트폰 잠금을 해제하고 다양한 뉴스와 소셜 미디어 사이트를 스크롤하며 세상 돌아가는 상황을 확인했습니다. 이는 내 멘탈 건강에 그다지 좋지 않았고, 꽤 오랫동안 화면 사용 시간을 줄이려고 노력해 왔습니다. 하지만 여전히 최신 소식을 접하고 싶었고, 특히 아침에 일어난 직후에는 더욱 그랬습니다. 최근에 eBay에서 도트 매트릭스 프린터(dot matrix printer)를 하나 구매했는데, 매일 아침 나를 위해 맞춤형 '1면'을 출력해 두는 멋진 핑계거리가 될 것 같았습니다. 그래서 그걸 직접 만들어보기로 했습니다! 아래 영상에서 프린터가 내는 ASMR 소리를 들어보세요 👇 이 글에서는 내가 어떤 부품을 사용했고, 어떻게 설정했으며, 모든 것을 작동시키는 PHP 스크립트는 어떻게 구성되어 있는지 자세히 설명하겠습니다. 전체 소스 코드가 궁금하신가요? GitHub 저장소에서 확인해 보세요!

하드웨어 구매하기 이 프로젝트에 필요한 부품 목록은 아주 적었고, 프린터를 제외한 대부분의 부품은 Amazon이나 다른 온라인 쇼핑몰에서 쉽게 찾을 수 있습니다.

  • 도트 매트릭스 프린터
  • Raspberry Pi Zero W [링크]
  • 시리얼(Serial) to USB 어댑터 [링크]
  • 전원 공급 장치

내가 구매한 프린터는 80년대 중반으로 보이는 Star NP-10 모델이었습니다. 100% 확신할 수는 없지만, 시리얼 포트가 있는 도트 매트릭스 프린터라면 아마 무엇이든 잘 작동할 것입니다. 가격은 대략 80~120달러 정도였지만, '작동 여부 불확실'이라고 표시되어 있던 덕분에 절반 가격인 약 40달러에 구입할 수 있었습니다. 잉크 리본 카트리지(타자기 같아서 정말 멋지지 않나요!)를 조금 닦고 조정해야 했지만, 그 후에는 바로 켜졌고 테스트 페이지 출력도 문제없이 진행되었습니다.

그 후 모든 것을 연결했습니다. 라즈베리 파이(Raspberry Pi)는 집 Wi-Fi에 연결하고, USB를 통해 프린터의 시리얼 포트와 연결했습니다. 프린터를 켜고 Pi에 SSH로 접속한 후, 프린터가 /dev/usb/lp0 경로에서 정상적으로 인식되는 것을 확인했습니다. 자, 이제 어떻게 이 녀석을 인쇄하게 만들 수 있을까요?

프린터 제어 코드 파악하기 프린터가 lp0에서 사용 가능하기 때문에, 단순히 원시 텍스트(raw text)를 echo 명령어로 보내면 프린터에서 출력되는지 확인해 보고 싶었습니다. 그래서 다음 명령어를 실행했습니다:

echo "Hello, world!" > /dev/usb/lp0

그랬더니 파일에 접근할 수 없다는 에러가 발생했습니다. 아쉽게도 권한(permission) 문제였습니다. 하지만 chmod 명령어를 사용하면 쉽게 해결할 수 있었습니다:

sudo chmod 666 /dev/usb/lp0

이보다 더 좋은 처리 방법이 있을 수도 있지만, 이 명령어 덕분에 echo가 전달되었고 프린터에서 텍스트가 인쇄되는 것을 볼 수 있었습니다! 훌륭합니다. 이 파일을 통해 프린터로 원시 데이터를 보낼 수 있게 되었으니, 이제 이를 더 확장할 방법을 찾아봅시다.

나는 일상적으로 PHP를 주 언어로 사용하며, 이번 프로젝트도 예외는 아니었습니다. fopen()을 통해 파일에 접근하고 텍스트를 기록하는 기본적인 PHP 스크립트를 작성했습니다. 몇 문장과 공백, 그리고 유니코드 아트(unicode art)를 몇 가지 시도해 보았지만, 프린터가 내가 보낸 만큼의 문자를 모두 지원하지는 않는다는 것을 금방 깨달았습니다.

그래서 이 프린터가 실제로 어떻게 작동하는지 파고들어야 할 때라고 생각했습니다. 인터넷 수집가들의 노력과 헌신 덕분에, 나는 이 프린터의 전체 매뉴얼이 스캔되어 PDF로 업로드된 것을 찾을 수 있었습니다. 알고 보니, 프린터의 오래된 연식 때문이거나 아니면 단순히 제조사의 결정 때문인지, 이 프린터는 매우 특정한 문자 세트(character set)만을 허용했습니다. IBM PC의 코드 페이지 437(Code Page 437)을 느슨하게 기반으로 하며, 대부분의 표준 알파벳 및 숫자 문자로 구성되어 있지만, 소수의 특수 기호, 선 및 상자 그리기 기호도 포함되어 있었습니다. 정말 멋지네요!

이러한 문자를 프린터로 보내는 것은 아주 간단합니다. PHP에서 다음과 같이 16진수(hex) 값을 echo로 출력하기만 하면 됩니다:

$horizontalDouble = "\xCD"; $deg = "\xF8"; echo str_repeat($horizontalDouble, 24); echo '78' . $deg . 'F' . PHP_EOL;

훌륭합니다. 이제 프린터에 텍스트를 문제없이 출력할 수 있고, 약간의 특수 문자 및 디자인 기호도 포함할 수 있게 되었습니다. 이제 매일 아침 내가 어떤 것을 보고 싶은지 결정할 차례입니다.

데이터 수집하기 나는 나만의 맞춤형 1면을 위해 네 가지의 뚜렷한 섹션을 원한다는 것을 알고 있었습니다. 날씨, 주식, 주요 뉴스 헤드라인, 그리고 인기 레딧(reddit) 게시물 몇 가지입니다. 결국, 이것들이 내가 아침마다 스마트폰에서 주로 확인하는 것들이기 때문입니다. 또한, 이것은 실험적인 프로젝트이기 때문에...

원문 보기
원문 보기 (영어)
For a while now I've started my day by unlocking my phone and scrolling through different news and social media sites to see what's going on. It's not exactly great for my mental health and I've been trying to cut down on screen time for a while. I still want to stay up-to-date though, especially after I get up in the morning. I recently purchased a dot matrix printer from eBay, and thought it would be a great excuse to have a custom "front page" printed out and ready for me each day. So, that's what I built! Printer ASMR noises in the video below 👇 I'll take this article to dive in and show you what I used, how I set it up, and the PHP script that powers it all. Interested in that full source code? Check it out on the GitHub repo ! Purchasing the hardware The supply list for this project was pretty small, and with the exception of the printer, most of this can be found on Amazon or other online retailers. Dot matrix printer Raspberry Pi Zero W [ link ] Serial to USB adapter [ link ] Power supply The printer I purchased was a Star NP-10 from what looks like the mid-80's. I can't be 100% sure, but any dot matrix printer with a serial port should do the trick. The prices range from about $80-120 USD, but I was able to get this one for about half that price because it was marked as "unsure if working". It did need a little cleaning up and some tuning of the ink ribbon cartridge (isn't that cool, it's like a typewriter!), but after that it fired right up and ran through the test page print. After that, I hooked everything up. The Raspberry Pi is connected to my WiFi, and then via USB to the serial port of the printer. After turning on the printer and ssh ing into the Pi, I can verify that the printer is available at /dev/usb/lp0 . Now, how do I get this thing to print? Figuring out the printer's code Because the printer is available at lp0 I wanted to see if I could just echo raw text to it and have it come through the printer. So I ran the following: echo " Hello, world! " > /dev/usb/lp0 Which resulted in an error that the file couldn't be accessed. Bummer, a permissions issue. Easily fixed though with some chmod 'ing: sudo chmod 666 /dev/usb/lp0 There might be a better way to handle that, but it allowed my echo to go through, and I saw the text available on the printer! Alright, I can send raw data to the printer via this file, so let's find a way to scale this up. I use PHP as my language of choice in a day-to-day basis, and this is no exception. I write a basic script that accesses the file through fopen() and starts writing text to it. I try a few sentences, some spacing, and then some unicode art, but quickly find out that there's not as much character support on the printer as I was sending. So I thought it was about time that I start digging into how this thing actually works . Thanks to the hard work and dedication of internet hoarders, I found a full manual for the printer scanned and uploaded as a PDF. Come to find out, either because of the age or just the manufacturing decision, this printer has a very specific character set that it accepts. Loosely based off of the IBM PC's Code Page 437 it consists mostly of your standard alpha-numeric characters, but with a small set of special symbols, lines, and boxes. Neat! Sending these characters to the printer is pretty straightforward, I can just echo out the hex values with PHP like so: $ horizontalDouble = " \xCD " ; $ deg = " \xF8 " ; echo str_repeat ($ horizontalDouble , 24 ); echo ' 78 ' . $ deg . ' F ' . PHP_EOL ; Alright, so I'm able to write text to the printer just fine, and include some special characters and design symbols. Now I need to figure out what I want to see every morning. Gathering the data I knew I wanted four distinct sections for my personal front page: weather, stocks, major news headlines, and a few top reddit posts . After all, that's usually what I end up look at on my phone in the morning. Additionally, since this is an experimental project, I wanted to remain super cheap for this data, free if at all possible. Thankfully there's an amazing GitHub repo for free and public APIs, so I just went through there and found the ones I needed. The weather pulls from Open-Meteo and no API key is needed Stocks data pulls from twelvedata that offers a generous free tier News headlines pull from NYTimes which has a decent free tier, good enough for this project Reddit posts pull from Reddit JSON which is free (but I had to spoof my User-Agent) For each of the sections, I wrote some basic PHP code to pull in the payload from the API endpoint and compile the data I wanted into a larger overall array. I only wanted specific stocks, types of headlines, and subreddit posts, and if any of the sections couldn't have data to present I just simply crash the script early so I can start it again at a later time. This can be seen in this snippet which I use for pulling news headlines: // Get news headlines data echo " Fetching news headlines data... " . PHP_EOL ; $ newsUrl = NEWS . " ?api-key= " . NEWSKEY ; $ newsData = []; $ newsAmount = 0 ; $ data = json_decode ( file_get_contents ($ newsUrl ), true); if (! isset ($ data [ ' results ' ])) { die ( " Unable to retrieve news data " ); } foreach ($ data [ ' results ' ] as $ article ) { if ( ($ article [ ' type ' ] === ' Article ' ) && ( in_array ($ article [ ' section ' ], [ ' U.S. ' , ' World ' , ' Weather ' , ' Arts ' ])) && ($ newsAmount < MAXNEWS ) ) { $ newsData [] = $ article ; $ newsAmount ++; } } The NEWS , NEWSKEY , and MAXNEWS variables are all constants instantiated at the top of the script for easy editing. Running this compiles everything I want to see displayed on the paper, but now I need to take on the actual task of formatting everything for the printer, and sending it the raw data. Printing out my front page I could just print out a heading for each section, but that's a little boring. I wanted a bit of flair to the project, so I decided to have a box at the top displaying the current date, day of the week, and my front page name all nicely bordered. It took a little math, but I got everything working by using a combination of the hex values I talked about above, str_repeat and the knowledge that the page width for this printer is 80 characters . Now, just simply go through each section, print a little heading: str_repeat ($ horizontalSingle , 3 ) . " WEATHER " . str_repeat ($ horizontalSingle , ( PAGEWIDTH - 9 )) . " \n " ; And then print out the data that I want displayed for that section: " " . round (($ weatherData [ ' daily ' ][ ' daylight_duration ' ][ 0 ] / 3600 ), 2 ) . " h of Sunlight - Sunrise: " . date ( ' g:ia ' , strtotime ($ weatherData [ ' daily ' ][ ' sunrise ' ][ 0 ])) . " - Sunset: " . date ( ' g:ia ' , strtotime ($ weatherData [ ' daily ' ][ ' sunset ' ][ 0 ])) . " \n " ; For the weather and stocks, I knew I wouldn't hit the edge of the paper so I just wrote everything in single long lines. But that's different for the news headlines and Reddit posts. If I just feed the printer one long line of text, it's smart enough to cut it and start printing on another line. But, I didn't want a word getting cut off in the middle and starting on the next line. So I implemented a small function to handle line length and instead return back an array of lines with a max length corresponding to the page width. function splitString ($ string , $ maxLength = PAGEWIDTH ) { $ result = []; $ words = explode ( ' ' , $ string ); $ currentLine = '' ; foreach ($ words as $ word ) { if ( strlen ($ currentLine . $ word ) <= $ maxLength ) { $ currentLine .= ($ currentLine ? ' ' : '' ) . $ word ; } else { if ($ currentLine ) { $ result [] = $ currentLine ; $ currentLine = $ word ; } else { // If a single word is longer than maxLength, split it $ result [] = substr ($ word , 0 , $ maxLength ); $ currentLine = substr ($ word , $ maxLength ); } } } if ($ currentLine ) { $ result [] = $ currentLine ; } return $ result ; } Then I can just u