02-08-2021
12:08
- last edited on
02-11-2021
03:35
by
JonFitbit
02-08-2021
12:08
- last edited on
02-11-2021
03:35
by
JonFitbit
I tried numerous ways to get a text file to my server from the watch/companion. There is no problem getting it to the companion, but it does not get to the server. I have verified the server code using fetch from a different WEB site and JS. The simulator sends the fornData appended email address, but not the appended file. The following is the code for the watch, companion, .htaccess and uploadFile.php:
//device code to create and send txt file to companion
function createSendFile() {
if (appbit.permissions.granted("access_internet")) {
var todaydate = new Date();
var hour = todaydate.getHours();
var fn = "SSData_" + (todaydate.getYear() + 1900) + "-" + util.zeroPad(todaydate.getMonth() + 1) + "-" + util.zeroPad(todaydate.getDate()) + "_"
+ util.zeroPad(todaydate.getHours()) + "-" + util.zeroPad(todaydate.getMinutes()) + "-" + util.zeroPad(todaydate.getSeconds()) + "_" + display_colors[6];
if (fn.length > 59) {
fn = fn.slice(0, 59); //only keep 0-60
fn += "."; //add extra . to show it was cut
}
fn += ".txt"; //full file name up to 64 chars
var Data = ("Filename: " + fn
+ "\nEmail: " + display_colors[6]
+ "\nDate: "
+ ((todaydate.getMonth() + 1) + "/" + util.zeroPad(todaydate.getDate()) + "/" + (todaydate.getYear() + 1900))
+ "\nTime: "
+ ((preferences.clockDisplay === "12h")
? (((hour % 12) || 12) + ":" + util.zeroPad(todaydate.getMinutes()) + ":" + util.zeroPad(todaydate.getSeconds()) + (hour >= 12 ? 'pm' : 'am'))
: (hour + ":" + util.zeroPad(todaydate.getMinutes()) + ":" + util.zeroPad(todaydate.getSeconds())))
+ "\n");
Data += "\n*** Stats Data ***\n";
calc_hr_auto_level();
for (mypage = 1; mypage < 25; mypage++) {
Data += get_stats();
}
Data += get_stats();
Data += "\n*** Setup Data ***\n";
for (mypage = 1; mypage < 11; mypage++) {
Data += get_setup();
}
Data += get_setup();
Data += "\n*** Time/Exercise Selection Flag Definitions ***\n";
Data += "Hex values: 0x400=Font or Pace, 0x200=Time Dim or GPS, 0x100=Caloriess, 0x80=Active Minutes, 0x40=Distance,\n0x20=Events, 0x10=Resting HR, 0x8=Floors, 0x4=Steps, 0x2=Battery, 0x1=Date or Time\n";
Data += "*Current Time Selection Flag = 0x" + display_data[4].toString(16).toUpperCase() + "\n";
Data += "*Exercise Selection Flag = 0x" + display_data[5].toString(16).toUpperCase() + "\n";
fs.writeFileSync(fn, Data, "ascii");
if (fs.existsSync("/private/data/" + fn)) {
/* let ascii_read = fs.readFileSync("/private/data/" + fn, "ascii");
console.log(ascii_read);*/
ctrl[22] = "WMR"; //Waiting or Missing Response
sendFileOutbox("/private/data/" + fn);
/* fs.unlinkSync("/private/data/" + fn);
console.log("Successful delete");*/
}
}
else {
ctrl[22] = "NIP"; //No Internet Permission
console.log("No Internet Permission");
}
}
async function sendFileOutbox(filename) {
// console.log("Start sending");
outbox
.enqueueFile(filename)
.then((ft) => {
// console.log("File " + filename + " successfully queued.");
fs.unlinkSync(filename);
// console.log("Success delete");
})
.catch((error) => {
// console.log(`Failed to schedule transfer: $?{error}`);
fs.unlinkSync(filename);
// console.log("Error delete");
})
/* fs.unlinkSync(filename);
console.log("Successful delete");*/
}
function checkOutbox() {
outbox
.enumerate()
.then((files) => {
outbox_files = files.length;
// console.log(files.length);
})
}
//Entire Companion code to receive file and fetch() POST to WEB site, then acknowledge
import * as messaging from "messaging";
import { me as appbit } from "appbit";
import { settingsStorage } from "settings";
import { inbox } from "file-transfer";
import { outbox } from "file-transfer";
import { Image } from "image";
import { device } from "peer";
settingsStorage.addEventListener("change", evt => {
/* if (evt.key === "ubg") {
compressAndTransferImage(evt.newValue);
}
else {*/
// console.log(evt.key + " " + evt.newValue); //test for data
// if (evt.oldValue !== evt.newValue) {
sendValue(evt.key, evt.newValue);
// }
// }
});
setDefaultSetting("dimstart", JSON.stringify({ selected: [23] }));
setDefaultSetting("dimstop", JSON.stringify({ selected: [5] }));
setDefaultSetting("dimlevel", JSON.stringify({ selected: [3] }));
setDefaultSetting("normallevel", JSON.stringify({ selected: [3] }));
setDefaultSetting("hrtriplevel", JSON.stringify({ selected: [2] }));
setDefaultSetting("dhtriplevel", JSON.stringify({ selected: [2] }));
setDefaultSetting("dh_mult", JSON.stringify({ selected: [2] }));
setDefaultSetting("dh_exer_mult", JSON.stringify({ selected: [2] }));
setDefaultSetting("hhrtriptime", JSON.stringify({ selected: [2] }));
setDefaultSetting("hhrmintime", JSON.stringify({ selected: [2] }));
setDefaultSetting("hhrtimeonly", JSON.stringify({ selected: [2] }));
setDefaultSetting("hrcolormode", JSON.stringify({ selected: [1] }));
setDefaultSetting("heartrateColor", JSON.stringify( "lawngreen" ));
setDefaultSetting("timeColor", JSON.stringify( "linen" ));
setDefaultSetting("statsIconColor", JSON.stringify( "aquamarine" ));
setDefaultSetting("statsTextColor", JSON.stringify( "bisque" ));
setDefaultSetting("statsDeltaTextColor", JSON.stringify( "gold" ));
setDefaultSetting("bgColor", JSON.stringify( "#000000" ));
function sendValue(key, val) {
if (val) {
// console.log("Key=" + key + " Val=" + val);
sendSettingData({
key: key,
value: JSON.parse(val)
});
}
}
function sendSettingData(data) {
if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
messaging.peerSocket.send(data);
}
}
function setDefaultSetting(key = "", value = "") {
let extantValue = "";
extantValue = settingsStorage.getItem(key);
// console.log ("Get="+extantValue+" key="+key+" value="+value);
if (extantValue !== null) {
// console.log("!null");
// settingsStorage.setItem(key, extantValue);
}
else {
// console.log("null");
settingsStorage.setItem(key, value);
}
}
settingsStorage.setItem("screenWidth", device.screen.width);
settingsStorage.setItem("screenHeight", device.screen.height);
async function processAllFiles() {
let file;
while ((file = await inbox.pop())) {
const data = await file.text();
let email = JSON.parse(settingsStorage.getItem("email_addr")).name;
let url = 'https://REDCACTED.com/REDCACTED/uploadFile.php';
const formData = new FormData();
formData.append('upEmail', email);
formData.append('upFile', file);
console.log(file.name);
console.log(email);
console.log(data);
fetch(url, {
method: 'POST',
// mode: 'cors', // no-cors, *cors, same-origin
/* headers: {
'Content-type': 'multipart/form-data'
},*/
// credentials: 'same-origin', // include, *same-origin, omit
body: formData,
})
.then(function (response) {
console.log("Response Status: " + response.status);
console.log("Response Ok: " + response.ok);
// console.log("Response Headers: " + response.headers);
// console.log("Response url: " + response.url);
response.text()
.then(function (text) {
console.log(text);
if ((response.ok) && (response.status >= 200) && (response.status < 300)) {
// console.log(JSON.parse(text).message);
sendValue("send_error",text); //tell watch fetch ok, but check message
}
else
sendValue("send_error", JSON.stringify({message:response.status})); //tell watch fetch failed
})
})
.catch(function (error) {
console.log(error.name + ":" + error.message);
sendValue("send_error", JSON.stringify({message:"IE"})) //tell watch fetch failed
})
}
}
inbox.addEventListener("newfile", processAllFiles);
processAllFiles();
//Companion Inbox File: Console.log Results
[1:20:41 PM]SSData_2021-02-07_13-20-41_REDCACTED@REDCACTED.com.txt companion/index.js:88,5
[1:20:41 PM]REDCACTED@REDCACTED.com companion/index.js:89,5
[1:20:41 PM]Filename: SSData_2021-02-07_13-20-41_REDCACTED@REDCACTED.com.txt companion/index.js:90,5
Email: REDCACTED@REDCACTED.com
Date: 2/07/2021
Time: 13:20:41
*** Stats Data ***
*Events = 0
*HHR = 0
*LDHR = 0
*HR Data Gaps = 138
*HR Gap Filtered = 473
*HR dt<0.9 = 140
*HR Valid Pts = 7.00000e+1
*HR Pts / Day = 4
*HR dt Avg = 36.380
*HR dt Min = 1.024
*HR dt Max = 59.008
*HR Avg = 65.1
*HR Min = 60
Date: 1/23/21
Time: 11:22:55
*HR Max = 220
Date: 1/25/21
Time: 14:25:35
*dh Min(41.47) = 0 (60->60)
Date: 1/23/21
Time: 11:22:55
*dh Max(48.58) = 48.8 (60->220)
Date: 1/25/21
Time: 14:25:35
*dh Normal
*+dh = 1
*+dh Trip = 1
*-dh = 0
*-dh Trig = 0
*HHRLevelTime = 0
*HHRTimeOnly = 0
*Battery = 100%
*On Battery = 0:00:21
*%/hr = (100% - 100%) / 0:00:00
*%/hr = 0.000
*D on = 0:00:21 / 0 (0, 0) = 0.00
(100.00%*0.85)
*** Setup Data ***
*Version 5.53
*BusyBrainConsulting.com for Clock Manual pdf file
*HHR/LDHR = Show Events
*Font = Light
*Exercise = pickleball
*Distance = km
*User Age = 30
*Rest HR = 56
*Sleep Dim = On
*Email for Send:
REDCACTED@REDCACTED.com
*Time Dim = On
(23 to 6)
*Dim = 20%
*Bright = 85%
*HHRTripLevel = 100% Calc = 136.0 BPM
*HHRDropout = 85% = 115.6 BPM
*dhTripLevel = 100% Calc = 19.6 BPM
*LDHR = 130% = 25.5 BPM
*dh*Exer = 130% = 25.5 BPM
*LDHR*Exer = 169% = 33.2 BPM
*HRDatagap = 1min
*HRGapFilter = 15sec/30sec
*HHRTripTime = 2min
*HHRMinTime = 15min
*HHRTimeOnly = 12hr
*HR Color = Fixed
*** Time/Exercise Selection Flag Definitions ***
Hex values: 0x400=Font or Pace, 0x200=Time Dim or GPS, 0x100=Caloriess, 0x80=Active Minutes, 0x40=Distance,
0x20=Events, 0x10=Resting HR, 0x8=Floors, 0x4=Steps, 0x2=Battery, 0x1=Date or Time
*Current Time Selection Flag = 0x627
*Exercise Selection Flag = 0x5E5
[1:20:41 PM]The 'permission' API is not yet supported in the Fitbit OS Simulator. Behavior may not match the documentation or real devices.
[1:20:42 PM]Response Status: 200 companion/index.js:102,9
[1:20:42 PM]Response Ok: true companion/index.js:103,9
[1:20:42 PM]"message":"MFT","code":43,"status":"Missing File\/TmpFile","email":"REDCACTED@REDCACTED.com","tmpfile":null,"file":null,"debug":[]}
companion/index.js:108,11
//https://REDCACTED.com/REDCACTED directory entire .htaccess file
#Protect .htaccess
<Files .htaccess>
order allow,deny
deny from all
</Files>
RewriteEngine On
RewriteCond %{ENV:HTTPS} !on
RewriteRule (.*) https://REDCACTED.com/$1 [R=301,L]
#FreeScript
AddDefaultCharset UTF-8
# Only the following file extensions are allowed
<FilesMatch "\.(txt)$">
Order Deny,Allow
Allow from all
Header add Access-Control-Allow-Origin: "*"
Header add Access-Control-Allow-Methods: "GET,POST,OPTIONS,DELETE,PUT"
Header add Access-Control-Allow-Credentials: true
</FilesMatch>
//https://REDCACTED.com/REDCACTED directory entire uploadFile.php file
<?php
#version 1.02
# leaving this on for now, could help us identify some php wierdness that could cause issues, change to 0 to disable
define('DEBUG_MODE', 1); //0=Off, 1=code line,status,filename, >1 add file data
# this might need to be 'upfile'
define('FILE_CONST', 'upFile');
define('EMAIL_CONST', 'upEmail');
define('TARGET_DIR', './uploads/');
# this can theretically change, but practically 1000000 has been the same for 15 years
define('MAX_UPLOAD', 5000);
$response = array(
'message' => "ERR",
);
DEBUG_MODE && $response['code'] = __LINE__;
DEBUG_MODE && $response['status'] = 'Undefined Error';
header('Content-Type: application/json; charset=utf-8');
header("Access-Control-Allow-Origin: *");
header('Access-Control-Allow-Credentials: true');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "MPT";
DEBUG_MODE && $response['status'] = 'Missing POST Type';
finish($response);
}
$email = $_POST[EMAIL_CONST];
$file = $_FILES[FILE_CONST];
$tmpfile = $_FILES[FILE_CONST]['tmp_name'];
DEBUG_MODE && $response['email'] = $email;
DEBUG_MODE && $response['tmpfile'] = $tmpfile;
DEBUG_MODE && $response['file'] = $file;
if (((empty($file)) || (empty($tmpfile))) && (empty($email))) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "MFE";
DEBUG_MODE && $response['status'] = 'Missing File and Email Address';
finish($response);
}
else if ((empty($file)) || (empty($tmpfile))) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "MFT";
DEBUG_MODE && $response['status'] = 'Missing File/TmpFile';
finish($response);
}
else if (empty($email)) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "MEA";
DEBUG_MODE && $response['status'] = 'Missing Email Address';
finish($response);
}
$filename = $_FILES[FILE_CONST]['name'];
$ext = strtolower(end(explode('.',$filename)));
DEBUG_MODE && $response['Target_Dir'] = TARGET_DIR;
DEBUG_MODE && $response['filename'] = $filename;
DEBUG_MODE && $response['ext'] = $ext;
if (!isset($file['error']) || is_array($file['error'])) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "IFS";
DEBUG_MODE && $response['status'] = 'Invalid File Structure';
finish($response);
}
if (!empty($file['error'])) {
# these are PHP constants defined somewhere else, treat it as JFM for now
switch($file['error']) {
case UPLOAD_ERR_NO_FILE:
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "UNF";
DEBUG_MODE && $response['status'] = 'UPLOAD_ERR_NO_FILE';
break;
case UPLOAD_ERR_INI_SIZE:
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "UIS";
DEBUG_MODE && $response['status'] = 'UPLOAD_ERR_INI_SIZE';
break;
case UPLOAD_ERR_FORM_SIZE:
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "UFS";
DEBUG_MODE && $response['status'] = 'UPLOAD_ERR_FORM_SIZE';
break;
default:
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "UND";
DEBUG_MODE && $response['status'] = 'UPLOAD_ERR_UNDEFINED';
}
finish($response);
}
if ($file['size']==0 || $file['size'] > MAX_UPLOAD) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "IFS";
DEBUG_MODE && $response['status'] = 'Invalid File Size (' . $file['size'] . ')';
finish($response);
}
$valid_mime_types = [
'text/plain',
];
$mime_type = get_file_mime($file['tmp_name']);
if (!in_array($mime_type, $valid_mime_types)) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "IMT";
DEBUG_MODE && $response['status'] = 'Invalid Mime Type (' . $mime_type . ')';
finish($response);
}
/*if (!empty($_POST[FILE_CONST])) {
$t = preg_replace('/[^A-Za-z0-9_.-]/' , '', $_POST[FILE_CONST]);
$t = explode('.', $t);
$filename = reset($t);
$ext = end($t);
}*/
$valid_ext = [
'txt',
]; //comma seperated values
if (!in_array($ext, $valid_ext)) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "IET";
DEBUG_MODE && $response['status'] = 'Invalid Ext Type (' . $ext . ')';
finish($response);
}
$perm_filename = save_uploaded_file($file['tmp_name'], $ext, basename($filename, '.txt'));
if (empty($perm_filename)) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "FSF";
DEBUG_MODE && $response['status'] = 'Failed Save File';
finish($response);
}
$data = get_file_contents($perm_filename);
(DEBUG_MODE > 1) && $response['data'] = $data;
$subject = "Fitbit Watch Data File: " . $filename;
$headers = array(
"From" => "REDCACTED@REDCACTED.com",
"ByWayOf" => "REDCACTED.com",
"Reply-To" => "REDCACTED@REDCACTED.com",
"X-Mailer" => "PHP/" . phpversion()
);
$success = mail($email, $subject, $data, $headers);
if (!$success) {
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "FSE";
DEBUG_MODE && $response['status'] = 'Failed Send Email';
DEBUG_MODE && $response['mail_error'] = error_get_last()[message];
finish($response);
}
DEBUG_MODE && $response['code'] = __LINE__;
$response['message'] = "Ok";
DEBUG_MODE && $response['status'] = 'success';
finish($response);
#########
function finish(array $input = []) {
DEBUG_MODE && $input['debug'] = $_FILES;
echo json_encode($input);
exit;
}
function get_file_contents($filename) {
# Don't know if you need this, used it in some debugging so now you have it
if (empty($filename) || !is_readable($filename)) {
return false;
}
$data = file_get_contents($filename);
// return explode(PHP_EOL, $data);
return $data;
}
function get_file_mime($filename) {
$finfo = new finfo(FILEINFO_MIME_TYPE);
return $finfo->file($filename);
}
function save_uploaded_file($tmpfile, $ext, $filename = '') {
# if you want a randomized filename, don't pass 3rd parameter
if (empty($filename)) {
$filename = sha1_file($tmpfile . time());
}
$success = move_uploaded_file($tmpfile, TARGET_DIR . $filename . '.' . $ext);
return $success ? TARGET_DIR . $filename . '.' .$ext : false;
}
?>
Answered! Go to the Best Answer.
02-11-2021 09:18
02-11-2021 09:18
Thanks for the information. I already determined that file transfer is not supported in the Fitbit fetch() function. I did upload the file content using JSON object and reconstructing the file in the php receiving script. All the examples that I was directed to did show how to upload a file and I was successful in uploading a file using fetch() in Javascript in a browser. The Fitbit fetch() API and Guide should indicated that file transfer is not currently supported. The ability to even do this is amazing. The user can press a button on the watch and receive an email with the requested data.
Thanks again.
Best Answer02-11-2021 03:30
Fitbit Developers oversee the SDK and API forums. We're here to answer questions about Fitbit developer tools, assist with projects, and make sure your voice is heard by the development team.
02-11-2021 03:30
I don't think you can transfer the file directly in the companion like that. I think you need to embed the file contents as the payload,
file.text()
See https://dev.fitbit.com/build/guides/communications/file-transfer/#companion-inbox
I removed some of the URLs from your post for privacy reasons.
Best Answer02-11-2021 09:18
02-11-2021 09:18
Thanks for the information. I already determined that file transfer is not supported in the Fitbit fetch() function. I did upload the file content using JSON object and reconstructing the file in the php receiving script. All the examples that I was directed to did show how to upload a file and I was successful in uploading a file using fetch() in Javascript in a browser. The Fitbit fetch() API and Guide should indicated that file transfer is not currently supported. The ability to even do this is amazing. The user can press a button on the watch and receive an email with the requested data.
Thanks again.
Best Answer