# data[].attributes.body
/*
This example demonstrates MTP with blinky using systick interrupt.

This example tests MTP and SdFat
*/

#include <MTP.h>

MTPStorage_SD storage;
MTPD mtpd(&storage);

volatile int status = 0;
/*
void systick_isr() {
systick_millis_count++;   // Original ISR code
if (systick_millis_count % 1000 == 0) {
digitalWrite(LED_BUILTIN, status);
status = !status;
}
}
*/
void setup() {
pinMode(LED_BUILTIN, OUTPUT);

if (SD.begin()) {
Serial.println("SD found");
} else {
Serial.println("SD not found");
}
}

void loop() {
mtpd.loop();
}

# data[].attributes.body
// MTP.h - Teensy MTP Responder library
// Copyright (C) 2017 Fredrik Hubinette <hubbe@hubbe.net>
//
// With updates from MichaelMC, YoongHM
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#ifndef MTP_H
#define MTP_H

#include <Arduino.h>
// #include <HardwareSerial.h>
#include <usb_dev.h>
#include <SPI.h>
#include "SdFat.h"

SdFatSdioEX SD;

// TODO:
//   support multiple storages
//   support serialflash
//   partial object fetch/receive
//   events (notify usb host when local storage changes)

// These should probably be weak.
void mtp_yield() {}
void mtp_lock_storage(bool lock) { }

// This interface lets the MTP responder interface any storage.
// We'll need to give the MTP responder a pointer to one of these.
class MTPStorageInterface {
public:
// Return true if this storage is read-only
virtual bool readonly() = 0;

// Does it have directories?
virtual bool has_directories() = 0;

// Return size of storage in bytes.
virtual uint64_t size() = 0;

// Return free space in bytes.
virtual uint64_t free() = 0;

// parent = 0 means get all handles.
// parent = 0xFFFFFFFF means get root folder.
virtual void StartGetObjectHandles(uint32_t parent) = 0;
virtual uint32_t GetNextObjectHandle() = 0;

// Size should be 0xFFFFFFFF if it's a directory.
virtual void GetObjectInfo(uint32_t handle,
char* name,
uint32_t* size,
uint32_t* parent) = 0;
virtual uint32_t GetSize(uint32_t handle) = 0;
virtual void read(uint32_t handle,
uint32_t pos,
char* buffer,
uint32_t bytes) = 0;
virtual uint32_t Create(uint32_t parent,
bool folder,
const char* filename) = 0;
virtual void write(const char* data, uint32_t size);
virtual void close();
virtual bool DeleteObject(uint32_t object) = 0;
};

