2019年8月12日月曜日

ESP-WROOM-02(ESP8266)で,9軸センサーBMX055から取得した値をパソコン(mac)にOSCフォーマットで送信する.

前の記事で,9軸センサーの値をUDPでテキスト形式でパソコンに送信できたので,いよいよESP-WROOM-02でBMX055から取得した9軸データをOSCフォーマットでパソコン(mac)に送信することにします.

Arduino用のOSCライブラリーはここの物を使いました.「Clone or Download」ボタンをクリックしてzipファイルをダウンロードし,Arduinoの「Sketch」メニュー→「Include Library」→「Add .ZIP Library...」を選択してzipファイルを指定し,「Choose」ボタンをクリックしてインストールします.再度「Sketch」メニュー→「Include Library」を選択すると,「Contributed libraries」の下の方に「OSC」が表示されます.

ESP-WROOM-02のプログラムを以下のように書き換えます.

//================================================================//
//  AE-BMX055             ESP8266                            //
//    3V3                    +3.3V(1)                                  //
//    GND                    GND(17)                                  //
//    SDA                    IO4(12)                              //
//    SCL                    IO5(16)                              //
//                                                                //
//   (JP6,JP4,JP5はショートした状態)                              //
//   http://akizukidenshi.com/catalog/g/gK-13010/                 //
//   -> 3.3Vで使うのでJP6はショートしない
//================================================================//

// 2019/8/12
// send accel,gyro,compass data to the destination port
// by text data
// add OSC library from https://github.com/CNMAT/OSC

#include 
#include 
#include 
#include 
#include  // OSC library https://github.com/CNMAT/OSC
#include  // sprintf()

WiFiUDP UDP;

IPAddress HOSTIP(192, 168, 3, 4); // IP address of the destination
const int remoteUdpPort = 30000;  // port number of the destination

IPAddress myIP(192, 168, 3, 100);  // this address is needed for WiFi config
const int localUdpPort = 31000;   // local port number

// since mac is connected to the internet through iPhone,
// iPhone is the WiFi router.
const char *ssid = "iPhone7PlusBlack";
const char *password = "qatacygd60y8u";

// BMX055 加速度センサのI2Cアドレス  
#define Addr_Accl 0x19  // (JP1,JP2,JP3 = Openの時)
// BMX055 ジャイロセンサのI2Cアドレス
#define Addr_Gyro 0x69  // (JP1,JP2,JP3 = Openの時)
// BMX055 磁気センサのI2Cアドレス
#define Addr_Mag 0x13   // (JP1,JP2,JP3 = Openの時)

// センサーの値を保存するグローバル関数
float xAccl = 0.00;
float yAccl = 0.00;
float zAccl = 0.00;
float xGyro = 0.00;
float yGyro = 0.00;
float zGyro = 0.00;
short int   xMag  = 0;
short int   yMag  = 0;
short int   zMag  = 0;

void connectWiFi() {
  Serial.println("WiFi setup start");
  WiFi.begin(ssid, password);
  WiFi.config(myIP, WiFi.gatewayIP(), WiFi.subnetMask());
  Serial.println("WiFi setup done");
  Serial.println("start_connect");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("CONNECTED!");
}

void reconnectWiFi() {
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.disconnect();
    Serial.println("disconnected! reconnect WiFi.");
    connectWiFi();
  }
}

void setup()
{
  // Wire(Arduino-I2C)の初期化
  Wire.begin();
  // デバック用シリアル通信は9600bps
  Serial.begin(9600);
  //BMX055 初期化
  BMX055_Init();
  delay(300);
  //WiFi初期化
  WiFi.mode(WIFI_STA);
  UDP.begin(localUdpPort);
  connectWiFi();
}

void loop()
{
  char buf[128];
  Serial.println("--------------------------------------"); 

  //BMX055 加速度の読み取り
  BMX055_Accl();
  sprintf(buf, "/ZIGSIM/hh3-zig/accel: (%f, %f, %f)",
          xAccl, yAccl, zAccl);
  Serial.println(buf);
  OSCMessage msgAccel("/ZIGSIM/hh3-zig/accel");
  msgAccel.add(xAccl).add(yAccl).add(zAccl);
  UDP.beginPacket(HOSTIP, remoteUdpPort);
  msgAccel.send(UDP);
  UDP.endPacket();
  msgAccel.empty();  
  delay(100);
  reconnectWiFi();

  //BMX055 ジャイロの読み取り
  BMX055_Gyro();
  sprintf(buf, "/ZIGSIM/hh3-zig/gyro: (%f, %f, %f)",
          xGyro, yGyro, zGyro);
  Serial.println(buf);
  OSCMessage msgGyro("/ZIGSIM/hh3-zig/gyro");
  msgGyro.add(xGyro).add(yGyro).add(zGyro);
  UDP.beginPacket(HOSTIP, remoteUdpPort);
  msgGyro.send(UDP);
  UDP.endPacket();
  msgGyro.empty();
  delay(100);
  reconnectWiFi();
  
  //BMX055 磁気の読み取り
  BMX055_Mag();
  sprintf(buf, "/ZIGSIM/hh3-zig/mag: (%d, %d, %d)",
          xMag, yMag, zMag);
  Serial.println(buf);
  OSCMessage msgMag("/ZIGSIM/hh3-zig/mag");
  msgMag.add(xMag).add(yMag).add(zMag);
  UDP.beginPacket(HOSTIP, remoteUdpPort);
  msgMag.send(UDP);
  UDP.endPacket();
  msgMag.empty();
  delay(100);
  reconnectWiFi();

  delay(700);
}

