Login: 
Passwort: 
Neuanmeldung 
Passwort vergessen



Das neue Heft erscheint am 1. März
LBA Medical: Runder Tisch mit einer Ecke
Ein Jahr TwinCo
Landung neben der Landebahn
Hebrideninseln Coll und Colonsay
Kleiner Reiseführer für Oshkosh
Unfallschwerpunkt PC-12: Loss of Control
Engagierter Journalismus aus Sicht des eigenen Cockpits
Engagierter Journalismus aus Sicht des eigenen Cockpits
Sortieren nach:  Datum - neue zuerst |  Datum - alte zuerst |  Bewertung

Sonstiges | Wetter und Webcam Widget unter iOS  
26. Februar 2026 14:27 Uhr: Von Frank Heßler  Bewertung: +8.00 [8]

Manchmal sind die Tage, an denen man nicht fliegen kann, ja doch ganz interessant. Ich wollte das lokale Wetter in Egelsbach etwas bequemer im Blick behalten. Der lokale Wetterreport ist zwar sehr hilfreich, über die DWD App aber nicht direkt abrufbar und die Webseite fand ich ehrlich gesagt etwas umständlich.

Egelsbach stellt ein schönes JPG Bild mit dem aktuellen Wetter zur Verfügung. Also habe ich nach einer Möglichkeit gesucht, dieses Bild als Widget auf meinem iPhone anzuzeigen. Wirklich brauchbare Lösungen habe ich spontan nicht gefunden. Im Prinzip müsste das ja mit jedem öffentlichen Webcam Bild funktionieren. Wie macht ihr das?

iOS Homescreen Widget

Ich habe mir dann die App Scriptable installiert. Dort kann man ein eigenes Script anlegen, zum Beispiel mit dem Namen EDFE Wetter, und folgenden Code einfügen:

let url = "https://images.egelsbach-airport.com/METREPEgelsbach/metrep.jpg"

// Cache umgehen
let fullUrl = url + "?t=" + Date.now()

if (config.runsInWidget) {
  // WIDGET ANSICHT
  let req = new Request(fullUrl)
  let img = await req.loadImage()

  let widget = new ListWidget()
  widget.backgroundColor = new Color("#000000")

  let image = widget.addImage(img)
  image.applyFittingContentMode()
  image.centerAlignImage()

  widget.refreshAfterDate = new Date(Date.now() + 5 * 60 * 1000)

  Script.setWidget(widget)
  Script.complete()

} else {
  // VOLLBILD ANSICHT BEIM ANTIPPEN
  let req = new Request(fullUrl)
  let img = await req.loadImage()

  let view = new WebView()

  // Bild in voller Breite anzeigen
  let html = `
  <html>
    <head>
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <style>
        body { margin:0; background:black; display:flex; justify-content:center; align-items:center; }
        img { width:100%; height:auto; }
      </style>
    </head>
    <body>
      <img src="${fullUrl}">
    </body>
  </html>
  `

  await view.loadHTML(html)
  await view.present(true)
}

Mit Done speichern. Anschließend auf den Homescreen wechseln, in den Bearbeitungsmodus gehen, Widget hinzufügen, Scriptable auswählen, die gewünschte Größe wählen und das Widget platzieren. Danach lässt sich das Widget konfigurieren, indem man das zuvor angelegte Script auswählt.

Und tatsächlich, es funktioniert. Das aktuelle Wetterbild wird direkt im Widget angezeigt. Wenn man drauf klickt sieht man das Bild größer.

Vermutlich gibt es dafür längst eine einfachere Lösung, aber das Wetter war ohnehin nicht fliegbar und ich hatte gerade etwas Zeit zum Basteln.




   Back      Slideshow
      
Forward   
1 / 4

20260226_132111000_iOS.jpg



26. Februar 2026 19:29 Uhr: Von Felix B—— an Frank Heßler Bewertung: +1.00 [1]

super idee, mit ai: plus webcam, ecet und ohnw schwarze balken:

// ----------------------------------------------------
// URLs
// ----------------------------------------------------
let urlMetar = "https://images.egelsbach-airport.com/METREPEgelsbach/metrep.jpg"
let urlCam   = "https://images.egelsbach-airport.com/WebCamEgelsbach/Wetter.jpg"
let urlDWD   = "https://www.dwd.de/DE/fachnutzer/luftfahrt/teaser/luftsportberichte/edfe/node.html"

// ----------------------------------------------------
// EINSTELLUNGEN
// ----------------------------------------------------
let cropWidget     = 30
let cropFullscreen = 80
let safeAreaMargin = 00
// ----------------------------------------------------

