# Project export: Emergency Safety Widget

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 10.0
- Tagline: Ever feel like someone is following you when you're walking alone at night? With our widget, a simple press will allow you to sound an alarm, share your live location, & help u get to safety quickly.
- Devpost: https://devpost.com/software/emergency-safety-widget
- GitHub: https://github.com/conniely04/safetywidget
- Result: winner (Best Use of Zepp Health)
- Team: 1 GitHub contributor(s) — conniely (3 commits)

## Devpost submission (written by the team)

### Inspiration

As female engineering students in a male-dominated field, we wanted to implement features that could often be overlooked. This includes safety features specifically for when college students feel unsafe walking alone at night.

### What it does

Our app aims to increase safety, specifically for individuals walking alone at night. We designed and implemented an SOS button, automatic rerouting to open locations, real-time tracking, and vibration and sound alarms.

### How we built it

Zepp Health is the company behind the innovative Amazfit Balance watch. We built this widget with the help of Zeus CLI, Google Places API, and libraries written by talented Zepp engineers. We have developed cutting-edge technology that enhances safety and provides safety for late-night walkers.

### Challenges we ran into

There were many challenges when creating this widget. We had challenges when downloading and setting up the simulator used for testing. This was the first challenge when getting started with coding and debugging our widget. Another main challenge we encountered was connecting backend data and the use of APIs to the frontend application.

### Accomplishments we're proud of

Through this project, we accomplished many components of this widget. These accomplishments included enhanced safety by utilizing the advanced features by receiving immediate help when needed with just a press of the SOS button. We also created automatic rerouting to the nearest open place where our app reroutes users to the closest open areas for added security.

### What we learned

We learned how to work with Zepp's Developer Interfaces and different APIs to create various UI elements and fetch sensor data. Using this information, we tied together our system to create a functional app on Amazfit's Balance Watch.

### What's next

Future implementations include sending the live-fetched location to local authorities and directing the user to the calculated safe spot. A back feature to the SOS alert should also be implemented.

## README (from the GitHub repository)


# Amazfit Emergency Safety Widget

Amazfit Emergency Safety Widget is a project designed to provide safety and peace of mind, not only for women but for anyone who needs to travel home at night. Leveraging the power of the Zepp OS platform, this project combines hardware and software to create a reliable companion for your nightly journeys.

## Features

- **Zeus CLI:** Built using the Zeus CLI for Zepp OS, this project integrates seamlessly with your Zepp Balance watch.
- **Real-time Navigation:** Utilizes HTTP requests and the Google Places API to help users find the nearest open and safe locations they can visit in their vicinity.
- **Sound Speaker:** Includes a built-in sound speaker that can be used to play soothing sounds, emergency alerts, or even act as a personal alarm.
- **Customizable for All:** While designed with safety in mind, the  Amazfit Emergency Safety Widget Companion is not limited to any specific gender or group. It's open for anyone who values safety during nighttime travels.

## How It Works

1. The user activates the Amazfit Emergency Safety Widget on their Zepp Balance watch.
2. The project makes HTTP requests to the Google Places API to identify nearby open and safe locations such as restaurants, cafes, or gas stations.
3. It presents the user with a list of these nearby locations along with navigation directions.
4. The sound speaker can be used to play soothing sounds during the journey or act as a personal alarm if needed.

## Getting Started

To get started with the Amazfit Emergency Safety Widget, follow these steps:

1. Ensure you have the Zepp Balance watch.
2. Clone this repository to your development environment.
3. Install the necessary dependencies using the Zeus CLI.
4. Configure your Google Places API key in the project settings.
5. Build and deploy the project to your Zepp Balance watch.
6. Activate the Amazfit Emergency Safety Widget on your watch when needed.

## Contributors

- Connie Ly
- Michelle Duong
- Katherine Yee

## License

(TBA) This project is licensed under the [License Name] - see the LICENSE.md file for details.

## Acknowledgments

