Tag Archives: arduino

New afvalwijzer using Home Assistant

New version of :

New schematic using ESPHOME and has reset button (pressing the lid).
(Resets the LED when you’ve put the trash at the street curb.)

Above, a simplified version. I’m using 4 ws2812 leds now.

ESPHOME code:

esphome:
  name: afvalwemos
  friendly_name: AfvalWemos
  on_boot:
    priority: 800
    then:
    - delay: 1s
    - light.turn_off: main_leds

esp8266:
  board: d1_mini

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "bT76oR8vOxxxxxxxxxxxxxxxxxxxxxxxxQbyjY6M="

ota:
  - platform: esphome
    password: "c1dbxxxxxxxxxxxxxxxxxxxxxxxxxx36e75"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Afvalwemos Fallback Hotspot"
    password: "vVxxxxxxxxxxxxxxaw"

captive_portal:

# LED strip definition
light:
  - platform: neopixelbus
    type: RGB
    variant: WS2812
    pin: D5
    num_leds: 4
    name: "All LEDs"
    id: main_leds
    restore_mode: ALWAYS_OFF   # ensures all LEDs start off


  # Individual LED partitions
  - platform: partition
    name: "LED 1"
    segments:
      - id: main_leds
        from: 0
        to: 0

  - platform: partition
    name: "LED 2"
    segments:
      - id: main_leds
        from: 1
        to: 1

  - platform: partition
    name: "LED 3"
    segments:
      - id: main_leds
        from: 2
        to: 2

  - platform: partition
    name: "LED 4"
    segments:
      - id: main_leds
        from: 3
        to: 3


# Physical button on D6 to turn off all LEDs
binary_sensor:
  - platform: gpio
    pin:
      number: D6
      mode: INPUT_PULLUP
      inverted: True
    name: "All Off Button"
    on_press:
      - logger.log: "Button pressed — turning all LEDs OFF"
      - light.turn_off: main_leds

Home Assistant automation:

alias: Afvalwijzer Plastic
description: Afvalwijzer leds
triggers:
  - at: "18:00:00"
    trigger: time
conditions:
  - condition: template
    value_template: >
      {% set raw = states('sensor.gad_pmd') %} {% if raw not in ['unknown',
      'unavailable', 'none', ''] %}
        {% set clean = raw.replace('Tomorrow, ', '').replace('Today, ', '').strip() %}
        {% set parts = clean.split(', ') %}
        {% set date_part = parts[-1] if parts|length > 1 else clean %}
        {% set pmd_date = strptime(date_part, '%d-%m-%Y').date() %}
        {% set tomorrow = (now().date() + timedelta(days=1)) %}
        {{ pmd_date == tomorrow }}
      {% else %}
        false
      {% endif %}
actions:
  - action: light.turn_on
    target:
      entity_id: light.afvalwemos_led_3
    data:
      rgb_color:
        - 236
        - 199
        - 14
mode: single

Easy cheap touch light

Remember those expensive touch lights you can buy?

This is a less than 5 euro version.

Warning : Some tricks I used

  • Using D5 as GND (I didn’t want to splice GND Wire.)
  • Using PWM for dimming
  • Using 5V led, use resistor if using a generic LED (220ohm)

TTP223 sensors are only a few cents!
And react even without touching (< 5mm)
So, you can build this in a case or behind fabric!

NOTE: If you are using an ESP32 you can configure a pin as touch!!!
No TTP223 needed.
But ESP32 are more expensive as Wemos mini.

Code:

#define TOUCH_PIN D2 // My video has D7
#define LED_PIN   D6
#define FAKE_GND D5

// Brightness steps (0 = off, 255 = full bright)
int brightnessLevels[] = {0, 25, 125, 255};
int currentLevel = 0;
bool lastTouchState = LOW;

