Papp-Murmelbahn

Ideen Sammlung für Murmelbahnen aus Pappe.

anlässlich des Make Your School Maker Festivals 2025

danke an alle die Mitgemacht haben!
es war uns eine große Freude 🙂

Bilder

elektronische Spielereien

nun eine Info-Sammlung für die elektronischen Spielereien.

Material (HW)

Code Beispiele

der Controller *MakerPi RP2040* wird mit circuitpython programmiert.
Beispielcodes für die verschiedenen Elemente finden dich in diesem Repository: https://github.com/s-light/murmelbahn-electronic-fun

als editor kann ich momentan MU empfehlen.
https://codewith.mu/

Umhang / Radmantel

es ist schon lange her (August 2017) das ich meinen Mantel für den Hüter des Lichts genäht habe.
(damals gab es die Idee des Hüters des Lichtes auch noch nicht..)
und leider habe ich dies wohl auch vergessen hier zu dokumentieren. das hole ich nun nach:

als Inspiration hatte ich diese Anleitung:
https://lilonomecon.wordpress.com/2014/05/11/anleitung-larp-wollmantel/

ich habe mich für einen Mantel mit einem Umfang von 280° entschieden:

dies ist das *Schnitt-Muster* welches ich dafür erstellt habe.
hier die FreeCAD Datei sowie SVG

Der Mantel besteht aus drei Teilen:
1x Kreis-Stück mit 140°. 2x Kreis-Stücke mit jeweils 70°

Diese Aufteilung ermöglicht Symmetrische Nähte und in den Nähte kann auf Höher der Arme ein Loch gelassen werden.
Dadurch bleiben auch bei vorne geschlossenen Mantel die Armen frei verfügbar.

Übertragen der Formen auf den Stoff

dafür habe ich mir einen riesigen Zirkel aus einem Stift/SchneiderKreide und einem dünnen Seil gebaut.

Nähen

ale *außen* Nähte habe ich ordentlich doppelt umgeschlagen bzw. *Jeans-Nähte* für die Stöße der Teil-Kreise verwendet.

Abmessungen

meine Seitenlänge von Hals/Schulter Kante über die Schulter an der Hüfte vorbei bis zum Knöchel sind ~150cm. Dies passt sehr gut zu dem hier gewählten Radius von 1,44m.
Es ergab sich einfach durch die Verfügbare Stoff-Lauf-Breite.

Länger wäre hinderlich gewesen beim Laufen…
(kann ich nun aus Erfahrung sagen..)
die umlaufende Borte ist aus Nachtblauem 100%-Baumwolle Samt den ich in Frankfurt bei Martino Stoffe gefunden habe. (map)

allerdings war das Annähen hier eine echte Herausforderung.. hier ist viel Geduld gefragt!

Kapuze

ist aus einem der Reststücke Entstanden. ich glaube ich hab dieses Sogar einfach so wie es war verwendet.
Diese ist allerdings durch den dünnen Leinen Stoff sehr unpraktikabel.. also dient aktuell nur als Optisches Design-Element..

Verschluss

hier habe ich anfangs einen simplen kleinen Karabiner verwendet.
eher unpassend. doch war halt da.

Dann hatte ich das große Gück auf einem Markt einen Schmied zu treffen der mit den Kids *Hufeisen* geschmiededt hat..
mit diesem zusammen habe ich dann einen eigenen kleinen Verschluss gebastelt 🙂 passt sehr zum restlichen creativen-DIY Charakter 🙂

Kinder Version

nun hat sich meine Tochter auch einen Umhang gewünscht.

also haben wir in Hameln ganz spontan im Stoffe-Paradies (map) ein feines Stück weißes Leinen mitgenommen 🙂
schön das es noch Stoff-Läden gibt!!

Vorbereitung