We would like to express our gratitude to the open-source community and the creators of Zepp OS, Zeus CLI, and the Google Places API for making this project possible as well countless mentors that helped us along the way.



## Detected evidence (automated analysis)

Indexed codebase: 9 recognized source files, 12 KB.
- JavaScript (language) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (15 of 15)

```
.gitignore
app-side/i18n/en-US.po
app-side/index.js
app.js
app.json
global.d.ts
jsconfig.json
package.json
page/i18n/en-US.po
page/index.js
page/index.r.layout.js
page/index.s.layout.js
README.md
utils/config/constants.js
utils/config/device.js
```

### Dependencies

- package.json: @zeppos/device-types@^3.0.0, @zeppos/zml@^0.0.9

### Recent commits (newest first)

- Update README.md
- Update README.md
- Update README.md
- working files
- Create README.md
- working code
- initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "fetch",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@zeppos/device-types": "^3.0.0"
  },
  "dependencies": {
    "@zeppos/zml": "^0.0.9"
  }
}

```

### app.js

```javascript
import { BaseApp } from "@zeppos/zml/base-app";

App(
  BaseApp({
    globalData: {},
    onCreate(options) {
      console.log("app on create invoke");
    },

    onDestroy(options) {
      console.log("app on destroy invoke");
    },
  })
);

```

### page/index.js

```javascript
import * as hmUI from "@zos/ui";
import AutoGUI from "@silver-zepp/autogui";
import { createWidget, widget, prop, event } from "@zos/ui";
import { Vibrator, VIBRATOR_SCENE_DURATION } from "@zos/sensor";
import { log as Logger } from "@zos/utils";
import { BasePage } from "@zeppos/zml/base-page";
import { Geolocation } from "@zos/sensor";

import {
  FETCH_BUTTON,
  FETCH_RESULT_TEXT,
} from "zosLoader:./index.[pf].layout.js";
import { create, id } from "@zos/media";

const player = create(id.PLAYER);

player.addEventListener(player.event.PREPARE, function (result) {
  if (result) {
    console.log("=== prepare succeed ===");
    player.setVolume(100);
    player.start();
  } else {
    console.log("=== prepare fail ===");
    player.release();
  }
});

player.addEventListener(player.event.COMPLETE, function (result) {
  console.log("=== play end ===");
  player.stop();
  player.release();
});

const gui = new AutoGUI();
const logger = Logger.getLogger("fetch_api");
const vibrator = new Vibrator();
vibrator.setMode(VIBRATOR_SCENE_STRONG_REMINDER);
const geolocation = new Geolocation();
geolocation.start();

const text = createWidget(widget.TEXT, {
  x: 96,
  y: 120,
  w: 288,
  h: 46,
  color: 0xffffff,
  text_size: 36,
  text: "",
});

var lattext = 0;
var lontext = 0;

var callback = () => {
  if (geolocation.getStatus() === "A") {
    lattext = geolocation.getLatitude();
    lontext = geolocation.getLongitude();
  }
};
geolocation.onChange(callback);

let textWidget;

