Wi-FiがないM5StickVを、M5StickCと繋ぎLINEに投稿してみるまでの手順

## 概要
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73020/2d6fb7a5-8af2-819f-001b-433a59468140.png" width=70%　>
M5StickVには、WifiやBlueToothなどの通信機能が付いていません。そこで、WifiやBlueToothと接続する機能のあるM5StickCとつなげてみました。M5StickVとM5StickCとをGroveポートで繋ぎ、M5StickVとM5StickCとでUART通信できるようにしました。

## 機材
M5StickV
RISC-Vとニューラルネットワークプロセッサ（KPU）搭載のK210を内蔵したAIカメラです。
https://www.switch-science.com/catalog/5700/
M5StickVの開発環境は、maixpyを使いました。
https://maixpy.sipeed.com/en/
M5StickC
ESP32を搭載した小型のM5Stackです。
https://www.switch-science.com/catalog/5517/
M5StickCの開発環境は、arduinoを使いました。
https://docs.m5stack.com/#/ja/core/m5stickc

#準備
## M5StickVでUART通信を準備する
M5StickVのGroveポートは、GPIOの34番と35番です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73020/c1967081-7405-4184-e5e9-3b6f8702d7e4.png" width=50%　>
https://maixpy.sipeed.com/en/libs/Maix/fpioa.html
FPIOA (Field Programmable Input and Output Array)という機能で、GPIOの34番と35番をUARTに割り付けます。

```py
from fpioa_manager import fm
from machine import UART
fm.register(35, fm.fpioa.UART2_TX, force=True)
fm.register(34, fm.fpioa.UART2_RX, force=True)
```

## M5StickVでUART通信で送信する
UARTで文字を送る場合、maixpyのwrite関数を使います。
本稿執筆時点(2019/7/31)で、英語のmaixpyのUARTのドキュメントはないのですが、中国語のドキュメントはあったので、これを参照しました。
https://maixpy.sipeed.com/zh/libs/machine/uart.html

```py
uart_Port = UART(UART.UART2, 115200,8,0,0, timeout=1000, read_buf_len= 4096)
data_packet = bytearray([0x00,0x00,0x00,0x00,0x00])
uart_Port.write(data_packet )
```

## M5StickCでUART通信を準備する
M5StickCのGroveポートは、GPIOの32番と33番です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73020/a294a913-17b0-f358-a592-889526bb37c8.png" width=20%　>

```c
HardwareSerial serial_ext(2);
serial_ext.begin(115200, SERIAL_8N1, 32, 33);
```

## M5StickCでUART通信を受信する
arduinoでは、UARTから、1byteデータを受信する場合は、readByte関数を使います。

```c
if (serial_ext.available()) {
uint8_t rx_buffer;
rx_buffer= serial_ext.readByte(rx_buffer);
}
```

arduinoでは、UARTから、複数byteデータを受信する場合は、readBytes関数を使います。

```c
if (serial_ext.available()) {
uint8_t rx_buffer[10];
int rx_size = serial_ext.readBytes(rx_buffer, 10);
}
```

#実装

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73020/b97e4144-3cf6-1019-1382-e3bf72460523.png" width=80%　>
M5StickVのボタンを押したときに、M5StickVは撮影した画像データをM5StickCにUART通信で送信し、M5StickCでは画像データをUART通信から受信してLINE投稿するように実装しました。

## 識別用パケットの実装

UART通信で、大サイズのデータを送信する場合、データがどこから始まるのかを検出できるようにしないといけません。
そこで、データの先端に識別用のパケットを埋め込む方法を取りました。

###M5StickV (MaixPY)

M5StickV (MaixPY)では、データがどこから始まるのかを検出できるようにするために、データの前に0xFF,0xD8,0xEAの識別用パケットdata_packet を送信するデータに付与します。また、識別用パケットに画像データサイズを格納します。画像データサイズは1byteでは収まらないため、3byteに分割します。

```py
img_size1 = (img.size()& 0xFF0000)>>16
img_size2 = (img.size()& 0x00FF00)>>8
img_size3 = (img.size()& 0x0000FF)>>0
data_packet = bytearray([0xFF,0xD8,0xEA,0x01,img_size1,img_size2,img_size3,0x00,0x00,0x00])
uart_Port.write(data_packet)
uart_Port.write(img_buf)
```

###M5StickC (Arduino)
M5StickC (Arduino)では、データの先頭に0xFF,0xD8,0xEAの識別用パケットがついていた場合にデータの受信を開始します。
識別用パケットに画像データサイズも3byteに分けて格納されていますので、画像データサイズを復号します。

```c
static const uint8_t packet_begin[3] = { 0xFF, 0xD8, 0xEA };

 if (serial_ext.available()) {
    uint8_t rx_buffer[10];
    int rx_size = serial_ext.readBytes(rx_buffer, 10);
    if (rx_size == 10) {   //packet receive of packet_begin
      if ((rx_buffer[0] == packet_begin[0]) && (rx_buffer[1] == packet_begin[1]) && (rx_buffer[2] == packet_begin[2])) {
        //image size receive of packet_begin
        jpeg_data.length = (uint32_t)(rx_buffer[4] << 16) | (rx_buffer[5] << 8) | rx_buffer[6];
        int rx_size = serial_ext.readBytes(jpeg_data.buf, jpeg_data.length);
      }
    }
  }
```

