Skip to content
This repository was archived by the owner on Feb 8, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ module.exports = { // eslint-disable-line no-undef
"prefer-const": [
"error",
],
"prefer-template": [
"error",
],
"quote-props": [
"error",
"as-needed",
Expand Down
2 changes: 2 additions & 0 deletions docs/config-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ Supported data types are: bool, uint8_t, int8_t, uint16_t, int16_t, uint32_t, in

The configuration parameter `projectName` is unique in this framework. You can remove it, but if you use a parameter with this name, it will be shown as the header title in the web interface :).

The parameter named `language` is also unique and can be used to change the language of the web interface. Supported languages are placed in the folder `gui/js/lang`. Change the language code and rebuild the HTML interface to change the language. If your language is not yet supported, feel free to create a pull request for it.

For this example, the pre-build python script `preBuildConfig.py` will generate the following two files. These should be fairly self explanatory and show how the JSON file is translated into a C struct.

The `configVersion` is the CRC32 hash of the JSON file. This is added to the C code in order to detect if the JSON has changed compared to the content of the EEPROM, since the `configVersion` will also be stored in the EEPROM. This means that if a field is changed in the JSON, the application will know to reject the current content of the EEPROM since it might not be in accordance to the struct definitions it knows.
Expand Down
14 changes: 14 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,17 @@ else
The command `npm run build` will generate a `html.h` file which contains the full bundle as a PROGMEM byte array to be included when building and flashing the framework to the ESP8266. But manual execution of this command is not needed if you use the `REBUILD_HTML` build flag.

If you have defined a custom location for the configuration JSON file, you need to build the ESP8266 sources at least once after changes in the JSON, so that it will be copied into the HTML folder. Otherwise the development server will not reflect the latest version of the JSON file.

## Changing the language of the web interface

The parameter named `language` in the `configuration.json` file:
```javascript
{
"name": "language",
"type": "char",
"length": 3,
"value": "nl",
"hidden": true
}
```
can be used to change the language of the web interface. Supported languages are placed in the folder `gui/js/lang`. Change the language code and rebuild the HTML interface as described above to change the language. If your language is not yet supported, feel free to create a pull request for it.
13 changes: 10 additions & 3 deletions gui/js/comp/ConfigPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ import { obj2bin } from "../functions/configHelpers";
import { Form, Button } from "./UiComponents";
import { DashboardItems } from "./DashboardItems";

let loc;
if (Config.find(entry => entry.name === "language")) {
loc = require("./../lang/" + Config.find(entry => entry.name === "language").value + ".json");
} else {
loc = require("./../lang/en.json");
}

export function ConfigPage(props) {

useEffect(() => {
document.title = "Configuration";
document.title = loc.titleConf;
}, []);

const confItems = <DashboardItems items={Config} data={props.configData} />;
Expand All @@ -25,7 +32,7 @@ export function ConfigPage(props) {
.then((status) => {
if (status == 200) {props.requestUpdate();}
})
}>Save</Button>;
}>{loc.globalSave}</Button>;
}

