Compare commits

..

6 Commits

7 changed files with 131 additions and 30 deletions

View File

@@ -12,3 +12,7 @@ target = "riscv32imac-unknown-none-elf"
[unstable] [unstable]
build-std = ["alloc", "core"] build-std = ["alloc", "core"]
[env]
WIFI_PASSWD = "hunter22"
WIFI_SSID = "fwa"

View File

@@ -2,7 +2,9 @@ use embassy_executor::Spawner;
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use esp_hal::gpio::{Output, OutputConfig}; use esp_hal::gpio::{Output, OutputConfig};
use esp_hal::peripherals::{GPIO3, GPIO14, WIFI}; use esp_hal::peripherals::{GPIO3, GPIO14, WIFI};
use esp_wifi::wifi::{AccessPointConfiguration, Configuration, WifiController, WifiEvent, WifiState}; use esp_wifi::wifi::{
AccessPointConfiguration, Configuration, WifiController, WifiEvent, WifiState,
};
use esp_wifi::{EspWifiRngSource, EspWifiTimerSource, wifi::Interfaces}; use esp_wifi::{EspWifiRngSource, EspWifiTimerSource, wifi::Interfaces};
use static_cell::make_static; use static_cell::make_static;
@@ -46,7 +48,9 @@ async fn connection(mut controller: WifiController<'static>) {
} }
if !matches!(controller.is_started(), Ok(true)) { if !matches!(controller.is_started(), Ok(true)) {
let client_config = Configuration::AccessPoint(AccessPointConfiguration { let client_config = Configuration::AccessPoint(AccessPointConfiguration {
ssid: "esp-wifi".try_into().unwrap(), ssid: env!("WIFI_SSID").try_into().unwrap(),
password: env!("WIFI_PASSWD").try_into().unwrap(),
auth_method: esp_wifi::wifi::AuthMethod::WPA2Personal,
..Default::default() ..Default::default()
}); });
controller.set_configuration(&client_config).unwrap(); controller.set_configuration(&client_config).unwrap();

View File

@@ -66,7 +66,7 @@ impl<T: Persistence> IDStore<T> {
.await .await
} }
async fn persist_mapping(&mut self) { pub async fn persist_mapping(&mut self) {
self.persistence_layer.save_mapping(&self.mapping).await self.persistence_layer.save_mapping(&self.mapping).await
} }

View File

@@ -1,4 +1,5 @@
use esp_println::dbg; use esp_println::dbg;
use log::error;
use picoserve::{ use picoserve::{
extract::{Json, Query, State}, extract::{Json, Query, State},
response::{self, IntoResponse}, response::{self, IntoResponse},
@@ -41,11 +42,23 @@ pub async fn add_mapping(
) -> impl IntoResponse { ) -> impl IntoResponse {
let mut store = state.store.lock().await; let mut store = state.store.lock().await;
store.mapping.add_mapping(data.id, data.name); store.mapping.add_mapping(data.id, data.name);
store.persist_mapping().await;
} }
// SSE /api/idevent // SSE /api/idevent
pub async fn get_idevent(State(state): State<AppState>) -> impl IntoResponse { pub async fn get_idevent(
response::EventStream(IDEvents(state.chan.subscriber().unwrap())) State(state): State<AppState>,
) -> Result<impl IntoResponse, impl IntoResponse> {
match state.chan.subscriber() {
Ok(chan) => Ok(response::EventStream(IDEvents(chan))),
Err(e) => {
error!("Failed to create SSE: {:?}", e);
Err((
response::StatusCode::INTERNAL_SERVER_ERROR,
"Internal server error",
))
}
}
} }
// GET /api/days // GET /api/days
@@ -68,7 +81,6 @@ pub async fn get_day(
State(state): State<AppState>, State(state): State<AppState>,
Query(QueryDay { timestamp, day }): Query<QueryDay>, Query(QueryDay { timestamp, day }): Query<QueryDay>,
) -> Result<impl IntoResponse, impl IntoResponse> { ) -> Result<impl IntoResponse, impl IntoResponse> {
let parsed_day = timestamp let parsed_day = timestamp
.map(Day::new_from_timestamp) .map(Day::new_from_timestamp)
.or_else(|| day.map(Day::new)) .or_else(|| day.map(Day::new))

View File

@@ -55,6 +55,13 @@
"first": "Juniper", "first": "Juniper",
"last": "Voss" "last": "Voss"
} }
],
[
"A0B1DB0D133B",
{
"first": "Öäü",
"last": "ßẞ"
}
] ]
], ],
"days": [ "days": [
@@ -62,7 +69,8 @@
"date": 20372, "date": 20372,
"ids": [ "ids": [
"123456789ABC", "123456789ABC",
"A1B2C3D4E5F6" "A1B2C3D4E5F6",
"A0B1DB0D133B"
] ]
}, },
{ {
@@ -77,7 +85,8 @@
"ids": [ "ids": [
"654321FEDCBA", "654321FEDCBA",
"DEADBEEFCAFE", "DEADBEEFCAFE",
"BADA55C0FFEE" "BADA55C0FFEE",
"A0B1DB0D133B"
] ]
}, },
{ {

View File

@@ -1,32 +1,81 @@
<script lang="ts"> <script lang="ts">
import Modal from "./Modal.svelte"; import Modal from "./Modal.svelte";
let { onSubmitted }: { onSubmitted?: (from: Date, to: Date) => void } = $props(); let { onSubmitted }: { onSubmitted?: (from: Date, to: Date) => void } =
$props();
let modal: Modal; let modal: Modal;
let fromDate: string | undefined = $state(); let fromDate: string | undefined = $state();
let toDate: string | undefined = $state(); let toDate: string | undefined = $state();
let selectedYear: number = $state(new Date().getFullYear());
let selectedTab = $state(0);
export function open() { export function open() {
modal.open(); modal.open();
} }
function generateYears() {
const currentYear = new Date().getFullYear();
const startingYear = 2020;
return Array.from(
new Array(currentYear + 1 - startingYear),
(_, i) => i + startingYear,
);
}
function onsubmit(e: SubmitEvent) { function onsubmit(e: SubmitEvent) {
let from: Date;
let to: Date;
switch (selectedTab) {
case 0:
if (!fromDate || !toDate) { if (!fromDate || !toDate) {
e.preventDefault(); e.preventDefault();
return; return;
} }
let from = new Date(fromDate); from = new Date(fromDate);
let to = new Date(toDate); to = new Date(toDate);
break;
case 1:
from = new Date(selectedYear, 0);
to = new Date(selectedYear + 1, 0);
break;
default:
console.error("Invalid tab");
return;
}
onSubmitted?.(from, to); onSubmitted?.(from, to);
} }
</script> </script>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<div class="flex">
<button
onclick={() => {
selectedTab = 0;
}}
class="tab {selectedTab === 0 ? 'tab-active' : ''}"
>
Datum
</button>
<button
onclick={() => {
selectedTab = 1;
}}
class="tab {selectedTab === 1 ? 'tab-active' : ''}"
>
Jahr
</button>
</div>
<form method="dialog" {onsubmit} class="flex flex-col"> <form method="dialog" {onsubmit} class="flex flex-col">
{#if selectedTab === 0}
<div>
<label class="form-row"> <label class="form-row">
<span>Von:</span> <span>Von:</span>
<input type="date" class="form-input" bind:value={fromDate} /> <input type="date" class="form-input" bind:value={fromDate} />
@@ -36,6 +85,21 @@
<span>Bis:</span> <span>Bis:</span>
<input type="date" class="form-input" bind:value={toDate} /> <input type="date" class="form-input" bind:value={toDate} />
</label> </label>
</div>
{/if}
{#if selectedTab === 1}
<div>
<label class="form-row">
<span>Kalendar Jahr:</span>
<select class="form-input" bind:value={selectedYear}>
{#each generateYears() as year}
<option value={year}>{year}</option>
{/each}
</select>
</label>
</div>
{/if}
<div class="flex justify-end mt-3"> <div class="flex justify-end mt-3">
<button <button
@@ -60,11 +124,19 @@
<style scoped> <style scoped>
@reference "../app.css"; @reference "../app.css";
.tab {
@apply px-4 py-2 rounded-t-lg bg-indigo-600 hover:bg-indigo-700 font-medium border-b-2 border-transparent cursor-pointer transition-colors duration-200;
}
.tab-active {
@apply px-4 py-2 bg-indigo-500 font-semibold border-b-2 border-blue-600 shadow-sm cursor-pointer;
}
.form-row { .form-row {
@apply flex justify-between; @apply flex justify-between my-1;
} }
.form-input { .form-input {
@apply ml-10; @apply ml-20;
} }
</style> </style>

View File

@@ -11,10 +11,10 @@ type InputRows = RowObject[];
export function generateCSVString(input: InputRows, opts: CSVOptions = {}): string { export function generateCSVString(input: InputRows, opts: CSVOptions = {}): string {
const { const {
delimiter = ",", delimiter = ";",
headerOrder, headerOrder,
eol = "\r\n", eol = "\r\n",
includeBOM = false, includeBOM = true,
nullString = "", nullString = "",
} = opts; } = opts;