WEEK 15

Interface and Application Programming

How the week started

I am told with AI's help this week is gonna be one easy week. Hope so!

How the week ended

Well the tool we use is as good as the person who uses it, except some tools. After many conversations with each Ai. I found a method that works with less errors. Never underestimate the time taken to document.!

Week 15’s Assignment

Interface and Application Programming

Group assignment:

  • Compare as many tool options as possible.
  • Document your work on the group work page and reflect on your individual page what you learned.

Link to group assignment

Individual assignment:

  • Write an application for the embedded board that you made that interfaces a user with input and/or output device(s).

My final project requires an app through which the user can choose their friends's location and set time based on that timezone.

I am using my Input sensor week's board which has RTC and Attiny connected with UART pins. Since ATtiny1614 does not have bluetooth or wifi I am pairing it with the breakout board I created in Embedded Networking and Communications week I am using Xiao esp32s3 in that so I can use wifi or bluetooth

This is how I did this week. I used ChatGPT and communicated my idea on what I want. I told the basic flow of how I wanted the communications.

System Block Diagram

"I have an RTC DS1307 connected to ATtiny1614 with UART pins. On another board, I got a XIAO ESP32S3 which has UART pins and an LED. I need the RTC to send time to the XIAO. I need this to be connected to a web application via Bluetooth. When I select a timezone in that, it should change the time in the RTC to that location."

I mostly used conversations like these and shared KiCad schematic images of the boards, then asked for complete code. That approach did not work well. The code had issues, did not run properly, and it became overwhelming because I could not track which AI generated which part. So I decided to work only with ChatGPT to better understand what it handles well and where I need more clarity. I will use Gemini or Claude only when ChatGPT cannot provide a better solution.

The entire conversation with ChatGPT. Click here

I have an attiny 1614 with rtc DS1307 with coin cell. these are connected. now i need the rtc to be programmed that i can get the real time. it should give out the region:time: date> I want the attiny to connect to xiao esp32s3 and give the time. All the timezones have to be added. Now the attiny is connected to xiao via the uart pins. connec the xiao over ble to a webpage which can be opened in phone or mobile. and I get the list of the cities, countires and timezones so i can either type and search for the city, region or time zone, and it shows a dropdown of these options. i need the xiao get get the correct curretn time of the timezone it is in an dhten change to the timezone set. attiny: PA1:LED PA0:UPDI PB2:TXD PB3:RXD PB0:SCL PB1:SDA XIAO ESP32S3 D6:TXD D7:RXD D9:LED tell me what you understood from this, what are the places IN WHICH clarity is need. keep it short , simple and clear.

I discussed this further and shared my preferences based on the questions it asked. This gave me clarity on my choices rather than blindly trusting AI. It helped customize the responses and code, leaving less room for error. I learned this the hard way after trying many code versions without understanding what I was missing while creating prompts.

- Source of correct time is from the web application, sent over Bluetooth - DS1307 is the primary time source after initial sync - The timezone is handled by the web application (recommended) - Data format over UART (this was missing earlier) - ATtiny responsibility: only read/write RTC or also apply timezone offsets (I chose whichever is better) - BLE communication includes full timestamp + timezone (to verify if the change is applied) - Initial setup: runs on first power. After the first connection with XIAO, it gets the current time and runs on it. After that, it continues based on the set timezone - Accuracy concerns: when connected to XIAO, it should get time from the internet - When XIAO and ESP32-S3 are connected, I need that connection over BLE. The connection can last for 2 minutes when powered on or when code is uploaded in Arduino - During that time, the webpage should fetch live time from an online source Is this possible? I need the serial monitor to show these updates when connected to BLE and when BLE is active

After confirming the workflow of the boards, I requested the code in stages. Earlier, when I asked for all the code together, an error in one part meant I had to modify the others as well. So I am now isolating errors and fixing them incrementally, editing as I go.