// Storage implementation for SD. SD needs to be already initialized.
class MTPStorage_SD : public MTPStorageInterface {
private:
File index_;

uint8_t mode_ = 0;
uint32_t open_file_ = 0xFFFFFFFEUL;
File f_;
uint32_t index_entries_ = 0;

struct Record {
uint32_t parent;
uint32_t child;  // size stored here for files
uint32_t sibling;
uint8_t isdir;
uint8_t scanned;
char name[64];
};

bool readonly() { return false; }
bool has_directories() { return true; }
uint64_t size(){
return (uint64_t)512 * (uint64_t)SD.clusterCount() * (uint64_t)SD.vol()->blocksPerCluster();
}

uint64_t free() {
uint64_t volFree = SD.vol()->freeClusterCount();
uint64_t ssize = (uint64_t)512 * volFree * (uint64_t)SD.vol()->blocksPerCluster();
return ssize;
}

void OpenIndex() {
if (index_) return;
mtp_lock_storage(true);
index_ = SD.open("mtpindex.dat", FILE_WRITE);
mtp_lock_storage(false);
}

void WriteIndexRecord(uint32_t i, const Record& r) {
OpenIndex();
mtp_lock_storage(true);
index_.seek(sizeof(r) * i);
index_.write((char*)&r, sizeof(r));
mtp_lock_storage(false);
}

uint32_t AppendIndexRecord(const Record& r) {
uint32_t new_record = index_entries_++;
WriteIndexRecord(new_record, r);
return new_record;
}

// TODO(hubbe): Cache a few records for speed.
Record ReadIndexRecord(uint32_t i) {
Record ret;
if (i > index_entries_) {
memset(&ret, 0, sizeof(ret));
return ret;
}
OpenIndex();
mtp_lock_storage(true);
index_.seek(sizeof(ret) * i);
index_.read(&ret, sizeof(ret));
mtp_lock_storage(false);
return ret;
}

void ConstructFilename(int i, char* out) {
if (i == 0) {
strcpy(out, "/");
} else {
Record tmp = ReadIndexRecord(i);
ConstructFilename(tmp.parent, out);
if (out[strlen(out)-1] != '/')
strcat(out, "/");
strcat(out, tmp.name);
}
}

void OpenFileByIndex(uint32_t i, uint8_t mode = O_RDONLY) {
if (open_file_ == i && mode_ == mode)
return;
char filename[256];
ConstructFilename(i, filename);
mtp_lock_storage(true);
f_.close();
f_ = SD.open(filename, mode);
open_file_ = i;
mode_ = mode;
mtp_lock_storage(false);
}

// MTP object handles should not change or be re-used during a session.
// This would be easy if we could just have a list of all files in memory.
// Since our RAM is limited, we'll keep the index in a file instead.
bool index_generated = false;
void GenerateIndex() {
if (index_generated) return;
index_generated = true;

mtp_lock_storage(true);
SD.remove("mtpindex.dat");
mtp_lock_storage(false);
index_entries_ = 0;

Record r;
r.parent = 0;
r.sibling = 0;
r.child = 0;
r.isdir = true;
r.scanned = false;
strcpy(r.name, "/");
AppendIndexRecord(r);
}

void ScanDir(uint32_t i) {
Record record = ReadIndexRecord(i);
if (record.isdir && !record.scanned) {
OpenFileByIndex(i);
if (!f_) return;
int sibling = 0;
while (true) {
mtp_lock_storage(true);
File child = f_.openNextFile();
mtp_lock_storage(false);

if (!child) break;

Record r;
r.parent = i;
r.sibling = sibling;
r.isdir = child.isDirectory();
r.child = r.isdir ? 0 : child.size();
r.scanned = false;
child.getName(r.name, 64);
sibling = AppendIndexRecord(r);
child.close();
}
record.scanned = true;
record.child = sibling;
WriteIndexRecord(i, record);
}
}

bool all_scanned_ = false;
void ScanAll() {
if (all_scanned_) return;
all_scanned_ = true;

GenerateIndex();
for (uint32_t i = 0; i < index_entries_; i++) {
ScanDir(i);
}
}

uint32_t next_;
bool follow_sibling_;
void StartGetObjectHandles(uint32_t parent) override {
GenerateIndex();
if (parent) {
if (parent == 0xFFFFFFFF) parent = 0;

ScanDir(parent);
follow_sibling_ = true;
// Root folder?
next_ = ReadIndexRecord(parent).child;
} else {
ScanAll();
follow_sibling_ = false;
next_ = 1;
}
}

uint32_t GetNextObjectHandle() override {
while (true) {
if (next_ == 0) return 0;

int ret = next_;
Record r = ReadIndexRecord(ret);
if (follow_sibling_) {
next_ = r.sibling;
} else {
next_++;
if (next_ >= index_entries_)
next_ = 0;
}
if (r.name[0]) return ret;
}
}

void GetObjectInfo(uint32_t handle,
char* name,
uint32_t* size,
uint32_t* parent) override {
Record r = ReadIndexRecord(handle);
strcpy(name, r.name);
*parent = r.parent;
*size = r.isdir ? 0xFFFFFFFFUL : r.child;
}

uint32_t GetSize(uint32_t handle) {
return ReadIndexRecord(handle).child;
}

void read(uint32_t handle,
uint32_t pos,
char* out,
uint32_t bytes) override {
OpenFileByIndex(handle);
mtp_lock_storage(true);
f_.seek(pos);
f_.read(out, bytes);
mtp_lock_storage(false);
}

bool DeleteObject(uint32_t object) override {
char filename[256];
Record r;
while (true) {
r = ReadIndexRecord(object == 0xFFFFFFFFUL ? 0 : object);
if (!r.isdir) break;
if (!r.child) break;
if (!DeleteObject(r.child))
return false;
}

// We can't actually delete the root folder,
// but if we deleted everything else, return true.
if (object == 0xFFFFFFFFUL) return true;

ConstructFilename(object, filename);
bool success;
mtp_lock_storage(true);
if (r.isdir) {
success = SD.rmdir(filename);
} else {
success = SD.remove(filename);
}
mtp_lock_storage(false);
if (!success) return false;
r.name[0] = 0;
int p = r.parent;
WriteIndexRecord(object, r);
Record tmp = ReadIndexRecord(p);
if (tmp.child == object) {
tmp.child = r.sibling;
WriteIndexRecord(p, tmp);
} else {
int c = tmp.child;
while (c) {
tmp = ReadIndexRecord(c);
if (tmp.sibling == object) {
tmp.sibling = r.sibling;
WriteIndexRecord(c, tmp);
break;
} else {
c = tmp.sibling;
}
}
}
return true;
}

uint32_t Create(uint32_t parent,
bool folder,
const char* filename) override {
uint32_t ret;
if (parent == 0xFFFFFFFFUL) parent = 0;
Record p = ReadIndexRecord(parent);
Record r;
if (strlen(filename) > 62) return 0;
strcpy(r.name, filename);
r.parent = parent;
r.child = 0;
r.sibling = p.child;
r.isdir = folder;
// New folder is empty, scanned = true.
r.scanned = 1;
ret = p.child = AppendIndexRecord(r);
WriteIndexRecord(parent, p);
if (folder) {
char filename[256];
ConstructFilename(ret, filename);
mtp_lock_storage(true);
SD.mkdir(filename);
mtp_lock_storage(false);
} else {
OpenFileByIndex(ret, FILE_WRITE);
}
return ret;
}

void write(const char* data, uint32_t bytes) override {
mtp_lock_storage(true);
f_.write(data, bytes);
mtp_lock_storage(false);
}

void close() override {
mtp_lock_storage(true);
uint64_t size = f_.size();
f_.close();
mtp_lock_storage(false);
Record r = ReadIndexRecord(open_file_);
r.child = size;
WriteIndexRecord(open_file_, r);
open_file_ = 0xFFFFFFFEUL;
}
};