Page(
  BasePage({
    state: {},
    build() {
      const shadowSOS = createWidget(widget.CIRCLE, {
        center_x: 240,
        center_y: 250,
        radius: 150,
        color: 0x3f2222,
        alpha: 255,
      });
      const button = createWidget(widget.BUTTON, {
        x: 100,
        y: 90,
        w: 300,
        h: 300,
        radius: 300,
        normal_color: 0x9e2828,
        press_color: 0x9e2828,
        text: "SOS",
        text_size: 120,
        click_func: (button_widget) => {
          button_widget.setProperty(prop.MORE, {
            x: 90,
            y: 105,
            w: 300,
            h: 300,
            radius: 300,
            TEXT_STYLE,
          });
          hmUI.createWidget(hmUI.widget.TEXT, {
            data,
          });
          player.setSource(player.source.FILE, {
            file: "raw/SOS_Siren.mp3",
          });
          vibrator.start();
          player.prepare();

          const img_hour = createWidget(widget.IMG);
          img_hour.setProperty(prop.MORE, {
            x: 0,
            y: 0,
            w: 500,
            h: 500,
            src: "/raw/mapImage2.png",
          });

          this.fetchData();

          console.log(lattext);
        },
      });
    },
    fetchData() {
      console.log("watch fetch data");

      this.request({
        method: "GET_DATA",
        lat: geolocation.getLatitude(),
        lon: geolocation.getLongitude(),
      })
        .then((data) => {
          console.log("Data:", data);
          const { text } = data;
          console.log(text);

          const textbutt = createWidget(widget.BUTTON, {
            x: (480 - 400) / 2,
            y: 100,
            w: 400,
            h: 50,
            radius: 12,
            text_size: 22,
            color: 0x000000,
            normal_color: 0xbfafaf,
            press_color: 0xbfafaf,
            text: data,
          });
        })
        .catch((res) => {
          console.log("watch fetch data failed");
          console.log(res);
        });
    },
  })
);

```

### app-side/index.js

```javascript
import { BaseSideService } from "@zeppos/zml/base-side";

const padStart = (str, maxLength, fillStr = "0") => {
  return str.toString().padStart(maxLength, fillStr);
};
const formatDate = (date = new Date()) => {
  const y = date.getFullYear();
  const m = date.getMonth() + 1;
  const d = date.getDate();
  const h = date.getHours();
  const mm = date.getMinutes();
  const s = date.getSeconds();

  return `${y}-${padStart(m, 2)}-${padStart(d, 2)} ${padStart(h, 2)}:${padStart(
    mm,
    2
  )}:${padStart(s, 2)}`;
};

// Simulating an asynchronous network request using Promise
async function mockAPI() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        body: {
          data: {
            text: "Location: " + formatDate(),
          },
        },
      });
    }, 1000);
  });
}

async function fetchData(res, lat, lon) {
  try {
    // Requesting network data using the fetch API
    // The sample program is for simulation only and does not request real network data, so it is commented here
    // Example of a GET method request

    const apiKey = "AIzaSyBHqGILdbKvAbh--iHABzjvCl2fURBu_zQ";
    // const latitude = lattext.getCurrent();
    // const longitude = lontext.getCurrent();
    // const radius = 1000;
    // const businessType = "restaurant";
    // const currentTime = new Date().toISOString();
    // const apiUrl = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=${apiKey}&location=${latitude},${longitude}&radius=${radius}&type=${businessType}&opennow=true&keyword=&rankby=distance&time=${currentTime}`;
    const apiUrl =
      "https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=AIzaSyBHqGILdbKvAbh--iHABzjvCl2fURBu_zQ&location=37.7749,-122.4194&radius=1000&type=restaurant";

    const { body: { data = {} } = {} } = await fetch({
      url: apiUrl,
      method: "GET",
    }).then((data) => {
      //returns whole list based on location here
      console.log(data.body.results);
      console.log(lat);
      console.log(lon);
      // find closest range here
      const safelat = [];
      const safelon = [];
      const safeName = [];
      for (let i = 0; i < 20; i++) {
        //change back to true
        if (data.body.results[i].opening_hours.open_now === true) {
          //console.log(data.body.results[i].name);
          safelat.push(data.body.results[i].geometry.location.lat);
          safelon.push(data.body.results[i].geometry.location.lng);
          safeName.push(data.body.results[i].name);

          //res(null, data.body.results[i].name);
        }
      }
      console.log("safe lat: ", safelat);
      console.log("safe lon: ", safelon);

      const diff = [];
      for (let i = 0; i < safelat.length; i++) {
        safelat[i] = Math.abs(safelat[i]);
        safelon[i] = Math.abs(safelon[i]);
        safelat[i] -= Math.abs(lat);
        safelon[i] -= Math.abs(lon);
        diff.push(safelat[i] + safelon[i]);
      }

      let minIndex = 0; // Initialize with the index of the first value

      for (let i = 1; i < diff.length; i++) {
        if (diff[i] < diff[minIndex]) {
          minIndex = i;
        }
      }

      console.log(safeName[minIndex]);
      res(null, safeName[minIndex]);
      /*for (let i = 0; i < 20; i++) {
        //change back to true
        if (data.body.results[i].opening_hours.open_now === false) {
          console.log(data.body.results[i].name);
          const safelat = data.body.results[i].geometry.location.lat;
          const safelon = data.body.results[i].geometry.location.lng;
          console.log("safe lat: ", safelat);
          console.log("safe lon: ", safelon);
          var temp;
          res(null, data.body.results[i].name);
        }
      }*/

      //res(null, safeName[minIndex]);

      //this line returns the name on watch
    });
  } catch (error) {
    console.log(error);
    return res(null, {
      result: "bruh",
    });
  }
}

