4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Opt TechnologiesAdvent Calendar 2020

Day 11

LINEThingsとM5CoreInkを使ってO2Oマーケティングを体験してみる

Last updated at Posted at 2020-12-11

概要

LINEThingsを使ってLINEとデバイスを連携させ、O2Oマーケティングの一例を再現してみる。今回は、M5Stack CoreInkという通信機能を持った電子ペーパーを値札として用い、LINEでアンケートに答えるとその値札の表示を変更し、購買を促す(本当かな?)といったものを作成した。

※注: 筆者がガス欠で説明不十分な点があるかと思います:pray:(2020/12/11)

やったこと

  • LINEThingsを用いて、LINEとM5Stack CoreInkを連携
  • LIFF上でアンケートに答えてもらったら、値札の表示を変更する

動作環境

  • maacOS Catalina ver.10.15.3
  • Python 3.7.0
  • python ライブラリ
    • line-bot-sdk==1.16.0
    • resspnder==1.3.2

うまくできなかったこと

  • M5Stack CoreInk側からLINE(スマホ)との接続を切断できなかった(すぐ再接続される)

説明しないこと

  • LINE BOTの作成方法
  • M5Stack CoreInkのsetup方法

LINE Thingsとは

getting-started-00.804e6cba.jpg

LINE Thingsは、LINEを介して、チャネルとBluetooth® Low Energy対応デバイスを連携し、操作を可能にするIoTプラットフォームです。
https://developers.line.biz/ja/docs/line-things/about-line-things/

M5Stack CoreInkとは

ざっと説明すると、WifiやBluetooth通信機能を備えた電子ペーパーデバイスです。

PXL_20201201_104424090.jpg 下記のページに詳細な説明があります。よければ、参考にしてください。 - [スイッチサイエンス販売ページ](https://www.switch-science.com/catalog/6735/)

この記事でいうO2Oマーケティングとは

**オンラインでの施策を、オフラインに反映させ、顧客の購買行動を促進させるマーケティンング手法。**とします。

本当は、参考ページのようなオフライン、オンライン両方で相互誘導できるようなマーケティング手法のことを言うのだと思います。。。(ざっくり認識です。)

今回の記事で具体例を挙げてみると、服屋さんに来店した顧客に、LINE上で回答できるアンケートを配信する。そのアンケートに回答すると、店舗にある値札が値引きされたり、おすすめのコーディネートなどが表示されたりする。それによって、顧客の購買意欲促進を狙う。(て感じなのかな...)

参考ページ:

フロー図

大体のフローはこんな感じです。
①今回作成するLINEBOTを友達登録している人のLINEと、値札(M5Stack CoreInk)がbluetooth接続される
②アンケートを送信する
③アンケートに回答する
④値札の表示が変更される

スクリーンショット 2020-12-11 10.14.32.png

手順

1. LINEBOTを作成する

適宜、LINEBOTを作成してください。
参考ページ:

2. Webhookされるサーバーを作成する

今回はLINEThingsを用いて、デバイスから送信されたメッセージに合わせて、利用者に送信するメッセージを変更する必要があります。ですので、メッセージを受け取り、処理をする部分を担うサーバーを作成します。

公式SDK
Messaging API対応のLINE公式SDKはこちらです。下記の中でお好きな言語を選択してください。

  • Java
  • PHP
  • Go
  • Perl
  • Ruby
  • Python
  • Node.js

私は、Pythonを用い、サーバー側の処理を作成しました。
ソースコードはこちら

今回特筆すべき部分としては、以下の処理で、M5 CoreInkからメッセージを受信した際に実行されるメソッドです。

main.py
#---略---

###
# deviceからメッセージ受信時
###
@handler.add(ThingsEvent)
def handle_message(event):
    
    if event.things is None:
        print(event)
        return
    if event.things.type != "scenarioResult":
        print(event)
        return
    if event.things.result.result_code != "success":
        print(event)
        return

    # 届いたメッセージをdecode
    message = base64.b64decode(event.things.result.ble_notification_payload).decode()
    if message[0] == "S":
        # LIFFアンケートページをトーク画面に返信する。
        liff_url = "https://liff.line.me/1655338407-PZBX17Wz"
        line_bot_api.reply_message(
           event.reply_token,
           TextSendMessage(text="今アンケートに答えると割引チャンス!\n" + liff_url)
        )
    else:
        line_bot_api.reply_message(
           event.reply_token,
           TextSendMessage(text="対応していません")
        )

#---略---

3. LINEBotと2で作成したサーバーを接続する

ここでは、
参考ページ:

4. LINEBotでLINEThingsを使えるようにする

下記記事がめちゃくちゃ詳細に記載してくれているので、おすすめです。

参考資料:

5. M5Stack CoreInkがLINEやサーバーと接続するための処理を追加する

ソースコードが割と長いため、折りたたんでいます。

LINEやサーバーとの接続時に使用するソースコード
ble.ino
#include <BLEServer.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <M5CoreInk.h>

Ink_Sprite InkPageSprite(&M5.M5Ink);

// Device Name: Maximum 30 bytes
#define DEVICE_NAME "Trial M5Stack"

// User service UUID: Change this to your generated service UUID
#define USER_SERVICE_UUID "{USER_SERVICE_UUID}"

// User service characteristics
#define WRITE_CHARACTERISTIC_UUID "{WRITE_CHARACTERISTIC_UUID}"
#define NOTIFY_CHARACTERISTIC_UUID "{NOTIFY_CHARACTERISTIC_UUID}"

// PSDI Service UUID: Fixed value for Developer Trial
#define PSDI_SERVICE_UUID "{PSDI_SERVICE_UUI}"
#define PSDI_CHARACTERISTIC_UUID "{PSDI_CHARACTERISTIC_UUID}"

BLEServer* thingsServer;
BLESecurity *thingsSecurity;
BLEService* userService;
BLEService* psdiService;
BLECharacteristic* psdiCharacteristic;
BLECharacteristic* writeCharacteristic;
BLECharacteristic* notifyCharacteristic;



bool deviceConnected = false;
bool oldDeviceConnected = false;

class serverCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
   deviceConnected = true;
  };

  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
  }
};
boolean flg = false;