//=====================================================================================//
void BMX055_Init()
{
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x0F); // Select PMU_Range register
  Wire.write(0x03);   // Range = +/- 2g
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x10);  // Select PMU_BW register
  Wire.write(0x08);  // Bandwidth = 7.81 Hz
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x11);  // Select PMU_LPW register
  Wire.write(0x00);  // Normal mode, Sleep duration = 0.5ms
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x0F);  // Select Range register
  Wire.write(0x04);  // Full scale = +/- 125 degree/s
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x10);  // Select Bandwidth register
  Wire.write(0x07);  // ODR = 100 Hz
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x11);  // Select LPM1 register
  Wire.write(0x00);  // Normal mode, Sleep duration = 2ms
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4B);  // Select Mag register
  Wire.write(0x83);  // Soft reset
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4B);  // Select Mag register
  Wire.write(0x01);  // Soft reset
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4C);  // Select Mag register
  Wire.write(0x00);  // Normal Mode, ODR = 10 Hz
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4E);  // Select Mag register
  Wire.write(0x84);  // X, Y, Z-Axis enabled
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x51);  // Select Mag register
  Wire.write(0x04);  // No. of Repetitions for X-Y Axis = 9
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x52);  // Select Mag register
  Wire.write(0x16);  // No. of Repetitions for Z-Axis = 15
  Wire.endTransmission();
}
//=====================================================================================//
void BMX055_Accl()
{
  unsigned char data[6];
  for (int i = 0; i < 6; i++)
  {
    Wire.beginTransmission(Addr_Accl);
    Wire.write((2 + i));// Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Accl, 1);// Request 1 byte of data
    // Read 6 bytes of data
    // xAccl lsb, xAccl msb, yAccl lsb, yAccl msb, zAccl lsb, zAccl msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data to 12-bits
  xAccl = ((data[1] * 256) + (data[0] & 0xF0)) / 16;
  if (xAccl > 2047)  xAccl -= 4096;
  yAccl = ((data[3] * 256) + (data[2] & 0xF0)) / 16;
  if (yAccl > 2047)  yAccl -= 4096;
  zAccl = ((data[5] * 256) + (data[4] & 0xF0)) / 16;
  if (zAccl > 2047)  zAccl -= 4096;
  xAccl = xAccl * 0.0098; // renge +-2g
  yAccl = yAccl * 0.0098; // renge +-2g
  zAccl = zAccl * 0.0098; // renge +-2g
}
//=====================================================================================//
void BMX055_Gyro()
{
  unsigned char data[6];
  for (int i = 0; i < 6; i++)
  {
    Wire.beginTransmission(Addr_Gyro);
    Wire.write((2 + i));    // Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Gyro, 1);    // Request 1 byte of data
    // Read 6 bytes of data
    // xGyro lsb, xGyro msb, yGyro lsb, yGyro msb, zGyro lsb, zGyro msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data
  xGyro = (data[1] * 256) + data[0];
  if (xGyro > 32767)  xGyro -= 65536;
  yGyro = (data[3] * 256) + data[2];
  if (yGyro > 32767)  yGyro -= 65536;
  zGyro = (data[5] * 256) + data[4];
  if (zGyro > 32767)  zGyro -= 65536;

  xGyro = xGyro * 0.0038; //  Full scale = +/- 125 degree/s
  yGyro = yGyro * 0.0038; //  Full scale = +/- 125 degree/s
  zGyro = zGyro * 0.0038; //  Full scale = +/- 125 degree/s
}
//=====================================================================================//
void BMX055_Mag()
{
  unsigned short int data[8];
  for (int i = 0; i < 8; i++)
  {
    Wire.beginTransmission(Addr_Mag);
    Wire.write((0x42 + i));    // Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Mag, 1);    // Request 1 byte of data
    // Read 6 bytes of data
    // xMag lsb, xMag msb, yMag lsb, yMag msb, zMag lsb, zMag msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data
  xMag = ((data[1] <<8 data="">>3));
  if (xMag > 4095)  xMag -= 8192;
  yMag = ((data[3] <<8 data="">>3));
  if (yMag > 4095)  yMag -= 8192;
  zMag = ((data[5] <<8 data="">>3));
  if (zMag > 16383)  zMag -= 32768;
}


OSCデータを受け取って表示するパソコン側のPythonプログラムは以下のとおりです.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Python 3.7.3 on macOS 10.15 Catalina Beta
# 2019/8/7
# receive OSC data by UDP

import argparse
from socket import *
from pythonosc import dispatcher
from pythonosc import osc_server

def handle_zigsim_osc_message(addr, *args):
    try:
        print("{0}: {1}".format(addr, args))
    except ValueError:
        print("ValueError occrurred")

def start_OSC_server(ip_addr, port_num):
    print("starting on address {}, port {}".format(ip_addr, port_num))

    # OSC dispatcher
    dispatch = dispatcher.Dispatcher()
    dispatch.map("/ZIGSIM/*", handle_zigsim_osc_message )

    server = osc_server.ThreadingOSCUDPServer(
        (ip_addr, port_num), dispatch)
    print("Serving on {}".format(server.server_address))
    server.serve_forever()

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--ip", default="127.0.0.1",
                        help="IP address to listen on")
    parser.add_argument("--port", type=int, default=30000,
                        help="port to listen on")
    args = parser.parse_args()
    start_OSC_server(args.ip, args.port)


動作環境は以下のとおりです.

  • パソコン側のIPアドレス:192.168.3.4
  • OSCデータを受け取るUDPポート番号:30000
  • 送信側ESP-WROOM-02のUUID:hh3_zig
パソコン側のターミナルソフトで以下のようにPythonプログラムを動かします.

% ./UDPserver2.py --ip 192.168.3.4 --port 30000


ESP-WROOM-02にプログラムを書き込んでリセットボタンを押すと,パソコンで以下のようにデータが受信できていることが確認できました.

/ZIGSIM/hh3-zig/accel: (-0.11760000139474869, 0.2547999918460846, 9.790200233459473)
/ZIGSIM/hh3-zig/gyro: (-0.3571999967098236, -0.20520000159740448, -0.04179999977350235)
/ZIGSIM/hh3-zig/mag: (2049, -762, -237)
/ZIGSIM/hh3-zig/accel: (-0.09799999743700027, 0.24500000476837158, 9.829400062561035)
/ZIGSIM/hh3-zig/gyro: (-0.47119998931884766, -0.2013999968767166, 0.11779999732971191)
/ZIGSIM/hh3-zig/mag: (1822, -762, -237)


ESP-WROOM-02(ESP8266)で,9軸センサーBMX055から取得した値をパソコンにUDPで送信する.

前の記事で,ESP-WROOM-02 (ESP8266)9軸センサーBMX055からの値を取得できたので,これをUDPでパソコンのPythonプログラムに送信しました. 

IPアドレスの設定は以下の通りです.
  • パソコン側のIPアドレス:172.20.10.4
パソコンの受け側のUDPのポート番号を以下の値とします.
  • パソコン側のUDPポート番号:30000
パソコン側のPythonのプログラムは以下のとおりです.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Python 3.7.3 on macOS 10.15 Catalina Beta
# 2019/8/4
# receive JSON data by UDP

import argparse
import threading
import time
from socket import *

class ServerThread(threading.Thread):
    def __init__(self, ADDRESS="127.0.0.1", PORT=30000):
        threading.Thread.__init__(self)
        self.data = None
        self.kill_flag = False
        # line information
        self.ADDRESS = ADDRESS
        self.PORT = PORT
        self.BUFSIZE = 1024
        self.ADDR = (gethostbyname(self.ADDRESS), self.PORT)
        print(self.ADDR)
        # bind
        self.udpServSock = socket(AF_INET, SOCK_DGRAM)
        self.udpServSock.bind(self.ADDR) # HOST, PORTでbinding

    def run(self):
        while True:
            try:
                # データ受信
                data, self.addr = self.udpServSock.recvfrom(self.BUFSIZE)
                if len(data) > 0:
                    print("receive {} bytes".format(len(data)))
                    print("received data: {}".format(data))
                # OSCはデコードできなくてexceptionが起こる
                # JSONだとテキストなのでデコードできる
                self.data = data.decode()
                print("decoded data: {}".format(self.data))
            except:
                print("exception occrurred")
                #pass

def start_UDP_server(ip_address, port_number):
    print("starting on address {}, port {}".format(ip_address, port_number))
    th = ServerThread(ip_address, port_number)
    th.setDaemon(True)
    th.start()

    while True:
        time.sleep(1.0)
        if not th.data:
            continue
        print(th.data)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--ip", default="127.0.0.1",
                        help="IP address to listen on")
    parser.add_argument("--port", type=int, default=30000,
                        help="port to listen on")
    args = parser.parse_args()
    start_UDP_server(args.ip, args.port)


ESP-WROOM-02側のプログラムは以下のとおりです.

//================================================================//
//  AE-BMX055             ESP8266                            //
//    3V3                    +3.3V(1)                                  //
//    GND                    GND(17)                                  //
//    SDA                    IO4(12)                              //
//    SCL                    IO5(16)                              //
//                                                                //
//   (JP6,JP4,JP5はショートした状態)                              //
//   http://akizukidenshi.com/catalog/g/gK-13010/                 //
//   -> 3.3Vで使うのでJP6はショートしない
//================================================================//

#include 
#include 
#include 
#include 
#include  // sprintf()

WiFiUDP UDP;
IPAddress HOSTIP(172, 20, 10, 4);
const int remoteUdpPort = 30000;
IPAddress myIP(172, 20, 10, 10);
const int localUdpPort = 31000;
const char *ssid = "iPhone7PlusBlack";
const char *password = "qatacygd60y8u";

// BMX055 加速度センサのI2Cアドレス  
#define Addr_Accl 0x19  // (JP1,JP2,JP3 = Openの時)
// BMX055 ジャイロセンサのI2Cアドレス
#define Addr_Gyro 0x69  // (JP1,JP2,JP3 = Openの時)
// BMX055 磁気センサのI2Cアドレス
#define Addr_Mag 0x13   // (JP1,JP2,JP3 = Openの時)

// センサーの値を保存するグローバル関数
float xAccl = 0.00;
float yAccl = 0.00;
float zAccl = 0.00;
float xGyro = 0.00;
float yGyro = 0.00;
float zGyro = 0.00;
short int   xMag  = 0;
short int   yMag  = 0;
short int   zMag  = 0;

void connectWiFi() {
  Serial.println("WiFi setup start");
  WiFi.begin(ssid, password);
  WiFi.config(myIP, WiFi.gatewayIP(), WiFi.subnetMask());
  Serial.println("WiFi setup done");
  Serial.println("start_connect");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("CONNECTED!");
}

void sendWiFi(char byteData[]) {
  if (UDP.beginPacket(HOSTIP, remoteUdpPort)) {
    UDP.write(byteData);
    UDP.endPacket();
    Serial.println(byteData);
  }
}

void reconnectWiFi() {
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.disconnect();
    Serial.println("disconnected! reconnect WiFi.");
    connectWiFi();
  }
}

