/* =============================================================================
   OAT LoRa Field Node  —  v1.0.0
   OpenAgricultureTechnology.com  ·  the Sketch Library (Collect layer)
   -----------------------------------------------------------------------------
   An ESP32 with a LoRa radio (Heltec WiFi LoRa 32 V2 or V3) reads the sensors
   wired to it and, on a schedule, broadcasts one small frame on a private 915 MHz
   channel. No Wi-Fi, no internet, no account, no pairing, no reply expected. The
   OAT LoRa Gateway at the house hears it and pushes oat-ods on this node's behalf,
   exactly as the BLE Listener does for a Bluetooth thermometer. This node is a
   Govee with a longer antenna.

   WHAT IT READS (all optional; it reports whatever it finds)
     DS18B20 temperature probes   one 1-Wire pin, up to 16 probes
     SHT-30 air temp + humidity   I2C, 0x44 and/or 0x45
     photoresistor                one ADC pin, as light level 0-100 %
     capacitive soil probes       up to 8 ADC pins, as soil moisture 0-100 % plus raw mV
     its own battery              volts + a rough %, if a battery is fitted

   WHAT IT SENDS (see lib/oat_lora/oat_lora_frame.h — the whole over-air contract)
     A ROSTER frame at boot and every 5th cycle: sub-id -> hardware id, so the
     gateway can name each stream by the probe's own factory address.
     A DATA frame every cycle: sub-id, measurand code, int16 value. Ten sensors
     fit in one ~60-byte frame; more spill into a second.

   SETUP is the USB serial console (115200): `help` lists it. Settings persist in
   NVS. Cadence, radio plan, and every pin are settings — no code editing. The
   radio plan (frequency, bandwidth, spreading factor, sync word) MUST match the
   gateway's; the default plan matches the gateway's default plan.

   OAT-SKETCH-STANDALONE: no Wi-Fi by design, so it cannot link oat_node_core; it
   owns its own console and NVS. check_sketch_contract.py honours this line.

   NAMING LAW: this node names nothing. Stream ids are the sensors' hardware ids;
   the gateway holds the hardware-to-place map, where it survives a reflash.

   LICENSE: openly licensed. Copy it, change it, sell what you build with it.
   ============================================================================= */

#include <Preferences.h>
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <oat_lora_frame.h>
#include <oat_lora_radio.h>

#define FW_SEMVER   "1.0.0"
#define FW_VERSION  "OAT-LoRa-Field-Node/1.0.0"
#define NVS_NS      "oatlfn"

#define MAX_DS      16
#define MAX_SOIL    8
#define SUB_NODE    0
#define SUB_DS0     1      // 1..16
#define SUB_SHT0    17     // 17..18
#define SUB_ADC0    33     // 33..40  (33 = light, 34.. = soil in pin order)

using namespace oatlora;

// ---------------------------------------------------------------------------
// Config — every knob a person may need to turn, persisted in NVS.
// ---------------------------------------------------------------------------
struct Config {
  uint16_t cadence   = 180;                 // seconds between transmits (60 floor)
  float    freq      = DEFAULT_PLAN.freqMHz;
  float    bw        = DEFAULT_PLAN.bwKHz;
  uint8_t  sf        = DEFAULT_PLAN.sf;
  uint8_t  sync      = DEFAULT_PLAN.syncWord;
  int8_t   power     = DEFAULT_PLAN.txDbm;
  int      dsPin     = DEF_DS_PIN;
  int      sda       = DEF_SDA;
  int      scl       = DEF_SCL;
  int      lightPin  = DEF_LIGHT_PIN;
  String   soilPins  = DEF_SOIL_PINS;       // csv of GPIO; "" = none
  int      soilDry   = 2800;                // mV the probe reads in dry air
  int      soilWet   = 1200;                // mV the probe reads in water
  int      battPin   = DEF_BATT_PIN;        // -1 = mains, report no battery
  int      battCtrl  = DEF_BATT_CTRL;
} cfg;
static Preferences prefs;