void setup() {
  
  pinMode(TOUCH_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  pinMode(FAKE_GND, OUTPUT);

  digitalWrite(FAKE_GND, LOW);  
  analogWrite(LED_PIN, brightnessLevels[currentLevel]);  // start OFF
}

void loop() {
  bool touchState = digitalRead(TOUCH_PIN);

    if (touchState == HIGH && lastTouchState == LOW) {
      // Advance brightness step
      currentLevel = (currentLevel + 1) % 4;  // 0-3 steps
      int pwmValue = brightnessLevels[currentLevel];
      analogWrite(LED_PIN, pwmValue);
    }

  lastTouchState = touchState;
}

Dim levels less obvious on recording, but you can change the levels in the code!

Three display C64 audio monitor

While I made this for my Commodore C64, it is applicable for many things.

It started with some cheap displays from Ali, and some leftover Wemos D1 from my Pressure Lab project.

I Started measuring the audio output from sound devices and from my C64.
I soon discovered that I needed some way to get the offset and amplification correct for the analogue input of a Wemos. (0-3v3)

So a little op-amp circuit was born, but not without some struggles.
I forgot many things about amplifiers. It was one of the first school books I got rid of. (Sorry mister Rafaela)

After searching the internet and posting a question on Reddit I ended up with the following.

R1 and R2 are 100M. The potentiometer P1 allows me to set the offset.
R3 is 1M
C1 is 100nF to decouple the audio signal from the RCA.

R4 is 47K and C2 is 330nF (thanks tycho205)
Cimportant=1/(2πfR2)
where f is the lowest frequency of interest. In this case Cimportant should be about 330nF

LM324 is a quad amplifier, leftover from another project.
Note, the SINGLE RAIL power.

P2 potentiometer is 2M (leftover) and gives me a variable amplifying opportunity.


A = Audio input

B = Setting the offset with P1

C = Setting the amplification

Below input signal (note negative values) above amplified signal with offset!

The displays are 3 Wemos controllers with a cheap I2C display.
These are just fast enough to do FFT.

Analogue in is the output from the OP-amp offsetter ..

CODE

Needs cleaning up, and a better stabilize routine.

ESPHome HA notification helper

A while ago I made a little notify thingy using an LCD display, LED, button and a buzzer.

Some friends of mine made one also, and today I was talking to a new guy.
I could not find this project on my site .. again .. so I’ll post it now.

Some things it does right now

  • Front door opens
  • Door bell rings (because my lab is in the garden)
  • Girlfriend is 10Km away, so let’s start cooking
  • Garage door opens (friend of mine uses this to know when kids arrive)
  • More .. because it’s very easy to customize in Home Assistant

Doorbell pressed: Led starts blinking, backlight LCD enabled, text displayed on LCD, Buzzer sounds (or plays a RTTTL ringtone)
LCD backlight on and buzzer beep until acknowledge button pressed.

Heating for brewing: temperature on display, led on when temperature reached. Press acknowledge to start timer.

….. etc

CODE:

esphome:
  name: lcdalarm
  friendly_name: LCDAlarm
  on_boot:
    priority: -100.0
    then:
      - lambda: 'id(lcddisplay).no_backlight();'

esp8266:
  board: d1_mini

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "OOpDzMu0VvfWAnJXXXXXXXXXXXXXXXXXXXXXXXXKNDa74OJ1Cc="

ota:
   - platform: esphome
     password: "627992017XXXXXXXXXXXXXXX738c2a3"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Lcdalarm Fallback Hotspot"
    password: "z9dZXXXXXXXX7a"

captive_portal:
web_server:
  port: 80

i2c:
  sda: D2
  scl: D1
  scan: true
  frequency: 400kHz

light:
  - platform: binary
    name: "Status LED"
    output: light_output
    icon: "mdi:led-on"
    id: led


binary_sensor:
  - platform: gpio
    pin:
      inverted: true
      number: D5
      mode:
        input: true
        pullup: true
    name: "LCD Button"

text_sensor:
  # Create text input helper in homeassistant and enter id below
  - platform: homeassistant
    entity_id: input_text.lcd_display_text
    id: lcd_display_text

  - platform: wifi_info
    ip_address:
      id: ip_address
      name: IP address
    mac_address:
      id: mac_address
      name: Mac address
    ssid:
      id: connected_ssid
      name: Network SSID
    bssid:
      id: connected_
      name: Network Mac

sensor:
  - platform: wifi_signal
    id: wifisignal
    name: wifi-signal
    update_interval: 300s

globals:
  - id: backlight_on
    type: bool
    initial_value: 'false'
  - id: flash_beep_on
    type: bool
    initial_value: 'false'

switch:
  - platform: template
    name: "LCD Backlight"
    id: backlight
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - globals.set:
          id: backlight_on
          value: 'true'
      - globals.set:
          id: flash_beep_on
          value: 'false'
      - lambda: |-
          id(lcddisplay).backlight();
      - delay: 5s
    turn_off_action:
      - globals.set:
          id: backlight_on
          value: 'false'
      - lambda: |-
            id(lcddisplay).no_backlight();
    lambda: |-
      return id(backlight_on);

  - platform: template
    name: "Flash and Beep"
    id: flash_beep
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - globals.set:
          id: flash_beep_on
          value: 'true'
      - globals.set:
          id: backlight_on
          value: 'false'
      - lambda: |-
          id(lcddisplay).backlight();
      - lambda: |-
          id(buzzer).turn_on();
      - delay: 0.75s
      - lambda: |-
          id(lcddisplay).no_backlight();
      - lambda: |-
          id(buzzer).turn_off();
      - delay: 0.75s
      - lambda: |-
          id(lcddisplay).backlight();
      - lambda: |-
          id(buzzer).turn_on();
      - delay: 0.75s
      - lambda: |-
          id(lcddisplay).no_backlight();
      - lambda: |-
          id(buzzer).turn_off();
      - delay: 0.75s
      - lambda: |-
          id(lcddisplay).backlight();
      - lambda: |-
          id(buzzer).turn_on();
      - delay: 0.75s
      - lambda: |-
          id(lcddisplay).no_backlight();
      - lambda: |-
          id(buzzer).turn_off();
      - delay: 0.75s
      - lambda: |-
          id(lcddisplay).backlight();
    turn_off_action:
      - globals.set:
          id: flash_beep_on
          value: 'false'
      - lambda: |-
            id(lcddisplay).no_backlight();
      - lambda: |-
          id(buzzer).turn_off();
    lambda: |-
      return id(flash_beep_on);


  - platform: restart
    name: "Restart ESPhome"

output:
  - platform: gpio
    pin: D3
    id: buzzer
  - platform: gpio
    pin: D6
    id: light_output

display:
  - platform: lcd_pcf8574
    id: lcddisplay
    dimensions: 16x2
    address: 0x27
    lambda: |-
      if(id(wifisignal).has_state()) {
        if (id(lcd_display_text).state != "") {
          it.print(0, 0, id(lcd_display_text).state.c_str());
        }
      } else {
        it.print("    ESPhome        booting...   ");
      }

CODE: RTTTL (Use passive buzzer!!!!)

esphome:
  name: lablcd
  friendly_name: LabLCD

esp8266:
  board: d1_mini

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "ga17mNCtxdiXXXXXXXXXXXXXXXXXXXXX70a5W0VPZR51Q="
  actions:
    - action: rtttl_play
      variables:
        song_str: string
      then:
        - rtttl.play:
            rtttl: !lambda 'return song_str;'

ota:
  - platform: esphome
    password: "84013b9XXXXXXXXXXXXXXX5401039f7c"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Lablcd Fallback Hotspot"
    password: "nnzXXXXXXXXXXXXXXXXXXXCb"

rtttl:
  output: rtttl_out
  on_finished_playback:
    - logger.log: 'Song ended!'


captive_portal:

i2c:
  sda: D2
  scl: D1
  scan: true
  frequency: 400kHz

light:
  - platform: status_led
    name: "Status LED"
    icon: "mdi:led-on"
    pin:
      number: D5
      inverted: true

binary_sensor:
  - platform: gpio
    name: "Push Button"
    pin: 
      number: D4
      inverted: true
      mode: 
        input: true
        pullup: true
    on_press:
      - homeassistant.service:
          service: homeassistant.toggle
          data:
            entity_id: input_boolean.esphome_notification_lcdlab_push_button

text_sensor:
  # Create text input helper in homeassistant and enter id below
  - platform: homeassistant
    entity_id: input_text.esphome_notification_lcdlcd_display_text
    id: lcd_display_text

  - platform: wifi_info
    ip_address:
      id: ip_address
      name: IP address
    mac_address:
      id: mac_address
      name: Mac address
    ssid:
      id: connected_ssid
      name: Network SSID
    bssid:
      id: connected_
      name: Network Mac

sensor:
  - platform: wifi_signal
    id: wifisignal
    name: wifi-signal
    update_interval: 300s

globals:
  - id: backlight_on
    type: bool
    initial_value: 'false'
  - id: flash_beep_on
    type: bool
    initial_value: 'false'

switch:
  - platform: template
    name: "LCD Backlight Lab"
    id: backlight
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - globals.set:
          id: backlight_on
          value: 'true'
      - globals.set:
          id: flash_beep_on
          value: 'false'
      - lambda: |-
          id(lcddisplay).backlight();
    turn_off_action:
      - globals.set:
          id: backlight_on
          value: 'false'
      - lambda: |-
            id(lcddisplay).no_backlight();
    lambda: |-
      return id(backlight_on);

 
  - platform: restart
    name: "Restart ESPhome"

output:
  - platform: esp8266_pwm
    pin: D8
    id: rtttl_out

display:
  - platform: lcd_pcf8574
    id: lcddisplay
    dimensions: 16x2
    address: 0x27
    lambda: |-
      if(id(wifisignal).has_state()) {
        if (id(lcd_display_text).state != "") {
          it.print(0, 0, id(lcd_display_text).state.c_str());
        }
      } else {
        it.print("    ESPhome        booting...   ");
      }


Weather display using LilyGO T5-4.7 inch S3 E-paper E-ink ESP32-S3

I was planning to make a RSS reader using this display, but I came across a weather display project I wanted to check out.
(So I probably end up buying another one)

There are many questions and issues around this project using the S3.

So here is my solution.

Install vscode

Goto plugins and install platformio

git clone https://github.com/Xinyuan-LilyGO/LilyGo-EPD-4-7-OWM-Weather-Display.git

Warning this is NOT the branch you want to use

git checkout web

(git pull)

open directory in code

open platformio.ini and change line 13
default_envs = T5_4_7Inc_Plus_V2
(If needed add upload_port = /dev/tty**** at the end)

Change INO file lines 144-146.
Comment serial out, else update won’t work using regular power or battery.



When saving this platformio.ini file, some downloading and installing should be popping up.
When issues occur about libraries see below.

Fill out
data>config.json
and owm_credentials.h to be sure.
(use owm_credentials information to fill config.json)

Next press the platformio icon

Fix for uploading:

Press and hold STR_IO0 button
Press REST button
Release STR_IO0 button

Libraries:

Press platformio icon, libaries and install ArduinoJson, Button2 and LilyGo-EPD47 (select your project while doing so!)

Note: Per default once per hour update, change if you want to.
Line 70 in the INO file

Build/Upload errors? .. Press clean to recompile using a clean state !

Wireless ping tester with beeps using Wemos

While I’ve used a Laptop with a ping script I made in the past, I needed something more portable.

So I build:

  • Scan for wifi networks
  • Connect
  • Enter IP to ping
  • Buzzer beeps when ICMP packet received
  • Gateway not reachable ? Sound alarm note

This way I can use both hands, hanging upside-down in a hard-to-reach place, without turning my head to a screen or my phone.

CODE (Below code is for D6)

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
#include <ESP8266Ping.h>

ESP8266WebServer server(80);
String pingHost = "";
bool startPinging = false;
unsigned long lastPingTime = 0;
bool gpioState = false;

void setup() {
  Serial.begin(115200);
  pinMode(D6, OUTPUT);  

  // WiFiManager captive portal
  WiFiManager wm;
  if (!wm.autoConnect("ESP_SetupAP")) {
    Serial.println("Failed to connect, restarting...");
    ESP.restart();
  }

  Serial.println("WiFi connected. IP:");
  Serial.println(WiFi.localIP());

  // Web UI
  server.on("/", HTTP_GET, []() {
    String html = "<html><body><h2>ESP Continuous Ping</h2>"
                  "<form action='/start'>"
                  "Host/IP to ping: <input name='host' type='text'>"
                  "<input type='submit' value='Start Pinging'>"
                  "</form></body></html>";
    server.send(200, "text/html", html);
  });

  server.on("/start", HTTP_GET, []() {
    if (!server.hasArg("host")) {
      server.send(400, "text/plain", "Missing 'host' parameter.");
      return;
    }
    pingHost = server.arg("host");
    startPinging = true;
    server.send(200, "text/plain", "Started pinging " + pingHost);
  });

  server.begin();
}

void loop() {
  server.handleClient();

  if (startPinging && millis() - lastPingTime > 2000) {
    lastPingTime = millis();
    bool success = Ping.ping(pingHost.c_str(), 1);

    if (success) {
      gpioState = !gpioState;
      digitalWrite(D6, HIGH);
      delay(500);
      digitalWrite(D6, LOW);
      delay(500);
      Serial.println("Ping success, toggled D6.");
    } else {
      Serial.println("Ping failed.");
      digitalWrite(D6, LOW);
    }
  }
}

Further ideas

D2 pin because easier soldering

New project : Find nearest booze shop.

Or anything else. ( idea from a reddit post )

UPDATE: 20250523

Combining a GPS module, compass, a LED ring and some code, I want to make a little device which shows you the way to the nearest … something.

To make it completely standalone, I have to use a SIM module. (Same as I have used before) This POC will use my phone as hotspot.

The LED ring will show the direction to go.

Edit: Maybe not a LED ring but a little display.

As previously posted, I was playing with Overpass turbo.
Using an API, I can use code to query this.

  • Arduino sends latitude, longitude to my webserver
  • Webserver queries API for neastest POIs and calculates distance.
  • Send data from webserver to arduino
  • Arduino uses heading data to light up direction LED
    (also on secondary display with distance info?)
    edit: and shop info

Test code for my web server to query the data

import overpy
import math

api = overpy.Overpass()

# This location will be filled with data from GPS module on Arduino.
latitude = 52.2270745        # Center latitude (e.g. Berlin)
longitude = 5.177519      # Center longitude
box_size = 0.05         # Box size in degrees (about ~5 km)

south = latitude - box_size
north = latitude + box_size
west = longitude - box_size
east = longitude + box_size

def haversine(lat1, lon1, lat2, lon2):
    R = 6371  # Earth radius in km
    phi1 = math.radians(lat1)
    phi2 = math.radians(lat2)
    d_phi = math.radians(lat2 - lat1)
    d_lambda = math.radians(lon2 - lon1)

    a = math.sin(d_phi / 2)**2 + math.cos(phi1) * math.cos(phi2) * math.sin(d_lambda / 2)**2
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))

    return R * c  # Distance in kilometers