als erstes legen wir fest für welche Körper-Größe der Mantel funktionieren soll.
hierfür ist es hilfreich wenn dieser nur bis zu den Knöcheln geht – sonst wird er erstens sehr schnell sehr schmutzig und – viel wichtiger – die Wahrscheinlichkeit das der Tragende selbst sich verfängt erhöht sich stark.

bei mir ist die Seitenlänge von Hals/Schulter Kante über die Schulter an der Hüfte vorbei bis zum Knöchel ~150cm.
(Körpergröße ~1,82m)
Hier haben die 144cm Radius sehr gut gepasst.

bei einer gemessenen Seitenlänge von 76cm wären dass dann ca 70cm Radius.
hier werde ich – damit der Mantel hält ihn länger lassen und dann unten nach innen Umschlagen und mit einfachen Stichen heften.

eine Borte bekommt er erst mal nicht. auch diese kann dann später als eine weitere Verlängerung dienen..

ich habe mich für ~80cm Radius entschieden.
und – da die Stoffbreite bei diesem Radius es einfach machte –
auch alles in einem Stück gelassen – und den gesamt-Mantel auf 310° erweitert. damit dürfte er sich vorne etwas leichter schließen lassen und beim drehen noch schöner fliegen 😉
und es passte einfach gut auf den Stoff 😉

den Halsausschnitt-Radius habe ich von 15cm auf 10cm angepasst.
mal sehen wie das passt – größer schneiden geht ja dann immer noch..

so sieht es dann auf dem Stoff aus:

hab dabei noch den Halsausschnitt erst mal auf 5cm Radius verkleinert – wahrscheinlich reicht das schon – und es soll ja definitiv auf den Schultern bleiben..

alles weitere dann hier ergänzt wenn ich es umgesetzt habe 🙂

quasar devSever with own certificate

i just tried to get a localhost certificate to work on my setup-
and after some try and error i got this to work:

using minica to generate a cert:
create folder .certs in your project root

mkdir .certs

navigating inside

yourProject $ cd .certs
yourProject/.certs $ 

create a cert:

yourProject/.certs $  minica -domain localhost

import the root ca minica.pem in firefox or your default browsers..

then add in your quasar.conf

