Skip to content

Commit

Permalink
add the demo
Browse files Browse the repository at this point in the history
  • Loading branch information
vnau committed Nov 16, 2024
1 parent 79f8e94 commit df375bd
Show file tree
Hide file tree
Showing 13 changed files with 2,464 additions and 2,231 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,25 @@

![FritzGate screenshot](docs/FritzGate_Screenshot.svg)

## Demo

You can view a demo of the FritzGate interface [here](https://vnau.github.io/FritzGate/).

## Features

- **FRITZ!Box integration:** Connect and control thermostats directly from Fritzbox. FritzGate does not affect weekly plans or other thermostat settings.
- **FRITZ!Box integration:** FritzGate does not affect weekly plans or other thermostat settings. Connect and control thermostats directly from FritzBox.
- **Binding with external Temperature Sensors:** Utilize third party temperature sensors placed in living spaces for precise temperature control in a room. FritzGate automatically corrects temperature offsets of thermostat sensors.
- **Embedded Web Server:** Easily manage thermostat-sensor bindings, monitor temperature and humidity through a user-friendly web interface.

## Advantages

- **Easy Configuration:** Set up effortlessly by scanning a QR code with the Ai Thinker camera for Wi-Fi credentials.
- **Cost-Effective Hardware:** Utilize the budget-friendly and energy efficient ESP32-CAM board.
- **Can be Powered by FRITZ!Box:** Leverage the convenience of powering the device directly from the FRITZ!Box USB port, eliminating the need for additional space and power sources.
- **Optimized for Climate Control with third party Sensors:** Use any of [70+ models](https://decoder.theengs.io/devices/devices.html) of temperature sensors for precise climate control in living spaces, ensuring optimal comfort and energy savings.
- **Cost-Effective Hardware:** Utilize the budget-friendly and energy efficient **ESP32-CAM** board.
- **Powered by FRITZ!Box:** Leverage the convenience of powering the device directly from the *FRITZ!Box** USB port, eliminating the need for additional space and power sources.
- **70+ Supported Sensors:** Use any of [70+ models](https://decoder.theengs.io/devices/devices.html) of temperature sensors for precise climate control in living spaces, ensuring optimal comfort and energy savings.
- **Embedded Web Server:** Easily manage thermostat-sensor bindings through the embedded web server, providing an intuitive interface for users to customize and monitor their smart heating system.
- **Can be used without FRITZ!Box:** If you're not authorized in FritzBox, you can still monitor temperature and humidity from nearby temperature sensors.

This project caters to users looking for a straightforward, cost-effective, and energy-efficient solution for smart thermostat control. With the added flexibility of integrating third party temperature sensors, users can achieve optimal climate control in their living spaces. The easy configuration process and minimal hardware requirements make **FritzGate** an ideal choice for DIY enthusiasts seeking a hassle-free smart home solution.

## Getting Started

### Hardware Requirements
Expand All @@ -28,7 +30,7 @@ This project caters to users looking for a straightforward, cost-effective, and
- [Temperature sensors](https://decoder.theengs.io/devices/devices.html)
- Fritzbox with compatible thermostats (optional)

### Installation Steps
### Installation

1. Clone the repository to your local machine:

Expand Down
7 changes: 7 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@tsconfig/svelte": "^5.0.2",
"bootstrap": "^5.3.2",
"cypress": "^13.6.0",
"jsooner": "^1.0.4",
"sass": "^1.69.5",
"svelte": "^4.2.19",
"svelte-check": "^3.6.0",
Expand Down
File renamed without changes.
51 changes: 25 additions & 26 deletions frontend/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,11 @@
import PageSensors from "./Pages/PageSensors.svelte";
import PageThermostats from "./Pages/PageThermostats.svelte";
import WaitBox from "./lib/WaitBox.svelte";
import type { SensorStatus, StatusData } from "./interfaces";
import type { ApiService, SensorStatus, StatusData } from "./interfaces";
import github from "./assets/github-mark.svg";
const host = window.location.host.split(":")[0];
const apiUrl =
host == "localhost" || host == "127.0.0.1"
? "http://fritzgate/api/"
: host == "vnau.github.io"
? "/FritzGate/api/"
: "/api/";
//const apiUrl = "/api/";
export let api: ApiService;
export let baseUrl: string;
let data: StatusData;
let busy = false;
let timer: NodeJS.Timeout;
Expand All @@ -30,17 +24,14 @@
}
async function fetchData() {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
busy = true;
}, 1000);
try {
const sensorsJson = await fetch(apiUrl + "status", {
signal: controller.signal,
});
const newData: StatusData = await sensorsJson.json();
newData?.sensors?.forEach((s) => {
const newData: StatusData | undefined = await api.fetchStatus();
if (!newData) {
busy = true;
return;
}
newData.sensors?.forEach((s) => {
// fix rssi 0 to undefined (not connected)
s.rssi = s.rssi ? s.rssi : undefined!;
});
Expand All @@ -58,9 +49,9 @@
data = newData;
busy = false;
} catch {
busy = true;
// request failed
} finally {
clearTimeout(timeoutId);
timer = setTimeout(fetchData, 2000);
}
}
Expand All @@ -77,8 +68,12 @@
</ul>
<ul>
<!-- <li><button on:click={requestNotifications}>Notifications</button></li> -->
<li><a use:active class="secondary" href="/sensors">Sensors</a></li>
<li><a use:active class="secondary" href="/heating">Heating</a></li>
<li>
<a use:active class="secondary" href={baseUrl + "/sensors"}>Sensors</a>
</li>
<li>
<a use:active class="secondary" href={baseUrl + "/heating"}>Heating</a>
</li>
<li>
<a
use:active
Expand All @@ -100,9 +95,13 @@
details="this buddy gossips too much with sensors"
/>
{:else}
<Route path="/" redirect="/sensors" />
<Route path="/sensors"><PageSensors {data} /></Route>
<Route path="/heating"><PageThermostats {apiUrl} {data} /></Route>
<Route path="/setup"><PageRouterLogin {apiUrl} {data} /></Route>
<Route path={baseUrl + "/"} redirect={baseUrl + "/sensors"} />
<Route path={baseUrl + "/sensors"}><PageSensors {data} /></Route>
<Route path={baseUrl + "/heating"}
><PageThermostats {api} {data} {baseUrl} /></Route
>
<Route path={baseUrl + "/setup"}
><PageRouterLogin {api} {data} {baseUrl} /></Route
>
{/if}
</main>
63 changes: 46 additions & 17 deletions frontend/src/Pages/PageRouterLogin.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import { router } from "tinro";
import iotSvg from "../assets/iot.svg";
import WaitBox from "../lib/WaitBox.svelte";
import { type ApiService, type StatusData, Status } from "../interfaces";
export let apiUrl: string;
export let data: any;
export let api: ApiService;
export let data: StatusData;
export let baseUrl: string;
let host: string;
let pass: string;
Expand All @@ -14,17 +16,18 @@
let submitForm = async (e: any): Promise<boolean> => {
console.log("submit");
await fetch(apiUrl + "fritzauth", {
method: "POST",
body: JSON.stringify({ host, user, pass }),
});
await api.setConfig(host, user, pass);
status = "CONFIGURED";
return false;
};
$: {
if (status && status !== data?.fritz?.status && data?.fritz?.status === "CONNECTED") {
router.goto("/heating");
if (
status &&
status !== data?.fritz?.status &&
data?.fritz?.status === Status.connected
) {
router.goto(baseUrl + "/heating");
} else {
status = data?.fritz?.status;
host ??= data?.fritz?.host;
Expand All @@ -37,26 +40,34 @@
});
</script>

{#if status === "CONFIGURED" || status === "CONNECTING"}
<WaitBox message="connecting to FRITZ!Box" details="please wait, this may take a while" />
{#if status === Status.configured || status === Status.connecting}
<WaitBox
message="connecting to FRITZ!Box"
details="please wait, this may take a while"
/>
{:else}
<article>
<hgroup>
<div class="message-splash">
<img src={iotSvg} class="wait-image" alt="" />
<h4>
{status === "CONNECTED" ? "update credentials for FRITZ!Box" : "connect to FRITZ!Box"}
{status === "CONNECTED"
? "update credentials for FRITZ!Box"
: "connect to FRITZ!Box"}
</h4>
<p>
Set up a hostname (in most cases <i>fritz.box</i> or
<i>192.168.178.1</i>), username and password to connect to the FRITZ!Box SmartHome.
<i>192.168.178.1</i>), username and password to connect to the
FRITZ!Box SmartHome.
</p>
</div>
</hgroup>
{#if status === "FAILURE"}
{#if status === Status.failure}
<center
><p>
<mark>failed to connect to FRITZ!Box with the provided credentials</mark>
<mark
>failed to connect to FRITZ!Box with the provided credentials</mark
>
</p></center
>
{/if}
Expand All @@ -69,10 +80,28 @@
bind:value={host}
autocomplete="off"
/>
<input type="text" name="login" placeholder="FRITZ!Box username" required bind:value={user} autocomplete="off" />
<input type="password" name="pass" placeholder="Password" required bind:value={pass} autocomplete="off" />
<input
type="text"
name="login"
placeholder="FRITZ!Box username"
required
bind:value={user}
autocomplete="off"
/>
<input
type="password"
name="pass"
placeholder="Password"
required
bind:value={pass}
autocomplete="off"
/>

<input type="submit" value="Connect FritzGate to FRITZ!Box" />
<input
type="submit"
disabled={!host || !user || !pass}
value="Connect FritzGate to FRITZ!Box"
/>
</form>
</article>
{/if}
28 changes: 20 additions & 8 deletions frontend/src/Pages/PageThermostats.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
<script lang="ts">
import type { DeviceStatus } from "../interfaces";
import type {
ApiService,
DeviceStatus,
SensorStatus,
StatusData,
} from "../interfaces";
import DevicesList from "../lib/DevicesList.svelte";
import WaitBox from "../lib/WaitBox.svelte";
import { router } from "tinro";
export let apiUrl: string;
export let data: any;
export let api: ApiService;
export let baseUrl: string;
export let data: StatusData;
let thermostats: DeviceStatus[] = [];
let sensors: DeviceStatus[] = [];
let sensors: SensorStatus[] = [];
var timestamp = 0;
$: {
Expand All @@ -19,16 +25,22 @@
data?.fritz?.status == "CONFIGURED" ||
data?.fritz?.status == "CONNECTING"
) {
router.goto("/setup");
router.goto(baseUrl + "/setup");
}
({ thermostats, sensors } = data);
timestamp = thermostats && thermostats.length > 0 ? Math.max(...thermostats.map((v) => v.seriesTimestamp)) : 0;
timestamp =
thermostats && thermostats.length > 0
? Math.max(...thermostats.map((v) => v.seriesTimestamp))
: 0;
}
}
</script>

{#if !thermostats || thermostats.length === 0}
<WaitBox message="waiting for data from FRITZ!Box" details="can't find any thermostat yet" />
<WaitBox
message="waiting for data from FRITZ!Box"
details="can't find any thermostat yet"
/>
{:else}
<DevicesList devices={thermostats} {sensors} {apiUrl} />
<DevicesList devices={thermostats} {sensors} {api} />
{/if}
24 changes: 24 additions & 0 deletions frontend/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,33 @@ export interface SensorStatus {
export interface StatusData {
sensors: SensorStatus[];
thermostats: DeviceStatus[];
fritz: {
status: string;
host: string;
user: string;
}
}

export interface DeviceStatus extends SensorStatus {
targetTemperature: number;
referenceSensor: string;
}

export interface Binding {
id: string,
referenceSensor: string,
}
export interface ApiService {
fetchStatus(): Promise<StatusData | undefined>;
setBinding(device: string | undefined, reference: string | undefined): Promise<Response>;
setConfig(host: string, user: string, pass: string): Promise<Response>;
}

export class Status {
public static unconfigured = "NOT_CONFIGURED";
public static connected = "CONNECTED";
public static configured = "CONFIGURED";
public static connecting = "CONNECTING";
public static failure = "FAILURE";
}

Loading

0 comments on commit df375bd

Please sign in to comment.