// MTP Responder.
class MTPD {
public:
explicit MTPD(MTPStorageInterface* storage) : storage_(storage) {}

private:
MTPStorageInterface* storage_;

struct MTPHeader {
uint32_t len;  // 0
uint16_t type; // 4
uint16_t op;   // 6
uint32_t transaction_id; // 8
};

struct MTPContainer {
uint32_t len;  // 0
uint16_t type; // 4
uint16_t op;   // 6
uint32_t transaction_id; // 8
uint32_t params[5];    // 12
};

void PrintPacket(const usb_packet_t *x) {
#if 0
for (int i = 0; i < x->len; i++) {
Serial1.print("0123456789ABCDEF"[x->buf[i] >> 4]);
Serial1.print("0123456789ABCDEF"[x->buf[i] & 0xf]);
if ((i & 3) == 3) Serial1.print(" ");
}
Serial1.println("");
#endif
#if 0
MTPContainer *tmp = (struct MTPContainer*)(x->buf);
Serial1.print(" len = ");
Serial1.print(tmp->len, HEX);
Serial1.print(" type = ");
Serial1.print(tmp->type, HEX);
Serial1.print(" op = ");
Serial1.print(tmp->op, HEX);
Serial1.print(" transaction_id = ");
Serial1.print(tmp->transaction_id, HEX);
for (int i = 0; i * 4 < x->len - 12; i ++) {
Serial1.print(" p");
Serial1.print(i);
Serial1.print(" = ");
Serial1.print(tmp->params[i], HEX);
}
Serial1.println("");
#endif
}

usb_packet_t *data_buffer_ = NULL;
void get_buffer() {
while (!data_buffer_) {
data_buffer_ = usb_malloc();
if (!data_buffer_) mtp_yield();
}
}

void receive_buffer() {
while (!data_buffer_) {
data_buffer_ = usb_rx(MTP_RX_ENDPOINT);
if (!data_buffer_) mtp_yield();
}
}

bool write_get_length_ = false;
uint32_t write_length_ = 0;
void write(const char *data, int len) {
if (write_get_length_) {
write_length_ += len;
} else {
int pos = 0;
while (pos < len) {
get_buffer();
int avail = sizeof(data_buffer_->buf) - data_buffer_->len;
int to_copy = min(len - pos, avail);
memcpy(data_buffer_->buf + data_buffer_->len,
data + pos,
to_copy);
data_buffer_->len += to_copy;
pos += to_copy;
if (data_buffer_->len == sizeof(data_buffer_->buf)) {
usb_tx(MTP_TX_ENDPOINT, data_buffer_);
data_buffer_ = NULL;
// Serial1.println("SENT...");
}
}
}
}

void write8 (uint8_t  x) { write((char*)&x, sizeof(x)); }

void write16(uint16_t x) { write((char*)&x, sizeof(x)); }

void write32(uint32_t x) { write((char*)&x, sizeof(x)); }

void write64(uint64_t x) { write((char*)&x, sizeof(x)); }

void writestring(const char* str) {
if (*str) {
write8(strlen(str) + 1);
while (*str) {
write16(*str);
++str;
}
write16(0);
} else {
write8(0);
}
}

void WriteDescriptor() {
write16(100);  // MTP version
write32(6);    // MTP extension
//    write32(0xFFFFFFFFUL);    // MTP extension
write16(100);  // MTP version
writestring("microsoft.com: 1.0;");
write16(0);    // functional mode

// Supported operations (array of uint16)
write32(14);
write16(0x1001);  // GetDeviceInfo
write16(0x1002);  // OpenSession
write16(0x1003);  // CloseSession
write16(0x1004);  // GetStorageIDs

write16(0x1005);  // GetStorageInfo
write16(0x1006);  // GetNumObjects
write16(0x1007);  // GetObjectHandles
write16(0x1008);  // GetObjectInfo

write16(0x1009);  // GetObject
write16(0x100B);  // DeleteObject
write16(0x100C);  // SendObjectInfo
write16(0x100D);  // SendObject

write16(0x1014);  // GetDevicePropDesc
write16(0x1015);  // GetDevicePropValue

//    write16(0x1010);  // Reset
//    write16(0x1019);  // MoveObject
//    write16(0x101A);  // CopyObject

write32(0);       // Events (array of uint16)

write32(1);       // Device properties (array of uint16)
write16(0xd402);  // Device friendly name

write32(0);       // Capture formats (array of uint16)

write32(2);       // Playback formats (array of uint16)
write16(0x3000);  // Undefined format
write16(0x3001);  // Folders (associations)

writestring("PJRC");     // Manufacturer
writestring("Teensy");   // Model
writestring("1.0");      // version
writestring("???");      // serial
}

void WriteStorageIDs() {
write32(1); // 1 entry
write32(1); // 1 storage
}

void GetStorageInfo(uint32_t storage) {
write16(storage_->readonly() ? 0x0001 : 0x0004);   // storage type (removable RAM)
write16(storage_->has_directories() ? 0x0002: 0x0001);   // filesystem type (generic hierarchical)
write16(0x0000);   // access capability (read-write)
write64(storage_->size());  // max capacity
write64(storage_->free());  // free space (100M)
write32(0xFFFFFFFFUL);  // free space (objects)
writestring("SD Card");  // storage descriptor
writestring("");  // volume identifier
}

uint32_t GetNumObjects(uint32_t storage,
uint32_t parent) {
storage_->StartGetObjectHandles(parent);
int num = 0;
while (storage_->GetNextObjectHandle()) num++;
return num;
}

void GetObjectHandles(uint32_t storage,
uint32_t parent) {
uint32_t num = 0;
if (!write_get_length_) {
num = GetNumObjects(storage, parent);
}
write32(num);
int handle;
storage_->StartGetObjectHandles(parent);
while ((handle = storage_->GetNextObjectHandle()))
write32(handle);
}

void GetObjectInfo(uint32_t handle) {
char filename[256];
uint32_t size, parent;
storage_->GetObjectInfo(handle, filename, &size, &parent);

write32(1); // storage
write16(size == 0xFFFFFFFFUL ? 0x3001 : 0x0000); // format
write16(0);  // protection
write32(size); // size
write16(0); // thumb format
write32(0); // thumb size
write32(0); // thumb width
write32(0); // thumb height
write32(0); // pix width
write32(0); // pix height
write32(0); // bit depth
write32(parent); // parent
write16(size == 0xFFFFFFFFUL ? 1 : 0); // association type
write32(0); // association description
write32(0);  // sequence number
writestring(filename);
writestring("");  // date created
writestring("");  // date modified
writestring("");  // keywords
}

void GetObject(uint32_t object_id) {
uint32_t size = storage_->GetSize(object_id);
if (write_get_length_) {
write_length_ += size;
} else {
uint32_t pos = 0;
while (pos < size) {
get_buffer();
uint32_t avail = sizeof(data_buffer_->buf) - data_buffer_->len;
uint32_t to_copy = min(pos - size, avail);
// Read directly from storage into usb buffer.
storage_->read(object_id, pos,
(char*)(data_buffer_->buf + data_buffer_->len),
to_copy);
pos += to_copy;
data_buffer_->len += to_copy;
if (data_buffer_->len == sizeof(data_buffer_->buf)) {
usb_tx(MTP_TX_ENDPOINT, data_buffer_);
data_buffer_ = NULL;
}
}
}
}

inline MTPContainer *contains (usb_packet_t *receive_buffer){
return (MTPContainer*)(receive_buffer->buf);
}
#define CONTAINER contains(receive_buffer)

#define TRANSMIT(FUN) do {                              \
write_length_ = 0;                                  \
write_get_length_ = true;                           \
FUN;                                                \
write_get_length_ = false;                          \
MTPHeader header;                                   \
header.len = write_length_ + 12;                    \
header.type = 2;                                    \
header.op = CONTAINER->op;                          \
header.transaction_id = CONTAINER->transaction_id;  \
write((char *)&header, sizeof(header));             \
FUN;                                                \
get_buffer();                                       \
usb_tx(MTP_TX_ENDPOINT, data_buffer_);              \
data_buffer_ = NULL;                                \
} while(0)