# Calculate bearing in degrees (0-360)
def bearing(lat1, lon1, lat2, lon2):
    phi1 = math.radians(lat1)
    phi2 = math.radians(lat2)
    delta_lon = math.radians(lon2 - lon1)

    x = math.sin(delta_lon) * math.cos(phi2)
    y = math.cos(phi1) * math.sin(phi2) - math.sin(phi1) * math.cos(phi2) * math.cos(delta_lon)

    initial_bearing = math.atan2(x, y)
    compass_bearing = (math.degrees(initial_bearing) + 360) % 360  # Normalize to 0–360

    return compass_bearing

# Overpass QL query
query = f"""
[out:json];
node
  ["shop"="alcohol"]
  ({south}, {west}, {north}, {east});
out body;
>;
out skel qt;
"""

try:
    result = api.query(query)

    # Collect and sort places by distance
    places = []
    for node in result.nodes:
        node_lat = float(node.lat)
        node_lon = float(node.lon)
        distance = haversine(latitude, longitude, node_lat, node_lon)
        direction = bearing(latitude, longitude, node_lat, node_lon)
        name = node.tags.get("name", "Unnamed")
        places.append((distance, direction, name, node_lat, node_lon))

    places.sort()

    print(f"Found {len(places)} alcohol-related places sorted by distance:")
    for dist, dir_deg, name, lat, lon in places:
        print(f"- {name} at ({lat:.5f}, {lon:.5f}) — {dist:.2f} km, {dir_deg:.0f}°")

