Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[apps/external] Add python external app support #266

Draft
wants to merge 1 commit into
base: upsilon-dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/external/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ ifdef HOME_DISPLAY_EXTERNALS
app_external_src = $(addprefix apps/external/,\
extapp_api.cpp \
archive.cpp \
execution_environment.cpp \
)

$(eval $(call depends_on_image,apps/home/controller.cpp,apps/external/external_icon.png))
Expand All @@ -18,6 +19,7 @@ app_external_src = $(addprefix apps/external/,\
archive.cpp \
main_controller.cpp \
pointer_text_table_cell.cpp \
execution_environment.cpp \
)

$(eval $(call depends_on_image,apps/external/app.cpp,apps/external/external_icon.png))
Expand Down
56 changes: 56 additions & 0 deletions apps/external/archive.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "archive.h"
#include "extapp_api.h"
#include "../global_preferences.h"
#include "execution_environment.h"
#include <python/port/port.h>

#include <string.h>
#include <stdlib.h>
Expand Down Expand Up @@ -101,6 +103,12 @@ extern "C" void (* const apiPointers[])(void);
typedef uint32_t (*entrypoint)(const uint32_t, const void *, void *, const uint32_t);

uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) {
uint32_t pythonResult = executePython(name, heap, heapSize);
// If the python file was executed, we're done.
if (pythonResult != 1) {
return pythonResult;
}
// Else, execute the native external app.
File entry;
if(fileAtIndex(indexFromName(name), entry)) {
if(!entry.isExecutable) {
Expand Down Expand Up @@ -133,6 +141,12 @@ bool fileAtIndex(size_t index, File &entry) {
extern "C" void extapp_main(void);

uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) {
uint32_t pythonResult = executePython(name, heap, heapSize);
// If the python file was executed, we're done.
if (pythonResult != 1) {
return pythonResult;
}
// Else, execute the native external app.
extapp_main();
return 0;
}
Expand Down Expand Up @@ -196,5 +210,47 @@ size_t numberOfExecutables() {
return final_count;
}

// Private function to check if a file is a Python script and if so, execute it.
uint32_t executePython(const char *name, void * heap, const uint32_t heapSize) {
// Check if the file exists
File entry;
if (!fileAtIndex(indexFromName(name), entry)) {
return 1;
}

// Get the extension without using strrchr
const char *ext = entry.name + strlen(entry.name) - 3;
if (ext == NULL) {
return 1;
}

// Check if it is a Python script
if (strcmp(ext, ".py") != 0) {
return 1;
}

// Clear the screen, exclude the top bar
// Prevent global-buffer-overflow when using Metric::TitleBarHeight
// KDRect rect(0, Metric::TitleBarHeight, 320, 240);
KDRect rect(0, 0, 320, 240);
Ion::Display::pushRectUniform(rect, KDColor::RGB16(0xFFFF));
// On simulator, we need to refresh the screen to see the changes
#ifndef DEVICE
Ion::Keyboard::scan();
#endif


uint32_t heapSizeReal = 16536;
// TODO: Use a namespace
// Clear the heap to avoid memory errors
memset(heap, 0, heapSizeReal);
// Initialize the Python interpreter
ExternalExecutionEnvironment env = init_environnement(heap, heapSizeReal);
bool result = execute_input(env, (const char *)entry.name);
deinit_environment();

return result ? 0 : 3;
}

}
}
1 change: 1 addition & 0 deletions apps/external/archive.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ size_t numberOfFiles();
size_t numberOfExecutables();
bool executableAtIndex(size_t index, File &entry);
uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize);
uint32_t executePython(const char *name, void * heap, const uint32_t heapSize);

}
}
Expand Down
36 changes: 36 additions & 0 deletions apps/external/execution_environment.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include "execution_environment.h"
#include <apps/home/app.h>
#include <string.h>

void ExternalExecutionEnvironment::printText(const char * text, size_t length) {
// TODO: Store the text somewhere
}

// TODO: Use a namespace

bool execute_input(ExternalExecutionEnvironment env, const char * filename) {
// Use the following syntax to execute a Python script:
// exec(open("filename").read())
const char * pythonCodeStart = "exec(open(\"";
const char * pythonCodeEnd = "\").read())";
// Initialize the buffer to store the python code (size = 50)
char pythonCode[50];
// Add the start of the python code in the buffer (until the file name)
strlcpy(pythonCode, pythonCodeStart, sizeof(pythonCode));
// Add the file name in the buffer
strlcpy(pythonCode + strlen(pythonCode), filename, sizeof(pythonCode) - strlen(pythonCode));
// Add the end of the python code in the buffer
strlcpy(pythonCode + strlen(pythonCode), pythonCodeEnd, sizeof(pythonCode) - strlen(pythonCode));
// Execute the python code
bool executionResult = env.runCode(pythonCode);
return executionResult;
}