void read(char* data, uint32_t size) {
while (size) {
receive_buffer();
uint32_t to_copy = data_buffer_->len - data_buffer_->index;
to_copy = min(to_copy, size);
if (data) {
memcpy(data, data_buffer_->buf + data_buffer_->index, to_copy);
data += to_copy;
}
size -= to_copy;
data_buffer_->index += to_copy;
if (data_buffer_->index == data_buffer_->len) {
usb_free(data_buffer_);
data_buffer_ = NULL;
}
}
}

uint32_t ReadMTPHeader() {
MTPHeader header;
read((char *)&header, sizeof(MTPHeader));
// check that the type is data
return header.len - 12;
}

uint8_t read8() {
uint8_t ret;
read((char*)&ret, sizeof(ret));
return ret;
}

uint16_t read16() {
uint16_t ret;
read((char*)&ret, sizeof(ret));
return ret;
}

uint32_t read32() {
uint32_t ret;
read((char*)&ret, sizeof(ret));
return ret;
}

void readstring(char* buffer) {
int len = read8();
if (!buffer) {
read(NULL, len * 2);
} else {
for (int i = 0; i < len; i++) {
*(buffer++) = read16();
}
}
}

void read_until_short_packet() {
bool done = false;
while (!done) {
receive_buffer();
done = data_buffer_->len != sizeof(data_buffer_->buf);
usb_free(data_buffer_);
data_buffer_ = NULL;
}
}