ATtiny full firmware (RTC + EEPROM + UART)
#include #include #define RTC_ADDR 0x68 // EEPROM address to store offset #define EEPROM_OFFSET_ADDR 0 // Structure for time struct DateTime { uint16_t year; uint8_t month, day, hour, minute, second; }; long timezoneOffset = 0; // ---------- BCD Conversion ---------- uint8_t decToBcd(uint8_t val) { return ((val / 10) << 4) | (val % 10); } uint8_t bcdToDec(uint8_t val) { return ((val >> 4) * 10) + (val & 0x0F); } // ---------- RTC Write ---------- void rtcWrite(DateTime t) { Wire.beginTransmission(RTC_ADDR); Wire.write(0x00); Wire.write(decToBcd(t.second)); Wire.write(decToBcd(t.minute)); Wire.write(decToBcd(t.hour)); Wire.write(1); // day of week (unused) Wire.write(decToBcd(t.day)); Wire.write(decToBcd(t.month)); Wire.write(decToBcd(t.year - 2000)); Wire.endTransmission(); } // ---------- RTC Read ---------- DateTime rtcRead() { DateTime t; Wire.beginTransmission(RTC_ADDR); Wire.write(0x00); Wire.endTransmission(); Wire.requestFrom(RTC_ADDR, 7); t.second = bcdToDec(Wire.read() & 0x7F); t.minute = bcdToDec(Wire.read()); t.hour = bcdToDec(Wire.read()); Wire.read(); // skip day t.day = bcdToDec(Wire.read()); t.month = bcdToDec(Wire.read()); t.year = 2000 + bcdToDec(Wire.read()); return t; } // ---------- Simple epoch conversion ---------- long toEpoch(DateTime t) { long days = t.day; for (int i = 1; i < t.month; i++) { days += 30; // simplified (OK for working system, not perfect calendar) } days += (t.year - 2000) * 365; return days * 86400L + t.hour * 3600L + t.minute * 60L + t.second; } DateTime fromEpoch(long epoch) { DateTime t; t.second = epoch % 60; epoch /= 60; t.minute = epoch % 60; epoch /= 60; t.hour = epoch % 24; epoch /= 24; t.year = 2000 + (epoch / 365); epoch %= 365; t.month = (epoch / 30) + 1; t.day = (epoch % 30); if (t.day == 0) t.day = 1; return t; } // ---------- UART parsing ---------- String inputBuffer = ""; void handleSerial() { while (Serial.available()) { char c = Serial.read(); if (c == '\n') { processCommand(inputBuffer); inputBuffer = ""; } else { inputBuffer += c; } } } // ---------- Process Sync ---------- void processCommand(String cmd) { if (cmd.startsWith("S,")) { int vals[8]; int index = 0; char *token = strtok((char*)cmd.c_str(), ","); while (token != NULL && index < 8) { vals[index++] = atoi(token); token = strtok(NULL, ","); } DateTime utc; utc.year = vals[1]; utc.month = vals[2]; utc.day = vals[3]; utc.hour = vals[4]; utc.minute = vals[5]; utc.second = vals[6]; timezoneOffset = vals[7]; EEPROM.put(EEPROM_OFFSET_ADDR, timezoneOffset); long epoch = toEpoch(utc); epoch += timezoneOffset; DateTime local = fromEpoch(epoch); rtcWrite(local); Serial.println("ACK"); } } // ---------- Setup ---------- void setup() { Serial.begin(9600); Wire.begin(); EEPROM.get(EEPROM_OFFSET_ADDR, timezoneOffset); } // ---------- Loop ---------- void loop() { handleSerial(); DateTime t = rtcRead(); Serial.print("T,"); Serial.print(t.year); Serial.print(","); Serial.print(t.month); Serial.print(","); Serial.print(t.day); Serial.print(","); Serial.print(t.hour); Serial.print(","); Serial.print(t.minute); Serial.print(","); Serial.println(t.second); delay(1000); }
void loop() { handleSerial(); DateTime t = rtcRead(); Serial.print("DATE:"); Serial.print(t.year); Serial.print("/"); Serial.print(t.month); Serial.print("/"); Serial.print(t.day); Serial.print("/"); Serial.print("TIME:"); Serial.print(t.hour); Serial.print(":"); Serial.print(t.minute); Serial.print(":"); Serial.println(t.second); delay(1000); }
void loop() { handleSerial(); DateTime t = rtcRead(); // --- Machine readable --- Serial.print("T,"); Serial.print(t.year); Serial.print(","); Serial.print(t.month); Serial.print(","); Serial.print(t.day); Serial.print(","); Serial.print(t.hour); Serial.print(","); Serial.print(t.minute); Serial.print(","); Serial.println(t.second); // --- Human readable --- Serial.print("DATE:"); Serial.print(t.year); Serial.print("/"); Serial.print(t.month); Serial.print("/"); Serial.print(t.day); Serial.print(" TIME:"); Serial.print(t.hour); Serial.print(":"); Serial.print(t.minute); Serial.print(":"); Serial.println(t.second); delay(1000); }

Troubleshooting - Attiny

After uploading the Arduino code to ATTiny.I prompted ChatGPT for "XIAO-esp32s3 Arduino code," which I got, and the code worked.

Issue pointed out:
At the same time, ChatGPT pointed out that ATtiny wants the local time. What was happening was that the web will already be sending the local time; e.g., it is 10:00 AM now. The ESP receives this and then it thinks it got the UTC, so it converts to IST (10:00 AM will become 3:30 PM). This step shouldn't happen. Because of this, ATtiny receives the wrong time.

ChatGPT fix:
Web will send UTC + offset.
XIAO-ESP32S3: will forward the UTC.
ATtiny will convert it.

XIAO-esp32s3 Arduino code

By now, ChatGPT did give the Arduino code for XIAO ESP32S3.

I was unable to follow the instructions ChatGPT gave me on where to change and delete the code in ATtiny's Arduino code and XIAO ESP32S3's Arduino code. It gave me the choice to fix both codes. So I prompted it to "Fix both sides cleanly."

#include #include #define RTC_ADDR 0x68 #define EEPROM_OFFSET_ADDR 0 struct DateTime { int year, month, day, hour, minute, second; }; long timezoneOffset = 0; // ---------- BCD ---------- uint8_t decToBcd(uint8_t val){ return ((val/10)<<4)|(val%10); } uint8_t bcdToDec(uint8_t val){ return ((val>>4)*10)+(val&0x0F); } // ---------- RTC ---------- void rtcWrite(DateTime t){ Wire.beginTransmission(RTC_ADDR); Wire.write(0x00); Wire.write(decToBcd(t.second)); Wire.write(decToBcd(t.minute)); Wire.write(decToBcd(t.hour)); Wire.write(1); Wire.write(decToBcd(t.day)); Wire.write(decToBcd(t.month)); Wire.write(decToBcd(t.year-2000)); Wire.endTransmission(); } DateTime rtcRead(){ DateTime t; Wire.beginTransmission(RTC_ADDR); Wire.write(0x00); Wire.endTransmission(); Wire.requestFrom(RTC_ADDR,7); t.second=bcdToDec(Wire.read()&0x7F); t.minute=bcdToDec(Wire.read()); t.hour=bcdToDec(Wire.read()); Wire.read(); t.day=bcdToDec(Wire.read()); t.month=bcdToDec(Wire.read()); t.year=2000+bcdToDec(Wire.read()); return t; } // ---------- Calendar ---------- bool isLeap(int y){ return (y%4==0 && y%100!=0) || (y%400==0); } int daysInMonth(int y,int m){ const int d[]={31,28,31,30,31,30,31,31,30,31,30,31}; if(m==2 && isLeap(y)) return 29; return d[m-1]; } long toEpoch(DateTime t){ long days=0; for(int y=2000;y=d){ epoch-=d; year++; } else break; } t.year=year; int month=1; while(true){ int dim=daysInMonth(year,month); if(epoch>=dim){ epoch-=dim; month++; } else break; } t.month=month; t.day=epoch+1; return t; } // ---------- UART ---------- String buffer=""; void process(String cmd){ if(!cmd.startsWith("S,")) return; int v[8]; int i=0; char temp[80]; cmd.toCharArray(temp,80); char* token=strtok(temp,","); while(token && i<8){ v[i++]=atoi(token); token=strtok(NULL,","); } DateTime utc; utc.year=v[1]; utc.month=v[2]; utc.day=v[3]; utc.hour=v[4]; utc.minute=v[5]; utc.second=v[6]; timezoneOffset=v[7]; EEPROM.put(EEPROM_OFFSET_ADDR, timezoneOffset); long epoch=toEpoch(utc); epoch+=timezoneOffset; DateTime local=fromEpoch(epoch); rtcWrite(local); Serial.println("ACK"); } void readSerial(){ while(Serial.available()){ char c=Serial.read(); if(c=='\n'){ process(buffer); buffer=""; } else buffer+=c; } } // ---------- SETUP ---------- void setup(){ Serial.begin(9600); Wire.begin(); EEPROM.get(EEPROM_OFFSET_ADDR, timezoneOffset); } // ---------- LOOP ---------- void loop(){ readSerial(); DateTime t=rtcRead(); // machine format Serial.print("T,"); Serial.print(t.year);Serial.print(","); Serial.print(t.month);Serial.print(","); Serial.print(t.day);Serial.print(","); Serial.print(t.hour);Serial.print(","); Serial.print(t.minute);Serial.print(","); Serial.println(t.second); // readable format Serial.print("DATE:"); Serial.print(t.year);Serial.print("/"); Serial.print(t.month);Serial.print("/"); Serial.print(t.day); Serial.print(" TIME:"); Serial.print(t.hour);Serial.print(":"); Serial.print(t.minute);Serial.print(":"); Serial.println(t.second); delay(1000); }
#include #include #include #include HardwareSerial mySerial(1); #define TX_PIN 6 #define RX_PIN 7 #define LED_PIN 9 #define SERVICE_UUID "12345678-1234-1234-1234-1234567890ab" #define CHARACTERISTIC_UUID "abcd1234-5678-1234-5678-abcdef123456" unsigned long startTime; bool bleActive=true; const unsigned long TIMEOUT=120000; BLECharacteristic* ch; struct DateTime{ int y,m,d,h,mi,s; }; DateTime parseISO(String s){ DateTime t; sscanf(s.c_str(),"%d-%d-%dT%d:%d:%d", &t.y,&t.m,&t.d,&t.h,&t.mi,&t.s); return t; } class CB: public BLECharacteristicCallbacks{ void onWrite(BLECharacteristic *c){ String v=c->getValue(); Serial.println("[BLE RX]"); Serial.println(v); int us=v.indexOf("\"utc\":\"")+7; int ue=v.indexOf("\"",us); String utcStr=v.substring(us,ue); int os=v.indexOf("\"offset\":")+9; int oe=v.indexOf(",",os); long offset=v.substring(os,oe).toInt(); DateTime t=parseISO(utcStr); Serial.println("[SYNC]"); Serial.println(utcStr); Serial.println(offset); char buf[64]; sprintf(buf,"S,%d,%02d,%02d,%02d,%02d,%02d,%ld\n", t.y,t.m,t.d,t.h,t.mi,t.s,offset); mySerial.print(buf); Serial.println("[UART SENT]"); } }; void setup(){ Serial.begin(115200); pinMode(LED_PIN,OUTPUT); digitalWrite(LED_PIN,HIGH); mySerial.begin(9600,SERIAL_8N1,RX_PIN,TX_PIN); BLEDevice::init("TimeSync"); BLEServer* s=BLEDevice::createServer(); BLEService* srv=s->createService(SERVICE_UUID); ch=srv->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_WRITE ); ch->setCallbacks(new CB()); srv->start(); BLEDevice::getAdvertising()->start(); Serial.println("[BLE ON]"); startTime=millis(); } void loop(){ if(bleActive && millis()-startTime>TIMEOUT){ BLEDevice::deinit(true); bleActive=false; digitalWrite(LED_PIN,LOW); Serial.println("[BLE OFF]"); } if(mySerial.available()){ String line=mySerial.readStringUntil('\n'); if(line.startsWith("T,")){ Serial.print("[TIME] "); Serial.println(line); } } }
I also got the web applications's code after I got the Arduino code for XIAO ESP32S3. Promt “web app code” please. thank you"


<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Time Sync</title>

<style>
body {
  font-family: Arial;
  padding: 20px;
}
input, button {
  width: 100%;
  padding: 12px;
  margin-top: 10px;
  font-size: 16px;
}
#list {
  max-height: 200px;
  overflow-y: auto;
  border: 1px solid #ccc;
}
.item {
  padding: 10px;
  border-bottom: 1px solid #eee;
  cursor: pointer;
}
.item:hover {
  background: #f0f0f0;
}
</style>
</head>

