TELNETサーマルプリンター
2024.03.08
YouTube でも紹介しています。画像をクリックすると再生できます。
●Bluetoothサーマルプリンタ

過去の記事「Bluetoothサーマルプリンター」では、Bluetooth接続により、ソースコードを印刷できるようにしました。
しかし、Bluetooth接続ではペアリングした端末からのサーマルプリンタへの印刷に限定されてしまいます。

そこで今回は、LAN内の複数の端末からTELNET接続によりサーマルプリンタを利用できるようにします。
動作確認には2種類のサーマルプリンタを使用しました。
■AS-289R2プリンタシールド

●入力電圧:5V±5%
●平均電流:約2A(印字率25% 2分割時)
●I/F:
・3.3V or 5Vレベル(JP5切替)
・9600 or 38400bps(JP6切替)
●ドット密度(ドット総数):8ドット/mm(384ドット/ライン)
●印字速度:約25mm/秒
●印字有効幅:48mm
●文字種類:
・UTF8
・ANK JIS160文字
・第一水準漢字2965文字(漢字 JIS C 6226-1983準拠)
・第二水準漢字3388文字(漢字 JIS C 6226-1983準拠)
・JIS非漢字524文字
・大文字0~9、A~Zの36文字
●文字のサイズ(H×W):
・8×16ドット:ANK
・12×24ドット:ANK(電源投入時ANK初期値)
・16x16ドット:ANK、第一水準漢字、第二水準漢字
・24×24ドット:ANK、第一水準漢字、第二水準漢字(電源投入時漢字初期値)
・48×96ドット:0-9、A-Z
●バーコード:QR、JAN(13,8)、2of5(ITF)、2of7(NW7)、3of9(CODE39)、UPC-A
●DP-EH600

日本語非対応の19200bos サーマルプリンター、転送速度は19200bpsです。
●Adafruit QT Py ESP32-S2 WiFi Dev Board with STEMMA QT
・ESP32-S2 240MHz
・4 MB Flash & 2 MB PSRAM
・2.4 GHz Wi-Fi (SoC)
・Two I2C ports
・Hardware UART
・Hardware SPI
・Hardware I2S on any pins
・3.3V regulator with 600mA peak output
Adafruit QT Py ESP32-S2
●Qt Py ESP32-S2 Pinout
AS-289R AS-289R2 DP-EH600 | | QT Py ESP32S2 | | |
| RX | - | RX | | |
| GND | - | GND | | |
| DP-EH600 GND | -10KΩ- | A2(pullup) | | |
| | └ | 3V | | |
| AS289R2 GND | -10KΩ- | A3(pullup) | | |
| | └ | 3V | | |
| AS289R | | NC | | |
| | | | | Battery |
| | | 5V | ←1N5817─ | 3.7V |
| | | ADAPTER | | |
| POWER | - | DC5V/4A | | |

DP-EH600は、分解してディスプレイケースに固定しています。
印刷時の消費電力が大きいため、USBからの給電では印刷できません。 5V3A以上のACアダプタをプリンタに接続します。

DP-EH600, AS-289R2をジャンパーピンによりGNDに落とすことで選択しています。
A2,A3いずれもHIGHの場合はデフォルトで旧型AS-289R:9600bpsが選択されます。

ジャンクのカメラにはいっていたリチウムバッテリーを電源として取り付けています。
背面にあるのは充電モジュールです。

