Arduino JSON to Touchdesigner Part 2

Vichagorn Lupponglung
5 min readMay 30, 2021

บทความจะแบ่งเป็น 2 ตอน
1.Arduino JSON to Touchdesigner Part 1
2.Arduino JSON to Touchdesigner Part 2

ในบทความนี้เราจะมาส่งข้อมูล JSON จาก Touchdesigner ไปที่ Arduino กันครับกลับทางกันกับบทความตอนที่แล้ว Arduino JSON to Touchdesigner Part 1 โดยโจทย์ในการส่งข้อมูลนี้คือเราจะส่งข้อมูล Solenoid 4 ตัววิ่งไล่จากซ้ายไปขวา เป็น JSON กลับไปให้ Arduino โดยแสดงข้อมูลเป็นแถบ ขาว-ดำ 4 แถบบนหน้าจอโปรแกรมกันครับด้วย Touchdesigner จากบทความตอนที่แล้วกันครับ

โดยจะแบ่งขั้นตอนในส่งข้อมูล JSON จาก Touchdsigner ไปที่ Arduino ดังนี้
1.Touchdsigner Coding
2.Arduino Coding

Touchdsigner Coding

ในการส่งข้อมูล Solenoid 4 ตัววิ่งไล่จากซ้ายไปขวาในที่นี้ผมจะใช้วิธี Graphic Mapping ครับ แล้วแปลง pixel ของ graphic ไปเป็นข้อมูลตัวเลข 0 และ 1 แล้วส่งไปเป็น JSON ผ่าน Serial ครับ

อันดับแรกภาพ graphic 2d เราจะใช้ ramp TOP แล้ว code ในไปใน phase ว่า “absTime.seconds” จะเป็นการกำหนดให้ phase ของ ramp เปลี่ยนไปตามเวลา

จากนั้นเติมกล่อง tranform TOP เอาไว้เผื่อหมุนหรือปรับแต่ง graphic และกล่อง fit TOP เพื่อปรับ size pixel โดยปรับในช่อง fit เป็น fill แล้วตั้ง resolution ตามข้อมูล solenoid ที่จะส่งเลยในที่นี้มี 4 ตัวเลยตั้งเป็น 4 ครับ จากนั้นปรับ Smoothness ทั้งหมดเป็น Nearest Pixel ด้วยเพื่อให้ Pixel ไม่ fade

เมื่อเราได้ภาพ graphic 4 pixel ที่วิ่งจากซ้ายไปขวาแล้ว เราจะมาเปลี่ยนข้อมูลภาพเป็นตัวเลขกันต่อครับ ใช้ topto TOP ในกล่อง topto จะกำหนดให้มีข้อมูลของสี r (chanel) เท่านั้นก็เพียงพอเอามาใช้งานแล้วครับ

แต่มันยังอยู่ในรูปแกน X เป็นลำดับ-ข้อมูล sample และแกน Y เป็นค่าของข้อมูล sample อยู่เราจะสลับให้ sample เปลี่ยนเป็น chanel ครับโดยใช้ shuffle TOP ในกล่อง shuffle ให้ตั้งค่า method เป็น Swap Chanels and Samples

แต่ชื่อ Chanels ที่ได้จากการสลับมาเป็นชื่อ r0 ถึง r4 เราถ้าให้เป็นชื่อ s1 ถึง s4 จะต้องใช้กล่อง rename TOP ต่อครับแล้วพิมดังภาพด้านล่างครับ ก็จะได้ชื่อ chanel ตามที่ต้องการแล้ว

ตอนนี้ถ้าสังเกตดูค่าตัวเลขที่เราได้มันเป็น float ทศนิยมอยู่เราจะ round มันด้วยกล่อง math TOP เท่านี้ก็ได้ค่าข้อมูลวิ่งตาม graphic เรียบร้อยแล้วครับ ขาวเป็น 1 และดำเป็น 0 แต่ในที่นี้ผมไม่อยากให้ส่งข้อมูล 1 ซ้อนกัน 2 chanel ได้ผมเลยเติมกล่อง logic ลงไปแล้วตั้ง Chanel Pre OP เป็น Radio Button ครับ (อีกวิธีคือไปปรับค่าขาว-ดำ ใน ramp ก็ได้ครับ) และต่อท้ายด้วยกล่อง null TOP

