LoginSignup
7
5

More than 3 years have passed since last update.

M5Stackをプリントサーバーにする

Posted at

はじめに

前回 プリンターに接続できることが確認できたので、プリントサーバーとして動作させてみました。下記のように、印刷したいファイルのURLをトピックに入れて発行したら、M5Stackが受け取ってファイルを印刷するイメージです。

m5stack_print_overview.png

検証

課題がいくつかありました。本当は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();
}

まとめ

課題はありますが、うまく動くようになれば、こんなことにも使えそうです。

m5stack_print_usecase1.png

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5