void setup()
{
  // Wire(Arduino-I2C)の初期化
  Wire.begin();
  // デバック用シリアル通信は9600bps
  Serial.begin(9600);
  //BMX055 初期化
  BMX055_Init();
  delay(300);
  //WiFi初期化
  WiFi.mode(WIFI_STA);
  UDP.begin(localUdpPort);
  connectWiFi();
}

void loop()
{
  char buf[128];
  Serial.println("--------------------------------------"); 

  //BMX055 加速度の読み取り
  BMX055_Accl();
  sprintf( buf, "Accel= %f,%f,%f", xAccl, yAccl, zAccl);
  sendWiFi(buf);
  delay(500);
  reconnectWiFi();

  //BMX055 ジャイロの読み取り
  BMX055_Gyro();
  sprintf( buf, "Gyro= %f,%f,%f", xGyro, yGyro, zGyro);
  sendWiFi(buf);
  delay(500);
  reconnectWiFi();
  
  //BMX055 磁気の読み取り
  BMX055_Mag();
  sprintf( buf, "Mag= %d,%d,%d", xMag, yMag, zMag);
  sendWiFi(buf);
  delay(500);
  reconnectWiFi();  
}

//=====================================================================================//
void BMX055_Init()
{
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x0F); // Select PMU_Range register
  Wire.write(0x03);   // Range = +/- 2g
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x10);  // Select PMU_BW register
  Wire.write(0x08);  // Bandwidth = 7.81 Hz
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x11);  // Select PMU_LPW register
  Wire.write(0x00);  // Normal mode, Sleep duration = 0.5ms
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x0F);  // Select Range register
  Wire.write(0x04);  // Full scale = +/- 125 degree/s
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x10);  // Select Bandwidth register
  Wire.write(0x07);  // ODR = 100 Hz
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x11);  // Select LPM1 register
  Wire.write(0x00);  // Normal mode, Sleep duration = 2ms
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4B);  // Select Mag register
  Wire.write(0x83);  // Soft reset
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4B);  // Select Mag register
  Wire.write(0x01);  // Soft reset
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4C);  // Select Mag register
  Wire.write(0x00);  // Normal Mode, ODR = 10 Hz
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4E);  // Select Mag register
  Wire.write(0x84);  // X, Y, Z-Axis enabled
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x51);  // Select Mag register
  Wire.write(0x04);  // No. of Repetitions for X-Y Axis = 9
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x52);  // Select Mag register
  Wire.write(0x16);  // No. of Repetitions for Z-Axis = 15
  Wire.endTransmission();
}
//=====================================================================================//
void BMX055_Accl()
{
  unsigned char data[6];
  for (int i = 0; i < 6; i++)
  {
    Wire.beginTransmission(Addr_Accl);
    Wire.write((2 + i));// Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Accl, 1);// Request 1 byte of data
    // Read 6 bytes of data
    // xAccl lsb, xAccl msb, yAccl lsb, yAccl msb, zAccl lsb, zAccl msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data to 12-bits
  xAccl = ((data[1] * 256) + (data[0] & 0xF0)) / 16;
  if (xAccl > 2047)  xAccl -= 4096;
  yAccl = ((data[3] * 256) + (data[2] & 0xF0)) / 16;
  if (yAccl > 2047)  yAccl -= 4096;
  zAccl = ((data[5] * 256) + (data[4] & 0xF0)) / 16;
  if (zAccl > 2047)  zAccl -= 4096;
  xAccl = xAccl * 0.0098; // renge +-2g
  yAccl = yAccl * 0.0098; // renge +-2g
  zAccl = zAccl * 0.0098; // renge +-2g
}
//=====================================================================================//
void BMX055_Gyro()
{
  unsigned char data[6];
  for (int i = 0; i < 6; i++)
  {
    Wire.beginTransmission(Addr_Gyro);
    Wire.write((2 + i));    // Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Gyro, 1);    // Request 1 byte of data
    // Read 6 bytes of data
    // xGyro lsb, xGyro msb, yGyro lsb, yGyro msb, zGyro lsb, zGyro msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data
  xGyro = (data[1] * 256) + data[0];
  if (xGyro > 32767)  xGyro -= 65536;
  yGyro = (data[3] * 256) + data[2];
  if (yGyro > 32767)  yGyro -= 65536;
  zGyro = (data[5] * 256) + data[4];
  if (zGyro > 32767)  zGyro -= 65536;

  xGyro = xGyro * 0.0038; //  Full scale = +/- 125 degree/s
  yGyro = yGyro * 0.0038; //  Full scale = +/- 125 degree/s
  zGyro = zGyro * 0.0038; //  Full scale = +/- 125 degree/s
}
//=====================================================================================//
void BMX055_Mag()
{
  unsigned short int data[8];
  for (int i = 0; i < 8; i++)
  {
    Wire.beginTransmission(Addr_Mag);
    Wire.write((0x42 + i));    // Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Mag, 1);    // Request 1 byte of data
    // Read 6 bytes of data
    // xMag lsb, xMag msb, yMag lsb, yMag msb, zMag lsb, zMag msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data
  xMag = ((data[1] <<8 data="">>3));
  if (xMag > 4095)  xMag -= 8192;
  yMag = ((data[3] <<8 data="">>3));
  if (yMag > 4095)  yMag -= 8192;
  zMag = ((data[5] <<8 data="">>3));
  if (zMag > 16383)  zMag -= 32768;
}


パソコン側で以下のようにPythonプログラムを起動します.

% ./UDPserver1.py --ip 172.20.10.4 --port 30000


ESP-WROOM-02側のシリアルモニタでは,以下のようにデータが送信できています.

--------------------------------------
Accel= -0.098000,0.098000,9.780400
Gyro= -0.049400,-0.262200,0.095000
Mag= -503,11,-498
--------------------------------------


パソコン側では以下のようにデータが受信できています.

receive 34 bytes
received data: b'Accel= -0.098000,0.098000,9.780400'
decoded data: Accel= -0.098000,0.098000,9.780400
Accel= -0.098000,0.098000,9.780400
receive 34 bytes
received data: b'Gyro= -0.049400,-0.262200,0.095000'
decoded data: Gyro= -0.049400,-0.262200,0.095000
receive 17 bytes
received data: b'Mag= -503,11,-498'
decoded data: Mag= -503,11,-498
Mag= -503,11,-498


ESP-WROOM-02で9軸センサーBMX055を使う

I2Cで使えるBMX055 9軸センサーモジュール秋月のESP-WROOM-02ボードから使ってみました.

I2Cの配線について

BMX055は以下の端子が出ています.

  • ピン1: GND
  • ピン2: SDA (I2Cデータ)
  • ピン3: SCL: (I2Cクロック)
  • ピン4: 3V3 (BMX055用電源端子)

一方,こちらのページを参照すると,ESP8266のI2C端子は以下のようになっています.

  • SDA: 10(IO4)
  • SCL: 14(IO5)
これが,秋月のESP-WROOM-02ボードでは以下のピンに出ています.
  • ピン12: IO4 (SDA)
  • ピン16: IO5 (SCL)
ESP8266用のWireライブラリでも,これを使うことになっているようです.そこで,以下のように配線しました.

接続にあたって,Arduinoの場合と同様,JP4とJP5をpull upのために半田付けしました.電源は3.3Vを取っているので,とりあえず他の場所は触っていません.


プログラム

まずは,9軸センサーから読んだデータをシリアルポートに書き出して,Arduino IDEのシリアルモニタで表示することにしました.

秋月電子のBMX055のページにあるサンプルプログラムそのままでは動きませんでした.
27行目から29行目の
int xMag = 0;
int yMag = 0;
int zMag = 0;

short int xMag = 0;
short int yMag = 0;
short int zMag = 0;
に,
154行目と180行目の
int data[6];

unsigned char data[6];
に,
207行目の
int data[8];

unsigned short int data[8];
に書き換えたところ,それらしいデータが出てくるようになりました.

プログラムを動かすには,プログラムを書き込んだ後,リセットボタンを押す必要があります.

参照ページ:
ESP-WROOM-02ボードのI2C関係のピン配置 http://radiopench.blog96.fc2.com/blog-entry-661.html

ESP-WROOM-02からmacOSにUDP通信

マイコンボードESP-WROOM-02からmacOSにUDP接続して通信テストを行った.

macはiPhoneにテザリングして,iPhoneをWiFiアクセスポイントとする.この時点で,iPhoneのWiFiアクセスポイントの設定は以下のとおり.

  • SSID: iPhone7PlusBlack
  • PASSWORD: qatacygd60y8u

macの設定は以下のとおり.

  • IP address: 172.20.10.4
  • router: 172.20.10.1

これに,ESP-WROOM-02から以下のようにUDPで接続する.

  • mac側UDPポート: 30000
  • ESP-WROOM-02 UDPポート: 31000
  • ESP-WROOM-02 IP address: 172.20.10.10

送信は,とりあえず1秒ごとに"abcd"という文字列を送信するだけ.
ESP-WROOM-02のプログラムは以下のとおり.
#include <esp8266wifi .h>
#include <wificlient .h>
#include <wifiudp .h>

WiFiUDP UDP;

IPAddress HOSTIP(172, 20, 10, 4);
const int remoteUdpPort = 30000;

IPAddress myIP(172, 20, 10, 10);
const int localUdpPort = 31000;

const char *ssid = "iPhone7PlusBlack";
const char *password = "qatacygd60y8u";

void connectWiFi() {
  Serial.println("WiFi setup start");
  WiFi.begin(ssid, password);
  WiFi.config(myIP, WiFi.gatewayIP(), WiFi.subnetMask());
  Serial.println("start_connect");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("CONNECTED!");
  Serial.println("WiFi setup done");
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("Serial setup done");

  WiFi.mode(WIFI_STA);
  UDP.begin(localUdpPort);
  connectWiFi();
}

void sendWiFi(char byteData[]) {
  if (UDP.beginPacket(HOSTIP, remoteUdpPort)) {
    UDP.write(byteData);
    UDP.endPacket();
    Serial.println(byteData);
  }
}

void loop() {
  char a[4];
  a[0] = 'a';
  a[1] = 'b';
  a[2] = 'c';
  a[3] = 'd';
  sendWiFi(a);
  delay(1000);
  end_loop();
}

void end_loop() {
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.disconnect();
    Serial.println("disconnect!");
    connectWiFi();
  }
}

mac側のPythonプログラムは以下のとおり.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Python 3.7.3 on macOS 10.15 Catalina Beta
# 2019/8/4
# receive JSON data by UDP

import argparse
import threading
import time
from socket import *

class ServerThread(threading.Thread):
    def __init__(self, ADDRESS="127.0.0.1", PORT=30000):
        threading.Thread.__init__(self)
        self.data = None
        self.kill_flag = False
        # line information
        self.ADDRESS = ADDRESS
        self.PORT = PORT
        self.BUFSIZE = 1024
        self.ADDR = (gethostbyname(self.ADDRESS), self.PORT)
        print(self.ADDR)
        # bind
        self.udpServSock = socket(AF_INET, SOCK_DGRAM)
        self.udpServSock.bind(self.ADDR) # HOST, PORTでbinding

    def run(self):
        while True:
            try:
                # データ受信
                data, self.addr = self.udpServSock.recvfrom(self.BUFSIZE)
                if len(data) > 0:
                    print("receive {} bytes".format(len(data)))
                    print("received data: {}".format(data))
                # OSCはデコードできなくてexceptionが起こる
                # JSONだとテキストなのでデコードできる
                self.data = data.decode()
                print("decoded data: {}".format(self.data))
            except:
                print("exception occrurred")
                #pass

def start_UDP_server(ip_address, port_number):
    print("starting on address {}, port {}".format(ip_address, port_number))
    th = ServerThread(ip_address, port_number)
    th.setDaemon(True)
    th.start()

    while True:
        time.sleep(1.0)
        if not th.data:
            continue
        print(th.data)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--ip", default="127.0.0.1",
                        help="IP address to listen on")
    parser.add_argument("--port", type=int, default=30000,
                        help="port to listen on")
    args = parser.parse_args()
    start_UDP_server(args.ip, args.port)

mac側で,ターミナル上でプログラムを以下のように立ち上げると,mac側では以下のように表示される.

% ./UDPserver1.py --ip 172.20.10.4 --port 30000
starting on address 172.20.10.4, port 30000
('172.20.10.4', 30000)
receive 4 bytes
received data: b'abcd'
decoded data: abcd
abcd
receive 4 bytes
received data: b'abcd'
decoded data: abcd
abcd
receive 4 bytes
received data: b'abcd'
decoded data: abcd
abcd
^C


一方,Arduino側のシリアルモニタでは以下のように表示される.

Serial setup done
WiFi setup start
start_connect
.......CONNECTED!
WiFi setup done
abcd
abcd
abcd


参照ページ:



秋月のESP-WROOM-02開発キットをmac OS上のArduino IDEから使えるようにする

Arduino開発環境からArduinoと同様にプログラミングができて,WiFiやBluetoothが使えるマイコンボードとして,秋月電子通商でESP-WROOM-02開発キットを購入しました.このボードは書き込み用のUSBジャックまで付いているので,パソコンとUSBケーブルで接続するだけで,すぐにプログラミング・書き込みが可能です.

ただ,素のArduino開発環境ではこのボードに対応していないので,追加インストールが必要です.

まず,Arduino IDEをインストールします.http://www.arduino.ccにアクセスして,上のメニューから「SOFTWARE」→「DOWNLOADS」をクリックします.

使用環境がmacOS 10.15 (Catalina) betaなので,ここではnightly buildsをダウンロード・インストールします.

zipファイルをダブルクリックするとアプリケーションが解凍されます.これを/Applicationsディレクトリにdrag&dropし,ダブルクリックして起動します.起動の際に,下記のようなワーニングが出て起動できません.
「システム環境設定」アプリの「セキュリティとプライバシー」にアクセスし,「ダウンロードしたアプリケーションの実行許可」で実行許可を出します(結局,自分の環境ではこれでも動かず,SIPをdisableにして実行しました).

環境設定から,ESP8266の開発ができるように追加のボードマネージャーをインストールします.まず,Arduino IDEを起動したら,メニューバーの「Arduino」をクリックし,「Preferences」をクリックして環境設定画面を開きます.「追加のボードマネージャのURL:」欄に以下のように記入します.

http://arduino.esp8266.com/stable/package_esp8266com_index.json

OKボタンを押して環境設定を終了します.

次に,メニューバーの「ツール」→「ボード」から「ボードマネージャ」をクリックし,選択します.

ボードマネージャーが開いたら,一番下までスクロールし,esp8266を確認し,「インストール」ボタンをクリックします.
ボードの定義は44Mbytesほどあるので,待ちます.ダウンロードが終了したら,メニューバーの「ツール」→「ボード」→「Generic ESP8266 Module」が選択可能になっているので,これを選択します.
「ツール」メニューで設定を以下のように書き換えます.
  • 書き換え場所:
  • Upload Speed: 115200 → 921600
  • CPU Frequency: 80MHz → 160MHz
  • Flash Size: 512K (no SPIFFS) → 4M (3M SPIFFS)
  • Flash Mode: DIO → QIO (fast)
  • Flash Frequency: 40MHz → 80MHz


書き換え前:
書き換え後:

シリアルポートを設定します./dev/cu.usbserial-????????に設定します.

これで設定が終了しました.

プログラムの書き込みの際には,以下のようにボタンを押して書き込みモードにする必要があります.
  1. デフォルト状態:RSTとPGMが両方押されていない
  2. リセット状態:RSTとPGMが両方押されている
  3. 書き込み準備状態:PGMが押されて,RSTは離されている.
  4. 書き込み可能状態:RSTとPGMが両方押されていない.

一言でいうと,PGMボタンを押した状態でRSTボタンを押して離し,そのあとでPGMボタンを離すと書き込み可能状態になります.

書き込みの一連の操作は以下のようになります.ここでは,Arduinoを立ち上げてプログラムを何も書いてない状態で書き込みのテストをしてみます.

まず,Arduinoで書き込み操作を行います.ウィンドウ下部に「Connecting......」というメッセージが表示されます.

ここで上記のボタンの操作を行うと,書き込みが始まります.

以下のように表示されれば書き込み終了です.

プログラムを書き込んだ後,そのままではプログラムの動作が始まらないようです.RSTボタンを一回押すと動作が始まります.

これで,Arduino IDEでESP-WROOM-02ボードの開発ができるようになりました.

2019年8月10日土曜日

Processing言語で3Dアニメーション ー 複数の描画パネルを使用する

Processing言語で,ウィンドウ上にこの記事の動画を,それぞれ複数の描画パネルに貼り付けてみました.通常の描画関数は実はPGraphics3Dクラスのメソッドになっているので,明示的にPGraphics3Dクラスのインスタンスを複数作成し,インスタンスを指定してメソッド呼び出しを行うと,複数の描画画面を使用することができます.

なお,PGraphics3Dクラスのインスタンスは実画面に繋がっていないgraphics contextのようで,最後にimage()関数で実ウィンドウにまとめて描画します.

ソースコードは以下のとおりです.重複するコードが多いので,Javaのサブラクスを作成すると,もっと簡潔になると思います.

// 2019/8/10
// 明示的にPGraphicsを用いて描画する
// 2つのPGraphicsを作って,別の描画をする

int loopCount; // draw()が呼ばれるたびにインクリメントされる変数
PGraphics3D pg1, pg2;
int panelWidth, panelHeight;

void setup() {
  size(950, 500, P3D); // 400x400のウィンドウ
  panelWidth = 400;
  panelHeight = 400;
  pg1 = (PGraphics3D)createGraphics(panelWidth, panelHeight, P3D);
  pg2 = (PGraphics3D)createGraphics(panelWidth, panelHeight, P3D);
  loopCount = 0;
  // 日本語フォントが効かない.サイズも反映されない.
  PFont font = createFont("MS Gothic", 48, true);
  pg1.textFont(font);
  pg1.textSize(24);
  pg1.hint(ENABLE_DEPTH_SORT); // z bufferが視点方向で正常に機能するように
  pg2.textFont(font);
  pg2.textSize(24);
  pg2.hint(ENABLE_DEPTH_SORT); // z bufferが視点方向で正常に機能するように
  frameRate(60);
}

void draw() {
  loopCount++;
  //
  pg1.beginDraw();
  pg1.background(192); // light grayの背景
  // ウィンドウの中心が(x, y)の原点になる
  pg1.translate(panelWidth/2, panelHeight/2, 0); // 原点を画面の中身にずらす
  // 座標軸を回転する(Processingは左手系)
  // x:右, y:下, z:手前 -> x:右, y:手前, z:上
  pg1.rotateX(PI/2);
  // この座標系で視点だけ回転させる
  // z軸が上の斜め上方向から原点を眺める
  pg1.camera(300.0*cos(radians(loopCount)),
          300.0*sin(radians(loopCount)),
          300.0,
          0.0, 0.0, 0.0,
          0.0, 0.0, -1.0);

  // 座標軸の描画
  pg1.fill(255, 0, 0); // 赤
  pg1.textAlign(CENTER); // x方向をセンタリング,y方向の座標はベースライン
  pg1.text("x軸", 150, 0, 0); // XY平面上に書く
  pg1.text("y軸", 0, 150, 0); // XY平面上に書く 
  pg1.text("z軸", 0, 0, 150); // Z=150のXY平面上に書く
  pg1.stroke(0, 0, 0); // 黒線
  pg1.line(0, 0, 0, 130, 0, 0); // X軸
  pg1.line(0, 0, 0, 0, 130, 0); // Y軸
  pg1.line(0, 0, 0, 0, 0, 145); // Z軸

  // 3Dオブジェクトの描画
  pg1.fill(0x7F0000FF); // 青の塗り潰し,透明度半分
  pg1.stroke(0, 255, 0); // 緑線
  pg1.box(100, 100, 100); // 原点中心,一辺のサイズ100のの立方体

  pg1.translate(-100, -100, 0); // 原点をずらす
  pg1.stroke(0, 0, 255); // 青線
  pg1.fill(0x7F00FFFF); // シアンの塗り潰し,透明度半分
  pg1.sphere(100); // 原点中心,半径100の球
  pg1.endDraw();
  //
  pg2.beginDraw();
  pg2.background(192); // light grayの背景
  // ウィンドウの中心が(x, y)の原点になる
  pg2.translate(panelWidth/2, panelHeight/2, 0); // 原点を画面の中身にずらす
  // 座標軸を回転する(Processingは左手系)
  // x:右, y:下, z:手前 -> x:右, y:手前, z:上
  pg2.rotateX(PI/2);
  // この座標系で視点だけ回転させる
  // z軸が上の斜め上方向から原点を眺める
  pg2.camera(300.0*cos(radians(-loopCount)),
          300.0*sin(radians(-loopCount)),
          300.0,
          0.0, 0.0, 0.0,
          0.0, 0.0, -1.0);

  // 座標軸の描画
  pg2.fill(255, 0, 0); // 赤
  pg2.textAlign(CENTER); // x方向をセンタリング,y方向の座標はベースライン
  pg2.text("x軸", 150, 0, 0); // XY平面上に書く
  pg2.text("y軸", 0, 150, 0); // XY平面上に書く 
  pg2.text("z軸", 0, 0, 150); // Z=150のXY平面上に書く
  pg2.stroke(0, 0, 0); // 黒線
  pg2.line(0, 0, 0, 130, 0, 0); // X軸
  pg2.line(0, 0, 0, 0, 130, 0); // Y軸
  pg2.line(0, 0, 0, 0, 0, 145); // Z軸

  // 3Dオブジェクトの描画
  pg2.fill(0x7FFF0000); // 赤の塗り潰し,透明度半分
  pg2.stroke(0, 255, 0); // 緑線
  pg2.box(100, 100, 100); // 原点中心,一辺のサイズ100のの立方体

  pg2.translate(-100, -100, 0); // 原点をずらす
  pg2.stroke(255, 0, 0); // 赤線
  pg2.fill(0x7FFF00FF); // マゼンタの塗り潰し,透明度半分
  pg2.sphere(100); // 原点中心,半径100の球
  pg2.endDraw();
  //
  image(pg1, 50, 50); // PGに描いた絵を表示
  image(pg2, 500, 50); // PGに描いた絵を表示
}


出来上がったGIFアニメーションは以下のとおりです.1つのウィンドウに2つの描画領域を作成して,それぞれの領域で別のアニメーションを描画しています.以下の不具合があります.

  • フォントの指定がうまくいかない
  • 半透明描画ができていない.


上記のGIFアニメーションを作成するために書き換えたソースコードは以下のとおりです.

// 2019/8/10
// 明示的にPGraphicsを用いて描画する
// 2つのPGraphicsを作って,別の描画をする

import gifAnimation.*;

GifMaker gifExport;
int loopCount; // draw()が呼ばれるたびにインクリメントされる変数
PGraphics3D pg1, pg2;
int panelWidth, panelHeight;

void setup() {
  size(950, 500, P3D); // 400x400のウィンドウ
  panelWidth = 400;
  panelHeight = 400;
  pg1 = (PGraphics3D)createGraphics(panelWidth, panelHeight, P3D);
  pg2 = (PGraphics3D)createGraphics(panelWidth, panelHeight, P3D);
  loopCount = 0;
  // 日本語フォントが効かない.サイズも反映されない.
  PFont font = createFont("MS Gothic", 48, true);
  pg1.textFont(font);
  pg1.textSize(24);
  pg1.hint(ENABLE_DEPTH_SORT); // z bufferが視点方向で正常に機能するように
  pg2.textFont(font);
  pg2.textSize(24);
  pg2.hint(ENABLE_DEPTH_SORT); // z bufferが視点方向で正常に機能するように
  frameRate(60);
  
  gifExport = new GifMaker(this, "export.gif");
  gifExport.setRepeat(0); // 無限ループ
  gifExport.setQuality(10); // default値
  gifExport.setDelay(30); // 30ms単位で1コマ(1秒33コマ)
  gifExport.setTransparent(0, 0, 0); // 黒は透過色
}

void draw() {
  loopCount++;
  //
  pg1.beginDraw();
  pg1.background(192); // light grayの背景
  // ウィンドウの中心が(x, y)の原点になる
  pg1.translate(panelWidth/2, panelHeight/2, 0); // 原点を画面の中身にずらす
  // 座標軸を回転する(Processingは左手系)
  // x:右, y:下, z:手前 -> x:右, y:手前, z:上
  pg1.rotateX(PI/2);
  // この座標系で視点だけ回転させる
  // z軸が上の斜め上方向から原点を眺める
  pg1.camera(300.0*cos(radians(loopCount)),
          300.0*sin(radians(loopCount)),
          300.0,
          0.0, 0.0, 0.0,
          0.0, 0.0, -1.0);

  // 座標軸の描画
  pg1.fill(255, 0, 0); // 赤
  pg1.textAlign(CENTER); // x方向をセンタリング,y方向の座標はベースライン
  pg1.text("x軸", 150, 0, 0); // XY平面上に書く
  pg1.text("y軸", 0, 150, 0); // XY平面上に書く 
  pg1.text("z軸", 0, 0, 150); // Z=150のXY平面上に書く
  pg1.stroke(0, 0, 0); // 黒線
  pg1.line(0, 0, 0, 130, 0, 0); // X軸
  pg1.line(0, 0, 0, 0, 130, 0); // Y軸
  pg1.line(0, 0, 0, 0, 0, 145); // Z軸

  // 3Dオブジェクトの描画
  pg1.fill(0x7F0000FF); // 青の塗り潰し,透明度半分
  pg1.stroke(0, 255, 0); // 緑線
  pg1.box(100, 100, 100); // 原点中心,一辺のサイズ100のの立方体

  pg1.translate(-100, -100, 0); // 原点をずらす
  pg1.stroke(0, 0, 255); // 青線
  pg1.fill(0x7F00FFFF); // シアンの塗り潰し,透明度半分
  pg1.sphere(100); // 原点中心,半径100の球
  pg1.endDraw();
  //
  pg2.beginDraw();
  pg2.background(192); // light grayの背景
  // ウィンドウの中心が(x, y)の原点になる
  pg2.translate(panelWidth/2, panelHeight/2, 0); // 原点を画面の中身にずらす
  // 座標軸を回転する(Processingは左手系)
  // x:右, y:下, z:手前 -> x:右, y:手前, z:上
  pg2.rotateX(PI/2);
  // この座標系で視点だけ回転させる
  // z軸が上の斜め上方向から原点を眺める
  pg2.camera(300.0*cos(radians(-loopCount)),
          300.0*sin(radians(-loopCount)),
          300.0,
          0.0, 0.0, 0.0,
          0.0, 0.0, -1.0);

  // 座標軸の描画
  pg2.fill(255, 0, 0); // 赤
  pg2.textAlign(CENTER); // x方向をセンタリング,y方向の座標はベースライン
  pg2.text("x軸", 150, 0, 0); // XY平面上に書く
  pg2.text("y軸", 0, 150, 0); // XY平面上に書く 
  pg2.text("z軸", 0, 0, 150); // Z=150のXY平面上に書く
  pg2.stroke(0, 0, 0); // 黒線
  pg2.line(0, 0, 0, 130, 0, 0); // X軸
  pg2.line(0, 0, 0, 0, 130, 0); // Y軸
  pg2.line(0, 0, 0, 0, 0, 145); // Z軸

  // 3Dオブジェクトの描画
  pg2.fill(0x7FFF0000); // 赤の塗り潰し,透明度半分
  pg2.stroke(0, 255, 0); // 緑線
  pg2.box(100, 100, 100); // 原点中心,一辺のサイズ100のの立方体

  pg2.translate(-100, -100, 0); // 原点をずらす
  pg2.stroke(255, 0, 0); // 赤線
  pg2.fill(0x7FFF00FF); // マゼンタの塗り潰し,透明度半分
  pg2.sphere(100); // 原点中心,半径100の球
  pg2.endDraw();
  //
  image(pg1, 50, 50); // PGに描いた絵を表示
  image(pg2, 500, 50); // PGに描いた絵を表示
  
  gifExport.addFrame(); // gifアニメーションに現在のフレームを追加
  if (loopCount >= 360) {
    gifExport.finish(); // 1回転したらアニメーション終了
  }
}


1回転だけした時点で,無限繰り返し再生を行うアニメーション画像を書き出しています.

Processing言語で3Dグラフィック ー 視点を回転させるアニメーション

Processing言語で3次元グラフィックが描けるので試してみた.

とりあえず,半透明の立方体と球体を置いて,カメラ視点を変化させてアニメーションにしてみる.XYZ軸も描いて,向きがわかるようにしてみた.

Processingの座標系は,左上が原点,右向きがX軸の正方向,下向きがY軸の正方向,手前向きがZ軸の正方向という左手系の座標系になっている.行列を使って座標軸を変換する方法が良くわからなかったので,とりあえず,右手系のまま,画面中心を原点にして,画面の上方向がZ軸になるように回転すると,X軸が右方向,Y軸が手前方向になった.回転だけでは,X軸が右方向,Y軸が画面の奥方向,Z軸が上方向という右手系にできないので,とりあえずこの座標系で話を進めることにする.

アニメーションの際は,座標系,物体の座標は変化させずに,カメラの座標と向きだけを変化させている,カメラの位置はZ軸方向300の高さで,半径300の円周上を動かし,向きは常に原点方向.

こうやって書いたソースが以下のとおり.

// 2019/8/9

int loopCount; // draw()が呼ばれるたびにインクリメントされる変数

void setup() {
  size(400, 400, P3D); // 400x400のウィンドウに3D描画
  loopCount = 0;
  // 日本語フォント
  PFont font = createFont("MS Gothic", 48, true);
  textFont(font);
  textSize(24);
  hint(ENABLE_DEPTH_SORT); // z bufferが視点方向で正常に機能するように
}

void draw() {
  loopCount++;
  background(192); // light grayの背景
  // ウィンドウの中心が(x, y)の原点になる
  translate(width/2, height/2, 0); // 原点を画面の中身にずらす
  // 座標軸を回転する(Processingは左手系)
  // x:右, y:下, z:手前 -> x:右, y:手前, z:上
  rotateX(PI/2);
  // この座標系で視点だけ回転させる
  // z軸が上の斜め上方向から原点を眺める
  camera(300.0*cos(radians(loopCount)),
          300.0*sin(radians(loopCount)),
          300.0,
          0.0, 0.0, 0.0,
          0.0, 0.0, -1.0);

  // 座標軸の描画
  fill(255, 0, 0); // 赤
  textAlign(CENTER); // x方向をセンタリング,y方向の座標はベースライン
  text("x軸", 150, 0, 0); // XY平面上に書く
  text("y軸", 0, 150, 0); // XY平面上に書く 
  text("z軸", 0, 0, 150); // Z=150のXY平面上に書く
  stroke(0, 0, 0); // 黒線
  line(0, 0, 0, 130, 0, 0); // X軸
  line(0, 0, 0, 0, 130, 0); // Y軸
  line(0, 0, 0, 0, 0, 145); // Z軸

  // 3Dオブジェクトの描画
  fill(0x7F0000FF); // 青の塗り潰し,透明度半分
  stroke(0, 255, 0); // 緑線
  box(100, 100, 100); // 原点中心,一辺のサイズ100のの立方体

  translate(-100, -100, 0); // 原点をずらす
  stroke(0, 0, 255); // 青線
  fill(0x7F00FFFF); // シアンの塗り潰し,透明度半分
  sphere(100); // 原点中心,半径100の球
}


これをGIFアニメーションで表示すると以下のとおりになった.

Processingのアニメーションからgifアニメーション画像を保存する方向はこちらを参照した.ここで使うgifAnimationライブラリは,Processing Ver.3では動かず,Processing Ver.2で動かす必要がある(Ver.3対応版はこちらにあった).追加ライブラリgifAnimationライブラリの置き場のディレクトリは,自分のスケッチブックの保存ディレクトリの中のlibrariesディレクトリである.ここに,拾ってきたアーカイブファイルを解凍して出てきたgifAnimationディレクトリ以下をコピーすれば良い.書き換えた後のソースコードは以下のとおり.

// 2019/8/10

import gifAnimation.*;

GifMaker gifExport;
int loopCount; // draw()が呼ばれるたびにインクリメントされる変数

void setup() {
  size(400, 400, P3D); // 400x400のウィンドウに3D描画
  loopCount = 0;
  // 日本語フォント
  PFont font = createFont("MS Gothic", 48, true);
  textFont(font);
  textSize(24);
  hint(ENABLE_DEPTH_SORT); // z bufferが視点方向で正常に機能するように
  gifExport = new GifMaker(this, "export.gif");
  gifExport.setRepeat(0); // 無限ループ
  gifExport.setQuality(10); // default値
  gifExport.setDelay(30); // 30ms単位で1コマ(1秒33コマ)
  gifExport.setTransparent(0, 0, 0); // 黒は透過色
}


void draw() {
  loopCount++;
  background(192); // light grayの背景
  // ウィンドウの中心が(x, y)の原点になる
  translate(width/2, height/2, 0); // 原点を画面の中身にずらす
  // 座標軸を回転する(Processingは左手系)
  // x:右, y:下, z:手前 -> x:右, y:手前, z:上
  rotateX(PI/2);
  // この座標系で視点だけ回転させる
  // z軸が上の斜め上方向から原点を眺める
  camera(300.0*cos(radians(loopCount)),
          300.0*sin(radians(loopCount)),
          300.0,
          0.0, 0.0, 0.0,
          0.0, 0.0, -1.0);

  // 座標軸の描画
  fill(255, 0, 0); // 赤
  textAlign(CENTER); // x方向をセンタリング,y方向の座標はベースライン
  text("x軸", 150, 0, 0); // XY平面上に書く
  text("y軸", 0, 150, 0); // XY平面上に書く 
  text("z軸", 0, 0, 150); // Z=150のXY平面上に書く
  stroke(0, 0, 0); // 黒線
  line(0, 0, 0, 130, 0, 0); // X軸
  line(0, 0, 0, 0, 130, 0); // Y軸
  line(0, 0, 0, 0, 0, 145); // Z軸

  // 3Dオブジェクトの描画
  fill(0x7F0000FF); // 青の塗り潰し,透明度半分
  stroke(0, 255, 0); // 緑線
  box(100, 100, 100); // 原点中心,一辺のサイズ100のの立方体

  translate(-100, -100, 0); // 原点をずらす
  stroke(0, 0, 255); // 青線
  fill(0x7F00FFFF); // シアンの塗り潰し,透明度半分
  sphere(100); // 原点中心,半径100の球
  
  gifExport.addFrame(); // gifアニメーションに現在のフレームを追加
  if (loopCount >= 360) {
    gifExport.finish(); // 1回転したらアニメーション終了
  }
}


2019年8月7日水曜日

9軸センサー(加速度,ジャイロ,地磁気)のデータを使う (2)

前の投稿と並行して,クライアントの構築が一番簡単な,iPhoneとZIG SIMアプリを使った9軸データのやり取りをやってみました.

iPhoneのZIG SIMアプリで9軸データを送り,macのPythonプログラムで受信してみます.mac側のプログラムは以下のとおり.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Python 3.7.3 on macOS 10.15 Catalina Beta
# 2019/8/7
# receive OSC data by UDP

import argparse
from socket import *
from pythonosc import dispatcher
from pythonosc import osc_server

def handle_zigsim_osc_message(addr, *args):
    try:
        print("{0}: {1}".format(addr, args))
    except ValueError:
        print("ValueError occrurred")

def start_OSC_server(ip_addr, port_num):
    print("starting on address {}, port {}".format(ip_addr, port_num))

    # OSC dispatcher
    dispatch = dispatcher.Dispatcher()
    dispatch.map("/ZIGSIM/*", handle_zigsim_osc_message )

    server = osc_server.ThreadingOSCUDPServer(
        (ip_addr, port_num), dispatch)
    print("Serving on {}".format(server.server_address))
    server.serve_forever()

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--ip", default="127.0.0.1",
                        help="IP address to listen on")
    parser.add_argument("--port", type=int, default=30000,
                        help="port to listen on")
    args = parser.parse_args()
    start_OSC_server(args.ip, args.port)