QTPy ESP32S2のUSB端子に電源を接続すると、5V端子にはUSBからの電源が供給され5Vが出力されます。
この5V端子は入力端子としても使えますが、USB端子への逆流防止用のダイオードがマイコンボードに内蔵されています。
外部電源用USB端子は、マイコン側のUSB接続を解除した状態で使用しますが、誤って接続したままの状態にある場合に、外部電源への逆流防止用に整流ダイオードを取り付けています。
外部電源からの電圧損失の少ないショットキーバリアダイオードを選びました。
ソースコードのビルドには、PlatformIOを使用しています。
Arduino開発環境構築 PlatformIO
$ mkdir -p ~/As289r2
$ cd ~/As289r2
$ pio init -b adafruit_qtpy_esp32s2
【telnet_prn.ino】
#include <WiFi.h>
#include <HardwareSerial.h>
#include "Telnet.h"
Telnet telnet;
#define SECRET_SSID "xxxxxxxxxxxx"
#define SECRET_PASS "xxxxxxxxxxxxx"
const uint8_t PRIMARY_DNS[4] = {192,168,11,1};
const uint8_t GATEWAY[4] = {192,168,11,1};
const uint8_t SUBNETMASK[4] = {255,255,255,0};
const uint8_t LOCAL_IP[4] = {192,168,11,64};
#define TELNET_PORT (23)
WiFiServer server(TELNET_PORT);
HardwareSerial SerialPRN(1);
#define BAUDRATE_9600 (9600)
#define BAUDRATE_19200 (19200)
#define BAUDRATE_38400 (38400)
#define PIN_19200 A2
#define PIN_38400 A3
#define PRN_AS_289R 0x01
#define PRN_DP_EH600 0x02
#define PRN_AS_289R2 0x04
unsigned char prn_model;
int max_column = 48;
int row = 0;
int col = 4;
int numByte;
void printWifiStatus() {
// print the SSID of the network you're attached to:
telnet.print("SSID: %s\n",WiFi.SSID());
// print your board's IP address:
IPAddress ip = WiFi.localIP();
telnet.print("IP Address: %d:%d;%d;%d\n",ip[0],ip[1],ip[2],ip[3]);
// print the received signal strength:
long rssi = WiFi.RSSI();
telnet.print("signal strength (RSSI):%d dBm\n",rssi);
}
void set_prn(HardwareSerial *prn)
{
// 初期化(印字バッファ内データ抹消))
prn->write(0x1b);
prn->write(0x40);
if (prn_model == PRN_DP_EH600) {
max_column = 42;
// Character font B (9*17)
prn->write(0x1b);
prn->write(0x21);
prn->write(0x01);
// Set line spacing
prn->write(0x1b);
prn->write(0x33);
prn->write(0x14);
}
if ((prn_model == PRN_AS_289R)||(prn_model == PRN_AS_289R2)) {
max_column = 48;
// ANK文字フォント指定(8x16ドット)
prn->write(0x1b);
prn->write(0x68);
prn->write(0x30);
// 漢字フォント指定(16x16ドット)
prn->write(0x12);
prn->write(0x53);
prn->write(0x31);
// 文字コード指定(UTF-8)
prn->write(0x1b);
prn->write(0x24);
prn->write(0x30);
// 行間スペース量指定(0ドット)
prn->write(0x1b);
prn->write(0x41);
prn->write(0x01);
}
// 文字間スペース量指定(0ドット)
prn->write(0x1b);
prn->write(0x20);
prn->write((uint8_t)0x00);
}
void put_prn(Telnet *tn, HardwareSerial *prn)
{
unsigned char ch;
int i;
while (telnet.available()) {
if (row == 0) {
tn->write(0x0a);
prn->write(0x0d);
prn->printf("%03d ",++row);
tn->print("%03d ",row);
}
ch = telnet.read();
switch(ch) {
case 0x09: // TAB
tn->write(' ');
prn->write(' ');
++col;
break;
case 0x0d:
--col;
break;
case 0x0a:
// 行末に改行コードがきた場合は、自動改行されるので無視する
if ( col % max_column ) {
tn->write(ch);
prn->write(0x0d);
}
prn->printf("%03d ",++row);
tn->print("%03d ",row);
col = 4;
break;
case EOF: // EOF
case 0x1a:
tn->write(0x0a);
tn->write(0x0a);
prn->write(0x0d);
prn->write(0x0d);
prn->write(0x0d);
row = 0;
col = 4;
return;
break;
default:
if ((ch & 0x80) == 0x00) { // 1byte文字
tn->write(ch);
prn->write(ch);
++col;
} else {
// 48桁目に2バイト文字がきた場合は、改行する
if ( !((++col) % max_column)) {
prn->write(0x0d);
col = 2;
} else {
++col;
}
// UTF-8文字コードのバイト数判定
if ((ch & 0xE0) == 0xC0) { numByte=2; // 2byte文字
} else if ((ch & 0xF0) == 0xE0) { numByte=3; // 3byte文字
} else if ((ch & 0xF8) == 0xF0) { numByte=4; // 4byte文字
} else if ((ch & 0xFC) == 0xF8) { numByte=5; // 5byte文字
} else if ((ch & 0xFE) == 0xFC) { numByte=6; // 6byte文字
} else { numByte=0;
}
if ((prn_model == PRN_AS_289R)||(prn_model == PRN_AS_289R2)) {
tn->write(ch);
prn->write(ch);
} else {
tn->write('?');
prn->write('?');
}
for(i=1;i<numByte;) {
if (tn->available()) {
ch = tn->read();
if ((prn_model == PRN_AS_289R)||(prn_model == PRN_AS_289R2)) {
tn->write(ch);
prn->write(ch);
}
i++;
} else {
delay(10);
}
}
}
break;
}
if (!(col % max_column)) {
// AS-289R2は自動改行される
tn->write(0x0a);
if ((prn_model == PRN_AS_289R)||(prn_model == PRN_AS_289R2)) prn->write(0x0a);
}
}
}
void setup()
{
pinMode(PIN_19200, INPUT);
pinMode(PIN_38400, INPUT);
if (!WiFi.config(LOCAL_IP, GATEWAY, SUBNETMASK, PRIMARY_DNS)) while(1);
// WiFi STA設定
WiFi.mode(WIFI_STA);
WiFi.begin(SECRET_SSID, SECRET_PASS); // Connect to WPA/WPA2 network
while (WiFi.status() != WL_CONNECTED) delay(100);
server.begin();
telnet.begin(&server);
printWifiStatus();
if (digitalRead(PIN_19200) == LOW) {
prn_model = PRN_DP_EH600;
SerialPRN.begin(BAUDRATE_19200);
telnet.print("DP-EH600 detected!\n");
} else if (digitalRead(PIN_38400) == LOW) {
prn_model = PRN_AS_289R2;
SerialPRN.begin(BAUDRATE_38400);
telnet.print("AS-289R2 detected!\n");
} else {
prn_model = PRN_AS_289R;
SerialPRN.begin(BAUDRATE_9600);
telnet.print("JP-QR204 detected!\n");
}
set_prn(&SerialPRN);
}
void loop()
{
if (telnet.connected()) {
put_prn(&telnet, &SerialPRN);
} else {
telnet.begin(&server);
printWifiStatus();
row = 0;
col = 4;
}
}
【Telnet.h】
#ifndef __TELNET_H
#define __TELNET_H
#include <WiFi.h>
#define DEBUG_BUF_SIZE 256
class Telnet
{
private:
WiFiServer *tnServer;
WiFiClient client;
char buf[DEBUG_BUF_SIZE];
unsigned char ch;
public:
Telnet();
~Telnet();
void begin(WiFiServer *tnserver);
void print(const char* format, ...);
void write(unsigned char ch);
boolean available();
boolean connected();
unsigned char read();
};
#endif /*__TELNET_H*/
【Telnet.cpp】
#include "Telnet.h"
Telnet::Telnet()
{
tnServer = NULL;
client = NULL;
}
Telnet::~Telnet() {}
void Telnet::begin(WiFiServer *tnserver)
{
tnServer = tnserver;
unsigned char negotiation[25] = {
0xFF,0xFB,0x03, // IAC WILL suppress_go_ahead
0xFF,0xFD,0x01, // IAC DO echo
0xFF,0xFD,0x1F, // IAC DO window_size
0xFF,0xFB,0x05, // IAC WILL status
0xFF,0xFD,0x21, // IAC DO remote_flow_control
0xFF,0xFD,0x18, // IAC DO terminal_type
0xFF,0xFD,0x03, // IAC DO suppress_go_ahead
0xFF,0xFB,0x01, // IAC WILL echo
};
client = tnServer->available();
while(1) {
if(client) {
if (client.connected()) {
while (client.available()) client.read();
for(int i=0;i<24;++i) client.write(negotiation[i]);
while (client.available()) client.read();
return;
}
}
client = tnServer->available();
delay(100);
}
}
boolean Telnet::connected()
{
if (client && client.connected()) return true;
else return false;
}
boolean Telnet::available()
{
if (connected()) {
while (client.available()) {
ch = client.read();
if (ch==0xFF) {
ch = client.read();
switch(ch) {
case 0xF0:
break;
case 0xFA:
for(int i=0;i<5;++i) ch = client.read();
break;
default:
ch = client.read();
break;
}
return false;
} else {
return true;
}
}
}
return false;
}
void Telnet::print(const char* format, ...)
{
va_list args;
va_start(args, format);
vsprintf(buf,format,args);
va_end(args);
if (client.connected()) {
for (int len = 0; buf[len]; len++) client.write(buf[len]);
}
}
void Telnet::write(unsigned char uch)
{
client.write(uch);
}
unsigned char Telnet::read()
{
return ch;
}
【コード解説】
void setup()
初期処理では、LAN内の固定IPアドレスを設定して、ステーションモードでWiFiを起動しています。
また、23番ポートのTELNETも開始しています。
ジャンパーピン設定によりサーマルプリンタ種別を判定し、通信速度を設定しています。
void set_prn()
AS-289シリーズとDP-EH600では使える最小フォントが異なります。
AS-289では1行に最大48文字、DP-EH600では42文字です。
set_prn()では機種別のフォントサイズ指定、行間指定を行っています。
void put_prn()
漢字コード識別、行末制御、行番号出力、制御コード評価などを行っています。
$ pio run -t upload
それでは、実行してみます。