// ....
const fs = require("fs");
// ....
module.exports = configure(function (/* ctx */) {
    return {
// ....
        // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
        devServer: {
            open: false, // opens browser window automatically
            // https: true, // for automagically self-signed cert.
            https: {
                key:  path.join(__dirname, ".certs/localhost/key.pem"),
                cert: path.join(__dirname, ".certs/localhost/cert.pem"),
            },
        },
// ....
});

and that indeed works 🙂

sadly if you are search the web you first will land at
https://quasar.dev/quasar-cli-webpack/quasar-config-file#devserver
(scroll down a bit)
as there is an example of the webpack config.
and in the vite doc there is currently none for this setup..

rename.py

some days ago my brother asked me if i can help him batch rename about 200 pdf files..
the needed name for the file was the heading found on the first page…

so i did a quick and hacky script and experiment with PdfReader library –
it worked really nicely and was only about 2h in learning and getting it to work as it should 😉

#!/usr/bin/env python3

import os
import re

from pathlib import Path

from pypdf import PdfReader

from operator import itemgetter, attrgetter

print(42 * "*")
print("running script rename.py")
print(42 * "*")
print()



# extract ids and title
regex_title = re.compile(
    r"Some Fixed Pre Text \(LM\),\s*(?P<id1>\d+-\d+)\s*(?P<title>.*?)\s*\((?P<id2>\s*G(\d+\s*)+)\)",
    re.IGNORECASE,
)


def parse_file(filename):
    print(42 * "-")
    print(f"reading file '{filename}'")
    reader = PdfReader(filename)
    page = reader.pages[0]

    # print(42*'-')
    # print(f"extracting text from page 0:")
    # print(page.extract_text(extraction_mode="layout"))
    # print(42*'-')

    parts = []

    def visitor_body(text, cm, tm, font_dict, font_size):
        # get top part
        y = cm[5]
        if 600 < y < 1020:
            parts.append(text)

    page.extract_text(visitor_text=visitor_body)
    text_body = "".join(parts)
    # text_body = text_body.replace('\n', ' ').replace('\r', ''
    text_body = " ".join(text_body.splitlines())
    # print(f"extracting text from page 0 with filter:")
    # print(42*'-')
    # print(text_body)
    # print(42*'-')
    # now we have on continus line.
    # let us get all the parts we need with some regex magic:
    regex_result = regex_title.search(text_body)
    # print(42 * "-")
    if regex_result:
        # print(regex_result.groupdict())
        result = regex_result.groupdict()
        result["title"] = result["title"].replace('/', '-')
        result["id2"] = result["id2"].replace(' ', '')
    else:
        result = {"text_body":text_body}
        
    # print(42 * "-")
    return result


def get_filelist():
    p = Path('.')
    filelist = list(p.glob('**/*.pdf'))
    return filelist

def main():
    files = get_filelist()

    results = []

    for file in files:
        result_dict = parse_file(file)
        result_dict["filename"] = file
        # result_dict["birth"] = file.stat().st_birthtime_ns
        result_dict["birth"] = file.stat().st_mtime
        results.append(result_dict)

    # we now have a list of dicts for each file with the extracted title and ids
    # we first sort it.
    # results.sort(key=itemgetter('id1', 'title', 'id2', 'birth'))
    results.sort(key=itemgetter('id1', 'title', 'id2'))

    for result in results:
        # print(result)
        title = result["title"]
        id1 = result["id1"]
        id2 = result["id2"]
        birth = result["birth"]
        filename_old = result["filename"].resolve()
        # create new base filename_new
        stem_new = f"{id1} - {title} - {id2}"
        # print(f"stem_new: '{stem_new}'")
        # modifie the stem part
        filename_new = filename_old.with_stem(stem_new)
        print(filename_new)
        # extend if this one already exists...
        while filename_new.exists():
            filename_new = filename_new.with_stem(filename_new.stem + " - 1")
        filename_old.rename(filename_new)

main()

maybe its of help for others…

maybe just as personal memo.

logging in docker + php

just a quick personal reminder:

hot to log php things inside of docker…

error_log("My Error Message... '" . $somevariable . "'", 0);

Pizza V2

neues Rezept 🙂


2 Bleche, 500g Mehle

– Buchweizenmehl 180g
– Reisvolkornmehl 180g
– Maisstärke 100g
– Braunhirsemehl 40g
– Wasser 360ml
– 1Tüte Weinstein-Backpulver
– eine Gute Priese gemahlene Flohsamenschalen (Pulver)

Umluft 190°C
auf unterster Schiene 15-20min

funny plastic material…

did you know there is a plasict material that melts at about 60°C ?
the official name / main ingredient is
Polycaprolacton (PCL) (wikipedia: de en)
product names i found:

the challeng is to get a good water bath in the right temperature.
so for this i reused my HotPlate SMD soldering hardware.
created a profile that just heats to 60°C and waits…

i will add some pictures and experiment results here in some days 😉



Arduino Light-Barrier with TSOP4438

you just want to build a Light-Barrier (DE: Lichtschranke) ?

for example to measure speed? Or Count goals on a Table-Top-Football?

then you are at the right place.

We use a TSOP4438 as Receiver and some 5mm IR-LED as Sender.

the code

IR_TSOP4438_light_barrier_withDebounce/ir_light_barrier.ino

// based on
// Example of modulating a 38 KHz carrier frequency at 500 Hz with a variable duty cycle
// Author: Nick Gammon
// Date: 24 September 2012
// https://forum.arduino.cc/t/how-to-create-a-38-khz-pulse-with-arduino-using-timer-or-pwm/100217/44

// tweaked for TSOP4438
// https://www.vishay.com/docs/82459/tsop48.pdf
// find max burst length and min gap times:
//
// Minimum burst length 10 cycles/burst
// After each burst of length 10 to 40 cycles
//  a minimum gap time is required of ≥ 10 cycles
// Maximum number of continuous short bursts/second: 1500
//
// translates to:
// 38kHz = 26,3 us / Pulse → *10= 263us
// max burst length: <= 1052us  (26,3*40)
// min gap   length: >=  263us  (26,3*10)
// total cycle time of (1052+263=) 1315us 
// that violates the max bursts/second (666us/burst)
//
// on option is to optimize for the most bursts/second:
// so we use the min gap time as given and use the rest of the available time.
// 666us - 263us = 403 us burst length
// hopefully this way the AGC does not filter our stream...
// we now have to fit this to the best available prescaler / counter values:
// 672us fits good (this way we have less than the 1500 burst)
// this translates to 42 counts a' (0,0625us*256=) 16us
// so we use a gap length of 16us*17 = 272us 
// this gives us a burst length of 16us*(42-17)= 400us
//
// second option is to optimize for the longest burst length and have less bursts/second.
// here we will use a in-between leaning towards longer bursts:
// 0,0625us*256=16us
// 16us*50counts =  800us = 0,80ms = 1250,00Hz = 1250bursts/s
// 16us*80counts = 1280us = 1,28ms =  781,25Hz = 781,25bursts/s
// gap   length: 16us*17      =  272us 
// burst length: 16us*(80-17) = 1008us

// http://www.gammon.com.au/forum/?id=11504
// Timer 1
//   OC1A: D9
//   OC1B: D10
const byte LED = 9;

// Timer 2 (8bit)
//   OC2A: D11
//   OC2B: D3


// Clock frequency divided by 38 kHz frequency desired
const long timer1_OCR1A_Setting = F_CPU / 38000L;
// (16000000 / 38000) = 421,05
// this only works on Timer 1 - as it is a 16bit timer.

// ------------------------------------------
// in-between -  leaning for longer bursts
// target counts:
// CPU          16MHz (0,0625us)
// prescaler    256
// target       781kHz (1280us = 1,28ms)
const long timer2_top = (F_CPU / 256L) / 781L;
// (16000000 / 256) / 781 = 80
// calculate on / off ratio (toggle point)
const long timer2_compare = timer2_top * 1008L / 1280L;
// 80 * 1008 / 1280   =  80 - 17  =  63



volatile bool sender_active = false;

ISR (TIMER2_COMPA_vect) {
    // used to combine the two timers...
    if (sender_active == false) {
        // enable timer1 output
        TCCR1A |= bit(COM1A0) ;  // Toggle OC1A on Compare Match
        // digitalWrite (LED_BUILTIN, HIGH);
        sender_active = true;
    } else {
        sender_active = false;
        // disable timer1 output
        TCCR1A &= ~bit(COM1A0) ;  // DO NOT Toggle OC1A on Compare Match
        digitalWrite (LED, LOW);  // ensure off
        // digitalWrite (LED_BUILTIN, LOW);
    }
}

void lightBarrierSender_setup() {
    pinMode(LED, OUTPUT);
    digitalWrite(LED, LOW);
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, LOW);

    // set up Timer 1 - gives us 38.095 KHz
    TCCR1A = bit (COM1A0); // toggle OC1A on compare
    TCCR1B = _BV(WGM12) | _BV (CS10);   // CTC to OCR1A, No prescaler
    OCR1A =  (16000000L / 38000L / 2) - 1;  // zero relative

    // setup Timer 2
    TCCR2A = 0;
    TCCR2B = 0;
    // toggle OC2A on compare
    // TCCR2A |= bit(COM2A0); 
    // fast pwm to OCR2A
    TCCR2A |= bit(WGM21) | bit(WGM20);
    TCCR2B |= bit(WGM22); 
    // prescaler 1024
    // TCCR2B |= bit(CS22) | bit(CS21) | bit(CS20);
    // prescaler 265
    TCCR2B |= bit(CS22) | bit(CS21);
    // top
    OCR2A = timer2_top - 1;  // zero relative
    // switch point
    OCR2B = timer2_compare - 1;  // zero relative
    // enable interrupts
    TIMSK2 = bit(OCIE2A);
    // TIMSK2 = bit(OCIE2B) | bit(OCIE2A);
}