クライアントのZIG SIMアプリの設定(送信データ)は以下のとおり.


ZIG SIMアプリの設定(プロトコル)は以下のとおり.IP ADDRESSにはパソコンのIPアドレスを入れます.ポート番号は30000にしました.


macでサーバープログラムを動かし,iPhoneでクライアントを動かすと,iPhoneから9軸データがサーバーに送られます.このときのクライアントの画面は以下のとおり.


サーバー側では,以下のようにデータが表示されて,ちゃんとデータがOSC形式で受信できていることが分かります.


現在のiPhone上でうごかしてZIG SIMを,「9軸センサー(加速度,ジャイロ,地磁気)のデータを使う (1)」で作成中のArduinoを使ったハードウェアで置き換えるのが目標です.

9軸センサー(加速度,ジャイロ,地磁気)のデータを使う (1)

秋月電子で9軸センサーBMX055モジュールを購入してみました.これは,加速度3軸,ジャイロ3軸,地磁気3軸のセンサーで,Arduinoから簡単にデータが取れるようです.チップ自体は3.3V電源ですが,モジュール上にFXMA2102が載っていて,I2Cの電圧変換をしてくれるので,Arduinoの5V入出力でも使えるようです.

まず,マニュアルに従ってJP4, 5, 6を半田でショートした後,ジャンパーピンを接続し,配線しました.Arduino側がこんな感じ.

BMX055側はこんな感じ.

全体はこんな感じ.

とりあえず,サンプルソースコードを取得して,このソースコードに書いてある通りにArduinoと結線しました.

Arduinoのシリアルコンソールを開くと,9軸の値がちゃんと取れているようです.

あとやることは,以下のとおりです.

  • ジャイロのデータを使って,加速度から,重力加速度とモジュール自体の加速を分離する.
  • OSCデータで送るようにする.
  • WiFiでパソコンに飛ばす.
最終的には,ZIG SIMの置き換えを狙ってます.