Compare commits
10 Commits
v1.1.1
...
c4f2006e8f
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c4f2006e8f | ||
![]() |
7b50441da2 | ||
![]() |
940f6ea1b5 | ||
![]() |
a48d1238a2 | ||
![]() |
7aaa0c0aab | ||
![]() |
00bf392931 | ||
![]() |
7d2ad1cd49 | ||
![]() |
307b467ac1 | ||
![]() |
995bf90efb | ||
![]() |
100bab01e4 |
@@ -3,7 +3,7 @@ run-name: ${{ gitea.actor }} build
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-go:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -21,3 +21,17 @@ jobs:
|
||||
run: cd admin && go get .
|
||||
- name: "[admin] Build"
|
||||
run: cd admin && go build -v ./...
|
||||
build-nuxt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Bun 1.2.0
|
||||
run: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.0
|
||||
- name: Display Bun version
|
||||
run: bun --version
|
||||
- name: "[web] Install dependencies"
|
||||
run: cd web && bun install
|
||||
- name: "[web] Build"
|
||||
run: cd web && bun run build
|
@@ -85,6 +85,7 @@ func makePostReq(url string, body any) ([]byte, error) {
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -119,8 +120,8 @@ func main() {
|
||||
Action: test,
|
||||
},
|
||||
{
|
||||
Name: "get",
|
||||
Usage: "get door state",
|
||||
Name: "open",
|
||||
Usage: "get door state (opened or closed)",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "raw",
|
||||
@@ -200,7 +201,7 @@ func getOpened(ctx context.Context, cmd *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
opened, err := makeGetReq(fmt.Sprintf("%s/read", server))
|
||||
opened, err := makeGetReq(fmt.Sprintf("%s/open", server))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -244,7 +245,7 @@ func getAlert(context.Context, *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
alert, err := makeGetReq(fmt.Sprintf("%s/alerts", server))
|
||||
alert, err := makeGetReq(fmt.Sprintf("%s/alert", server))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -305,7 +306,7 @@ func createManageAlert(alert bool) func(context.Context, *cli.Command) error {
|
||||
data.For = secs
|
||||
}
|
||||
|
||||
_, err = makePostReq(fmt.Sprintf("%s/alerts", server), data)
|
||||
_, err = makePostReq(fmt.Sprintf("%s/alert", server), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import gc
|
||||
import utime
|
||||
import ujson
|
||||
import network
|
||||
@@ -7,7 +8,6 @@ from machine import Pin
|
||||
THRESHOLD_DISTANCE = 15 # cm
|
||||
SOUND_SPEED = 340 * 100 # m/s * centi = cm/s
|
||||
MAX_CONNECTION_RETRIES = 50
|
||||
ULTRA_OPENED_THRESHOLD = 3
|
||||
|
||||
|
||||
class App:
|
||||
@@ -18,7 +18,7 @@ class App:
|
||||
|
||||
opened: bool = False
|
||||
previously_opened: bool = False
|
||||
ultra_opened_counter: int = 0
|
||||
wlan: network.WLAN
|
||||
|
||||
led = Pin(15, Pin.OUT)
|
||||
trigger = Pin(2, Pin.OUT)
|
||||
@@ -57,18 +57,18 @@ class App:
|
||||
|
||||
def connect(self):
|
||||
print("Connecting to Wi-Fi...")
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
self.wlan = network.WLAN(network.STA_IF)
|
||||
|
||||
wlan.active(False)
|
||||
self.wlan.active(False)
|
||||
utime.sleep_ms(250)
|
||||
wlan.active(True)
|
||||
self.wlan.active(True)
|
||||
utime.sleep_ms(250)
|
||||
|
||||
wlan.connect(self.ssid, self.password)
|
||||
self.wlan.connect(self.ssid, self.password)
|
||||
utime.sleep_ms(100)
|
||||
|
||||
retry_count = 0
|
||||
while not wlan.isconnected():
|
||||
while not self.wlan.isconnected():
|
||||
if retry_count >= MAX_CONNECTION_RETRIES:
|
||||
print("Max connection retries reached")
|
||||
exit(1)
|
||||
@@ -82,22 +82,14 @@ class App:
|
||||
|
||||
if retry_count % 10 == 0:
|
||||
print("Attempting to restart connection...")
|
||||
wlan.connect(self.ssid, self.password)
|
||||
self.wlan.connect(self.ssid, self.password)
|
||||
for _ in range(10):
|
||||
self.led.toggle()
|
||||
utime.sleep_ms(50)
|
||||
|
||||
print(f"Connected with IP {wlan.ifconfig()[0]}")
|
||||
print(f"Connected with IP {self.wlan.ifconfig()[0]}")
|
||||
self.update_server()
|
||||
|
||||
def health_check_server(self):
|
||||
print("Health checking server...", end="\r")
|
||||
try:
|
||||
r = urequests.get(f"{self.server}/")
|
||||
print(f"Server healthy [{r.status_code}]{" " * 8}")
|
||||
except Exception as e:
|
||||
print(f"Error occurred: {e}")
|
||||
|
||||
def update_server(self):
|
||||
print("Updating state...", end="\r")
|
||||
data = {"opened": self.opened}
|
||||
@@ -113,24 +105,32 @@ class App:
|
||||
except Exception as e:
|
||||
print(f"Error occurred: {e}")
|
||||
|
||||
def measure_distance(self):
|
||||
def measure_distance(self) -> float:
|
||||
self.trigger.low()
|
||||
utime.sleep_us(2)
|
||||
self.trigger.high()
|
||||
utime.sleep_us(10)
|
||||
self.trigger.low()
|
||||
|
||||
start_time = utime.ticks_us()
|
||||
sent_time = utime.ticks_us()
|
||||
while self.echo.value() == 0:
|
||||
print("hey", end="\r")
|
||||
sent_time = utime.ticks_us()
|
||||
if sent_time - start_time >= 100_000: # if it takes more than 100ms, stop
|
||||
return -1
|
||||
|
||||
start_time = utime.ticks_us()
|
||||
received_time = utime.ticks_us()
|
||||
while self.echo.value() == 1:
|
||||
print("ho", end="\r")
|
||||
received_time = utime.ticks_us()
|
||||
if received_time - start_time >= 100_000: # same
|
||||
return -1
|
||||
|
||||
delta_time = received_time - sent_time
|
||||
distance = delta_time / 1_000_000 * SOUND_SPEED / 2
|
||||
print(f"Distance: {distance} cm")
|
||||
print(f"Distance: {distance:0<5} cm; mem_free = {gc.mem_free()}")
|
||||
|
||||
return distance
|
||||
|
||||
@@ -141,38 +141,43 @@ class App:
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
if not self.wlan.isconnected():
|
||||
self.connect()
|
||||
|
||||
distance = self.measure_distance()
|
||||
self.opened = distance >= THRESHOLD_DISTANCE
|
||||
|
||||
if not self.opened:
|
||||
self.led.low()
|
||||
self.ultra_opened_counter = 0
|
||||
|
||||
# was it just closed?
|
||||
if self.previously_opened:
|
||||
self.update_server()
|
||||
self.previously_opened = False
|
||||
else:
|
||||
self.led.high()
|
||||
self.ultra_opened_counter += 1
|
||||
|
||||
if self.ultra_opened_counter >= ULTRA_OPENED_THRESHOLD:
|
||||
# was it just opened? +wait delay
|
||||
if not self.previously_opened:
|
||||
self.led.high()
|
||||
self.update_server()
|
||||
self.previously_opened = True
|
||||
|
||||
if i >= 20:
|
||||
print("Blinking...")
|
||||
self.led.toggle()
|
||||
utime.sleep_ms(100)
|
||||
self.led.toggle()
|
||||
utime.sleep_ms(400)
|
||||
|
||||
i = 0
|
||||
self.health_check_server()
|
||||
gc.collect()
|
||||
else:
|
||||
utime.sleep_ms(500)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fatal exception occurred: {e}")
|
||||
print(f"Fatal error occurred: {e}")
|
||||
|
||||
self.previously_opened = self.opened
|
||||
i += 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
App().run()
|
||||
|
@@ -73,6 +73,10 @@ func main() {
|
||||
LogLevel: log.ERROR,
|
||||
}))
|
||||
app.Use(middleware.Secure())
|
||||
app.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
|
||||
}))
|
||||
|
||||
app.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||
Format: "${time_custom} ${method} ${uri} ---> ${status} in ${latency_human} (${bytes_out} bytes)\n",
|
||||
@@ -213,7 +217,8 @@ func main() {
|
||||
|
||||
func authed(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if c.Request().Header.Get("Authorization") != token {
|
||||
provided := c.Request().Header.Get("Authorization")
|
||||
if provided != fmt.Sprintf("Bearer %s", token) {
|
||||
return c.NoContent(http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
|
24
web/.gitignore
vendored
Normal file
24
web/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
75
web/README.md
Normal file
75
web/README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Nuxt Minimal Starter
|
||||
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
9
web/app.vue
Normal file
9
web/app.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<UApp>
|
||||
<NuxtPage/>
|
||||
</UApp>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import "assets/css/main.css";
|
||||
</style>
|
2
web/assets/css/main.css
Normal file
2
web/assets/css/main.css
Normal file
@@ -0,0 +1,2 @@
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
3
web/assets/css/tailwind.css
Normal file
3
web/assets/css/tailwind.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
1999
web/bun.lock
Normal file
1999
web/bun.lock
Normal file
File diff suppressed because it is too large
Load Diff
6
web/nuxt.config.ts
Normal file
6
web/nuxt.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-05-15',
|
||||
devtools: { enabled: true },
|
||||
modules: ['@nuxt/icon', '@nuxt/ui', '@nuxtjs/tailwindcss']
|
||||
})
|
17
web/pages/index.vue
Normal file
17
web/pages/index.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<main class="flex items-center justify-center min-w-screen min-h-screen">
|
||||
<UButton icon="material-symbols:lock" size="xl" color="primary" variant="solid">Lock</UButton>
|
||||
{{ res }}
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const token = useCookie("token")
|
||||
|
||||
onMounted(() => {
|
||||
if (!token.value) {
|
||||
return navigateTo("/token")
|
||||
}
|
||||
console.log(token.value)
|
||||
})
|
||||
</script>
|
42
web/pages/token.vue
Normal file
42
web/pages/token.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<main class="flex items-center justify-center min-w-screen min-h-screen">
|
||||
<UForm :schema="schema" :state="state" @submit="onSubmit" class="flex flex-col items-end justify-center gap-y-2">
|
||||
<UFormField label="Token" name="token" size="xl" required>
|
||||
<UInput v-model="state.token" placeholder="Your token..." size="xl"/>
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit" size="xl">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as v from "valibot"
|
||||
import type { FormSubmitEvent } from "@nuxt/ui"
|
||||
|
||||
const schema = v.object({
|
||||
token: v.pipe(v.string(), v.nonEmpty("Please enter your token"))
|
||||
})
|
||||
type Schema = v.InferOutput<typeof schema>
|
||||
|
||||
const state = reactive({
|
||||
token: ""
|
||||
})
|
||||
const toast = useToast()
|
||||
|
||||
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
const token = event.data.token
|
||||
const res = await $fetch("https://door.svitan.dev/open", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
|
||||
console.log(token)
|
||||
console.log(res)
|
||||
toast.add({ title: "Token saved", color: "success" })
|
||||
}
|
||||
</script>
|
BIN
web/public/favicon.ico
Normal file
BIN
web/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 264 KiB |
2
web/public/robots.txt
Normal file
2
web/public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow: /
|
3
web/server/tsconfig.json
Normal file
3
web/server/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
11
web/tailwind.config.js
Normal file
11
web/tailwind.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
purge: [],
|
||||
darkMode: "class", // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
4
web/tsconfig.json
Normal file
4
web/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
Reference in New Issue
Block a user