except Exception as e:
    print(f"Error: {e}")

Output:

Found 10 alcohol-related places sorted by distance:
- The Skiff at (52.22583, 5.17860) — 0.16 km, 152°
- Onzewijnen at (52.22612, 5.17045) — 0.49 km, 258°
- Gall & Gall at (52.23244, 5.19204) — 1.15 km, 59°
- Gall & Gall at (52.21536, 5.16735) — 1.48 km, 208°
- Eric's Beer Craft at (52.21549, 5.16632) — 1.50 km, 211°
- Slijterij at (52.21082, 5.15692) — 2.29 km, 218°
- Gall & Gall at (52.21590, 5.14074) — 2.80 km, 244°
- Gall & Gall at (52.25422, 5.22705) — 4.53 km, 48°
- Gall & Gall at (52.26808, 5.18348) — 4.58 km, 5°
- Il DiVino at (52.27507, 5.16414) — 5.41 km, 350°

Example using Overpass Turbo to find breweries

Other ideas

  • Geocaching (Thanks Vincent)
  • Find each other at festivals?

UPDATE

Building the hardware : First design

Screen programming (First setup)

Some test code

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_GC9A01A.h"

// Overrule stuff
#define TFT_CS 18   // Chip select
#define TFT_DC 5    // Data/command mode
#define TFT_BL 4    // Backlight control
#define TFT_MOSI 12 // SPI Out AKA SDA
#define TFT_SCLK 13 // Clock out AKA SCL 
#define TFT_MISO -1 // pin not used
#define TFT_RST 23  // Reset ################# IMPORTANT, won't work without!! Took me a hour!

