# -*- coding: utf-8 -*-
# PoliviaBot UME V3 ＋ RaspyPicoW V3 ブリングアップ（自己完結・1ファイル）
#
#   mpremote connect auto run ume_bringup.py
#
# pins.py も lib も投入不要（ピン定義・OLEDドライバを内蔵）。メニューで各テストを選ぶ。
# V3 で是正済み（実機検証は本スクリプトで）：
#   ① U5(HC-SR04)配置反転是正 → 正挿しで逆接なし   ② 右モータOUT入替 → GP2駆動で前進
#   ③ D2 SS14→SS34 → 突入電流で発熱しない          ④ SW高電位 3.3V化 → 押下でGPIO定格内
#   ⑤ ECHO分圧 2.2k/3.3k=3.0V → GP6定格内          ⑥ I2Cプルアップ4.7k → 400kHz一括OLED可
# ピン定義は pins.py（唯一の真実）と一致させること（変更時は両方直す）。
import time
from machine import Pin, PWM, ADC, I2C, time_pulse_us
import framebuf

# ===== ピン定義（pins.py と一致）=====
MOTOR_L_IN1, MOTOR_L_IN2 = 0, 1
MOTOR_R_IN1, MOTOR_R_IN2 = 2, 3
LINE_L, LINE_C, LINE_R = 26, 27, 28
SONAR_ECHO, SONAR_TRIG = 6, 7
SERVO_0, SERVO_1, SERVO_2 = 8, 9, 10
LED_1, LED_2 = 11, 12
SW_1, SW_2 = 13, 14
PRESSED = 1                       # プルダウン・押下＝High
I2C_SDA, I2C_SCL = 4, 5
OLED_ADDR = 0x3C


# ===== SSD1306 最小ドライバ（内蔵・ページ分割show）=====
class SSD1306_I2C(framebuf.FrameBuffer):
    def __init__(self, width, height, i2c, addr=0x3C):
        self.width, self.height, self.i2c, self.addr = width, height, i2c, addr
        self.pages = height // 8
        self.buffer = bytearray(self.pages * width)
        super().__init__(self.buffer, width, height, framebuf.MONO_VLSB)
        for cmd in (0xAE, 0x20, 0x00, 0x40, 0xA1, 0xA8, height - 1, 0xC8, 0xD3, 0x00,
                    0xDA, 0x12 if height == 64 else 0x02, 0xD5, 0x80, 0xD9, 0xF1,
                    0xDB, 0x30, 0x81, 0xFF, 0xA4, 0xA6, 0x8D, 0x14, 0xAF):
            self.i2c.writeto(self.addr, bytes([0x80, cmd]))
        self.fill(0); self.show()

    def _win(self):
        for c in (0x21, 0, self.width - 1, 0x22, 0, self.pages - 1):
            self.i2c.writeto(self.addr, bytes([0x80, c]))

    def show(self):                       # ページ分割（どの速度でも安全）
        self._win()
        w = self.width
        for p in range(self.pages):
            self.i2c.writeto(self.addr, b"\x40" + self.buffer[p * w:(p + 1) * w])

    def show_bulk(self):                  # 一括転送（V3 プルアップ検証用・400kHz想定）
        self._win()
        self.i2c.writeto(self.addr, b"\x40" + self.buffer)


# ===== 各テスト =====
def test_led():
    print("[01] LED1(GP11)/LED2(GP12)＋本体LED を交互5回点滅")
    l1 = Pin(LED_1, Pin.OUT, 0); l2 = Pin(LED_2, Pin.OUT, 0)
    try:
        ob = Pin("LED", Pin.OUT)
    except Exception:
        try:
            ob = Pin(25, Pin.OUT)
        except Exception:
            ob = None
    for _ in range(5):
        l1.value(1); l2.value(0); ob and ob.value(1); time.sleep(0.3)
        l1.value(0); l2.value(1); ob and ob.value(0); time.sleep(0.3)
    l1.value(0); l2.value(0); ob and ob.value(0)
    print("  → 両方点滅で合格")


def test_switch(sec=15):
    print("[02] SW1(GP13)/SW2(GP14) 状態表示（V3:3.3V側・押下=1）/ %d秒" % sec)
    sw1 = Pin(SW_1, Pin.IN); sw2 = Pin(SW_2, Pin.IN)
    t0 = time.ticks_ms(); prev = None
    while time.ticks_diff(time.ticks_ms(), t0) < sec * 1000:
        cur = (sw1.value(), sw2.value())
        if cur != prev:
            print("  SW1=%d SW2=%d" % cur); prev = cur
        time.sleep(0.02)
    print("  → 押すと該当が1になれば合格（V3はGPIO定格内）")