static void loadConfig() {
  prefs.begin(NVS_NS, true);
  cfg.cadence  = prefs.getUShort("cadence", cfg.cadence);
  cfg.freq     = prefs.getFloat("freq", cfg.freq);
  cfg.bw       = prefs.getFloat("bw", cfg.bw);
  cfg.sf       = prefs.getUChar("sf", cfg.sf);
  cfg.sync     = prefs.getUChar("sync", cfg.sync);
  cfg.power    = prefs.getChar("power", cfg.power);
  cfg.dsPin    = prefs.getInt("dspin", cfg.dsPin);
  cfg.sda      = prefs.getInt("sda", cfg.sda);
  cfg.scl      = prefs.getInt("scl", cfg.scl);
  cfg.lightPin = prefs.getInt("lightpin", cfg.lightPin);
  cfg.soilPins = prefs.getString("soilpins", cfg.soilPins);
  cfg.soilDry  = prefs.getInt("soildry", cfg.soilDry);
  cfg.soilWet  = prefs.getInt("soilwet", cfg.soilWet);
  cfg.battPin  = prefs.getInt("battpin", cfg.battPin);
  cfg.battCtrl = prefs.getInt("battctrl", cfg.battCtrl);
  prefs.end();
  if (cfg.cadence < 60) cfg.cadence = 60;
}
static void saveConfig() {
  prefs.begin(NVS_NS, false);
  prefs.putUShort("cadence", cfg.cadence); prefs.putFloat("freq", cfg.freq); prefs.putFloat("bw", cfg.bw);
  prefs.putUChar("sf", cfg.sf); prefs.putUChar("sync", cfg.sync); prefs.putChar("power", cfg.power);
  prefs.putInt("dspin", cfg.dsPin); prefs.putInt("sda", cfg.sda); prefs.putInt("scl", cfg.scl);
  prefs.putInt("lightpin", cfg.lightPin); prefs.putString("soilpins", cfg.soilPins);
  prefs.putInt("soildry", cfg.soilDry); prefs.putInt("soilwet", cfg.soilWet);
  prefs.putInt("battpin", cfg.battPin); prefs.putInt("battctrl", cfg.battCtrl);
  prefs.end();
}
static RadioPlan planFromConfig() {
  RadioPlan p = DEFAULT_PLAN;
  p.freqMHz = cfg.freq; p.bwKHz = cfg.bw; p.sf = cfg.sf; p.syncWord = cfg.sync; p.txDbm = cfg.power;
  return p;
}

// ---------------------------------------------------------------------------
// Identity: the low 32 bits of the efuse MAC. Hardware-derived, never editable.
// ---------------------------------------------------------------------------
static uint32_t g_unitId = 0;
static uint32_t unitIdFromMac() {
  uint64_t mac = ESP.getEfuseMac();
  return (uint32_t)(mac & 0xFFFFFFFFULL);
}

// ---------------------------------------------------------------------------
// Sensors
// ---------------------------------------------------------------------------
static Radio radio;
static OneWire* ow = nullptr;
static DallasTemperature* ds = nullptr;
struct DsProbe { DeviceAddress rom; bool ok; float t; };
static DsProbe dsProbes[MAX_DS]; static int dsCount = 0;

struct Sht { uint8_t addr; bool present; uint8_t serial[4]; bool haveSerial; float t, h; bool ok; };
static Sht shts[2] = { {0x44, false, {0}, false, 0, 0, false}, {0x45, false, {0}, false, 0, 0, false} };

static int soilPins[MAX_SOIL]; static int soilCount = 0;
static int soilMv[MAX_SOIL]; static int lightMv = -1;
static float battV = 0;

