From 7ef1a57a17cdb9ebd3d98c85ebb47cd03a536cd6 Mon Sep 17 00:00:00 2001 From: redfast00 Date: Sat, 28 Aug 2021 03:13:01 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + .gitmodules | 6 + CMakeLists.txt | 12 ++ LICENSE | 21 ++++ cpp-httplib | 1 + main/CMakeLists.txt | 23 ++++ main/interpreter_config.h | 12 ++ main/log.h | 56 +++++++++ main/main.cpp | 242 ++++++++++++++++++++++++++++++++++++++ rpi_ws281x | 1 + test.lua | 3 + 11 files changed, 378 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 160000 cpp-httplib create mode 100644 main/CMakeLists.txt create mode 100644 main/interpreter_config.h create mode 100644 main/log.h create mode 100644 main/main.cpp create mode 160000 rpi_ws281x create mode 100644 test.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3ec4893 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "rpi_ws281x"] + path = rpi_ws281x + url = https://github.com/jgarff/rpi_ws281x.git +[submodule "cpp-httplib"] + path = cpp-httplib + url = https://github.com/yhirose/cpp-httplib.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2d394a9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.7) + +project( SharingLeds ) + +add_subdirectory(lua-5.4.3) + +SET(BUILD_TEST OFF CACHE BOOL "") +SET(BUILD_SHARED OFF CACHE BOOL "") +add_subdirectory(rpi_ws281x) + +add_subdirectory(cpp-httplib) +add_subdirectory(main) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..50f76e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Zeus WPI + +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. \ No newline at end of file diff --git a/cpp-httplib b/cpp-httplib new file mode 160000 index 0000000..9648f95 --- /dev/null +++ b/cpp-httplib @@ -0,0 +1 @@ +Subproject commit 9648f950f5a8a41d18833cf4a85f5821b1bcac54 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..d768805 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.7) + +project( Main ) + +add_compile_options(-W -Wall -Wno-missing-field-initializers) + +set (SOURCES "main.cpp") + +source_group("src" FILES ${SOURCES}) + +add_executable(server + ${SOURCES} + ) + +target_include_directories ( ws2811 PUBLIC "${PROJECT_SOURCE_DIR}/../rpi_ws281x") +target_link_libraries(server PUBLIC LuaLib ) +target_link_libraries(server PUBLIC ws2811 ) + +target_link_libraries(server PUBLIC httplib::httplib) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) +target_link_libraries(server PRIVATE Threads::Threads) \ No newline at end of file diff --git a/main/interpreter_config.h b/main/interpreter_config.h new file mode 100644 index 0000000..a6b8f80 --- /dev/null +++ b/main/interpreter_config.h @@ -0,0 +1,12 @@ +#include +#include "log.h" + +using namespace std; +struct InterpreterConfig { + unsigned int begin; + unsigned int length; + bool enabled; + std::string scriptkey; // To change the script in this part of the interpreter and get logger output + std::string persistkey; // To persist the current running script to disk + Log logger; +}; \ No newline at end of file diff --git a/main/log.h b/main/log.h new file mode 100644 index 0000000..bbbed02 --- /dev/null +++ b/main/log.h @@ -0,0 +1,56 @@ +#include +#include +#include + +#ifndef _LOG_ +#define _LOG_ + +class Log { +public: + Log() {} + Log(unsigned int s) {size = s;} + ~Log() { + } + template + Log& operator<<(const T &msg) { + std::stringstream current_string; + current_string << msg; + os << msg; + + if (current_string.str().back() == '\n') { + std::cout << "LOG: " << os.str(); + if (current >= size || full) { + if (ctr >= size) { + ctr = 0; + } + buffer[ctr] = os.str(); + full = true; + } else { + buffer.push_back(os.str()); + } + ctr++; + current++; + os.str(""); + } + return *this; + } + std::vector getLogs() { + std::vector result; + int iterator_limit = std::min(size, current); + for (int i = iterator_limit - 1; i >= 0; i--) { + result.emplace_back(buffer[(current - 1 - i) % size]); + } + return result; + } + +private: + unsigned int current = 0; + unsigned int ctr = 0; + bool full = false; + std::vector buffer; + std::stringstream os; + unsigned int size = 255; + bool last_char_is_newline = true; +}; + +#endif \ No newline at end of file diff --git a/main/main.cpp b/main/main.cpp new file mode 100644 index 0000000..f97d6ae --- /dev/null +++ b/main/main.cpp @@ -0,0 +1,242 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lua.hpp" +#include "ws2811.h" +#include "interpreter_config.h" +#include "log.h" + +#include "httplib.h" + + +#define TARGET_FREQ WS2811_TARGET_FREQ +#define GPIO_PIN 18 +#define DMA 10 +#define STRIP_TYPE WS2811_STRIP_GBR // WS2812/SK6812RGB integrated chip+leds +#define LED_COUNT 690 + +#define FPS 15 + +int frametime = 1000 / FPS; + +std::unordered_map statemap; +std::atomic framecounter = 0; + +ws2811_t ledstring = +{ + .freq = TARGET_FREQ, + .dmanum = DMA, + .channel = + { + [0] = + { + .gpionum = GPIO_PIN, + .invert = 0, + .count = LED_COUNT, + .strip_type = WS2811_STRIP_GBR, + .brightness = 255, + }, + [1] = + { + .gpionum = 0, + .invert = 0, + .count = 0, + .brightness = 0, + }, + }, +}; + +extern "C" { + static int c_override_print (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int i; + for (i = 1; i <= n; i++) { /* for each argument */ + size_t l; + const char *s = luaL_tolstring(L, i, &l); /* convert it to string */ + if (i > 1) /* not the first element? */ + statemap[L]->logger << '\t'; /* add a tab before it */ + statemap[L]->logger << s; /* print it */ + lua_pop(L, 1); /* pop result */ + } + statemap[L]->logger << '\n'; + return 0; + } + + static int c_led (lua_State *L) { + int virtual_location = luaL_checkinteger(L, 1); + int red = luaL_checkinteger(L, 2); + int green = luaL_checkinteger(L, 3); + int blue = luaL_checkinteger(L, 4); + InterpreterConfig* config = statemap[L]; + + // Lua is one-based, let's keep it consistent and also make our API one-based + if (virtual_location <= 0 || virtual_location > (int) config->length) { + std::ostringstream errstream; + errstream << "setting led " << virtual_location << " of strip with lenght " << config->length << ""; + luaL_argerror(L, 1, errstream.str().c_str()); + return 0; + } else if (red < 0 || red > 0xff) { + std::ostringstream errstream; + errstream << "setting red channel to " << red << " but should be between 0 and 255"; + luaL_argerror(L, 2, errstream.str().c_str()); + return 0; + } else if (green < 0 || green > 0xff) { + std::ostringstream errstream; + errstream << "setting green channel to " << green << " but should be between 0 and 255"; + luaL_argerror(L, 3, errstream.str().c_str()); + return 0; + } else if (blue < 0 || blue > 0xff) { + std::ostringstream errstream; + errstream << "setting blue channel to " << blue << " but should be between 0 and 255"; + luaL_argerror(L, 4, errstream.str().c_str()); + return 0; + } + unsigned int real_location = config->begin + virtual_location - 1; + // TODO remove this debugging line + printf("Setting led %d to %d %d %d\n", real_location, red, green, blue); + ledstring.channel[0].leds[real_location] = (red << 16) | (green << 8)| blue; + return 0; + } + + static int c_ledamount(lua_State *L) { + lua_pushinteger(L, statemap[L]->length); + return 1; + } + + static int c_delay(lua_State *L) { + int millis = luaL_checkinteger (L, 1); + std::this_thread::sleep_for(std::chrono::milliseconds(millis)); + return 0; + } + + static int c_waitframes(lua_State *L) { + int amount = luaL_checkinteger(L, 1); + uint64_t destination = amount + framecounter; + if (amount >= 2 * FPS) { + std::this_thread::sleep_for(std::chrono::milliseconds(1000 * ((amount / FPS) - 1))); + } + while (framecounter <= destination) { + std::this_thread::sleep_for(std::chrono::milliseconds(frametime / 2)); + } + return 0; + } +} + +// Thanks to https://www.stefanmisik.com/post/sandboxing-lua-from-c.html +static void LuaLoadAndUndefine(lua_State* L, lua_CFunction openFunction, const char* moduleName, const char* functions[]) +{ + /* Load the module, the module table gets placed on the top of the stack */ + luaL_requiref(L, moduleName, openFunction, 1); + + /* Undefine the unwanted functions */ + for (int i = 0; functions[i] != NULL; i++) + { + lua_pushnil(L); + lua_setfield(L, -2, functions[i]); + } + + /* Pop the module table */ + lua_pop(L, 1); +} + +lua_State* setup_lua_sandbox(const char* luacode) { + lua_State* L = luaL_newstate(); + L = luaL_newstate(); + if (!L) { + return nullptr; + } + + static const char* remove_base[] = {"assert", + "collectgarbage", "dofile", "getmetatable", "loadfile", "load", + "loadstring", "rawequal", "rawlen", "rawget", "rawset", + "setmetatable", "print", NULL}; + LuaLoadAndUndefine(L, luaopen_base, "_G", remove_base); + static const char* remove_str[] = {"dump", NULL}; + LuaLoadAndUndefine(L, luaopen_string, LUA_STRLIBNAME, remove_str); + + static const char* all_allowed[] = {NULL}; + LuaLoadAndUndefine(L, luaopen_table, LUA_TABLIBNAME, all_allowed); + LuaLoadAndUndefine(L, luaopen_math, LUA_MATHLIBNAME, all_allowed); + + lua_pushcfunction(L, c_led); + lua_setglobal(L, "led"); + lua_pushcfunction(L, c_ledamount); + lua_setglobal(L, "ledamount"); + lua_pushcfunction(L, c_delay); + lua_setglobal(L, "delay"); + lua_pushcfunction(L, c_waitframes); + lua_setglobal(L, "waitframes"); + lua_pushcfunction(L, c_override_print); + lua_setglobal(L, "print"); + + luaL_loadbuffer(L, luacode, strlen(luacode), "script"); + return L; +} + +int execute_lua_sandbox(lua_State* L) { + int ret = lua_pcall(L, 0, 0, 0); + if (ret != 0) { + statemap[L]->logger << "CRASH: " << lua_tostring(L, -1) << '\n'; + lua_close(L); + return 1; + } + lua_close(L); + return 0; +} + +std::thread spawn_lua_tread(const char* luacode, InterpreterConfig* config) { + lua_State* L = setup_lua_sandbox(luacode); + statemap[L] = config; + std::thread t(execute_lua_sandbox, L); + return t; +} + +void signal_callback_handler(int signum) { + (void) signum; + ws2811_fini(&ledstring); + exit(1); +} + + +int main(int argc, char** argv) +{ + InterpreterConfig* config = new InterpreterConfig { + .begin = 0, + .length = 10, + .enabled = true + }; + std::thread t = spawn_lua_tread("print(\"hi\")\nled(0, 0, 0, 0)\ndelay(0)\nled(0, 0, 0, 0)\nprint(\"hi\")", config); + + + t.join(); + httplib::Server svr; + + svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) { + res.set_content("Hello World!", "text/plain"); + }); + + svr.listen("0.0.0.0", 8080); + + ws2811_return_t ret; + if ((ret = ws2811_init(&ledstring)) != WS2811_SUCCESS) + { + fprintf(stderr, "ws2811_init failed: %s\n", ws2811_get_return_t_str(ret)); + return ret; + } + signal(SIGINT, signal_callback_handler); + signal(SIGHUP, signal_callback_handler); + signal(SIGTERM, signal_callback_handler); + + ledstring.channel[0].leds[0] = 0x0000ff00; + + while (true) { + ws2811_render(&ledstring); + std::this_thread::sleep_for(std::chrono::milliseconds(frametime)); + } +} \ No newline at end of file diff --git a/rpi_ws281x b/rpi_ws281x new file mode 160000 index 0000000..7624b78 --- /dev/null +++ b/rpi_ws281x @@ -0,0 +1 @@ +Subproject commit 7624b7828829497bfbbd6b13e7afd1f05593f7c6 diff --git a/test.lua b/test.lua new file mode 100644 index 0000000..1dd465b --- /dev/null +++ b/test.lua @@ -0,0 +1,3 @@ +print("hello world") +led(1, 255, 255, 3) +print("There are " .. tostring(ledamount()) .. " leds")