// ----------------------------------------------------
// LOGIK
// ----------------------------------------------------
let isFullscreen = (args.queryParameters["fullscreen"] === "true")
let showWidget   = config.runsInWidget || !isFullscreen

let activeCrop   = showWidget ? cropWidget : cropFullscreen
let activeMargin = showWidget ? safeAreaMargin : 0

// ----------------------------------------------------
// WIDGET-GRÖSSE (IN PUNKTEN!)
// ----------------------------------------------------
let widgetWidthPt  = 329
let widgetHeightPt = 155

let canvasW = widgetWidthPt
let canvasH = widgetHeightPt

// ----------------------------------------------------
// DATEN LADEN (Bilder & DWD BDE)
// ----------------------------------------------------
let reqMetar = new Request(urlMetar + "?t=" + Date.now())
let imgMetar = await reqMetar.loadImage()

let reqCam = new Request(urlCam + "?t=" + Date.now())
let imgCam = await reqCam.loadImage()

async function fetchBDE() {
  try {
    let req = new Request(urlDWD)
    let html = await req.loadString()
    
    let now = new Date()
    let year = now.getFullYear()
    let month = ("0" + (now.getMonth() + 1)).slice(-2)
    let day = ("0" + now.getDate()).slice(-2)
    let todayStr = year + "-" + month + "-" + day

    let rows = html.split("\n")
    for (let row of rows) {
      if (row.includes(todayStr)) {
        let cleanRow = row.replace(/<[^>]*>/g, " ").trim()
        let parts = cleanRow.split(/\s+/).filter(p => p.length > 0)
        
        // Index 5 ist 17:35
        if (parts.length >= 6) {
          // Wieder einzeilig
          return "BDE: " + parts[5]
        }
      }
    }
    return "BDE: --"
  } catch(e) {
    return "BDE: Err"
  }
}
let bdeText = await fetchBDE()

// ----------------------------------------------------
// DRAW CONTEXT
// ----------------------------------------------------
let ctx = new DrawContext()
ctx.size = new Size(canvasW, canvasH)
ctx.respectScreenScale = true
ctx.opaque = true

// ----------------------------------------------------
// 1. Webcam Hintergrund
// ----------------------------------------------------
let camW = imgCam.size.width
let camH = imgCam.size.height

let scaleCam = Math.max(canvasW / camW, canvasH / camH)
let drawCamW = camW * scaleCam
let drawCamH = camH * scaleCam

let drawCamX = (canvasW - drawCamW) / 2
let drawCamY = (canvasH - drawCamH) / 2

ctx.drawImageInRect(imgCam, new Rect(drawCamX, drawCamY, drawCamW, drawCamH))

// ----------------------------------------------------
// 2. METAR Overlay
// ----------------------------------------------------
let metarW = imgMetar.size.width
let metarH = imgMetar.size.height

let scaleMetar = canvasH / metarH
let drawMetW   = metarW * scaleMetar
let drawMetH   = canvasH

let shiftLeft  = activeCrop * scaleMetar
let drawMetX   = -shiftLeft + activeMargin
let drawMetY   = 0

ctx.drawImageInRect(imgMetar, new Rect(drawMetX, drawMetY, drawMetW, drawMetH))

// ----------------------------------------------------
// 3. BDE Text Einblendung (Rechtsbündig unten)
// ----------------------------------------------------
if (bdeText) {
  ctx.setFont(Font.boldSystemFont(10))
  ctx.setTextColor(Color.white())
  ctx.setTextAlignedRight()
  
  // Unten rechts positionieren (Höhe des Canvas minus 30 Pixel)
  let rectText = new Rect(canvasW - 110, canvasH - 30, 100, 20)
  ctx.drawTextInRect(bdeText, rectText)
}

let compositeImg = ctx.getImage()

// ----------------------------------------------------
// AUSGABE
// ----------------------------------------------------
if (showWidget) {

  let widget = new ListWidget()
  widget.url =
    "scriptable:///run?scriptName=" +
    encodeURIComponent(Script.name()) +
    "&fullscreen=true"

  widget.backgroundImage = compositeImg
  widget.refreshAfterDate = new Date(Date.now() + 5 * 60 * 1000)

  if (config.runsInWidget) {
    Script.setWidget(widget)
  } else {
    await widget.presentMedium()
  }

  Script.complete()

} else {

  let view = new WebView()

  let imgData = Data.fromPNG(compositeImg).toBase64String()
  let base64Url = "data:image/png;base64," + imgData

  let html = `
  <html>
    <head>
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <style>
        body {
          margin:0;
          background:black;
          display:flex;
          justify-content:center;
          align-items:center;
          min-height:100vh;
        }
        img {
          width:100%;
          height:auto;
        }
      </style>
    </head>
    <body>
      <img src="${base64Url}">
    </body>
  </html>
  `

  await view.loadHTML(html)
  await view.present(true)
  Script.complete()
}