<body>

<h2>Time Sync</h2>

<button onclick="connectBLE()">Connect Device</button>

<input id="search" placeholder="Search city / country / timezone" oninput="filterList()">

<div id="list"></div>

<button onclick="sendTime()">Sync Time</button>

<p id="status"></p>

<script>

// ---------- TIMEZONE LIST ----------
const zones = [
  { name: "Asia/Kolkata", offset: 19800 },
  { name: "Europe/London", offset: 0 },
  { name: "America/New_York", offset: -18000 },
  { name: "Asia/Dubai", offset: 14400 },
  { name: "Asia/Singapore", offset: 28800 },
  { name: "Australia/Sydney", offset: 36000 }
];

let selectedZone = null;
let device, characteristic;

// ---------- SHOW LIST ----------
function filterList() {
  const val = document.getElementById("search").value.toLowerCase();
  const list = document.getElementById("list");
  list.innerHTML = "";

  zones
    .filter(z => z.name.toLowerCase().includes(val))
    .forEach(z => {
      const div = document.createElement("div");
      div.className = "item";
      div.innerText = z.name;
      div.onclick = () => {
        selectedZone = z;
        document.getElementById("status").innerText = "Selected: " + z.name;
      };
      list.appendChild(div);
    });
}

// ---------- BLE CONNECT ----------
async function connectBLE() {
  try {
    device = await navigator.bluetooth.requestDevice({
      filters: [{ name: "TimeSync" }],
      optionalServices: ["12345678-1234-1234-1234-1234567890ab"]
    });

    const server = await device.gatt.connect();
    const service = await server.getPrimaryService("12345678-1234-1234-1234-1234567890ab");
    characteristic = await service.getCharacteristic("abcd1234-5678-1234-5678-abcdef123456");

    document.getElementById("status").innerText = "Connected";
  } catch (e) {
    alert("Connection failed");
    console.log(e);
  }
}