static uint8_t sht_crc8(const uint8_t* d, int n) {
  uint8_t crc = 0xFF;
  for (int i = 0; i < n; i++) { crc ^= d[i]; for (int b = 0; b < 8; b++) crc = (crc & 0x80) ? (uint8_t)((crc << 1) ^ 0x31) : (uint8_t)(crc << 1); }
  return crc;
}
static bool shtCmd(uint8_t addr, uint16_t cmd) {
  Wire.beginTransmission(addr); Wire.write((uint8_t)(cmd >> 8)); Wire.write((uint8_t)(cmd & 0xFF));
  return Wire.endTransmission() == 0;
}
static bool shtRead(uint8_t addr, uint8_t* buf, int n) {
  if (Wire.requestFrom((int)addr, n) != n) return false;
  for (int i = 0; i < n; i++) buf[i] = Wire.read();
  return true;
}
static void shtDiscover() {
  for (int i = 0; i < 2; i++) {
    Sht& s = shts[i];
    Wire.beginTransmission(s.addr); s.present = (Wire.endTransmission() == 0);
    s.haveSerial = false;
    if (!s.present) continue;
    uint8_t b[6];
    if (shtCmd(s.addr, 0x3780)) { delay(2); if (shtRead(s.addr, b, 6) && sht_crc8(b, 2) == b[2] && sht_crc8(b + 3, 2) == b[5]) {
      s.serial[0] = b[0]; s.serial[1] = b[1]; s.serial[2] = b[3]; s.serial[3] = b[4]; s.haveSerial = true; } }
    if (!s.haveSerial) { s.serial[0] = 0; s.serial[1] = 0; s.serial[2] = 0; s.serial[3] = s.addr; }   // "sht30:000000<addr>"
  }
}
static void shtSample() {
  for (int i = 0; i < 2; i++) {
    Sht& s = shts[i]; s.ok = false;
    if (!s.present) continue;
    if (!shtCmd(s.addr, 0x2400)) continue;          // high repeatability, no clock stretch
    delay(20);
    uint8_t b[6];
    if (!shtRead(s.addr, b, 6)) continue;
    if (sht_crc8(b, 2) != b[2] || sht_crc8(b + 3, 2) != b[5]) continue;
    uint16_t rt = ((uint16_t)b[0] << 8) | b[1], rh = ((uint16_t)b[3] << 8) | b[4];
    s.t = -45.0f + 175.0f * rt / 65535.0f; s.h = 100.0f * rh / 65535.0f; s.ok = true;
  }
}

static void parseSoilPins() {
  soilCount = 0;
  String s = cfg.soilPins; s.trim();
  int from = 0;
  while (from < (int)s.length() && soilCount < MAX_SOIL) {
    int comma = s.indexOf(',', from); if (comma < 0) comma = s.length();
    String tok = s.substring(from, comma); tok.trim();
    if (tok.length()) { int p = tok.toInt(); if (p > 0) soilPins[soilCount++] = p; }
    from = comma + 1;
  }
}
static int readMv(int pin) {
  analogSetPinAttenuation(pin, ADC_11db);
  uint32_t mv = 0; for (int i = 0; i < 8; i++) mv += analogReadMilliVolts(pin);
  return (int)(mv / 8);
}

static void sensorsBegin() {
  if (ds) { delete ds; ds = nullptr; } if (ow) { delete ow; ow = nullptr; }
  dsCount = 0;
  if (cfg.dsPin >= 0) {
    ow = new OneWire(cfg.dsPin); ds = new DallasTemperature(ow);
    ds->begin(); ds->setWaitForConversion(false); ds->setResolution(12);
    int n = ds->getDeviceCount();
    for (int i = 0; i < n && dsCount < MAX_DS; i++) {
      DeviceAddress a;
      if (!ds->getAddress(a, i)) continue;
      if (a[0] != 0x28 && a[0] != 0x10) continue;    // temperature families only
      memcpy(dsProbes[dsCount].rom, a, 8); dsProbes[dsCount].ok = false; dsCount++;
    }
  }
  Wire.end(); Wire.begin(cfg.sda, cfg.scl, 100000); Wire.setTimeOut(50);
  shtDiscover();
  parseSoilPins();
}

// A full read: issue the DS conversion, read everything else while it runs, then
// collect. Blocking for ~800 ms is fine here: this node has no web page to serve.
static void sensorsRead() {
  if (ds && dsCount) ds->requestTemperatures();
  shtSample();
  lightMv = cfg.lightPin >= 0 ? readMv(cfg.lightPin) : -1;
  for (int i = 0; i < soilCount; i++) soilMv[i] = readMv(soilPins[i]);
  battV = readBatteryVolts(cfg.battPin, cfg.battCtrl, BATT_MULT);
  if (ds && dsCount) {
    delay(800);
    for (int i = 0; i < dsCount; i++) {
      float t = ds->getTempC(dsProbes[i].rom);
      dsProbes[i].ok = (t != DEVICE_DISCONNECTED_C && t > -55 && t < 125);
      dsProbes[i].t = t;
    }
  }
}