1 / 1

Screenshot2026-02-26at20.13.58.jpg

27. Februar 2026 14:51 Uhr: Von Frank Heßler an Felix B——

Ja super! Das ist wirklich das Luxusupgrade! Vielen Dank hierfür!

28. Februar 2026 18:21 Uhr: Von Felix B—— an Frank Heßler

Leider habe ich es mit diversen AI Anfragen nicht hinbekommen die Auflösung zu erhöhen in der Widget-Darstellung. In Scriptable wird das Bild höheraufgelöst angezeigt. Vielleicht bekommt das ja die Community hin :-)

10. März 2026 20:14 Uhr: Von Felix B—— an Felix B——

Nochmal bischen optimiert (z für Zulu) und die Abrufzeit mit eingeblendet, BDE in ECET umbenannt. War kürzlich schon ganz praktisch auch im Anflug die aktive Piste übers Internet schnell mal abzurufen und schon 15min vorher Klarheit zu haben. Habe im Übrigen mal den DWD gebeten die MET-Reports zu veröffentlichen oder zumindest in der Flugwetter-APP verfügbar zu machen, wobei die nicht für schlechten Empfang optimiert ist und mich meist genau dort ausloggt.

// ----------------------------------------------------
// URLs
// ----------------------------------------------------
let urlMetar = "https://images.egelsbach-airport.com/METREPEgelsbach/metrep.jpg"
let urlCam   = "https://images.egelsbach-airport.com/WebCamEgelsbach/Wetter.jpg"
let urlDWD   = "https://www.dwd.de/DE/fachnutzer/luftfahrt/teaser/luftsportberichte/edfe/node.html"

// ----------------------------------------------------
// EINSTELLUNGEN
// ----------------------------------------------------
let cropWidget     = 30
let cropFullscreen = 80
let safeAreaMargin = 00
// ----------------------------------------------------

// ----------------------------------------------------
// LOGIK
// ----------------------------------------------------
let isFullscreen = (args.queryParameters["fullscreen"] === "true")
let showWidget   = config.runsInWidget || !isFullscreen

let activeCrop   = showWidget ? cropWidget : cropFullscreen
let activeMargin = showWidget ? safeAreaMargin : 0

// ----------------------------------------------------
// WIDGET-GRÖSSE (IN PUNKTEN!)
// ----------------------------------------------------
let widgetWidthPt  = 329
let widgetHeightPt = 155

let canvasW = widgetWidthPt
let canvasH = widgetHeightPt

// ----------------------------------------------------
// DATEN LADEN (Bilder & DWD ECET)
// ----------------------------------------------------
let reqMetar = new Request(urlMetar + "?t=" + Date.now())
let imgMetar = await reqMetar.loadImage()

let reqCam = new Request(urlCam + "?t=" + Date.now())
let imgCam = await reqCam.loadImage()

async function fetchECET() {
  try {
    let req = new Request(urlDWD)
    let html = await req.loadString()
    
    let now = new Date()
    let year = now.getFullYear()
    let month = ("0" + (now.getMonth() + 1)).slice(-2)
    let day = ("0" + now.getDate()).slice(-2)
    let todayStr = year + "-" + month + "-" + day

    let rows = html.split("\n")
    for (let row of rows) {
      if (row.includes(todayStr)) {
        let cleanRow = row.replace(/<[^>]*>/g, " ").trim()
        let parts = cleanRow.split(/\s+/).filter(p => p.length > 0)
        
        // Index 5 ist die Zeit für ECET.
        if (parts.length >= 6) {
          return parts[5]
        }
      }
    }
    return "--"
  } catch(e) {
    return "Err"
  }
}
let ecetTime = await fetchECET()

// ----------------------------------------------------
// DRAW CONTEXT
// ----------------------------------------------------
let ctx = new DrawContext()
ctx.size = new Size(canvasW, canvasH)
ctx.respectScreenScale = true
ctx.opaque = true

// ----------------------------------------------------
// 1. Webcam Hintergrund
// ----------------------------------------------------
let camW = imgCam.size.width
let camH = imgCam.size.height

let scaleCam = Math.max(canvasW / camW, canvasH / camH)
let drawCamW = camW * scaleCam
let drawCamH = camH * scaleCam

let drawCamX = (canvasW - drawCamW) / 2
let drawCamY = (canvasH - drawCamH) / 2