ExternalExecutionEnvironment init_environnement(void * heap, const uint32_t heapSize) {
MicroPython::init((uint32_t *)heap, (uint32_t *)heap + heapSize);
return ExternalExecutionEnvironment();
}

void deinit_environment() {
MicroPython::deinit();
}
17 changes: 17 additions & 0 deletions apps/external/execution_environment.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <python/port/port.h>
#include <apps/code/app.h>

class ExternalExecutionEnvironment : public MicroPython::ExecutionEnvironment {
public:
ExternalExecutionEnvironment() : m_printTextIndex(0) {}
void printText(const char * text, size_t length) override;
const char * lastPrintedText() const { return m_printTextBuffer; }
private:
static constexpr size_t k_maxPrintedTextSize = 256;
char m_printTextBuffer[k_maxPrintedTextSize];
size_t m_printTextIndex;
};

ExternalExecutionEnvironment init_environnement(void * heap, const uint32_t heapSize);
void deinit_environment();
bool execute_input(ExternalExecutionEnvironment env, const char * filename);
145 changes: 108 additions & 37 deletions python/port/mod/ion/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ extern "C" {
#include <algorithm>
#include <string.h>
#include <ion/storage.h>
#include <apps/external/archive.h>

STATIC void file_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
STATIC mp_obj_t file_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args);
Expand Down Expand Up @@ -199,10 +200,16 @@ typedef struct _file_obj_t {
file_bin_t binary_mode;

Ion::Storage::Record record;

size_t position;

bool closed;

/// Index, for external files.
int index;

/// True for the ram fs, False for the flash fs (external)
bool filesystem;

} file_obj_t;