/IR_TSOP4438_light_barrier_withDebounce/IR_TSOP4438_light_barrier_withDebounce.ino

// simple light barrier test


unsigned long debounceDuration = 10;

const byte beam1Pin = 2;
bool beam1State = LOW;
bool beam1StateLast = LOW;
unsigned long beam1Timestamp = 0;

const byte beam2Pin = 3;
bool beam2State = LOW;
bool beam2StateLast = LOW;
unsigned long beam2Timestamp = 0;



void setup(){ 
    Serial.begin(115200);
    Serial.println("IR_TSOP4438_light_barrier");
    Serial.println("setup...");
    
    pinMode(beam1Pin, INPUT);
    pinMode(beam2Pin, INPUT);

    lightBarrierSender_setup();
    
    Serial.println("running.");
}

void loop() {
    beam1_check();
    beam2_check();
}


void beam1_check() {
    bool beam1Current = digitalRead(beam1Pin);

    if(beam1Current != beam1StateLast) {
        beam1Timestamp = millis();
    }

    if ((millis() - beam1Timestamp) > debounceDuration) {
        // egal welcher wert - dieser ist länger als debounceDuration da!

        // check for state change
        if(beam1Current != beam1State) {
            beam1State = beam1Current;
            if(beam1State) {
                Serial.println("beam1 brock...");
            }
        }
    }

    beam1StateLast = beam1Current;
}

