不知道各位有沒有觀察過Tracker這類型的產品
這個也可以拿來做導遊帶團的人員追蹤
這次要做的是Lora + GPS + SOS 按鍵追蹤器
起因是有社群的朋友問到,indoor location我們都做了,那outdoor呢?
其實不太想寫這篇的,因為市面上太多同類型產品可挑選
但為了讓社群朋友知道這個專案能做的真的非常…非常多
連室外定位也可以設定為長照的一個分支~
這一篇是由MakerPro社群賴桑的兩篇文章修改而來
做法非常簡單,Linkit7697 + MPU6050 + Button + GPS
module + Lora就搞定了
光華商場一顆MPU6050 + Button不用130元
因為這種GPS module只會有4支腳,VCC GND TX RX,接到MT7697就可以了
而Lora是之前參加MakerPro的自造松留下來的
可以在沒屋頂的拍賣場找到柯大創客屋
所以做手環加下來大約2000元
如果再加上前一篇做的智障型燈座,這樣要2個Linkit7697 ,Total大約為2500~2700
咦! 比lota的early bird USD99還便宜耶!
這樣知道為什麼歐美市場比較好賺了吧~ 2500NTD放在台灣誰要買?
台灣人力最便宜了,寵物跑了就叫消防隊找囉~
但其實這種想法跟最近幾年大家開始重視汽車的安全性
主被動安全都要,而不是省油省到爆,讓整個城市都是我的潰縮區是一樣的
經過上一篇的洗禮,決定先用手邊的Arduino Yun做,不然又被Linkit 7697坑到 @@
Arduino Yun的做法相當簡單,已經很習慣在開始之前先上Github看看有沒有勇士分享文章
結果真的有耶!!! 出來吧! 勇士 !
文章是在說明如何使用Aduino Yun的SoftwareSerial取出GPS數據
簡易的code如下
#include "SoftwareSerial.h"
SoftwareSerial mySerial(10, 11); // RX, TX
void setup()
{
//
Open serial communications and wait for port to open:
Serial.begin(9600);
Serial.println("uBlox Neo 6M");
//
set the data rate for the SoftwareSerial port
mySerial.begin(9600);
}
void loop()
{
if
(mySerial.available())
Serial.write(mySerial.read());
}
輸出如下圖
輸出如下圖
~~~~~~~~~~~~~科普時間~~~~~~~~~~~~
GPS有許多有用的資料格式
GPGGA UTC時間、緯度值、經度值、定位狀態、觀測的GPS衛星個數、差分基準站編號…
GPRMC UTC時間、定位狀態、緯度值、經度值、對地速度、日期…
我們只要GPRMC或是GPGGA內的2501.50164與12131.38674就行了
但這代表什麼意思呢?
用網頁來輸入經緯度2501.50164, 12131.38674看看
出現上圖這個耶~ 代表圖資是不吃這種格式的
所以需要轉換格式
1、首先直接除以100,2501.50164/100=25.0150164
2、把小數點的0.0150164轉為150164
3、然後(150164/60)*10000=250273333.3
4、第二步移了一個0,所以轉小數的時候,多加一個0.02502733333
5、緯度=25+0.02502733333=25.
02502733333
1、首先直接除以100,12131.38674/100=121.313867
2、把小數點轉為313867
3、然後(313867/60)*10000=52311166.7
4、經度=121+0.52311166=121.523111667
所以轉換後的是25.02502733333, 121.523111667
來試試有沒有這個地方
地圖顯示我在台北市中正區南昌路二段146-1號,其實我是在MakerPro辦公室的樓下
為什麼要在樓下而不在辦公室內呢? 因為衛星是在地球上方,而GPS模組是在地面上收訊息
所以在家裡吹冷氣寫code,會發現GPS收不到訊號,圖示如下
在Arduino Yun上GPS的code會是這樣 (主要是黃底紅字的部份,不要被大量的code嚇到)
boolean gpsStatus[] = {false, false, false,
false, false, false, false};
unsigned long start;
boolean grabNext = false;
int pointerGPS = 0;
String dataGPS[6] ;
uint8_t hour,minute,second;
float buf1, buf2, buf3;
#include <SoftwareSerial.h>
SoftwareSerial gpsSerial(10, 11); // RX, TX
void setup()
{
gpsSerial.begin(9600);
//
START OUR SERIAL DEBUG PORT
Serial.begin(115200);
//
//Settings Array contains the following settings: [0]NavMode,
[1]DataRate1, [2]DataRate2, [3]PortRateByte1, [4]PortRateByte2,
[5]PortRateByte3,
//[6]NMEA GLL Sentence, [7]NMEA GSA Sentence, [8]NMEA GSV Sentence,
[9]NMEA RMC Sentence, [10]NMEA VTG Sentence
//NavMode:
//Pedestrian Mode = 0x03
//Automotive Mode = 0x04
//Sea Mode = 0x05
//Airborne < 1G Mode = 0x06
//
//DataRate:
//1Hz = 0xE8 0x03
//2Hz = 0xF4 0x01
//3.33Hz = 0x2C 0x01
//4Hz = 0xFA 0x00
//
//PortRate:
//4800 = C0 12 00
//9600 = 80 25 00
//19200 = 00 4B 00 **SOFTWARESERIAL LIMIT FOR ARDUINO UNO R3!**
//38400 = 00 96 00 **SOFTWARESERIAL LIMIT FOR ARDUINO MEGA
2560!**
//57600 = 00 E1 00
//115200 = 00 C2 01
//230400 = 00 84 03
//
//NMEA Messages:
//OFF = 0x00
//ON = 0x01
//
byte settingsArray[] = {0x03, 0xFA, 0x00, 0x00, 0xE1, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00}; //
configureUblox(settingsArray);
}
double convertDegMinToDecDeg (float degMin)
{
double min = 0.0;
double decDeg = 0.0;
//get the minutes, fmod() requires double
min
= fmod((double)degMin, 100.0);
//rebuild coordinates in decimal degrees
degMin
= (int) ( degMin / 100 );
decDeg = degMin + ( min / 60 );
return decDeg;
}
void loop()
{
char recvChar;
String sentence;
while(1) {
if(gpsSerial.available())
{
// THIS IS THE MAIN LOOP JUST READS IN FROM THE GPS SERIAL AND ECHOS OUT
TO THE ARDUINO SERIAL.
recvChar = gpsSerial.read();
if(String(recvChar) == ",") {
if(grabNext){
dataGPS[pointerGPS] = sentence;
//Serial.println(dataGPS[pointerGPS]);
pointerGPS = pointerGPS -1;
if(pointerGPS <1){
grabNext = false;
// print out data
/*
Serial.println(dataGPS[5]); //
Time
Serial.print(dataGPS[4]);
Serial.println(dataGPS[3]); //
North
Serial.print(dataGPS[2]);
Serial.println(dataGPS[1]); //
West*/
dataGPS[5].toInt();
hour = dataGPS[5].toInt()/10000+8;
minute = (dataGPS[5].toInt()/100) %100;
second = dataGPS[5].toInt() %100;
Serial.print("Time:");
// Time
Serial.print(hour);
Serial.print(":");
Serial.print(minute);
Serial.print(":");
Serial.println(second);
buf1 = dataGPS[4].toFloat();
buf2 = dataGPS[2].toFloat();
Serial.print("Lat, Lon:");
//Lat, Lon
Serial.print(convertDegMinToDecDeg(buf1),6);
Serial.print(",");
Serial.println(convertDegMinToDecDeg(buf2),6);
}
}
if(sentence == "M$GPGGA"){
grabNext = true;
pointerGPS = 5;
}
sentence ="";
}
else{
sentence = sentence + String(recvChar);
}
}
}
}
void configureUblox(byte
*settingsArrayPointer) {
byte gpsSetSuccess = 0;
Serial.println("Configuring u-Blox GPS initial state...");
//Generate the configuration string for Navigation Mode
byte setNav[] = {0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF,
*settingsArrayPointer, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00,
0x05, 0x00, 0xFA, 0x00, 0xFA, 0x00, 0x64, 0x00, 0x2C, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
calcChecksum(&setNav[2], sizeof(setNav) - 4);
//Generate the configuration string for Data Rate
byte setDataRate[] = {0xB5, 0x62, 0x06, 0x08, 0x06, 0x00,
settingsArrayPointer[1], settingsArrayPointer[2], 0x01, 0x00, 0x01, 0x00, 0x00,
0x00};
calcChecksum(&setDataRate[2], sizeof(setDataRate) - 4);
//Generate the configuration string for Baud Rate
byte setPortRate[] = {0xB5, 0x62, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00,
0x00, 0x00, 0xD0, 0x08, 0x00, 0x00, settingsArrayPointer[3], settingsArrayPointer[4],
settingsArrayPointer[5], 0x00, 0x07, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00};
calcChecksum(&setPortRate[2], sizeof(setPortRate) - 4);
byte setGLL[] = {0xB5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x2B};
byte setGSA[] = {0xB5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x32};
byte setGSV[] = {0xB5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x39};
byte setRMC[] = {0xB5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x04, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x40};
byte setVTG[] = {0xB5, 0x62, 0x06, 0x01, 0x08, 0x00, 0xF0, 0x05, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x46};
delay(2500);
while(gpsSetSuccess < 3)
{
Serial.print("Setting Navigation Mode... ");
sendUBX(&setNav[0], sizeof(setNav));
//Send UBX Packet
gpsSetSuccess += getUBX_ACK(&setNav[2]); //Passes Class ID and
Message ID to the ACK Receive function
if (gpsSetSuccess == 5) {
gpsSetSuccess -= 4;
setBaud(settingsArrayPointer[4]);
delay(1500);
byte lowerPortRate[] = {0xB5, 0x62, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00,
0x00, 0x00, 0xD0, 0x08, 0x00, 0x00, 0x80, 0x25, 0x00, 0x00, 0x07, 0x00, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0xA2, 0xB5};
sendUBX(lowerPortRate, sizeof(lowerPortRate));
gpsSerial.begin(9600);
delay(2000);
}
if(gpsSetSuccess == 6) gpsSetSuccess -= 4;
if (gpsSetSuccess == 10) gpsStatus[0] = true;
}
if
(gpsSetSuccess == 3) Serial.println("Navigation mode configuration
failed.");
gpsSetSuccess = 0;
while(gpsSetSuccess < 3) {
Serial.print("Setting Data Update Rate... ");
sendUBX(&setDataRate[0], sizeof(setDataRate)); //Send UBX Packet
gpsSetSuccess +=
getUBX_ACK(&setDataRate[2]); //Passes Class ID and Message ID to the ACK
Receive function
if (gpsSetSuccess == 10) gpsStatus[1] = true;
if (gpsSetSuccess == 5 | gpsSetSuccess == 6) gpsSetSuccess -= 4;
}
if
(gpsSetSuccess == 3) Serial.println("Data update mode configuration
failed.");
gpsSetSuccess = 0;
while(gpsSetSuccess < 3 && settingsArrayPointer[6] == 0x00) {
Serial.print("Deactivating NMEA GLL Messages ");
sendUBX(setGLL, sizeof(setGLL));
gpsSetSuccess += getUBX_ACK(&setGLL[2]);
if (gpsSetSuccess == 10) gpsStatus[2] = true;
if (gpsSetSuccess == 5 | gpsSetSuccess == 6) gpsSetSuccess -= 4;
}
if
(gpsSetSuccess == 3) Serial.println("NMEA GLL Message Deactivation
Failed!");
gpsSetSuccess = 0;
while(gpsSetSuccess
< 3 && settingsArrayPointer[7] == 0x00) {
Serial.print("Deactivating NMEA GSA Messages ");
sendUBX(setGSA, sizeof(setGSA));
gpsSetSuccess += getUBX_ACK(&setGSA[2]);
if (gpsSetSuccess == 10) gpsStatus[3] = true;
if (gpsSetSuccess == 5 | gpsSetSuccess == 6) gpsSetSuccess -= 4;
}
if
(gpsSetSuccess == 3) Serial.println("NMEA GSA Message Deactivation
Failed!");
gpsSetSuccess = 0;
while(gpsSetSuccess < 3 && settingsArrayPointer[8] == 0x00) {
Serial.print("Deactivating NMEA GSV Messages ");
sendUBX(setGSV, sizeof(setGSV));
gpsSetSuccess += getUBX_ACK(&setGSV[2]);
if (gpsSetSuccess == 10) gpsStatus[4] = true;
if (gpsSetSuccess == 5 | gpsSetSuccess == 6) gpsSetSuccess -= 4;
}
if
(gpsSetSuccess == 3) Serial.println("NMEA GSV Message Deactivation
Failed!");
gpsSetSuccess = 0;
while(gpsSetSuccess < 3 && settingsArrayPointer[9] == 0x00) {
Serial.print("Deactivating NMEA RMC Messages ");
sendUBX(setRMC, sizeof(setRMC));
gpsSetSuccess += getUBX_ACK(&setRMC[2]);
if (gpsSetSuccess == 10) gpsStatus[5] = true;
if (gpsSetSuccess == 5 | gpsSetSuccess == 6) gpsSetSuccess -= 4;
}
if
(gpsSetSuccess == 3) Serial.println("NMEA RMC Message Deactivation
Failed!");
gpsSetSuccess = 0;
while(gpsSetSuccess
< 3 && settingsArrayPointer[10] == 0x00) {
Serial.print("Deactivating NMEA VTG Messages ");
sendUBX(setVTG, sizeof(setVTG));
gpsSetSuccess += getUBX_ACK(&setVTG[2]);
if (gpsSetSuccess == 10) gpsStatus[6] = true;
if (gpsSetSuccess == 5 | gpsSetSuccess == 6) gpsSetSuccess -= 4;
}
if
(gpsSetSuccess == 3) Serial.println("NMEA VTG Message Deactivation
Failed!");
gpsSetSuccess = 0;
if
(settingsArrayPointer[4] != 0x25) {
Serial.print("Setting Port Baud Rate... ");
sendUBX(&setPortRate[0], sizeof(setPortRate));
setBaud(settingsArrayPointer[4]);
Serial.println("Success!");
delay(500);
}
}
void calcChecksum(byte *checksumPayload,
byte payloadSize) {
byte CK_A = 0, CK_B = 0;
for
(int i = 0; i < payloadSize ;i++) {
CK_A = CK_A + *checksumPayload;
CK_B = CK_B + CK_A;
checksumPayload++;
}
*checksumPayload = CK_A;
checksumPayload++;
*checksumPayload = CK_B;
}
void sendUBX(byte *UBXmsg, byte msgLength)
{
for(int i = 0; i < msgLength; i++) {
gpsSerial.write(UBXmsg[i]);
gpsSerial.flush();
}
gpsSerial.println();
gpsSerial.flush();
}
byte getUBX_ACK(byte *msgID) {
byte CK_A = 0, CK_B = 0;
byte incoming_char;
boolean headerReceived = false;
unsigned long ackWait = millis();
byte ackPacket[10] = {0xB5, 0x62, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00};
int
i = 0;
while (1) {
if (gpsSerial.available()) {
incoming_char = gpsSerial.read();
if (incoming_char == ackPacket[i]) {
i++;
}
else if (i > 2) {
ackPacket[i] = incoming_char;
i++;
}
}
if (i > 9) break;
if ((millis() - ackWait) > 1500) {
Serial.println("ACK Timeout");
return 5;
}
if (i == 4 && ackPacket[3] == 0x00) {
Serial.println("NAK Received");
return 1;
}
}
for
(i = 2; i < 8 ;i++) {
CK_A = CK_A + ackPacket[i];
CK_B = CK_B + CK_A;
}
if
(msgID[0] == ackPacket[6] && msgID[1] == ackPacket[7] && CK_A
== ackPacket[8] && CK_B == ackPacket[9]) {
Serial.println("Success!");
Serial.print("ACK Received! ");
printHex(ackPacket, sizeof(ackPacket));
return 10;
}
else {
Serial.print("ACK Checksum Failure: ");
printHex(ackPacket, sizeof(ackPacket));
delay(1000);
return 1;
}
}
void printHex(uint8_t *data, uint8_t
length) // prints 8-bit data in hex
{
char tmp[length*2+1];
byte first ;
int
j=0;
for
(byte i = 0; i < length; i++)
{
first = (data[i] >> 4) | 48;
if (first > 57) tmp[j] = first + (byte)7;
else tmp[j] = first ;
j++;
first = (data[i] & 0x0F) | 48;
if (first > 57) tmp[j] = first + (byte)7;
else tmp[j] = first;
j++;
}
tmp[length*2] = 0;
for
(byte i = 0, j = 0; i < sizeof(tmp); i++) {
Serial.print(tmp[i]);
if (j == 1) {
Serial.print(" ");
j = 0;
}
else j++;
}
Serial.println();
}
void setBaud(byte baudSetting) {
if
(baudSetting == 0x12) gpsSerial.begin(4800);
if
(baudSetting == 0x4B) gpsSerial.begin(19200);
if
(baudSetting == 0x96) gpsSerial.begin(38400);
if
(baudSetting == 0xE1) gpsSerial.begin(57600);
if
(baudSetting == 0xC2) gpsSerial.begin(115200);
if
(baudSetting == 0x84) gpsSerial.begin(230400);
}
這裡印出的東西非常簡單,就是GPS轉換過後的數據
由於沒存到圖,又剛好在室內寫文章,故示意圖如下
好了,Arduino Yun 可以了,換上Linkit7697吧~
先用簡單的code
void setup(){
Serial.begin(9600);
Serial.println("Hello");
}
void loop(){
Serial.println( Serial.read());
}
結果如下圖
我的天天天啊!
連食尚玩家的阿松都發現了我們找到了第4個bug!
為什麼看別人的文章都沒在寫bug,我好像在做Linkit7697 QA bug report
為什麼看別人的文章都沒在寫bug,我好像在做Linkit7697 QA bug report
這個bug是代表Linkit7697不能使用SoftwareSerial.h
也就是說沒辦法自定腳位給GPS模組使用,只能用實體Uart給GPS用
好吧~ 那我們就來接Linkit7697的實體Uart pin0與pin1吧
但因為pin0與pin1已經給USB Debug用了,所以再接上GPS模組會出現亂碼
天怒人怨啊!
這表示Linkit
7697開發不能外接GPS/3G/4G...等Uart模組???
其實Arduino Yun開發完了就可以直接換上Linkit 7697,因為USB Debug不會再用到了~
所以這次就用Arduino Yun開發手環連接到燈座
有朋友問到,那不如改用ESP32,也是BLE+WiFi
其實用ESP32也行! 因為我們的功能不會非常複雜
但…我們要支持可以跟國際市場抗衡的國貨
而不是盲目的支持國貨
相信這4個Bug只是MTK QA沒查到,也許下一版更新就改過來了
復習一下另外三個Bug
MTK還有論壇哦! 有問題可以po上去,但可能MTKer比較忙,又常加班的關係~
如果曾用過TI、Espressif、Realtek論壇,就會發現MTK回應速度…
先前用過對岸的Realtek QQ群組,只能用火速來形容
這也是為什麼Espressif會在全球Maker圈中火熱起來
除了很清楚Maker要什麼之外,回應速度也飛快
好了! 抱怨結束! 回到正題!
接下來把把賴桑的code擷取下來
#include “I2Cdev.h”
#include “MPU6050.h”
#include “Wire.h”
MPU6050 accelgyro;
int16_t ax, ay, az;
int16_t gx, gy, gz;
int time_interval = 500; //500ms
unsigned long current, lastTime
= 0;
int16_t prev_accx = 0;
int16_t prev_accy = 0;
int16_t prev_accz = 0;
float acc_upv = 2.8; //The thresholds for fall
detection
void setup()
{
Wire.begin();
Serial.begin(115200);
accelgyro.initialize();
accelgyro.getMotion6(&ax,
&ay, &az, &gx, &gy, &gz);
prev_accx
= ax;
prev_accy
= ay;
prev_accz
= az;
lastTime
= millis();
}
void loop()
{
current
= millis();
//Set for every interval
if(current
– lastTime >= time_interval)
{
lastTime
= current;
accelgyro.getMotion6(&ax,
&ay, &az, &gx, &gy, &gz);
float
accx = ((ax – prev_accx) / 16384) * ((ax – prev_accx) / 16384);
float
accy = ((ay – prev_accy) / 16384) * ((ay – prev_accy) / 16384);
float
accz = ((az – prev_accz) / 16384) * ((az – prev_accz) / 16384);
Serial.print(accx);
Serial.print(“\t”);
Serial.print(accy);
Serial.print(“\t”);
Serial.print(accz);
Serial.println();
if(
sqrt(accx + accy + accz) > acc_upv )
Serial.println(“GOT!!!”);
prev_accx
= ax;
prev_accy
= ay;
prev_accz
= az;
}
}
然後再把柯大的MiniLora Example,LoraSender與LoRaReceiver貼出來
LoraSenderà
#include <SPI.h>
#include <LoRa.h>
int counter = 0;
void setup() {
Serial.begin(9600);
while (!Serial);
Serial.println("LoRa Sender");
if
(!LoRa.begin(915E6)) {
Serial.println("Starting LoRa failed!");
while (1);
}
}
void loop() {
Serial.print("Sending packet: ");
Serial.println(counter);
//
send packet
LoRa.beginPacket();
LoRa.print("hello ");
LoRa.print(counter);
LoRa.endPacket();
counter++;
delay(5000);
}
LoRaReceiverà
#include "SPI.h"
#include <LoRa.h>
void setup() {
Serial.begin(9600);
while (!Serial);
Serial.println("LoRa Receiver");
if
(!LoRa.begin(915E6)) {
Serial.println("Starting LoRa failed!");
while (1);
}
}
void loop() {
//
try to parse packet
int
packetSize = LoRa.parsePacket();
if
(packetSize) {
// received a packet
Serial.print("Received packet '");
// read packet
while (LoRa.available()) {
Serial.print((char)LoRa.read());
}
// print RSSI of packet
Serial.print("' with RSSI ");
Serial.println(LoRa.packetRssi());
}
}
Sender是用在Arduino Yun上面,MPU6050判斷長輩跌倒或由按下Button送出Trigger訊號到及GPS位置到燈座
而Receiver是用在Linkit7697上的code,其實Receiver將來需要修正,因為手機需要下指令到燈座,燈座再送訊號到手環取GPS位置
流程如下
手機-->Cloud Server-->燈座-->手環
手環回傳GPS位置-->燈座-->Cloud Server-->手機
手環回傳GPS位置-->燈座-->Cloud Server-->手機
猜測lota的架構大概也是這個樣子
這次留下四個Part的Code給大家手動實驗~
我先去104投MTK的QA了! 希望也可以負責論壇這一塊 ^ ^
~~~~~~~~~~~~~面試時~~~~~~~~~~~
主審官
我:ㄜ…
主審官
我:鳴…
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
P.S. 實做文章應該只剩下2~3篇,主要是燈座連接到智能家居配件(smart home security)及燈座室內定位就結束了
接下來會找時間開個線上會議,看看大家對這個群組有什麼想法
基本上Simple care is yours,怎麼讓照護變得更簡單
如何內化目前專案,怎麼整合照護人員及最後一哩路
P.S.1. 這個專案整合了四大領域,長照、保全、室內定位及智能燈泡
再細分下去還有Big data、AI、IOT,要做的事太多了
而這個群組內的參與人員領域很廣,也希望大家能多多幫這個社會盡一份力
動手改變世界 ^ ^
再細分下去還有Big data、AI、IOT,要做的事太多了
而這個群組內的參與人員領域很廣,也希望大家能多多幫這個社會盡一份力
動手改變世界 ^ ^
Good Luck!
加入我們吧!
沒有留言:
張貼留言