static float soilPercent(int mv) {
  float span = (float)(cfg.soilDry - cfg.soilWet); if (span == 0) return 0;
  float pct = (cfg.soilDry - mv) / span * 100.0f;
  return pct < 0 ? 0 : (pct > 100 ? 100 : pct);
}

// ---------------------------------------------------------------------------
// Frames
// ---------------------------------------------------------------------------
static uint8_t g_seq = 0;
static uint32_t g_txOk = 0, g_txFail = 0, g_cycles = 0;
static Builder fb;

static uint8_t battByte() { return cfg.battPin >= 0 ? batteryPercent(battV) : BATT_NA; }

static bool sendFrame() {
  fb.seal();
  digitalWrite(PIN_LED, HIGH);
  int st = radio.send(fb.buf, fb.len);
  digitalWrite(PIN_LED, LOW);
  if (st == RADIOLIB_ERR_NONE) g_txOk++; else g_txFail++;
  Serial.printf("[tx] seq=%u kind=%u bytes=%u entries=%u -> %s\n", fb.buf[6], fb.buf[7], (unsigned)fb.len, fb.count(), Radio::stateName(st));
  return st == RADIOLIB_ERR_NONE;
}

static void sendRoster() {
  fb.begin(g_unitId, g_seq++, FRAME_ROSTER, battByte(), cfg.cadence);
  uint8_t none = 0;
  fb.addRoster(SUB_NODE, SK_NODE, &none, 0);
  for (int i = 0; i < dsCount; i++) {
    if (!fb.addRoster(SUB_DS0 + i, SK_DS18B20, dsProbes[i].rom, 8)) { sendFrame(); fb.begin(g_unitId, g_seq++, FRAME_ROSTER, battByte(), cfg.cadence); fb.addRoster(SUB_DS0 + i, SK_DS18B20, dsProbes[i].rom, 8); }
  }
  for (int i = 0; i < 2; i++) if (shts[i].present) fb.addRoster(SUB_SHT0 + i, SK_SHT30, shts[i].serial, 4);
  if (cfg.lightPin >= 0) { uint8_t p = (uint8_t)cfg.lightPin; fb.addRoster(SUB_ADC0, SK_ANALOG, &p, 1); }
  for (int i = 0; i < soilCount; i++) { uint8_t p = (uint8_t)soilPins[i]; if (!fb.addRoster(SUB_ADC0 + 1 + i, SK_ANALOG, &p, 1)) { sendFrame(); fb.begin(g_unitId, g_seq++, FRAME_ROSTER, battByte(), cfg.cadence); fb.addRoster(SUB_ADC0 + 1 + i, SK_ANALOG, &p, 1); } }
  sendFrame();
  delay(150 + (esp_random() % 200));   // let the gateway file the roster before data lands
}

static void addOrFlush(uint8_t sub, uint8_t code, double v) {
  const Code* c = codeFor(code); if (!c) return;
  if (!fb.addData(sub, code, encodeValue(*c, v))) {
    sendFrame(); delay(100 + (esp_random() % 150));
    fb.begin(g_unitId, g_seq++, FRAME_DATA, battByte(), cfg.cadence);
    fb.addData(sub, code, encodeValue(*c, v));
  }
}

static void sendData() {
  fb.begin(g_unitId, g_seq++, FRAME_DATA, battByte(), cfg.cadence);
  for (int i = 0; i < dsCount; i++) if (dsProbes[i].ok) addOrFlush(SUB_DS0 + i, 1, dsProbes[i].t);
  for (int i = 0; i < 2; i++) if (shts[i].ok) { addOrFlush(SUB_SHT0 + i, 1, shts[i].t); addOrFlush(SUB_SHT0 + i, 2, shts[i].h); }
  if (lightMv >= 0) addOrFlush(SUB_ADC0, 4, lightMv / 3300.0 * 100.0);
  for (int i = 0; i < soilCount; i++) { addOrFlush(SUB_ADC0 + 1 + i, 3, soilPercent(soilMv[i])); addOrFlush(SUB_ADC0 + 1 + i, 5, soilMv[i]); }
  if (cfg.battPin >= 0 && battV > 0.5f) addOrFlush(SUB_NODE, 6, battV);
  addOrFlush(SUB_NODE, 13, millis() / 1000.0);
  if (fb.count()) sendFrame();
}