// A network request is simulated here

AppSideService(
  BaseSideService({
    onInit() {},

    onRequest(req, res) {
      if (req.method === "GET_DATA") {
        console.log("fetch data");
        fetchData(res, req.lat, req.lon);
        console.log("done");
      }
    },

    onRun() {},

    onDestroy() {},
  })
);

```

### global.d.ts

```typescript
/// <reference path="node_modules/@zeppos/device-types/dist/index.d.ts" />

```

### page/index.r.layout.js

```javascript
import * as hmUI from "@zos/ui";
import { px } from "@zos/utils";

import {
  DEFAULT_COLOR,
  DEFAULT_COLOR_TRANSPARENT,
} from "../utils/config/constants";
import { DEVICE_WIDTH } from "../utils/config/device";

export const FETCH_BUTTON = {
  x: (DEVICE_WIDTH - px(400)) / 2,
  y: (DEVICE_HEIGHT - px(400)) / 2,
  w: px(400),
  h: px(100),
  text_size: px(36),
  radius: px(12),
  normal_color: DEFAULT_COLOR,
  press_color: DEFAULT_COLOR_TRANSPARENT,
  text: "Fetch Data",
};

export const FETCH_RESULT_TEXT = {
  x: px(50),
  y: px(100),
  w: DEVICE_WIDTH - 2 * px(50),
  h: px(120),
  color: 0xffffff,
  text_size: px(36),
  align_h: hmUI.align.CENTER_H,
  align_v: hmUI.align.CENTER_V,
  text_style: hmUI.text_style.WRAP,
};

```

### page/index.s.layout.js

```javascript
import * as hmUI from "@zos/ui";
import { px } from "@zos/utils";

import {
  DEFAULT_COLOR,
  DEFAULT_COLOR_TRANSPARENT,
} from "../utils/config/constants";
import { DEVICE_WIDTH } from "../utils/config/device";

export const FETCH_BUTTON = {
  x: (DEVICE_WIDTH - px(340)) / 2,
  y: (DEVICE_HEIGHT - px(400)) / 2,
  w: px(340),
  h: px(100),
  text_size: px(36),
  radius: px(12),
  normal_color: DEFAULT_COLOR,
  press_color: DEFAULT_COLOR_TRANSPARENT,
  text: "Fetch Data",
};

export const FETCH_RESULT_TEXT = {
  x: px(50),
  y: px(100),
  w: DEVICE_WIDTH - 2 * px(50),
  h: px(160),
  color: 0xffffff,
  text_size: px(36),
  align_h: hmUI.align.CENTER_H,
  align_v: hmUI.align.CENTER_V,
  text_style: hmUI.text_style.WRAP,
};

```

### utils/config/constants.js

```javascript
export const DEFAULT_COLOR = 0xfc6950;
export const DEFAULT_COLOR_TRANSPARENT = 0xfeb4a8;

```

### utils/config/device.js

```javascript
import { getDeviceInfo } from "@zos/device";
export const { width: DEVICE_WIDTH, height: DEVICE_HEIGHT } = getDeviceInfo();

```