// ---------- SEND TIME ----------
async function sendTime() {
  if (!characteristic || !selectedZone) {
    alert("Connect + select timezone");
    return;
  }

  const now = new Date();

  const utc = new Date(now.getTime() - now.getTimezoneOffset()*60000)
                .toISOString()
                .slice(0,19);

  const payload = {
    utc: utc,
    offset: selectedZone.offset,
    tz: selectedZone.name
  };

  const data = JSON.stringify(payload);

  await characteristic.writeValue(new TextEncoder().encode(data));

  document.getElementById("status").innerText =
    "Sent: " + selectedZone.name + " | " + utc;
}

filterList();

</script>

</body>
</html>

Till this it worked well.

I wanted to add more locations and I wanted to know if the time was updated after the sync. so I added the below promt.

“upgrade web app”
i also want the serial monitor of xiao esp32s3 to show the time updated based on the timezones elected. and show the time for every 5 seconds

What Chatgpt asked me to do



<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Time Sync Pro</title>

<style>
body { font-family: Arial; padding: 20px; }
input, button {
  width: 100%;
  padding: 12px;
  margin-top: 10px;
  font-size: 16px;
}
#list {
  max-height: 250px;
  overflow-y: auto;
  border: 1px solid #ccc;
}
.item {
  padding: 10px;
  border-bottom: 1px solid #eee;
  cursor: pointer;
}
.item:hover { background: #f2f2f2; }
</style>
</head>

<body>

<h2>Time Sync</h2>

<button onclick="connectBLE()">Connect Device</button>

<input id="search" placeholder="Search city or timezone" oninput="filterList()">

<div id="list"></div>

<button onclick="sendTime()">Sync Time</button>

<p id="status"></p>

<script>

// -------- FULL TIMEZONE LIST --------
const zones = Intl.supportedValuesOf('timeZone');

let selectedZone = null;
let characteristic = null;

// -------- FILTER --------
function filterList() {
  const val = document.getElementById("search").value.toLowerCase();
  const list = document.getElementById("list");
  list.innerHTML = "";

  zones
    .filter(z => z.toLowerCase().includes(val))
    .slice(0, 50)
    .forEach(z => {
      const div = document.createElement("div");
      div.className = "item";
      div.innerText = z;

      div.onclick = () => {
        selectedZone = z;
        document.getElementById("status").innerText = "Selected: " + z;
      };

      list.appendChild(div);
    });
}

// -------- BLE --------
async function connectBLE() {
  try {
    const device = await navigator.bluetooth.requestDevice({
      filters: [{ name: "TimeSync" }],
      optionalServices: ["12345678-1234-1234-1234-1234567890ab"]
    });

    const server = await device.gatt.connect();
    const service = await server.getPrimaryService("12345678-1234-1234-1234-1234567890ab");
    characteristic = await service.getCharacteristic("abcd1234-5678-1234-5678-abcdef123456");

    document.getElementById("status").innerText = "Connected";
  } catch (e) {
    alert("BLE failed");
  }
}

// -------- SEND TIME --------
async function sendTime() {
  if (!characteristic || !selectedZone) {
    alert("Connect + select timezone");
    return;
  }

  const now = new Date();

  const utc = new Date(now.getTime() - now.getTimezoneOffset()*60000)
    .toISOString()
    .slice(0,19);

  const tzTime = new Date().toLocaleString("en-US", { timeZone: selectedZone });
  const tzDate = new Date(tzTime);

  const offset = (tzDate - new Date(utc)) / 1000;

  const payload = {
    utc: utc,
    offset: offset,
    tz: selectedZone
  };

  await characteristic.writeValue(new TextEncoder().encode(JSON.stringify(payload)));

  document.getElementById("status").innerText =
    "Synced: " + selectedZone;
}

filterList();

</script>

</body>
</html>

I didnt notice an issue which was right in front of me. The pins were named
#define TX_PIN 6
#define RX_PIN 7
#define LED_PIN 9
and therefore there were no data being send. then I remembered and changed the pins to
#define TX_PIN D6
#define RX_PIN D7
#define LED_PIN D9

Download Week 15 Zip File

Zip file contains the prompts, Ardunio codes and html file