// Need this changed from example also
Adafruit_GC9A01A tft(TFT_CS, TFT_DC,TFT_MOSI,TFT_SCLK,TFT_RST,TFT_MISO);

float angle = 0;

void setup() {
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(GC9A01A_BLACK);
  drawCompassFace();
}

void loop() {
  drawNeedle(angle, GC9A01A_RED);
  delay(1000);
  drawNeedle(angle, GC9A01A_BLACK);  // Erase previous needle
  angle += 15;
  if (angle >= 360) angle = 0;

  tft.setCursor(60, 100);
  tft.setTextColor(GC9A01A_WHITE);  tft.setTextSize(2);
  tft.println("230 Meters");

}

// Draw static compass face
void drawCompassFace() {
  int cx = tft.width() / 2;
  int cy = tft.height() / 2;
  int radius = 100;

  tft.drawCircle(cx, cy, radius, GC9A01A_WHITE);
  tft.setTextColor(GC9A01A_WHITE);
  tft.setTextSize(1);
  tft.setCursor(cx - 3, cy - radius + 5); tft.print("N");
  tft.setCursor(cx - 3, cy + radius - 10); tft.print("S");
  tft.setCursor(cx - radius + 5, cy - 3); tft.print("W");
  tft.setCursor(cx + radius - 10, cy - 3); tft.print("E");
}