void beam2_check() {
    bool beam2Current = digitalRead(beam2Pin);

    if(beam2Current != beam2StateLast) {
        beam2Timestamp = millis();
    }

    if ((millis() - beam2Timestamp) > debounceDuration) {
        // egal welcher wert - dieser ist länger als debounceDuration da!

        // check for state change
        if(beam2Current != beam2State) {
            beam2State = beam2Current;
            if(beam2State) {
                Serial.println("beam2 brock...");
            }
        }
    }

    beam2StateLast = beam2Current;
}

or just download this arduino sketchbook:

deep dive into the details

To get the TSOP4438 to work we need to send a 38kHz Modulated Signal from the LED. the catch: the receiver is designed to ignore CONTINUOUS signals – that is the way to also reject all the Disturbance from surrounding things like Fluorescent Lamps or other things…

therefore we need to modulate again the 38kHz signal:
so that there are times the signal is send and breaks where the beam is off. The timing requirements for this Pattern are described in the Datasheet :

Minimum burst length 10 cycles/burst

After each burst of length 10 to 40 cycles
a minimum gap time is required of ≥ 10 cycles

For bursts greater than  40 cycles
a minimum gap time in the data stream is needed of  > 10 x burst length

Maximum number of continuous short bursts/second 1500

To Be continued…

Research:

  • https://www.vishay.com/docs/82459/tsop48.pdf#page=6&zoom=250,-155,381
  • https://forum.arduino.cc/t/how-to-create-a-38-khz-pulse-with-arduino-using-timer-or-pwm/100217/12
  • https://forum.arduino.cc/t/how-to-create-a-38-khz-pulse-with-arduino-using-timer-or-pwm/100217/64
  • http://www.gammon.com.au/forum/?id=11504&reply=6#reply6
  • http://www.gammon.com.au/forum/bbshowpost.php?id=11504&page=2
  • http://www.gammon.com.au/images/Arduino/Timer_2.png
  • http://www.gammon.com.au/images/Arduino/Timer_1.png
  • https://arduino.stackexchange.com/questions/31187/irremote-send-and-receive-same-arduino
  • http://www.righto.com/2010/03/detecting-ir-beam-break-with-arduino-ir.html?showComment=1447463463512#c570784324410264988