class writeCallback: public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *bleWriteCharacteristic) {
    std::string value = bleWriteCharacteristic->getValue();
    String read_value;
    if (value.length() > 0) {
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          read_value = value[i];
        if (read_value == "1"){
            flg = true;
        }

        Serial.println();
        Serial.println("*********");
    }
  }
};

void setup() {
  M5.begin();
  Serial.begin(115200);
  
  BLEDevice::init("");
  BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM);

  // Security Settings
  BLESecurity *thingsSecurity = new BLESecurity();
  thingsSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);
  thingsSecurity->setCapability(ESP_IO_CAP_NONE);
  thingsSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);

  setupServices();
  startAdvertising();

  digitalWrite(LED_EXT_PIN,LOW);
  if( !M5.M5Ink.isInit()){
      Serial.printf("Ink Init faild");
      while (1) delay(100);   
  }
  M5.M5Ink.clear();
  if( InkPageSprite.creatSprite(0,0,200,200,true) != 0 ){
    Serial.printf("Ink Sprite creat faild");
  }
  Serial.printf("Ink Sprite creat successful");
  
  draw_nehuda();
  draw_nedan(false);
  InkPageSprite.pushSprite();  

}

void loop() {
  if (flg) {
      InkPageSprite.clear();
      draw_nehuda();
      draw_nedan(true);
      InkPageSprite.pushSprite();
      flg = false;
  }
  if ( M5.BtnUP.wasPressed() ) {
    // デバッグ用
    String mes = "S"+(String)1;
    notifyCharacteristic->setValue(mes.c_str());
    notifyCharacteristic->notify();
    Serial.println("BtnUP.wasPressed() == TRUE");
  }
  if ( M5.BtnDOWN.wasPressed() ) {
    // 再接続用
    Serial.println("BtnDOWN.wasPressed() == TRUE");
    oldDeviceConnected = false;
  }
  // Connection
  if (deviceConnected && !oldDeviceConnected) {
    oldDeviceConnected = deviceConnected;
    delay(3000);
    String mes = "S"+(String)2;
    notifyCharacteristic->setValue(mes.c_str());
    notifyCharacteristic->notify();
    Serial.println("connected");
    delay(3000);
    uint16_t connId = thingsServer->getConnId();
    thingsServer->disconnect(connId);
    thingsServer->getAdvertising()->stop();
  }
  M5.update();
}