## M5StickV (MaixPY) のソースコード
M5StickV でAボタンが押されたら、写真データをUARTに送信するソ－スコードです。

```py


## This Source is M5StickV MaixPy
import network, socket, time, sensor, image,lcd
from Maix import GPIO
from fpioa_manager import fm, board_info
from machine import UART

#M5StickV Camera Start
clock = time.clock()
lcd.init()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.run(1)
sensor.skip_frames(time = 2000)
#M5StickV GPIO_UART
fm.register(35, fm.fpioa.UART2_TX, force=True)
fm.register(34, fm.fpioa.UART2_RX, force=True)
uart_Port = UART(UART.UART2, 115200,8,0,0, timeout=1000, read_buf_len= 4096)
#M5StickV ButtonA
but_a=GPIO(GPIO.GPIO1, GPIO.IN, GPIO.PULL_UP)
while True:
    clock.tick()
    img = sensor.snapshot()
    lcd.display(img)
#   IF Button A Push Then Image Send UART
    if but_a.value() == 0:
        img_buf = img.compress(quality=70)
        img_size1 = (img.size()& 0xFF0000)>>16
        img_size2 = (img.size()& 0x00FF00)>>8
        img_size3 = (img.size()& 0x0000FF)>>0
        data_packet = bytearray([0xFF,0xD8,0xEA,0x01,img_size1,img_size2,img_size3,0x00,0x00,0x00])
        uart_Port.write(data_packet)
        uart_Port.write(img_buf)
        time.sleep(1.0)
#   Send UART End
uart_Port.deinit()
del uart_Port
print("finish")



```

## M5StickC (Arduino)のソースコード

UARTから、写真データを受信するソ－スコードです。
読み取ったパケットが[0xFF,0xD8,0xEA]から始まるデータだった場合に、画像データの読み取りを行います。
-今回は、画像データをLINEに投稿してみました。
+さらに、今回は、受信した画像データをLINEに投稿してみました。

```c
#include <M5StickC.h>
#include <WiFi.h>
#include <ssl_client.h>
#include <WiFiClientSecure.h>
const char* ssid = "your_ssid";
const char* passwd = "your_passwd";

HardwareSerial serial_ext(2);

typedef struct {
  uint32_t length;
  uint8_t *buf;
} jpeg_data_t;

jpeg_data_t jpeg_data;
static const int RX_BUF_SIZE = 20000;
static const uint8_t packet_begin[3] = { 0xFF, 0xD8, 0xEA };

/*
sendLineNotify関数は、以下参照。
https://github.com/anoken/purin_wo_mimamoru_gijutsu/blob/master/2_6_M5Camera_Send_LineNotify/2_6_M5Camera_Send_LineNotify.ino
*/
void sendLineNotify(uint8_t* image_data, size_t image_sz);

void setup() {
  M5.begin();
  M5.Lcd.setRotation(3);
  M5.Lcd.setCursor(0, 30, 4);
  M5.Lcd.println("m5stick_uart_wifi_converter");

  setup_wifi();

  jpeg_data.buf = (uint8_t *) malloc(sizeof(uint8_t) * RX_BUF_SIZE);
  jpeg_data.length = 0;
  if (jpeg_data.buf == NULL) {
    Serial.println("malloc jpeg buffer 1 error");
  }

  serial_ext.begin(115200, SERIAL_8N1, 32, 33);
}

void loop() {
  M5.update();

  if (serial_ext.available()) {
    uint8_t rx_buffer[10];
    int rx_size = serial_ext.readBytes(rx_buffer, 10);
    if (rx_size == 10) {   //packet receive of packet_begin
      if ((rx_buffer[0] == packet_begin[0]) && (rx_buffer[1] == packet_begin[1]) && (rx_buffer[2] == packet_begin[2])) {
        //image size receive of packet_begin
        jpeg_data.length = (uint32_t)(rx_buffer[4] << 16) | (rx_buffer[5] << 8) | rx_buffer[6];
        int rx_size = serial_ext.readBytes(jpeg_data.buf, jpeg_data.length);

        //image processing, for example, line notify send image
        sendLineNotify(jpeg_data.buf, jpeg_data.length);
        //image processing end
      }
    }
  }
  vTaskDelay(10 / portTICK_RATE_MS);
}

void setup_wifi() {
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, passwd);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}


```

LINE Notifyに画像を投稿するsendLineNotify関数は、冗長になることから割愛しています。
以下の「プリンを見守る技術」のgithubに掲載されています。
https://github.com/anoken/purin_wo_mimamoru_gijutsu/blob/master/2_6_M5Camera_Send_LineNotify/2_6_M5Camera_Send_LineNotify.ino

##参考文献
Maixpy_m5stickv
https://github.com/sipeed/MaixPy/
MaixPy_scripts EXAMPLE
https://github.com/sipeed/MaixPy_scripts/
プリンを守る技術 ～その１：LINEへの通知～
https://qiita.com/nnn112358/items/8d2021bce1113d7e60ce
プリンを見守る技術
https://github.com/anoken/purin_wo_mimamoru_gijutsu/