Compare commits

..

15 commits

Author SHA1 Message Date
8209a9a936 minify web content
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-12 08:54:29 +01:00
b10103377d update ignore files 2022-07-04 21:05:22 +02:00
d5c10e441d use hostname in dhcp request
All checks were successful
continuous-integration/drone/push Build is passing
2021-11-21 00:24:19 +01:00
5466bceb0e implement syslog via websocket with 1k buffer 2021-11-18 07:40:08 +01:00
7a627ee1f1 add cli argument for address to otaflash.py 2021-11-18 04:03:53 +01:00
24f5e4398d Add error handling to OTA process
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-12 22:00:59 +02:00
Marcus
8f052ecb37 Added Progress bar and Fixed OTA update
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-12 20:59:09 +02:00
7m9
c0228cdf49 Added gzip compression
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-09 14:30:10 +02:00
5fa1ec14e2 add otaflash.py script to release files
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-09-06 23:21:43 +02:00
09452ef7ab add minimal OTA Update CLI
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-28 23:48:01 +02:00
8874fecf15 add basic OTA Update fucionality
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-28 23:44:01 +02:00
495163060d switch to css only paging
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-08 12:42:07 +02:00
c049c04e8e add pages and menu to web frontend
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-03 02:02:48 +02:00
9e8f14846a add crc32 implementation
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-03 01:37:04 +02:00
0551aff5d1 add some project files
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-29 15:44:42 +02:00
41 changed files with 949 additions and 305804 deletions

View file

@ -9,17 +9,19 @@ steps:
- name: submodules - name: submodules
image: alpine/git image: alpine/git
commands: commands:
- git submodule update --init --recursive - git submodule update --init --recursive --depth 1
- name: firmware - name: firmware
image: docker-repo.service.intern.lab.or.it:5000/fiatlux-build-env image: docker-repo.service.intern.lab.or.it:5000/fiatlux-build-env
depends_on: [ submodules ] depends_on: [ submodules ]
commands: commands:
- export PATH=$(pwd)/modules/sdk/xtensa-lx106-elf/bin:$PATH - export PATH=$(pwd)/modules/sdk/xtensa-lx106-elf/bin:$PATH
- apt update
- apt install -y minify
- make firmware -j$(nproc) - make firmware -j$(nproc)
- name: pcb - name: pcb
image: setsoft/kicad_auto image: setsoft/kicad_auto:ki6
commands: commands:
- apt update - apt update
- apt install -y make zip - apt install -y make zip
@ -57,10 +59,11 @@ steps:
base_url: https://git.neulandlabor.de/ base_url: https://git.neulandlabor.de/
files: files:
- firmware/firmware/fiatlux.bin - firmware/firmware/fiatlux.bin
- firmware/otaflash.py
- pcb/pcb.zip - pcb/pcb.zip
checksum: checksum:
- sha512 - sha512
- md5 - md5
title: buildtest title: fiatlux
when: when:
event: tag event: tag

2
.gitmodules vendored
View file

@ -1,6 +1,6 @@
[submodule "modules/rtos"] [submodule "modules/rtos"]
path = modules/rtos path = modules/rtos
url = https://github.com/SuperHouse/esp-open-rtos.git url = https://git.neulandlabor.de/j3d1/esp-open-rtos.git
[submodule "modules/sdk"] [submodule "modules/sdk"]
path = modules/sdk path = modules/sdk
url = https://github.com/pfalcon/esp-open-sdk.git url = https://github.com/pfalcon/esp-open-sdk.git

View file

@ -1,4 +1,3 @@
.PHONY: firmware flash firmware_docker case pcb .PHONY: firmware flash firmware_docker case pcb
all: firmware case pcb all: firmware case pcb
@ -19,9 +18,14 @@ clean:
+@make -C firmware clean +@make -C firmware clean
+@make -C pcb clean +@make -C pcb clean
flash_docker:
sh -c "docker build -t fiatlux_firmware_env docker/firmware"
sh -c "docker run --volume "$$(pwd)"/firmware:/app/firmware --device=/dev/ttyUSB0 fiatlux_firmware_env make -C firmware flash"
firmware_docker: firmware_docker:
sh -c "docker build -t fiatlux_firmware_env docker/firmware" sh -c "docker build -t fiatlux_firmware_env docker/firmware"
sh -c "docker run --volume "$$(pwd)"/firmware:/app/firmware fiatlux_firmware_env make -C firmware all" sh -c "docker run --volume "$$(pwd)"/firmware:/app/firmware fiatlux_firmware_env make -C firmware html all"
pcb_docker: pcb_docker:
sh -c "docker build -t fiatlux_pcb_env docker/pcb" sh -c "docker build -t fiatlux_pcb_env docker/pcb"

View file

@ -13,7 +13,7 @@ git submodule update --init --recursive
### Build Requirements ### Build Requirements
- make - make
- bash gawk perl - bash gawk
- g++ gcc - g++ gcc
- libc6-dev - libc6-dev
- flex bison - flex bison
@ -27,6 +27,7 @@ git submodule update --init --recursive
- ncurses-dev libexpat-dev - ncurses-dev libexpat-dev
- python3 python3-serial python-dev - python3 python3-serial python-dev
- pip install websocket-client (for otaflash.py, optional)
### Build Steps ### Build Steps

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

View file

@ -6,7 +6,7 @@ RUN cd app; git clone --recursive https://github.com/SuperHouse/esp-open-rtos.gi
RUN cd app; sed -i 's/GNU bash, version (3\\\.\[1-9\]|4)/GNU bash, version (3.[1-9]|4|5)/g' modules/sdk/crosstool-NG/configure.ac; mkdir -p modules/sdk/crosstool-NG/.build/tarballs; wget https://github.com/libexpat/libexpat/releases/download/R_2_1_0/expat-2.1.0.tar.gz -O modules/sdk/crosstool-NG/.build/tarballs/expat-2.1.0.tar.gz RUN cd app; sed -i 's/GNU bash, version (3\\\.\[1-9\]|4)/GNU bash, version (3.[1-9]|4|5)/g' modules/sdk/crosstool-NG/configure.ac; mkdir -p modules/sdk/crosstool-NG/.build/tarballs; wget https://github.com/libexpat/libexpat/releases/download/R_2_1_0/expat-2.1.0.tar.gz -O modules/sdk/crosstool-NG/.build/tarballs/expat-2.1.0.tar.gz
RUN cd app/modules/sdk; export CT_EXPERIMENTAL=y; export CT_ALLOW_BUILD_AS_ROOT=y; export CT_ALLOW_BUILD_AS_ROOT_SURE=y; make standalone=y -j$(nproc); wget -N https://raw.githubusercontent.com/espressif/esptool/master/esptool.py -O xtensa-lx106-elf/bin/esptool.py RUN cd app/modules/sdk; export CT_EXPERIMENTAL=y; export CT_ALLOW_BUILD_AS_ROOT=y; export CT_ALLOW_BUILD_AS_ROOT_SURE=y; make standalone=y -j$(nproc); wget -N https://raw.githubusercontent.com/espressif/esptool/master/esptool.py -O xtensa-lx106-elf/bin/esptool.py
USER 0 USER 0
RUN apt remove --purge -y python2 && apt autoremove --purge -y && apt install -y python3 python3-serial perl RUN apt remove --purge -y python2 && apt autoremove --purge -y && apt install -y python3 python3-serial
RUN apt install -y --reinstall python-is-python3 RUN apt install -y --reinstall python-is-python3
USER 1000 USER 1000
WORKDIR /app WORKDIR /app