สุดท้ายเราจะมาส่งข้อมูล JSON ไปทาง serial กันต่อคลิกขวาที่กล่อง null แล้วเลือก CHOP Execute ครับ ภายในกล่องเราเขียน python ลงไปกันครับ เริ่มต้นจะมี function ที่เตรียมไว้ให้เราหลายอันเลย ที่จะทำ function นั้นๆเมื่อเกิดเหตุการณ์ใด ในที่นี้ผมเลือกใช้เมื่อมีค่าเปลี่ยนแปลง onValueChange() ครับ

ใน code จะต้อง import json เข้ามาด้วยครับเพื่อที่จะใช้ function json.dumps() มันเอาไว้แปลง JSON Object เป็น JSON String ครับ และการส่งนั้นเราจะใช้กล่อง serial ส่งข้อมูล JSON String ออกไป

import jsondef onValueChange(channel, sampleIndex, val, prev):
obj = {
'setSolenoid':{
's1':str(int(op('null1')['s1'])),
's2':str(int(op('null1')['s2'])),
's3':str(int(op('null1')['s3'])),
's4':str(int(op('null1')['s4'])),
}
}
str_ = json.dumps(obj)
print(str_)
op('serial2').send(str_,terminator="\n")
return

ในที่นี้เราสามารถ check JSON String ที่จะส่งออกไปได้โดยกด Alt+t เพื่อเปิด Textport ออกมาค่าที่แสดงออกมาเป็นผลจากการ print(str_) ครับ

Textport Command line
Overview (Shortkey “H”)

Arduino Coding

หลังจาก Touchdesigner ส่งค่าข้อมูลเป็น JSON String ออกมาเรียบร้อยแล้วเราจะมาเขียน code ให้ Arduino รับ JSON String กันครับ

อันดับแรกเพื่อให้ code เราสามารถ check ค่าว่า ค่าที่ส่งมาอย่างต่อเนื่องนั้นมันมีค่าเปลี่ยนแปลง(Update) ไปหรือไม่ เราต้องประกาศตัวแปลเก็บค่าครั้งที่แล้วไว้ด้วยครับ

StaticJsonDocument<1024> send;
StaticJsonDocument<1024> send_last;
String str_last;

ในการอ่านค่า String มาทาง serial นั้นจะมีค่า timeout โดย default จะอยู่ที่ 1000 ms มันช้าไปเราจะปรับให้เป็น 30 ms

Serial.setTimeout(30);

ต่อไปเราจะเขียนให้ code รับค่า JSON เมื่อมี JSON ส่งเข้ามาทาง serial และอ่านค่า String ขนาด 1KB เก็บไว้ใน ตัวแปร str

if (Serial.available()){
String str = Serial.readString(1024);
}

ใช้ deserializeJson() แปลงค่าจาก JSON String ไปเป็น JsonDocument แล้วตรวจ error จากการแปลงค่า

DeserializationError error = deserializeJson(read, str);
if (error){
Serial.println("ERROR : " + String(error.c_str()));
} else {
// do something
}

ประกาศ function ที่ใช้ในการ merge ค่า JSON เข้าด้วยกัน

bool merge(JsonObject dest, JsonObjectConst src)
{
for (auto kvp : src)
{
if (kvp.value().is<JsonObject>())
{
if (!dest.containsKey(kvp.key()))
{
if (!dest.createNestedObject(kvp.key()))
return false;
}
if (!merge(dest[kvp.key()], kvp.value()))
return false;
}
else
{
if (!dest[kvp.key()].set(kvp.value()))
return false;
}
}
return true;
}
  • โดยในการ merge JSON เข้าด้วยกันต้องส่งข้อมูล JsonObject เข้าไปใน function ด้วยการใช้ .as<JsonObject>()
  • เมื่อ merge กันเรียบร้อยแล้วเราจะต้องใช้ garbageCollect() ของข้อมูลหลักด้วยเมื่อนั้นจะทำให้ข้อมูลขาดหายไปได้ เนื่องจากเกิด memory leak

จากนั้นผมได้ใช้ตัวแปร String ในการช่วย check ว่ามีข้อมูล update ไม่ซ้ำเดิมทั้งหมด และเมื่อข้อมูลมีการ update จริงก็จะไป check การ update ย่อยอีกที่ด้วย JSON