static void cycle() {
  g_cycles++;
  sensorsRead();
  if ((g_cycles - 1) % ROSTER_EVERY == 0) sendRoster();
  sendData();
}

// ---------------------------------------------------------------------------
// Console
// ---------------------------------------------------------------------------
static void printStatus() {
  Serial.printf("%s on %s\nunit id %08lx  cadence %us  seq %u  cycles %lu  tx ok %lu fail %lu  uptime %lus\n",
                FW_VERSION, BOARD_NAME, (unsigned long)g_unitId, cfg.cadence, g_seq, (unsigned long)g_cycles,
                (unsigned long)g_txOk, (unsigned long)g_txFail, (unsigned long)(millis() / 1000));
  Serial.printf("radio %.1f MHz bw %.0f kHz sf %u sync 0x%02x %d dBm -> %s\n", cfg.freq, cfg.bw, cfg.sf, cfg.sync, cfg.power, Radio::stateName(radio.lastState));
  Serial.printf("ds18b20 pin %d: %d probe(s)\n", cfg.dsPin, dsCount);
  for (int i = 0; i < dsCount; i++) {
    Serial.printf("  sub %u ds18b20:", SUB_DS0 + i); for (int j = 0; j < 8; j++) Serial.printf("%02x", dsProbes[i].rom[j]);
    if (dsProbes[i].ok) Serial.printf("  %.2f C\n", dsProbes[i].t); else Serial.println("  (no reading yet)");
  }
  Serial.printf("sht30 sda %d scl %d:", cfg.sda, cfg.scl);
  for (int i = 0; i < 2; i++) if (shts[i].present) { Serial.printf("  0x%02x sub %u", shts[i].addr, SUB_SHT0 + i); if (shts[i].ok) Serial.printf(" %.2f C %.1f %%RH", shts[i].t, shts[i].h); }
  Serial.println(shts[0].present || shts[1].present ? "" : "  none found");
  Serial.printf("light pin %d: %d mV\n", cfg.lightPin, lightMv);
  for (int i = 0; i < soilCount; i++) Serial.printf("soil pin %d sub %u: %d mV = %.0f %%\n", soilPins[i], SUB_ADC0 + 1 + i, soilMv[i], soilPercent(soilMv[i]));
  Serial.printf("battery pin %d: %.2f V (%u %%)\n", cfg.battPin, battV, battByte());
}
static void printHelp() {
  Serial.println("commands: help | status | show | set <key> <value> | scan | read | tx | roster | reboot | factory");
  Serial.println("keys: cadence(s) freq(MHz) bw(kHz) sf sync(hex) power(dBm) dspin sda scl lightpin soilpins(csv) soildry(mV) soilwet(mV) battpin battctrl");
  Serial.println("the radio keys must match the gateway; -1 disables a pin");
}
static void printShow() {
  Serial.printf("cadence=%u freq=%.1f bw=%.0f sf=%u sync=0x%02x power=%d dspin=%d sda=%d scl=%d lightpin=%d soilpins=%s soildry=%d soilwet=%d battpin=%d battctrl=%d\n",
                cfg.cadence, cfg.freq, cfg.bw, cfg.sf, cfg.sync, cfg.power, cfg.dsPin, cfg.sda, cfg.scl, cfg.lightPin, cfg.soilPins.c_str(), cfg.soilDry, cfg.soilWet, cfg.battPin, cfg.battCtrl);
}
static bool setKey(const String& k, const String& v) {
  bool radioChanged = false, pinsChanged = false;
  if      (k == "cadence")  { int n = v.toInt(); if (n < 60 || n > 2550) { Serial.println("cadence must be 60..2550 s"); return false; } cfg.cadence = n; }
  else if (k == "freq")     { cfg.freq = v.toFloat(); radioChanged = true; }
  else if (k == "bw")       { cfg.bw = v.toFloat(); radioChanged = true; }
  else if (k == "sf")       { int n = v.toInt(); if (n < 7 || n > 12) { Serial.println("sf must be 7..12"); return false; } cfg.sf = n; radioChanged = true; }
  else if (k == "sync")     { cfg.sync = (uint8_t)strtol(v.c_str(), nullptr, 16); radioChanged = true; }
  else if (k == "power")    { cfg.power = v.toInt(); radioChanged = true; }
  else if (k == "dspin")    { cfg.dsPin = v.toInt(); pinsChanged = true; }
  else if (k == "sda")      { cfg.sda = v.toInt(); pinsChanged = true; }
  else if (k == "scl")      { cfg.scl = v.toInt(); pinsChanged = true; }
  else if (k == "lightpin") { cfg.lightPin = v.toInt(); pinsChanged = true; }
  else if (k == "soilpins") { cfg.soilPins = v; pinsChanged = true; }
  else if (k == "soildry")  { cfg.soilDry = v.toInt(); }
  else if (k == "soilwet")  { cfg.soilWet = v.toInt(); }
  else if (k == "battpin")  { cfg.battPin = v.toInt(); }
  else if (k == "battctrl") { cfg.battCtrl = v.toInt(); }
  else { Serial.println("unknown key; `help` lists them"); return false; }
  saveConfig();
  if (radioChanged) { bool ok = radio.begin(planFromConfig()); Serial.printf("radio re-init: %s\n", Radio::stateName(radio.lastState)); (void)ok; }
  if (pinsChanged) { sensorsBegin(); Serial.printf("rescan: %d ds18b20, sht30 %s%s, %d soil pin(s)\n", dsCount, shts[0].present ? "0x44 " : "", shts[1].present ? "0x45" : "", soilCount); }
  Serial.println("saved");
  return true;
}
static void console() {
  static String line;
  while (Serial.available()) {
    char c = Serial.read();
    if (c == '\r') continue;
    if (c != '\n') { if (line.length() < 200) line += c; continue; }
    line.trim();
    if (line.length()) {
      String cmd = line, rest; int sp = line.indexOf(' ');
      if (sp > 0) { cmd = line.substring(0, sp); rest = line.substring(sp + 1); rest.trim(); }
      if      (cmd == "help")    printHelp();
      else if (cmd == "status")  printStatus();
      else if (cmd == "show")    printShow();
      else if (cmd == "set")     { int s2 = rest.indexOf(' '); if (s2 < 0) Serial.println("set <key> <value>"); else setKey(rest.substring(0, s2), rest.substring(s2 + 1)); }
      else if (cmd == "scan")    { sensorsBegin(); printStatus(); }
      else if (cmd == "read")    { sensorsRead(); printStatus(); }
      else if (cmd == "tx")      { sensorsRead(); sendData(); }
      else if (cmd == "roster")  { sendRoster(); }
      else if (cmd == "reboot")  { ESP.restart(); }
      else if (cmd == "factory") { prefs.begin(NVS_NS, false); prefs.clear(); prefs.end(); Serial.println("cleared; rebooting"); delay(200); ESP.restart(); }
      else Serial.println("unknown command; `help`");
    }
    line = "";
  }
}