// Draw compass needle
void drawNeedle(float angleDeg, uint16_t color) {
  int cx = tft.width() / 2;
  int cy = tft.height() / 2;
  float angleRad = angleDeg * DEG_TO_RAD;
  int x = cx + cos(angleRad) * 90;
  int y = cy + sin(angleRad) * 90;
  tft.drawLine(cx, cy, x, y, color);
}

Found a rogue AP in my house

Where is it, what device could it be?

Its SSID started with ESP. So I probably am the one responsible for its existence.
I’ve got a sh*tload of ESPs/NodeMCUs/8266 turned on 24-7.

Using a Wifi analizer I could narrow it down to my livingroom.
Checked all devices, and they are all connected to my AccessPoint.
(So no fallback AP mode)

The problem with this method is that you can’t figure out a direction.

So I used this on my Laptop.

See graphs on the left

This is a directional antenna.

Using Wireshark and wavemon, I could find the direction.

There were only two devices in the direction with the strongest signal.
My photo viewer remote, and my mini turntable controller with RFID.

But these devices are working just fine! .. So lets disconnect the power.
So it IS the mini recordplayer!

Lets look at the code. (part of)

WiFi.mode(WIFI_AP_STA); 

I should have used

WiFi.mode(WIFI_STA); 

Now it was a client AND an Access Point!