4
firmware/.gitignore vendored
View file

@ -142,5 +142,5 @@ dkms.conf
*.remove *.remove
firmware/ firmware/
fsdata/fsdata.c build/
compile_commands.json compile_commands.json

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="External" external.linked.project.id="firmware" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="CompDB" type="CPP_MODULE" version="4" />
<module version="4">
<component name="FacetManager">
<facet type="Python" name="Python facet">
<configuration sdkName="Python 3.9" />
</facet>
</component>
</module>

View file

@ -1,18 +1,21 @@
PROGRAM=fiatlux PROGRAM=fiatlux
EXTRA_CFLAGS=-O3 -Ifsdata EXTRA_CFLAGS=-O3 -Ibuild/gen -DLWIP_NETIF_HOSTNAME=1
EXTRA_COMPONENTS=extras/i2s_dma extras/ws2812_i2s extras/dhcpserver extras/mbedtls extras/httpd extras/sntp extras/cpp_support EXTRA_COMPONENTS=extras/i2s_dma extras/ws2812_i2s extras/dhcpserver extras/rboot-ota extras/mbedtls extras/httpd extras/sntp extras/cpp_support extras/paho_mqtt_c
LIBS = hal m LIBS = hal m
FLASH_MODE = dio FLASH_MODE = qio
include ../modules/rtos/common.mk include ../modules/rtos/common.mk
html: html: build/gen/fsdata.c
build/gen/fsdata.c: webdir/index.html webdir/404.html webdir/css/picnic.min.css webdir/css/style.css webdir/js/smoothie_min.js
@echo "Generating fsdata.." @echo "Generating fsdata.."
cd fsdata && ./makefsdata @mkdir -p $(dir $@)
@./mkwebfs.py --gzip --minify -o $@ $^
test: unittest systest test: unittest systest
@ -22,4 +25,4 @@ unittest:
systest: systest:
true true
.NOTPARALLEL: html all .NOTPARALLEL: html all

71
firmware/crc32.c Normal file
View file

@ -0,0 +1,71 @@
//
// Created by jedi on 02.08.21.
//
#include "crc32.h"
#define UPDC32(octet, crc) (crc_32_tab[((crc)\
^ ((uint8_t)octet)) & 0xff] ^ ((crc) >> 8))
static uint32_t crc_32_tab[] = { /* CRC polynomial 0xedb88320 */
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
uint32_t updateCRC32(uint8_t ch, uint32_t crc) {
return UPDC32(ch, crc);
}
uint32_t crc32_partial(uint32_t init, uint8_t *buf, uint32_t len) {
register uint32_t oldcrc32;
oldcrc32 = ~init;
for (; len; --len, ++buf) {
oldcrc32 = UPDC32(*buf, oldcrc32);
}
return ~oldcrc32;
}
uint32_t crc32(uint8_t *data, uint32_t len) {
return crc32_partial(0, data, len);
}

14
firmware/crc32.h Normal file
View file

@ -0,0 +1,14 @@
//
// Created by jedi on 02.08.21.
//
#ifndef FIRMWARE_CRC32_H
#define FIRMWARE_CRC32_H
#include <stdint.h>
uint32_t crc32_partial(uint32_t init, uint8_t *data, uint32_t len);
uint32_t crc32(uint8_t *data, uint32_t len);
#endif //FIRMWARE_CRC32_H

View file

@ -22,9 +22,9 @@ void user_init(void)
wifi_available_semaphore = xSemaphoreCreateBinary(); wifi_available_semaphore = xSemaphoreCreateBinary();
xTaskCreate(wifi_task, "wifi_task", 512, NULL, 2, NULL); xTaskCreate(wifi_task, "wifi_task", 1024, NULL, 1, NULL);
xTaskCreate(&httpd_task, "httpd_task", 512, NULL, 2, NULL); xTaskCreate(&httpd_task, "httpd_task", 1024, NULL, 2, NULL);
xTaskCreate(&lux_task, "lux_task", 512, NULL, 1, NULL); xTaskCreate(&lux_task, "lux_task", 512, NULL, 1, NULL);
} }

View file

@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="shortcut icon" href="img/favicon.png">
<title>HTTP Server</title>
</head>
<body>
<ul class="navbar">
<li><a href="/">Home</a></li>
<li><a href="websockets">WebSockets</a></li>
<li><a href="about">About</a></li>
</ul>
<div class="grid main">
<h1>404 - Page not found</h1>
<div class="alert alert-error">Sorry, the page you are requesting was not found on this server.</div>
</div>
</body>
</html>

View file