def test_line(sec=15):
    print("[03] ラインセンサ L/C/R 生値 / %d秒（白で下降・黒/浮かしで上昇）" % sec)
    a = (ADC(LINE_L), ADC(LINE_C), ADC(LINE_R))
    t0 = time.ticks_ms()
    while time.ticks_diff(time.ticks_ms(), t0) < sec * 1000:
        v = [x.read_u16() for x in a]
        print("  L=%5d C=%5d R=%5d  %s" % (v[0], v[1], v[2], "#" * (sum(v) // 24000)))
        time.sleep(0.25)


def test_oled():
    print("[04] I2C0スキャン＋OLED（V3プルアップを400kHzで検証）")
    i2c = I2C(0, sda=Pin(I2C_SDA), scl=Pin(I2C_SCL), freq=400_000)
    found = i2c.scan()
    print("  scan:", [hex(x) for x in found])
    if OLED_ADDR not in found:
        print("  0x3C なし → 電池ON・OLED装着/向き(J4:1GND 2VCC 3SCL 4SDA)を確認")
        return
    o = SSD1306_I2C(128, 64, i2c, OLED_ADDR)
    o.fill(0); o.rect(0, 0, 128, 64, 1)
    o.text("PoliviaBot UME", 4, 8); o.text("V3 bring-up", 4, 22)
    o.text("I2C 400kHz", 4, 36); o.text(repr(time.localtime())[:13], 4, 50)
    try:
        o.show_bulk()      # 一括転送が通れば V3 プルアップが効いている
        print("  → 表示OK＋400kHz一括転送OK（⑥I2Cプルアップ有効）")
    except OSError as e:
        o.show()           # ダメなら分割（V2相当）
        print("  → 一括転送NG(%s)→分割で表示。プルアップ未実装/不良の疑い" % e)


def test_motor():
    print("[05] モータ L/R（スローディケイ・要・車輪を浮かす／V3:両輪とも前進方向か確認）")
    if input("  ★車輪を浮かせたか？ [y/N]: ").strip().lower() != "y":
        print("  中止"); return
    FREQ, WEAK, MID, FULL = 20000, 30000, 45000, 65535
    def mk(p): q = PWM(Pin(p)); q.freq(FREQ); q.duty_u16(0); return q
    L1, L2, R1, R2 = mk(MOTOR_L_IN1), mk(MOTOR_L_IN2), mk(MOTOR_R_IN1), mk(MOTOR_R_IN2)
    fwd = lambda a, b, d: (a.duty_u16(FULL), b.duty_u16(FULL - d))
    rev = lambda a, b, d: (b.duty_u16(FULL), a.duty_u16(FULL - d))
    off = lambda a, b: (a.duty_u16(0), b.duty_u16(0))
    try:
        for nm, a, b in (("左L", L1, L2), ("右R", R1, R2)):
            print("  --- %s：前進 弱→中 → 停止 → 後進 中 → 停止" % nm)
            fwd(a, b, WEAK); time.sleep(1.3); fwd(a, b, MID); time.sleep(1.3)
            off(a, b); time.sleep(0.8); rev(a, b, MID); time.sleep(1.3); off(a, b); time.sleep(0.6)
        print("  → V3は左右とも『前進』で同じ向きに進めば合格（GP2駆動=右前進）")
        print("  ※発熱したら即電源OFF（D2=SS34化で突入電流対策済のはず）")
    finally:
        for p in (L1, L2, R1, R2):
            p.duty_u16(0); p.deinit()


def test_servo():
    print("[06] サーボ J5/J6/J7（GP8/9/10）中央→左→右→中央")
    d = lambda us: int(us * 65535 // 20000)
    for nm, gp in (("S0 J5", SERVO_0), ("S1 J6", SERVO_1), ("S2 J7", SERVO_2)):
        print("  ---", nm)
        s = PWM(Pin(gp)); s.freq(50)
        for us in (1500, 1000, 2000, 1500):
            s.duty_u16(d(us)); time.sleep(0.8)
        s.duty_u16(0); s.deinit()


def test_sonar(sec=15):
    print("[07] 超音波 HC-SR04 距離（V3:ECHO分圧3.0V・正挿し）/ %d秒" % sec)
    trig = Pin(SONAR_TRIG, Pin.OUT, 0); echo = Pin(SONAR_ECHO, Pin.IN)
    t0 = time.ticks_ms()
    while time.ticks_diff(time.ticks_ms(), t0) < sec * 1000:
        trig.value(0); time.sleep_us(5); trig.value(1); time.sleep_us(10); trig.value(0)
        dur = time_pulse_us(echo, 1, 30000)
        if dur < 0:
            print("  応答なし（給電5V・向き・分圧を確認）")
        else:
            print("  %.1f cm (echo %d us)" % (dur * 0.0343 / 2, dur))
        time.sleep(0.4)


def info():
    print("ピン: MOTOR L=GP0/1 R=GP2/3 / LINE=GP26/27/28 / SONAR TRIG=GP7 ECHO=GP6")
    print("      SERVO=GP8/9/10 / LED=GP11/12 / SW=GP13/14 / I2C=GP4(SDA)/GP5(SCL) 0x3C")
    print("電源: 電池→SW3→Vin→MT3608→5V→XC6206→3V3 / USB単独はベース無給電(センサ等は電池ON)")


TESTS = {"1": test_led, "2": test_switch, "3": test_line, "4": test_oled,
         "5": test_motor, "6": test_servo, "7": test_sonar, "i": info}


def menu():
    print("\n==== PoliviaBot UME V3 ブリングアップ ====")
    while True:
        print("\n 1:LED 2:SW 3:ライン 4:OLED 5:モータ 6:サーボ 7:超音波  i:ピン情報  q:終了")
        sel = input(" 番号> ").strip().lower()
        if sel == "q":
            print("終了"); return
        fn = TESTS.get(sel)
        if not fn:
            print("  不明な入力"); continue
        try:
            fn()
        except KeyboardInterrupt:
            print("  （中断）")
        except Exception as e:
            print("  エラー:", e)


menu()