Mystery solved!

Reverse engineering Epaper Arduino for own image pusher

To display quotes, changing once per hour.

There is not much to be found for Waveshare 4.2 Epaper.
Except for an Arduino web example.
( see https://www.waveshare.com/wiki/E-Paper_ESP32_Driver_Board )

I reversed engineered the workings, and created a python upload script to push images.

Original workings are a mess.
Per 4 bit of color, high-low switched in a byte.
Black and red separated.
Using a till p encoding over curl commands.

My implementation uses a python script called as:

python3 epaper-pusher.py ~/Downloads/Untitled.png
http://10.1.0.99/EPDI_
30 times something like 
http://10.1.0.99/ppppppppppppppppppppppppppppppppppppppppppppppppppppppaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppiodaLOAD_
http://10.1.0.99/NEXT_
30 times something like
http://10.1.0.99/pbcdefghijjjjjjffffffoooooooaaabbbbbbeeeedddppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppiodaLOAD_
http://10.1.0.99/SHOW_
NOTES:
a = 0000
-
-
-
p = 1111 = 15

30 lines with 1000 bytes ( ending with iodaLOAD_ )

black pixels
first block 1
second block 0

red pixels
first block 0
second block 1

white pixels
first block 1
second block 1

PIXEL Example
RBRB
BWBW

First block 
1010 - letter K
0101 - Letter F - second nibble = white

Second block
0101 - Letter F
1111 - Letter P - second nibble white

Code

from PIL import Image
import numpy
import requests

url="http://10.1.0.99/" 

black_pixels = numpy.zeros((400,300))
red_pixels = numpy.zeros((400,300))



def classify_pixel_color(pixel):
    """
    Classify a pixel as black, white, or red.
    """
    r, g, b = pixel[:3]  # Ignore alpha if present

    # Define thresholds for classification
    if r < 128 and g < 128 and b < 128:
        return 'black'
    elif r > 200 and g > 200 and b > 200:
        return 'white'
    elif r > 128 and g < 100 and b < 100:
        return 'red'
    else:
        return None

def process_image(image_path):
    """
    Process the image and classify its pixels into black, white, or red.
    """
    image = Image.open(image_path)
    image = image.convert("RGB")  # Ensure the image is in RGB mode

    width, height = image.size
    pixel_data = image.load()

    color_counts = {'black': 0, 'white': 0, 'red': 0}

    for y in range (0, 299):
        for x in range (0, 399):
            black_pixels[x][y] = 0
            red_pixels[x][y] = 0

    for y in range(299):
        for x in range(399):
            color = classify_pixel_color(pixel_data[x, y])
            if color:
                color_counts[color] += 1
                if color == 'black':
                    black_pixels[x][y] = 1;
                if color == 'red':
                    red_pixels[x][y] = 1;
                if color == 'white':
                    black_pixels[x][y] = 1;
                    red_pixels[x][y] = 1;

    return color_counts, black_pixels, red_pixels

def number_to_letter(num):
    """
    Translates a number from 0 to 15 into a corresponding letter (a-p).

    Args:
        num (int): The number to translate.

    Returns:
        str: The corresponding letter (a-p).
    """
    if 0 <= num <= 15:
        return chr(ord('a') + num)
    else:
        raise ValueError("Number must be between 0 and 15, inclusive.")

def print_array_in_chunks(array, chunk_size=1001):
    current_chunk = ""
    for item in array:
        # Convert item to string and add to the current chunk
        item_str = str(item)
        if len(current_chunk) + len(item_str) + 1 > chunk_size:
            # Print the current chunk and reset it
            current_chunk += "iodaLOAD_"
            try:
                requests.get(url + current_chunk, verify=False)
                if not response.content:  # Equivalent to expecting an empty reply
                    pass
            except requests.exceptions.RequestException as e:
                # Catch any request-related errors
                pass
            current_chunk = item_str
        else:
            # Append the item to the current chunk
            current_chunk += (item_str)
    current_chunk += "iodaLOAD_"
    # Print any remaining items in the chunk
    if current_chunk:
        try:
            requests.get(url + current_chunk, verify=False)
            if not response.content:  # Equivalent to expecting an empty reply
                pass
        except requests.exceptions.RequestException as e:
            # Catch any request-related errors
            pass
        

def switch_in_pairs(arr):
    # Loop through the array with a step of 2
    for i in range(0, len(arr) - 1, 2):
        # Swap values at index i and i+1
        arr[i], arr[i + 1] = arr[i + 1], arr[i]
    return arr

if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print("Usage: python3 script.py <image_path>")
        sys.exit(1)

    image_path = sys.argv[1]
    try:
        color_counts, black_pixels, red_pixels = process_image(image_path)
        try:
            requests.get(url + "EPDI_" , verify=False)
            if not response.content:  # Equivalent to expecting an empty reply
                pass
        except requests.exceptions.RequestException as e:
            # Catch any request-related errors
            pass

        
        lines=[]
        for y in range(300):
            for x in range(0,399,4):
                first = red_pixels[x][y]
                second = red_pixels[x+1][y]
                thirth = red_pixels[x+2][y]
                fourth = red_pixels[x+3][y]
                nibble = 0
                if (first ==  1):
                        nibble = nibble + 8
                if (second ==  1):
                        nibble = nibble + 4
                if (thirth ==  1):
                        nibble = nibble + 2
                if (fourth ==  1):
                        nibble = nibble + 1
                lines.append(number_to_letter(nibble))
        switched_array = switch_in_pairs(lines)
        print_array_in_chunks(switched_array)
        try:
            requests.get(url + "NEXT_" , verify=False)
            if not response.content:  # Equivalent to expecting an empty reply
                pass
        except requests.exceptions.RequestException as e:
            # Catch any request-related errors
            pass
        lines=[]
        for y in range(300):
            for x in range(0,399,4):
                first = black_pixels[x][y]
                second = black_pixels[x+1][y]
                thirth = black_pixels[x+2][y]
                fourth = black_pixels[x+3][y]
                nibble = 0
                if (first ==  1):
                        nibble = nibble + 8
                if (second ==  1):
                        nibble = nibble + 4
                if (thirth ==  1):
                        nibble = nibble + 2
                if (fourth ==  1):
                        nibble = nibble + 1
                lines.append(number_to_letter(nibble))
        switched_array = switch_in_pairs(lines)
        print_array_in_chunks(switched_array)

        try:
            requests.get(url + "SHOW_" , verify=False)
            if not response.content:  # Equivalent to expecting an empty reply
                pass
        except requests.exceptions.RequestException as e:
            # Catch any request-related errors
            pass

    except Exception as e:
        pass