if (str_.compareTo(str_last) != 0){
if (send["solenoid"] != send_last["solenoid"]){
// update
}
}

ดังนั้น code ในการรับค่าข้อมูล JSON จะเป็นดังนี้

if (Serial.available()){
String str = Serial.readString(1024);
StaticJsonDocument<1024> read;
DeserializationError error = deserializeJson(read, str);
if (error)
{
Serial.println("ERROR : " + String(error.c_str()));
}
else
{
merge(send.as<JsonObject>(), read.as<JsonObject>());
String str_ = "";
serializeJson(send, str_);
send.garbageCollect();
if (str_.compareTo(str_last) != 0)
{
if (send["solenoid"] != send_last["solenoid"])
{
// Solenoid UPDATE
SOL_1 = byte(send["solenoid"]["s1"]);
SOL_2 = byte(send["solenoid"]["s2"]);
SOL_3 = byte(send["solenoid"]["s3"]);
SOL_4 = byte(send["solenoid"]["s4"]);
}
}
}
str_last = str;
send_last = send;
}

และ code ทั้งหมดจะเป็นดังนี้

#include <Arduino.h>
#include <ArduinoJson.h>
StaticJsonDocument<1024> send;
StaticJsonDocument<1024> send_last;
String str_last;
float temp1 = 0.0;
float temp2 = 0.0;
bool SOL_1;
bool SOL_2;
bool SOL_3;
bool SOL_4;
unsigned long time_;bool merge(JsonObject dest, JsonObjectConst src)
{
for (auto kvp : src)
{
if (kvp.value().is<JsonObject>())
{
if (!dest.containsKey(kvp.key()))
{
if (!dest.createNestedObject(kvp.key()))
return false;
}
if (!merge(dest[kvp.key()], kvp.value()))
return false;
}
else
{
if (!dest[kvp.key()].set(kvp.value()))
return false;
}
}
return true;
}
void setup()
{
Serial.begin(115200);
Serial.setTimeout(30);
time_ = millis();
}
void loop()
{
if (Serial.available())
{
String str = Serial.readString(1024);
StaticJsonDocument<1024> read;
DeserializationError error = deserializeJson(read, str);
if (error)
{
Serial.println("ERROR : " + String(error.c_str()));
}
else
{
merge(send.as<JsonObject>(), read.as<JsonObject>());
String str_ = "";
serializeJson(send, str_);
send.garbageCollect();
if (str_.compareTo(str_last) != 0)
{
if (send["solenoid"] != send_last["solenoid"])
{
// Solenoid UPDATE
SOL_1 = byte(send["solenoid"]["s1"]);
SOL_2 = byte(send["solenoid"]["s2"]);
SOL_3 = byte(send["solenoid"]["s3"]);
SOL_4 = byte(send["solenoid"]["s4"]);
}
}
}
str_last = str;
send_last = send;
}
if (millis() - time_ > 100)
{
send["temp1"] = temp1;
send["temp2"] = temp2;
send["solenoid"]["s1"] = byte(SOL_1);
send["solenoid"]["s2"] = byte(SOL_2);
send["solenoid"]["s3"] = byte(SOL_3);
send["solenoid"]["s4"] = byte(SOL_4);
serializeJson(send, Serial);
// Serial.println();
temp1 += 0.2;
temp2 += 0.5;
time_ = millis();
}
}

เมื่อเรา upload code ไปที่ controller แล้วเปิด Touchdesigner ขึ้นมาจะได้การทำงานลักษณะนี้

เรียบร้อยละครับในบทความนี้ ทั้งหมดนี้ก็คือการส่งข้อมูล JSON ไปมาระหว่าง Arduino และ Touchdesigner ครับ โดยในบทความแรก Arduino JSON to Touchdesigner จะเป็นการส่ง JSON จาก Arduino ไป Touchdesigner และบทความที่สองจะเป็นส่งข้อมูลจาก Touchdesigner ไป Arduino ครับ

หวังเนื้อหาในบทความนี้จะช่วยให้ทุกคนได้รับประโยชน์กันนะครับรวมถึงผมด้วยเตือนความจำ 555

--

--