TeraTermを起動して、サーマルプリンタのIPアドレスを指定します。

セッションが確立されると、接続情報が表示されます。

Tera Term 端末設定の改行コードを送受信ともに[LF]にします。

画面上から文字列を入力すると、行番号の付いた文字列がプリント出力されると同時にTeraTermの画面上に印刷イメージが表示されます。

サーマルプリンター DP-EH600 では、漢字コードが含まれる場合はその部分を「?」で出力しています。
●テキストファイルを印刷する場合
テキストファイルの漢字コードはUTF-8、改行コードはLFです。

ファイルタブからファイル送信を選択します。

ファイルを選択、バイナリにチェックを入れて送信します。

AS289R2で漢字印刷しています。48桁目が漢字の場合には47桁目で改行されます。
●行番号をリセットする場合
テキストファイル送信では、ファイル終端のEOFを検知して、行番号をリセットしています。
ここでは、TeraTerm画面上から入力した際の行番号リセットの方法を説明します。
send $1A
上記マクロを、eof.ttl という名前でTeratermディレクトリに保存します。

TeraTerm の[コントロール]→[マクロ]で eof.ttl を開くと EOFが送信され。行番号がリセットされます。
●日本語非対応機種における日本語印刷
ナダ電子のAS-289R2はすでに販売を終了しており、現時点で後継機種のアナウンスはありません。
ここで紹介したDP-EH600やJP-QR204などのESC/Pコマンドをサポートし、かつTTLシリアルポートが実装されている機種であれば、現時点でも容易に調達できます。
これらの機種で日本語を簡単に印刷したいのであれば、Pythonの画像処理ライブラリPillow(PIL)のImageDrawを用いて、画像として印刷することができます。
漢字フォントの表示と拡張
ESC/Pコマンドは画像処理もサポートしています。「漢字フォントの表示と拡張」で紹介した日本語ビットマップフォントを用いて、画像印刷することも可能です。
|
Raspberry Pi(ラズベリー パイ)は、ARMプロセッサを搭載したシングルボードコンピュータ。イギリスのラズベリーパイ財団によって開発されている。
たいていのことは100日あれば、うまくいく。長田英知著
「時間がなくて、なかなか自分のやりたいことができない」
「一念発起して何かを始めても、いつも三日坊主で終わってしまう」
「色んなことを先延ばしにしたまま、時間だけが過ぎていく」
そこで本書では、そんな著者が独自に開発した、
まったく新しい目標達成メソッド「100日デザイン」について、
その知識と技術を、余すところなくご紹介します。
まんがで納得ナポレオン・ヒル 思考は現実化する
OLとして雑務をこなす日々に飽き足らず、科学者だった父が残した薬品を商品化すべく、起業を決意した内山麻由(27)。彼女はセミナーで知り合った謎の女性からサポートを得ながら、彼女と二人三脚でナポレオン・ヒルの成功哲学を実践し、さまざまな問題を乗り越えていく。
ヒル博士の<ゴールデンルール>に従い、仕事に、恋に全力疾走する彼女の、成功への物語。
今日は人生最悪で最高の日 1秒で世界を変えるたったひとつの方法 ひすいこたろう著
偉人の伝記を読むと、最悪な日は、不幸な日ではなく、新しい自分が始まる日であることがわかります。最悪な出来事は、自分の人生が、想像を超えて面白くなる兆しなのです。偉人伝を読むことで、このときの不幸があったおかげで、未来にこういう幸せがくるのかと、人生を俯瞰する視線が立ち上がるのです。
ご飯は私を裏切らない heisoku著
辛い現実から目を背けて食べるご飯は、いつも美味しく幸せを届けてくれる。
29歳、中卒、恋人いない歴イコール年齢。バイト以外の職歴もなく、短期バイトを転々とする日々。ぐるぐると思索に耽るけど、ご飯を食べると幸せになれる。奇才の新鋭・heisokuが贈るリアル労働グルメ物語!
【最新版Gemini 3に対応!】できるGemini (できるシリーズ)
Geminiを「最強の知的生産パートナー」として使いこなすための、実践的なノウハウを凝縮した一冊です。
基本的な操作方法から、具体的なビジネスシーンでの活用、日々の業務を自動化するGoogle Workspaceとの連携、さらには自分だけのオリジナルAIを作成する方法まで余すところなく解説します。
Rustプログラミング完全ガイド 他言語との比較で違いが分かる!
Rustの各手法や考え方を幅広く解説!
500以上のサンプルを掲載。実行結果も確認。
全24章の包括的なチュートリアル。
ポチらせる文章術
販売サイト・ネット広告・メルマガ・ブログ・ホームページ・SNS…
全WEB媒体で効果バツグン!
カリスマコピーライターが教える「見てもらう」「買ってもらう」「共感してもらう」すべてに効くネット文章術
小型で便利な Type-C アダプター USB C オス - USB3.1 オスアダプター
Type-C端子のマイコンボードをこのアダプタを介して直接Raspberry Piに挿すことができます。ケーブルなしで便利なツールです。
Divoom Ditoo Pro ワイヤレススピーカー
15W高音質重低音/青軸キーボード/Bluetooth5.3/ピクセルアート 専用アプリ/USB接続/microSDカード
電源供給USBケーブル スリム 【5本セット】
USB電源ケーブル 5V DC電源供給ケーブル スリム 【5本セット】 電源供給 バッテリー 修理 自作 DIY 電子工作 (100cm)
|