const form = <><Form>
Expand All @@ -34,7 +41,7 @@ export function ConfigPage(props) {
{button}
</>;

return <><h2>Configuration</h2><p>{form}</p></>;
return <><h2>{loc.titleConf}</h2><p>{form}</p></>;

function form2bin() {
const newData = {};
Expand Down
10 changes: 9 additions & 1 deletion gui/js/comp/DashboardItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import styled from "styled-components";
import { ControlItem } from "./ControlItem";
import { DisplayItem } from "./DisplayItem";

import Config from "./../configuration.json";
let loc;
if (Config.find(entry => entry.name === "language")) {
loc = require("./../lang/" + Config.find(entry => entry.name === "language").value + ".json");
} else {
loc = require("./../lang/en.json");
}

const Grey = styled.span`
color:#666;
white-space: nowrap;
Expand Down Expand Up @@ -112,7 +120,7 @@ export function DashboardItems(props) {

let confItems;
if (props.items.length == 0) {
confItems = <p>There are no items defined in the JSON file</p>;
confItems = <p>{loc.dashEmpty}</p>;
} else {
for (let i = 0; i < props.items.length; i++) {
if (props.items[i].hidden) {
Expand Down
12 changes: 10 additions & 2 deletions gui/js/comp/DashboardPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,21 @@ const Disconnected = styled.span`
vertical-align:0.3em;
`;

import Config from "./../configuration.json";
let loc;
if (Config.find(entry => entry.name === "language")) {
loc = require("./../lang/" + Config.find(entry => entry.name === "language").value + ".json");
} else {
loc = require("./../lang/en.json");
}

export function DashboardPage(props) {

const [counter, setCounter] = useState(0);
const [socketStatus, setSocketStatus] = useState(0);

useEffect(() => {
document.title = "Dashboard";
document.title = loc.titleDash;
}, []);

useEffect(() => {
Expand All @@ -68,7 +76,7 @@ export function DashboardPage(props) {
</Form>
</>;

return <><h2>Dashboard {socketStatus != 0 ? socketStatus == 1 ? <Live>LIVE</Live> : <Disconnected>DISCONNECTED</Disconnected> : <Connecting>CONNECTING</Connecting>}</h2><p>{form}</p></>;
return <><h2>{loc.titleDash} {socketStatus != 0 ? socketStatus == 1 ? <Live>{loc.dashLive}</Live> : <Disconnected>{loc.dashDisconn}</Disconnected> : <Connecting>{loc.dashConn}</Connecting>}</h2><p>{form}</p></>;

}

Expand Down
27 changes: 17 additions & 10 deletions gui/js/comp/FileListing.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import styled from "styled-components";
import { Fetch, Flex, RedButton, Button, buttonStyle, cPrimary, Alert, Spinner } from "./UiComponents";
import { File, Trash2, Download } from "react-feather";

import Config from "./../configuration.json";
let loc;
if (Config.find(entry => entry.name === "language")) {
loc = require("./../lang/" + Config.find(entry => entry.name === "language").value + ".json");
} else {
loc = require("./../lang/en.json");
}

const FileLine = styled(Flex)`
padding:0.35em 0em;
border-bottom:1px solid #ddd;
Expand Down Expand Up @@ -62,7 +70,6 @@ export function FileListing(props) {
const [state, setState] = useState({ files: [], used: 0, max: 0});

useEffect(() => {
document.title = "File Manager";
fetchData();
}, []);

Expand All @@ -86,7 +93,7 @@ export function FileListing(props) {
}

if (filtered == 0) {
list = <FileLine><div>No files available</div></FileLine>;
list = <FileLine><div>{loc.filesEmpty}</div></FileLine>;
} else {
for (let i = 0; i < state.files.length; i++) {
if (typeof props.filter === "undefined" ||
Expand All @@ -97,10 +104,10 @@ export function FileListing(props) {
<div><File /><span>{state.files[i]}</span></div>
<div>
<a href={`${props.API}/download/${state.files[i]}`} rel="noreferrer" target="_blank" onClick={(e) => { e.stopPropagation();}}>
<Button title="Download file"><Download /></Button>
<Button title={loc.filesDl}><Download /></Button>
</a>
<Fetch href={`${props.API}/api/files/remove?filename=${state.files[i]}`} POST onFinished={fetchData}>
<RedButton title="Remove file" ><Trash2 /></RedButton>
<RedButton title={loc.filesRm} ><Trash2 /></RedButton>
</Fetch>
</div>
</FileLine></>;
Expand All @@ -114,14 +121,14 @@ export function FileListing(props) {

let header;
if (props.selectable) {
header = "Select a file:";
header = loc.filesFwTitle + ":";
} else {
header = "File list";
header = loc.filesTitle;
}

return <><Flex>
<div><Upload action={`${props.API}/upload`} onFinished={fetchData} filter={props.filter} /></div>
{parseInt(state.max) > 0 ? <div>{Math.round(state.used / 1000)} / {Math.round(state.max / 1000)} kB used</div> : ""}
{parseInt(state.max) > 0 ? <div>{Math.round(state.used / 1000)} / {Math.round(state.max / 1000)} kB {loc.filesUsed}</div> : ""}
</Flex><h3>{header}</h3>{list}</>;

}
Expand All @@ -138,7 +145,7 @@ function Upload(props) {
const [state, setState] = useState("");

let status;
if (state == "busy") {status = <><Spinner /></>;} else {status = <>Upload File</>;}
if (state == "busy") {status = <><Spinner /></>;} else {status = <>{loc.filesBtn}</>;}

const render =
<><form action={props.action} method="post" name="upload" encType="multipart/form-data">
Expand Down Expand Up @@ -182,10 +189,10 @@ function Upload(props) {
</form>
<Alert active={state == "nok"}
confirm={() => setState("")}>
The upload has failed. The file could be too large, or it&apos;s name too long ({">"}32).</Alert>
{loc.filesMsg1}</Alert>
<Alert active={state == "wrongtype"}
confirm={() => setState("")}>
The selected file is not of the correct type (.{props.filter})</Alert>
{loc.filesMsg2} (.{props.filter})</Alert>
</>;

return render;
Expand Down
15 changes: 13 additions & 2 deletions gui/js/comp/FilePage.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import React from "react";
import React, { useEffect } from "react";
import PropTypes from "prop-types";

import { FileListing } from "./FileListing";

import Config from "./../configuration.json";
let loc;
if (Config.find(entry => entry.name === "language")) {
loc = require("./../lang/" + Config.find(entry => entry.name === "language").value + ".json");
} else {
loc = require("./../lang/en.json");
}

export function FilePage(props) {

return <><h2>File Manager</h2><FileListing API={props.API} /></>;
useEffect(() => {
document.title = loc.titleFile;
}, []);

return <><h2>{loc.titleFile}</h2><FileListing API={props.API} /></>;

}

Expand Down
42 changes: 27 additions & 15 deletions gui/js/comp/FirmwarePage.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";

import styled from "styled-components";
Expand All @@ -7,6 +7,14 @@ import { FileListing } from "./FileListing";
import { Card, Flex, cSecondary, Button, DisabledButton, Confirmation, Alert, Spinner} from "./UiComponents";
import { Zap, Power } from "react-feather";

import Config from "./../configuration.json";
let loc;
if (Config.find(entry => entry.name === "language")) {
loc = require("./../lang/" + Config.find(entry => entry.name === "language").value + ".json");
} else {
loc = require("./../lang/en.json");
}

const WizardBox = styled.div`
text-align:center;
`;
Expand Down Expand Up @@ -41,6 +49,10 @@ const Wizard = styled(Flex)`

export function FirmwarePage(props) {

useEffect(() => {
document.title = loc.titleFw;
}, []);

const [state, setState] = useState(1);
const [filename, setFilename] = useState("");
const [modal, setModal] = useState(false);
Expand All @@ -52,25 +64,25 @@ export function FirmwarePage(props) {
let buttons;
if (state == 2) {
if (!busy) {
buttons = <Flex><Button onClick={() => setState(1)}>Back</Button><Button onClick={() => setModal(true)}>Update Firmware</Button></Flex>;
step = <WizardBox><h1><Zap /></h1><p>You have selected <b>{filename}</b> to be flashed</p></WizardBox>;
buttons = <Flex><Button onClick={() => setState(1)}>{loc.globalBack}</Button><Button onClick={() => setModal(true)}>{loc.fwBtn}</Button></Flex>;
step = <WizardBox><h1><Zap /></h1><p>{loc.fwStep1a_preFilename} <b>{filename}</b> {loc.fwStep1b_postFilename}</p></WizardBox>;
} else {
buttons = <Flex><DisabledButton>Back</DisabledButton><DisabledButton>Update Firmware</DisabledButton></Flex>;
step = <WizardBox><h1><Spinner /></h1><p>The firmware <b>{filename}</b> is being flashed</p><p><small>Please be patient, this can take a few minutes. Do not turn off the device!</small></p></WizardBox>;
buttons = <Flex><DisabledButton>{loc.globalBack}</DisabledButton><DisabledButton>{loc.fwBtn}</DisabledButton></Flex>;
step = <WizardBox><h1><Spinner /></h1><p>{loc.fwStep2a_preFilename} <b>{filename}</b> {loc.fwStep2b_postFilename}</p><p><small>{loc.fwStep2c}</small></p></WizardBox>;
}
} else if (state == 3) {
step = <WizardBox><h1><Power /></h1><p>The firmware <b>{filename}</b> has been flashed successfully.</p><p>Please restart the device to boot from the new software version.</p><p>
<Button onClick={() => { fetch(`${props.API}/api/restart`, { method: "POST" }); setRestart(true);}}>Restart Now</Button>
step = <WizardBox><h1><Power /></h1><p>{loc.fwStep3a_preFilename} <b>{filename}</b> {loc.fwStep3b_postFilename}</p><p>{loc.fwStep3c}</p><p>
<Button onClick={() => { fetch(`${props.API}/api/restart`, { method: "POST" }); setRestart(true);}}>{loc.fwBtn2}</Button>
</p></WizardBox>;
buttons = <Button onClick={() => setState(1)}>Back</Button>;
buttons = <Button onClick={() => setState(1)}>{loc.globalBack}</Button>;
} else {step = <FileListing API={props.API} selectable={true} onSelect={(name) => {setFilename(name);setState(2);}} filter="bin" />;}

return <><h2>Firmware Update</h2>
return <><h2>{loc.titleFw}</h2>

<Wizard>
<h3 className={state == 1 ? "active" : ""}>1. Select</h3>
<h3 className={state == 2 ? "active" : ""}>2. Flash</h3>
<h3 className={state == 3 ? "active" : ""}>3. Reboot</h3>
<h3 className={state == 1 ? "active" : ""}>1. {loc.fwSelect}</h3>
<h3 className={state == 2 ? "active" : ""}>2. {loc.fwFlash}</h3>
<h3 className={state == 3 ? "active" : ""}>3. {loc.fwReboot}</h3>
</Wizard>

<Card>
Expand All @@ -81,15 +93,15 @@ export function FirmwarePage(props) {

<Confirmation active={modal}
confirm={() => { setModal(false);startFlashing(); }}
cancel={() => setModal(false)}>Are you sure? If you continue, the current software version will be overwritten.</Confirmation>
cancel={() => setModal(false)}>{loc.fwModal1}</Confirmation>

<Alert active={failed}
confirm={() => setFailed(false)}>
The firmware update has failed.</Alert>
{loc.fwModal2}</Alert>

<Alert active={restart}
confirm={() => setRestart(false)}>
The device is restarting. Please wait a few seconds and refresh this page</Alert>
{loc.fwModal3}</Alert>
</>;

function startFlashing() {
Expand Down
14 changes: 11 additions & 3 deletions gui/js/comp/UiComponents.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ export const cHeaderHover = "#333";
export const cSecondary = "#ff00cc";
export const cSecondaryHover = "#cc0099";

import Config from "./../configuration.json";
let loc;
if (Config.find(entry => entry.name === "language")) {
loc = require("./../lang/" + Config.find(entry => entry.name === "language").value + ".json");
} else {
loc = require("./../lang/en.json");
}

export const GlobalStyle = createGlobalStyle`

${normalize}
Expand Down Expand Up @@ -242,8 +250,8 @@ const ConfirmationSrc = ({ active, confirm, cancel, className, children }) => (
onClick={() => cancel()}>
<div onClick={(e) => e.stopPropagation()}><p>{children}</p>
<div>
<CancelButton onClick={() => cancel()}>Cancel</CancelButton>
<Button onClick={() => confirm()}>Continue</Button>
<CancelButton onClick={() => cancel()}>{loc.globalCancel}</CancelButton>
<Button onClick={() => confirm()}>{loc.globalContinue}</Button>
</div>
</div>
</div> : ""
Expand All @@ -266,7 +274,7 @@ const AlertSrc = ({ active, confirm, className, children }) => (
onClick={() => confirm()}>
<div onClick={(e) => e.stopPropagation()}><p>{children}</p>
<div>
<Button onClick={() => confirm()}>OK</Button>
<Button onClick={() => confirm()}>{loc.globalOk}</Button>
</div>
</div>
</div> : ""
Expand Down
Loading