Thursday, November 6, 2025

Panasonic Viera Remote Control app w/ Python.

Wiz Light akıllı ampüller gibi resmi mobil uygulaması olup resmi desktop uygulaması olmayan bir başka cihaz ekosistemi de Panasonic Viera serisi TV'ler. Bu TV'lerin LAN üzerinden kontrol edilebilmesini sağlayan uzaktan kumanda uygulaması için ekran görüntüsü ve Python çözümü aşağıda.



Panasonic Viera - UPnP discovery + simple remote control (CLI + Tkinter GUI)
Works without PIN (if your TV doesn't require pairing).
Discovery via SSDP -> fetch device description -> find remote control service.
If discovery fails, you can enter IP manually.

Dependencies:
    pip install requests

Run:
    python panasonic_remote.py        # opens GUI
    python panasonic_remote.py --cli  # example CLI mode

Author: kadir — lightweight and pragmatic.
"""
import sys
import socket
import time
import threading
import requests
import xml.etree.ElementTree as ET
from urllib.parse import urljoin, urlparse
import argparse

try:
    import tkinter as tk
    from tkinter import simpledialog, messagebox
    HAS_TK = True
except Exception:
    HAS_TK = False

SSDP_ADDR = "239.255.255.250"
SSDP_PORT = 1900
SSDP_MX = 2
# Some Panasonic TVs respond to urn:schemas-upnp-org:device:MediaRenderer:1
SSDP_STS = [
    "urn:schemas-upnp-org:device:MediaRenderer:1",
    "ssdp:all",
    "urn:schemas-sony-com:service:IRCC:1",  # sometimes other vendors, harmless
]

DISCOVERY_RETRIES = 3
DISCOVERY_TIMEOUT = 2.0  # seconds per SSDP recv
HTTP_TIMEOUT = 4.0

# Common Panasonic remote keys (NRC_* family). You can add more if needed.
COMMON_KEYS = [
    "NRC_POWER-ONOFF", "NRC_VOLUP-ONOFF", "NRC_VOLDOWN-ONOFF",
    "NRC_CH_UP-ONOFF", "NRC_CH_DOWN-ONOFF", "NRC_MUTE-ONOFF",
    "NRC_MENU-ONOFF", "NRC_EXIT-ONOFF", "NRC_HOME-ONOFF",
    "NRC_INFO-ONOFF", "NRC_EPG-ONOFF", "NRC_GUIDE-ONOFF",
]

def ssdp_search(st, timeout=DISCOVERY_TIMEOUT, mx=SSDP_MX):
    """Send a single SSDP M-SEARCH and collect responses (non-blocking)."""
    msg = "\r\n".join([
        'M-SEARCH * HTTP/1.1',
        f'HOST: {SSDP_ADDR}:{SSDP_PORT}',
        'MAN: "ssdp:discover"',
        f'ST: {st}',
        f'MX: {mx}',
        '', ''
    ]).encode('utf-8')

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    sock.settimeout(timeout)
    # Set TTL to 2 so it stays in local net
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)

    try:
        sock.sendto(msg, (SSDP_ADDR, SSDP_PORT))
    except Exception as e:
        print("SSDP send error:", e)
        sock.close()
        return []

    responses = []
    t0 = time.time()
    while True:
        try:
            data, addr = sock.recvfrom(65507)
            text = data.decode('utf-8', errors='replace')
            responses.append((text, addr))
        except socket.timeout:
            break
        except Exception:
            break
        # small safety to not loop forever
        if time.time() - t0 > timeout + 0.5:
            break
    sock.close()
    return responses

def parse_ssdp_response(text):
    """Parse SSDP response into dict of headers (case-insensitive)."""
    lines = text.splitlines()
    headers = {}
    for line in lines[1:]:
        if ':' in line:
            k, v = line.split(':', 1)
            headers[k.strip().upper()] = v.strip()
    return headers

def discover_panasonic(timeout=DISCOVERY_TIMEOUT, retries=DISCOVERY_RETRIES):
    """Try multiple SSDP searches and return list of candidate device LOCATION URLs."""
    locations = {}
    for attempt in range(retries):
        for st in SSDP_STS:
            resps = ssdp_search(st, timeout=timeout)
            for text, addr in resps:
                hdr = parse_ssdp_response(text)
                loc = hdr.get('LOCATION') or hdr.get('LOCATION'.upper())
                st_hdr = hdr.get('ST') or hdr.get('NT')
                usn = hdr.get('USN', '')
                if loc:
                    # Use parsed netloc as key to reduce duplicates
                    try:
                        key = urlparse(loc).netloc
                    except Exception:
                        key = loc
                    locations[key] = loc
        # quick sleep between retries
        time.sleep(0.25)
        if locations:
            break
    return list(locations.values())

def fetch_device_description(location_url):
    """GET device description XML and parse services. Return base URL and XML root."""
    try:
        r = requests.get(location_url, timeout=HTTP_TIMEOUT)
        r.raise_for_status()
        xmltext = r.text
        root = ET.fromstring(xmltext)
        # Base URL: try to read  or use location origin
        base_elem = root.find('{urn:schemas-upnp-org:device-1-0}URLBase')
        if base_elem is not None and base_elem.text:
            base = base_elem.text.strip()
        else:
            # fallback to scheme+netloc
            p = urlparse(location_url)
            base = f"{p.scheme}://{p.netloc}"
        return base, root
    except Exception as e:
        # print("fetch description error:", e)
        return None, None

def find_remote_control_service(base_url, root):
    """
    Scan device description for a service that provides Panasonic remote control.
    Commonly RemoteControl or IRCC-like services. Return control URL (absolute).
    """
    # Namespaces might vary; map known ones
    ns = {'upnp': 'urn:schemas-upnp-org:device-1-0'}
    service_list = root.findall('.//upnp:service', namespaces=ns)
    for svc in service_list:
        svc_type = svc.find('upnp:serviceType', namespaces=ns)
        svc_control = svc.find('upnp:controlURL', namespaces=ns)
        svc_event = svc.find('upnp:eventSubURL', namespaces=ns)
        if svc_type is None or svc_control is None:
            continue
        st = svc_type.text or ''
        ctrl = svc_control.text or ''
        # heuristics: Panasonic IR/Remote often has "IRCC" or "NRC" or specific Panasonic service types
        if 'IRCC' in st or 'RenderingControl' in st or 'AVTransport' in st or 'panasonic' in st.lower() or 'nrc' in st.lower():
            ctrl_abs = urljoin(base_url, ctrl)
            return ctrl_abs
    # fallback: try first service controlURL
    first = root.find('.//upnp:service/upnp:controlURL', namespaces=ns)
    if first is not None and first.text:
        return urljoin(base_url, first.text)
    return None

def send_key_http(remote_control_url, key):
    """
    Send remote key via HTTP SOAP. Many Panasonic TVs accept POST to /nrc/control_0 or similar.
    This implementation uses common Panasonic IRCC SOAP format.
    """
    # Build simple SOAP message that many Panasonic TVs accept:
    soap_body = f"""
    
      
        
          {key}
        
      
    """

    headers = {
        "Content-Type": "text/xml; charset=utf-8",
        "SOAPACTION": '"urn:panasonic-com:service:p00NetworkControl:1#X_SendKey"',
        "Connection": "close"
    }

    # Try POST; some TVs expect different endpoint paths; we'll attempt variations if necessary.
    candidates = [remote_control_url]
    # common alternative endpoints:
    parsed = urlparse(remote_control_url)
    base = f"{parsed.scheme}://{parsed.netloc}"
    alt_paths = [
        "/nrc/control_0", "/dmr/control_0", "/upnp/control/remote", "/nrc/control_0/HTTP/1.1"
    ]
    for p in alt_paths:
        url = urljoin(base, p)
        if url not in candidates:
            candidates.append(url)

    last_err = None
    for url in candidates:
        try:
            r = requests.post(url, data=soap_body.encode('utf-8'), headers=headers, timeout=HTTP_TIMEOUT)
            # Accept success codes (200/202) and even 500 sometimes (some TVs respond weirdly)
            if r.status_code in (200, 202, 500, 404):
                return True, url, r.status_code
            else:
                last_err = (url, r.status_code, r.text[:200])
        except Exception as e:
            last_err = (url, str(e))
    return False, last_err, None

class PanasonicRemote:
    def __init__(self):
        self.control_url = None
        self.last_location = None
        self.cached_host = None

    def discover_and_setup(self):
        locations = discover_panasonic()
        if not locations:
            return False, "No devices discovered via SSDP."
        # try each location until we find a control URL
        for loc in locations:
            base, root = fetch_device_description(loc)
            if not base or not root:
                continue
            ctrl = find_remote_control_service(base, root)
            if ctrl:
                self.control_url = ctrl
                self.last_location = loc
                self.cached_host = urlparse(loc).netloc
                return True, ctrl
        return False, "No control service found in device descriptions."

    def set_manual_ip(self, ip_or_url):
        # Accept either bare IP or full URL; convert to likely control endpoint
        if ip_or_url.startswith("http://") or ip_or_url.startswith("https://"):
            base = ip_or_url
        else:
            base = f"http://{ip_or_url}"
        # try fetching device description at /description.xml or /dd.xml common endpoints
        candidates = [base, urljoin(base, "/description.xml"), urljoin(base, "/dd.xml")]
        for c in candidates:
            try:
                b, r = fetch_device_description(c)
                if b and r:
                    ctrl = find_remote_control_service(b, r)
                    if ctrl:
                        self.control_url = ctrl
                        self.last_location = c
                        self.cached_host = urlparse(c).netloc
                        return True, ctrl
            except Exception:
                continue
        # fallback: set a plausible control endpoint
        self.control_url = urljoin(base, "/nrc/control_0")
        self.last_location = base
        self.cached_host = urlparse(base).netloc
        return True, self.control_url

    def send_key(self, key, retries=2):
        if not self.control_url:
            return False, "No control URL configured."

        for attempt in range(retries):
            ok, info, status = send_key_http(self.control_url, key)
            if ok:
                return True, info
            # on failure, try rediscover once
            if attempt == 0:
                # try quick rediscover if SSDP available
                try:
                    found, detail = self.discover_and_setup()
                    # if rediscovered a different endpoint, try again
                except Exception:
                    pass
            time.sleep(0.3)
        return False, info

# --- Simple CLI ---
def cli_mode():
    pr = PanasonicRemote()
    print("Discovering Panasonic TVs via SSDP...")
    ok, info = pr.discover_and_setup()
    if ok:
        print("Found control URL:", info)
    else:
        print("Discovery failed:", info)
        manual = input("Manual IP or URL (or Enter to abort): ").strip()
        if not manual:
            print("Aborted.")
            return
        ok, info = pr.set_manual_ip(manual)
        if ok:
            print("Using control URL (manual):", info)
        else:
            print("Manual setup failed:", info)
            return

    print("Available keys (common):", ", ".join(COMMON_KEYS))
    print("Type key and Enter to send. 'exit' to quit.")
    while True:
        k = input("key> ").strip()
        if not k:
            continue
        if k.lower() in ("exit", "quit"):
            break
        ok, info = pr.send_key(k)
        if ok:
            print("OK ->", info)
        else:
            print("FAIL ->", info)

# --- Simple Tkinter GUI ---
def gui_mode():
    if not HAS_TK:
        print("Tkinter not available on this Python. Use --cli.")
        return
    pr = PanasonicRemote()

    root = tk.Tk()
    root.title("Panasonic Remote — kadir")
    root.geometry("420x360")

    status_var = tk.StringVar(value="Durum: keşfediliyor... (SSDP)")
    ip_var = tk.StringVar(value="")

    def discover_thread():
        status_var.set("Keşfediliyor...")
        ok, info = pr.discover_and_setup()
        if ok:
            status_var.set(f"Bulundu: {pr.cached_host}")
            ip_var.set(pr.cached_host)
        else:
            status_var.set("Keşif başarısız. IP gir veya Yeniden Dene.")
            ip_var.set("")

    def on_discover():
        threading.Thread(target=discover_thread, daemon=True).start()

    def on_manual():
        ip = simpledialog.askstring("Manual IP", "TV IP veya URL gir:", initialvalue=ip_var.get())
        if not ip:
            return
        ok, info = pr.set_manual_ip(ip)
        if ok:
            status_var.set(f"Manual set: {pr.cached_host}")
            ip_var.set(pr.cached_host)
        else:
            messagebox.showerror("Hata", "Manual kurulum başarısız.")

    def send_key_gui(key):
        status_var.set(f"Sending {key} ...")
        def work():
            ok, info = pr.send_key(key)
            if ok:
                status_var.set(f"Sent {key}")
            else:
                status_var.set(f"Failed {key}: {info}")
        threading.Thread(target=work, daemon=True).start()

    # Layout
    top = tk.Frame(root, pady=6)
    top.pack(fill=tk.X)
    tk.Label(top, textvariable=status_var, anchor='w').pack(fill=tk.X, padx=6)
    ctrl = tk.Frame(root)
    ctrl.pack(padx=6, pady=8, fill=tk.BOTH, expand=True)
    # Buttons grid
    btns = [
        ("Power", "NRC_POWER-ONOFF"),
        ("Vol+", "NRC_VOLUP-ONOFF"),
        ("Vol-", "NRC_VOLDOWN-ONOFF"),
        ("Mute", "NRC_MUTE-ONOFF"),
        ("CH+", "NRC_CH_UP-ONOFF"),
        ("CH-", "NRC_CH_DOWN-ONOFF"),
        ("Menu", "NRC_MENU-ONOFF"),
        ("Home", "NRC_HOME-ONOFF"),
        ("Info", "NRC_INFO-ONOFF"),
        ("Exit", "NRC_EXIT-ONOFF"),
        ("Return", "NRC_RETURN-ONOFF"),
    ]
    r = 0; c = 0
    for label, k in btns:
        b = tk.Button(ctrl, text=label, width=10, command=lambda kk=k: send_key_gui(kk))
        b.grid(row=r, column=c, padx=4, pady=4)
        c += 1
        if c >= 3:
            c = 0; r += 1

    bottom = tk.Frame(root, pady=4)
    bottom.pack(fill=tk.X)
    tk.Button(bottom, text="Discover", command=on_discover).pack(side=tk.LEFT, padx=6)
    tk.Button(bottom, text="Manual IP", command=on_manual).pack(side=tk.LEFT)
    tk.Button(bottom, text="Quit", command=root.quit).pack(side=tk.RIGHT, padx=6)

    # Start initial discovery
    threading.Thread(target=discover_thread, daemon=True).start()
    root.mainloop()

# --- main ---
def main():
    p = argparse.ArgumentParser()
    p.add_argument("--cli", action="store_true", help="run CLI mode")
    args = p.parse_args()
    if args.cli:
        cli_mode()
    else:
        gui_mode()

if __name__ == "__main__":
    main()
    

Wednesday, October 8, 2025

Osiloskop vs ucuz logic analyzer.

555'li basit bir devrenin çıkışını Çin üretimi ucuz USB logic analyzer ve Hantek osisloskopla karşılaştırdım, sonuç aşağıdaki gibi. 

Tabii daha farklı ve hızlı sinyallerde sonuç değişebilir ama 3-5 dolar bandında satılan, 8 kanallı ve saleae Logic yazılımıyla uyumlu bir USB oyuncak için sonuç çok tatminkar.

Thursday, October 2, 2025

WiZ Akıllı Ampulleri PC'den Kontrol - Part II IP SCANNER

Daha önce Philips Wiz ampülleri python kütüphanelerini kullanarak PC'den kontrol etmeyi yazmıştım.

Wiz PC Kontrol

Zamanla dinamik network ortamında DHCP veya başka sebeplerden ampül TCPIP konfigürasyonları ve IP adreslerinin değişmesi dolayısıyla ampüllere ulaşmak için daha dinamik bir çözüm üretmek gerekti. Böylece networkteki Wiz ampülleri tarayıp IP adreslerini listeleyen bir uygulama yazdım.


Taramadan önce.

Tarama ağda üç ampül bulmuş.


Kod:


# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import messagebox
import asyncio
from pywizlight import wizlight, PilotBuilder
import socket

# IP araligini buradan ayarla
ip_range_start = 100
ip_range_end = 120
network_prefix = "192.168.1."

# GUI icinden cagirmak icin asyncio loop wrapper
class WizController:
    def __init__(self):
        self.loop = asyncio.new_event_loop()

    def turn_on(self, ip):
        self.loop.run_until_complete(self._turn_on(ip))

    def turn_off(self, ip):
        self.loop.run_until_complete(self._turn_off(ip))

    async def _turn_on(self, ip):
        try:
            bulb = wizlight(ip)
            await bulb.turn_on(PilotBuilder())
        except Exception as e:
            messagebox.showerror("Baglanti Hatasi", f"{ip} icin hata: {e}")

    async def _turn_off(self, ip):
        try:
            bulb = wizlight(ip)
            await bulb.turn_off()
        except Exception as e:
            messagebox.showerror("Baglanti Hatasi", f"{ip} icin hata: {e}")

    async def is_wiz_bulb(self, ip):
        try:
            bulb = wizlight(ip)
            await bulb.getPilot()
            return True
        except:
            return False

# Arayuz
app = tk.Tk()
app.title("WiZ Ampul Kontrol Paneli")
app.geometry("350x400")

controller = WizController()
frames = []

found_ips = []

# Tarama butonu ve alani
def scan_for_bulbs():
    result_box.delete(0, tk.END)
    found_ips.clear()
    for i in range(ip_range_start, ip_range_end + 1):
        ip = f"{network_prefix}{i}"
        try:
            socket.setdefaulttimeout(0.3)
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.sendto(b'{"method":"getPilot"}', (ip, 38899))
            data, addr = s.recvfrom(1024)
            if b"method" in data:
                found_ips.append(ip)
                result_box.insert(tk.END, ip)
        except:
            continue

# Ampul kontrol paneli
button_frame = tk.Frame(app)
button_frame.pack(pady=10)

scan_btn = tk.Button(button_frame, text="Ampul Tara", command=scan_for_bulbs)
scan_btn.pack()

result_box = tk.Listbox(app, height=6)
result_box.pack(padx=10, pady=5, fill="x")

control_frame = tk.LabelFrame(app, text="Secilen IP'ye Komut Gonder", padx=10, pady=10)
control_frame.pack(padx=10, pady=10, fill="x")

btn_on = tk.Button(control_frame, text="Ac", width=10, command=lambda: controller.turn_on(result_box.get(tk.ACTIVE)))
btn_on.pack(side="left", padx=5)

btn_off = tk.Button(control_frame, text="Kapat", width=10, command=lambda: controller.turn_off(result_box.get(tk.ACTIVE)))
btn_off.pack(side="left", padx=5)

app.mainloop()

Monday, September 1, 2025

Amiga 600 Tamir Notları Part 1 - Genel Kontroller ve Yeşil Video Sinyal Hattı Tamiri.

 





İkinci elden durumu bilinmeyen (bu piyasada bu bozuk demek oluyor) bir Amiga 600 aldım ve elimde RGB skart kablo olmadığı için composite çıkışa (sarı RCA jack) bağlayarak çalışıp çalışmadığını hızlıca görmek istedim. Gücü açtığımda TV ekranı siyah ve Amiga ölü gibiydi. İlk olarak voltajları kontrol ettim, CN12 FDD portundaki 5v eksikti. Power soketin yerine tam oturmaması gibi ufak bir mekanik sorunu çözdükten sonra FDD pin header 5v hattı yerine geldi. Düzgün çalışan bir Amiga’da (sadece 500 ve 600 için bundan eminim çünkü elimde sadece bu ikisi var) anakarttaki 4 pin FDD power soketinde 12v, GND, GND ve 5v olmalı. Burası bence Amigalarda düzgün ve sağlıklı bir voltaj dağıtımı olup olmadığını kontrol etmenin uygun noktalarından biri. Zaten sistemde 5v, 12v ve GND dışında bir de -12v var, o da ses katındaki opamplar için gerekli. -12v hattı olmayınca sistem çalışıyor ama sesi bozuluyor, bunu da daha önce 500’de deneyimlemiştim.

Makine power soket tam oturmazken ölü gibiydi, ne 68k CPU ne de custom çipler ısınmıyor, ayrı ayrı denediğim LCD TV ve Sony PVM monitörde en ufak parazitlenme olmuyordu. Hiçbir yaşam belirtisi yoktu. LED board’u da eksik olduğundan oradan da bir ipucu alamıyordum. Power’ı hallettikten ve 4 pin FDD soketinde tüm voltajları gördükten sonra nette A600 black screen olarak aradım ve bu durumun en kronik sebebinin Amiga 600’deki reset devresi olduğunu gördüm. Bu devre kabaca bir RC osilatör ve 555 timer’dan oluşuyor. Reset devresi makineye güç geldikten sonra RC zaman sabiti kadar bir sürede Amiga’yı resetleyip normal state’e çekiyor. Bulduğum kaynaklarda RC zamanlama devresindeki C kapasitörünün bozulmasıyla ilgili kronik bir sorundan bahsediliyor ve ilgili C değiştirilince sorunun düzeldiği söyleniyordu. (Zaten çoğu hata yaşlı kondansatörlerden ortaya çıkıyor.) Kendi makinemdeki 555’in ilgili pinleri [2 ve 3] osiloskopla kontrol ettiğimde kapasitör şarj-deşarj eğrisinin gözle yakalanabilecek kadar yavaş işleyen bir sinyal olduğunu gördüm. Devre kabaca 1 sn kadar bir sürede 555’in ilgili pinindeki voltaj eğrisini GND’ye çekerek makineye reset atıyordu. Benim sistemimde burada bir sorun yoktu.

Bu arada bende eksik olan LED board yerine 330 ohm’a seri bir LED bağlayarak klavye yakınındaki status LED çıkışını tek tek gözlemledim. Buna göre power sürekli yanık, FDD boot esnasında seek edip kapanıyor, HDD ise hiç yanmıyordu. Buradan edindiğim izlenim makinenin CPU ve ROM’unda bir problem olmadığı yönündeydi çünkü bu rutinlerin minimumda CPU ve ROM’un sağlam olmasını gerektirdiğini düşünüyorum.

Status LED devresi.
A600 Led Board - Credit: Amigastore.eu

Kompozitte dalgalı siyah ekran hariç hiçbir görüntü olmayınca RGB portuna A520 modülatör takarak görüntü almayı denedim. Bu sefer bozuk da olsa aşağıdaki ekranı gördüm. Amiga büyük oranda sağlamdı fakat video sinyalinde yeşil hattı yoktu. Composite çıkışta da bu yüzden hiçbir görüntü yoktu çünkü RGB’nin aksine composite sinyal üretiminde kanal eksikliği tolere edilemiyor olmalı. A520 bunu nasıl yapıyor bilmiyorum zira Denise’ten çıkan analog RGB, önce composite sinyali üreten Sony CDX encoder’e, oradan da RGB porta geliyor. Çıkışta 15khz analog display sürüyor veya modülatör varsa A520 üzerindeki kompozit ve RF görüntü sinyali yine aynı kaynaktan gelen bu analog RGB’den üretiliyor. Oysa digital RGB pinleri CDX encodere uğramadan RGB konnektöre direkt bağlı. Özetle CDX’in yeşil kanalı olmayınca yapamadığı analog red ve blue’dan composite sinyal üretimini A520 yapabiliyor. Veya aynı işi CDX de yapabiliyor ama CDX’ten composite RCA sokete giden hatta bozuk komponentler var. Bu bölgeyi henüz detaylı incelemiş değilim.

A520 modulator çıkışı. Yeşil renk eksik.

Olması gereken.


Pin 5-4-3 
Sony CDX encoder entegresinden gelen analog sinyal hattı (AB, AG, AR) . Sorun pin 4'teki AG (analog yeşil) hattında. 7-8 ve 9 no'lu pinler dijital video çıkışı ve doğrudan Denise tarafından sürülüyorlar.

74HCT244 Buffer pinout.




Denise'den çıkan analog yeşil sinyali U31 buffer çipi pin 11'den girip  pin 9'den çıkıyor ve Sony CDX encoder green_in (G_IN) hattına ulaşamıyor. Sorun bu iki nokta arasında.




Video port 8,9 ve 7 numaralı pinlerden birindeki dijital RGB sinyali. Üçü sinyal da 15.6 khz gibi anlamlı bir frekansa sahip ve benzer dalgaformlarında, dolayısıyla sorunsuz görünüyorlar. Üç hattın üçünü de eklemeye gerek görmedim. Bu gözlemler neticesinde Denise'in sağlam olduğunu varsayabiliriz; yay 🥳



Bu sefer CDX'ten gelen analog video sinyallerini gözlüyoruz. Üstteki resim normal örnek, R ve B hatları böyle görünüyor.

CDX'ten gelen son sinyal böyle. Video out pin 4, analog yeşil hattı. Dalga şekli R ve B'deki diğer analog sinyallere benzemeli ama sadece düze yakın anormal bir DC offset var. CDX giriş ve çıkışlarını kontrole geçiyorum.

Sorunlu yeşil hattının şematik görünümü. 74HCT244 bufferdan çıkıp sağda görülen CDX'in R_IN, G_IN ve B_IN girişlerini besliyor.

Bu hattın PCB'deki yeri.


Burada yaptığım osiloskop ölçümünde Q212’nin base’inde R215’den gelen green sinyalinin var olduğunu, collector’de düzgün VCC gerilimi (~4v gibi) ve C212’ye çıkan emiterde DC ofset gözlemledim. Yani transistöre sinyal giriyor, beslemesi de düzgün fakat çıkışında olması gereken yeşil sinyali yok. Denise – CDX encoder hattında yeşil sinyali taşıyan arızalı eleman Q212 transistörü.


PCB

Pinout




1AM BC847B Sot-23 paket NPN tipi. Üretici Nxp, on semi vs. h: üretim kodu olabilir, önemsiz.




Q212 için elimdeki uyumlu alternatiflerden biri BC546, test amaçlı bozuk olanı söküp bunu lehimliyorum.
BC546 pinout

Tamir videosu:

Sorunlu transistörü değiştirdikten sonra düzelen görüntü. 🥳

Friday, August 1, 2025

"Ciklet Pil" için LM317 ile Sabit Akım NiCd Şarj Devresi / DIY Gumstick Battery Charger

Yıllardır kenarda köşede yatan Sanyo Cadnica NiCd "ciklet" pili şarj etmek istedim ve bunun için LM317 regülatörü ile sabit akım kontrollü basit bir şarj devresi kurdum. 

Marka/Model: Sanyo Cadnica KF-A650

Tip: NiCd
Gerilim: 1.2 V
Şarj önerisi: 65 mA @ 14–16 saat

Devre LM317 üzerinden sabit akım sağlayacak şekilde tasarlanmıştır. Girişe uygulanacak gerilim, LM317’nin düzgün çalışabilmesi için belirli sınırlar içinde olmalıdır.

LM317 Sabit Akım Formülü:

I = 1.25 V / R

65 mA elde etmek için:

R = 1.25 / 0.065 ≈ 19.2 ohm

20 ohm dirençle akım yaklaşık 62.5 mA olur.

Projede 9V 1A’lik bir DC adaptör kullanılmıştır. Bu adaptör, hem yeterli akımı sağlayabilmesi hem de LM317’nin giriş-çıkış farkı için uygun voltaj aralığında kalması açısından idealdir.

LM317 sabit akım konfigürasyonunda düzgün çalışabilmek için çıkışta hedeflenen pil voltajına (yaklaşık 1.2–1.4 V arası) göre en az 3 V’luk bir farkla beslenmelidir. Bu nedenle güvenli çalışma aralığı: 8 – 12V'tur. 8V altı durumlarda LM317 yeterli regülasyon sağlayamayabilir. 12V üzeri durumlarda ise LM317 üzerinde harcanan güç artacağı için soğutma gerekebilir.

Devre ortalama 65–70 mA civarında sabit akım çeker. 9 V girişle, adaptörün sağlayacağı gücü şöyle hesaplayabiliriz:

Toplam çekilen güç: P_in = 9 V × 0.07 A = 0.63 W

Bu değer oldukça düşük olduğu için 1A kapasiteli sıradan bir adaptör bu yükü rahatlıkla karşılayabilir.

Şarj Esnasında Güç Tüketimi

Şarj ilerledikçe pil voltajı yükselir ve LM317 üzerindeki gerilim farkı azalır. Bu da çekilen gücün zamanla düşmesini sağlar.

Örnek:
Giriş voltajı: 9 V
Pil voltajı: 1.3 V
Akım: 65 mA

LM317 üzerindeki kayıp (ısı): 
V_drop = 9 V − 1.3 V = 7.7 V
P_loss = 7.7 V × 0.065 A ≈ 0.5 W

Bu yaklaşık yarım watt’lık bir ısı kaybıdır. Soğutmasız da çalışabilir, ancak sürekli kullanımda basit bir soğutucu önerilir.

Regaülatörü ters bağlantıya karşı korumak için girişte 1N400x diyot kullanılmıştır. 

Devreyi parazitik etkilerden arındırarak stabilize etmek için yine girişte 100 uf ve 100 nf cap kullanmak üreticinin önerdiği bir tasarım detayı olmaktadır. 

Out ve Adj arasındaki 20 ohm'luk direnç istenen değerde sabit akım sağlar.

Pil şarj çıkışı regülatör out bacağı (pil +) ve devrenin GND'sinden (pin -) olacak şekilde kurulmalıdır.

Şema:

Breadbord:


Ciklet pil yuvası olarak 18650 yuvası kullanıldı:


LM317 Pinout:

Tuesday, July 8, 2025

Rahatsız Edici 30 Film / The 30 Most Extreme Films of the 21st Century (So Far)

1. A Serbian Film (Srdjan Spasojevic, 2010, Serbia)
2. Irreversible (Gaspar Noé, 2002, France)
3. Martyrs (Pascal Laugier, 2008, France)
4. Inside (À l’intérieur) (Alexandre Bustillo, Julien Maury, 2007, France)
5. Visitor Q (Takashi Miike, 2001, Japan)
6. Ichi the Killer (Takashi Miike, 2001, Japan)
7. Taxidermia (György Pálfi, 2006, Hungary)
8. Gozu (Takashi Miike, 2003, Japan)
9. Haute Tension (Alexandre Aja, 2003, France)
10. Cold Fish (Sion Sono, 2010, Japan)
11. Wolf Creek (Greg Mclean, 2005, Australia)
12. Frontière(s) (Xavier Gens, 2007, France)
13. Oldboy (Chan-wook Park, 2003, South Korea)
14. The Loved Ones (Sean Byrne, 2009, Australia)
15. Funny Games (Michael Haneke, 1997 & 2007, Austria & US)
16. Ex Drummer (Koen Mortier, 2007, Belgium)
17. I Saw the Devil (Kim Jee-woon, 2010, South Korea)
18. The Woman (Lucky McKee, 2011, U.S)
19. Calvaire (The Ordeal) (Fabrice Du Welz, 2004, Belgium)
20. Strange Circus (Sion Sono, 2005, Japan)
21. Eden Lake (James Watkins, 2008, UK)
22. The Chaser (Hong-jin Na, 2008, South Korea)
23. In My Skin (Marina de Van, 2002, France)
24. Three…Extremes (Fruit Chan’s segment “Dumplings,” 2004, Hong Kong)
25. Bedevilled (Chul-soo Jang, 2010, South Korea)
26. Red White & Blue (Simon Rumley, 2010, UK/US)
27. The Skin I Live In (Pedro Almodóvar, 2011, Spain)
28. Das Experiment (Oliver Hirschbiegel, 2001, Germany)
29. Kidnapped (Miguel Ángel Vivas, 2010, Spain)
30. Love Exposure (Sion Sono, 2008, Japan)

Thursday, June 19, 2025

🎞️ ffmpeg ile Videodan Belirli Bir Alanı Kırpma (Crop)

Video işleme projelerinde, bazen bir videonun sadece belirli bir bölümünü saklamak ve kenarlardaki alanları atmak isteyebilirsiniz. Örneğin, dikey bir videoda sadece orta kısmı almak ya da sabit bir kameranın kadrajındaki sabit çerçeveyi izole etmek gerekebilir.

Bu yazıda, ffmpeg kullanarak bir videodan istediğiniz bölgeyi nasıl kırpabileceğinizi adım adım göstereceğim.

🔧 Gerekli Araçlar

  • ffmpeg — komut satırı tabanlı açık kaynak video işlem aracı

Ubuntu için:

sudo apt install ffmpeg

Windows kullanıcıları buradan indirip ffmpeg.exe'yi bir klasöre çıkarıp PATH'e eklemeli.

📐 Adım 1: Videonun Boyutunu Öğren

ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 input.mp4

Örnek çıktı:

1080,1918

Bu durumda videonun genişliği 1080 piksel, yüksekliği 1918 piksel.

🖼️ Adım 2: Almak İstediğiniz Bölgeyi Belirleyin

Diyelim ki videonun ortasındaki 864x1442’lik bir bölgeyi almak istiyorsunuz. Geri kalan her şey silinecek.

Kırpma için gereken x ve y değerleri (video ortalanacak şekilde):

x = (1080 - 864) / 2 = 108
y = (1918 - 1442) / 2 = 238

✂️ Adım 3: ffmpeg ile Kırpma İşlemi

ffmpeg -i input.mp4 -filter:v "crop=864:1442:108:238" -c:a copy output.mp4

Parametreler:

  • w: alınacak bölgenin genişliği
  • h: yüksekliği
  • x: videonun solundan itibaren başlangıç noktası
  • y: üstten itibaren başlangıç noktası

🎯 Alternatif: Dört Kenardan Kırpma Mantığı

Eğer her kenardan kırpılacak miktar belliyse, formül şu şekilde olur:

crop_w = width - left - right
crop_h = height - top - bottom
x = left
y = top

Örnek:

ffmpeg -i input.mp4 -filter:v "crop=1060:1868:10:20" -c:a copy output.mp4

🎁 Bonus: Tek Kare Alıp Görselde Ölçüm Yapmak

ffmpeg -i input.mp4 -frames:v 1 -q:v 2 -ss 00:00:01 frame.jpg

Bu görseli Paint veya GIMP ile açarak tam olarak hangi alanı almanız gerektiğini ölçebilirsiniz.

📌 Sonuç

ffmpeg'in crop filtresiyle videolardan yalnızca istediğiniz kısmı almak oldukça pratik ve güçlü bir yöntemdir. İster sabit bir kamera çerçevesi kırpın, ister estetik düzenlemeler yapın — doğru kullanıldığında zamandan ve disk alanından büyük tasarruf sağlar.

Friday, June 6, 2025

Xbox One Termal Macun Deneyimi

Geçtiğimiz günlerde Xbox One konsolumun termal macununu değiştirmeye karar verdim. Konsolda hiçbir sıkıntı yoktu, sırf çok uzun yıllardır el değmeden kullandığım için temizlik ve bakım yapasım geldi. Bu iş için önce elimde hazır bulunan kalitesiz, markasız bir macun kullandım Sonuç: Felaket.

Uygulama sonrasında fan bir anda çok yüksek devirde çalışmaya başladı. Konsol boşta bile sanki ağır oyun çalıştırıyormuş gibi ses çıkarıyordu. Dahası, fan çıkışından gelen hava  soğuktu. Yani işlemciden çıkan ısı heatsink’e doğru düzgün aktarılmıyordu. Heatsink neredeyse çalışmıyordu.

Bunun üzerine Arctic MX-4 aldım ve tekrar uyguladım ve konsol eski sessiz haline döndü. Bazen Netflix'te film izlerken, bazen Pacman'de bile çıldıran konsol artık RDR2'de bile fanı düşük hızlarda tutuyordu. En güzeli, artık fan çıkışından gelen hava sıcaktı. Bu da ısının olması gerektiği gibi heatsink'e ulaşabildiğini gösteriyordu.

Xbox One’ın APU yapısı (SoC), CPU ve GPU’nun birleşik olduğu karmaşık bir tasarıma sahip. Bu yüzden termal temas çok kritik. Kalitesiz bir termal macun, sistemin dengesini tamamen bozabiliyor. 

Ucuz termal macun kullanmak bana zaman ve huzur kaybettirdi. Sessizlik ve stabilite istiyorsanız, özellikle Xbox One gibi cihazlarda iyi bir termal macun kullanmak şart. Arctic MX-4 ya da benzeri kaliteli macunları tavsiye ederim.

Tuesday, June 3, 2025

WiZ Akıllı Ampulleri PC Üzerinden Kontrol Etmek (Python GUI Arayüzlü Uygulama)

WiZ markalı akıllı ampulleri (Signify/Philips) cep telefonu uygulaması yerine bilgisayardan kontrol etmek için Python tabanlı GUI uygulaması. Bu uygulama sayesinde aynı ağa bağlı WiZ ampulleri PC'den açmak ve kapatmak mümkün.



✨ Özellikler:

  • 3 farklı WiZ ampul için "Aç" ve "Kapat" butonları
  • Tüm ampulleri tek tuşta açıp kapama; "Hepsini Aç" / "Hepsini Kapat"
  • Tkinter tabanlı basit ve hızlı arayüz
  • Kod Python 3 ve pywizlight kütüphanesiyle yazıldı

📅 Gerekenler:

  • Python 3.8+
  • pywizlight kütüphanesi:

pip install pywizlight

Bilgisayar ile ampuller aynı ağda olmalı

⚙️ Kod:


import tkinter as tk
from tkinter import messagebox
import asyncio
from pywizlight import wizlight, PilotBuilder

# Ampul IP listesi, kendi senaryonuza göre düzenleyin
bulb_ips = ["192.168.1.104", "192.168.1.105", "192.168.1.106"]

class WizController:
    def __init__(self):
        self.loop = asyncio.new_event_loop()

    def turn_on(self, ip):
        self.loop.run_until_complete(self._turn_on(ip))

    def turn_off(self, ip):
        self.loop.run_until_complete(self._turn_off(ip))

    async def _turn_on(self, ip):
        try:
            bulb = wizlight(ip)
            await bulb.turn_on(PilotBuilder())
        except Exception as e:
            messagebox.showerror("Bağlantı Hatası", f"{ip} için hata: {e}")

    async def _turn_off(self, ip):
        try:
            bulb = wizlight(ip)
            await bulb.turn_off()
        except Exception as e:
            messagebox.showerror("Bağlantı Hatası", f"{ip} için hata: {e}")

app = tk.Tk()
app.title("WiZ Ampul Kontrol Paneli")
app.geometry("300x300")

controller = WizController()

for idx, ip in enumerate(bulb_ips):
    frame = tk.LabelFrame(app, text=f"Ampul {idx + 1} - {ip}", padx=10, pady=10)
    frame.pack(padx=10, pady=5, fill="x")

    btn_on = tk.Button(frame, text="Aç", width=10, command=lambda ip=ip: controller.turn_on(ip))
    btn_on.pack(side="left", padx=5)

    btn_off = tk.Button(frame, text="Kapat", width=10, command=lambda ip=ip: controller.turn_off(ip))
    btn_off.pack(side="left", padx=5)

bulk_frame = tk.Frame(app)
bulk_frame.pack(pady=15)

btn_all_on = tk.Button(bulk_frame, text="Hepsini Aç", width=12, command=lambda: [controller.turn_on(ip) for ip in bulb_ips])
btn_all_on.pack(side="left", padx=5)

btn_all_off = tk.Button(bulk_frame, text="Hepsini Kapat", width=12, command=lambda: [controller.turn_off(ip) for ip in bulb_ips])
btn_all_off.pack(side="left", padx=5)

app.mainloop()

Friday, May 30, 2025

Inkscape'te EPS Dosyası Açmak İçin Ghostscript Kurulumu.

EPS (Encapsulated PostScript) dosyası bir grafik formatıdır. Ancak Inkscape gibi modern yazılımlar bu dosya türünü açmak için arka planda Ghostscript adlı ek bir yazılımı kullanır. Eğer sistemde Ghostscript kurulu değilse EPS dosyalarını açamaz. Bu yazıda, Windows ortamında Ghostscript kurulumu ve Inkscape ile EPS dosyasını nasıl açabileceğini işliyoruz.

1. Ghostscript Nedir? Ghostscript, PostScript (PS) ve EPS dosyalarını okuyan, yorumlayan ve rasterlaştıren açık kaynaklı bir yazılımdır. Inkscape, EPS dosyasını içeri aktarabilmek için arka planda Ghostscript'i kullanır.

2. Ghostscript Kurulumu

  1. Ghostscript'in en güncel sürümünü indirin: https://ghostscript.com/download/gsdnld.html

  2. Varsayılan kurulum dizini: C:\Program Files\gs\gs10.05.1\

  3. Aşağıdaki dizinleri sistem PATH ortam değişkenine ekleyin:

    • C:\Program Files\gs\gs10.05.1\bin

    • C:\Program Files\gs\gs10.05.1\lib

PATH Ortam Değişkeni Nasıl Eklenir?

  • Bu Bilgisayar > Özellikler > Gelişmiş Sistem Ayarları > Ortam Değişkenleri > Sistem Değişkenleri > Path > Düzenle > Yeni


3. Ghostscript Kurulumunun Doğrulanması Komut İsteminde (CMD) şunu yazın:

gswin64c --version

Bu komut, yüklü sürümü gösteriyorsa her şey hazırdır.

4. Inkscape ile EPS Dosyası Açmak

  1. Inkscape'i çalıştırın

  2. File > Open ile EPS dosyasını seçin

  3. Ghostscript çağrılır ve EPS dosyası SVG gibi düzenlenebilir hale gelir

İPUCU:

  • EPS dosyasının adında Türkçe karakter veya boşluk olmaması import sırasında hata olasılığını azaltır.

  • Microsoft Store sürümü yerine MSI kurulumlu sürüm kullanın.


5. EPS Alternatifi: PDF'ye Dönüştürmek EPS dosyasını çevirmek için Ghostscript kullanabilirsiniz:

gswin64c -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=output.pdf input.eps

PDF dosyası Inkscape'e import edilerek kolayca SVG'ye dönüştürülebilir.


Sonuç Ghostscript kurulduktan sonra Inkscape, EPS dosyalarını sorunsuz şekilde açabilir. Bu sayede karmaşık vektör setlerini kolayca seçebilir, İkonları SVG formatında düzenleyebilir ve animasyonlu gösterimler için hazır hale getirebilirsiniz.

Elektron Volt, Enerji Taşınımı ve Newton Sarkacı Benzetmesi

Elektron Volt, Elektronun Hızı ve Gerçek Enerji Taşınımı

Elektron volt (eV), bir elektronun 1 voltluk potansiyel farkı boyunca hareket ettiğinde kazandığı enerjidir. Enerji birimi olarak, mikroskobik düzeyde müthiş pratik bir araçtır.

1 eV ≈ 1.602 × 10−19 joule

Mesafe Değil Voltaj Belirleyicidir

Bir elektron 10 cm'lik bir telde ya da 1 km'lik bir kabloda hareket etsin, eğer iki uç arasında 1 voltluk potansiyel fark varsa, elektron yine 1 eV enerji kazanır. Yani:

E = q × V = (1.6 × 10−19 C) × (1 V) = 1.6 × 10−19 J = 1 eV

Elektron Ne Kadar Hızlanır?

Kinetik enerji ile hız arasındaki ilişki:

E = (1/2)mv² → v = √(2E/m)

1 eV alan bir elektronun hızı yaklaşık:

v ≈ 5.93 × 105 m/s

Drift Hızı Başka, Hız Başka

Elektronlar iletken içinde çok yavaş ilerler (drift hızı). Örneğin 1 A akım geçen bir mm²'lik telde:

vd ≈ 70 µm/s

Yani saniyede sadece saç teli kadar yol alır. Buna rağmen lambayı açtığında ışık anında yanar. Neden?

Newton Sarkacı Benzetmesi

Bu olay, Newton sarkacına çok benzer:

  • Bir uçtaki top hareket eder, ortadakiler yerinde durur, son top anında fırlar.
  • Enerji taşıyıcısı madde değil, etkidir.
Aynı şekilde elektrik akımında da enerji, elektronların birbirini dürtmesiyle iletilmez; bir etki zinciri olarak yayılır.

Gerçek Enerji Taşıyıcısı: Poynting Vektörü

Enerjinin nasıl, nereden aktığını tanımlar:

S = E x B
  • E: Elektrik alan
  • B: Manyetik alan
  • S: Enerjinin yönü ve yoğunluğu (Watt/m²)

Ve işte çarpıcı gerçek:

Elektrik devrelerinde enerji, telin içinden değil, telin dışındaki boşluktan akar.

Elektronlar içeride yavaşça hareket eder ama telin dışını saran E ve B alanları sayesinde, enerji dışarıdan akar — tıpkı görünmeyen bir “yağ gibi”.

O Zaman Elektronlar Ne İşe Yarıyor?

Elektronlar aslında enerji taşımaz. Onlar, enerji taşıyan alanların oluşması için gereken “akım” kaynağıdır. Yani:

  • Elektron akışı → manyetik alan oluşturur (B)
  • Potansiyel farkı → elektrik alan oluşturur (E)
  • Bunların etkileşimiyle oluşan S = E × B → enerjiyi taşır

Sonuç

Elektrik akımı, sadece elektronların yavaş hareketi değil; alanların etkileşimiyle enerjinin kablonun dışından taşınmasıdır.

Elektronlar orada ama enerjiyi taşıyanlar onlar değil, onları çevreleyen alanlar.


PowerShell ile Web Sunucusu Loglarından Saldırgan IP Engelleme

Sunucunuzun erişim loglarını incelerken bazı IP'lerin sürekli olarak phpMyAdmin, sqladmin veya cgi-bin gibi yolları taradığını mı fark ettiniz? Bu yazıda, saldırgan davranışlar sergileyen IP adreslerini loglardan otomatik olarak ayıklayan ve Windows Güvenlik Duvarı'nda engelleyen bir PowerShell scripti paylaşıyorum.

Adım 1 – Log Dosyasını Hazırla

Öncelikle Apache/Nginx gibi bir sunucudan alınmış bir erişim logunuz olmalı. Örnek format:


45.131.155.254 - - [29/May/2025:01:01:46 +0300] "GET / HTTP/1.1" 200 5795 "-" "Mozilla/5.0"

Bu dosyayı örneğin C:\loglar\access.log yoluna yerleştirelim.

Adım 2 – PowerShell Scripti

Yeni bir PowerShell scripti oluşturun:

# Kaydet: block-malicious-ips.ps1

$logFile = "C:\loglar\access.log"
$logLines = Get-Content $logFile
$ipRegex = '(\d{1,3}\.){3}\d{1,3}'

# Şüpheli istekleri filtrele
$suspiciousPatterns = @(
    "/phpmyadmin", "/sqladmin", "/mysql", "/db/php", "/cgi-bin", "/PMA",
    "/MyAdmin", "Sakura.sh", "/Management.asp", "/authLogin.cgi"
)

$badIps = @{}

foreach ($line in $logLines) {
    foreach ($pattern in $suspiciousPatterns) {
        if ($line -match $pattern -and $line -match $ipRegex) {
            $ip = ($line -match $ipRegex) | Out-Null; $matches[0]
            if (-not $badIps.ContainsKey($ip)) {
                $badIps[$ip] = 1
            } else {
                $badIps[$ip] += 1
            }
        }
    }
}

# Engelle
foreach ($ip in $badIps.Keys) {
    Write-Output "Engelleniyor: $ip"
    New-NetFirewallRule -DisplayName "Block $ip" -Direction Inbound -RemoteAddress $ip -Action Block -Profile Any -Enabled True
}

Adım 3 – Scripti Çalıştırma

  1. PowerShell’i Yönetici olarak açın.
  2. Script dosyasının bulunduğu klasöre gidin:
    cd C:\loglar
  3. Scripti çalıştırın:
    Set-ExecutionPolicy RemoteSigned -Scope Process
    .\block-malicious-ips.ps1

İsteğe Bağlı: BAT Dosyası ile Çalıştırmak

Scripti çift tıklamayla başlatmak için aşağıdaki gibi bir .bat dosyası oluşturabilirsiniz:

@echo off
powershell -ExecutionPolicy Bypass -File "C:\loglar\block-malicious-ips.ps1"
pause

Sonuç

Bu PowerShell çözümü ile şüpheli IP'leri loglardan ayıklayıp otomatik olarak Windows güvenlik duvarında engelleyebilirsiniz. Script, özellikle küçük çaplı web sunucuları veya kişisel sistemler için pratik bir güvenlik önlemi sunar.

Tuesday, May 27, 2025

Statik IP ve DHCP IP Çakışmaları: Neden Olur, Nasıl Önlenir?

Ağ yöneticileri ve teknik kullanıcılar için "IP çakışması" sinir bozucu bir sorundur. Özellikle aynı yerel ağda hem statik IP atanmış cihazlar hem de DHCP sunucusu tarafından IP dağıtılan cihazlar varsa bu risk daha da büyür. Bu yazıda şu sorunun cevabını detaylıca ele alacağız:

"Statik IP verdim ama modem başka bir cihaza da aynı IP'yi verdi, nasıl olur?"


DHCP Sunucusu Statik IP'yi Nasıl Görür?

❗ Görmez.

Evet, DHCP sunucusu yalnızca kendi üzerinden IP talep eden cihazları tanır. Bir cihaza manuel olarak (statik) 192.168.1.102 verildiğinde bu cihaz DHCP'den IP istemediyse, DHCP sunucusunun ondan haberi yoktur.

Bu durumda DHCP sunucusu 192.168.1.102 adresini "boşta" sanabilir ve başka bir cihaza da aynı IP'yi kiralayabilir.


IP Çakışması Nasıl Oluşur?

Senaryo:

  • PC'ye manuel olarak 192.168.1.102 atanır

  • PC kapanır veya ağdan çıkar

  • DHCP havuzunda 192.168.1.100–192.168.1.150 arası aktif

  • DHCP, boşta zannettiği 192.168.1.102'yi başka bir cihaza verir

  • PC yeniden açılır → IP çakışması oluşur

DHCP sunucusu bu IP'nin zaten başka bir cihaza verildiğini bilemez çünkü ona hiç sorulmadı.


DHCP Lease Süresi de Etkili midir?

Evet. IP adresleri belirli bir "kiralama süresi (lease time)" ile dağıtılır.

  • Eğer cihaz bu süre boyunca yeniden bağlanmazsa,

  • DHCP sunucusu bu IP'yi tekrar kullanılabilir sayar.

Ancak bu yalnızca DHCP üzerinden IP almış cihazlar için geçerlidir. Statik IP alanlar lease tablosuna hiç girmez.


Statik IP Kullanırken Yapılmaması Gerekenler

  • DHCP havuzu içinden rastgele bir IP seçip statik vermek ❌

  • Statik IP verdikten sonra modem DHCP'sine haber vermemek ❌

  • Aynı IP'yi birden fazla cihazda statik olarak kullanmak ❌


Doğru Yaklaşım Nedir?

✅ Yöntem 1: DHCP Rezervasyonu (Önerilen)

  • Modem arayüzüne girilir

  • Cihaza ait MAC adresini seçilir

  • "IP Rezervasyonu" tanımlanır (örneğin: 192.168.1.113)

  • Artık cihaz her bağlandığında otomatik olarak bu IP’yi alır

  • Ağ içinde çakışma riski sıfırlanır

✅ Yöntem 2: DHCP aralığı dışından Statik IP vermek

  • Örneğin DHCP aralığı 192.168.1.100–192.168.1.150 olsun

  • 192.168.1.200 gibi bir IP'yi elle verince DHCP o IP'ye karışmaz.


DHCP Sunucusu IP Pingleyerek Tespit Eder mi?

Bazı gelişmiş DHCP sunucuları IP’yi atamadan önce "bu IP kullanılıyor mu?" diye ping atabilir:

arping 192.168.1.102

Ancak:

  • Bu çoğu modem/router’da yoktur

  • Cihaz kapalıysa veya ICMP yanıtı vermiyorsa yine işe yaramaz


Sonuç

DurumSonuç
Statik IP verildi,                             DHCP bilmezÇakışma olabilir
Lease süresi dolduIP tekrar kullanılabilir
DHCP rezervasyonu yapıldıGüvenli, çakışma yaşanmaz

DHCP'nin doğası gereği sadece istekte bulunan cihazları takip etmesi, ağda statik IP kullanımında dikkatli olunmasını zorunlu kılar.

En güvenli yol: IP'yi elle değil, DHCP rezervasyonu ile sabitlemek.


Ekstra Not: Windows Ağ Ayarında

Statik IP yerine DHCP seçip modemde rezervasyon yapılırsa:

  • IP otomatik gelir

  • DNS ve gateway otomatik doğru olur

  • Manüel girişe gerek kalmaz

Modern ağlarda bu yöntem en sağlam ve sürdürülebilir çözümdür. 🧠🔐

Monday, May 26, 2025

Windows + Nginx: Sade ve Guvenli Firewall Yapilandirmasi

1. Başlangıç Durumu

Windows'un varsayılan güvenlik duvarı:

  • Profil tabanlı (Private/Public/Domain) çalışır.
  • Gelen bağlantılar Block, giden bağlantılar Allow şeklindedir.
  • Yüzlerce "Network Discovery", "Remote Assistance" vb. kural aktif olabilir.

Bu karmaşa dış dünyaya açık kalan portların gözden kaçmasına neden olabilir.

2. Amacımız Nedir?

Servis Durum Açıklama
HTTP (port 80) ✅ Açık Her IP'den erişilsin
SMB (445/139) ✅ Sadece LAN 192.168.1.0/24 ağından erişilsin
UPnP, SSDP, mDNS ❌ Kapalı Güvenlik zaafiyetleri nedeniyle
Diğer tüm servisler ❌ Kapalı Tanımlanmadıkça bloklanacak

3. Kuralları Script ile Tanımlama

firewall_rule_set.ps1 Scripti

# 1. HTTP (80) tüm profillerde açık
New-NetFirewallRule -DisplayName "Allow HTTP on All" `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 80 `
    -Profile Any `
    -Action Allow

# 2. SMB sadece LAN'dan
New-NetFirewallRule -DisplayName "Allow SMB from LAN only" `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 445,139 `
    -RemoteAddress 192.168.1.0/24 `
    -Action Allow

# 3. SMB dış IP'lere kapalı
New-NetFirewallRule -DisplayName "Block SMB from Others" `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 445,139 `
    -RemoteAddress Any `
    -Action Block

# 4. UPnP ve SSDP kapalı
New-NetFirewallRule -DisplayName "Block UPnP" `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 2869 `
    -Action Block

New-NetFirewallRule -DisplayName "Block SSDP" `
    -Direction Inbound `
    -Protocol UDP `
    -LocalPort 1900 `
    -Action Block

Scripti Çalıştırma

  1. PowerShell'i Yönetici olarak açın.
  2. Güvenlik politikasını geçerli kılmak için:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
  1. Scripti çalıştırın:
powershell -ExecutionPolicy Bypass -File "firewall_rule_set.ps1"

4. Nginx Loglarını Temizleme ve Takip Etme

Logları sıfırla:

Clear-Content "C:\nginx\logs\access.log"
Clear-Content "C:\nginx\logs\error.log"

Anlık log takibi:

Get-Content "C:\nginx\logs\access.log" -Wait

Gelen istekleri, IP adreslerini ve durum kodlarını gerçek zamanlı izleyebilirsiniz.

5. Kötü Niyetli IP'leri Engelleme

Loglarda aşağıdakiler görülürse:

  • GET /login.rsp gibi zaafiyet denemeleri
  • User-Agent: sqlmap, Hello World, curl vb. bot imzaları
  • Kara liste IP'lerden gelen bağlantılar

Engellemek için:

New-NetFirewallRule -DisplayName "Block malicious IP 196.251.81.93" `
  -Direction Inbound `
  -RemoteAddress 196.251.81.93 `
  -Action Block

(Opsiyonel) Nginx'te engelleme:

server {
    deny 196.251.81.93;
}

6. Otomatik IP Analizi (PowerShell)

$log = "C:\nginx\logs\access.log"
$loglines = Get-Content $log -Tail 100

$blocklist = @()
foreach ($line in $loglines) {
    if ($line -match "login\.rsp" -or $line -match "Hello World" -or $line -match "sqlmap" -or $line -match "curl") {
        if ($line -match "^(\d+\.\d+\.\d+\.\d+)") {
            $ip = $matches[1]
            if (-not ($blocklist -contains $ip)) {
                $blocklist += $ip
                Write-Host "Engelleniyor: $ip"
                New-NetFirewallRule -DisplayName "AutoBlock $ip" -Direction Inbound -RemoteAddress $ip -Action Block
            }
        }
    }
}
Bu script tekrar çalıştırıldığında sadece yeni IP’leri ekler. Görev Zamanlayıcı ile otomatikleştirilebilir.

7. Durum Kontrolleri

Aktif ağ profili:

Get-NetConnectionProfile | Format-Table Name, InterfaceAlias, NetworkCategory

Aktif kurallar özeti:

Get-NetFirewallRule -Enabled True | Format-Table DisplayName, Direction, Action, Profile, LocalPort, Protocol -AutoSize

Dinlenen portlar:

Get-NetTCPConnection -State Listen | Sort-Object LocalPort | Format-Table LocalAddress, LocalPort, OwningProcess

8. Sonuç

  • Sadece tanımlı servisler aktif olur
  • Minimum port dışa açıktır
  • Loglar temiz ve okunabilirdir
  • Profil bazlı karmaşa ortadan kalkar
  • Saldırgan IP'ler anında engellenebilir
  • Otomatik analiz ile sürekli güvenlik sağlanır

Güvenli, sade ve kontrollü bir Windows + nginx ortamı! 🛡️