uint32_t SendObjectInfo(uint32_t storage, uint32_t parent) {
ReadMTPHeader();
char filename[256];

read32(); // storage
bool dir = read16() == 0x3001; // format
read16();  // protection
read32(); // size
read16(); // thumb format
read32(); // thumb size
read32(); // thumb width
read32(); // thumb height
read32(); // pix width
read32(); // pix height
read32(); // bit depth
read32(); // parent
read16(); // association type
read32(); // association description
read32(); // sequence number

readstring(filename);
read_until_short_packet();  // ignores dates & keywords
return storage_->Create(parent, dir, filename);
}

void SendObject() {
uint32_t len = ReadMTPHeader();
while (len) {
receive_buffer();
uint32_t to_copy = data_buffer_->len - data_buffer_->index;
to_copy = min(to_copy, len);
storage_->write((char*)(data_buffer_->buf + data_buffer_->index),
to_copy);
data_buffer_->index += to_copy;
len -= to_copy;
if (data_buffer_->index == data_buffer_->len) {
usb_free(data_buffer_);
data_buffer_ = NULL;
}
}
storage_->close();
}

void GetDevicePropValue(uint32_t prop) {
switch (prop) {
case 0xd402: // friendly name
// This is the name we'll actually see in the windows explorer.
// Should probably be configurable.
writestring("Teensy");
break;
}
}

void GetDevicePropDesc(uint32_t prop) {
switch (prop) {
case 0xd402: // friendly name
write16(prop);
write16(0xFFFF); // string type
write8(0);       // read-only
GetDevicePropValue(prop);
GetDevicePropValue(prop);
write8(0);       // no form
}
}

