diff --git a/Documentation/how-to-build.rst b/Documentation/how-to-build.rst index 33a6dc9791f1123b6cfbaec4b3e51e82f77ef1b6..252c1cf2dd424ad7da9648c0272742b32d453531 100644 --- a/Documentation/how-to-build.rst +++ b/Documentation/how-to-build.rst @@ -1,8 +1,8 @@ How To Build ============ If you just want to write MicroPython code for card10, you probably **won't** -need to build the firmware yourself. This page is for people who want to work -on the underlying firmware itself. +need to build the firmware yourself. This page is for people **who want to work +on the underlying firmware itself**. Dependencies ------------ @@ -26,29 +26,55 @@ Dependencies .. code-block:: shell-session dnf install arm-none-eabi-gcc arm-none-eabi-binutils arm-none-eabi-newlib + + - macOS (Note: The card10 firmware team used Linux so far. macOS recommendations here are experimental.) + + You can use `Homebrew`_ to install the required tools. + The version of the Arm crosscompiler tool chain is quite important; with the wrong version, e.g. strip and/or ld might throw strange errors. + + .. code-block:: shell-session + + brew tap px4/px4 + brew install px4/px4/gcc-arm-none-eabi-63 + brew install coreutils - Alternative: Download `ARM's GNU toolchain`_. **TODO** +.. _Homebrew: https://brew.sh/ + * **python3**: For meson and various scripts needed for building. * **meson** (>0.43.0) & **ninja**: Unfortunately most distros only have very old versions of meson in their repositories. Instead, you'll probably save yourself a lot of headaches by installing meson from *pip*. - - Ubuntu / Debian: + - Ubuntu / Debian: .. code-block:: shell-session apt install ninja-build pip3 install --user meson - - Arch (has latest *meson* in the repos): + - Arch (has latest *meson* in the repos): .. code-block:: shell-session pacman -S meson + + - macOS + + .. code-block:: shell-session + + brew install ninja + pip3 install --user meson # see https://mesonbuild.com/Getting-meson.html - you will have to add ~/.local/bin to your PATH. * **python3-crc16**: Install with ``pip3 install --user crc16``. * **python3-pillow**: Python Image Library ``pip3 install --user pillow``. + - Arch + + .. code-block:: shell-session + + pacman -S python-crc16 python-pillow + .. _ARM's GNU toolchain: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads Cloning diff --git a/Documentation/pycardium/os.rst b/Documentation/pycardium/os.rst index 325586631487d80b8658953ad5f7578bdf2ada58..e7e174ca3dc7b27dad5746f6bce86782b76f6bc8 100644 --- a/Documentation/pycardium/os.rst +++ b/Documentation/pycardium/os.rst @@ -2,7 +2,8 @@ ``os`` - OS Functions ===================== -The ``os`` module allows access to a few core functionalities of Epicardium and functions found in CPythons ``os`` module. +The ``os`` module allows access to a few core functionalities of Epicardium and +functions found in CPythons ``os`` module. CPython-Like ------------ @@ -29,8 +30,8 @@ CPython-Like :returns: ``bytes()`` object with ``n`` random bytes. -Epicardium-Specific -------------------- +Card10-Specific +--------------- .. py:function:: exit(ret = None) @@ -52,6 +53,15 @@ Epicardium-Specific :param str name: Path to new app/script/l0dable. :return: This function never returns. It can, however raise an exception. +.. py:function:: read_battery() + + Read the current battery voltage in V. Please keep in mind that battery + voltage behaves exponentially when interpreting this value. + + .. warning:: + + Card10 will hard-shutdown once the voltage drops below 3.4 V + .. py:function:: reset() Reboot card10. diff --git a/Documentation/pycardium/personal_state.rst b/Documentation/pycardium/personal_state.rst index 62355e1a05ccab5eda7e871ffc9bcf654e21ec22..2601a056ecd882bd09b5dadce03811b01631a71d 100644 --- a/Documentation/pycardium/personal_state.rst +++ b/Documentation/pycardium/personal_state.rst @@ -2,7 +2,12 @@ ``personal_state`` - Personal State =================================== -The :py:mod:`personal_state` module allows you to set and get the card10 users personal state from your script. The personal state is displayed on the top-left LED on the bottom of the harmonics board. While the personal state is set the LED can't be controlled by the :py:mod:`leds` module. +The :py:mod:`personal_state` module allows you to set and get the card10 users +`personal state`_ from your script. The personal state is displayed on the +top-left LED on the bottom of the harmonics board. While the personal state is +set the LED can't be controlled by the :py:mod:`leds` module. + +.. _personal state: https://card10.badge.events.ccc.de/ps/ **Example**: @@ -26,8 +31,13 @@ The :py:mod:`personal_state` module allows you to set and get the card10 users p Set the users personal state. - :param int state: ID of the personal state to set. Must be one of :py:data:`personal_state.NO_CONTACT`, :py:data:`personal_state.CHAOS`, :py:data:`personal_state.COMMUNICATION`, :py:data:`personal_state.CAMP`. - :param int persistent: Controls whether the personal state is persistent. A persistent state is not reset when the pycardium application is changed or restarted. In persistent mode the personal state LED is not controllable by the pycardium application. + :param int state: ID of the personal state to set. Must be one of + :py:data:`personal_state.NO_CONTACT`, :py:data:`personal_state.CHAOS`, + :py:data:`personal_state.COMMUNICATION`, :py:data:`personal_state.CAMP`. + :param int persistent: Controls whether the personal state is persistent. A + persistent state is not reset when the pycardium application is changed + or restarted. In persistent mode the personal state LED is not + controllable by the pycardium application. .. py:function:: clear() @@ -40,7 +50,8 @@ The :py:mod:`personal_state` module allows you to set and get the card10 users p Get the users personal state. - :returns: A tuple containing the currently set state and a boolean indicating if it's persistent or not. + :returns: A tuple containing the currently set state and a boolean + indicating if it's persistent or not. .. py:data:: NO_STATE diff --git a/epicardium/ble/filetransfer.c b/epicardium/ble/filetransfer.c index 03181390c573edb90e5be63ede43ed3b0b33b161..5efc3a77a68db51bb6ec6ed194012377faa5e6a4 100644 --- a/epicardium/ble/filetransfer.c +++ b/epicardium/ble/filetransfer.c @@ -33,6 +33,7 @@ #include "hci_vs.h" #include <epicardium.h> +#include "modules/log.h" #include "util/bstream.h" #include "att_api.h" @@ -40,6 +41,7 @@ #include "FreeRTOS.h" #include "crc32.h" +#include <stdlib.h> #include <stdio.h> #include <string.h> #include <stdbool.h> @@ -217,9 +219,11 @@ static void sendCrcResponse( msg, sizeof(answer) - len); len += strlen(msg); - printf("BLE file transfer: %s\n", msg); + LOG_ERR("filetrans", "%s\n", msg); } else { - printf("error message \"%s\" too long\n", msg); + LOG_ERR("filetrans", + "error message \"%s\" too long\n", + msg); } } @@ -227,6 +231,41 @@ static void sendCrcResponse( AttsHandleValueNtf(connId, FILE_TRANS_CENTRAL_RX_VAL_HDL, len, answer); } +/* + * This function splits the path into the folders and the file name and + * creates all the missing folders. + */ +static int bleFileCreateOrOpen(char *filepath) +{ + char *pathEnd; + int pos = 0; + int ret; + + while (true) { + pathEnd = strchr(filepath + pos, '/'); + if (!pathEnd) + return epic_file_open(filepath, "w"); + + pathEnd[0] = '\00'; + pos = pathEnd - filepath + 1; + + if (strlen(filepath)) { + ret = epic_file_stat(filepath, NULL); + if (ret == -ENOENT) { + ret = epic_file_mkdir(filepath); + if (ret) { + LOG_ERR("filetrans", + "mkdir failed: %s, ret: %i\n", + filepath, + ret); + return ret; + } + } + } + pathEnd[0] = '/'; + } +} + static uint8_t bleFileOpen(dmConnId_t connId, uint8_t *pValue, uint16_t len) { char filepath[100]; @@ -238,11 +277,12 @@ static uint8_t bleFileOpen(dmConnId_t connId, uint8_t *pValue, uint16_t len) /* Copy only file path and not type, make sure this is NULL terminated */ strncpy(filepath, (char *)pValue + 1, len - 1); + filepath[len - 1] = 0; if (file_fd != -1) epic_file_close(file_fd); - file_fd = epic_file_open(filepath, "w"); + file_fd = bleFileCreateOrOpen(filepath); if (file_fd < 0) { sendCrcResponse(connId, 'e', 0, NULL, "open failed"); return ATT_ERR_RESOURCES; @@ -305,8 +345,9 @@ static uint8_t handleCentralTX( } else if ( operation != ATT_PDU_EXEC_WRITE_REQ && operation != ATT_PDU_WRITE_CMD) { - printf("operation 0x%x not supported, try normal write\n", - operation); + LOG_ERR("filetrans", + "operation 0x%x not supported, try normal write\n", + operation); return ATT_ERR_INVALID_PDU; } @@ -334,7 +375,7 @@ static uint8_t handleCentralTX( return ATT_SUCCESS; case 'E': - printf("Error was acked"); + LOG_ERR("filetrans", "Error was acked"); return ATT_SUCCESS; default: @@ -365,7 +406,9 @@ static uint8_t writeCallback( connId, handle, operation, offset, len, pValue, pAttr ); default: - printf("unsupported characteristic: %c\n", handle); + LOG_ERR("filetrans", + "unsupported characteristic: %c\n", + handle); return ATT_ERR_HANDLE; } } @@ -377,7 +420,7 @@ static uint8_t readCallback( uint16_t offset, attsAttr_t *pAttr ) { - printf("read callback\n"); + LOG_ERR("filetrans", "read callback\n"); return ATT_SUCCESS; } diff --git a/lib/gfx/gfx.c b/lib/gfx/gfx.c index 72a5d67f0191b78563725255521227396b799c88..55f1debf584f7df765e71015cf2f792628286ab8 100644 --- a/lib/gfx/gfx.c +++ b/lib/gfx/gfx.c @@ -86,17 +86,25 @@ void gfx_puts( Color fg, Color bg ) { + // iterate over the string while (*str) { - gfx_putchar(font, r, x, y, *str, fg, bg); - str++; - - x += font->Width; - if (x >= r->width) { + // if the current position plus the width of the next character + // would bring us outside of the display ... + if ((x + font->Width) > r->width) { + // ... we move down a line before printing the character x = 0; y += font->Height; } + // if the line is outside the display we return if (y >= r->height) return; + + // now print the character + gfx_putchar(font, r, x, y, *str, fg, bg); + str++; + + // move along on the x axis to get the position of the next character + x += font->Width; } } diff --git a/preload/apps/card10_nickname/__init__.py b/preload/apps/card10_nickname/__init__.py index 301b0906d8d92770f949dbbabb989b6156e7b2b0..d38523fbf5d5ffd31b040e77797ede286cf30c4f 100644 --- a/preload/apps/card10_nickname/__init__.py +++ b/preload/apps/card10_nickname/__init__.py @@ -7,7 +7,6 @@ Improvement ideas - fade effekt - led nick writing """ - import utime import display import leds @@ -19,7 +18,7 @@ import os FILENAME = 'nickname.txt' FILENAME_ADV = 'nickname.json' -ANIM_TYPES = ['none', 'led', 'fade'] +ANIM_TYPES = ['none', 'led', 'fade', 'gay'] def render_error(err1, err2): @@ -31,24 +30,70 @@ def render_error(err1, err2): disp.close() +def get_time(): + timestamp = '' + if utime.localtime()[3] < 10: + timestamp = timestamp + '0' + timestamp = timestamp + str(utime.localtime()[3]) + ':' + if utime.localtime()[4] < 10: + timestamp = timestamp + '0' + timestamp = timestamp + str(utime.localtime()[4]) + ':' + if utime.localtime()[5] < 10: + timestamp = timestamp + '0' + timestamp = timestamp + str(utime.localtime()[5]) + return timestamp + + +def toggle_rockets(state): + brightness = 15 + if not state: + brightness = 0 + leds.set_rocket(0, brightness) + leds.set_rocket(1, brightness) + leds.set_rocket(2, brightness) + + def render_nickname(title, sub, fg, bg, fg_sub, bg_sub, main_bg): - anim = 'led' + anim = 0 posy = 30 if sub != '': posy = 18 r = 255 g = 0 b = 0 + r_sub = sub + last_btn_poll = utime.time() - 2 while True: + sleep = 0.5 + if sub == '#time': + r_sub = get_time() dark = 0 - if light_sensor.get_reading() < 30: + if light_sensor.get_reading() < 40: dark = 1 r_fg_color = fg[dark] r_bg_color = bg[dark] r_fg_sub_color = fg_sub[dark] r_bg_sub_color = bg_sub[dark] r_bg = main_bg[dark] - if anim == 'fade': + # Button handling + pressed = buttons.read( + buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT + ) + if utime.time() - last_btn_poll >= 1: + last_btn_poll = utime.time() + if pressed & buttons.BOTTOM_RIGHT != 0: + anim = anim + 1 + if anim >= len(ANIM_TYPES): + anim = 0 + if pressed & buttons.BOTTOM_LEFT != 0: + anim = anim - 1 + if anim < 0: + anim = len(ANIM_TYPES) - 1 + # Animations + if ANIM_TYPES[anim] == 'fade': + sleep = 0.1 + leds.clear() + toggle_rockets(False) if r > 0 and b == 0: r = r - 1 g = g + 1 @@ -59,34 +104,32 @@ def render_nickname(title, sub, fg, bg, fg_sub, bg_sub, main_bg): r = r + 1 b = b - 1 r_bg = [r, g, b] - if anim == 'led': - for i in range(0, 11): - leds.prep(i, r_bg) - leds.update() - leds.dim_top(3) - leds.set_rocket(0, 15) - leds.set_rocket(1, 15) - leds.set_rocket(2, 15) - if anim == 'none': + r_bg_color = r_bg + r_bg_sub_color = r_bg + if ANIM_TYPES[anim] == 'led': + if dark == 1: + for i in range(0, 11): + leds.prep(i, r_bg) + leds.update() + leds.dim_top(4) + toggle_rockets(True) + else: + leds.clear() + toggle_rockets(False) + if ANIM_TYPES[anim] == 'gay': + toggle_rockets(False) + leds.gay(0.4) + if ANIM_TYPES[anim] == 'none': leds.clear() - leds.set_rocket(0, 0) - leds.set_rocket(1, 0) - leds.set_rocket(2, 0) + toggle_rockets(False) with display.open() as disp: disp.rect(0, 0, 160, 80, col=r_bg, filled=True) disp.print(title, fg=r_fg_color, bg=r_bg_color, posx=80 - round(len(title) / 2 * 14), posy=posy) - if sub != '': - disp.print(sub, fg=r_fg_sub_color, bg=r_bg_sub_color, posx=80 - round(len(sub) / 2 * 14), posy=42) + if r_sub != '': + disp.print(r_sub, fg=r_fg_sub_color, bg=r_bg_sub_color, posx=80 - round(len(r_sub) / 2 * 14), posy=42) disp.update() disp.close() - pressed = buttons.read( - buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT - ) - if pressed & buttons.BOTTOM_LEFT != 0: - anim = ANIM_TYPES[1] - if pressed & buttons.BOTTOM_RIGHT != 0: - anim = ANIM_TYPES[0] - utime.sleep(0.3) + utime.sleep(sleep) def get_key(json, key, default): @@ -95,12 +138,10 @@ def get_key(json, key, default): except KeyError: return default - leds.clear() with display.open() as disp: disp.clear().update() disp.close() - if FILENAME_ADV in os.listdir("."): f = open(FILENAME_ADV, 'r') try: diff --git a/preload/apps/card10_nickname/metadata.json b/preload/apps/card10_nickname/metadata.json index db70873d61d8a58c53d656f8e9c593c818fe811e..2323731badeabe7aaa4f58d1a88e0c1eb1a6c979 100644 --- a/preload/apps/card10_nickname/metadata.json +++ b/preload/apps/card10_nickname/metadata.json @@ -1 +1 @@ -{"name":"Card10 Nickname","description":"Nickname app for the card10 badge\r\n\r\nEverything you need can be found here: https:\/\/github.com\/vabene1111\/card10-nickname","category":"graphics","author":"vabene1111","revision":3} \ No newline at end of file +{"name":"Card10 Nickname","description":"Nickname app for the card10 badge\r\n\r\nEverything you need can be found here: https:\/\/github.com\/vabene1111\/card10-nickname","category":"graphics","author":"vabene1111","revision":4} \ No newline at end of file diff --git a/preload/apps/lsd_nickname/__init__.py b/preload/apps/lsd_nickname/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b4d20c38b323e591fa65e7bb8240d187985680ef --- /dev/null +++ b/preload/apps/lsd_nickname/__init__.py @@ -0,0 +1,37 @@ +import display +import leds +import utime + +_rand = 123456789 +def rand(): + global _rand + _rand = (1103515245 * _rand + 12345) & 0xffffff + return _rand + +gs = 160 +colors = [ ((i>>2)*gs, (i>>1&1)*gs, (i&1)*gs) for i in range(1, 8) ] + +nick = 'sample text' +try: + with open('/nickname.txt') as f: + nick = f.read() +except: + pass + +while True: + with display.open() as d: + for k in range(4): + (x1, y1) = (rand()%159, rand()%79) + (x2, y2) = (min(x1+rand()%40, 159), min(y1+rand()%40, 79)) + try: + d.rect(x1, y1, x2, y2, col=colors[rand() % len(colors)], filled=True) + except: + pass + fg = colors[rand()%len(colors)] + nx = 80-round(len(nick)/2 * 14) + d.print(nick, fg=fg, bg=[0xff-c for c in fg], posx=(nx-8)+rand()%16, posy=22+rand()%16) + d.update() + d.close() + leds.set(rand() % 11, colors[rand() % len(colors)]) + leds.set_rocket(rand() % 3, rand() % 32) + utime.sleep_us(1) # Feed watch doge \ No newline at end of file diff --git a/preload/apps/lsd_nickname/metadata.json b/preload/apps/lsd_nickname/metadata.json new file mode 100644 index 0000000000000000000000000000000000000000..5aa83b008c5aa8292e9bf5f3a5e379f3fceb61fc --- /dev/null +++ b/preload/apps/lsd_nickname/metadata.json @@ -0,0 +1 @@ +{"name":"LSD-Nickname","description":"Renderer for nickname.txt, but oh god what the fuck","category":"graphics","author":"polyfloyd","revision":1} \ No newline at end of file diff --git a/pycardium/modules/py/ledfx.py b/pycardium/modules/py/ledfx.py index 1d9e3acace3faa61ef0fdde9171b9b32ab6ef196..ab8076e652a9ec9b87373574b624e7fae47cf544 100644 --- a/pycardium/modules/py/ledfx.py +++ b/pycardium/modules/py/ledfx.py @@ -1,4 +1,6 @@ -import leds, utime, math +import leds +import math +import utime def col_cor(colors, brightness=1, gamma=1): @@ -12,13 +14,16 @@ def col_cor(colors, brightness=1, gamma=1): def halo(colors): - """ - sets the four bottom/side LEDs to colors corresponding to the color spectrum on the outermost of the top 11 LEDs + """ + Set the four bottom/side LEDs to colors corresponding to the color spectrum + on the outermost of the top 11 LEDs. """ used_leds = len(colors) - #add additional RGB-Color-lists to the colors-list to fill up the top LEDs with emptiness + + # add additional RGB-Color-lists to the colors-list to fill up the top LEDs with emptiness colors += [[0, 0, 0]] * (11 - used_leds) - #add four additional colors. the last one, the first one twice, the last one. + + # add four additional colors. the last one, the first one twice, the last one. colors += [colors[used_leds - 1]] + [colors[0]] * 2 + [colors[used_leds - 1]] return colors @@ -30,51 +35,61 @@ def kitt( minimum=0.3, rgb=[255, 0, 0], spectrum=[], - halo=False, ): """ - LED Animation. Knight rider-Style. - :param cycles: amount of cycles for the animation - :param delay: time in microseconds until the animation moves on. (we could also call it framerate) - :param power: the shape of your brightness curve. bigger values make a steeper curve, smaller values make the curve wider. - :param minimum: the minimal brightness - :param rgb: if you don't enter a spectrum this is the color we'll use - :param specttrum: a color spectrum consisting of up to 11 RGB-Value-Lists (e.g. [[255,255,255], [0,0,0], [255,255,255] and so on] - ). if you use less, the animation will be less wide. + LED Animation. Knight rider-Style. + + :param int cycles: Amount of cycles for the animation + :param int delay: Time in microseconds until the animation moves on (Inverse of Framerate). + :param int power: Shape of your brightness curve. Bigger values make a + steeper curve, smaller values make the curve wider. + :param float minimum: Minimal brightness. + :param [r,g,b] rgb: If you don't enter a spectrum this is the color used. + :param list spectrum: A color spectrum consisting of up to 11 RGB-Value-Lists + (e.g. ``[[255,255,255], [0,0,0], [255,255,255], ...]`` ). If you use + less, the animation will be less wide. + :param func halo: Halo function. See :py:func:`ledfx.halo`. """ - - # create a basic table of values for a smooth increment of the LED brightness (if you don't understand this, don't worry, i don't either. just paste it into the python shell and see the output). Basically creates a negative cosinus curve. + + # create a basic table of values for a smooth increment of the LED + # brightness (if you don't understand this, don't worry, i don't either. + # just paste it into the python shell and see the output). Basically + # creates a negative cosinus curve. kitt_table = [((-math.cos(math.pi * (x / 10.0))) + 1) / 2.0 for x in range(21)] - #adjust the values to start with a minimum brightness and the width of the curve to the given power. + + # adjust the values to start with a minimum brightness and the width of the + # curve to the given power. kitt_table = [math.pow(x, power) * (1 - minimum) + minimum for x in kitt_table] - #for the amount of specified cycles + # for the amount of specified cycles for i in range(cycles): - #repeat every 20 steps + # repeat every 20 steps j = i % 20 - #and go backwards after 10 steps + # and go backwards after 10 steps if j > 10: j = 20 - j - #if a color spectrum wasn't given + if spectrum == []: - #set the amount of LEDs used to 11, because we're using the full width used_leds = 11 - #set the color values to the LEDs by multiplying the given color value with the corresponding brightness value in the kitt table + + # set the color values to the LEDs by multiplying the given color + # value with the corresponding brightness value in the kitt table output = [[int(x * y) for y in rgb] for x in kitt_table[j : (j + used_leds)]] else: - #use the amount of leds specified in the spectrum used_leds = len(spectrum) - #multiply the color values in the corresponding spectrum tuple with the brightness value in the kitt table + + # multiply the color values in the corresponding spectrum tuple + # with the brightness value in the kitt table output = [ [int(y * kitt_table[j + x]) for y in spectrum[x]] for x in range(used_leds) ] - #if a halo is True, also use the four bottom LEDs + if halo: halo(output) - #set the LEDs to the output defined above + leds.set_all(output) - #sleep for the amount of milliseconds specified in delay utime.sleep_ms(delay) - #Switch off all LEDs. + leds.clear() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..73516b14481fc30a0516fad07a866ec007db3a8c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +meson~=0.51.1 +crc16~=0.1.1 +pillow~=6.1.0