ctx.drawImageInRect(imgCam, new Rect(drawCamX, drawCamY, drawCamW, drawCamH))

// ----------------------------------------------------
// 2. METAR Overlay
// ----------------------------------------------------
let metarW = imgMetar.size.width
let metarH = imgMetar.size.height

let scaleMetar = canvasH / metarH
let drawMetW   = metarW * scaleMetar
let drawMetH   = canvasH

let shiftLeft  = activeCrop * scaleMetar
let drawMetX   = -shiftLeft + activeMargin
let drawMetY   = 0

ctx.drawImageInRect(imgMetar, new Rect(drawMetX, drawMetY, drawMetW, drawMetH))

// ----------------------------------------------------
// 3. ECET Text & UTC Einblendung (Rechtsbündig unten)
// ----------------------------------------------------
if (ecetTime) {
  // Aktuelle UTC Zeit generieren
  let timeNow = new Date()
  let utcH = ("0" + timeNow.getUTCHours()).slice(-2)
  let utcM = ("0" + timeNow.getUTCMinutes()).slice(-2)
  let utcTime = utcH + ":" + utcM
  
  // Zwei getrennte Textblöcke für perfekte Ausrichtung
  let labelText = "ECET:\nFROM:"
  let valueText = ecetTime + "ᶻ\n" + utcTime + "ᶻ"

  // Angepasste Box-Größe (100px breit, 30px hoch)
  let bgW = 72
  let bgH = 30
  // Box liegt wieder bündig rechts am Rand
  let bgX = canvasW - bgW
  let bgY = canvasH - bgH

  // Halbtransparenter Hintergrundkasten
  let bgRect = new Rect(bgX, bgY, bgW, bgH)
  ctx.setFillColor(new Color("000000", 0.4))
  ctx.fillRect(bgRect)

  // Globale Texteinstellungen
  ctx.setFont(Font.boldSystemFont(9))
  ctx.setTextColor(Color.white())
  
  // 1. Spalte: Labels (Linksbündig zeichnen) - Textfeld startet um 2 Pixel weiter links als vorher (jetzt bgX + 4 statt bgX + 6)
  ctx.setTextAlignedLeft()
  let rectLabels = new Rect(bgX + 1, bgY + 3, bgW - 10, bgH)
  ctx.drawTextInRect(labelText, rectLabels)

  // 2. Spalte: Zeiten (Rechtsbündig zeichnen) - Rechte Kante rückt um genau 2 Pixel nach links
  ctx.setTextAlignedRight()
  let rectValues = new Rect(bgX + 1, bgY + 3, bgW - 10, bgH)
  ctx.drawTextInRect(valueText, rectValues)
}

let compositeImg = ctx.getImage()

// ----------------------------------------------------
// AUSGABE
// ----------------------------------------------------
if (showWidget) {

  let widget = new ListWidget()
  widget.url =
    "scriptable:///run?scriptName=" +
    encodeURIComponent(Script.name()) +
    "&fullscreen=true"

  widget.backgroundImage = compositeImg
  widget.refreshAfterDate = new Date(Date.now() + 5 * 60 * 1000)

  if (config.runsInWidget) {
    Script.setWidget(widget)
  } else {
    await widget.presentMedium()
  }

  Script.complete()

} else {

  let view = new WebView()

  let imgData = Data.fromPNG(compositeImg).toBase64String()
  let base64Url = "data:image/png;base64," + imgData

  let html = `
  <html>
    <head>
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <style>
        body {
          margin:0;
          background:black;
          display:flex;
          justify-content:center;
          align-items:center;
          min-height:100vh;
        }
        img {
          width:100%;
          height:auto;
        }
      </style>
    </head>
    <body>
      <img src="${base64Url}">
    </body>
  </html>
  `

  await view.loadHTML(html)
  await view.present(true)
  Script.complete()
}


1 / 1

IMG_9648copy.jpg


5 Beiträge Seite 1 von 1

 

Home
Impressum
© 2004-2026 Airwork Press GmbH. Alle Rechte vorbehalten. Vervielfältigung nur mit Genehmigung der Airwork Press GmbH. Die Nutzung des Pilot und Flugzeug Internet-Forums unterliegt den allgemeinen Nutzungsbedingungen (hier). Es gelten unsere Datenschutzerklärung unsere Allgemeinen Geschäftsbedingungen (hier). Kartendaten: © OpenStreetMap-Mitwirkende, SRTM | Kartendarstellung: © OpenTopoMap (CC-BY-SA) Hub Version 14.29.06
Zur mobilen Ansicht wechseln
Seitenanfang