はじめに
前回 プリンターに接続できることが確認できたので、プリントサーバーとして動作させてみました。下記のように、印刷したいファイルのURLをトピックに入れて発行したら、M5Stackが受け取ってファイルを印刷するイメージです。
検証
課題がいくつかありました。本当はAmazon FreeRTOSを使いたかったけど、まだAmazon FreeRTOSがBLEのClient部分がまだ実装されていないとか、M5StackのBLEがメモリをたくさん使うといったところです。次のページを参考にさせて頂きました。
テストコードはこちら。
main.cpp
/**
* Test print for BlueTooth Printer
*/
#include <M5Stack.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
#include "BLERemoteDescriptor.h"
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#define PRINTER_NAME "BlueTooth Printer" // Printer BD Name
static esp_bd_addr_t peer_bd_addr = {0x00,0x11,0x22,0x33,0x44,0x55}; // Printer BD Adress
static String printer_service_uuid = "";
static String printer_characteristic_uuid = "";
BLEClient* pClient;
std::map<std::string, BLERemoteService*> *pRemoteServices;
BLERemoteService *printerService;
std::map<std::string, BLERemoteCharacteristic*> *pRemoteCharacteristics;
BLERemoteCharacteristic *printerCharacteristic;
std::map<std::string, BLERemoteDescriptor*> *pRemoteDescriptors;
BLERemoteDescriptor *printerDescriptor;
volatile boolean deviceBleConnected = false; // flag BLE conneted
char *ssid = "<SSID>";
char *password = "<パスワード>";
WiFiClient client;
WiFiClientSecure httpsClient;
PubSubClient mqttClient(httpsClient);
const char *endpoint = "<エンドポイント>"; // Example: xxxxxxxxxxxxxx.iot.ap-northeast-1.amazonaws.com
const int port = 8883;
String pubTopic = "/sample123/complete";
String subTopic = "/sample123";
#define THING_NAME "ESP32_printer"
long messageSentAt = 0;
int dummyValue = 0;
char pubMessage[128];
const char* rootCA = "-----BEGIN CERTIFICATE-----\n" \
"...\n" \
"-----END CERTIFICATE-----\n";
const char* certificate = "-----BEGIN CERTIFICATE-----\n" \
"...\n" \
"-----END CERTIFICATE-----\n";
const char* privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" \
"...\n" \
"-----END RSA PRIVATE KEY-----\n";
/*
* Connect To Wi-Fi
*/
void connectWiFi(void)
{
// Start WiFi
Serial.print("Connecting to Wi-Fi: ");
Serial.print(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("\nConnected.");
delay(500);
}
/*
* Disconnect from Wi-Fi
*/
void disconnectWiFi()
{
WiFi.disconnect();
}
void mqttCallback (char* topic, byte* payload, unsigned int length);
/*
* Connect To MQTT
*/
void connectMQTT()
{
// Configure MQTT Client
httpsClient.setCACert(rootCA);
httpsClient.setCertificate(certificate);
httpsClient.setPrivateKey(privateKey);
mqttClient.setServer(endpoint, port);
mqttClient.setCallback(mqttCallback);
}
/*
* Disconnect from MQTT
*/
void disconnectMQTT()
{
mqttClient.disconnect();
}
/*
* Connect to AWS IoT
*/
void connectAWSIoT()
{
if (WiFi.status() != WL_CONNECTED)
{
connectWiFi();
}
Serial.println("Connecting to MQTT");
while (!mqttClient.connected())
{
if (mqttClient.connect(THING_NAME))
{
Serial.println("Connected.");
int qos = 0;
mqttClient.subscribe((subTopic + "/#").c_str(), qos);
Serial.println("Subscribed.");
}
else
{
Serial.print("Failed. Error state=");
Serial.println(mqttClient.state());
// Wait 5 seconds before retrying
delay(5000);
// re-connect
disconnectMQTT();
connectMQTT();
}
}
}
/*
* Client callback
*/
class PrinterClientCallbacks : public BLEClientCallbacks
{
void onConnect(BLEClient *pclient)
{
deviceBleConnected = true; // set ble connected flag
Serial.printf("%s connected\n", PRINTER_NAME);
}
void onDisconnect(BLEClient *pclient)
{
pclient->disconnect();
deviceBleConnected = false; // clear ble connected flag
Serial.printf("%s disconnected\n", PRINTER_NAME);
}
};
/*
* Notify Callback (but seems not called)
*/
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic,
uint8_t *pData, size_t length, bool isNotify)
{
Serial.printf("notifyCallbacklength=%d, isNofity=%d\n", length, isNotify);
Serial.printf("uuid: %s\n", pBLERemoteCharacteristic->getUUID().toString().c_str());
}
/*
* Connect to printer
*/
bool connectToServer(BLEAddress pAddress)
{
Serial.printf("Create a connection to addr: %s\n", pAddress.toString().c_str());
BLEDevice::init("M5Stack");
pClient = BLEDevice::createClient();
Serial.printf(" – Client created\n");
pClient->setClientCallbacks(new PrinterClientCallbacks());
Serial.printf(" – Connecting to server…\n");
if (pClient->connect(pAddress)) // connect to the remove BLE Server.
{
Serial.printf(" - Connected to printer\n");
}
else
{
Serial.printf(" - Connection failed\n");
return false;
}
Serial.printf("Retrieving service UUIDs...\n");
pRemoteServices = pClient->getServices();
for (const auto &p : *pRemoteServices)
{
String uuid = p.first.c_str();
Serial.printf("UUID: %s\n", uuid.c_str());
if (!uuid.endsWith("-0000-1000-8000-00805f9b34fb"))
{
printer_service_uuid = uuid;
}
}
if (printer_service_uuid == "")
{
Serial.printf(" – Printer service not found\n");
return false;
}
printerService = pRemoteServices->at(printer_service_uuid.c_str());
if (printerService == nullptr)
{
Serial.printf(" – Service not found (UUID: %s)\n", printer_service_uuid.c_str());
return false;
}
else
{
Serial.printf(" – Service found (UUID: %s)\n", printer_service_uuid.c_str());
}
// printer characteristic
Serial.printf("Retrieving printer characteristics...");
pRemoteCharacteristics = printerService->getCharacteristics();
for (const auto &p : *pRemoteCharacteristics)
{
String uuid = p.first.c_str();
Serial.printf("UUID: %s\n", uuid.c_str());
}
if (pRemoteCharacteristics->size() > 0)
{
printerCharacteristic = printerService->getCharacteristic(pRemoteCharacteristics->begin()->first.c_str());
if (printerCharacteristic == nullptr)
{
Serial.printf(" – Notify characteristic not found (UUID: %s)\n", pRemoteCharacteristics->begin()->first.c_str());
return false;
}
else
{
Serial.printf(" – Notify characteristic found (UUID: %s)\n", pRemoteCharacteristics->begin()->first.c_str());
}
}
else
{
Serial.printf(" – Notify characteristic not found\n");
return false;
}
printerCharacteristic->registerForNotify(notifyCallback, true); //register callback
return true;
}
/*
* Scan device & log
*/
void scan()
{
Serial.printf("UUID, Name, RSSI, Manufacturer\n");
// Serial.printf("UUID, Name, RSSI\n");
// disconnect Wi-Fi
disconnectMQTT();
disconnectWiFi();
// connect to BLE
BLEDevice::init("M5Stack");
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setActiveScan(false);
BLEScanResults foundDevices = pBLEScan->start(3);
int count = foundDevices.getCount();
for (int i = 0; i < count; i++)
{
BLEAdvertisedDevice d = foundDevices.getDevice(i);
Serial.printf(d.getAddress().toString().c_str());
Serial.printf(", ");
Serial.printf(d.getName().c_str());
Serial.printf(", ");
Serial.printf("%d", d.getRSSI());
Serial.printf(", ");
if (d.haveManufacturerData())
{
std::string data = d.getManufacturerData();
int manu = data[1] << 8 | data[0];
Serial.printf("%d", manu);
}
Serial.printf("\n");
}
pBLEScan->clearResults();
// disconnect BLE
BLEDevice::deinit(true);
deviceBleConnected = false;
ESP.restart();
}
void testCharacter()
{
printerCharacteristic->writeValue(0x1b); // NORMAL TEXT
printerCharacteristic->writeValue(0x21);
printerCharacteristic->writeValue(0x00);
printerCharacteristic->writeValue("[Font A]\n");
printerCharacteristic->writeValue("!\"#$%&\'()*+,-./01234567890:;<=>?\n");
printerCharacteristic->writeValue("@ABCDEFGHIJKLMNOPQRSTUVWXYZ\n");
}
void testJapanese()
{
printerCharacteristic->writeValue(0x1b); // NORMAL TEXT
printerCharacteristic->writeValue(0x21);
printerCharacteristic->writeValue(0x00);
printerCharacteristic->writeValue("[Japanese Hiragana]\n");
printerCharacteristic->writeValue({'\xA4', '\xA2', '\xA4', '\xA4', '\xA4', '\xA6', '\xA4', '\xA8', '\xA4', '\xAA', '\x0a', '\0'}); // あいうえお
printerCharacteristic->writeValue({'\xA3', '\xB1', '\xA3', '\xB2', '\xA3', '\xB3', '\xA3', '\xB4', '\xA3', '\xB5'});
printerCharacteristic->writeValue({'\xA3', '\xB6', '\xA3', '\xB7', '\xA3', '\xB8', '\xA3', '\xB9', '\xA3', '\xB0', '\x0a', '\0'}); // 1234567890
}
/*
* Test print
*/
void testPrint()
{
// disconnect Wi-Fi
disconnectMQTT();
disconnectWiFi();
// connect to BLE
BLEDevice::init("M5Stack");
for (int i = 0; i < 3; i++)
{
if (deviceBleConnected)
{
testCharacter();
testJapanese();
break;
}
connectToServer(peer_bd_addr);
sleep(3);
}
// disconnect BLE
client.flush();
pRemoteCharacteristics->clear();
pRemoteServices->clear();
pClient->disconnect();
// pClient->setClientCallbacks(NULL);
BLEDevice::deinit(true);
deviceBleConnected = false;
ESP.restart();
}
// Utility to extract header value from headers
String getHeaderValue(String header, String headerName)
{
return header.substring(strlen(headerName.c_str()));
}
/*
* Print from S3
*/
void printFromS3(String& url)
{
String host = "";
int s3port = 80;
String bin = "";
Serial.println("printFromS3: " + url);
long contentLength = 0;
bool isValidContentType = false;
if (url.startsWith("http://"))
{
host = url.substring(7);
}
else if (url.startsWith("https://")) // not supported yet
{
host = url.substring(8);
s3port = 443;
}
else
{
host = url;
}
if (url.indexOf("/") > -1)
{
bin = host.substring(host.indexOf("/"));
host = host.substring(0, host.indexOf("/"));
}
Serial.println("Connecting to: " + String(host));
// Connect to S3
if (client.connect(host.c_str(), s3port))
{
Serial.println("Fetching Bin: " + String(bin));
client.print(String("GET ") + bin + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Cache-Control: no-cache\r\n" +
"Connection: close\r\n\r\n");
unsigned long timeout = millis();
while (client.available() == 0)
{
if (millis() - timeout > 5000)
{
Serial.println("Client Timeout !");
client.stop();
return;
}
}
while (client.available())
{
String line = client.readStringUntil('\n');
line.trim();
if (!line.length())
{
//headers ended
break;
}
if (line.startsWith("HTTP/1.1"))
{
if (line.indexOf("200") < 0)
{
Serial.println("Got a non 200 status code from server. Exiting OTA Update.");
break;
}
}
if (line.startsWith("Content-Length: "))
{
contentLength = atol((getHeaderValue(line, "Content-Length: ")).c_str());
Serial.println("Got " + String(contentLength) + " bytes from server");
}
if (line.startsWith("Content-Type: "))
{
String contentType = getHeaderValue(line, "Content-Type: ");
Serial.println("Got " + contentType + " payload.");
if (contentType == "application/octet-stream" || contentType == "text/plain")
{
isValidContentType = true;
}
}
}
} else {
// Connect to S3 failed
Serial.println("Connection to " + String(host) + " failed. Please check your setup");
}
// get data
if (contentLength && isValidContentType)
{
Serial.println("Start printing");
// get print data
String text = client.readString();
// diconnect Wi-Fi
client.flush();
disconnectWiFi();
// connect to BLE
for (int i = 0; i < 3; i++)
{
if (deviceBleConnected)
{
break;
}
connectToServer(peer_bd_addr);
sleep(3);
}
// print datas
printerCharacteristic->writeValue(0x1b); // NORMAL TEXT
printerCharacteristic->writeValue(0x21);
printerCharacteristic->writeValue(0x00);
for (int i = 0; i < text.length(); i++)
{
printerCharacteristic->writeValue((uint8_t *)text.substring(i, i + 1).c_str(), 1, true);
}
printerCharacteristic->writeValue(0x1b); // NORMAL TEXT
printerCharacteristic->writeValue(0x21);
printerCharacteristic->writeValue(0x00);
}
// disconnect BLE
client.flush();
pRemoteCharacteristics->clear();
pRemoteServices->clear();
pClient->disconnect();
BLEDevice::deinit(true);
deviceBleConnected = false;
Serial.println("Finished printing");
// publish print result
connectAWSIoT();
mqttClient.publish(pubTopic.c_str(), "{}");
ESP.restart();
}
/*
* print data url
*/
/*
String getUrl(char* topic, byte* payload, unsigned int length)
{
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, payload);
if (error)
{
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.c_str());
const char* err = "http://xxx";
return err;
}
String url = doc["url"];
return url;
}
*/
/*
* MQTT Callback (Receive)
*/
void mqttCallback (char* topic, byte* payload, unsigned int length)
{
Serial.print("Received. topic=");
Serial.println(topic);
// payload
// for (int i = 0; i < length; i++)
// {
// Serial.print((char)payload[i]);
// }
// Serial.print("\n");
// get url from payload
// String url = getUrl(topic, payload, length);
// get url from topic
String url = String(topic);
if (url.startsWith(subTopic))
{
url = url.substring(subTopic.length() + 1);
}
printFromS3(url);
}
/*
* Setup
*/
void setup()
{
Serial.begin(115200);
M5.begin();
// dacWrite(25, 0); // Speaker OFF
M5.Lcd.setTextFont(2);
M5.Lcd.println("Please wait...");
connectWiFi();
connectAWSIoT();
M5.Lcd.clearDisplay(0);
M5.Lcd.setTextFont(2);
M5.Lcd.println("BLE print test.");
M5.Lcd.println();
M5.Lcd.println("A button: scan BLE & log.");
M5.Lcd.println("B button: test print.");
// BLEDevice::init("M5Stack");
}
/*
* main loop
*/
void loop()
{
if (!mqttClient.connected())
{
connectAWSIoT();
}
mqttClient.loop();
if(M5.BtnA.wasPressed())
{
scan();
}
if(M5.BtnB.wasPressed())
{
testPrint();
}
M5.update();
}
まとめ
課題はありますが、うまく動くようになれば、こんなことにも使えそうです。