// ---------------------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(300);
  pinMode(PIN_LED, OUTPUT); digitalWrite(PIN_LED, LOW);
  loadConfig();
  g_unitId = unitIdFromMac();
  Serial.printf("\n[OAT] %s booting on %s, unit id %08lx\n", FW_VERSION, BOARD_NAME, (unsigned long)g_unitId);
  bool ok = radio.begin(planFromConfig());
  Serial.printf("[OAT] radio %s\n", Radio::stateName(radio.lastState));
  if (!ok) Serial.println("[OAT] the radio did not start. Check the board type this image was built for, then the antenna. Sensors still read; nothing will transmit.");
  sensorsBegin();
  Serial.printf("[OAT] found %d ds18b20, sht30 %s%s, %d soil pin(s), light pin %d\n", dsCount, shts[0].present ? "0x44 " : "", shts[1].present ? "0x45" : "", soilCount, cfg.lightPin);
  Serial.println("[OAT] type `help` for the console");
  randomSeed(g_unitId ^ micros());
  cycle();                                   // first roster + first readings right away
}

static unsigned long g_nextMs = 0;
void loop() {
  console();
  unsigned long now = millis();
  if (g_nextMs == 0) g_nextMs = now + (unsigned long)cfg.cadence * 1000UL + (esp_random() % 3000);
  if ((long)(now - g_nextMs) >= 0) {
    cycle();
    g_nextMs = millis() + (unsigned long)cfg.cadence * 1000UL + (esp_random() % 3000);   // jitter keeps units from lock-stepping
  }
  delay(10);
}