public:
void loop() {
usb_packet_t *receive_buffer;
if ((receive_buffer = usb_rx(MTP_RX_ENDPOINT))) {
PrintPacket(receive_buffer);
uint32_t return_code = 0;
uint32_t p1 = 0;
if (receive_buffer->len >= 12) {
return_code = 0x2001;  // Ok
receive_buffer->len = 16;
if (CONTAINER->type == 1) { // command
switch (CONTAINER->op) {
case 0x1001: // GetDescription
TRANSMIT(WriteDescriptor());
break;
case 0x1002:  // OpenSession
break;
case 0x1003:  // CloseSession
break;
case 0x1004:  // GetStorageIDs
TRANSMIT(WriteStorageIDs());
break;
case 0x1005:  // GetStorageInfo
TRANSMIT(GetStorageInfo(CONTAINER->params[0]));
break;
case 0x1006:  // GetNumObjects
if (CONTAINER->params[1]) {
return_code = 0x2014; // spec by format unsupported
} else {
p1 = GetNumObjects(CONTAINER->params[0],
CONTAINER->params[2]);
}
break;
case 0x1007:  // GetObjectHandles
if (CONTAINER->params[1]) {
return_code = 0x2014; // spec by format unsupported
} else {
TRANSMIT(GetObjectHandles(CONTAINER->params[0],
CONTAINER->params[2]));
}
break;
case 0x1008:  // GetObjectInfo
TRANSMIT(GetObjectInfo(CONTAINER->params[0]));
break;
case 0x1009:  // GetObject
TRANSMIT(GetObject(CONTAINER->params[0]));
break;
case 0x100B:  // DeleteObject
if (CONTAINER->params[1]) {
return_code = 0x2014; // spec by format unsupported
} else {
if (!storage_->DeleteObject(CONTAINER->params[0])) {
return_code = 0x2012; // partial deletion
}
}
break;
case 0x100C:  // SendObjectInfo
CONTAINER->params[2] =
SendObjectInfo(CONTAINER->params[0], // storage
CONTAINER->params[1]); // parent
p1 = CONTAINER->params[0];
if (!p1) p1 = 1;
CONTAINER->len = receive_buffer->len = 12 + 3 * 4;
break;
case 0x100D:  // SendObject
SendObject();
break;
case 0x1014:  // GetDevicePropDesc
TRANSMIT(GetDevicePropDesc(CONTAINER->params[0]));
break;
case 0x1015:  // GetDevicePropvalue
TRANSMIT(GetDevicePropValue(CONTAINER->params[0]));
break;
default:
return_code = 0x2005;  // operation not supported
break;
}
} else {
return_code = 0x2000;  // undefined
}
}
if (return_code) {
CONTAINER->type = 3;
CONTAINER->op = return_code;
CONTAINER->params[0] = p1;
PrintPacket(receive_buffer);
usb_tx(MTP_TX_ENDPOINT, receive_buffer);
receive_buffer = 0;
} else {
usb_free(receive_buffer);
}
}
// Maybe put event handling inside mtp_yield()?
if ((receive_buffer = usb_rx(MTP_EVENT_ENDPOINT))) {
usb_free(receive_buffer);
}
}
};

#endif

# data[].attributes.body
https://github.com/yoonghm/Teensy_3.x

# data[].attributes.attachment_link.description
Contribute to Teensy_3.x development by creating an account on GitHub.

# data[].attributes.body
#include <Adafruit_SSD1306.h>

#define OLED_DC     9
#define OLED_CS     2
#define OLED_RESET 14

Adafruit_SSD1306 display(OLED_DC, OLED_RESET, OLED_CS);

void setup() {

display.begin(SSD1306_SWITCHCAPVCC); // Generate high voltage internally
display.setTextColor(WHITE); // White text
}

void loop() {
display.clearDisplay(); // clear internal display buffer
display.setCursor(0,0);
display.print("Hello");
display.display();
delay(1000);
}

# data[].attributes.body
#include <TimeLib.h>
time_t    t;

time_t getTeensy3Time() { return Teensy3Clock.get(); }

void setup() {
setSyncProvider(getTeensy3Time);
Serial.begin(19200);
}
// A function to print date in "dd-mm-yyyy " format
void printDate() {
char DateStr[16]; // Buffer to store date string
snprintf(DateStr, 16, "%02d-%02d-%04d ", day(t), month(t), year(t));
Serial.print(DateStr);
}
// A function to print time in hh:mm:ss format with a new line
void printTime() {
char TimeStr[16]; // Buffer to store time string
snprintf(TimeStr, 16, "%02d:%02d:%02d", hour(t), minute(t), second(t));
Serial.println(TimeStr);
}

void loop() {
t = now(); // Get the time now
printDate(); printTime(); delay(1000);
}