@ -1,62 +0,0 @@
main {
padding: 4em 2em 2em 2em;
max-width: 960px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
section {
display: none;
}
canvas{
width: 100%;
}
main[data-page="dashboard"] section[id="dashboard"] {
display: block;
}
main[data-page="ota"] section[id="ota"] {
display: block;
}
main[data-page="wifi"] section[id="wifi"] {
display: block;
}
main[data-page="io"] section[id="io"] {
display: block;
}
.table {
width: 100%;
display: table;
}
.table>.row{
display: table-row;
}
.table>.row:nth-child(2n) {
background: rgba(17,17,17,0.05);
}
.table>.row>*{
display: table-cell;
padding: .3em 2.4em .3em .6em;
}
.table>header.row>*{
text-align: left;
font-weight: 900;
color: #fff;
background-color: #0074d9;
}
.table>.row>input{
border: none;
background: none;
font-weight: 900;
}
.plain{
opacity: initial;
width: initial;
}

View file

@ -1,114 +0,0 @@
#!/usr/bin/perl
$incHttpHeader = 1;
open(OUTPUT, "> fsdata.c");
print(OUTPUT "#include \"httpd/fsdata.h\"\n\n");
chdir("fs");
open(FILES, "find . -type f |");
while($file = <FILES>) {
# Do not include files in CVS directories nor backup files.
if($file =~ /(CVS|~)/) {
next;
}
chop($file);
if($incHttpHeader == 1) {
open(HEADER, "> /tmp/header") || die $!;
if($file =~ /404/) {
print(HEADER "HTTP/1.0 404 File not found\r\n");
} else {
print(HEADER "HTTP/1.0 200 OK\r\n");
}
print(HEADER "lwIP/1.4.1 (http://savannah.nongnu.org/projects/lwip)\r\n");
if($file =~ /\.html$/ || $file =~ /\.htm$/ || $file =~ /\.shtml$/ || $file =~ /\.shtm$/ || $file =~ /\.ssi$/) {
print(HEADER "Content-type: text/html\r\n");
} elsif($file =~ /\.js$/) {
print(HEADER "Content-type: application/x-javascript\r\n\r\n");
} elsif($file =~ /\.css$/) {
print(HEADER "Content-type: text/css\r\n\r\n");
} elsif($file =~ /\.ico$/) {
print(HEADER "Content-type: image/x-icon\r\n\r\n");
} elsif($file =~ /\.gif$/) {
print(HEADER "Content-type: image/gif\r\n");
} elsif($file =~ /\.png$/) {
print(HEADER "Content-type: image/png\r\n");
} elsif($file =~ /\.jpg$/) {
print(HEADER "Content-type: image/jpeg\r\n");
} elsif($file =~ /\.bmp$/) {
print(HEADER "Content-type: image/bmp\r\n\r\n");
} elsif($file =~ /\.class$/) {
print(HEADER "Content-type: application/octet-stream\r\n");
} elsif($file =~ /\.ram$/) {
print(HEADER "Content-type: audio/x-pn-realaudio\r\n");
} else {
print(HEADER "Content-type: text/plain\r\n");
}
print(HEADER "\r\n");
close(HEADER);
unless($file =~ /\.plain$/ || $file =~ /cgi/) {
system("cat /tmp/header $file > /tmp/file");
} else {
system("cp $file /tmp/file");
}
} else {
system("cp $file /tmp/file");
}
open(FILE, "/tmp/file");
unlink("/tmp/file");
unlink("/tmp/header");
$file =~ s/\.//;
$fvar = $file;
$fvar =~ s-/-_-g;
$fvar =~ s-\.-_-g;
print(OUTPUT "static const unsigned char data".$fvar."[] = {\n");
print(OUTPUT "\t/* $file */\n\t");
for($j = 0; $j < length($file); $j++) {
printf(OUTPUT "0x%02X, ", unpack("C", substr($file, $j, 1)));
}
printf(OUTPUT "0,\n");
$i = 0;
while(read(FILE, $data, 1)) {
if($i == 0) {
print(OUTPUT "\t");
}
printf(OUTPUT "0x%02X, ", unpack("C", $data));
$i++;
if($i == 10) {
print(OUTPUT "\n");
$i = 0;
}
}
print(OUTPUT "};\n\n");
close(FILE);
push(@fvars, $fvar);
push(@files, $file);
}
for($i = 0; $i < @fvars; $i++) {
$file = $files[$i];
$fvar = $fvars[$i];
if($i == 0) {
$prevfile = "NULL";
} else {
$prevfile = "file" . $fvars[$i - 1];
}
print(OUTPUT "const struct fsdata_file file".$fvar."[] = {{\n$prevfile,\ndata$fvar, ");
print(OUTPUT "data$fvar + ". (length($file) + 1) .",\n");
print(OUTPUT "sizeof(data$fvar) - ". (length($file) + 1) .",\n");
print(OUTPUT $incHttpHeader."\n}};\n\n");
}
print(OUTPUT "#define FS_ROOT file$fvars[$i - 1]\n\n");
print(OUTPUT "#define FS_NUMFILES $i\n");

49
firmware/log.cpp Normal file
View file

@ -0,0 +1,49 @@
//
// Created by jedi on 18.11.21.
//
#include "log.h"
#include <espressif/esp_common.h>
constexpr unsigned syslog_buffer_size = 1024;
char syslog_buf[syslog_buffer_size];
volatile unsigned head = 0;
volatile unsigned streams = 0;
extern "C" void syslog(const char *msg) {
printf("syslog> %s", msg);
while (char c = *msg++) {
syslog_buf[head++ % syslog_buffer_size] = c;
}
syslog_buf[head] = 0;
}
unsigned syslog_current_tail() {
if(head < syslog_buffer_size)
return 0;
return head + 1 - syslog_buffer_size;
}
unsigned syslog_data_after(unsigned local_tail) {
if(local_tail > head)
return 0;
return (head % syslog_buffer_size) - (local_tail % syslog_buffer_size);
}
extern "C" int syslog_copy_out(char *out, int len, unsigned local_tail) {
unsigned cnt = 0;
while (cnt < syslog_data_after(local_tail) && cnt < len) {
out[cnt] = syslog_buf[local_tail % syslog_buffer_size + cnt];
cnt++;
}
return cnt;
}
extern "C" void syslog_attach() {
streams++;
}
extern "C" void syslog_detach() {
streams--;
}

28
firmware/log.h Normal file
View file

@ -0,0 +1,28 @@
//
// Created by jedi on 18.11.21.
//
#ifndef FIRMWARE_LOG_H
#define FIRMWARE_LOG_H
#ifdef __cplusplus
extern "C" {
#endif
void syslog(const char *);
unsigned syslog_current_tail();
unsigned syslog_data_after(unsigned);
int syslog_copy_out(char *, int, unsigned);
void syslog_attach();
void syslog_detach();
#ifdef __cplusplus
}
#endif
#endif //FIRMWARE_LOG_H

130
firmware/mkwebfs.py Executable file
View file

@ -0,0 +1,130 @@
#!/usr/bin/env python3
import os
import gzip
import argparse
import subprocess
parser = argparse.ArgumentParser()
parser.add_argument('-o', '--output', help='Output file name', default='stdout')
parser.add_argument('-W', '--webroot', help='Output file name', default='webdir/')
parser.add_argument('--gzip', dest='gzip', action='store_true')
parser.add_argument('--no-gzip', dest='gzip', action='store_false')
parser.set_defaults(gzip=False)
parser.add_argument('--minify', dest='minify', action='store_true')
parser.add_argument('--no-minify', dest='minify', action='store_false')
parser.set_defaults(minify=False)
parser.add_argument('--header', dest='header', action='store_true')
parser.add_argument('--no-header', dest='header', action='store_false')
parser.set_defaults(header=True)
parser.add_argument('input', nargs='+', default=os.getcwd())
args = parser.parse_args()
def mimeFromName(name):
if name.endswith(".html") or name.endswith(".htm") or name.endswith(".shtml") or name.endswith(
".shtm") or name.endswith(".ssi"):
return "text/html"
if name.endswith(".js"):
return "application/x-javascript"
if name.endswith(".css"):
return "text/css"
if name.endswith(".ico"):
return "image/x-icon"
if name.endswith(".gif"):
return "image/gif"
if name.endswith(".png"):
return "image/png"
if name.endswith(".jpg"):
return "image/jpeg"
if name.endswith(".bmp"):
return "image/bmp"
if name.endswith(".class"):
return "application/octet-stream"
if name.endswith(".ram"):
return "audio/x-pn-realaudio"
return "text/plain"
def dumpBin2CHex(f, b):
oStr = "\t"
n = 0
for val in b:
oStr += hex(val) + ", "
n += 1
if n % 8 == 0:
oStr += "\n\t"
oStr += "\n"
f.write(oStr)
f_fsdata_c = open(args.output, 'w')
f_fsdata_c.write('#include "httpd/fsdata.h"\n\n')
httpFiles = [file for file in args.input if (args.webroot in file)]
lastFileStruct = "NULL"
for file in httpFiles:
response = b''
webPath = ("/" + file.removeprefix(args.webroot)).replace("//", "/")
print("{} > {}".format(file, webPath))
mimeType = mimeFromName(file)
if args.header:
if ("404" in file):
response = b'HTTP/1.0 404 File not found\r\n'
else:
response = b'HTTP/1.0 200 OK\r\n'
response += b"lwIP/1.4.1 (http://savannah.nongnu.org/projects/lwip)\r\n"
response += b'Content-type: ' + mimeType.encode() + b'\r\n'
binFile = open(file, 'rb')
binData = binFile.read()
compEff = False
if args.minify:
p = subprocess.Popen(["minify", "--html-keep-document-tags", "--mime", mimeType], stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
minData = p.communicate(binData)[0]
if len(minData) < len(binData):
print("- Minify: {} -> {}".format(len(binData), len(minData)))
compEff = True
binData = minData
if args.gzip:
compData = gzip.compress(binData, 9)
if len(compData) < len(binData):
compEff = True
print("- Compressed from {} to {}".format(len(binData), len(compData)))
binData = compData
else:
print("- Compression skipped Orig: {} Comp: {}".format(len(binData), len(compData)))
binFile.close()
if compEff:
response += b'Content-Encoding: gzip\r\n'
response += b"\r\n"
response += binData
binFile.close()
escFile = file.replace("/", "_").replace(".", "_")
escFileData = "data_" + escFile
escFileFile = "file_" + escFile
f_fsdata_c.write('static const unsigned char {}[] = {{\n'.format(escFileData))
f_fsdata_c.write('\t/* LOCAL:{} */\n'.format(file))
f_fsdata_c.write('\t/* WEB: {} */\n'.format(webPath))
fnameBin = webPath.encode("ascii") + b'\0'
dumpBin2CHex(f_fsdata_c, fnameBin)
dumpBin2CHex(f_fsdata_c, response)
f_fsdata_c.write("};\n\n")
f_fsdata_c.write("const struct fsdata_file {}[] = {{{{\n {},\n {}, {} + {}, sizeof({}) - {}, 1 }}}};\n\n"
.format(escFileFile, lastFileStruct, escFileData, escFileData, len(fnameBin), escFileData,
len(fnameBin)))
# TODO: The last value is 1 if args.header == True
lastFileStruct = escFileFile
f_fsdata_c.write("\n")
f_fsdata_c.write("#define FS_ROOT {}\n\n".format(lastFileStruct))
f_fsdata_c.write("#define FS_NUMFILES {}\n\n".format(len(httpFiles)))

73
firmware/otaflash.py Executable file
View file

@ -0,0 +1,73 @@
#!/usr/bin/env python3
import time
import websocket
import argparse
import zlib
from websocket import WebSocketTimeoutException
parser = argparse.ArgumentParser(description='Update fiatlux firmware via websocket.')
parser.add_argument("binfile")
parser.add_argument("address")
args = parser.parse_args()
bs = 1024
def parse_reply(bytes):
cmd = bytes[0:1].decode("utf-8")
ret = int.from_bytes(bytes[1:2], "big")
val = int.from_bytes(bytes[2:4], "big")
return {'cmd': cmd, 'ret': ret, 'val': val}
with open(args.binfile, "rb") as f:
try:
ws = websocket.WebSocket()
print("send {}".format(args.binfile))
ws.connect("ws://" + args.address)
i = 0
bytes = f.read()
rolling = zlib.crc32(bytes)
total = len(bytes)
while True:
chunk = bytes[bs * i:bs * i + bs]
msg = b'F\x00\x00\x00'
msg += i.to_bytes(2, 'big')
msg += len(chunk).to_bytes(2, 'big')
msg += (zlib.crc32(chunk) & 0xffffffff).to_bytes(4, 'big')
msg += chunk
ws.send(msg)
print("\r{:6.2f}%".format(100 * i * bs / total), end='')
reply = parse_reply(ws.recv())
if reply['cmd'] == 'F' and reply['ret'] == 0:
i += 1
elif reply['cmd'] == 'F' and reply['ret'] == 1:
print("Error: SEQUENCE_OUT_OF_ORDER")
i = reply['val']
elif reply['cmd'] == 'F' and reply['ret'] == 2:
print("Error: CHECKSUM_MISMATCH")
i = reply['val']
else:
print(reply)
time.sleep(0.05)
if len(chunk) != bs:
break
print("\rdone ")
msg = b'C\x00\x00\x00'
msg += total.to_bytes(4, 'big')
msg += (rolling).to_bytes(4, 'big')
ws.settimeout(5)
ws.send(msg)
reply = parse_reply(ws.recv())
print(reply)
ws.close()
except WebSocketTimeoutException:
pass
except ConnectionResetError:
pass
except KeyboardInterrupt:
pass

View file

@ -3,35 +3,120 @@
// //
#include "system.h" #include "system.h"
#include "crc32.h"
#include "log.h"
#include <FreeRTOS.h> #include <FreeRTOS.h>
#include <task.h>
#include <sysparam.h> #include <sysparam.h>
#include <spiflash.h> #include <spiflash.h>
#include <espressif/esp_common.h>
#include <espressif/user_interface.h> #include <espressif/user_interface.h>
#include <espressif/esp_system.h>
#include <rboot/rboot.h>
#include <rboot-ota/rboot-api.h>
#include <string.h>
void system_clear_config(){ #define min(a, b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; })
void system_clear_config() {
vPortEnterCritical(); vPortEnterCritical();
uint32_t num_sectors = 5 + DEFAULT_SYSPARAM_SECTORS; uint32_t num_sectors = 0x2000 / sdk_flashchip.sector_size;
uint32_t start = sdk_flashchip.chip_size - num_sectors * sdk_flashchip.sector_size; uint32_t start = 0x00100000;
for (uint32_t i = 0; i < num_sectors; i++) { for (uint32_t i = 0; i < num_sectors; i++) {
spiflash_erase_sector(start + i * sdk_flashchip.sector_size); spiflash_erase_sector(start + i * sdk_flashchip.sector_size);
} }
if(sysparam_create_area(start, num_sectors, true) == SYSPARAM_OK) {
sysparam_init(start, 0);
}
sysparam_init(start, start + 0x2000);
sdk_system_restart(); sdk_system_restart();
} }
void system_init_config(){ void system_init_config() {
uint32_t base_addr; uint32_t base_addr = 0x00100000;
uint32_t num_sectors; uint32_t num_sectors;
sysparam_init(base_addr, 0);
if(sysparam_get_info(&base_addr, &num_sectors) != SYSPARAM_OK) { if(sysparam_get_info(&base_addr, &num_sectors) != SYSPARAM_OK) {
printf("Warning: WiFi config, sysparam not initialized\n"); syslog("Warning: WiFi config, sysparam not initialized\n");
num_sectors = DEFAULT_SYSPARAM_SECTORS; num_sectors = 0x2000 / sdk_flashchip.sector_size;
base_addr = sdk_flashchip.chip_size - (5 + num_sectors) * sdk_flashchip.sector_size;
if(sysparam_create_area(base_addr, num_sectors, true) == SYSPARAM_OK) { if(sysparam_create_area(base_addr, num_sectors, true) == SYSPARAM_OK) {
sysparam_init(base_addr, 0); sysparam_init(base_addr, 0);
} }
sdk_system_restart(); sdk_system_restart();
} }
}
#define MAX_IMAGE_SIZE 0x100000
struct {
rboot_write_status status;
uint32_t head;
uint32_t base;
uint16_t seq;
uint8_t slot;
} otaflash_context;
void system_otaflash_init() {
rboot_config conf;
conf = rboot_get_config();
otaflash_context.slot = (conf.current_rom + 1) % conf.count;
otaflash_context.base = rboot_get_slot_offset(otaflash_context.slot);
otaflash_context.status = rboot_write_init(otaflash_context.base);
otaflash_context.head = otaflash_context.base;
otaflash_context.seq = 0;
}
enum return_code system_otaflash_chunk(uint8_t *data, uint16_t len, uint16_t seq, uint32_t hash, uint16_t *ack) {
uint32_t local_hash = crc32(data, len);
if(hash == local_hash && otaflash_context.seq == seq) {
if(otaflash_context.head % SECTOR_SIZE == 0) {
sdk_spi_flash_erase_sector(otaflash_context.head / SECTOR_SIZE);
}
if(((uint32_t) data) % 4) {
uint32 buf[len / 4];
memcpy(buf, data, len);
sdk_spi_flash_write(otaflash_context.head, buf, len);
} else {
sdk_spi_flash_write(otaflash_context.head, (uint32_t *) data, len);
}
otaflash_context.head += len;
otaflash_context.seq++;
return OK;
} else if(hash != local_hash) {
return CHECKSUM_MISMATCH;
} else {
if(ack)
*ack = otaflash_context.seq;
return SEQUENCE_OUT_OF_ORDER;
}
}
void system_otaflash_verify_chunk(void *ctx, void *data, size_t len) {
uint32_t digest = *(uint32_t *) ctx;
digest = crc32_partial(digest, data, len);
*(uint32_t *) ctx = digest;
}
enum return_code system_otaflash_verify_and_switch(uint32_t len, uint32_t hash) {
uint32_t digest = 0;
rboot_digest_image(otaflash_context.base, min(len, MAX_IMAGE_SIZE), system_otaflash_verify_chunk, &digest);
if(hash != digest) {
syslog("OTA failed to verify firmware\r\n");
return CHECKSUM_MISMATCH;
}
vPortEnterCritical();
if(!rboot_set_current_rom(otaflash_context.slot)) {
syslog("OTA Update failed to set new rboot slot\r\n");
vPortExitCritical();
return RBOOT_SWITCH_FAILED;
}
vPortExitCritical();
return OK;
} }

View file

@ -5,13 +5,23 @@
#ifndef FIRMWARE_SYSTEM_H #ifndef FIRMWARE_SYSTEM_H
#define FIRMWARE_SYSTEM_H #define FIRMWARE_SYSTEM_H
#include <stdint.h>
#include "types.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
void system_clear_config(); void system_clear_config();
void system_init_config(); void system_init_config();
void system_otaflash_init();
enum return_code system_otaflash_chunk(uint8_t *data, uint16_t len, uint16_t seq, uint32_t hash, uint16_t *ack);
enum return_code system_otaflash_verify_and_switch(uint32_t len, uint32_t hash);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

20
firmware/types.h Normal file
View file

@ -0,0 +1,20 @@
//
// Created by jedi on 09.09.21.
//
#ifndef FIRMWARE_TYPES_H
#define FIRMWARE_TYPES_H
#ifdef __cplusplus
extern "C" {
#endif
enum return_code {
OK = 0, SEQUENCE_OUT_OF_ORDER, CHECKSUM_MISMATCH, RBOOT_SWITCH_FAILED, ERROR = 0xFF
};
#ifdef __cplusplus
}
#endif
#endif //FIRMWARE_TYPES_H

View file

@ -6,6 +6,7 @@
#include "system.h" #include "system.h"
#include "lux.h" #include "lux.h"
#include "wifi.h" #include "wifi.h"
#include "log.h"
#include <cstring> #include <cstring>
#include <FreeRTOS.h> #include <FreeRTOS.h>
@ -36,28 +37,46 @@ void websocket_task(void *pvParameter) {
size_t connstarttime = xTaskGetTickCount(); size_t connstarttime = xTaskGetTickCount();
has_changed = {true, true, true}; has_changed = {true, true, true};
syslog_attach();
unsigned local_log_tail = syslog_current_tail();
for (;;) { for (;;) {
if(pcb == NULL || pcb->state != ESTABLISHED) { if(pcb == nullptr || pcb->state != ESTABLISHED) {
printf("Connection closed, deleting task\n"); syslog("Connection closed, deleting task\n");
break; break;
} }
//Syslog
if(syslog_data_after(local_log_tail) != 0) {
char response[128];
response[0] = 'L';
size_t len = syslog_copy_out(&response[4], 124, local_log_tail);
response[1] = len;
((uint16_t &) response[2]) = local_log_tail & 0xFFFF;
if(len < sizeof(response)) {
LOCK_TCPIP_CORE();
websocket_write(pcb, (unsigned char *) response, len + 4, WS_BIN_MODE);
local_log_tail += len;
UNLOCK_TCPIP_CORE();
} else
syslog("buffer too small -1\n");
vTaskDelayMs(1000);
}
//Global Info //Global Info
if(has_changed.global) { if(has_changed.global) {
has_changed.global = false; timeval tv{};
timeval tv; gettimeofday(&tv, nullptr);
gettimeofday(&tv, NULL); size_t uptime = xTaskGetTickCount() * portTICK_PERIOD_MS / 1000;
int uptime = xTaskGetTickCount() * portTICK_PERIOD_MS / 1000;
int heap = (int) xPortGetFreeHeapSize(); int heap = (int) xPortGetFreeHeapSize();
uint32_t chip_id = sdk_system_get_chip_id(); uint32_t chip_id = sdk_system_get_chip_id();
uint32_t flash_id = sdk_spi_flash_get_id(); uint32_t flash_id = sdk_spi_flash_get_id();
uint32_t flash_size = sdk_flashchip.chip_size >> 10; uint32_t flash_size = sdk_flashchip.chip_size >> 10;
char *hostname = NULL; char *hostname = nullptr;
sysparam_get_string("hostname", &hostname); sysparam_get_string("hostname", &hostname);
/* Generate response in JSON format */ /* Generate response in JSON format */
char response[160]; char response[192];
size_t len = snprintf(response, sizeof(response), size_t len = snprintf(response, sizeof(response),
"{\"walltime\" : \"%d\"," "{\"walltime\" : \"%d\","
"\"uptime\" : \"%d\"," "\"uptime\" : \"%d\","
@ -71,38 +90,34 @@ void websocket_task(void *pvParameter) {
if(len < sizeof(response)) { if(len < sizeof(response)) {
LOCK_TCPIP_CORE(); LOCK_TCPIP_CORE();
websocket_write(pcb, (unsigned char *) response, len, WS_TEXT_MODE); websocket_write(pcb, (unsigned char *) response, len, WS_TEXT_MODE);
has_changed.global = false;
UNLOCK_TCPIP_CORE(); UNLOCK_TCPIP_CORE();
} else } else
printf("buffer too small 1"); syslog("buffer too small 0\n");
vTaskDelayMs(2000); vTaskDelayMs(1000);
} }
//Connection Info //Connection Info
if(has_changed.connection) { if(has_changed.connection) {
has_changed.connection = false; timeval tv{};
timeval tv; gettimeofday(&tv, nullptr);
gettimeofday(&tv, NULL); size_t connuptime = (xTaskGetTickCount() - connstarttime) * portTICK_PERIOD_MS / 1000;
int connuptime = (xTaskGetTickCount() - connstarttime) * portTICK_PERIOD_MS / 1000;
printf("conn %d: " printf("conn %d: " IPSTR " <-> " IPSTR " \n", pcb->netif_idx, IP2STR(&pcb->local_ip),
IPSTR IP2STR(&pcb->remote_ip));
" <-> " char response[192];
IPSTR
" \n", pcb->netif_idx, IP2STR(&pcb->local_ip), IP2STR(&pcb->remote_ip));
char response[160];
size_t len = snprintf(response, sizeof(response), size_t len = snprintf(response, sizeof(response),
"{\"connage\" : \"%d\"," "{\"connage\" : \"%d\","
"\"clientip\" : \"" "\"clientip\" : \"" IPSTR "\""
IPSTR
"\""
"}", connuptime, IP2STR(&pcb->remote_ip)); "}", connuptime, IP2STR(&pcb->remote_ip));
if(len < sizeof(response)) { if(len < sizeof(response)) {
LOCK_TCPIP_CORE(); LOCK_TCPIP_CORE();
websocket_write(pcb, (unsigned char *) response, len, WS_TEXT_MODE); websocket_write(pcb, (unsigned char *) response, len, WS_TEXT_MODE);
has_changed.connection = false;
UNLOCK_TCPIP_CORE(); UNLOCK_TCPIP_CORE();
} else } else
printf("buffer too small 1"); syslog("buffer too small 1\n");
vTaskDelayMs(2000); vTaskDelayMs(1000);
} }
if(has_changed.wifi) { if(has_changed.wifi) {
@ -137,10 +152,10 @@ void websocket_task(void *pvParameter) {
if(opmode == SOFTAP_MODE || opmode == STATIONAP_MODE) { if(opmode == SOFTAP_MODE || opmode == STATIONAP_MODE) {
uint8_t hwaddr[6]; uint8_t hwaddr[6];
sdk_wifi_get_macaddr(SOFTAP_IF, hwaddr); sdk_wifi_get_macaddr(SOFTAP_IF, hwaddr);
ip_info info; ip_info info{};
sdk_wifi_get_ip_info(SOFTAP_IF, &info); sdk_wifi_get_ip_info(SOFTAP_IF, &info);
char *apssid = NULL; char *apssid = nullptr;
sysparam_get_string("wifi_ap_ssid", &apssid); sysparam_get_string("wifi_ap_ssid", &apssid);
/* Generate response in JSON format */ /* Generate response in JSON format */
@ -148,12 +163,8 @@ void websocket_task(void *pvParameter) {
size_t len = snprintf(response, sizeof(response), size_t len = snprintf(response, sizeof(response),
"{\"opmode\" : \"%s\"," "{\"opmode\" : \"%s\","
" \"apssid\" : \"%s\"," " \"apssid\" : \"%s\","
" \"apip\" : \"" " \"apip\" : \"" IPSTR "\","
IPSTR " \"apmac\" : \"" MACSTR "\""
"\","
" \"apmac\" : \""
MACSTR
"\""
"}", opmode_str, apssid, IP2STR(&info.ip), MAC2STR(hwaddr)); "}", opmode_str, apssid, IP2STR(&info.ip), MAC2STR(hwaddr));
free(apssid); free(apssid);
if(len < sizeof(response)) { if(len < sizeof(response)) {
@ -161,15 +172,15 @@ void websocket_task(void *pvParameter) {
websocket_write(pcb, (unsigned char *) response, len, WS_TEXT_MODE); websocket_write(pcb, (unsigned char *) response, len, WS_TEXT_MODE);
UNLOCK_TCPIP_CORE(); UNLOCK_TCPIP_CORE();
} else } else
printf("buffer too small 2"); syslog("buffer too small 2\n");
} }
vTaskDelayMs(2000); vTaskDelayMs(1000);
if(opmode == STATION_MODE || opmode == STATIONAP_MODE) { if(opmode == STATION_MODE || opmode == STATIONAP_MODE) {
uint8_t hwaddr[6]; uint8_t hwaddr[6];
sdk_wifi_get_macaddr(STATION_IF, hwaddr); sdk_wifi_get_macaddr(STATION_IF, hwaddr);
ip_info info; ip_info info{};
sdk_wifi_get_ip_info(STATION_IF, &info); sdk_wifi_get_ip_info(STATION_IF, &info);
char *stassid = nullptr; char *stassid = nullptr;
sysparam_get_string("wifi_sta_ssid", &stassid); sysparam_get_string("wifi_sta_ssid", &stassid);
@ -179,12 +190,8 @@ void websocket_task(void *pvParameter) {
size_t len = snprintf(response, sizeof(response), size_t len = snprintf(response, sizeof(response),
"{\"opmode\" : \"%s\"," "{\"opmode\" : \"%s\","
" \"stassid\" : \"%s\"," " \"stassid\" : \"%s\","
" \"staip\" : \"" " \"staip\" : \"" IPSTR "\","
IPSTR " \"stamac\" : \"" MACSTR "\""
"\","
" \"stamac\" : \""
MACSTR
"\""
"}", opmode_str, stassid, IP2STR(&info.ip), MAC2STR(hwaddr)); "}", opmode_str, stassid, IP2STR(&info.ip), MAC2STR(hwaddr));
free(stassid); free(stassid);
if(len < sizeof(response)) { if(len < sizeof(response)) {
@ -192,62 +199,121 @@ void websocket_task(void *pvParameter) {
websocket_write(pcb, (unsigned char *) response, len, WS_TEXT_MODE); websocket_write(pcb, (unsigned char *) response, len, WS_TEXT_MODE);
UNLOCK_TCPIP_CORE(); UNLOCK_TCPIP_CORE();
} else } else
printf("buffer too small 3"); syslog("buffer too small 3\n");
} }
} }
vTaskDelayMs(500); vTaskDelayMs(500);
{
uint8_t response[3];
uint16_t val = 0;
val = sdk_system_adc_read();
response[2] = (uint8_t) val;
response[1] = val >> 8;
response[0] = 'V';
websocket_write(pcb, response, 3, WS_BIN_MODE);
}
vTaskDelayMs(500);
} }
vTaskDelete(NULL); syslog_detach();
vTaskDelete(nullptr);
} }
struct fw_frame {
char t;
uint8_t reserved[3];
uint16_t seq;
uint16_t len;
uint32_t hash;
uint8_t data[];
} __attribute__((packed));
struct fw_check {
char t;
uint8_t reserved[3];
uint32_t len;
uint32_t hash;
} __attribute__((packed));
/** /**
* This function is called when websocket frame is received. * This function is called when websocket frame is received.
* *
* Note: this function is executed on TCP thread and should return as soon * Note: this function is executed on TCP thread and should return as soon
* as possible. * as possible.
*/ */
void websocket_cb(struct tcp_pcb *pcb, char *data, u16_t data_len, uint8_t mode) { void websocket_cb(struct tcp_pcb *pcb, char *data, u16_t data_len,
uint8_t /*mode*/) { //mode should be WS_BIN_MODE or WS_TEXT_MODE
uint8_t response[3]; uint8_t response[4];
uint16_t val = 0; auto &cmd = (char &) response[0];
char cmd = '0'; auto &ret = response[1];
auto &val = (uint16_t &) response[2];
cmd = '0';
ret = ERROR;
val = 0;
bool togl = false;
switch (data[0]) { switch (data[0]) {
case 'R': // Restart
cmd = 'R';
ret = OK;
break;
case 'X': // Clear Config
cmd = 'X';
ret = OK;
break;
case 'D': // Disable LED case 'D': // Disable LED
syslog("G\n");
signal_led(false); signal_led(false);
val = 1;
cmd = 'G'; cmd = 'G';
ret = OK;
val = 1;
break; break;
case 'E': // Enable LED case 'E': // Enable LED
syslog("E\n");
signal_led(true); signal_led(true);
val = 0;
cmd = 'G'; cmd = 'G';
ret = OK;
val = 0;
break;
case 'F':
togl = !togl;
signal_led(togl);
{
auto *f = (fw_frame *) data;
if(f->seq == 0) {
system_otaflash_init();
}
uint16_t ack = 0;
ret = system_otaflash_chunk(f->data, ntohs(f->len), ntohs(f->seq), ntohl(f->hash), &ack);
val = htons(ack);
}
cmd = 'F';
break;
case 'C':
signal_led(false);
{
auto *f = (fw_check *) data;
ret = system_otaflash_verify_and_switch(ntohl(f->len), ntohl(f->hash));
}
cmd = 'C';
break; break;
default: default:
printf("[websocket_callback]:\n%.*s\n", (int) data_len, (char *) data); printf("[websocket_callback]:\n%.*s\n", (int) data_len, (char *) data);
printf("Unknown command\n"); printf("Unknown command %c\n", data[0]);
val = 0; ret = ERROR;
break; break;
} }
response[2] = (uint8_t) val; LOCK_TCPIP_CORE();
response[1] = val >> 8; websocket_write(pcb, response, 4, WS_BIN_MODE);
response[0] = cmd; UNLOCK_TCPIP_CORE();
websocket_write(pcb, response, 3, WS_BIN_MODE); if(ret == OK) {
if(cmd == 'R' || cmd == 'C') { // Restart
printf("rebooting now");
vTaskDelay(1000 / portTICK_PERIOD_MS);
vPortEnterCritical();
sdk_system_restart();
} else if(cmd == 'X') { // Clear Config
vTaskDelay(1000 / portTICK_PERIOD_MS);
system_clear_config();
}
}
} }
/** /**
@ -262,10 +328,10 @@ void websocket_open_cb(struct tcp_pcb *pcb, const char *uri) {
} }
extern "C" void httpd_task(void *pvParameters) { extern "C" void httpd_task(void *pvParameters) {
(void) pvParameters;
while (!uxSemaphoreGetCount(wifi_available_semaphore)) while (!uxSemaphoreGetCount(wifi_available_semaphore))
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
websocket_register_callbacks((tWsOpenHandler) websocket_open_cb, (tWsHandler) websocket_cb); websocket_register_callbacks((tWsOpenHandler) websocket_open_cb, (tWsHandler) websocket_cb);
httpd_init(); httpd_init();

22
firmware/webdir/404.html Normal file
View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="shortcut icon" href="img/favicon.png">
<title>HTTP Server</title>
</head>
<body>
<ul class="navbar">
<li><a href="/">Home</a></li>
<li><a href="websockets">WebSockets</a></li>
<li><a href="about">About</a></li>
</ul>
<div class="grid main">
<h1>404 - Page not found</h1>
<div class="alert alert-error">Sorry, the page you are requesting was not found on this server.</div>
</div>
</body>
</html>

View file

@ -0,0 +1,55 @@
main {
padding: 4em 2em 2em 2em;
max-width: 960px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
canvas {
width: 100%;
}
main section:target ~ section, main section#io, main section#wifi, main section#ota {
display: none;
}
main section:target {
display: block !important;
}
.table {
width: 100%;
display: table;
}
.table > .row {
display: table-row;
}
.table > .row:nth-child(2n) {
background: rgba(17, 17, 17, 0.05);
}
.table > .row > * {
display: table-cell;
padding: .3em .6em .3em .6em;
}
.table > header.row > * {
text-align: left;
font-weight: 900;
color: #fff;
background-color: #0074d9;
}
.table > .row > input {
border: none;
background: none;
font-weight: 900;
}
.plain {
opacity: initial;
width: initial;
}

View file

@ -5,6 +5,7 @@
<title>fiatlux v0.2</title> <title>fiatlux v0.2</title>
<link rel="stylesheet" href="css/picnic.min.css"> <link rel="stylesheet" href="css/picnic.min.css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="css/style.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head> </head>
<body> <body>
<nav> <nav>
@ -12,8 +13,68 @@
<span>fiatlux v0.2</span> <span>fiatlux v0.2</span>
<span class="label warning" id="status_box">Loading...</span> <span class="label warning" id="status_box">Loading...</span>
</a> </a>
<input id="bmenub" type="checkbox" class="show">
<label for="bmenub" class="burger pseudo button">&#9776;</label>
<div class="menu">
<a href="/#" class="button icon-picture">Dashboard</a>
<a href="/#ota" class="button icon-picture">System</a>
</div>
</nav> </nav>
<main data-page="dashboard" id="page"> <main id="page">
<section id="ota">
<h2>System</h2>
<article class="card">
<header>
<h3>Firmware Update</h3>
</header>
<div class="table">
<div class="row">
<span><input id="firmware_file" type="file" onchange="load_firmware(event)"/></span>
</div>
<div class="row">
<div class="full" id="progress_ct_ota">
<div id="progress_bar_ota"
style="background:#0074d9; display: block; width:9%; color: #fff"></div>
</div>
</div>
<div class="row">
<span><input id="transmit_firmware" disabled type="submit" value="Upload"
onclick="transmit_firmware(event)"></span>
</div>
</div>
</article>
<article class="card">
<header>
<h3>Restart</h3>
</header>
<div class="table">
<div class="row">
<span><input type="submit" value="Restart" onclick="wsWrite('R')"></span>
</div>
</div>
</article>
<article class="card">
<header>
<h3>Reset Config</h3>
</header>
<div class="table">
<div class="row">
<span><input type="submit" class="warning" value="Reset" onclick="wsWrite('X')"></span>
</div>
</div>
</article>
<article class="card">
<header>
<h3>Syslog</h3>
</header>
<div class="table">
<div class="row">
<pre id="syslog"></pre>
</div>
</div>
</article>
</section>
<section id="dashboard"> <section id="dashboard">
<h2>Status</h2> <h2>Status</h2>
<div class="flex"> <div class="flex">
@ -130,16 +191,21 @@
<script> <script>
var menu = document.getElementById("bmenub"); var menu = document.getElementById("bmenub");
var voltage = document.getElementById("out_voltage"); var voltage = document.getElementById("out_voltage");
var syslog = document.getElementById("syslog");
var unused_values = {}; var unused_values = {};
DataView.prototype.setChar = function(pos, char) { DataView.prototype.setChar = function (pos, char) {
this.setInt8(pos++, char.charCodeAt(0)); this.setInt8(pos++, char.charCodeAt(0));
return pos; return pos;
}; };
DataView.prototype.setString = function(pos, str) { DataView.prototype.getChar = function (pos) {
for(var i = 0; i < str.length; i++) { return String.fromCharCode(this.getInt8(pos));
};
DataView.prototype.setString = function (pos, str) {
for (var i = 0; i < str.length; i++) {
this.setInt8(pos++, str.charCodeAt(i)); this.setInt8(pos++, str.charCodeAt(i));
} }
this.setInt8(pos++, 0); this.setInt8(pos++, 0);
@ -154,7 +220,7 @@
sbox = document.getElementById('status_box'); sbox = document.getElementById('status_box');
sbox.className = "label " + cls; sbox.className = "label " + cls;
sbox.innerHTML = text; sbox.innerHTML = text;
console.log(text); console.info(text);
} }
function startPolling() { function startPolling() {
@ -169,6 +235,9 @@
chart.streamTo(canvas, 500); chart.streamTo(canvas, 500);
} }
var receive_chunk_confirmation = () => {
};
function onMessage(evt) { function onMessage(evt) {
retries = 0; retries = 0;
if (typeof evt.data == 'string') { if (typeof evt.data == 'string') {
@ -190,16 +259,25 @@
if (cmd === 'G') if (cmd === 'G')
console.log("LED switched", val); console.log("LED switched", val);
else if (cmd === 'V') { else if ((cmd === 'F') || (cmd === "C")) {
receive_chunk_confirmation(dv);
} else if (cmd === 'V') {
voltage.innerHTML = (val * 13 / 1024).toFixed(2); voltage.innerHTML = (val * 13 / 1024).toFixed(2);
series.append(new Date().getTime(), val); series.append(new Date().getTime(), val);
} else if (cmd === 'L') {
var len = dv.getUint8(1);
var offset = dv.getUint16(2);
var str = "";
for (var i = 0; i < len; i++)
str += dv.getChar(4 + i);
syslog.innerHTML = syslog.innerHTML.slice(0, offset) + str;
} else } else
console.log('unknown command', cmd, val); console.log('unknown command', cmd, val);
} }
} }
function wsOpen() { function wsOpen() {
var uri = "/stream" var uri = "/stream";
if (ws === undefined || ws.readyState !== 0) { if (ws === undefined || ws.readyState !== 0) {
if (retries) if (retries)
setMsg("warning", "WebSocket timeout, retrying.."); setMsg("warning", "WebSocket timeout, retrying..");
@ -215,6 +293,12 @@
console.error(evt); console.error(evt);
setMsg("error", "WebSocket error!"); /*window.location.reload(true);*/ setMsg("error", "WebSocket error!"); /*window.location.reload(true);*/
}; };
ws.onclose = function (evt) {
msgStyle = "warning";
if (!evt.wasClean) msgStyle = "error";
setMsg(msgStyle, "WebSocket closed!");
setTimeout(() => wsOpen(), 0);
};
ws.onmessage = function (evt) { ws.onmessage = function (evt) {
onMessage(evt); onMessage(evt);
}; };
@ -227,7 +311,7 @@
} }
function wsWrite(data) { function wsWrite(data) {
console.info(buf2hex(data)); //console.info(buf2hex(data));
if (ws.readyState === 3 || retries++ > 5) if (ws.readyState === 3 || retries++ > 5)
wsOpen(); wsOpen();
else if (ws.readyState === 1) else if (ws.readyState === 1)
@ -246,6 +330,115 @@
startPolling(); startPolling();
} }
var makeCRCTable = function () {
var c;
var crcTable = [];
for (var n = 0; n < 256; n++) {
c = n;
for (var k = 0; k < 8; k++) {
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
}
crcTable[n] = c;
}
return crcTable;
}
var crc32 = function (buf) {
const bufview = new DataView(buf);
var crcTable = window.crcTable || (window.crcTable = makeCRCTable());
var crc = 0 ^ (-1);
for (var i = 0; i < bufview.byteLength; i++) {
crc = (crc >>> 8) ^ crcTable[(crc ^ bufview.getInt8(i)) & 0xFF];
}
return (crc ^ (-1)) >>> 0;
};
var firmware_file;
function load_firmware(evt) {
var file = evt.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = function (e) {
firmware_file = e.target.result;
document.getElementById("transmit_firmware").disabled = false;
};
reader.readAsArrayBuffer(file)
}
const chunk_size = 512;
function transmit_firmware_chunk(buf, i) {
return new Promise((resolve, reject) => {
const begin = i * chunk_size;
const end = Math.min((i + 1) * chunk_size, buf.byteLength);
const slice = buf.slice(begin, end)
var header = new ArrayBuffer(12);
var headerview = new DataView(header);
headerview.setChar(0, 'F');
headerview.setInt16(4, i);
headerview.setInt16(6, (end - begin));
headerview.setInt32(8, crc32(slice));
var frame = new Uint8Array(12 + slice.byteLength);
frame.set(new Uint8Array(header), 0);
frame.set(new Uint8Array(slice), 12);
receive_chunk_confirmation = (dv) => {
setTimeout(() => {
resolve({cmd: dv.getChar(0), ret: dv.getUint8(1), val: dv.getUint16(2)});
}, 50);
}
setTimeout(() => {
reject({frame_error: i});
}, 2000);
wsWrite(frame.buffer);
});
}
function transmit_firmware_final(buf, hash) {
return new Promise((resolve, reject) => {
var frame = new ArrayBuffer(12);
var headerview = new DataView(frame);
headerview.setChar(0, 'C');
headerview.setInt32(4, buf.byteLength);
headerview.setInt32(8, hash);
receive_chunk_confirmation = (dv) => {
resolve({cmd: dv.getChar(0), ret: dv.getUint8(1), val: dv.getUint16(2)});
}
setTimeout(() => {
reject({final_error: 0});
}, 500);
wsWrite(frame);
});
}
function transmit_firmware(evt) {
console.log("transmit_firmware begin");
if (firmware_file) {
(async () => {
const ash = crc32(firmware_file);
for (var i = 0; i * chunk_size < firmware_file.byteLength; i++) {
update_progress("ota", i * chunk_size / firmware_file.byteLength * 100);
const reply = await transmit_firmware_chunk(firmware_file, i);
console.log("reply", reply)
}
await transmit_firmware_final(firmware_file, crc32(firmware_file));
update_progress("ota", 100);
})().then(() => {
console.log("transmit_firmware done");
})
}
}
function update_progress(progressBar, progress) {
var iP = Math.floor(progress);
var dBar = document.getElementById("progress_bar_" + progressBar);
dBar.innerText = iP + "%";
dBar.style.width = progress + "%";
}
</script> </script>
</body> </body>
</html> </html>

View file

@ -3,6 +3,7 @@
// //
#include "wifi.h" #include "wifi.h"
#include "log.h"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@ -36,7 +37,7 @@ SemaphoreHandle_t wifi_available_semaphore = nullptr;
char *wifi_ap_ip_addr = nullptr; char *wifi_ap_ip_addr = nullptr;
sysparam_get_string("wifi_ap_ip_addr", &wifi_ap_ip_addr); sysparam_get_string("wifi_ap_ip_addr", &wifi_ap_ip_addr);
if(!wifi_ap_ip_addr) { if(!wifi_ap_ip_addr) {
printf("dns: no ip address\n"); syslog("dns: no ip address\n");
vTaskDelete(nullptr); vTaskDelete(nullptr);
} }
ip4_addr_t server_addr; ip4_addr_t server_addr;
@ -194,7 +195,7 @@ extern "C" void wifi_task(void *pvParameters) {
/* If the ssid and password are not valid then disable the AP interface. */ /* If the ssid and password are not valid then disable the AP interface. */
if(!wifi_ap_ssid || strlen(wifi_ap_ssid) < 1 || strlen(wifi_ap_ssid) >= 32 || if(!wifi_ap_ssid || strlen(wifi_ap_ssid) < 1 || strlen(wifi_ap_ssid) >= 32 ||
!wifi_ap_password || strlen(wifi_ap_password) < 8 || strlen(wifi_ap_password) >= 64) { !wifi_ap_password || strlen(wifi_ap_password) < 8 || strlen(wifi_ap_password) >= 64) {
printf("len err\n"); syslog("len err\n");
wifi_ap_enable = 0; wifi_ap_enable = 0;
} }
} }

@ -1 +1 @@
Subproject commit 503e66a500419e8863998b7ea784c5e26a7a5f7c Subproject commit 7faa16b07ce0d606f9525a316990da5b58e61314

4
pcb/.gitignore vendored
View file

@ -31,6 +31,8 @@ fp-info-cache
*.wrl *.wrl
*.step *.step
*-bak *-backups/
gen/ gen/
pcb.zip pcb.zip
report.txt

3
webapp/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
node_modules/
src/gen/
package-lock.json