void setupServices(void) {
  // Create BLE Server
  thingsServer = BLEDevice::createServer();
  thingsServer->setCallbacks(new serverCallbacks());

  // Setup User Service
  userService = thingsServer->createService(USER_SERVICE_UUID);
  // Create Characteristics for User Service
  writeCharacteristic = userService->createCharacteristic(WRITE_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_WRITE);
  writeCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
  writeCharacteristic->setCallbacks(new writeCallback());

  notifyCharacteristic = userService->createCharacteristic(NOTIFY_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_NOTIFY);
  notifyCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
  BLE2902* ble9202 = new BLE2902();
  ble9202->setNotifications(true);
  ble9202->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
  notifyCharacteristic->addDescriptor(ble9202);

  // Setup PSDI Service
  psdiService = thingsServer->createService(PSDI_SERVICE_UUID);
  psdiCharacteristic = psdiService->createCharacteristic(PSDI_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ);
  psdiCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);

  // Set PSDI (Product Specific Device ID) value
  uint64_t macAddress = ESP.getEfuseMac();
  psdiCharacteristic->setValue((uint8_t*) &macAddress, sizeof(macAddress));

  // Start BLE Services
  userService->start();
  psdiService->start();
}

void startAdvertising(void) {
  // Start Advertising
  BLEAdvertisementData scanResponseData = BLEAdvertisementData();
  scanResponseData.setFlags(0x06); // GENERAL_DISC_MODE 0x02 | BR_EDR_NOT_SUPPORTED 0x04
  scanResponseData.setName(DEVICE_NAME);

  thingsServer->getAdvertising()->addServiceUUID(userService->getUUID());
  thingsServer->getAdvertising()->setScanResponseData(scanResponseData);
  thingsServer->getAdvertising()->start();
}

6. 2で作成したサーバーに、サーバー側からM5CoreInkにwriteする処理を追加

M5CoreInkに接続・writeする処理

write.py
import asyncio
import time
from bleak import BleakClient, BleakScanner

WRITE_CHARACTERISTIC_UUID = "WRITE_CHARACTERISTIC_UUID"
mac_addr = "M5 CoreInkのMACアドレス"

async def run(address, loop):
    async with BleakClient(address, loop=loop) as client:
        x = await client.is_connected()
        print("Connected: {0}".format(x))
        write_value = bytearray(b"1")
        await client.write_gatt_char(WRITE_CHARACTERISTIC_UUID, write_value)
        print("write")
        # 5秒後に終了
        await asyncio.sleep(5.0, loop=loop)

if __name__=="__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(run(mac_addr, loop))

アンケートに回答したことを受信した際、上記処理を呼び出す

main.py
from write import run, mac_addr, WRITE_CHARACTERISTIC_UUID
#--- 略 ---

###
# メッセージ受信時
###
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    if event.message.text == "送信しました":
        loop = asyncio.get_event_loop()
        loop.run_until_complete(run(mac_addr, loop))
        line_bot_api.reply_message(
           event.reply_token,
           TextSendMessage(text="割引されるのでお待ちを")
        )

7. アンケートフォームを作成する

アンケートフォームを作成します。
ソースコードはこちら
スクリーンショット 2020-12-11 9.21.32.png

4
0
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
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?