Expand Down Expand Up @@ -417,13 +424,25 @@ STATIC mp_obj_t file_make_new(const mp_obj_type_t *type, size_t n_args, size_t n

Ion::Storage::Record::ErrorStatus status;

bool isExternalRecord = External::Archive::indexFromName(file_name) != -1;

switch(file->open_mode) {
case READ:
file->record = Ion::Storage::sharedStorage()->recordNamed(file_name);
file->position = 0;
// File not found in RAM file system
if (file->record == Ion::Storage::Record()) {
mp_raise_OSError(2);
// If the file is not found in the external archive, raise an error
if (!isExternalRecord) {
// File not found in external archive
mp_raise_OSError(ENOENT);
break;
}
// File found in external archive
file->index = External::Archive::indexFromName(file_name);
break;
}
file->filesystem = true;
break;
case CREATE:
file->position = 0;
Expand Down Expand Up @@ -499,6 +518,11 @@ STATIC mp_obj_t file_make_new(const mp_obj_type_t *type, size_t n_args, size_t n
}

if (file->edit_mode) {
// Error read only file on external archive
if (!file->filesystem) {
mp_raise_OSError(30);
// TODO: Return ?
}
file_mode[1] = '+';
offset = 1;
}
Expand Down Expand Up @@ -529,7 +553,7 @@ void check_closed(file_obj_t* file) {
const char* file_name = mp_obj_str_get_data(file->name, &l);
file->record = Ion::Storage::sharedStorage()->recordNamed(file_name);

if (file->record == Ion::Storage::Record()) {
if (file->record == Ion::Storage::Record() && file->filesystem) {
mp_raise_OSError(2);
}
}
Expand Down Expand Up @@ -765,45 +789,92 @@ STATIC mp_obj_t file_writelines(mp_obj_t o_in, mp_obj_t o_lines) {
* Simpler read function used by read and readline.
*/
STATIC mp_obj_t __file_read_backend(file_obj_t* file, mp_int_t size, bool with_line_sep) {
size_t file_size = file->record.value().size;
size_t start = file->position;

// Handle seek pos > file size
// And size = 0
if (start >= file_size || size == 0) {
// If the file system is the RAM filesystem, we can directly read the data.
if (file->filesystem) {
size_t file_size = file->record.value().size;
size_t start = file->position;

// Handle seek pos > file size
// And size = 0
if (start >= file_size || size == 0) {
return mp_const_none;
}

size_t end = 0;

// size == 0 handled earlier.
if (size < 0) {
end = file_size;
} else {
end = std::min(file_size, file->position + size);
}

// Handle line separator case.
// Always use \n, because simpler.
if (with_line_sep) {
for(size_t i = start; i < end; i++) {
if (*((uint8_t*)(file->record.value().buffer) + i) == '\n') {
end = i + 1;
break;
}
}
}

file->position = end;


// Return different type based on mode.
if (file->binary_mode == TEXT)
return mp_obj_new_str((const char*)file->record.value().buffer + start, end - start);
if (file->binary_mode == BINARY)
return mp_obj_new_bytes((const byte*)file->record.value().buffer + start, end - start);
return mp_const_none;
}

size_t end = 0;

// size == 0 handled earlier.
if (size < 0) {
end = file_size;
} else {
end = std::min(file_size, file->position + size);
}

// Handle line separator case.
// Always use \n, because simpler.
if (with_line_sep) {
for(size_t i = start; i < end; i++) {
if (*((uint8_t*)(file->record.value().buffer) + i) == '\n') {
end = i + 1;
break;
else {
// Read the data from the external archive
// Get the file using fileAtIndex
External::Archive::File file_archive;
External::Archive::fileAtIndex(file->index, file_archive);
size_t file_size = file_archive.dataLength;
size_t start = file->position;

// Handle seek pos > file size
// And size = 0
if (start >= file_size || size == 0) {
return mp_const_none;
}

size_t end = 0;

// size == 0 handled earlier.
if (size < 0) {
end = file_size;
} else {
end = std::min(file_size, file->position + size);
}

// Handle line separator case.
// Always use \n, because simpler.
if (with_line_sep) {
for(size_t i = start; i < end; i++) {
if (*((uint8_t*)(file_archive.data) + i) == '\n') {
end = i + 1;
break;
}
}
}

file->position = end;


// Return different type based on mode.
if (file->binary_mode == TEXT)
return mp_obj_new_str((const char*)file_archive.data + start, end - start);
if (file->binary_mode == BINARY)
return mp_obj_new_bytes((const byte*)file_archive.data + start, end - start);
return mp_const_none;

}

file->position = end;


// Return different type based on mode.
if (file->binary_mode == TEXT)
return mp_obj_new_str((const char*)file->record.value().buffer + start, end - start);
if (file->binary_mode == BINARY)
return mp_obj_new_bytes((const byte*)file->record.value().buffer + start, end - start);

return mp_const_none;
}

STATIC mp_obj_t file_read(size_t n_args, const mp_obj_t* args) {
Expand Down
20 changes: 16 additions & 4 deletions python/port/mod/kandinsky/modkandinsky.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,13 @@ mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t * args) {
KDColor textColor = (n_args >= 4) ? MicroPython::Color::Parse(args[3]) : Palette::PrimaryText;
KDColor backgroundColor = (n_args >= 5) ? MicroPython::Color::Parse(args[4]) : Palette::HomeBackground;
const KDFont * font = (n_args >= 6) ? ((mp_obj_is_true(args[5])) ? KDFont::SmallFont : KDFont::LargeFont) : KDFont::LargeFont;
MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox();
KDIonContext::sharedContext()->drawString(text, point, font, textColor, backgroundColor);
// Get tbe context and draw the string.
KDIonContext * ctx = KDIonContext::sharedContext();
ctx->setClippingRect(KDRect(0, 0, 320, 240));
ctx->setOrigin(KDPoint(0, 0));
ctx->drawString(text, point, font, KDColor::RGB16(textColor), KDColor::RGB16(backgroundColor));
// MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox();
// KDIonContext::sharedContext()->drawString(text, point, font, textColor, backgroundColor);
return mp_const_none;
}

Expand Down Expand Up @@ -111,8 +116,15 @@ mp_obj_t modkandinsky_fill_rect(size_t n_args, const mp_obj_t * args) {
}
KDRect rect(x, y, width, height);
KDColor color = MicroPython::Color::Parse(args[4]);
MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox();
KDIonContext::sharedContext()->fillRect(rect, color);
// TODO: Get if the actual application is Python or not.
// If it isn't, we need to call directly the Ion fillRect function.
// If it is, we call the displaySandbox function, which will hide the console
// and switch to another mode.
// KDIonContext::sharedContext()->fillRect(rect, color);
Ion::Display::pushRectUniform(rect, color);
// KDColor color = KDColorBlue;
// MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox();
// KDIonContext::sharedContext()->fillRect(rect, color);
return mp_const_none;
}

Expand Down