Compare commits
No commits in common. 'master' and 'gui-framework' have entirely different histories.
master
...
gui-framew
@ -1,5 +1,5 @@ |
|||||||
# Breadflow |
# Hello World Example |
||||||
|
|
||||||
is a toaster oven mod with a thermistor, SSR and ESP32. |
Starts a FreeRTOS task to print "Hello World" |
||||||
|
|
||||||
It can bake bread or reflow PCBs. |
See the README.md file in the upper level 'examples' directory for more information about examples. |
||||||
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
@ -1,56 +0,0 @@ |
|||||||
use std::fs; |
|
||||||
use std::process::Command; |
|
||||||
use std::string::ToString; |
|
||||||
|
|
||||||
fn main() { |
|
||||||
for entry in fs::read_dir(".").unwrap() { |
|
||||||
let entry = entry.unwrap(); |
|
||||||
let path = entry.path(); |
|
||||||
|
|
||||||
if !path.display().to_string().ends_with(".ico") { |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
let name = path.file_stem().unwrap().to_str().unwrap(); |
|
||||||
|
|
||||||
Command::new("/bin/convert").arg(&path).arg("./tmp.xbm").output().unwrap(); |
|
||||||
|
|
||||||
let content = fs::read_to_string("./tmp.xbm").unwrap(); |
|
||||||
|
|
||||||
let a = content.find('{').unwrap() + 1; |
|
||||||
let b = content.find('}').unwrap(); |
|
||||||
|
|
||||||
let bytes = &content[a..b].trim(); |
|
||||||
|
|
||||||
let bytes = bytes.split(",") |
|
||||||
.map(|s| s.trim()) |
|
||||||
.filter(|s| !s.is_empty()) |
|
||||||
.map(|s| u8::from_str_radix(&s[2..], 16).unwrap()) |
|
||||||
.collect::<Vec<_>>(); |
|
||||||
|
|
||||||
assert!(bytes.len() == 8); |
|
||||||
|
|
||||||
let mut columns : [u8; 5] = [0,0,0,0,0]; |
|
||||||
|
|
||||||
for (n, r) in bytes.iter().enumerate() { |
|
||||||
for i in 0..5 { |
|
||||||
if 0 != *r & (1<<i) { |
|
||||||
columns[i] |= 1 << n; |
|
||||||
} |
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn testchar(c : char) -> bool { |
|
||||||
c.is_alphanumeric() || c=='_' |
|
||||||
} |
|
||||||
|
|
||||||
let mut name = name.replace(|c:char|!testchar(c), "_"); |
|
||||||
if !name.starts_with(testchar) { |
|
||||||
name = format!("_{}", name); |
|
||||||
} |
|
||||||
|
|
||||||
println!("const uint8_t {}[5] = {{ {} }};", name, columns.iter().map(|c| format!("{:#04x}", !c)).collect::<Vec<_>>().join(", ")); |
|
||||||
} |
|
||||||
|
|
||||||
let _ = fs::remove_file("./tmp.xbm"); |
|
||||||
} |
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
@ -1,124 +0,0 @@ |
|||||||
#include "bitmaps.h" |
|
||||||
#include <stddef.h> |
|
||||||
#include <string.h> |
|
||||||
|
|
||||||
|
|
||||||
/* 21x22: no_flame */ |
|
||||||
// ········██····█······
|
|
||||||
// ·········██···██·····
|
|
||||||
// ··········██·········
|
|
||||||
// ··········███········
|
|
||||||
// ·····█····███···█····
|
|
||||||
// ···██····████··███···
|
|
||||||
// ··██···█████··███····
|
|
||||||
// ··█···█████··███·····
|
|
||||||
// ·██··█████··███······
|
|
||||||
// ·█··█████··███·······
|
|
||||||
// ·█··████··███··█·····
|
|
||||||
// ····███··███··███····
|
|
||||||
// ·····█··███···███··█·
|
|
||||||
// ·······███··█████··██
|
|
||||||
// ·█····███··███████··█
|
|
||||||
// █····███···███████···
|
|
||||||
// ██··███··█··██████···
|
|
||||||
// █··███··██···█████···
|
|
||||||
// ··███··███···█████···
|
|
||||||
// ·███··██·····█·███···
|
|
||||||
// ███··█·········██····
|
|
||||||
// ·█··███·······██·····
|
|
||||||
static const uint8_t G_NO_FLAME_BYTES[] = { 0x00, 0x00, 0xc0, 0x60, 0x20, 0x10, 0x80, 0xc0, 0xc1, 0xe3, 0xfe, 0x7c, 0x38, 0x80, 0xc3, 0xe2, 0x70, 0x20, 0x00, 0x00, 0x00, 0x80, 0x47, 0x01, 0x00, 0x0e, 0x9f, 0xcf, 0xe7, 0x73, 0x39, 0x1c, 0xce, 0xe7, 0xe3, 0xf9, 0xfc, 0xf8, 0xc0, 0x00, 0x30, 0x60, 0x13, 0x39, 0x1c, 0x0e, 0x27, 0x33, 0x29, 0x0c, 0x06, 0x07, 0x00, 0x00, 0x01, 0x0f, 0x27, 0x3f, 0x1f, 0x0f, 0x00, 0x00, 0x00 }; |
|
||||||
|
|
||||||
/* 21x22: flame */ |
|
||||||
// ········██····█······
|
|
||||||
// ·········██···██·····
|
|
||||||
// ··········██·········
|
|
||||||
// ··········███········
|
|
||||||
// ·····█····███········
|
|
||||||
// ···██····█████···█···
|
|
||||||
// ··██···███████··█····
|
|
||||||
// ··█···███████··██····
|
|
||||||
// ·██··████████·██·····
|
|
||||||
// ·█··████████··██·····
|
|
||||||
// ·█··████████·███·····
|
|
||||||
// ····████████·████····
|
|
||||||
// ·····████·███·███··█·
|
|
||||||
// ······███·███████··██
|
|
||||||
// ·█·····██··███████··█
|
|
||||||
// █·······██·███████···
|
|
||||||
// ██·····███··██████···
|
|
||||||
// █████·████···█████···
|
|
||||||
// ·█████████···█████···
|
|
||||||
// ··██████·····█·███···
|
|
||||||
// ···███·········██····
|
|
||||||
// ····███·······██·····
|
|
||||||
static const uint8_t G_FLAME_BYTES[] = { 0x00, 0x00, 0xc0, 0x60, 0x20, 0x10, 0x80, 0xc0, 0xc1, 0xe3, 0xfe, 0xfc, 0xf8, 0x60, 0x03, 0x82, 0xc0, 0x20, 0x00, 0x00, 0x00, 0x80, 0x47, 0x01, 0x00, 0x0e, 0x1f, 0x3f, 0x7f, 0xff, 0x8f, 0x3f, 0xff, 0xf1, 0xec, 0xff, 0xff, 0xf8, 0xc0, 0x00, 0x30, 0x60, 0x03, 0x07, 0x0e, 0x1e, 0x3e, 0x3c, 0x2e, 0x0f, 0x07, 0x07, 0x00, 0x00, 0x01, 0x0f, 0x27, 0x3f, 0x1f, 0x0f, 0x00, 0x00, 0x00 }; |
|
||||||
|
|
||||||
/* 84x48: boot_logo */ |
|
||||||
// ····················································································
|
|
||||||
// ·····································································████████·······
|
|
||||||
// ························································█████████████····█···██·····
|
|
||||||
// ···················█·······························█████····█·███·····███······█····
|
|
||||||
// ······█·█····································██████···██·█████··███████·········█···
|
|
||||||
// ·······█······························███████····██·█████·█·······███··········█····
|
|
||||||
// ······█·█······················███████·····███·███████··██···················██·····
|
|
||||||
// ···························██████·██·███·███·███████████·████·············███·······
|
|
||||||
// ······················████████████·██··███··█████████·█████·············██·██·······
|
|
||||||
// ··················█████·██·████···█·████···███████·███·············█████···██·······
|
|
||||||
// ·············███████████·█████████████···██·······█············█████··█·█·█·██······
|
|
||||||
// ············██████████·██████████··███████·················█████··█·██·█···███······
|
|
||||||
// ·········██████··█··████·██·███···████·█···············████···█··███··█······██·····
|
|
||||||
// ······███·████████████████████··██················█████··█···█·█·█···········█·█····
|
|
||||||
// ···███████····················██··············████·█····█·····█················█····
|
|
||||||
// ····██··························█·············█···█····██······················█····
|
|
||||||
// ··██·····························█·······█·█·█···█····██·······················█····
|
|
||||||
// ·█······························█·······█·█·█···█·█····························█····
|
|
||||||
// ·█····························██·······█·█·█·····█····················█········█····
|
|
||||||
// ··██······················████·█·█·█··█·······························█······█··█···
|
|
||||||
// ····████···················█··█·█·█··█································█······█··█···
|
|
||||||
// ·······█···················██·█·██··························█·········█······█··█···
|
|
||||||
// ·······█····················█·██······█·····················█··········█·····█··█···
|
|
||||||
// ·······█····················█·█········█··········█··········█·········█·····███····
|
|
||||||
// ········█···················█··········█···········█·········█·····██████████·······
|
|
||||||
// ········█····················█·········█············█·········█·███·················
|
|
||||||
// ········█····················█··········█···········█·······████····················
|
|
||||||
// ········█·····················█·········█············█·█████························
|
|
||||||
// ········█······················█·········█·········████·····························
|
|
||||||
// ········█·······················█········█····█████···██················█·█·········
|
|
||||||
// ·········█······················█·········█·██···························█··········
|
|
||||||
// ·········█······················█·······████····························█·█·········
|
|
||||||
// ·········█······················█····███·····················█······················
|
|
||||||
// ·········█·······················████·······················███·····················
|
|
||||||
// ·········█·····················███···························█······················
|
|
||||||
// ·········█········██████████████····················································
|
|
||||||
// ··········█·██████·····························································█····
|
|
||||||
// ···········█····························································█······█····
|
|
||||||
// ··························································█············█·······█····
|
|
||||||
// ··████·································█··················█······██····█··█····█····
|
|
||||||
// ··█···█···██████···█████████····█·····███████···███████···█·····█··█····█·██····█···
|
|
||||||
// ··█···█··██·····█·█·█··········█·█······█····█·█·█·······█·····█····█···█··█····█···
|
|
||||||
// ··████····█·····██··█··········█·█······█····█···██████··█·····█····█···█··█····█···
|
|
||||||
// ··█···█···██████··███████·····█···█·····█···█··███·······█·····█···█·····█·██···█···
|
|
||||||
// ··█····█··██········█········███████····█··█·····█·······█······█·█······█·█·█·█····
|
|
||||||
// ··█···█···█·██······█·····██··█····█··█████······█········█████··█········█···█·····
|
|
||||||
// ██████····█···███····█████···█·····██············█··································
|
|
||||||
// ··········█·····█···································································
|
|
||||||
static const uint8_t G_BOOT_LOGO_BYTES[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x20, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xc0, 0xc0, 0x40, 0xc0, 0xc0, 0x40, 0xc0, 0xa0, 0xa0, 0x20, 0xa0, 0xa0, 0xe0, 0x60, 0xd0, 0x90, 0xd0, 0xd0, 0xf0, 0xf0, 0xc8, 0xe8, 0xe8, 0xb8, 0xb8, 0x64, 0xd4, 0xb4, 0x94, 0x9c, 0x14, 0x0c, 0x0c, 0x1c, 0x14, 0x34, 0x34, 0x34, 0x12, 0x1a, 0x0a, 0x0a, 0x06, 0x82, 0x82, 0x82, 0x44, 0x44, 0x28, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xc0, 0xc0, 0x60, 0x60, 0x60, 0x50, 0x30, 0x30, 0x38, 0x3c, 0x3c, 0x2c, 0x2c, 0x3c, 0x2e, 0x2e, 0x3e, 0x3e, 0x37, 0x3d, 0x2b, 0x3f, 0x3d, 0x2f, 0x3f, 0x3f, 0x5f, 0x4d, 0xad, 0x25, 0x16, 0x1d, 0x1f, 0x1e, 0x0a, 0x1b, 0x09, 0x0d, 0x04, 0x02, 0x03, 0x03, 0xc3, 0x43, 0x43, 0x43, 0xa5, 0x63, 0x23, 0x22, 0x21, 0x91, 0xd1, 0x31, 0x11, 0x08, 0x08, 0x28, 0x58, 0x2c, 0x04, 0x34, 0x1c, 0x16, 0x0a, 0x0a, 0x16, 0x0a, 0x05, 0x01, 0x04, 0x0b, 0x0f, 0x3c, 0x10, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x09, 0x09, 0x10, 0x10, 0x10, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x38, 0xe8, 0x08, 0xf4, 0x4c, 0x32, 0x29, 0x10, 0x08, 0x00, 0x10, 0x48, 0x84, 0x02, 0x05, 0x02, 0x05, 0x02, 0x01, 0x00, 0x00, 0x02, 0x05, 0x82, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x60, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x80, 0x87, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x08, 0x10, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x8c, 0xb0, 0xc0, 0x80, 0x40, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x11, 0x16, 0x18, 0x30, 0x28, 0x08, 0x08, 0x08, 0x08, 0x04, 0x05, 0x06, 0x04, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0xa1, 0x41, 0xa1, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x0f, 0x10, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x05, 0x06, 0x02, 0x02, 0x02, 0x01, 0x01, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x02, 0x07, 0x02, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x20, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x7f, 0x44, 0x44, 0x44, 0x2b, 0x10, 0x00, 0x02, 0xff, 0x19, 0x29, 0x29, 0x49, 0x49, 0xc6, 0x04, 0x0a, 0x09, 0x3f, 0x49, 0x49, 0x49, 0x49, 0x41, 0x21, 0x21, 0x00, 0x50, 0x38, 0x16, 0x11, 0x16, 0x18, 0x70, 0x40, 0x00, 0x21, 0x21, 0x3f, 0x21, 0x21, 0x11, 0x09, 0x06, 0x00, 0x0a, 0x09, 0x7f, 0x05, 0x05, 0x05, 0x05, 0x05, 0x00, 0x00, 0x1e, 0x21, 0x20, 0x20, 0x20, 0x20, 0x0e, 0x11, 0x20, 0x10, 0x09, 0x06, 0x00, 0x00, 0x00, 0x07, 0x18, 0x21, 0x1f, 0x08, 0x10, 0x20, 0x10, 0x0f, 0x00, 0x00, 0x00 }; |
|
||||||
|
|
||||||
static const struct BitmapImage bitmaps[] = {
|
|
||||||
{ .name="no_flame", .width=21, .height=22, .bytes=G_NO_FLAME_BYTES }, |
|
||||||
{ .name="flame", .width=21, .height=22, .bytes=G_FLAME_BYTES }, |
|
||||||
{ .name="boot_logo", .width=84, .height=48, .bytes=G_BOOT_LOGO_BYTES }, |
|
||||||
{} |
|
||||||
}; |
|
||||||
|
|
||||||
const struct BitmapImage *Bitmap_Get(const char *name) { |
|
||||||
const struct BitmapImage *ptr = &bitmaps[0]; |
|
||||||
while (ptr->name) { |
|
||||||
if (0 == strcmp(ptr->name, name)) { |
|
||||||
return ptr; |
|
||||||
} |
|
||||||
ptr++; |
|
||||||
} |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
|
|
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 1.7 KiB |
@ -1,172 +0,0 @@ |
|||||||
use std::fs::{self,OpenOptions}; |
|
||||||
use std::process::Command; |
|
||||||
use std::string::ToString; |
|
||||||
use std::io::Write; |
|
||||||
|
|
||||||
fn main() { |
|
||||||
let mut c_constants = Vec::new(); |
|
||||||
let mut c_table = Vec::new(); |
|
||||||
|
|
||||||
for entry in fs::read_dir(".").unwrap() { |
|
||||||
let entry = entry.unwrap(); |
|
||||||
let path = entry.path();
|
|
||||||
|
|
||||||
if !path.display().to_string().ends_with(".ico") { |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
let name = path.file_stem().unwrap().to_str().unwrap(); |
|
||||||
|
|
||||||
Command::new("/bin/convert").arg(&path).arg("./tmp.xbm").output().unwrap(); |
|
||||||
|
|
||||||
let content = fs::read_to_string("./tmp.xbm").unwrap(); |
|
||||||
|
|
||||||
println!("\n------------------------------------------------------------------------\n"); |
|
||||||
|
|
||||||
println!("{}", content); |
|
||||||
|
|
||||||
let mut lines = content.lines(); |
|
||||||
let l1 = lines.nth(0).unwrap(); |
|
||||||
let l2 = lines.nth(0).unwrap(); |
|
||||||
|
|
||||||
let w : usize = l1[l1.find("width").unwrap()+6..].parse().unwrap();
|
|
||||||
let h : usize = l2[l2.find("height").unwrap()+7..].parse().unwrap(); |
|
||||||
|
|
||||||
let a = content.find('{').unwrap() + 1; |
|
||||||
let b = content.find('}').unwrap();
|
|
||||||
let bytes = &content[a..b].trim(); |
|
||||||
|
|
||||||
let bytes = bytes.split(",") |
|
||||||
.map(|s| s.trim()) |
|
||||||
.filter(|s| !s.is_empty()) |
|
||||||
.map(|s| ! u8::from_str_radix(&s[2..], 16).unwrap()) |
|
||||||
.collect::<Vec<_>>(); |
|
||||||
|
|
||||||
|
|
||||||
print!("---SOURCE--\n"); |
|
||||||
for y in 0..h { |
|
||||||
for x in 0..(w-1)/8+1 { |
|
||||||
for xx in 0..8 {
|
|
||||||
if (bytes[y*(((w-1)/8+1)) + x] & (1u8 << xx)) != 0 { |
|
||||||
print!("█"); |
|
||||||
} else { |
|
||||||
print!("·"); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
print!("\n"); |
|
||||||
} |
|
||||||
println!("---\n"); |
|
||||||
|
|
||||||
println!("name {},\nw {} h {},\nbytes {}", name, w, h, bytes.iter().map(|c| format!("{:#04x}", c)).collect::<Vec<_>>().join(", ")); |
|
||||||
|
|
||||||
let bit_xy = |x : usize, y : usize| { |
|
||||||
let xx = x / 8; |
|
||||||
let nthbit = x % 8; |
|
||||||
let n = y*((w-1)/8+1) + xx; |
|
||||||
if n >= bytes.len() { |
|
||||||
0u8 |
|
||||||
} else {
|
|
||||||
let r = ((bytes[n] & (1 << nthbit)) >> nthbit) as u8; |
|
||||||
r |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
let result_cols = w; |
|
||||||
let result_rows = ((h-1)/8)+1; |
|
||||||
|
|
||||||
let mut result = Vec::new(); |
|
||||||
|
|
||||||
for y in 0..result_rows { |
|
||||||
for x in 0..result_cols { |
|
||||||
let mut buf = 0u8; |
|
||||||
for yy in 0..8 { |
|
||||||
buf |= bit_xy(x, y * 8 + yy) << yy; |
|
||||||
} |
|
||||||
result.push(buf); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//println!("final bytes {}", result.iter().map(|c| format!("{:#04x}", c)).collect::<Vec<_>>().join(", "));
|
|
||||||
|
|
||||||
let mut render = String::new(); |
|
||||||
print!("---\n"); |
|
||||||
for y in 0..result_rows { |
|
||||||
for yy in 0..8 { |
|
||||||
if y*8+yy >= h { |
|
||||||
break; |
|
||||||
} |
|
||||||
render.push_str("// "); |
|
||||||
for x in 0..result_cols { |
|
||||||
if (result[y*result_cols + x] & (1u8 << yy)) != 0 { |
|
||||||
print!("█"); |
|
||||||
render.push_str("█"); |
|
||||||
} else { |
|
||||||
print!("·"); |
|
||||||
render.push_str("·"); |
|
||||||
} |
|
||||||
} |
|
||||||
if yy == 7 { |
|
||||||
print!("/ end of byte row"); |
|
||||||
} |
|
||||||
render.push_str("\n"); |
|
||||||
print!("\n"); |
|
||||||
} |
|
||||||
} |
|
||||||
println!("---\n"); |
|
||||||
|
|
||||||
fn testchar(c : char) -> bool { |
|
||||||
c.is_alphanumeric() || c=='_' |
|
||||||
} |
|
||||||
|
|
||||||
let mut name = name.replace(|c:char|!testchar(c), "_"); |
|
||||||
if !name.starts_with(testchar) { |
|
||||||
name = format!("_{}", name); |
|
||||||
} |
|
||||||
|
|
||||||
c_constants.push(format!("\n/* {}x{}: {} */\n{}static const uint8_t G_{}_BYTES[] = {{ {} }};",
|
|
||||||
w,h, |
|
||||||
name, |
|
||||||
render, |
|
||||||
name.to_uppercase(), |
|
||||||
result.iter().map(|c| format!("{:#04x}", c)).collect::<Vec<_>>().join(", ") |
|
||||||
)); |
|
||||||
|
|
||||||
c_table.push(format!("{{ .name=\"{}\", .width={}, .height={}, .bytes=G_{}_BYTES }}",
|
|
||||||
name, |
|
||||||
w, h, |
|
||||||
name.to_uppercase() |
|
||||||
)); |
|
||||||
} |
|
||||||
|
|
||||||
let c_source = format!(r##"#include "bitmaps.h" |
|
||||||
#include <stddef.h> |
|
||||||
#include <string.h> |
|
||||||
|
|
||||||
{} |
|
||||||
|
|
||||||
static const struct BitmapImage bitmaps[] = {{
|
|
||||||
{}, |
|
||||||
{{}} |
|
||||||
}}; |
|
||||||
|
|
||||||
const struct BitmapImage *Bitmap_Get(const char *name) {{ |
|
||||||
const struct BitmapImage *ptr = &bitmaps[0]; |
|
||||||
while (ptr->name) {{ |
|
||||||
if (0 == strcmp(ptr->name, name)) {{ |
|
||||||
return ptr; |
|
||||||
}} |
|
||||||
ptr++; |
|
||||||
}} |
|
||||||
return NULL; |
|
||||||
}} |
|
||||||
"##,
|
|
||||||
c_constants.join("\n"), |
|
||||||
c_table.join(",\n ") |
|
||||||
); |
|
||||||
|
|
||||||
let mut f = OpenOptions::new().write(true).create(true).truncate(true).open("./bitmaps.c").unwrap(); |
|
||||||
f.write(c_source.as_bytes()).unwrap(); |
|
||||||
|
|
||||||
let _ = fs::remove_file("./tmp.xbm"); |
|
||||||
} |
|
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
@ -1,8 +0,0 @@ |
|||||||
set(COMPONENT_ADD_INCLUDEDIRS include) |
|
||||||
|
|
||||||
set(COMPONENT_SRCDIRS |
|
||||||
"src") |
|
||||||
|
|
||||||
#set(COMPONENT_REQUIRES) |
|
||||||
|
|
||||||
register_component() |
|
@ -1,2 +0,0 @@ |
|||||||
General purpose, mostly platofrm-idependent utilities |
|
||||||
that may be used by other components. |
|
@ -1,3 +0,0 @@ |
|||||||
|
|
||||||
COMPONENT_SRCDIRS := src
|
|
||||||
COMPONENT_ADD_INCLUDEDIRS := include
|
|
@ -1,75 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>. |
|
||||||
* |
|
||||||
* This program is free software; you can redistribute it and/or |
|
||||||
* modify it under the terms of the GNU General Public License as |
|
||||||
* published by the Free Software Foundation; either version 2 of the |
|
||||||
* License, or any later version. |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, but |
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
* General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program; if not, write to the Free Software |
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef BASE16_H_ |
|
||||||
#define BASE16_H_ |
|
||||||
|
|
||||||
#include <stdint.h> |
|
||||||
#include <string.h> |
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate length of base16-encoded data |
|
||||||
* @param raw_len Raw data length |
|
||||||
* @return Encoded string length (excluding NUL) |
|
||||||
*/ |
|
||||||
static inline size_t base16_encoded_len(size_t raw_len) { |
|
||||||
return (2 * raw_len); |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate maximum length of base16-decoded string |
|
||||||
* @param encoded Encoded string |
|
||||||
* @return Maximum length of raw data |
|
||||||
*/ |
|
||||||
static inline size_t base16_decoded_max_len(const char *encoded) { |
|
||||||
return ((strlen(encoded) + 1) / 2); |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Base16-encode data |
|
||||||
* |
|
||||||
* The buffer must be the correct length for the encoded string. Use |
|
||||||
* something like |
|
||||||
* |
|
||||||
* char buf[ base16_encoded_len ( len ) + 1 ]; |
|
||||||
* |
|
||||||
* (the +1 is for the terminating NUL) to provide a buffer of the |
|
||||||
* correct size. |
|
||||||
* |
|
||||||
* @param raw Raw data |
|
||||||
* @param len Length of raw data |
|
||||||
* @param encoded Buffer for encoded string |
|
||||||
*/ |
|
||||||
extern void base16_encode(uint8_t *raw, size_t len, char *encoded); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Base16-decode data |
|
||||||
* |
|
||||||
* The buffer must be large enough to contain the decoded data. Use |
|
||||||
* something like |
|
||||||
* |
|
||||||
* char buf[ base16_decoded_max_len ( encoded ) ]; |
|
||||||
* |
|
||||||
* to provide a buffer of the correct size. |
|
||||||
* @param encoded Encoded string |
|
||||||
* @param raw Raw data |
|
||||||
* @return Length of raw data, or negative error |
|
||||||
*/ |
|
||||||
extern int base16_decode(const char *encoded, uint8_t *raw); |
|
||||||
|
|
||||||
#endif /* BASE16_H_ */ |
|
@ -1,131 +0,0 @@ |
|||||||
/**
|
|
||||||
* TODO file description |
|
||||||
*
|
|
||||||
* Created on 2019/09/13. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef CSPEMU_DATETIME_H |
|
||||||
#define CSPEMU_DATETIME_H |
|
||||||
|
|
||||||
#include <stdbool.h> |
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
enum weekday { |
|
||||||
MONDAY = 1, |
|
||||||
TUESDAY, |
|
||||||
WEDNESDAY, |
|
||||||
THURSDAY, |
|
||||||
FRIDAY, |
|
||||||
SATURDAY, |
|
||||||
SUNDAY |
|
||||||
}; |
|
||||||
_Static_assert(MONDAY==1, "enum weekday numbering Mon"); |
|
||||||
_Static_assert(SUNDAY==7, "enum weekday numbering Sun"); |
|
||||||
|
|
||||||
enum month { |
|
||||||
JANUARY = 1, |
|
||||||
FEBRUARY, |
|
||||||
MARCH, |
|
||||||
APRIL, |
|
||||||
MAY, |
|
||||||
JUNE, |
|
||||||
JULY, |
|
||||||
AUGUST, |
|
||||||
SEPTEMBER, |
|
||||||
OCTOBER, |
|
||||||
NOVEMBER, |
|
||||||
DECEMBER |
|
||||||
}; |
|
||||||
_Static_assert(JANUARY==1, "enum month numbering Jan"); |
|
||||||
_Static_assert(DECEMBER==12, "enum month numbering Dec"); |
|
||||||
|
|
||||||
/** Abbreviated weekday names */ |
|
||||||
extern const char *DT_WKDAY_NAMES[]; |
|
||||||
/** Full-length weekday names */ |
|
||||||
extern const char *DT_WKDAY_NAMES_FULL[]; |
|
||||||
/** Abbreviated month names */ |
|
||||||
extern const char *DT_MONTH_NAMES[]; |
|
||||||
/** Full-length month names */ |
|
||||||
extern const char *DT_MONTH_NAMES_FULL[]; |
|
||||||
|
|
||||||
typedef struct datetime { |
|
||||||
uint16_t year; |
|
||||||
enum month month; |
|
||||||
uint8_t day; |
|
||||||
uint8_t hour; |
|
||||||
uint8_t min; |
|
||||||
uint8_t sec; |
|
||||||
enum weekday wkday; // 1=monday
|
|
||||||
} datetime_t; |
|
||||||
|
|
||||||
// Templates for printf
|
|
||||||
#define DT_FORMAT_DATE "%d/%d/%d" |
|
||||||
#define DT_SUBS_DATE(dt) (dt).year, (dt).month, (dt).day |
|
||||||
|
|
||||||
#define DT_FORMAT_TIME "%d:%02d:%02d" |
|
||||||
#define DT_SUBS_TIME(dt) (dt).hour, (dt).min, (dt).sec |
|
||||||
|
|
||||||
#define DT_FORMAT_DATE_WK DT_FORMAT_WK " " DT_FORMAT_DATE |
|
||||||
#define DT_SUBS_DATE_WK(dt) DT_SUBS_WK(dt), DT_SUBS_DATE(dt) |
|
||||||
|
|
||||||
#define DT_FORMAT_WK "%s" |
|
||||||
#define DT_SUBS_WK(dt) DT_WKDAY_NAMES[(dt).wkday] |
|
||||||
|
|
||||||
#define DT_FORMAT_DATE_TIME DT_FORMAT_DATE " " DT_FORMAT_TIME |
|
||||||
#define DT_SUBS_DATE_TIME(dt) DT_SUBS_DATE(dt), DT_SUBS_TIME(dt) |
|
||||||
|
|
||||||
#define DT_FORMAT DT_FORMAT_DATE_WK " " DT_FORMAT_TIME |
|
||||||
#define DT_SUBS(dt) DT_SUBS_DATE_WK(dt), DT_SUBS_TIME(dt) |
|
||||||
|
|
||||||
// base century for two-digit year conversions
|
|
||||||
#define DT_CENTURY 2000 |
|
||||||
// start year for weekday computation
|
|
||||||
#define DT_START_YEAR 2019 |
|
||||||
// January 1st weekday of DT_START_YEAR
|
|
||||||
#define DT_START_WKDAY TUESDAY |
|
||||||
// max date supported by 2-digit year RTC counters (it can't check Y%400==0 with only two digits)
|
|
||||||
#define DT_END_YEAR 2399 |
|
||||||
|
|
||||||
typedef union __attribute__((packed)) { |
|
||||||
struct __attribute__((packed)) { |
|
||||||
uint8_t ones : 4; |
|
||||||
uint8_t tens : 4; |
|
||||||
}; |
|
||||||
uint8_t byte; |
|
||||||
} bcd_t; |
|
||||||
_Static_assert(sizeof(bcd_t) == 1, "Bad bcd_t len"); |
|
||||||
|
|
||||||
/** Check if a year is leap */ |
|
||||||
static inline bool is_leap_year(int year) |
|
||||||
{ |
|
||||||
return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0); |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a datetime could be valid (ignores leap years) |
|
||||||
* |
|
||||||
* @param[in] dt |
|
||||||
* @return basic validations passed |
|
||||||
*/ |
|
||||||
bool datetime_is_valid(const datetime_t *dt); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Set weekday based on a date in a given datetime |
|
||||||
* |
|
||||||
* @param[in,out] dt |
|
||||||
* @return success |
|
||||||
*/ |
|
||||||
bool datetime_set_weekday(datetime_t *dt); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Get weekday for given a date |
|
||||||
* |
|
||||||
* @param year - year number |
|
||||||
* @param month - 1-based month number |
|
||||||
* @param day - 1-based day number |
|
||||||
* @return weekday |
|
||||||
*/ |
|
||||||
enum weekday date_weekday(uint16_t year, enum month month, uint8_t day); |
|
||||||
|
|
||||||
|
|
||||||
#endif //CSPEMU_DATETIME_H
|
|
@ -1,19 +0,0 @@ |
|||||||
/**
|
|
||||||
* @file |
|
||||||
* @brief A simple way of dumping memory to a hex output |
|
||||||
* |
|
||||||
* \addtogroup Hexdump |
|
||||||
* |
|
||||||
* @{ |
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
#include <stdio.h> |
|
||||||
|
|
||||||
#define HEX_DUMP_LINE_BUFF_SIZ 16 |
|
||||||
|
|
||||||
extern void hex_dump(FILE * fp,void *src, int len); |
|
||||||
extern void hex_dump_buff_line(FILE *fp, int addr_size, unsigned pos, char *line, unsigned len); |
|
||||||
/**
|
|
||||||
* }@ |
|
||||||
*/ |
|
@ -1,101 +0,0 @@ |
|||||||
/**
|
|
||||||
* General purpose, platform agnostic, reusable utils |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef COMMON_UTILS_UTILS_H |
|
||||||
#define COMMON_UTILS_UTILS_H |
|
||||||
|
|
||||||
#include <stdbool.h> |
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
#include "base16.h" |
|
||||||
#include "datetime.h" |
|
||||||
#include "hexdump.h" |
|
||||||
|
|
||||||
/** Convert a value to BCD struct */ |
|
||||||
static inline bcd_t num2bcd(uint8_t value) |
|
||||||
{ |
|
||||||
return (bcd_t) {.ones=value % 10, .tens=value / 10}; |
|
||||||
} |
|
||||||
|
|
||||||
/** Convert unpacked BCD to value */ |
|
||||||
static inline uint8_t bcd2num(uint8_t tens, uint8_t ones) |
|
||||||
{ |
|
||||||
return tens * 10 + ones; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Append to a buffer. |
|
||||||
* |
|
||||||
* In case the buffer capacity is reached, it is reverted to the previous length (by replacing the NUL byte) |
|
||||||
* and NULL is returned. |
|
||||||
* |
|
||||||
* @param buf - buffer position to append at; if NULL is given, the function immediately returns NULL. |
|
||||||
* @param appended - string to append |
|
||||||
* @param pcap - pointer to a capacity variable |
|
||||||
* @return the new end of the string (null byte); use as 'buf' for following appends |
|
||||||
*/ |
|
||||||
char *append(char *buf, const char *appended, size_t *pcap); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Append, re-allocating as needed. |
|
||||||
* |
|
||||||
* @param head - pointer to heap buffer head, may be updated on realloc. |
|
||||||
* @param size - string size pointer, may be updated on realloc. |
|
||||||
* @param appended - string to append |
|
||||||
* @return false on alloc error |
|
||||||
*/ |
|
||||||
bool append_realloc(char **head, size_t *cap, const char *appended); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if a file descriptor is valid (e.g. when cleaning up after a failed select) |
|
||||||
* |
|
||||||
* @param fd - file descriptor number |
|
||||||
* @return is valid |
|
||||||
*/ |
|
||||||
bool fd_is_valid(int fd); |
|
||||||
|
|
||||||
/**
|
|
||||||
* parse user-provided string as boolean |
|
||||||
* |
|
||||||
* @param str |
|
||||||
* @return is true |
|
||||||
*/ |
|
||||||
bool parse_boolean_arg(const char *str); |
|
||||||
|
|
||||||
/**
|
|
||||||
* complementary function to parse_boolean_arg() that matches strings |
|
||||||
* meaning 'false'. Can be used together with the positive version |
|
||||||
* in case there can be other values as well. |
|
||||||
* |
|
||||||
* @param str |
|
||||||
* @return is false |
|
||||||
*/ |
|
||||||
bool parse_boolean_arg_false(const char *str); |
|
||||||
|
|
||||||
/** Check equality of two strings; returns bool */ |
|
||||||
#define streq(a, b) (strcmp((const char*)(a), (const char*)(b)) == 0) |
|
||||||
|
|
||||||
/** Check prefix equality of two strings; returns bool */ |
|
||||||
#define strneq(a, b, n) (strncmp((const char*)(a), (const char*)(b), (n)) == 0) |
|
||||||
|
|
||||||
/** Check if a string starts with a substring; returns bool */ |
|
||||||
#define strstarts(a, b) strneq((a), (b), (int)strlen((b))) |
|
||||||
|
|
||||||
#ifndef MIN |
|
||||||
/** Get min of two numbers */ |
|
||||||
#define MIN(a, b) ((a) > (b) ? (b) : (a)) |
|
||||||
#endif |
|
||||||
|
|
||||||
#ifndef MAX |
|
||||||
/** Get max of two values */ |
|
||||||
#define MAX(a, b) ((a) > (b) ? (a) : (b)) |
|
||||||
#endif |
|
||||||
|
|
||||||
#ifndef STR |
|
||||||
#define STR_HELPER(x) #x |
|
||||||
/** Stringify a token */ |
|
||||||
#define STR(x) STR_HELPER(x) |
|
||||||
#endif |
|
||||||
|
|
||||||
#endif //COMMON_UTILS_UTILS_H
|
|
@ -1,62 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>. |
|
||||||
* |
|
||||||
* This program is free software; you can redistribute it and/or |
|
||||||
* modify it under the terms of the GNU General Public License as |
|
||||||
* published by the Free Software Foundation; either version 2 of the |
|
||||||
* License, or any later version. |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, but |
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
* General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program; if not, write to the Free Software |
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
|
||||||
*/ |
|
||||||
|
|
||||||
#include <stdint.h> |
|
||||||
#include <stdlib.h> |
|
||||||
#include <string.h> |
|
||||||
#include <stdio.h> |
|
||||||
#include <esp_log.h> |
|
||||||
|
|
||||||
static const char *TAG = "base16"; |
|
||||||
|
|
||||||
void base16_encode(uint8_t *raw, size_t len, char *encoded) { |
|
||||||
uint8_t *raw_bytes = raw; |
|
||||||
char *encoded_bytes = encoded; |
|
||||||
size_t remaining = len; |
|
||||||
|
|
||||||
for (; remaining--; encoded_bytes += 2) |
|
||||||
snprintf(encoded_bytes, 3, "%02X", *(raw_bytes++)); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
int base16_decode(const char *encoded, uint8_t *raw) { |
|
||||||
const char *encoded_bytes = encoded; |
|
||||||
uint8_t *raw_bytes = raw; |
|
||||||
char buf[3]; |
|
||||||
char *endp; |
|
||||||
size_t len; |
|
||||||
|
|
||||||
while (encoded_bytes[0]) { |
|
||||||
if (!encoded_bytes[1]) { |
|
||||||
ESP_LOGE(TAG, "Base16-encoded string \"%s\" has invalid length\n", |
|
||||||
encoded); |
|
||||||
return -22; |
|
||||||
} |
|
||||||
memcpy(buf, encoded_bytes, 2); |
|
||||||
buf[2] = '\0'; |
|
||||||
*(raw_bytes++) = strtoul(buf, &endp, 16); |
|
||||||
if (*endp != '\0') { |
|
||||||
ESP_LOGE(TAG,"Base16-encoded string \"%s\" has invalid byte \"%s\"\n", |
|
||||||
encoded, buf); |
|
||||||
return -22; |
|
||||||
} |
|
||||||
encoded_bytes += 2; |
|
||||||
} |
|
||||||
len = (raw_bytes - raw); |
|
||||||
return (len); |
|
||||||
} |
|
@ -1,85 +0,0 @@ |
|||||||
#include <stdint.h> |
|
||||||
#include <string.h> |
|
||||||
#include <stdbool.h> |
|
||||||
#include <fcntl.h> |
|
||||||
#include <errno.h> |
|
||||||
#include <assert.h> |
|
||||||
|
|
||||||
#include "common_utils/utils.h" |
|
||||||
|
|
||||||
char *append(char *buf, const char *appended, size_t *pcap) |
|
||||||
{ |
|
||||||
char c; |
|
||||||
char *buf0 = buf; |
|
||||||
size_t cap = *pcap; |
|
||||||
|
|
||||||
if (buf0 == NULL) return NULL; |
|
||||||
if (appended == NULL || appended[0] == 0) return buf0; |
|
||||||
|
|
||||||
if (*pcap < strlen(appended)+1) { |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
|
|
||||||
while (cap > 1 && 0 != (c = *appended++)) { |
|
||||||
*buf++ = c; |
|
||||||
cap--; |
|
||||||
} |
|
||||||
assert(cap > 0); |
|
||||||
|
|
||||||
*pcap = cap; |
|
||||||
*buf = 0; |
|
||||||
return buf; |
|
||||||
} |
|
||||||
|
|
||||||
bool append_realloc(char **head, size_t *cap, const char *appended) { |
|
||||||
if (!head) return NULL; |
|
||||||
if (!*head) return NULL; |
|
||||||
if (!cap) return NULL; |
|
||||||
if (!appended) return NULL; |
|
||||||
|
|
||||||
size_t cursize = strlen(*head); |
|
||||||
size_t needed = strlen(appended) + 1; |
|
||||||
size_t remains = *cap - cursize; |
|
||||||
|
|
||||||
if (remains < needed) { |
|
||||||
size_t need_extra = needed - remains; |
|
||||||
size_t newsize = *cap + need_extra; |
|
||||||
char *new = realloc(*head, newsize); |
|
||||||
if (!new) return false; |
|
||||||
*head = new; |
|
||||||
*cap = newsize; |
|
||||||
} |
|
||||||
|
|
||||||
strcpy(*head + cursize, appended); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
bool fd_is_valid(int fd) |
|
||||||
{ |
|
||||||
return fcntl(fd, F_GETFD) != -1 || errno != EBADF; |
|
||||||
} |
|
||||||
|
|
||||||
bool parse_boolean_arg(const char *str) |
|
||||||
{ |
|
||||||
if (0 == strcasecmp(str, "on")) return true; |
|
||||||
if (0 == strcmp(str, "1")) return true; |
|
||||||
if (0 == strcasecmp(str, "yes")) return true; |
|
||||||
if (0 == strcasecmp(str, "enable")) return true; |
|
||||||
if (0 == strcasecmp(str, "en")) return true; |
|
||||||
if (0 == strcasecmp(str, "y")) return true; |
|
||||||
if (0 == strcasecmp(str, "a")) return true; |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
bool parse_boolean_arg_false(const char *str) |
|
||||||
{ |
|
||||||
if (0 == strcasecmp(str, "off")) return true; |
|
||||||
if (0 == strcmp(str, "0")) return true; |
|
||||||
if (0 == strcasecmp(str, "no")) return true; |
|
||||||
if (0 == strcasecmp(str, "disable")) return true; |
|
||||||
if (0 == strcasecmp(str, "dis")) return true; |
|
||||||
if (0 == strcasecmp(str, "n")) return true; |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
@ -1,110 +0,0 @@ |
|||||||
|
|
||||||
#include <stdint.h> |
|
||||||
#include <stdbool.h> |
|
||||||
#include <stddef.h> |
|
||||||
#include "common_utils/datetime.h" |
|
||||||
|
|
||||||
const char *DT_WKDAY_NAMES[] = { |
|
||||||
[MONDAY] = "Mon", |
|
||||||
[TUESDAY] = "Tue", |
|
||||||
[WEDNESDAY] = "Wed", |
|
||||||
[THURSDAY] = "Thu", |
|
||||||
[FRIDAY] = "Fri", |
|
||||||
[SATURDAY] = "Sat", |
|
||||||
[SUNDAY] = "Sun" |
|
||||||
}; |
|
||||||
|
|
||||||
const char *DT_WKDAY_NAMES_FULL[] = { |
|
||||||
[MONDAY] = "Monday", |
|
||||||
[TUESDAY] = "Tuesday", |
|
||||||
[WEDNESDAY] = "Wednesday", |
|
||||||
[THURSDAY] = "Thursday", |
|
||||||
[FRIDAY] = "Friday", |
|
||||||
[SATURDAY] = "Saturday", |
|
||||||
[SUNDAY] = "Sunday" |
|
||||||
}; |
|
||||||
|
|
||||||
const char *DT_MONTH_NAMES[] = { |
|
||||||
[JANUARY] = "Jan", |
|
||||||
[FEBRUARY] = "Feb", |
|
||||||
[MARCH] = "Mar", |
|
||||||
[APRIL] = "Apr", |
|
||||||
[MAY] = "May", |
|
||||||
[JUNE] = "Jun", |
|
||||||
[JULY] = "Jul", |
|
||||||
[AUGUST] = "Aug", |
|
||||||
[SEPTEMBER] = "Sep", |
|
||||||
[OCTOBER] = "Oct", |
|
||||||
[NOVEMBER] = "Nov", |
|
||||||
[DECEMBER] = "Dec" |
|
||||||
}; |
|
||||||
|
|
||||||
const char *DT_MONTH_NAMES_FULL[] = { |
|
||||||
[JANUARY] = "January", |
|
||||||
[FEBRUARY] = "February", |
|
||||||
[MARCH] = "March", |
|
||||||
[APRIL] = "April", |
|
||||||
[MAY] = "May", |
|
||||||
[JUNE] = "June", |
|
||||||
[JULY] = "July", |
|
||||||
[AUGUST] = "August", |
|
||||||
[SEPTEMBER] = "September", |
|
||||||
[OCTOBER] = "October", |
|
||||||
[NOVEMBER] = "November", |
|
||||||
[DECEMBER] = "December" |
|
||||||
}; |
|
||||||
|
|
||||||
static const uint16_t MONTH_LENGTHS[] = { /* 1-based, normal year */ |
|
||||||
[JANUARY]=31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 |
|
||||||
}; |
|
||||||
_Static_assert(sizeof(MONTH_LENGTHS) / sizeof(uint16_t) == 13, "Months array length"); |
|
||||||
|
|
||||||
static const uint16_t MONTH_YEARDAYS[] = { /* 1-based */ |
|
||||||
[JANUARY]=0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 // // days until 1st of month
|
|
||||||
}; |
|
||||||
_Static_assert(sizeof(MONTH_YEARDAYS) / sizeof(uint16_t) == 13, "Months array length"); |
|
||||||
|
|
||||||
_Static_assert(MONDAY < SUNDAY, "Weekday ordering"); |
|
||||||
|
|
||||||
bool datetime_is_valid(const datetime_t *dt) |
|
||||||
{ |
|
||||||
if (dt == NULL) return false; |
|
||||||
|
|
||||||
// check month first to avoid out-of-bounds read from the MONTH_LENGTHS table
|
|
||||||
if (!(dt->month >= JANUARY && dt->month <= DECEMBER)) return false; |
|
||||||
|
|
||||||
int monthlen = MONTH_LENGTHS[dt->month]; |
|
||||||
if (dt->month == FEBRUARY && is_leap_year(dt->year)) { |
|
||||||
monthlen = 29; |
|
||||||
} |
|
||||||
|
|
||||||
return dt->sec < 60 && |
|
||||||
dt->min < 60 && |
|
||||||
dt->hour < 24 && |
|
||||||
dt->wkday >= MONDAY && |
|
||||||
dt->wkday <= SUNDAY && |
|
||||||
dt->year >= DT_START_YEAR && |
|
||||||
dt->year <= DT_END_YEAR && |
|
||||||
dt->day >= 1 && |
|
||||||
dt->day <= monthlen; |
|
||||||
} |
|
||||||
|
|
||||||
bool datetime_set_weekday(datetime_t *dt) |
|
||||||
{ |
|
||||||
dt->wkday = MONDAY; // prevent the validator func erroring out on invalid weekday
|
|
||||||
if (!datetime_is_valid(dt)) return false; |
|
||||||
dt->wkday = date_weekday(dt->year, dt->month, dt->day); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
enum weekday date_weekday(uint16_t year, enum month month, uint8_t day) |
|
||||||
{ |
|
||||||
uint16_t days = (DT_START_WKDAY - MONDAY) + (year - DT_START_YEAR) * 365 + MONTH_YEARDAYS[month] + (day - 1); |
|
||||||
|
|
||||||
for (uint16_t i = DT_START_YEAR; i <= year; i++) { |
|
||||||
if (is_leap_year(i) && (i < year || month > FEBRUARY)) days++; |
|
||||||
} |
|
||||||
|
|
||||||
return MONDAY + days % 7; |
|
||||||
} |
|
||||||
|
|
@ -1,72 +0,0 @@ |
|||||||
/*
|
|
||||||
* util.c |
|
||||||
* |
|
||||||
* Created on: Aug 12, 2009 |
|
||||||
* Author: johan |
|
||||||
*/ |
|
||||||
|
|
||||||
// adapted from libgomspace
|
|
||||||
|
|
||||||
#include <string.h> |
|
||||||
#include <stdio.h> |
|
||||||
#include "common_utils/hexdump.h" |
|
||||||
|
|
||||||
|
|
||||||
//! Dump memory to debugging output
|
|
||||||
/**
|
|
||||||
* Dumps a chunk of memory to the screen |
|
||||||
*/ |
|
||||||
void hex_dump(FILE * fp, void *src, int len) { |
|
||||||
int i, j=0, k; |
|
||||||
char text[17]; |
|
||||||
|
|
||||||
text[16] = '\0'; |
|
||||||
//printf("Hex dump:\r\n");
|
|
||||||
fprintf(fp, "%p : ", src); |
|
||||||
for(i=0; i<len; i++) { |
|
||||||
j++; |
|
||||||
fprintf(fp, "%02X ", ((volatile unsigned char *)src)[i]); |
|
||||||
if(j == 8) |
|
||||||
fputc(' ', fp); |
|
||||||
if(j == 16) { |
|
||||||
j = 0; |
|
||||||
memcpy(text, &((char *)src)[i-15], 16); |
|
||||||
for(k=0; k<16; k++) { |
|
||||||
if((text[k] < 32) || (text[k] > 126)) { |
|
||||||
text[k] = '.'; |
|
||||||
} |
|
||||||
} |
|
||||||
fprintf(fp, " |%s|\n\r", text); |
|
||||||
if(i<len-1) { |
|
||||||
fprintf(fp, "%p : ", src+i+1); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
if (i % 16) |
|
||||||
fprintf(fp, "\r\n"); |
|
||||||
} |
|
||||||
|
|
||||||
void hex_dump_buff_line(FILE *fp, int addr_size, unsigned pos, char *line, unsigned len) |
|
||||||
{ |
|
||||||
unsigned i; |
|
||||||
|
|
||||||
fprintf(fp, "%0*x", addr_size, pos); |
|
||||||
for (i = 0; i < HEX_DUMP_LINE_BUFF_SIZ; i++) |
|
||||||
{ |
|
||||||
if (!(i % 8)) |
|
||||||
fputc(' ', fp); |
|
||||||
if (i < len) |
|
||||||
fprintf(fp, " %02x", (unsigned char)line[i]); |
|
||||||
else |
|
||||||
fputs(" ", fp); |
|
||||||
} |
|
||||||
fputs(" |", fp); |
|
||||||
for (i = 0; i < HEX_DUMP_LINE_BUFF_SIZ && i < len; i++) |
|
||||||
{ |
|
||||||
if (line[i] >= 32 && line[i] <= 126) |
|
||||||
fprintf(fp, "%c", (unsigned char)line[i]); |
|
||||||
else |
|
||||||
fputc('.', fp); |
|
||||||
} |
|
||||||
fputs("|\r\n", fp); |
|
||||||
} |
|
@ -1,9 +0,0 @@ |
|||||||
set(COMPONENT_ADD_INCLUDEDIRS |
|
||||||
"include") |
|
||||||
|
|
||||||
set(COMPONENT_SRCDIRS |
|
||||||
"src") |
|
||||||
|
|
||||||
set(COMPONENT_REQUIRES tcpip_adapter esp_http_server httpd_utils common_utils) |
|
||||||
|
|
||||||
register_component() |
|
@ -1,2 +0,0 @@ |
|||||||
File and template serving support for the http_server bundled with ESP-IDF. |
|
||||||
|
|
@ -1,3 +0,0 @@ |
|||||||
|
|
||||||
COMPONENT_SRCDIRS := src
|
|
||||||
COMPONENT_ADD_INCLUDEDIRS := include
|
|
@ -1,51 +0,0 @@ |
|||||||
//
|
|
||||||
// Created on 2018/10/17 by Ondrej Hruska <ondra@ondrovo.com>
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef FBNODE_EMBEDDED_FILES_H |
|
||||||
#define FBNODE_EMBEDDED_FILES_H |
|
||||||
|
|
||||||
#include <stdint.h> |
|
||||||
#include <esp_err.h> |
|
||||||
#include <stdbool.h> |
|
||||||
|
|
||||||
struct embedded_file_info { |
|
||||||
const uint8_t * start; |
|
||||||
const uint8_t * end; |
|
||||||
const char * name; |
|
||||||
const char * mime; |
|
||||||
}; |
|
||||||
|
|
||||||
enum file_access_level { |
|
||||||
/** Public = file accessed by a wildcard route */ |
|
||||||
FILE_ACCESS_PUBLIC = 0, |
|
||||||
/** Protected = file included in a template or explicitly specified in a route */ |
|
||||||
FILE_ACCESS_PROTECTED = 1, |
|
||||||
/** Files protected against read-out */ |
|
||||||
FILE_ACCESS_PRIVATE = 2, |
|
||||||
}; |
|
||||||
|
|
||||||
extern const struct embedded_file_info EMBEDDED_FILE_LOOKUP[]; |
|
||||||
extern const size_t EMBEDDED_FILE_LOOKUP_LEN; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Find an embedded file by its name. |
|
||||||
*
|
|
||||||
* This function is weak. It crawls the EMBEDDED_FILE_LOOKUP table and checks for exact match, also |
|
||||||
* testing with www_get_static_file_access_check if the access is allowed. |
|
||||||
* |
|
||||||
* @param name - file name |
|
||||||
* @param access - access level (public - wildcard fallthrough, protected - files for the server, loaded explicitly) |
|
||||||
* @param[out] file - the file struct is stored here if found, unchanged if not found. |
|
||||||
* @return status code |
|
||||||
*/ |
|
||||||
esp_err_t www_get_static_file(const char *name, enum file_access_level access, const struct embedded_file_info **file); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Check file access permission (if using the default www_get_static_file implementation). |
|
||||||
*
|
|
||||||
* This function is weak. The default implementation returns always true. |
|
||||||
*/ |
|
||||||
bool www_get_static_file_access_check(const struct embedded_file_info *file, enum file_access_level access); |
|
||||||
|
|
||||||
#endif //FBNODE_EMBEDDED_FILES_H
|
|
@ -1,227 +0,0 @@ |
|||||||
//
|
|
||||||
// This module implements token substitution in files served by the server.
|
|
||||||
//
|
|
||||||
// Tokens are in the form {token}, or {escape:token}, where escape can be:
|
|
||||||
// - h ... html escape (plain text in a html file, attribute value)
|
|
||||||
// - j ... js escape (for use in JS strings)
|
|
||||||
//
|
|
||||||
// When no escape is specified, the token substitution is written verbatim into the response.
|
|
||||||
//
|
|
||||||
// var foo = "{j:foo}";
|
|
||||||
// <input value="{h:old_value}">
|
|
||||||
// {generated-html-goes-here}
|
|
||||||
//
|
|
||||||
// Token can be made optional by adding '?' at the end (this can't be used for includes).
|
|
||||||
// Such token then simply becomes empty string when not substituted, as opposed to being included in the page verbatim.
|
|
||||||
//
|
|
||||||
// <input value="{h:old_value?}">
|
|
||||||
//
|
|
||||||
// token names can contain alnum, dash, period and underscore, and are case sensitive.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// It is further possible to include a static file with optional key-value replacements. These serve as defaults.
|
|
||||||
//
|
|
||||||
// {@_subfile.html}
|
|
||||||
// {@_subfile.html|key=value lalala}
|
|
||||||
// {@_subfile.html|key=value lalala|other=value}
|
|
||||||
//
|
|
||||||
// File inclusion can be nested, and the files can use replacement tokens as specified by the include statement
|
|
||||||
//
|
|
||||||
// Created on 2019/01/24 by Ondrej Hruska <ondra@ondrovo.com>
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef FBNODE_TOKEN_SUBS_H |
|
||||||
#define FBNODE_TOKEN_SUBS_H |
|
||||||
|
|
||||||
#include "embedded_files.h" |
|
||||||
#include <rom/queue.h> |
|
||||||
#include <esp_err.h> |
|
||||||
#include <esp_http_server.h> |
|
||||||
|
|
||||||
/** Max length of a token buffer (must suffice for all included filenames) */ |
|
||||||
#define MAX_TOKEN_LEN 32 |
|
||||||
|
|
||||||
/** Max length of a key-value substitution when using tpl_kv_replacer;
|
|
||||||
* This is also used internally for in-line replacements in file imports. */ |
|
||||||
#define TPL_KV_KEY_LEN 24 |
|
||||||
/** Max length of a substituion in tpl_kv_replacer */ |
|
||||||
#define TPL_KV_SUBST_LEN 64 |
|
||||||
|
|
||||||
/**
|
|
||||||
* Escape type - argument for httpd_resp_send_chunk_escaped() |
|
||||||
*/ |
|
||||||
typedef enum { |
|
||||||
TPL_ESCAPE_NONE = 0, |
|
||||||
TPL_ESCAPE_HTML, |
|
||||||
TPL_ESCAPE_JS, |
|
||||||
} tpl_escape_t; |
|
||||||
|
|
||||||
enum { |
|
||||||
HTOPT_NONE = 0, |
|
||||||
HTOPT_NO_HEADERS = 1 << 0, |
|
||||||
HTOPT_NO_CLOSE = 1 << 1, |
|
||||||
HTOPT_INCLUDE = HTOPT_NO_HEADERS|HTOPT_NO_CLOSE, |
|
||||||
}; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Send string using a given escaping scheme |
|
||||||
* |
|
||||||
* @param r |
|
||||||
* @param buf - buf to send |
|
||||||
* @param len - buf len, or HTTPD_RESP_USE_STRLEN |
|
||||||
* @param escape - escaping type |
|
||||||
* @return success |
|
||||||
*/ |
|
||||||
esp_err_t httpd_resp_send_chunk_escaped(httpd_req_t *r, const char *buf, ssize_t len, tpl_escape_t escape); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Template substitution callback. Data shall be sent using httpd_resp_send_chunk_escaped(). |
|
||||||
* |
|
||||||
* @param[in,out] context - user-defined page state data |
|
||||||
* @param[in] token - replacement token |
|
||||||
* @return ESP_OK if the token was substituted, ESP_ERR_NOT_FOUND if it is unknown, other errors on e.g. send failure |
|
||||||
*/ |
|
||||||
typedef esp_err_t (*template_subst_t)(httpd_req_t *r, void *context, const char *token, tpl_escape_t escape); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a template file as a response. The content type from the file struct will be used. |
|
||||||
* |
|
||||||
* Use HTOPT_INCLUDE when used to embed a file inside a template. |
|
||||||
* |
|
||||||
* @param r - request |
|
||||||
* @param file_index - file index in EMBEDDED_FILE_LOOKUP |
|
||||||
* @param replacer - substitution callback, can be NULL if only includes are to be processed |
|
||||||
* @param context - arbitrary context, will be passed to the replacer function; can be NULL |
|
||||||
* @param opts - flag options (HTOPT_*) |
|
||||||
*/ |
|
||||||
esp_err_t httpd_send_template_file(httpd_req_t *r, int file_index, template_subst_t replacer, void *context, uint32_t opts); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as httpd_send_template_file, but using an `embedded_file_info` struct. |
|
||||||
*/ |
|
||||||
esp_err_t httpd_send_template_file_struct(httpd_req_t *r, const struct embedded_file_info *file, template_subst_t replacer, void *context, uint32_t opts); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Process and send a string template. |
|
||||||
* The content-type header should be set beforehand, if different from the default (text/html). |
|
||||||
* |
|
||||||
* Use HTOPT_INCLUDE when used to embed a file inside a template. |
|
||||||
* |
|
||||||
* @param r - request |
|
||||||
* @param template - template string (does not have to be terminated by a null byte) |
|
||||||
* @param template_len - length of the template string; -1 to use strlen() |
|
||||||
* @param replacer - substitution callback, can be NULL if only includes are to be processed |
|
||||||
* @param context - arbitrary context, will be passed to the replacer function; can be NULL |
|
||||||
* @param opts - flag options (HTOPT_*) |
|
||||||
*/ |
|
||||||
esp_err_t httpd_send_template(httpd_req_t *r, const char *template, ssize_t template_len, template_subst_t replacer, void *context, uint32_t opts); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a static file. This can be used to just send a file, or to embed a static template as a token substitution. |
|
||||||
* |
|
||||||
* Use HTOPT_INCLUDE when used to embed a file inside a template. |
|
||||||
* |
|
||||||
* Note: use httpd_resp_send_chunk_escaped() or httpd_resp_send_chunk() to send a plain string. |
|
||||||
* |
|
||||||
* @param r - request |
|
||||||
* @param file_index - file index in EMBEDDED_FILE_LOOKUP |
|
||||||
* @param escape - escape option |
|
||||||
* @param opts - flag options (HTOPT_*) |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
esp_err_t httpd_send_static_file(httpd_req_t *r, int file_index, tpl_escape_t escape, uint32_t opts); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as httpd_send_template_file, but using an `embedded_file_info` struct. |
|
||||||
*/ |
|
||||||
esp_err_t httpd_send_static_file_struct(httpd_req_t *r, const struct embedded_file_info *file, tpl_escape_t escape, uint32_t opts); |
|
||||||
|
|
||||||
struct tpl_kv_entry { |
|
||||||
char key[TPL_KV_KEY_LEN]; // copied here
|
|
||||||
char subst[TPL_KV_SUBST_LEN]; // copied here
|
|
||||||
char *subst_heap; |
|
||||||
SLIST_ENTRY(tpl_kv_entry) link; |
|
||||||
}; |
|
||||||
|
|
||||||
SLIST_HEAD(tpl_kv_list, tpl_kv_entry); |
|
||||||
|
|
||||||
/**
|
|
||||||
* key-value replacer that works with a dynamically allocated SLIST. |
|
||||||
* |
|
||||||
* @param r - request |
|
||||||
* @param context - context - must be a pointer to `struct tpl_kv_list` |
|
||||||
* @param token - token to replace |
|
||||||
* @param escape - escape option |
|
||||||
* @return OK/not found/other |
|
||||||
*/ |
|
||||||
esp_err_t tpl_kv_replacer(httpd_req_t *r, void *context, const char *token, tpl_escape_t escape); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a pair into the substitutions list |
|
||||||
* |
|
||||||
* @param head - list head |
|
||||||
* @param key - key, copied |
|
||||||
* @param subst - value, copied |
|
||||||
* @return success (fails if malloc failed) |
|
||||||
*/ |
|
||||||
esp_err_t tpl_kv_add(struct tpl_kv_list *head, const char *key, const char *subst); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a heap-allocated string to the replacer. |
|
||||||
* |
|
||||||
* @param head - list head |
|
||||||
* @param key - key, copied |
|
||||||
* @param subst - value, copied |
|
||||||
* @return success (fails if malloc failed) |
|
||||||
*/ |
|
||||||
esp_err_t tpl_kv_add_heapstr(struct tpl_kv_list *head, const char *key, char *subst); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience function that converts an IP address to string and adds it as a substitution |
|
||||||
* |
|
||||||
* @param head - list head |
|
||||||
* @param key - key, copied |
|
||||||
* @param ip4h - host order ipv4 address |
|
||||||
* @return success |
|
||||||
*/ |
|
||||||
esp_err_t tpl_kv_add_ipv4str(struct tpl_kv_list *head, const char *key, uint32_t ip4h); |
|
||||||
|
|
||||||
/** add int as a substitution; key is copied */ |
|
||||||
esp_err_t tpl_kv_add_int(struct tpl_kv_list *head, const char *key, int32_t num); |
|
||||||
|
|
||||||
/** add long as a substitution; key is copied */ |
|
||||||
esp_err_t tpl_kv_add_long(struct tpl_kv_list *head, const char *key, int64_t num); |
|
||||||
|
|
||||||
/** add printf-formatted value; key is copied */ |
|
||||||
esp_err_t tpl_kv_sprintf(struct tpl_kv_list *head, const char *key, const char *format, ...) |
|
||||||
__attribute__((format(printf,3,4))); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Init a substitutions list (on the stack) |
|
||||||
* |
|
||||||
* @return the list |
|
||||||
*/ |
|
||||||
static inline struct tpl_kv_list tpl_kv_init(void) |
|
||||||
{ |
|
||||||
return (struct tpl_kv_list) {.slh_first = NULL}; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Free the list (head is left alone because it was allocated on the stack) |
|
||||||
* @param head |
|
||||||
*/ |
|
||||||
void tpl_kv_free(struct tpl_kv_list *head); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the map as an ASCII table separated by Record Separator (30) and Unit Separator (31). |
|
||||||
* Content type is set to application/octet-stream. |
|
||||||
* |
|
||||||
* key 31 value 30 |
|
||||||
* key 31 value 30 |
|
||||||
* key 31 value |
|
||||||
* |
|
||||||
* @param req |
|
||||||
*/ |
|
||||||
esp_err_t tpl_kv_send_as_ascii_map(httpd_req_t *req, struct tpl_kv_list *head); |
|
||||||
|
|
||||||
#endif //FBNODE_TOKEN_SUBS_H
|
|
@ -1,29 +0,0 @@ |
|||||||
Place the `rebuild_file_tables.php` script in a `files` subfolder of the main component. |
|
||||||
It requires PHP 7 to run. |
|
||||||
|
|
||||||
This is what the setup should look like |
|
||||||
|
|
||||||
``` |
|
||||||
main/files/embed/index.html |
|
||||||
main/files/rebuild_file_tables.php |
|
||||||
main/CMakeLists.txt |
|
||||||
main/main.c |
|
||||||
``` |
|
||||||
|
|
||||||
Add this to your CMakeLists.txt before `register_component`: |
|
||||||
|
|
||||||
``` |
|
||||||
#begin staticfiles |
|
||||||
#end staticfiles |
|
||||||
``` |
|
||||||
|
|
||||||
The script will update CMakeLists.txt and generate `files_enum.c` and `files_enum.h` when run. |
|
||||||
|
|
||||||
``` |
|
||||||
main/files/files_enum.h |
|
||||||
main/files/files_enum.c |
|
||||||
``` |
|
||||||
|
|
||||||
Ensure `files/files_enum.c` is included in the build. |
|
||||||
|
|
||||||
`www_get_static_file()` is implemented as weak to let you provide custom access authentication logic. |
|
@ -1,170 +0,0 @@ |
|||||||
#!/usr/bin/env php |
|
||||||
<?php |
|
||||||
// This script rebuilds the static files enum, extern symbols pointing to the embedded byte buffers, |
|
||||||
// and the look-up structs table. To add more files, simply add them in the 'files' directory. |
|
||||||
|
|
||||||
// Note that all files will be accessible by the webserver, unless you filter them in embedded_files.c. |
|
||||||
|
|
||||||
|
|
||||||
// List all files |
|
||||||
$files = scandir(__DIR__.'/embed'); |
|
||||||
|
|
||||||
$files = array_filter(array_map(function ($f) { |
|
||||||
if (!is_file(__DIR__.'/embed/'.$f)) return null; |
|
||||||
if (preg_match('/^\.|\.kate-swp|\.bak$|~$|\.sh$/', $f)) return null; |
|
||||||
|
|
||||||
echo "Found: $f\n"; |
|
||||||
return $f; |
|
||||||
}, $files)); |
|
||||||
|
|
||||||
sort($files); |
|
||||||
|
|
||||||
$formatted = array_filter(array_map(function ($f) { |
|
||||||
return "\"files/embed/$f\""; |
|
||||||
}, $files)); |
|
||||||
|
|
||||||
$cmake = file_get_contents(__DIR__.'/../CMakeLists.txt'); |
|
||||||
|
|
||||||
$cmake = preg_replace('/#begin staticfiles\n.*#end staticfiles/s', |
|
||||||
"#begin staticfiles\n". |
|
||||||
"set(COMPONENT_EMBED_FILES\n ". |
|
||||||
implode("\n ", $formatted) . ")\n". |
|
||||||
"#end staticfiles", |
|
||||||
$cmake); |
|
||||||
|
|
||||||
file_put_contents(__DIR__.'/../CMakeLists.txt', $cmake); |
|
||||||
|
|
||||||
|
|
||||||
// Generate a list of files |
|
||||||
|
|
||||||
$num = 0; |
|
||||||
$enum_keys = array_map(function ($f) use(&$num) { |
|
||||||
$a = preg_replace("/[^A-Z0-9_]+/", "_", strtoupper($f)); |
|
||||||
return 'FILE_'. $a.' = '.($num++); |
|
||||||
}, $files); |
|
||||||
|
|
||||||
$keylist = implode(",\n ", $enum_keys); |
|
||||||
|
|
||||||
$struct_array = []; |
|
||||||
|
|
||||||
$externs = array_map(function ($f) use (&$struct_array) { |
|
||||||
$a = preg_replace("/[^A-Z0-9_]+/", "_", strtoupper($f)); |
|
||||||
|
|
||||||
$start = '_binary_'. strtolower($a).'_start'; |
|
||||||
$end = '_binary_'. strtolower($a).'_end'; |
|
||||||
|
|
||||||
static $mimes = array( |
|
||||||
'txt' => 'text/plain', |
|
||||||
'htm' => 'text/html', |
|
||||||
'html' => 'text/html', |
|
||||||
'php' => 'text/html', |
|
||||||
'css' => 'text/css', |
|
||||||
'js' => 'application/javascript', |
|
||||||
'json' => 'application/json', |
|
||||||
'xml' => 'application/xml', |
|
||||||
'swf' => 'application/x-shockwave-flash', |
|
||||||
'flv' => 'video/x-flv', |
|
||||||
|
|
||||||
'pem' => 'application/x-pem-file', |
|
||||||
|
|
||||||
// images |
|
||||||
'png' => 'image/png', |
|
||||||
'jpe' => 'image/jpeg', |
|
||||||
'jpeg' => 'image/jpeg', |
|
||||||
'jpg' => 'image/jpeg', |
|
||||||
'gif' => 'image/gif', |
|
||||||
'bmp' => 'image/bmp', |
|
||||||
'ico' => 'image/vnd.microsoft.icon', |
|
||||||
'tiff' => 'image/tiff', |
|
||||||
'tif' => 'image/tiff', |
|
||||||
'svg' => 'image/svg+xml', |
|
||||||
'svgz' => 'image/svg+xml', |
|
||||||
|
|
||||||
// archives |
|
||||||
'zip' => 'application/zip', |
|
||||||
'rar' => 'application/x-rar-compressed', |
|
||||||
'exe' => 'application/x-msdownload', |
|
||||||
'msi' => 'application/x-msdownload', |
|
||||||
'cab' => 'application/vnd.ms-cab-compressed', |
|
||||||
|
|
||||||
// audio/video |
|
||||||
'mp3' => 'audio/mpeg', |
|
||||||
'qt' => 'video/quicktime', |
|
||||||
'mov' => 'video/quicktime', |
|
||||||
|
|
||||||
// adobe |
|
||||||
'pdf' => 'application/pdf', |
|
||||||
'psd' => 'image/vnd.adobe.photoshop', |
|
||||||
'ai' => 'application/postscript', |
|
||||||
'eps' => 'application/postscript', |
|
||||||
'ps' => 'application/postscript', |
|
||||||
|
|
||||||
// ms office |
|
||||||
'doc' => 'application/msword', |
|
||||||
'rtf' => 'application/rtf', |
|
||||||
'xls' => 'application/vnd.ms-excel', |
|
||||||
'ppt' => 'application/vnd.ms-powerpoint', |
|
||||||
|
|
||||||
// open office |
|
||||||
'odt' => 'application/vnd.oasis.opendocument.text', |
|
||||||
'ods' => 'application/vnd.oasis.opendocument.spreadsheet', |
|
||||||
); |
|
||||||
|
|
||||||
$parts = explode('.', $f); |
|
||||||
$suffix = end($parts); |
|
||||||
$mime = $mimes[$suffix] ?? 'application/octet-stream'; |
|
||||||
|
|
||||||
$len = filesize('embed/'.$f); |
|
||||||
|
|
||||||
$struct_array[] = "[FILE_$a] = {{$start}, {$end}, \"{$f}\", \"{$mime}\"},"; |
|
||||||
|
|
||||||
return |
|
||||||
'extern const uint8_t '.$start.'[];'."\n". |
|
||||||
'extern const uint8_t '.$end.'[];'; |
|
||||||
}, $files); |
|
||||||
|
|
||||||
$externlist = implode("\n", $externs); |
|
||||||
$structlist = implode("\n ", $struct_array); |
|
||||||
|
|
||||||
|
|
||||||
file_put_contents('files_enum.h', <<<FILE |
|
||||||
// Do not change, auto-generated by gen_staticfiles.php |
|
||||||
|
|
||||||
#ifndef _EMBEDDED_FILES_ENUM_H |
|
||||||
#define _EMBEDDED_FILES_ENUM_H |
|
||||||
|
|
||||||
#include <stddef.h> |
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
enum embedded_file_id { |
|
||||||
$keylist, |
|
||||||
FILE_MAX |
|
||||||
}; |
|
||||||
|
|
||||||
struct embedded_file_info { |
|
||||||
const uint8_t * start; |
|
||||||
const uint8_t * end; |
|
||||||
const char * name; |
|
||||||
const char * mime; |
|
||||||
}; |
|
||||||
|
|
||||||
$externlist |
|
||||||
|
|
||||||
extern const struct embedded_file_info EMBEDDED_FILE_LOOKUP[]; |
|
||||||
|
|
||||||
#endif // _EMBEDDED_FILES_ENUM_H |
|
||||||
|
|
||||||
FILE |
|
||||||
); |
|
||||||
|
|
||||||
file_put_contents("files_enum.c", <<<FILE |
|
||||||
// Do not change, auto-generated by gen_staticfiles.php |
|
||||||
#include <stdint.h> |
|
||||||
#include "files_enum.h" |
|
||||||
|
|
||||||
const struct embedded_file_info EMBEDDED_FILE_LOOKUP[] = { |
|
||||||
$structlist |
|
||||||
}; |
|
||||||
|
|
||||||
FILE |
|
||||||
); |
|
@ -1,22 +0,0 @@ |
|||||||
#include <esp_err.h> |
|
||||||
#include "fileserver/embedded_files.h" |
|
||||||
#include "string.h" |
|
||||||
|
|
||||||
esp_err_t __attribute__((weak))
|
|
||||||
www_get_static_file(const char *name, enum file_access_level access, const struct embedded_file_info **file) |
|
||||||
{ |
|
||||||
// simple search by name
|
|
||||||
for(int i = 0; i < EMBEDDED_FILE_LOOKUP_LEN; i++) { |
|
||||||
if (0 == strcmp(EMBEDDED_FILE_LOOKUP[i].name, name)) { |
|
||||||
*file = &EMBEDDED_FILE_LOOKUP[i]; |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return ESP_ERR_NOT_FOUND; |
|
||||||
} |
|
||||||
|
|
||||||
bool __attribute__((weak))
|
|
||||||
www_get_static_file_access_check(const struct embedded_file_info *file, enum file_access_level access) { |
|
||||||
return true; |
|
||||||
} |
|
@ -1,625 +0,0 @@ |
|||||||
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
|
||||||
|
|
||||||
#include <esp_log.h> |
|
||||||
#include <esp_http_server.h> |
|
||||||
#include <rom/queue.h> |
|
||||||
#include <lwip/ip4_addr.h> |
|
||||||
#include <sys/param.h> |
|
||||||
#include <common_utils/utils.h> |
|
||||||
#include <fileserver/token_subs.h> |
|
||||||
|
|
||||||
#include "fileserver/embedded_files.h" |
|
||||||
#include "fileserver/token_subs.h" |
|
||||||
|
|
||||||
#define ESP_TRY(x) \ |
|
||||||
do { \
|
|
||||||
esp_err_t try_er = (x); \
|
|
||||||
if (try_er != ESP_OK) return try_er; \
|
|
||||||
} while(0) |
|
||||||
|
|
||||||
static const char* TAG = "token_subs"; |
|
||||||
|
|
||||||
// TODO implement buffering to avoid sending many tiny chunks when escaping
|
|
||||||
|
|
||||||
/* encode for HTML. returns 0 or 1 - 1 = success */ |
|
||||||
static esp_err_t send_html_chunk(httpd_req_t *r, const char *data, ssize_t len) |
|
||||||
{ |
|
||||||
assert(r); |
|
||||||
assert(data); |
|
||||||
|
|
||||||
int start = 0, end = 0; |
|
||||||
char c; |
|
||||||
if (len < 0) len = (int) strlen(data); |
|
||||||
if (len==0) return ESP_OK; |
|
||||||
|
|
||||||
for (end = 0; end < len; end++) { |
|
||||||
c = data[end]; |
|
||||||
if (c == 0) { |
|
||||||
// we found EOS
|
|
||||||
break; // not return - the last chunk is printed after the loop
|
|
||||||
} |
|
||||||
|
|
||||||
if (c == '"' || c == '\'' || c == '<' || c == '>') { |
|
||||||
if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start)); |
|
||||||
start = end + 1; |
|
||||||
} |
|
||||||
|
|
||||||
if (c == '"') ESP_TRY(httpd_resp_send_chunk(r, """, 5)); |
|
||||||
else if (c == '\'') ESP_TRY(httpd_resp_send_chunk(r, "'", 5)); |
|
||||||
else if (c == '<') ESP_TRY(httpd_resp_send_chunk(r, "<", 4)); |
|
||||||
else if (c == '>') ESP_TRY(httpd_resp_send_chunk(r, ">", 4)); |
|
||||||
} |
|
||||||
|
|
||||||
if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start)); |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
|
|
||||||
/* encode for JS. returns 0 or 1 - 1 = success */ |
|
||||||
static esp_err_t send_js_chunk(httpd_req_t *r, const char *data, ssize_t len) |
|
||||||
{ |
|
||||||
assert(r); |
|
||||||
assert(data); |
|
||||||
|
|
||||||
int start = 0, end = 0; |
|
||||||
char c; |
|
||||||
if (len < 0) len = (int) strlen(data); |
|
||||||
if (len==0) return ESP_OK; |
|
||||||
|
|
||||||
for (end = 0; end < len; end++) { |
|
||||||
c = data[end]; |
|
||||||
if (c == 0) { |
|
||||||
// we found EOS
|
|
||||||
break; // not return - the last chunk is printed after the loop
|
|
||||||
} |
|
||||||
|
|
||||||
if (c == '"' || c == '\\' || c == '/' || c == '\'' || c == '<' || c == '>' || c == '\n' || c == '\r') { |
|
||||||
if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start)); |
|
||||||
start = end + 1; |
|
||||||
} |
|
||||||
|
|
||||||
if (c == '"') ESP_TRY(httpd_resp_send_chunk(r, "\\\"", 2)); |
|
||||||
else if (c == '\'') ESP_TRY(httpd_resp_send_chunk(r, "\\'", 2)); |
|
||||||
else if (c == '\\') ESP_TRY(httpd_resp_send_chunk(r, "\\\\", 2)); |
|
||||||
else if (c == '/') ESP_TRY(httpd_resp_send_chunk(r, "\\/", 2)); |
|
||||||
else if (c == '<') ESP_TRY(httpd_resp_send_chunk(r, "\\u003C", 6)); |
|
||||||
else if (c == '>') ESP_TRY(httpd_resp_send_chunk(r, "\\u003E", 6)); |
|
||||||
else if (c == '\n') ESP_TRY(httpd_resp_send_chunk(r, "\\n", 2)); |
|
||||||
else if (c == '\r') ESP_TRY(httpd_resp_send_chunk(r, "\\r", 2)); |
|
||||||
} |
|
||||||
|
|
||||||
if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start)); |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
esp_err_t httpd_resp_send_chunk_escaped(httpd_req_t *r, const char *buf, ssize_t len, tpl_escape_t escape) |
|
||||||
{ |
|
||||||
switch (escape) { |
|
||||||
default: // this enum should be exhaustive, but in case something went wrong, just print it verbatim
|
|
||||||
|
|
||||||
case TPL_ESCAPE_NONE: |
|
||||||
return httpd_resp_send_chunk(r, buf, len); |
|
||||||
|
|
||||||
case TPL_ESCAPE_HTML: |
|
||||||
return send_html_chunk(r, buf, len); |
|
||||||
|
|
||||||
case TPL_ESCAPE_JS: |
|
||||||
return send_js_chunk(r, buf, len); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t httpd_send_static_file(httpd_req_t *r, int file_index, tpl_escape_t escape, uint32_t opts) |
|
||||||
{ |
|
||||||
assert(file_index < EMBEDDED_FILE_LOOKUP_LEN); |
|
||||||
const struct embedded_file_info *file = &EMBEDDED_FILE_LOOKUP[file_index]; |
|
||||||
|
|
||||||
return httpd_send_static_file_struct(r, file, escape, opts); |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t httpd_send_static_file_struct(httpd_req_t *r, const struct embedded_file_info *file, tpl_escape_t escape, uint32_t opts) |
|
||||||
{ |
|
||||||
if (0 == (opts & HTOPT_NO_HEADERS)) { |
|
||||||
ESP_TRY(httpd_resp_set_type(r, file->mime)); |
|
||||||
ESP_TRY(httpd_resp_set_hdr(r, "Cache-Control", "max-age=86400, public, must-revalidate")); |
|
||||||
} |
|
||||||
|
|
||||||
ESP_TRY(httpd_resp_send_chunk_escaped(r, (const char *) file->start, (size_t)(file->end - file->start), escape)); |
|
||||||
|
|
||||||
if (0 == (opts & HTOPT_NO_CLOSE)) { |
|
||||||
ESP_TRY(httpd_resp_send_chunk(r, NULL, 0)); |
|
||||||
} |
|
||||||
|
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t httpd_send_template_file(httpd_req_t *r, |
|
||||||
int file_index, |
|
||||||
template_subst_t replacer, |
|
||||||
void *context, |
|
||||||
uint32_t opts) |
|
||||||
{ |
|
||||||
assert(file_index < EMBEDDED_FILE_LOOKUP_LEN); |
|
||||||
const struct embedded_file_info *file = &EMBEDDED_FILE_LOOKUP[file_index]; |
|
||||||
return httpd_send_template_file_struct(r,file,replacer,context,opts); |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t httpd_send_template_file_struct(httpd_req_t *r, |
|
||||||
const struct embedded_file_info *file, |
|
||||||
template_subst_t replacer, |
|
||||||
void *context, |
|
||||||
uint32_t opts) |
|
||||||
{ |
|
||||||
if (0 == (opts & HTOPT_NO_HEADERS)) { |
|
||||||
ESP_TRY(httpd_resp_set_type(r, file->mime)); |
|
||||||
ESP_TRY(httpd_resp_set_hdr(r, "Cache-Control", "no-cache, no-store, must-revalidate")); |
|
||||||
} |
|
||||||
|
|
||||||
return httpd_send_template(r, (const char *) file->start, (size_t)(file->end - file->start), replacer, context, opts); |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t tpl_kv_replacer(httpd_req_t *r, void *context, const char *token, tpl_escape_t escape) |
|
||||||
{ |
|
||||||
assert(context); |
|
||||||
assert(token); |
|
||||||
|
|
||||||
struct tpl_kv_entry *entry; |
|
||||||
struct tpl_kv_list *head = context; |
|
||||||
SLIST_FOREACH(entry, head, link) { |
|
||||||
if (0==strcmp(entry->key, token)) { |
|
||||||
if (entry->subst_heap) { |
|
||||||
if (entry->subst_heap[0]) { |
|
||||||
return httpd_resp_send_chunk_escaped(r, entry->subst_heap, -1, escape); |
|
||||||
} |
|
||||||
} else { |
|
||||||
if (entry->subst[0]) { |
|
||||||
return httpd_resp_send_chunk_escaped(r, entry->subst, -1, escape); |
|
||||||
} |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return ESP_ERR_NOT_FOUND; |
|
||||||
} |
|
||||||
|
|
||||||
struct stacked_replacer_context { |
|
||||||
template_subst_t replacer0; |
|
||||||
void *context0; |
|
||||||
template_subst_t replacer1; |
|
||||||
void *context1; |
|
||||||
}; |
|
||||||
|
|
||||||
esp_err_t stacked_replacer(httpd_req_t *r, void *context, const char *token, tpl_escape_t escape) |
|
||||||
{ |
|
||||||
assert(context); |
|
||||||
assert(token); |
|
||||||
|
|
||||||
struct stacked_replacer_context *combo = context; |
|
||||||
|
|
||||||
if (ESP_OK == combo->replacer0(r, combo->context0, token, escape)) { |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
|
|
||||||
if (ESP_OK == combo->replacer1(r, combo->context1, token, escape)) { |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
|
|
||||||
return ESP_ERR_NOT_FOUND; |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t httpd_send_template(httpd_req_t *r, |
|
||||||
const char *template, ssize_t template_len, |
|
||||||
template_subst_t replacer, |
|
||||||
void *context, |
|
||||||
uint32_t opts) |
|
||||||
{ |
|
||||||
if (template_len < 0) template_len = strlen(template); |
|
||||||
|
|
||||||
// replacer and context may be NULL
|
|
||||||
assert(template); |
|
||||||
assert(r); |
|
||||||
|
|
||||||
// data end
|
|
||||||
const char * const end = template + template_len; |
|
||||||
|
|
||||||
// start of to-be-processed data
|
|
||||||
const char * pos = template; |
|
||||||
|
|
||||||
// start position for finding opening braces, updated after a failed match to avoid infinite loop on the same bad token
|
|
||||||
const char * searchpos = pos; |
|
||||||
|
|
||||||
// tokens must be copied to a buffer to allow adding the terminating null byte
|
|
||||||
char token_buf[MAX_TOKEN_LEN]; |
|
||||||
|
|
||||||
while (pos < end) { |
|
||||||
const char * openbr = strchr(searchpos, '{'); |
|
||||||
if (openbr == NULL) { |
|
||||||
// no more templates
|
|
||||||
ESP_TRY(httpd_resp_send_chunk(r, pos, (size_t) (end - pos))); |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
// this brace could start a valid template. check if it seems valid...
|
|
||||||
const char * closebr = strchr(openbr, '}'); |
|
||||||
if (closebr == NULL) { |
|
||||||
// there are no further closing braces, so it can't be a template
|
|
||||||
|
|
||||||
// we also know there can't be any more substitutions, because they would lack a closing } too
|
|
||||||
ESP_TRY(httpd_resp_send_chunk(r, pos, (size_t) (end - pos))); |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
// see if the braces content looks like a token
|
|
||||||
const char *t = openbr + 1; |
|
||||||
bool token_valid = true; |
|
||||||
struct tpl_kv_list substitutions_head = tpl_kv_init(); |
|
||||||
struct tpl_kv_entry *new_subst_pair = NULL; |
|
||||||
|
|
||||||
// a token can be either a name for replacement by the replacer func, or an include with static kv replacements
|
|
||||||
bool is_include = false; |
|
||||||
bool token_is_optional = false; |
|
||||||
const char *token_end = NULL; // points one char after the end of the token
|
|
||||||
|
|
||||||
// parsing the token
|
|
||||||
{ |
|
||||||
if (*t == '@') { |
|
||||||
ESP_LOGD(TAG, "Parsing an Include token"); |
|
||||||
is_include = true; |
|
||||||
t++; |
|
||||||
} |
|
||||||
|
|
||||||
enum { |
|
||||||
P_NAME, P_KEY, P_VALUE |
|
||||||
} state = P_NAME; |
|
||||||
|
|
||||||
const char *kv_start = NULL; |
|
||||||
while (t != closebr || state == P_VALUE) { |
|
||||||
char c = *t; |
|
||||||
|
|
||||||
if (state == P_NAME) { |
|
||||||
if (!((c >= 'a' && c <= 'z') || |
|
||||||
(c >= 'A' && c <= 'Z') || |
|
||||||
(c >= '0' && c <= '9') || |
|
||||||
c == '.' || c == '_' || c == '-' || c == ':')) { |
|
||||||
|
|
||||||
if (!is_include && c == '?') { |
|
||||||
token_end = t; |
|
||||||
token_is_optional = true; |
|
||||||
} else { |
|
||||||
if (is_include && c == '|') { |
|
||||||
token_end = t; |
|
||||||
state = P_KEY; |
|
||||||
kv_start = t + 1; |
|
||||||
// pipe separates the include's filename and literal substitutions
|
|
||||||
// we know there is a closing } somewhere, and {@....| doesn't occur normally, so let's assume it's correct
|
|
||||||
} |
|
||||||
else { |
|
||||||
token_valid = false; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
else if (state == P_KEY) { |
|
||||||
if (!((c >= 'a' && c <= 'z') || |
|
||||||
(c >= 'A' && c <= 'Z') || |
|
||||||
(c >= '0' && c <= '9') || |
|
||||||
c == '.' || c == '_' || c == '-')) { |
|
||||||
if (c == '=') { |
|
||||||
new_subst_pair = calloc(1, sizeof(struct tpl_kv_entry)); |
|
||||||
const size_t klen = MIN(TPL_KV_KEY_LEN, t - kv_start); |
|
||||||
strncpy(new_subst_pair->key, kv_start, klen); |
|
||||||
new_subst_pair->key[klen] = 0; |
|
||||||
|
|
||||||
kv_start = t + 1; |
|
||||||
|
|
||||||
state = P_VALUE; |
|
||||||
// pipe separates the include's filename and literal substitutions
|
|
||||||
// we know there is a closing } somewhere, and {@....| doesn't occur normally, so let's assume it's correct
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
else if (state == P_VALUE) { |
|
||||||
if (c == '|' || c == '}') { |
|
||||||
const size_t vlen = MIN(TPL_KV_SUBST_LEN, t - kv_start); |
|
||||||
strncpy(new_subst_pair->subst, kv_start, vlen); |
|
||||||
new_subst_pair->subst[vlen] = 0; |
|
||||||
|
|
||||||
// attach the kv pair to the list
|
|
||||||
SLIST_INSERT_HEAD(&substitutions_head, new_subst_pair, link); |
|
||||||
ESP_LOGD(TAG, "Adding subs kv %s -> %s", new_subst_pair->key, new_subst_pair->subst); |
|
||||||
new_subst_pair = NULL; |
|
||||||
|
|
||||||
kv_start = t + 1; // go past the pipe
|
|
||||||
state = P_KEY; |
|
||||||
|
|
||||||
if (t == closebr) { |
|
||||||
break; // found the ending brace, so let's quit the kv parse loop
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
t++; |
|
||||||
} |
|
||||||
// clean up after a messed up subs kv pairs syntax
|
|
||||||
if (new_subst_pair != NULL) { |
|
||||||
free(new_subst_pair); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (!token_valid) { |
|
||||||
// false match, include it in the block to send before the next token
|
|
||||||
searchpos = openbr + 1; |
|
||||||
ESP_LOGD(TAG, "Skip invalid token near %10s", openbr); |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
// now we know it looks like a substitution token
|
|
||||||
|
|
||||||
// flush data before the token
|
|
||||||
if (pos != openbr) ESP_TRY(httpd_resp_send_chunk(r, pos, (size_t) (openbr - pos))); |
|
||||||
|
|
||||||
const char *token_start = openbr; |
|
||||||
|
|
||||||
tpl_escape_t escape = TPL_ESCAPE_NONE; |
|
||||||
|
|
||||||
// extract and terminate the token
|
|
||||||
size_t token_len = MIN(MAX_TOKEN_LEN-1, closebr - openbr - 1); |
|
||||||
if (token_end) { |
|
||||||
token_len = MIN(token_len, token_end - openbr - 1); |
|
||||||
} |
|
||||||
|
|
||||||
if (is_include) { |
|
||||||
token_start += 1; // skip the @
|
|
||||||
token_len -= 1; |
|
||||||
} else { |
|
||||||
if (0 == strncmp("h:", openbr + 1, 2)) { |
|
||||||
escape = TPL_ESCAPE_HTML; |
|
||||||
token_start += 2; |
|
||||||
token_len -= 2; |
|
||||||
} |
|
||||||
else if (0 == strncmp("j:", openbr + 1, 2)) { |
|
||||||
escape = TPL_ESCAPE_JS; |
|
||||||
token_start += 2; |
|
||||||
token_len -= 2; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
strncpy(token_buf, token_start+1, token_len); |
|
||||||
token_buf[token_len] = 0; |
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Token: %s", token_buf); |
|
||||||
|
|
||||||
esp_err_t rv; |
|
||||||
|
|
||||||
if (is_include) { |
|
||||||
ESP_LOGD(TAG, "Trying to include a sub-file"); |
|
||||||
|
|
||||||
const struct embedded_file_info *file = NULL; |
|
||||||
rv = www_get_static_file(token_buf, FILE_ACCESS_PROTECTED, &file); |
|
||||||
if (rv != ESP_OK) { |
|
||||||
ESP_LOGE(TAG, "Failed to statically include \"%s\" in a template - %s", token_buf, esp_err_to_name(rv)); |
|
||||||
// this will cause the token to be emitted verbatim
|
|
||||||
} else { |
|
||||||
ESP_LOGD(TAG, "Descending..."); |
|
||||||
|
|
||||||
// combine the two replacers
|
|
||||||
struct stacked_replacer_context combo = { |
|
||||||
.replacer0 = replacer, |
|
||||||
.context0 = context, |
|
||||||
.replacer1 = tpl_kv_replacer, |
|
||||||
.context1 = &substitutions_head |
|
||||||
}; |
|
||||||
|
|
||||||
rv = httpd_send_template_file_struct(r, file, stacked_replacer, &combo, HTOPT_INCLUDE); |
|
||||||
ESP_LOGD(TAG, "...back in parent"); |
|
||||||
} |
|
||||||
|
|
||||||
// tear down the list
|
|
||||||
tpl_kv_free(&substitutions_head); |
|
||||||
|
|
||||||
if (rv != ESP_OK) { |
|
||||||
// just send it verbatim...
|
|
||||||
ESP_TRY(httpd_resp_send_chunk(r, openbr, (size_t) (closebr - openbr + 1))); |
|
||||||
} |
|
||||||
} else { |
|
||||||
if (replacer) { |
|
||||||
ESP_LOGD(TAG, "Running replacer for \"%s\" with escape %d", token_buf, escape); |
|
||||||
rv = replacer(r, context, token_buf, escape); |
|
||||||
|
|
||||||
if (rv != ESP_OK) { |
|
||||||
if (rv == ESP_ERR_NOT_FOUND) { |
|
||||||
ESP_LOGD(TAG, "Token rejected"); |
|
||||||
// optional token becomes empty string if not replaced
|
|
||||||
if (!token_is_optional) { |
|
||||||
ESP_LOGD(TAG, "Not optional, keeping verbatim"); |
|
||||||
// replacer rejected the token, keep it verbatim
|
|
||||||
ESP_TRY(httpd_resp_send_chunk(r, openbr, (size_t) (closebr - openbr + 1))); |
|
||||||
} |
|
||||||
} |
|
||||||
else { |
|
||||||
ESP_LOGE(TAG, "Unexpected error from replacer func: 0x%02x - %s", rv, esp_err_to_name(rv)); |
|
||||||
return rv; |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
ESP_LOGD(TAG, "Not replacer"); |
|
||||||
// no replacer, only includes - used for 'static' files
|
|
||||||
if (!token_is_optional) { |
|
||||||
ESP_LOGD(TAG, "Token not optional, keeping verbatim"); |
|
||||||
ESP_TRY(httpd_resp_send_chunk(r, openbr, (size_t) (closebr - openbr + 1))); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
searchpos = pos = closebr + 1; |
|
||||||
} |
|
||||||
|
|
||||||
if (0 == (opts & HTOPT_NO_CLOSE)) { |
|
||||||
ESP_TRY(httpd_resp_send_chunk(r, NULL, 0)); |
|
||||||
} |
|
||||||
|
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
esp_err_t tpl_kv_add_int(struct tpl_kv_list *head, const char *key, int32_t num) |
|
||||||
{ |
|
||||||
char buf[12]; |
|
||||||
itoa(num, buf, 10); |
|
||||||
return tpl_kv_add(head, key, buf); |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t tpl_kv_add_long(struct tpl_kv_list *head, const char *key, int64_t num) |
|
||||||
{ |
|
||||||
char buf[21]; |
|
||||||
sprintf(buf, "%"PRIi64, num); |
|
||||||
return tpl_kv_add(head, key, buf); |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t tpl_kv_add_ipv4str(struct tpl_kv_list *head, const char *key, uint32_t ip4h) |
|
||||||
{ |
|
||||||
char buf[IP4ADDR_STRLEN_MAX]; |
|
||||||
ip4_addr_t addr; |
|
||||||
addr.addr = lwip_htonl(ip4h); |
|
||||||
ip4addr_ntoa_r(&addr, buf, IP4ADDR_STRLEN_MAX); |
|
||||||
|
|
||||||
return tpl_kv_add(head, key, buf); |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t tpl_kv_add(struct tpl_kv_list *head, const char *key, const char *subst) |
|
||||||
{ |
|
||||||
ESP_LOGD(TAG, "kv add subs %s := %s", key, subst); |
|
||||||
struct tpl_kv_entry *entry = calloc(1, sizeof(struct tpl_kv_entry)); |
|
||||||
if (entry == NULL) return ESP_ERR_NO_MEM; |
|
||||||
|
|
||||||
assert(strlen(key) < TPL_KV_KEY_LEN); |
|
||||||
assert(strlen(subst) < TPL_KV_SUBST_LEN); |
|
||||||
|
|
||||||
strncpy(entry->key, key, TPL_KV_KEY_LEN); |
|
||||||
entry->key[TPL_KV_KEY_LEN - 1] = 0; |
|
||||||
|
|
||||||
strncpy(entry->subst, subst, TPL_KV_SUBST_LEN - 1); |
|
||||||
entry->subst[TPL_KV_KEY_LEN - 1] = 0; |
|
||||||
|
|
||||||
SLIST_INSERT_HEAD(head, entry, link); |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t tpl_kv_add_heapstr(struct tpl_kv_list *head, const char *key, char *subst) |
|
||||||
{ |
|
||||||
ESP_LOGD(TAG, "kv add subs %s := (heap str)", key); |
|
||||||
struct tpl_kv_entry *entry = calloc(1, sizeof(struct tpl_kv_entry)); |
|
||||||
if (entry == NULL) return ESP_ERR_NO_MEM; |
|
||||||
|
|
||||||
assert(strlen(key) < TPL_KV_KEY_LEN); |
|
||||||
|
|
||||||
strncpy(entry->key, key, TPL_KV_KEY_LEN); |
|
||||||
entry->key[TPL_KV_KEY_LEN - 1] = 0; |
|
||||||
|
|
||||||
entry->subst_heap = subst; |
|
||||||
|
|
||||||
SLIST_INSERT_HEAD(head, entry, link); |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t tpl_kv_sprintf(struct tpl_kv_list *head, const char *key, const char *format, ...) |
|
||||||
{ |
|
||||||
ESP_LOGD(TAG, "kv printf %s := %s", key, format); |
|
||||||
struct tpl_kv_entry *entry = calloc(1, sizeof(struct tpl_kv_entry)); |
|
||||||
if (entry == NULL) return ESP_ERR_NO_MEM; |
|
||||||
|
|
||||||
assert(strlen(key) < TPL_KV_KEY_LEN); |
|
||||||
|
|
||||||
strncpy(entry->key, key, TPL_KV_KEY_LEN); |
|
||||||
entry->key[TPL_KV_KEY_LEN - 1] = 0; |
|
||||||
|
|
||||||
va_list list; |
|
||||||
va_start(list, format); |
|
||||||
vsnprintf(entry->subst, TPL_KV_SUBST_LEN, format, list); |
|
||||||
va_end(list); |
|
||||||
entry->subst[TPL_KV_KEY_LEN - 1] = 0; |
|
||||||
|
|
||||||
SLIST_INSERT_HEAD(head, entry, link); |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
|
|
||||||
void tpl_kv_free(struct tpl_kv_list *head) |
|
||||||
{ |
|
||||||
struct tpl_kv_entry *item, *next; |
|
||||||
SLIST_FOREACH_SAFE(item, head, link, next) { |
|
||||||
if (item->subst_heap) { |
|
||||||
free(item->subst_heap); |
|
||||||
item->subst_heap = NULL; |
|
||||||
} |
|
||||||
|
|
||||||
free(item); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t tpl_kv_send_as_ascii_map(httpd_req_t *req, struct tpl_kv_list *head) |
|
||||||
{ |
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8"); |
|
||||||
|
|
||||||
#define BUF_CAP 512 |
|
||||||
char *buf_head = malloc(BUF_CAP); |
|
||||||
if (!buf_head) { |
|
||||||
ESP_LOGE(TAG, "Malloc err"); |
|
||||||
return ESP_FAIL; |
|
||||||
} |
|
||||||
char *buf = buf_head; |
|
||||||
size_t cap = BUF_CAP; |
|
||||||
struct tpl_kv_entry *entry; |
|
||||||
|
|
||||||
// GCC nested function
|
|
||||||
esp_err_t send_part() { |
|
||||||
esp_err_t suc = httpd_resp_send_chunk(req, buf_head, BUF_CAP-cap); |
|
||||||
buf = buf_head; // buf is assigned to buf head
|
|
||||||
cap = BUF_CAP; |
|
||||||
if (suc != ESP_OK) { |
|
||||||
ESP_LOGE(TAG, "Error sending buffer"); |
|
||||||
free(buf_head); |
|
||||||
httpd_resp_send_chunk(req, NULL, 0); |
|
||||||
} |
|
||||||
return suc; |
|
||||||
} |
|
||||||
|
|
||||||
SLIST_FOREACH(entry, head, link) { |
|
||||||
while(NULL == (buf = append(buf, entry->key, &cap))) ESP_TRY(send_part()); |
|
||||||
while(NULL == (buf = append(buf, "\x1f", &cap))) ESP_TRY(send_part()); |
|
||||||
|
|
||||||
if (entry->subst_heap) { |
|
||||||
if (strlen(entry->subst_heap) >= BUF_CAP) { |
|
||||||
// send what we have
|
|
||||||
ESP_TRY(send_part()); |
|
||||||
esp_err_t suc = httpd_resp_send_chunk(req, entry->subst_heap, -1); |
|
||||||
if (suc != ESP_OK) { |
|
||||||
ESP_LOGE(TAG, "Error sending buffer"); |
|
||||||
free(buf_head); |
|
||||||
httpd_resp_send_chunk(req, NULL, 0); |
|
||||||
} |
|
||||||
} else { |
|
||||||
while (NULL == (buf = append(buf, entry->subst_heap, &cap))) ESP_TRY(send_part()); |
|
||||||
} |
|
||||||
} else { |
|
||||||
while(NULL == (buf = append(buf, entry->subst, &cap))) ESP_TRY(send_part()); |
|
||||||
} |
|
||||||
|
|
||||||
if (entry->link.sle_next) { |
|
||||||
while(NULL == (buf = append(buf, "\x1e", &cap))) ESP_TRY(send_part()); |
|
||||||
} |
|
||||||
} |
|
||||||
// send leftovers
|
|
||||||
if (buf != buf_head) { |
|
||||||
esp_err_t suc = httpd_resp_send_chunk(req, buf_head, BUF_CAP-cap); |
|
||||||
if (suc != ESP_OK) { |
|
||||||
ESP_LOGE(TAG, "Error sending buffer"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Commit
|
|
||||||
httpd_resp_send_chunk(req, NULL, 0); |
|
||||||
free(buf_head); |
|
||||||
return ESP_OK; |
|
||||||
} |
|
@ -1,9 +0,0 @@ |
|||||||
set(COMPONENT_ADD_INCLUDEDIRS |
|
||||||
"include") |
|
||||||
|
|
||||||
set(COMPONENT_SRCDIRS |
|
||||||
"src") |
|
||||||
|
|
||||||
set(COMPONENT_REQUIRES tcpip_adapter esp_http_server common_utils) |
|
||||||
|
|
||||||
register_component() |
|
@ -1,4 +0,0 @@ |
|||||||
Functions enriching the HTTP server bundled with ESP-IDF. |
|
||||||
This package includes HTTP-related utilities, captive |
|
||||||
portal implementation, and a cookie-based session system with a |
|
||||||
key-value store and expirations. |
|
@ -1,3 +0,0 @@ |
|||||||
|
|
||||||
COMPONENT_SRCDIRS := src
|
|
||||||
COMPONENT_ADD_INCLUDEDIRS := include
|
|
@ -1,32 +0,0 @@ |
|||||||
#ifndef HTTPD_UTILS_CAPTIVE_H |
|
||||||
#define HTTPD_UTILS_CAPTIVE_H |
|
||||||
|
|
||||||
#include <esp_http_server.h> |
|
||||||
#include <esp_err.h> |
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect if needed when a captive portal capture is detected |
|
||||||
* |
|
||||||
* @param r |
|
||||||
* @return ESP_OK on redirect, ESP_ERR_NOT_FOUND if not needed, other error on failure |
|
||||||
*/ |
|
||||||
esp_err_t httpd_captive_redirect(httpd_req_t *r); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Get URL to redirect to. WEAK. |
|
||||||
* |
|
||||||
* @param r |
|
||||||
* @param buf |
|
||||||
* @param maxlen |
|
||||||
* @return http(s)://foo.bar/
|
|
||||||
*/ |
|
||||||
esp_err_t httpd_captive_redirect_get_url(httpd_req_t *r, char *buf, size_t maxlen); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Get captive portal domain. WEAK. |
|
||||||
* |
|
||||||
* @return foo.bar |
|
||||||
*/ |
|
||||||
const char * httpd_captive_redirect_get_domain(); |
|
||||||
|
|
||||||
#endif //HTTPD_UTILS_CAPTIVE_H
|
|
@ -1,13 +0,0 @@ |
|||||||
#ifndef HTTPD_FDIPV4_H |
|
||||||
#define HTTPD_FDIPV4_H |
|
||||||
|
|
||||||
/**
|
|
||||||
* Get IP address for a FD |
|
||||||
* |
|
||||||
* @param fd |
|
||||||
* @param[out] ipv4 |
|
||||||
* @return success |
|
||||||
*/ |
|
||||||
esp_err_t fd_to_ipv4(int fd, in_addr_t *ipv4); |
|
||||||
|
|
||||||
#endif //HTTPD_FDIPV4_H
|
|
@ -1,16 +0,0 @@ |
|||||||
#ifndef HTTPD_UTILS_REDIRECT_H |
|
||||||
#define HTTPD_UTILS_REDIRECT_H |
|
||||||
|
|
||||||
#include <esp_http_server.h> |
|
||||||
#include <esp_err.h> |
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to other URI - sends a HTTP response from the http server |
|
||||||
* |
|
||||||
* @param r - request |
|
||||||
* @param uri - target uri |
|
||||||
* @return success |
|
||||||
*/ |
|
||||||
esp_err_t httpd_redirect_to(httpd_req_t *r, const char *uri); |
|
||||||
|
|
||||||
#endif // HTTPD_UTILS_REDIRECT_H
|
|
@ -1,55 +0,0 @@ |
|||||||
/**
|
|
||||||
* Session store system main include file |
|
||||||
* |
|
||||||
* Created on 2019/07/13. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef HTTPD_UTILS_SESSION_H |
|
||||||
#define HTTPD_UTILS_SESSION_H |
|
||||||
|
|
||||||
#include "session_kvmap.h" |
|
||||||
#include "session_store.h" |
|
||||||
|
|
||||||
// Customary keys
|
|
||||||
|
|
||||||
/** Session key for OK flash message */ |
|
||||||
#define SESS_FLASH_OK "flash_ok" |
|
||||||
/** Session key for error flash message */ |
|
||||||
#define SESS_FLASH_ERR "flash_err" |
|
||||||
/** Session key for a "logged in" flag. Value is 1 if logged in. */ |
|
||||||
#define SESS_AUTHED "authed" |
|
||||||
|
|
||||||
// ..
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to /login form if unauthed, |
|
||||||
* but also retrieve the session key-value store for further use |
|
||||||
*/ |
|
||||||
#define HTTP_GET_AUTHED_SESSION(kvstore, r) do { \ |
|
||||||
kvstore = httpd_req_find_session_and((r), SESS_GET_DATA); \
|
|
||||||
if (NULL == kvstore || NULL == sess_kv_map_get(kvstore, SESS_AUTHED)) { \
|
|
||||||
return httpd_redirect_to((r), "/login"); \
|
|
||||||
} \
|
|
||||||
} while(0) |
|
||||||
|
|
||||||
/**
|
|
||||||
* Start or resume a session without checking for authed status. |
|
||||||
* When started, the session cookie is added to the response immediately. |
|
||||||
* |
|
||||||
* @param[out] kvstore - a place to store the allocated or retrieved session kvmap |
|
||||||
* @param[in] r - request |
|
||||||
* @return success |
|
||||||
*/ |
|
||||||
esp_err_t HTTP_GET_SESSION(sess_kv_map_t **kvstore, httpd_req_t *r); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to the login form if unauthed. |
|
||||||
* This is the same as `HTTP_GET_AUTHED_SESSION`, except the kvstore variable is |
|
||||||
* not needed in the uri handler calling this, so it is declared internally. |
|
||||||
*/ |
|
||||||
#define HTTP_REDIRECT_IF_UNAUTHED(r) do { \ |
|
||||||
sess_kv_map_t *_kvstore; \
|
|
||||||
HTTP_GET_AUTHED_SESSION(_kvstore, r); \
|
|
||||||
} while(0) |
|
||||||
|
|
||||||
#endif // HTTPD_UTILS_SESSION_H
|
|
@ -1,77 +0,0 @@ |
|||||||
/**
|
|
||||||
* Simple key-value map for session data storage. |
|
||||||
* Takes care of dynamic allocation and cleanup. |
|
||||||
*
|
|
||||||
* Created on 2019/01/28. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef SESSION_KVMAP_H |
|
||||||
#define SESSION_KVMAP_H |
|
||||||
|
|
||||||
/**
|
|
||||||
* Prototype for a free() func to clean up session-held objects |
|
||||||
*/ |
|
||||||
typedef void (*sess_kv_free_func_t)(void *obj); |
|
||||||
|
|
||||||
typedef struct sess_kv_map sess_kv_map_t; |
|
||||||
|
|
||||||
#define SESS_KVMAP_KEY_LEN 16 |
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocate a new session key-value store |
|
||||||
* |
|
||||||
* @return the store, NULL on error |
|
||||||
*/ |
|
||||||
sess_kv_map_t *sess_kv_map_alloc(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Free the session kv store. |
|
||||||
* |
|
||||||
* @param head - store head |
|
||||||
*/ |
|
||||||
void sess_kv_map_free(void *head); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a value from the session kv store. |
|
||||||
* |
|
||||||
* @param head - store head |
|
||||||
* @param key - key to get a value for |
|
||||||
* @return the value, or NULL if not found |
|
||||||
*/ |
|
||||||
void *sess_kv_map_get(sess_kv_map_t *head, const char *key); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Get and remove a value from the session store. |
|
||||||
* |
|
||||||
* The free function is not called in this case and the recipient is |
|
||||||
* responsible for cleaning it up correctly. |
|
||||||
* |
|
||||||
* @param head - store head |
|
||||||
* @param key - key to get a value for |
|
||||||
* @return the value, or NULL if not found |
|
||||||
*/ |
|
||||||
void * sess_kv_map_take(sess_kv_map_t *head, const char *key); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an entry from the session by its key name. |
|
||||||
* The slot is not free'd yet, but is made available for reuse. |
|
||||||
* |
|
||||||
* @param head - store head |
|
||||||
* @param key - key to remove |
|
||||||
* @return success |
|
||||||
*/ |
|
||||||
esp_err_t sess_kv_map_remove(sess_kv_map_t *head, const char *key); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a key value. If there is an old value stored, it will be freed by its free function and replaced by the new one. |
|
||||||
* Otherwise a new slot is allocated for it, or a previously released one is reused. |
|
||||||
* |
|
||||||
* @param head - store head |
|
||||||
* @param key - key to assign to |
|
||||||
* @param value - new value |
|
||||||
* @param free_fn - value free func |
|
||||||
* @return success |
|
||||||
*/ |
|
||||||
esp_err_t sess_kv_map_set(sess_kv_map_t *head, const char *key, void *value, sess_kv_free_func_t free_fn); |
|
||||||
|
|
||||||
#endif //SESSION_KVMAP_H
|
|
@ -1,100 +0,0 @@ |
|||||||
/**
|
|
||||||
* Cookie-based session store |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef SESSION_STORE_H |
|
||||||
#define SESSION_STORE_H |
|
||||||
|
|
||||||
#include "esp_http_server.h" |
|
||||||
|
|
||||||
#define SESSION_EXPIRY_TIME_S 60*30 |
|
||||||
#define SESSION_COOKIE_NAME "SESSID" |
|
||||||
|
|
||||||
/** function that frees a session data object */ |
|
||||||
typedef void (*sess_data_free_fn_t)(void *); |
|
||||||
|
|
||||||
enum session_find_action { |
|
||||||
SESS_DROP, SESS_GET_DATA |
|
||||||
}; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Find session and either get data, or drop it. |
|
||||||
* |
|
||||||
* @param cookie |
|
||||||
* @param action |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
void *session_find_and(const char *cookie, enum session_find_action action); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the session store. |
|
||||||
* Safely empty it if initialized |
|
||||||
*/ |
|
||||||
void session_store_init(void); |
|
||||||
|
|
||||||
// placeholder for when no data is stored
|
|
||||||
#define SESSION_DUMMY ((void *) 1) |
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new session. Data must not be NULL, because it wouldn't be possible |
|
||||||
* to distinguish between NULL value and session not found in return values. |
|
||||||
* It can be e.g. 1 if no data storage is needed. |
|
||||||
* |
|
||||||
* @param data - data object to attach to the session |
|
||||||
* @param free_fn - function that disposes of the data when the session expires |
|
||||||
* @return NULL on error, or the new session ID. This is a live pointer into the session structure, |
|
||||||
* must be copied if stored, as it can become invalid at any time |
|
||||||
*/ |
|
||||||
const char *session_new(void *data, sess_data_free_fn_t free_fn); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a session by it's ID (from a cookie) |
|
||||||
* |
|
||||||
* @param cookie - session ID string |
|
||||||
* @return session data (void*), or NULL |
|
||||||
*/ |
|
||||||
void *session_find(const char *cookie); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Loop through all sessions and drop these that expired. |
|
||||||
*/ |
|
||||||
void session_drop_expired(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Drop a session by its ID. Does nothing if not found. |
|
||||||
* |
|
||||||
* @param cookie - session ID string |
|
||||||
*/ |
|
||||||
void session_drop(const char *cookie); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the Cookie header from a request, and do something with the corresponding session. |
|
||||||
* |
|
||||||
* To also delete the cookie, use req_delete_session_cookie(r) |
|
||||||
* |
|
||||||
* @param r - request |
|
||||||
* @param action - what to do with the session |
|
||||||
* @return session data, NULL if removed or not found |
|
||||||
*/ |
|
||||||
void *httpd_req_find_session_and(httpd_req_t *r, enum session_find_action action); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a header that deletes the session cookie |
|
||||||
* |
|
||||||
* @param r - request |
|
||||||
*/ |
|
||||||
void httpd_resp_delete_session_cookie(httpd_req_t *r); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a header that sets the session cookie. |
|
||||||
* |
|
||||||
* This must be called after creating a session (e.g. user logged in) to make it persistent. |
|
||||||
* |
|
||||||
* @attention NOT RE-ENTRANT, CAN'T BE USED AGAIN UNTIL THE REQUEST IT WAS CALLED FOR IS DISPATCHED. |
|
||||||
* |
|
||||||
* @param r - request |
|
||||||
* @param cookie - cookie ID |
|
||||||
*/ |
|
||||||
void httpd_resp_set_session_cookie(httpd_req_t *r, const char *cookie); |
|
||||||
|
|
||||||
#endif //SESSION_STORE_H
|
|
@ -1,106 +0,0 @@ |
|||||||
#include <esp_wifi_types.h> |
|
||||||
#include <esp_wifi.h> |
|
||||||
#include <esp_log.h> |
|
||||||
#include <fcntl.h> |
|
||||||
#include <sys/socket.h> |
|
||||||
#include <sys/param.h> |
|
||||||
|
|
||||||
#include "httpd_utils/captive.h" |
|
||||||
#include "httpd_utils/fd_to_ipv4.h" |
|
||||||
#include "httpd_utils/redirect.h" |
|
||||||
#include <common_utils/utils.h> |
|
||||||
|
|
||||||
static const char *TAG="captive"; |
|
||||||
|
|
||||||
const char * __attribute__((weak)) |
|
||||||
httpd_captive_redirect_get_domain(void) |
|
||||||
{ |
|
||||||
return "fb_node.captive"; |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t __attribute__((weak)) |
|
||||||
httpd_captive_redirect_get_url(httpd_req_t *r, char *buf, size_t maxlen) |
|
||||||
{ |
|
||||||
buf = append(buf, "http://", &maxlen); |
|
||||||
buf = append(buf, httpd_captive_redirect_get_domain(), &maxlen); |
|
||||||
append(buf, "/", &maxlen); |
|
||||||
|
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t httpd_captive_redirect(httpd_req_t *r) |
|
||||||
{ |
|
||||||
// must be static to survive being used in the redirect header
|
|
||||||
static char s_buf[64]; |
|
||||||
|
|
||||||
wifi_mode_t mode = 0; |
|
||||||
esp_wifi_get_mode(&mode); |
|
||||||
|
|
||||||
// Check if we have an softap interface. No point checking IPs and hostnames if the client can't be on AP.
|
|
||||||
if (mode == WIFI_MODE_STA || mode == WIFI_MODE_NULL) { |
|
||||||
goto no_redirect; |
|
||||||
} |
|
||||||
|
|
||||||
int fd = httpd_req_to_sockfd(r); |
|
||||||
|
|
||||||
tcpip_adapter_ip_info_t apip; |
|
||||||
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &apip); |
|
||||||
|
|
||||||
u32_t client_addr; |
|
||||||
if(ESP_OK != fd_to_ipv4(fd, &client_addr)) { |
|
||||||
return ESP_FAIL; |
|
||||||
} |
|
||||||
|
|
||||||
ESP_LOGD(TAG, "[captive] Client addr = 0x%08x, ap addr 0x%08x, ap nmask 0x%08x", |
|
||||||
client_addr, |
|
||||||
apip.ip.addr, |
|
||||||
apip.netmask.addr |
|
||||||
); |
|
||||||
|
|
||||||
// Check if client IP looks like from our AP dhcps
|
|
||||||
if ((client_addr & apip.netmask.addr) != (apip.ip.addr & apip.netmask.addr)) { |
|
||||||
ESP_LOGD(TAG, "[captive] Client not in AP IP range"); |
|
||||||
goto no_redirect; |
|
||||||
} |
|
||||||
|
|
||||||
// Get requested hostname from the header
|
|
||||||
esp_err_t rv = httpd_req_get_hdr_value_str(r, "Host", s_buf, 64); |
|
||||||
if (rv != ESP_OK) { |
|
||||||
ESP_LOGW(TAG, "[captive] No host in request?"); |
|
||||||
goto no_redirect; |
|
||||||
} |
|
||||||
|
|
||||||
ESP_LOGD(TAG, "[captive] Candidate for redirect: %s%s", s_buf, r->uri); |
|
||||||
|
|
||||||
// Never redirect if host is an IP
|
|
||||||
if (strlen(s_buf)>7) { |
|
||||||
bool isIP = 1; |
|
||||||
for (int x = 0; x < strlen(s_buf); x++) { |
|
||||||
if (s_buf[x] != '.' && (s_buf[x] < '0' || s_buf[x] > '9')) { |
|
||||||
isIP = 0; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (isIP) { |
|
||||||
ESP_LOGD(TAG, "[captive] Access via IP, no redirect needed"); |
|
||||||
goto no_redirect; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Redirect if host differs
|
|
||||||
// - this can be e.g. connectivitycheck.gstatic.com or the equivalent for ios
|
|
||||||
|
|
||||||
if (0 != strcmp(s_buf, httpd_captive_redirect_get_domain())) { |
|
||||||
ESP_LOGD(TAG, "[captive] Host differs, redirecting..."); |
|
||||||
|
|
||||||
httpd_captive_redirect_get_url(r, s_buf, 64); |
|
||||||
return httpd_redirect_to(r, s_buf); |
|
||||||
} else { |
|
||||||
ESP_LOGD(TAG, "[captive] Host is OK"); |
|
||||||
goto no_redirect; |
|
||||||
} |
|
||||||
|
|
||||||
no_redirect: |
|
||||||
return ESP_ERR_NOT_FOUND; |
|
||||||
} |
|
@ -1,42 +0,0 @@ |
|||||||
#include <esp_wifi_types.h> |
|
||||||
#include <esp_wifi.h> |
|
||||||
#include <esp_log.h> |
|
||||||
#include <fcntl.h> |
|
||||||
#include <sys/socket.h> |
|
||||||
|
|
||||||
#include "httpd_utils/fd_to_ipv4.h" |
|
||||||
|
|
||||||
static const char *TAG = "fd2ipv4"; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Get IP address for a FD |
|
||||||
* |
|
||||||
* @param fd |
|
||||||
* @param[out] ipv4 |
|
||||||
* @return success |
|
||||||
*/ |
|
||||||
esp_err_t fd_to_ipv4(int fd, in_addr_t *ipv4) |
|
||||||
{ |
|
||||||
struct sockaddr_in6 addr; |
|
||||||
size_t len = sizeof(addr); |
|
||||||
int rv = getpeername(fd, (struct sockaddr *) &addr, &len); |
|
||||||
if (rv != 0) { |
|
||||||
ESP_LOGE(TAG, "Failed to get IP addr for fd %d", fd); |
|
||||||
return ESP_FAIL; |
|
||||||
} |
|
||||||
|
|
||||||
uint32_t client_addr = 0; |
|
||||||
if (addr.sin6_family == AF_INET6) { |
|
||||||
// this would fail in a real ipv6 network
|
|
||||||
// with ipv4 the addr is simply in the last ipv6 byte
|
|
||||||
struct sockaddr_in6 *s = &addr; |
|
||||||
client_addr = s->sin6_addr.un.u32_addr[3]; |
|
||||||
} |
|
||||||
else { |
|
||||||
struct sockaddr_in *s = (struct sockaddr_in *) &addr; |
|
||||||
client_addr = s->sin_addr.s_addr; |
|
||||||
} |
|
||||||
|
|
||||||
*ipv4 = client_addr; |
|
||||||
return ESP_OK; |
|
||||||
} |
|
@ -1,20 +0,0 @@ |
|||||||
#include <esp_wifi_types.h> |
|
||||||
#include <esp_wifi.h> |
|
||||||
#include <esp_log.h> |
|
||||||
#include <fcntl.h> |
|
||||||
#include <sys/socket.h> |
|
||||||
|
|
||||||
#include "httpd_utils/redirect.h" |
|
||||||
|
|
||||||
static const char *TAG="redirect"; |
|
||||||
|
|
||||||
esp_err_t httpd_redirect_to(httpd_req_t *r, const char *uri) |
|
||||||
{ |
|
||||||
ESP_LOGD(TAG, "Redirect to %s", uri); |
|
||||||
|
|
||||||
httpd_resp_set_hdr(r, "Location", uri); |
|
||||||
httpd_resp_set_status(r, "303 See Other"); |
|
||||||
httpd_resp_set_type(r, HTTPD_TYPE_TEXT); |
|
||||||
const char *msg = "Redirect"; |
|
||||||
return httpd_resp_send(r, msg, -1); |
|
||||||
} |
|
@ -1,181 +0,0 @@ |
|||||||
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
|
||||||
|
|
||||||
#include <rom/queue.h> |
|
||||||
#include <malloc.h> |
|
||||||
#include <assert.h> |
|
||||||
#include <esp_log.h> |
|
||||||
#include <esp_err.h> |
|
||||||
#include <string.h> |
|
||||||
#include "httpd_utils/session_kvmap.h" |
|
||||||
|
|
||||||
static const char *TAG = "sess_kvmap"; |
|
||||||
|
|
||||||
// this struct is opaque, a stub like this is sufficient for the head pointer.
|
|
||||||
struct sess_kv_entry; |
|
||||||
|
|
||||||
/** Session head structure, dynamically allocated */ |
|
||||||
SLIST_HEAD(sess_kv_map, sess_kv_entry); |
|
||||||
|
|
||||||
struct sess_kv_entry { |
|
||||||
SLIST_ENTRY(sess_kv_entry) link; |
|
||||||
char key[SESS_KVMAP_KEY_LEN]; |
|
||||||
void *value; |
|
||||||
sess_kv_free_func_t free_fn; |
|
||||||
}; |
|
||||||
|
|
||||||
struct sess_kv_map *sess_kv_map_alloc(void) |
|
||||||
{ |
|
||||||
ESP_LOGD(TAG, "kv store alloc"); |
|
||||||
struct sess_kv_map *map = malloc(sizeof(struct sess_kv_map)); |
|
||||||
assert(map); |
|
||||||
SLIST_INIT(map); |
|
||||||
return map; |
|
||||||
} |
|
||||||
|
|
||||||
void sess_kv_map_free(void *head_v) |
|
||||||
{ |
|
||||||
struct sess_kv_map* head = head_v; |
|
||||||
|
|
||||||
ESP_LOGD(TAG, "kv store free"); |
|
||||||
assert(head); |
|
||||||
struct sess_kv_entry *item, *tmp; |
|
||||||
SLIST_FOREACH_SAFE(item, head, link, tmp) { |
|
||||||
if (item->free_fn) { |
|
||||||
item->free_fn(item->value); |
|
||||||
free(item); |
|
||||||
} |
|
||||||
} |
|
||||||
free(head); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void * sess_kv_map_get(struct sess_kv_map *head, const char *key) |
|
||||||
{ |
|
||||||
assert(head); |
|
||||||
assert(key); |
|
||||||
ESP_LOGD(TAG, "kv store get %s", key); |
|
||||||
|
|
||||||
struct sess_kv_entry *item; |
|
||||||
SLIST_FOREACH(item, head, link) { |
|
||||||
if (0==strcmp(item->key, key)) { |
|
||||||
ESP_LOGD(TAG, "got ok"); |
|
||||||
return item->value; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
ESP_LOGD(TAG, "not found in store"); |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
|
|
||||||
void * sess_kv_map_take(struct sess_kv_map *head, const char *key) |
|
||||||
{ |
|
||||||
assert(head); |
|
||||||
assert(key); |
|
||||||
ESP_LOGD(TAG, "kv store take %s", key); |
|
||||||
|
|
||||||
struct sess_kv_entry *item; |
|
||||||
SLIST_FOREACH(item, head, link) { |
|
||||||
if (0==strcmp(item->key, key)) { |
|
||||||
item->key[0] = 0; |
|
||||||
item->free_fn = NULL; |
|
||||||
ESP_LOGD(TAG, "taken ok"); |
|
||||||
return item->value; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
ESP_LOGD(TAG, "not found in store"); |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
|
|
||||||
esp_err_t sess_kv_map_remove(struct sess_kv_map *head, const char *key) |
|
||||||
{ |
|
||||||
assert(head); |
|
||||||
assert(key); |
|
||||||
ESP_LOGD(TAG, "kv store remove %s", key); |
|
||||||
|
|
||||||
struct sess_kv_entry *item; |
|
||||||
SLIST_FOREACH(item, head, link) { |
|
||||||
if (0==strcmp(item->key, key)) { |
|
||||||
if (item->free_fn) { |
|
||||||
item->free_fn(item->value); |
|
||||||
} |
|
||||||
item->key[0] = 0; |
|
||||||
item->value = NULL; |
|
||||||
item->free_fn = NULL; |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
ESP_LOGD(TAG, "couldn't remove, not found: %s", key); |
|
||||||
return ESP_ERR_NOT_FOUND; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
esp_err_t sess_kv_map_set(struct sess_kv_map *head, const char *key, void *value, sess_kv_free_func_t free_fn) |
|
||||||
{ |
|
||||||
assert(head); |
|
||||||
assert(key); |
|
||||||
ESP_LOGD(TAG, "kv set value for key %s", key); |
|
||||||
|
|
||||||
size_t key_len = strlen(key); |
|
||||||
if (key_len > SESS_KVMAP_KEY_LEN-1) { |
|
||||||
ESP_LOGE(TAG, "Key too long: %s", key); |
|
||||||
// discard illegal key
|
|
||||||
return ESP_FAIL; |
|
||||||
} |
|
||||||
|
|
||||||
if (key_len == 0) { |
|
||||||
ESP_LOGE(TAG, "Key too short: \"%s\"", key); |
|
||||||
// discard illegal key
|
|
||||||
return ESP_FAIL; |
|
||||||
} |
|
||||||
|
|
||||||
struct sess_kv_entry *item = NULL; |
|
||||||
struct sess_kv_entry *empty_item = NULL; // found item with no content
|
|
||||||
SLIST_FOREACH(item, head, link) { |
|
||||||
ESP_LOGD(TAG, "test item with key %s, ptr %p > %p", item->key, item, item->link.sle_next); |
|
||||||
if (0 == item->key[0]) { |
|
||||||
ESP_LOGD(TAG, "found an empty slot"); |
|
||||||
empty_item = item; |
|
||||||
} |
|
||||||
else if (0==strcmp(item->key, key)) { |
|
||||||
ESP_LOGD(TAG, "old value replaced"); |
|
||||||
if (item->free_fn) { |
|
||||||
item->free_fn(item->value); |
|
||||||
} |
|
||||||
item->value = value; |
|
||||||
item->free_fn = free_fn; |
|
||||||
return ESP_OK; |
|
||||||
} else { |
|
||||||
ESP_LOGD(TAG, "skip this one"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
struct sess_kv_entry *new_item = NULL; |
|
||||||
|
|
||||||
// insert new or reuse an empty item
|
|
||||||
if (empty_item) { |
|
||||||
new_item = empty_item; |
|
||||||
ESP_LOGD(TAG, "empty item reused (%p)", new_item); |
|
||||||
} else { |
|
||||||
ESP_LOGD(TAG, "alloc new item"); |
|
||||||
// key not found, add a new entry.
|
|
||||||
new_item = malloc(sizeof(struct sess_kv_entry)); |
|
||||||
if (!new_item) { |
|
||||||
ESP_LOGE(TAG, "New entry alloc failed"); |
|
||||||
return ESP_ERR_NO_MEM; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
strcpy(new_item->key, key); |
|
||||||
new_item->free_fn = free_fn; |
|
||||||
new_item->value = value; |
|
||||||
|
|
||||||
if (!empty_item) { |
|
||||||
ESP_LOGD(TAG, "insert new item into list"); |
|
||||||
// this item was malloc'd
|
|
||||||
SLIST_INSERT_HEAD(head, new_item, link); |
|
||||||
} |
|
||||||
|
|
||||||
return ESP_OK; |
|
||||||
} |
|
@ -1,220 +0,0 @@ |
|||||||
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
|
||||||
|
|
||||||
#include <malloc.h> |
|
||||||
#include <esp_log.h> |
|
||||||
#include <freertos/FreeRTOS.h> |
|
||||||
#include <freertos/semphr.h> |
|
||||||
#include <esp_http_server.h> |
|
||||||
#include <rom/queue.h> |
|
||||||
|
|
||||||
#include "httpd_utils/session_store.h" |
|
||||||
|
|
||||||
// TODO add a limit on simultaneously open sessions (can cause memory exhaustion DoS)
|
|
||||||
|
|
||||||
#define COOKIE_LEN 32 |
|
||||||
static const char *TAG = "session"; |
|
||||||
|
|
||||||
struct session { |
|
||||||
char cookie[COOKIE_LEN + 1]; |
|
||||||
void *data; |
|
||||||
TickType_t last_activity_time; |
|
||||||
LIST_ENTRY(session) link; |
|
||||||
sess_data_free_fn_t free_fn; |
|
||||||
}; |
|
||||||
|
|
||||||
static LIST_HEAD(sessions_, session) s_store; |
|
||||||
|
|
||||||
static SemaphoreHandle_t sess_store_lock = NULL; |
|
||||||
static bool sess_store_inited = false; |
|
||||||
|
|
||||||
|
|
||||||
void session_store_init(void) |
|
||||||
{ |
|
||||||
if (sess_store_inited) { |
|
||||||
xSemaphoreTake(sess_store_lock, portMAX_DELAY); |
|
||||||
{ |
|
||||||
struct session *it, *tit; |
|
||||||
LIST_FOREACH_SAFE(it, &s_store, link, tit) { |
|
||||||
ESP_LOGW(TAG, "Session cookie expired: \"%s\"", it->cookie); |
|
||||||
if (it->free_fn) it->free_fn(it->data); |
|
||||||
// no relink, we dont care if the list breaks after this - we're removing all of it
|
|
||||||
free(it); |
|
||||||
} |
|
||||||
} |
|
||||||
LIST_INIT(&s_store); |
|
||||||
xSemaphoreGive(sess_store_lock); |
|
||||||
} else { |
|
||||||
LIST_INIT(&s_store); |
|
||||||
sess_store_lock = xSemaphoreCreateMutex(); |
|
||||||
sess_store_inited = true; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Fill buffer with base64 symbols. Does not add a trailing null byte |
|
||||||
* |
|
||||||
* @param buf |
|
||||||
* @param len |
|
||||||
*/ |
|
||||||
static void esp_fill_random_alnum(char *buf, size_t len) |
|
||||||
{ |
|
||||||
#define alphabet_len 64 |
|
||||||
static const char alphabet[alphabet_len] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"; |
|
||||||
|
|
||||||
unsigned int seed = xTaskGetTickCount(); |
|
||||||
|
|
||||||
assert(buf != NULL); |
|
||||||
for (int i = 0; i < len; i++) { |
|
||||||
int index = rand_r(&seed) % alphabet_len; |
|
||||||
*buf++ = (uint8_t) alphabet[index]; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const char *session_new(void *data, sess_data_free_fn_t free_fn) |
|
||||||
{ |
|
||||||
assert(data != NULL); |
|
||||||
|
|
||||||
struct session *item = malloc(sizeof(struct session)); |
|
||||||
if (item == NULL) return NULL; |
|
||||||
|
|
||||||
item->data = data; |
|
||||||
item->free_fn = free_fn; |
|
||||||
esp_fill_random_alnum(item->cookie, COOKIE_LEN); |
|
||||||
item->cookie[COOKIE_LEN] = 0; // add the terminator
|
|
||||||
|
|
||||||
xSemaphoreTake(sess_store_lock, portMAX_DELAY); |
|
||||||
{ |
|
||||||
item->last_activity_time = xTaskGetTickCount(); |
|
||||||
|
|
||||||
LIST_INSERT_HEAD(&s_store, item, link); |
|
||||||
} |
|
||||||
xSemaphoreGive(sess_store_lock); |
|
||||||
|
|
||||||
ESP_LOGD(TAG, "New HTTP session: %s", item->cookie); |
|
||||||
|
|
||||||
return item->cookie; |
|
||||||
} |
|
||||||
|
|
||||||
void *session_find_and(const char *cookie, enum session_find_action action) |
|
||||||
{ |
|
||||||
// no point in searching if the length is wrong
|
|
||||||
if (strlen(cookie) != COOKIE_LEN) { |
|
||||||
ESP_LOGW(TAG, "Wrong session cookie length: \"%s\"", cookie); |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
|
|
||||||
struct session *it = NULL; |
|
||||||
|
|
||||||
bool found = false; |
|
||||||
xSemaphoreTake(sess_store_lock, portMAX_DELAY); |
|
||||||
{ |
|
||||||
LIST_FOREACH(it, &s_store, link) { |
|
||||||
if (0==strcmp(it->cookie, cookie)) { |
|
||||||
ESP_LOGD(TAG, "Session cookie matched: \"%s\"", cookie); |
|
||||||
|
|
||||||
it->last_activity_time = xTaskGetTickCount(); |
|
||||||
found = true; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
if (found && action == SESS_DROP) { |
|
||||||
if (it->free_fn) it->free_fn(it->data); |
|
||||||
LIST_REMOVE(it, link); |
|
||||||
free(it); |
|
||||||
ESP_LOGD(TAG, "Dropped session: \"%s\"", cookie); |
|
||||||
} |
|
||||||
} |
|
||||||
xSemaphoreGive(sess_store_lock); |
|
||||||
if (found) { |
|
||||||
if (action == SESS_DROP) { |
|
||||||
// it was dropped inside the guarded block
|
|
||||||
// the return value is not used with DROP
|
|
||||||
return NULL; |
|
||||||
} |
|
||||||
else if(action == SESS_GET_DATA) { |
|
||||||
return it->data; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
ESP_LOGW(TAG, "Session cookie not found: \"%s\"", cookie); |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
|
|
||||||
void *session_find(const char *cookie) |
|
||||||
{ |
|
||||||
return session_find_and(cookie, SESS_GET_DATA); |
|
||||||
} |
|
||||||
|
|
||||||
void session_drop(const char *cookie) |
|
||||||
{ |
|
||||||
session_find_and(cookie, SESS_DROP); |
|
||||||
} |
|
||||||
|
|
||||||
void session_drop_expired(void) |
|
||||||
{ |
|
||||||
struct session *it; |
|
||||||
struct session *tit; |
|
||||||
|
|
||||||
xSemaphoreTake(sess_store_lock, portMAX_DELAY); |
|
||||||
{ |
|
||||||
TickType_t now = xTaskGetTickCount(); |
|
||||||
|
|
||||||
LIST_FOREACH_SAFE(it, &s_store, link, tit) { |
|
||||||
TickType_t elapsed = now - it->last_activity_time; |
|
||||||
if (elapsed > pdMS_TO_TICKS(SESSION_EXPIRY_TIME_S*1000)) { |
|
||||||
ESP_LOGD(TAG, "Session cookie expired: \"%s\"", it->cookie); |
|
||||||
if (it->free_fn) it->free_fn(it->data); |
|
||||||
LIST_REMOVE(it, link); |
|
||||||
free(it); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
xSemaphoreGive(sess_store_lock); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void *httpd_req_find_session_and(httpd_req_t *r, enum session_find_action action) |
|
||||||
{ |
|
||||||
// this could be called periodically, but it's sufficient to run it at each request
|
|
||||||
// it won't slow anything down unless there are hundreds of sessions
|
|
||||||
session_drop_expired(); |
|
||||||
|
|
||||||
static char buf[256]; |
|
||||||
esp_err_t rv = httpd_req_get_hdr_value_str(r, "Cookie", buf, 256); |
|
||||||
if (rv == ESP_OK || rv == ESP_ERR_HTTPD_RESULT_TRUNC) { |
|
||||||
ESP_LOGD(TAG, "Cookie header: %s", buf); |
|
||||||
|
|
||||||
// probably OK, see if we have a cookie
|
|
||||||
char *start = strstr(buf, SESSION_COOKIE_NAME"="); |
|
||||||
if (start != 0) { |
|
||||||
start += strlen(SESSION_COOKIE_NAME"="); |
|
||||||
char *end = strchr(start, ';'); |
|
||||||
if (end != NULL) *end = 0; |
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Cookie is: %s", start); |
|
||||||
return session_find_and(start, action); |
|
||||||
} |
|
||||||
} else { |
|
||||||
ESP_LOGD(TAG, "No cookie."); |
|
||||||
} |
|
||||||
|
|
||||||
return NULL; |
|
||||||
} |
|
||||||
|
|
||||||
void httpd_resp_delete_session_cookie(httpd_req_t *r) |
|
||||||
{ |
|
||||||
httpd_resp_set_hdr(r, "Set-Cookie", SESSION_COOKIE_NAME"="); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
// Static because the value is passed and stored by reference, so it wouldn't live long enough if it was on stack,
|
|
||||||
// and there also isn't any hook for freeing it if we used malloc(). This is an SDK bug.
|
|
||||||
static char cookie_hdr_buf[COOKIE_LEN + 10]; |
|
||||||
|
|
||||||
// !!! this must not be called concurrently from a different thread.
|
|
||||||
// That is no problem so long as the server stays single-threaded
|
|
||||||
void httpd_resp_set_session_cookie(httpd_req_t *r, const char *cookie) |
|
||||||
{ |
|
||||||
snprintf(cookie_hdr_buf, COOKIE_LEN + 10, "SESSID=%s", cookie); |
|
||||||
httpd_resp_set_hdr(r, "Set-Cookie", cookie_hdr_buf); |
|
||||||
} |
|
@ -1,41 +0,0 @@ |
|||||||
/**
|
|
||||||
* TODO file description |
|
||||||
*
|
|
||||||
* Created on 2019/07/13. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef SESSION_UTILS_C_H |
|
||||||
#define SESSION_UTILS_C_H |
|
||||||
|
|
||||||
#include <esp_err.h> |
|
||||||
#include <esp_http_server.h> |
|
||||||
#include "httpd_utils/session_kvmap.h" |
|
||||||
#include "httpd_utils/session_store.h" |
|
||||||
|
|
||||||
/**
|
|
||||||
* Start or resume a session. |
|
||||||
*/ |
|
||||||
esp_err_t HTTP_GET_SESSION(sess_kv_map_t **ppkvstore, httpd_req_t *r) |
|
||||||
{ |
|
||||||
sess_kv_map_t *kvstore; |
|
||||||
kvstore = httpd_req_find_session_and((r), SESS_GET_DATA); |
|
||||||
if (NULL == kvstore) { |
|
||||||
kvstore = sess_kv_map_alloc(); |
|
||||||
if (!kvstore) return ESP_ERR_NO_MEM; |
|
||||||
|
|
||||||
const char *cookie = session_new(kvstore, sess_kv_map_free); |
|
||||||
if (cookie == NULL) { |
|
||||||
// session alloc failed
|
|
||||||
sess_kv_map_free(kvstore); |
|
||||||
*ppkvstore = NULL; |
|
||||||
return ESP_ERR_NO_MEM; |
|
||||||
} |
|
||||||
httpd_resp_set_session_cookie(r, cookie); |
|
||||||
} |
|
||||||
|
|
||||||
*ppkvstore = kvstore; |
|
||||||
return ESP_OK; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
#endif //SESSION_UTILS_C_H
|
|
@ -1,34 +1,13 @@ |
|||||||
set(COMPONENT_SRCS |
set(COMPONENT_SRCS |
||||||
"app_main.c" |
"app_main.c" |
||||||
|
"nokia.c" |
||||||
"knob.c" |
"knob.c" |
||||||
"liquid/gui.c" |
"gui.c" |
||||||
"analog.c" |
"analog.c" |
||||||
"liquid/liquid.c" |
"liquid/liquid.c" |
||||||
"scenes/scene_root.c" |
"liquid/scene_root.c" |
||||||
"scenes/scene_bootanim.c" |
"liquid/scene_car.c" |
||||||
"scenes/scene_menu.c" |
|
||||||
"scenes/scene_number.c" |
|
||||||
"scenes/scene_manual_menu.c" |
|
||||||
"scenes/scene_popup_menu.c" |
|
||||||
"scenes/scene_popup_test.c" |
|
||||||
"graphics/nokia.c" |
|
||||||
"graphics/utf8.c" |
|
||||||
"graphics/font.c" |
|
||||||
"graphics/drawing.c" |
|
||||||
"graphics/bitmaps.c" |
|
||||||
"web/websrv.c" |
|
||||||
"files/files_enum.c" |
|
||||||
"utils.c" |
|
||||||
"arduinopid.c" |
|
||||||
"firehazard.c" |
|
||||||
) |
) |
||||||
set(COMPONENT_ADD_INCLUDEDIRS "." "liquid" "graphics") |
set(COMPONENT_ADD_INCLUDEDIRS "liquid") |
||||||
|
|
||||||
#begin staticfiles |
|
||||||
# generated by rebuild_file_tables |
|
||||||
set(COMPONENT_EMBED_FILES |
|
||||||
"files/embed/favicon.ico" |
|
||||||
"files/embed/index.html") |
|
||||||
#end staticfiles |
|
||||||
|
|
||||||
register_component() |
register_component() |
||||||
|
@ -1,106 +0,0 @@ |
|||||||
#include "arduinopid.h" |
|
||||||
#include <stdbool.h> |
|
||||||
#include <stdint.h> |
|
||||||
#include <freertos/FreeRTOS.h> |
|
||||||
#include <freertos/task.h> |
|
||||||
|
|
||||||
static void clampOutput(struct PID *self) { |
|
||||||
if (self->Output > self->outMax) self->Output = self->outMax; |
|
||||||
else if (self->Output < self->outMin) self->Output = self->outMin; |
|
||||||
} |
|
||||||
|
|
||||||
static void clampIterm(struct PID *self) { |
|
||||||
if (self->ITerm > self->outMax) self->ITerm = self->outMax; |
|
||||||
else if (self->ITerm < self->outMin) self->ITerm = self->outMin; |
|
||||||
} |
|
||||||
|
|
||||||
void PID_Compute(struct PID *self, float Input) |
|
||||||
{ |
|
||||||
if (!self->ctlMode) return; |
|
||||||
self->Input = Input; |
|
||||||
|
|
||||||
uint32_t now = xTaskGetTickCount(); |
|
||||||
int32_t timeChange = (now - self->lastTime); |
|
||||||
if (timeChange >= self->SampleTime) { |
|
||||||
/*Compute all the working error variables*/ |
|
||||||
float error = self->Setpoint - Input; |
|
||||||
self->ITerm += (self->ki * error); |
|
||||||
|
|
||||||
clampIterm(self); |
|
||||||
|
|
||||||
float dInput = (Input - self->lastInput); |
|
||||||
|
|
||||||
/*Compute PID Output*/ |
|
||||||
self->Output = self->kp * error + self->ITerm - self->kd * dInput; |
|
||||||
|
|
||||||
clampOutput(self); |
|
||||||
|
|
||||||
/*Remember some variables for next time*/ |
|
||||||
self->lastInput = Input; |
|
||||||
self->lastTime = now; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void PID_SetSetpoint(struct PID *self, float Setpoint) |
|
||||||
{ |
|
||||||
self->Setpoint = Setpoint; |
|
||||||
} |
|
||||||
|
|
||||||
void PID_SetTunings(struct PID *self, float Kp, float Ki, float Kd) |
|
||||||
{ |
|
||||||
if (Kp < 0 || Ki < 0 || Kd < 0) return; |
|
||||||
|
|
||||||
float SampleTimeInSec = ((float) self->SampleTime) / 1000; |
|
||||||
self->kp = Kp; |
|
||||||
self->ki = Ki * SampleTimeInSec; |
|
||||||
self->kd = Kd / SampleTimeInSec; |
|
||||||
|
|
||||||
if (self->controllerDirection == PID_REVERSE) { |
|
||||||
self->kp = -self->kp; |
|
||||||
self->ki = -self->ki; |
|
||||||
self->kd = -self->kd; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void PID_SetSampleTime(struct PID *self, uint32_t NewSampleTime) |
|
||||||
{ |
|
||||||
if (NewSampleTime > 0) { |
|
||||||
float ratio = (float) NewSampleTime |
|
||||||
/ (float) self->SampleTime; |
|
||||||
self->ki *= ratio; |
|
||||||
self->kd /= ratio; |
|
||||||
self->SampleTime = (uint32_t) NewSampleTime; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void PID_SetOutputLimits(struct PID *self, float Min, float Max) |
|
||||||
{ |
|
||||||
if (Min > Max) return; |
|
||||||
self->outMin = Min; |
|
||||||
self->outMax = Max; |
|
||||||
|
|
||||||
clampOutput(self); |
|
||||||
clampIterm(self); |
|
||||||
} |
|
||||||
|
|
||||||
void PID_SetCtlMode(struct PID *self, enum PIDCtlMode Mode) |
|
||||||
{ |
|
||||||
bool newAuto = (Mode == PID_AUTOMATIC); |
|
||||||
if (newAuto == !self->ctlMode) { /*we just went from manual to auto*/ |
|
||||||
PID_Initialize(self); |
|
||||||
} |
|
||||||
self->ctlMode = newAuto; |
|
||||||
} |
|
||||||
|
|
||||||
void PID_Initialize(struct PID *self) |
|
||||||
{ |
|
||||||
self->lastInput = self->Input; |
|
||||||
self->ITerm = self->Output; |
|
||||||
|
|
||||||
clampIterm(self); |
|
||||||
} |
|
||||||
|
|
||||||
void PID_SetControllerDirection(struct PID *self, enum PIDDirection Direction) |
|
||||||
{ |
|
||||||
self->controllerDirection = Direction; |
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
/**
|
|
||||||
* adapted from the Arduino PID library |
|
||||||
*
|
|
||||||
* Created on 2020/01/08. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef ARDUINOPID_H |
|
||||||
#define ARDUINOPID_H |
|
||||||
|
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
enum PIDCtlMode { |
|
||||||
PID_MANUAL = 0, |
|
||||||
PID_AUTOMATIC = 1, |
|
||||||
}; |
|
||||||
|
|
||||||
enum PIDDirection { |
|
||||||
PID_DIRECT = 0, |
|
||||||
PID_REVERSE = 1, |
|
||||||
}; |
|
||||||
|
|
||||||
struct PID { |
|
||||||
/*working variables*/ |
|
||||||
uint32_t lastTime; |
|
||||||
float Input, Output, Setpoint; |
|
||||||
float ITerm, lastInput; |
|
||||||
float kp, ki, kd; |
|
||||||
uint32_t SampleTime; //1 sec = 1000
|
|
||||||
float outMin, outMax; |
|
||||||
enum PIDCtlMode ctlMode; // false
|
|
||||||
enum PIDDirection controllerDirection; |
|
||||||
}; |
|
||||||
|
|
||||||
#define PID_DEFAULT() { .SampleTime = 1000, .ctlMode=PID_MANUAL, .controllerDirection=PID_DIRECT } |
|
||||||
|
|
||||||
void PID_Compute(struct PID *self, float Input); |
|
||||||
|
|
||||||
void PID_SetTunings(struct PID *self, float Kp, float Ki, float Kd); |
|
||||||
|
|
||||||
void PID_SetSampleTime(struct PID *self, uint32_t NewSampleTime); |
|
||||||
|
|
||||||
void PID_SetOutputLimits(struct PID *self, float Min, float Max); |
|
||||||
|
|
||||||
void PID_SetCtlMode(struct PID *self, enum PIDCtlMode Mode); |
|
||||||
|
|
||||||
void PID_Initialize(struct PID *self); |
|
||||||
|
|
||||||
void PID_SetSetpoint(struct PID *self, float Setpoint); |
|
||||||
|
|
||||||
void PID_SetControllerDirection(struct PID *self, enum PIDDirection Direction); |
|
||||||
|
|
||||||
#endif //ARDUINOPID_H
|
|
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.2 KiB |
@ -1,166 +0,0 @@ |
|||||||
<!DOCTYPE html> |
|
||||||
<html> |
|
||||||
<head> |
|
||||||
<meta charset="utf-8"> |
|
||||||
<title>Breadflow Web Control</title> |
|
||||||
<style> |
|
||||||
* { |
|
||||||
box-sizing: content-box; |
|
||||||
} |
|
||||||
h1 { |
|
||||||
text-align: center; |
|
||||||
} |
|
||||||
#ctab { |
|
||||||
width: 1400px; |
|
||||||
margin: 0 auto; |
|
||||||
border: 1px solid #ccc; |
|
||||||
border-collapse: collapse; |
|
||||||
} |
|
||||||
#td-side { |
|
||||||
border-left: 1px solid #ccc; |
|
||||||
width: 340px; |
|
||||||
padding: 15px; |
|
||||||
vertical-align: top; |
|
||||||
padding-top: 22px; |
|
||||||
} |
|
||||||
#td-img { |
|
||||||
vertical-align: top; |
|
||||||
} |
|
||||||
</style> |
|
||||||
</head> |
|
||||||
<body> |
|
||||||
<h1>Breadflow {version}</h1> |
|
||||||
|
|
||||||
<table id="ctab"> |
|
||||||
<tr> |
|
||||||
<td id="td-img"> |
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 660 430"> |
|
||||||
<style> |
|
||||||
.ticks, .frame { |
|
||||||
stroke-width: 1px; |
|
||||||
fill: none; |
|
||||||
stroke: black; |
|
||||||
} |
|
||||||
|
|
||||||
.ylabels text { |
|
||||||
font-size: 10px; |
|
||||||
text-anchor: start; |
|
||||||
font-family: Droid Sans, sans-serif; |
|
||||||
vertical-align: middle; |
|
||||||
} |
|
||||||
|
|
||||||
.grid { |
|
||||||
stroke-dasharray: 2; |
|
||||||
stroke: #dbdbdb; |
|
||||||
} |
|
||||||
|
|
||||||
.series { |
|
||||||
stroke-width: 2px; |
|
||||||
fill: none; |
|
||||||
} |
|
||||||
</style> |
|
||||||
<g transform="translate(10,15)"> |
|
||||||
<path d="M100,0 v400m100,-400 v400m100,-400 v400m100,-400 v400m100,-400 v400m100,-400 v400" |
|
||||||
class="grid" transform="translate(0,0)" id="grid-v" /> |
|
||||||
<path d="M0,100 h600m-600,100 h600m-600,100 h600m-600,100" |
|
||||||
class="grid" stroke-dashoffset="0" id="grid-h" /> |
|
||||||
<path d="M600,0 h10m-10,100 h10m-10,100 h10m-10,100 h10m-10,100 h10" class="ticks" /> |
|
||||||
<path d="M600,10 |
|
||||||
h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,20 |
|
||||||
h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,20 |
|
||||||
h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,20 |
|
||||||
h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5m-5,10h5" class="ticks" /> |
|
||||||
<g class="series"> |
|
||||||
<path d="{ser-set}" stroke="blue" id="ser-set" /><!--M0,400L600,0--> |
|
||||||
<path d="{ser-act}" stroke="red" id="ser-act" /><!--M0,0L300,100L600,400--> |
|
||||||
</g> |
|
||||||
<path d="M0,0h600v400h-600Z" class="frame" /> |
|
||||||
<g class="ylabels" transform="translate(630,3)"> |
|
||||||
<text x="-15" y="0">400 °C</text> |
|
||||||
<text x="-15" y="100">300 °C</text> |
|
||||||
<text x="-15" y="200">200 °C</text> |
|
||||||
<text x="-15" y="300">100 °C</text> |
|
||||||
<text x="-15" y="400">0 °C</text> |
|
||||||
</g> |
|
||||||
</g> |
|
||||||
</svg> |
|
||||||
</td> |
|
||||||
<td id="td-side"> |
|
||||||
t<sub>sens</sub> = <span id="temp">{temp}</span>°C<br> |
|
||||||
|
|
||||||
<form action="/set" method="POST"> |
|
||||||
<h3>Oven Control</h3> |
|
||||||
<table> |
|
||||||
<tr> |
|
||||||
<th>Heater:</th> |
|
||||||
<td><select name="fire"> |
|
||||||
<option value="0" {fire_dis_ck}>Disable</option> |
|
||||||
<option value="1" {fire_en_ck}>Enable</option> |
|
||||||
</select></td> |
|
||||||
</tr> |
|
||||||
<tr> |
|
||||||
<th>t<sub>set</sub> =</th> |
|
||||||
<td><input type="number" step="1" name="tset" value="{tset}"></td> |
|
||||||
</tr> |
|
||||||
<tr><td></td><td><input type="submit" value="Set"></td></tr> |
|
||||||
</table> |
|
||||||
</form> |
|
||||||
|
|
||||||
<form action="/set" method="POST"> |
|
||||||
<h3>PID tuning</h3> |
|
||||||
<table> |
|
||||||
<tr><th>Kp = </th><td><input type="number" step="any" name="kp" value="{kp}"></td></tr> |
|
||||||
<tr><th>Ki = </th><td><input type="number" step="any" name="ki" value="{ki}"></td></tr> |
|
||||||
<tr><th>Kd = </th><td><input type="number" step="any" name="kd" value="{kd}"></td></tr> |
|
||||||
<tr><td></td><td><input type="submit" value="Set"></td></tr> |
|
||||||
</table> |
|
||||||
</form> |
|
||||||
</td> |
|
||||||
</tr> |
|
||||||
</table> |
|
||||||
|
|
||||||
<script> |
|
||||||
let Qi = function (x) { return document.getElementById(x) }; |
|
||||||
const REFR_TIME = 1000; |
|
||||||
function update(data) { |
|
||||||
if (data) { |
|
||||||
let rows = data.split('\x1e'); |
|
||||||
rows.forEach(function (v) { |
|
||||||
let [k, va] = v.split('\x1f'); |
|
||||||
switch (k) { |
|
||||||
case 'ser-set': |
|
||||||
Qi('ser-set').setAttribute('d', va); |
|
||||||
break; |
|
||||||
case 'ser-act': |
|
||||||
Qi('ser-act').setAttribute('d', va); |
|
||||||
break; |
|
||||||
case 'timeshift': |
|
||||||
Qi('grid-v').setAttribute('transform', 'translate(-'+(va*5)+',0)'); |
|
||||||
Qi('grid-h').setAttribute('stroke-dashoffset', -va * 5); |
|
||||||
break; |
|
||||||
case 'temp': |
|
||||||
Qi('temp').innerHTML = va; |
|
||||||
break; |
|
||||||
// form fields are not live updated. |
|
||||||
} |
|
||||||
}); |
|
||||||
} else { |
|
||||||
let xhr=new XMLHttpRequest(); |
|
||||||
xhr.onreadystatechange = function () { |
|
||||||
if (xhr.readyState===4){ |
|
||||||
if (xhr.status===200) { |
|
||||||
update(xhr.responseText); |
|
||||||
} |
|
||||||
setTimeout(update, REFR_TIME); |
|
||||||
} |
|
||||||
}; |
|
||||||
xhr.onerror = function () { |
|
||||||
setTimeout(update, REFR_TIME); |
|
||||||
}; |
|
||||||
xhr.open('GET', '/data'); |
|
||||||
xhr.send(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
setTimeout(update, REFR_TIME); |
|
||||||
</script> |
|
@ -1,15 +0,0 @@ |
|||||||
// Generated by 'rebuild_file_tables'
|
|
||||||
#include <stdint.h> |
|
||||||
#include "files_enum.h" |
|
||||||
|
|
||||||
extern const uint8_t _binary_favicon_ico_start[]; |
|
||||||
extern const uint8_t _binary_favicon_ico_end[]; |
|
||||||
extern const uint8_t _binary_index_html_start[]; |
|
||||||
extern const uint8_t _binary_index_html_end[]; |
|
||||||
|
|
||||||
const struct embedded_file_info EMBEDDED_FILE_LOOKUP[] = { |
|
||||||
[FILE_FAVICON_ICO] = {_binary_favicon_ico_start, _binary_favicon_ico_end, "favicon.ico", "image/vnd.microsoft.icon"}, |
|
||||||
[FILE_INDEX_HTML] = {_binary_index_html_start, _binary_index_html_end, "index.html", "text/html"}, |
|
||||||
}; |
|
||||||
|
|
||||||
const size_t EMBEDDED_FILE_LOOKUP_LEN = 2; |
|
@ -1,13 +0,0 @@ |
|||||||
// Generated by 'rebuild_file_tables'
|
|
||||||
|
|
||||||
#ifndef _EMBEDDED_FILES_ENUM_H |
|
||||||
#define _EMBEDDED_FILES_ENUM_H |
|
||||||
|
|
||||||
#include "fileserver/embedded_files.h" |
|
||||||
|
|
||||||
enum embedded_file_id { |
|
||||||
FILE_FAVICON_ICO = 0, |
|
||||||
FILE_INDEX_HTML = 1 |
|
||||||
}; |
|
||||||
|
|
||||||
#endif // _EMBEDDED_FILES_ENUM_H
|
|
@ -1,163 +0,0 @@ |
|||||||
#!/usr/bin/env php |
|
||||||
<?php |
|
||||||
// This script rebuilds the static files enum, extern symbols pointing to the embedded byte buffers, |
|
||||||
// and the look-up structs table. To add more files, simply add them in the 'files' directory. |
|
||||||
|
|
||||||
// Note that all files will be accessible by the webserver, unless you filter them in embedded_files.c. |
|
||||||
|
|
||||||
|
|
||||||
// List all files |
|
||||||
$files = scandir(__DIR__.'/embed'); |
|
||||||
|
|
||||||
$files = array_filter(array_map(function ($f) { |
|
||||||
if (!is_file(__DIR__.'/embed/'.$f)) return null; |
|
||||||
if (preg_match('/^\.|\.kate-swp|\.bak$|~$|\.sh|\.ignore\..*$/', $f)) return null; |
|
||||||
|
|
||||||
echo "Found: $f\n"; |
|
||||||
return $f; |
|
||||||
}, $files)); |
|
||||||
|
|
||||||
sort($files); |
|
||||||
|
|
||||||
$formatted = array_filter(array_map(function ($f) { |
|
||||||
return "\"files/embed/$f\""; |
|
||||||
}, $files)); |
|
||||||
|
|
||||||
$cmake = file_get_contents(__DIR__.'/../CMakeLists.txt'); |
|
||||||
|
|
||||||
$cmake = preg_replace('/#begin staticfiles\n.*#end staticfiles/s', |
|
||||||
"#begin staticfiles\n". |
|
||||||
"# generated by rebuild_file_tables\n". |
|
||||||
"set(COMPONENT_EMBED_FILES\n ". |
|
||||||
implode("\n ", $formatted) . ")\n". |
|
||||||
"#end staticfiles", |
|
||||||
$cmake); |
|
||||||
|
|
||||||
file_put_contents(__DIR__.'/../CMakeLists.txt', $cmake); |
|
||||||
|
|
||||||
|
|
||||||
// Generate a list of files |
|
||||||
|
|
||||||
$num = 0; |
|
||||||
$enum_keys = array_map(function ($f) use(&$num) { |
|
||||||
$a = preg_replace("/[^A-Z0-9_]+/", "_", strtoupper($f)); |
|
||||||
return 'FILE_'. $a.' = '.($num++); |
|
||||||
}, $files); |
|
||||||
|
|
||||||
$keylist = implode(",\n ", $enum_keys); |
|
||||||
|
|
||||||
$struct_array = []; |
|
||||||
|
|
||||||
$externs = array_map(function ($f) use (&$struct_array) { |
|
||||||
$a = preg_replace("/[^A-Z0-9_]+/", "_", strtoupper($f)); |
|
||||||
|
|
||||||
$start = '_binary_'. strtolower($a).'_start'; |
|
||||||
$end = '_binary_'. strtolower($a).'_end'; |
|
||||||
|
|
||||||
static $mimes = array( |
|
||||||
'txt' => 'text/plain', |
|
||||||
'htm' => 'text/html', |
|
||||||
'html' => 'text/html', |
|
||||||
'php' => 'text/html', |
|
||||||
'css' => 'text/css', |
|
||||||
'js' => 'application/javascript', |
|
||||||
'json' => 'application/json', |
|
||||||
'xml' => 'application/xml', |
|
||||||
'swf' => 'application/x-shockwave-flash', |
|
||||||
'flv' => 'video/x-flv', |
|
||||||
|
|
||||||
'pem' => 'application/x-pem-file', |
|
||||||
|
|
||||||
// images |
|
||||||
'png' => 'image/png', |
|
||||||
'jpe' => 'image/jpeg', |
|
||||||
'jpeg' => 'image/jpeg', |
|
||||||
'jpg' => 'image/jpeg', |
|
||||||
'gif' => 'image/gif', |
|
||||||
'bmp' => 'image/bmp', |
|
||||||
'ico' => 'image/vnd.microsoft.icon', |
|
||||||
'tiff' => 'image/tiff', |
|
||||||
'tif' => 'image/tiff', |
|
||||||
'svg' => 'image/svg+xml', |
|
||||||
'svgz' => 'image/svg+xml', |
|
||||||
|
|
||||||
// archives |
|
||||||
'zip' => 'application/zip', |
|
||||||
'rar' => 'application/x-rar-compressed', |
|
||||||
'exe' => 'application/x-msdownload', |
|
||||||
'msi' => 'application/x-msdownload', |
|
||||||
'cab' => 'application/vnd.ms-cab-compressed', |
|
||||||
|
|
||||||
// audio/video |
|
||||||
'mp3' => 'audio/mpeg', |
|
||||||
'qt' => 'video/quicktime', |
|
||||||
'mov' => 'video/quicktime', |
|
||||||
|
|
||||||
// adobe |
|
||||||
'pdf' => 'application/pdf', |
|
||||||
'psd' => 'image/vnd.adobe.photoshop', |
|
||||||
'ai' => 'application/postscript', |
|
||||||
'eps' => 'application/postscript', |
|
||||||
'ps' => 'application/postscript', |
|
||||||
|
|
||||||
// ms office |
|
||||||
'doc' => 'application/msword', |
|
||||||
'rtf' => 'application/rtf', |
|
||||||
'xls' => 'application/vnd.ms-excel', |
|
||||||
'ppt' => 'application/vnd.ms-powerpoint', |
|
||||||
|
|
||||||
// open office |
|
||||||
'odt' => 'application/vnd.oasis.opendocument.text', |
|
||||||
'ods' => 'application/vnd.oasis.opendocument.spreadsheet', |
|
||||||
); |
|
||||||
|
|
||||||
$parts = explode('.', $f); |
|
||||||
$suffix = end($parts); |
|
||||||
$mime = $mimes[$suffix] ?? 'application/octet-stream'; |
|
||||||
|
|
||||||
$len = filesize('embed/'.$f); |
|
||||||
|
|
||||||
$struct_array[] = "[FILE_$a] = {{$start}, {$end}, \"{$f}\", \"{$mime}\"},"; |
|
||||||
|
|
||||||
return |
|
||||||
'extern const uint8_t '.$start.'[];'."\n". |
|
||||||
'extern const uint8_t '.$end.'[];'; |
|
||||||
}, $files); |
|
||||||
|
|
||||||
$externlist = implode("\n", $externs); |
|
||||||
$structlist = implode("\n ", $struct_array); |
|
||||||
|
|
||||||
|
|
||||||
file_put_contents('files_enum.h', <<<FILE |
|
||||||
// Generated by 'rebuild_file_tables' |
|
||||||
|
|
||||||
#ifndef _EMBEDDED_FILES_ENUM_H |
|
||||||
#define _EMBEDDED_FILES_ENUM_H |
|
||||||
|
|
||||||
#include "fileserver/embedded_files.h" |
|
||||||
|
|
||||||
enum embedded_file_id { |
|
||||||
$keylist |
|
||||||
}; |
|
||||||
|
|
||||||
#endif // _EMBEDDED_FILES_ENUM_H |
|
||||||
|
|
||||||
FILE |
|
||||||
); |
|
||||||
|
|
||||||
$files_count = count($struct_array); |
|
||||||
file_put_contents("files_enum.c", <<<FILE |
|
||||||
// Generated by 'rebuild_file_tables' |
|
||||||
#include <stdint.h> |
|
||||||
#include "files_enum.h" |
|
||||||
|
|
||||||
$externlist |
|
||||||
|
|
||||||
const struct embedded_file_info EMBEDDED_FILE_LOOKUP[] = { |
|
||||||
$structlist |
|
||||||
}; |
|
||||||
|
|
||||||
const size_t EMBEDDED_FILE_LOOKUP_LEN = $files_count; |
|
||||||
|
|
||||||
FILE |
|
||||||
); |
|
@ -1,133 +0,0 @@ |
|||||||
#include "firehazard.h" |
|
||||||
#include "arduinopid.h" |
|
||||||
#include <stdio.h> |
|
||||||
#include <stdbool.h> |
|
||||||
|
|
||||||
#include <math.h> |
|
||||||
#include <esp_log.h> |
|
||||||
#include <nvs.h> |
|
||||||
#include "driver/ledc.h" |
|
||||||
#include "esp_err.h" |
|
||||||
#include "utils.h" |
|
||||||
|
|
||||||
static const char *TAG = "fire"; |
|
||||||
|
|
||||||
static struct PID pid = PID_DEFAULT(); |
|
||||||
|
|
||||||
static void pwm_init(void); |
|
||||||
static void pwm_set(float duty); |
|
||||||
|
|
||||||
void fire_get_tuning(float *kp, float *ki, float *kd) { |
|
||||||
*kp = pid.kp; |
|
||||||
*ki = pid.ki; |
|
||||||
*kd = pid.kd; |
|
||||||
} |
|
||||||
|
|
||||||
void fire_init() { |
|
||||||
printf("Regulator init"); |
|
||||||
|
|
||||||
PID_Initialize(&pid); |
|
||||||
PID_SetOutputLimits(&pid, 0, 1); |
|
||||||
PID_SetCtlMode(&pid, PID_MANUAL); |
|
||||||
|
|
||||||
nvs_handle nvs; |
|
||||||
ESP_ERROR_CHECK(nvs_open("config", NVS_READWRITE, &nvs)); // must use RW to allow opening nonexistent
|
|
||||||
union uf32 kp, ki, kd; |
|
||||||
kp.f = 0.4f; |
|
||||||
ki.f = 0.5f; |
|
||||||
kd.f = 0.0f; |
|
||||||
nvs_get_u32(nvs, "kp", &kp.u); |
|
||||||
nvs_get_u32(nvs, "ki", &ki.u); |
|
||||||
nvs_get_u32(nvs, "kd", &kd.u); |
|
||||||
PID_SetTunings(&pid, kp.f, ki.f, kd.f); |
|
||||||
nvs_close(nvs); |
|
||||||
|
|
||||||
pwm_init(); |
|
||||||
|
|
||||||
fire_setlevel(20); |
|
||||||
// fire_enable(true);
|
|
||||||
} |
|
||||||
|
|
||||||
void fire_set_tuning(float kp, float ki, float kd) { |
|
||||||
ESP_LOGI(TAG, "PID set tuning Kp=%.3f, Ki=%.3f, Kd=%.3f", kp, ki, kd); |
|
||||||
PID_SetTunings(&pid, kp, ki, kd); |
|
||||||
} |
|
||||||
|
|
||||||
float fire_get_setpoint(bool off_is_zero) { |
|
||||||
if (off_is_zero) { |
|
||||||
return pid.ctlMode == PID_MANUAL ? 0 : pid.Setpoint; |
|
||||||
} else { |
|
||||||
return pid.Setpoint; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void fire_setlevel(float cels) { |
|
||||||
ESP_LOGI(TAG, "PID set target %.3f°C", cels); |
|
||||||
|
|
||||||
if (cels < 0) cels = 0; |
|
||||||
if (cels > MAX_SETPOINT) cels = MAX_SETPOINT; |
|
||||||
PID_SetSetpoint(&pid, cels); |
|
||||||
} |
|
||||||
|
|
||||||
void fire_enable(bool enable) { |
|
||||||
ESP_LOGI(TAG, "Heater %s", enable ? "enable" : "disable"); |
|
||||||
|
|
||||||
PID_SetCtlMode(&pid, enable ? PID_AUTOMATIC : PID_MANUAL); |
|
||||||
} |
|
||||||
|
|
||||||
bool fire_enabled() { |
|
||||||
return pid.ctlMode == PID_AUTOMATIC; |
|
||||||
} |
|
||||||
|
|
||||||
void fire_regulate(float cels) { |
|
||||||
PID_Compute(&pid, cels); |
|
||||||
|
|
||||||
if (cels > MAX_TSENSE || cels < MIN_TSENSE) { |
|
||||||
ESP_LOGE(TAG, "Tsense out of bounds! Stopping."); |
|
||||||
fire_enable(false); |
|
||||||
} |
|
||||||
|
|
||||||
if (pid.ctlMode == PID_MANUAL) { |
|
||||||
pwm_set(0); |
|
||||||
} else { |
|
||||||
printf("PID in %.2f°C, out %.3f, I %.3f\n", cels, pid.Output, pid.ITerm); |
|
||||||
pwm_set(pid.Output); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
#define PWM_CHANNEL LEDC_CHANNEL_1 |
|
||||||
#define PWM_TIMER LEDC_TIMER_1 |
|
||||||
#define PWM_BIT_NUM LEDC_TIMER_12_BIT |
|
||||||
|
|
||||||
#define PWM_PIN GPIO_NUM_14 |
|
||||||
|
|
||||||
static void pwm_init(void) |
|
||||||
{ |
|
||||||
ledc_channel_config_t ledc_channel_left = {0}; |
|
||||||
ledc_channel_left.gpio_num = PWM_PIN; |
|
||||||
ledc_channel_left.speed_mode = LEDC_HIGH_SPEED_MODE; |
|
||||||
ledc_channel_left.channel = PWM_CHANNEL; |
|
||||||
ledc_channel_left.intr_type = LEDC_INTR_DISABLE; |
|
||||||
ledc_channel_left.timer_sel = PWM_TIMER; |
|
||||||
ledc_channel_left.duty = 0; |
|
||||||
|
|
||||||
ledc_timer_config_t ledc_timer = {0}; |
|
||||||
ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE; |
|
||||||
ledc_timer.duty_resolution = PWM_BIT_NUM; |
|
||||||
ledc_timer.timer_num = PWM_TIMER; |
|
||||||
ledc_timer.freq_hz = 1; // TODO ??
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK( ledc_channel_config(&ledc_channel_left) ); |
|
||||||
ESP_ERROR_CHECK( ledc_timer_config(&ledc_timer) ); |
|
||||||
} |
|
||||||
|
|
||||||
static void pwm_set(float duty) |
|
||||||
{ |
|
||||||
uint32_t max_duty = (1 << PWM_BIT_NUM);// - 1
|
|
||||||
uint32_t dutycycle = lroundf((duty) * (float)max_duty); |
|
||||||
printf("Dutycycle %d\n", dutycycle); |
|
||||||
|
|
||||||
ESP_ERROR_CHECK( ledc_set_duty(LEDC_HIGH_SPEED_MODE, PWM_CHANNEL, dutycycle) ); |
|
||||||
ESP_ERROR_CHECK( ledc_update_duty(LEDC_HIGH_SPEED_MODE, PWM_CHANNEL) ); |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
/**
|
|
||||||
* TODO file description |
|
||||||
*
|
|
||||||
* Created on 2020/01/08. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef REFLOWER_FIREHAZARD_H |
|
||||||
#define REFLOWER_FIREHAZARD_H |
|
||||||
#include <stdbool.h> |
|
||||||
|
|
||||||
#define MAX_SETPOINT 350 |
|
||||||
#define MAX_TSENSE 400 |
|
||||||
#define MIN_TSENSE 0 |
|
||||||
|
|
||||||
void fire_regulate(float cels); |
|
||||||
void fire_init(); |
|
||||||
void fire_setlevel(float cels); |
|
||||||
void fire_enable(bool enable); |
|
||||||
|
|
||||||
float fire_get_setpoint(bool off_is_zero); |
|
||||||
void fire_get_tuning(float *kp, float *ki, float *kd); |
|
||||||
bool fire_enabled(); |
|
||||||
void fire_set_tuning(float kp, float ki, float kd); |
|
||||||
|
|
||||||
#endif //REFLOWER_FIREHAZARD_H
|
|
@ -1,124 +0,0 @@ |
|||||||
#include "bitmaps.h" |
|
||||||
#include <stddef.h> |
|
||||||
#include <string.h> |
|
||||||
|
|
||||||
|
|
||||||
/* 21x22: no_flame */ |
|
||||||
// ········██····█······
|
|
||||||
// ·········██···██·····
|
|
||||||
// ··········██·········
|
|
||||||
// ··········███········
|
|
||||||
// ·····█····███···█····
|
|
||||||
// ···██····████··███···
|
|
||||||
// ··██···█████··███····
|
|
||||||
// ··█···█████··███·····
|
|
||||||
// ·██··█████··███······
|
|
||||||
// ·█··█████··███·······
|
|
||||||
// ·█··████··███··█·····
|
|
||||||
// ····███··███··███····
|
|
||||||
// ·····█··███···███··█·
|
|
||||||
// ·······███··█████··██
|
|
||||||
// ·█····███··███████··█
|
|
||||||
// █····███···███████···
|
|
||||||
// ██··███··█··██████···
|
|
||||||
// █··███··██···█████···
|
|
||||||
// ··███··███···█████···
|
|
||||||
// ·███··██·····█·███···
|
|
||||||
// ███··█·········██····
|
|
||||||
// ·█··███·······██·····
|
|
||||||
static const uint8_t G_NO_FLAME_BYTES[] = { 0x00, 0x00, 0xc0, 0x60, 0x20, 0x10, 0x80, 0xc0, 0xc1, 0xe3, 0xfe, 0x7c, 0x38, 0x80, 0xc3, 0xe2, 0x70, 0x20, 0x00, 0x00, 0x00, 0x80, 0x47, 0x01, 0x00, 0x0e, 0x9f, 0xcf, 0xe7, 0x73, 0x39, 0x1c, 0xce, 0xe7, 0xe3, 0xf9, 0xfc, 0xf8, 0xc0, 0x00, 0x30, 0x60, 0x13, 0x39, 0x1c, 0x0e, 0x27, 0x33, 0x29, 0x0c, 0x06, 0x07, 0x00, 0x00, 0x01, 0x0f, 0x27, 0x3f, 0x1f, 0x0f, 0x00, 0x00, 0x00 }; |
|
||||||
|
|
||||||
/* 21x22: flame */ |
|
||||||
// ········██····█······
|
|
||||||
// ·········██···██·····
|
|
||||||
// ··········██·········
|
|
||||||
// ··········███········
|
|
||||||
// ·····█····███········
|
|
||||||
// ···██····█████···█···
|
|
||||||
// ··██···███████··█····
|
|
||||||
// ··█···███████··██····
|
|
||||||
// ·██··████████·██·····
|
|
||||||
// ·█··████████··██·····
|
|
||||||
// ·█··████████·███·····
|
|
||||||
// ····████████·████····
|
|
||||||
// ·····████·███·███··█·
|
|
||||||
// ······███·███████··██
|
|
||||||
// ·█·····██··███████··█
|
|
||||||
// █·······██·███████···
|
|
||||||
// ██·····███··██████···
|
|
||||||
// █████·████···█████···
|
|
||||||
// ·█████████···█████···
|
|
||||||
// ··██████·····█·███···
|
|
||||||
// ···███·········██····
|
|
||||||
// ····███·······██·····
|
|
||||||
static const uint8_t G_FLAME_BYTES[] = { 0x00, 0x00, 0xc0, 0x60, 0x20, 0x10, 0x80, 0xc0, 0xc1, 0xe3, 0xfe, 0xfc, 0xf8, 0x60, 0x03, 0x82, 0xc0, 0x20, 0x00, 0x00, 0x00, 0x80, 0x47, 0x01, 0x00, 0x0e, 0x1f, 0x3f, 0x7f, 0xff, 0x8f, 0x3f, 0xff, 0xf1, 0xec, 0xff, 0xff, 0xf8, 0xc0, 0x00, 0x30, 0x60, 0x03, 0x07, 0x0e, 0x1e, 0x3e, 0x3c, 0x2e, 0x0f, 0x07, 0x07, 0x00, 0x00, 0x01, 0x0f, 0x27, 0x3f, 0x1f, 0x0f, 0x00, 0x00, 0x00 }; |
|
||||||
|
|
||||||
/* 84x48: boot_logo */ |
|
||||||
// ····················································································
|
|
||||||
// ·····································································████████·······
|
|
||||||
// ························································█████████████····█···██·····
|
|
||||||
// ···················█·······························█████····█·███·····███······█····
|
|
||||||
// ······█·█····································██████···██·█████··███████·········█···
|
|
||||||
// ·······█······························███████····██·█████·█·······███··········█····
|
|
||||||
// ······█·█······················███████·····███·███████··██···················██·····
|
|
||||||
// ···························██████·██·███·███·███████████·████·············███·······
|
|
||||||
// ······················████████████·██··███··█████████·█████·············██·██·······
|
|
||||||
// ··················█████·██·████···█·████···███████·███·············█████···██·······
|
|
||||||
// ·············███████████·█████████████···██·······█············█████··█·█·█·██······
|
|
||||||
// ············██████████·██████████··███████·················█████··█·██·█···███······
|
|
||||||
// ·········██████··█··████·██·███···████·█···············████···█··███··█······██·····
|
|
||||||
// ······███·████████████████████··██················█████··█···█·█·█···········█·█····
|
|
||||||
// ···███████····················██··············████·█····█·····█················█····
|
|
||||||
// ····██··························█·············█···█····██······················█····
|
|
||||||
// ··██·····························█·······█·█·█···█····██·······················█····
|
|
||||||
// ·█······························█·······█·█·█···█·█····························█····
|
|
||||||
// ·█····························██·······█·█·█·····█····················█········█····
|
|
||||||
// ··██······················████·█·█·█··█·······························█······█··█···
|
|
||||||
// ····████···················█··█·█·█··█································█······█··█···
|
|
||||||
// ·······█···················██·█·██··························█·········█······█··█···
|
|
||||||
// ·······█····················█·██······█·····················█··········█·····█··█···
|
|
||||||
// ·······█····················█·█········█··········█··········█·········█·····███····
|
|
||||||
// ········█···················█··········█···········█·········█·····██████████·······
|
|
||||||
// ········█····················█·········█············█·········█·███·················
|
|
||||||
// ········█····················█··········█···········█·······████····················
|
|
||||||
// ········█·····················█·········█············█·█████························
|
|
||||||
// ········█······················█·········█·········████·····························
|
|
||||||
// ········█·······················█········█····█████···██················█·█·········
|
|
||||||
// ·········█······················█·········█·██···························█··········
|
|
||||||
// ·········█······················█·······████····························█·█·········
|
|
||||||
// ·········█······················█····███·····················█······················
|
|
||||||
// ·········█·······················████·······················███·····················
|
|
||||||
// ·········█·····················███···························█······················
|
|
||||||
// ·········█········██████████████····················································
|
|
||||||
// ··········█·██████·····························································█····
|
|
||||||
// ···········█····························································█······█····
|
|
||||||
// ··························································█············█·······█····
|
|
||||||
// ··████·································█··················█······██····█··█····█····
|
|
||||||
// ··█···█···██████···█████████····█·····███████···███████···█·····█··█····█·██····█···
|
|
||||||
// ··█···█··██·····█·█·█··········█·█······█····█·█·█·······█·····█····█···█··█····█···
|
|
||||||
// ··████····█·····██··█··········█·█······█····█···██████··█·····█····█···█··█····█···
|
|
||||||
// ··█···█···██████··███████·····█···█·····█···█··███·······█·····█···█·····█·██···█···
|
|
||||||
// ··█····█··██········█········███████····█··█·····█·······█······█·█······█·█·█·█····
|
|
||||||
// ··█···█···█·██······█·····██··█····█··█████······█········█████··█········█···█·····
|
|
||||||
// ██████····█···███····█████···█·····██············█··································
|
|
||||||
// ··········█·····█···································································
|
|
||||||
static const uint8_t G_BOOT_LOGO_BYTES[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x20, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xc0, 0xc0, 0x40, 0xc0, 0xc0, 0x40, 0xc0, 0xa0, 0xa0, 0x20, 0xa0, 0xa0, 0xe0, 0x60, 0xd0, 0x90, 0xd0, 0xd0, 0xf0, 0xf0, 0xc8, 0xe8, 0xe8, 0xb8, 0xb8, 0x64, 0xd4, 0xb4, 0x94, 0x9c, 0x14, 0x0c, 0x0c, 0x1c, 0x14, 0x34, 0x34, 0x34, 0x12, 0x1a, 0x0a, 0x0a, 0x06, 0x82, 0x82, 0x82, 0x44, 0x44, 0x28, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xc0, 0xc0, 0x60, 0x60, 0x60, 0x50, 0x30, 0x30, 0x38, 0x3c, 0x3c, 0x2c, 0x2c, 0x3c, 0x2e, 0x2e, 0x3e, 0x3e, 0x37, 0x3d, 0x2b, 0x3f, 0x3d, 0x2f, 0x3f, 0x3f, 0x5f, 0x4d, 0xad, 0x25, 0x16, 0x1d, 0x1f, 0x1e, 0x0a, 0x1b, 0x09, 0x0d, 0x04, 0x02, 0x03, 0x03, 0xc3, 0x43, 0x43, 0x43, 0xa5, 0x63, 0x23, 0x22, 0x21, 0x91, 0xd1, 0x31, 0x11, 0x08, 0x08, 0x28, 0x58, 0x2c, 0x04, 0x34, 0x1c, 0x16, 0x0a, 0x0a, 0x16, 0x0a, 0x05, 0x01, 0x04, 0x0b, 0x0f, 0x3c, 0x10, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x09, 0x09, 0x10, 0x10, 0x10, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x38, 0xe8, 0x08, 0xf4, 0x4c, 0x32, 0x29, 0x10, 0x08, 0x00, 0x10, 0x48, 0x84, 0x02, 0x05, 0x02, 0x05, 0x02, 0x01, 0x00, 0x00, 0x02, 0x05, 0x82, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x60, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x80, 0x87, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x08, 0x10, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x8c, 0xb0, 0xc0, 0x80, 0x40, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x11, 0x16, 0x18, 0x30, 0x28, 0x08, 0x08, 0x08, 0x08, 0x04, 0x05, 0x06, 0x04, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0xa1, 0x41, 0xa1, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x0f, 0x10, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x05, 0x06, 0x02, 0x02, 0x02, 0x01, 0x01, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x02, 0x07, 0x02, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x20, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x7f, 0x44, 0x44, 0x44, 0x2b, 0x10, 0x00, 0x02, 0xff, 0x19, 0x29, 0x29, 0x49, 0x49, 0xc6, 0x04, 0x0a, 0x09, 0x3f, 0x49, 0x49, 0x49, 0x49, 0x41, 0x21, 0x21, 0x00, 0x50, 0x38, 0x16, 0x11, 0x16, 0x18, 0x70, 0x40, 0x00, 0x21, 0x21, 0x3f, 0x21, 0x21, 0x11, 0x09, 0x06, 0x00, 0x0a, 0x09, 0x7f, 0x05, 0x05, 0x05, 0x05, 0x05, 0x00, 0x00, 0x1e, 0x21, 0x20, 0x20, 0x20, 0x20, 0x0e, 0x11, 0x20, 0x10, 0x09, 0x06, 0x00, 0x00, 0x00, 0x07, 0x18, 0x21, 0x1f, 0x08, 0x10, 0x20, 0x10, 0x0f, 0x00, 0x00, 0x00 }; |
|
||||||
|
|
||||||
static const struct BitmapImage bitmaps[] = {
|
|
||||||
{ .name="no_flame", .width=21, .height=22, .bytes=G_NO_FLAME_BYTES }, |
|
||||||
{ .name="flame", .width=21, .height=22, .bytes=G_FLAME_BYTES }, |
|
||||||
{ .name="boot_logo", .width=84, .height=48, .bytes=G_BOOT_LOGO_BYTES }, |
|
||||||
{} |
|
||||||
}; |
|
||||||
|
|
||||||
const struct BitmapImage *Bitmap_Get(const char *name) { |
|
||||||
const struct BitmapImage *ptr = &bitmaps[0]; |
|
||||||
while (ptr->name) { |
|
||||||
if (0 == strcmp(ptr->name, name)) { |
|
||||||
return ptr; |
|
||||||
} |
|
||||||
ptr++; |
|
||||||
} |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
|
|
@ -1,21 +0,0 @@ |
|||||||
/**
|
|
||||||
* TODO file description |
|
||||||
*
|
|
||||||
* Created on 2020/01/05. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef BITMAPS_H |
|
||||||
#define BITMAPS_H |
|
||||||
|
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
struct BitmapImage { |
|
||||||
const char *name; |
|
||||||
uint8_t width; |
|
||||||
uint8_t height; |
|
||||||
const uint8_t* bytes; |
|
||||||
}; |
|
||||||
|
|
||||||
const struct BitmapImage *Bitmap_Get(const char *name); |
|
||||||
|
|
||||||
#endif //BITMAPS_H
|
|
@ -1,13 +0,0 @@ |
|||||||
/**
|
|
||||||
* TODO file description |
|
||||||
*
|
|
||||||
* Created on 2020/01/05. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef REFLOWER_DISPLAY_SPEC_H |
|
||||||
#define REFLOWER_DISPLAY_SPEC_H |
|
||||||
|
|
||||||
#define LCD_WIDTH 84 // Note: x-coordinates go wide
|
|
||||||
#define LCD_HEIGHT 48 // Note: y-coordinates go high
|
|
||||||
|
|
||||||
#endif //REFLOWER_DISPLAY_SPEC_H
|
|
@ -1,369 +0,0 @@ |
|||||||
#include "drawing.h" |
|
||||||
#include "display_spec.h" |
|
||||||
#include "font.h" |
|
||||||
#include "bitmaps.h" |
|
||||||
|
|
||||||
extern uint8_t LCD_displayMap[LCD_WIDTH * LCD_HEIGHT / 8]; |
|
||||||
|
|
||||||
// This function sets a pixel on displayMap to your preferred
|
|
||||||
// color. 1=Black, 0= white.
|
|
||||||
void LCD_setPixel(int x, int y, enum Color bw) |
|
||||||
{ |
|
||||||
// First, double check that the coordinate is in range.
|
|
||||||
if ((x >= 0) && (x < LCD_WIDTH) && (y >= 0) && (y < LCD_HEIGHT)) { |
|
||||||
uint8_t shift = y % 8; |
|
||||||
|
|
||||||
if (bw) // If black, set the bit.
|
|
||||||
LCD_displayMap[x + (y / 8) * LCD_WIDTH] |= 1 << shift; |
|
||||||
else // If white clear the bit.
|
|
||||||
LCD_displayMap[x + (y / 8) * LCD_WIDTH] &= ~(1 << shift); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// setLine draws a line from x0,y0 to x1,y1 with the set color.
|
|
||||||
// This function was grabbed from the SparkFun ColorLCDShield
|
|
||||||
// library.
|
|
||||||
void LCD_setLine(int x0, int y0, int x1, int y1, enum Color bw) |
|
||||||
{ |
|
||||||
int dy = y1 - y0; // Difference between y0 and y1
|
|
||||||
int dx = x1 - x0; // Difference between x0 and x1
|
|
||||||
int stepx, stepy; |
|
||||||
|
|
||||||
if (dy < 0) { |
|
||||||
dy = -dy; |
|
||||||
stepy = -1; |
|
||||||
} |
|
||||||
else |
|
||||||
stepy = 1; |
|
||||||
|
|
||||||
if (dx < 0) { |
|
||||||
dx = -dx; |
|
||||||
stepx = -1; |
|
||||||
} |
|
||||||
else |
|
||||||
stepx = 1; |
|
||||||
|
|
||||||
dy <<= 1; // dy is now 2*dy
|
|
||||||
dx <<= 1; // dx is now 2*dx
|
|
||||||
LCD_setPixel(x0, y0, bw); // Draw the first pixel.
|
|
||||||
|
|
||||||
if (dx > dy) { |
|
||||||
int fraction = dy - (dx >> 1); |
|
||||||
while (x0 != x1) { |
|
||||||
if (fraction >= 0) { |
|
||||||
y0 += stepy; |
|
||||||
fraction -= dx; |
|
||||||
} |
|
||||||
x0 += stepx; |
|
||||||
fraction += dy; |
|
||||||
LCD_setPixel(x0, y0, bw); |
|
||||||
} |
|
||||||
} |
|
||||||
else { |
|
||||||
int fraction = dx - (dy >> 1); |
|
||||||
while (y0 != y1) { |
|
||||||
if (fraction >= 0) { |
|
||||||
x0 += stepx; |
|
||||||
fraction -= dy; |
|
||||||
} |
|
||||||
y0 += stepy; |
|
||||||
fraction += dx; |
|
||||||
LCD_setPixel(x0, y0, bw); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// setRect will draw a rectangle from x0,y0 top-left corner to
|
|
||||||
// a x1,y1 bottom-right corner. Can be filled with the fill
|
|
||||||
// parameter, and colored with bw.
|
|
||||||
// This function was grabbed from the SparkFun ColorLCDShield
|
|
||||||
// library.
|
|
||||||
void LCD_setRect(int x0, int y0, int x1, int y1, bool fill, enum Color bw) |
|
||||||
{ |
|
||||||
// check if the rectangle is to be filled
|
|
||||||
if (fill == 1) { |
|
||||||
int xDiff; |
|
||||||
|
|
||||||
if (x0 > x1) |
|
||||||
xDiff = x0 - x1; //Find the difference between the x vars
|
|
||||||
else |
|
||||||
xDiff = x1 - x0; |
|
||||||
|
|
||||||
while (xDiff >= 0) { |
|
||||||
LCD_setLine(x0, y0, x0, y1, bw); |
|
||||||
|
|
||||||
if (x0 > x1) |
|
||||||
x0--; |
|
||||||
else |
|
||||||
x0++; |
|
||||||
|
|
||||||
xDiff--; |
|
||||||
} |
|
||||||
} |
|
||||||
else { |
|
||||||
// best way to draw an unfilled rectangle is to draw four lines
|
|
||||||
LCD_setLine(x0, y0, x1, y0, bw); |
|
||||||
LCD_setLine(x0, y1, x1, y1, bw); |
|
||||||
LCD_setLine(x0, y0, x0, y1, bw); |
|
||||||
LCD_setLine(x1, y0, x1, y1, bw); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// setCircle draws a circle centered around x0,y0 with a defined
|
|
||||||
// radius. The circle can be black or white. And have a line
|
|
||||||
// thickness ranging from 1 to the radius of the circle.
|
|
||||||
// This function was grabbed from the SparkFun ColorLCDShield
|
|
||||||
// library.
|
|
||||||
void LCD_setCircle(int x0, int y0, int radius, enum Color bw, int lineThickness) |
|
||||||
{ |
|
||||||
for (int r = 0; r < lineThickness; r++) { |
|
||||||
int f = 1 - radius; |
|
||||||
int ddF_x = 0; |
|
||||||
int ddF_y = -2 * radius; |
|
||||||
int x = 0; |
|
||||||
int y = radius; |
|
||||||
|
|
||||||
LCD_setPixel(x0, y0 + radius, bw); |
|
||||||
LCD_setPixel(x0, y0 - radius, bw); |
|
||||||
LCD_setPixel(x0 + radius, y0, bw); |
|
||||||
LCD_setPixel(x0 - radius, y0, bw); |
|
||||||
|
|
||||||
while (x < y) { |
|
||||||
if (f >= 0) { |
|
||||||
y--; |
|
||||||
ddF_y += 2; |
|
||||||
f += ddF_y; |
|
||||||
} |
|
||||||
x++; |
|
||||||
ddF_x += 2; |
|
||||||
f += ddF_x + 1; |
|
||||||
|
|
||||||
LCD_setPixel(x0 + x, y0 + y, bw); |
|
||||||
LCD_setPixel(x0 - x, y0 + y, bw); |
|
||||||
LCD_setPixel(x0 + x, y0 - y, bw); |
|
||||||
LCD_setPixel(x0 - x, y0 - y, bw); |
|
||||||
LCD_setPixel(x0 + y, y0 + x, bw); |
|
||||||
LCD_setPixel(x0 - y, y0 + x, bw); |
|
||||||
LCD_setPixel(x0 + y, y0 - x, bw); |
|
||||||
LCD_setPixel(x0 - y, y0 - x, bw); |
|
||||||
} |
|
||||||
radius--; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static void LCD_setCharEx(struct Utf8Char character, int x, int y, enum Color color, struct TextStyle style) |
|
||||||
{ |
|
||||||
const struct FontGraphic *symbol = Font_GetSymbol(character); |
|
||||||
|
|
||||||
uint8_t column; // temp byte to store character's column bitmap
|
|
||||||
for (int i = 0; i < 5; i++) // 5 columns (x) per character
|
|
||||||
{ |
|
||||||
column = symbol->columns[i]; |
|
||||||
for (int j = 0; j < 8; j++) // 8 rows (y) per character
|
|
||||||
{ |
|
||||||
bool bit = column & (1 << j); |
|
||||||
|
|
||||||
if (style.size == FONT_NORMAL) { |
|
||||||
if (bit) {// test bits to set pixels
|
|
||||||
LCD_setPixel(x + i, y + j, color); |
|
||||||
} |
|
||||||
else if (style.bg) { |
|
||||||
LCD_setPixel(x + i, y + j, !color); |
|
||||||
} |
|
||||||
} |
|
||||||
else if (style.size == FONT_DOUBLE) { |
|
||||||
if (bit) {// test bits to set pixels
|
|
||||||
LCD_setPixel(x + i * 2, y + j * 2, color); |
|
||||||
LCD_setPixel(x + i * 2 + 1, y + j * 2, color); |
|
||||||
LCD_setPixel(x + i * 2, y + j * 2 + 1, color); |
|
||||||
LCD_setPixel(x + i * 2 + 1, y + j * 2 + 1, color); |
|
||||||
} |
|
||||||
else if (style.bg) { |
|
||||||
LCD_setPixel(x + i * 2, y + j * 2, !color); |
|
||||||
LCD_setPixel(x + i * 2 + 1, y + j * 2, !color); |
|
||||||
LCD_setPixel(x + i * 2, y + j * 2 + 1, !color); |
|
||||||
LCD_setPixel(x + i * 2 + 1, y + j * 2 + 1, !color); |
|
||||||
} |
|
||||||
} |
|
||||||
else if (style.size == FONT_BOLD) { |
|
||||||
if (bit) {// test bits to set pixels
|
|
||||||
LCD_setPixel(x + i, y + j, color); |
|
||||||
LCD_setPixel(x + i + 1, y + j, color); |
|
||||||
} |
|
||||||
else if (style.bg) { |
|
||||||
LCD_setPixel(x + i, y + j, !color); |
|
||||||
LCD_setPixel(x + i + 1, y + j, !color); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// setStr draws a string of characters, calling setChar with
|
|
||||||
// progressive coordinates until it's done.
|
|
||||||
// This function was grabbed from the SparkFun ColorLCDShield
|
|
||||||
// library.
|
|
||||||
void LCD_setStr(const char *dString, int x, int y, enum Color bw) |
|
||||||
{ |
|
||||||
LCD_setStrEx(dString, x, y, bw, (struct TextStyle) {}); |
|
||||||
} |
|
||||||
|
|
||||||
static const int char_widths[] = { |
|
||||||
[FONT_NORMAL] = 5, |
|
||||||
[FONT_DOUBLE] = 10, |
|
||||||
[FONT_BOLD] = 6 |
|
||||||
}; |
|
||||||
|
|
||||||
static const int char_spacings[] = { |
|
||||||
[FONT_NORMAL] = 1, |
|
||||||
[FONT_DOUBLE] = 2, |
|
||||||
[FONT_BOLD] = 1 |
|
||||||
}; |
|
||||||
|
|
||||||
static const int char_heights[] = { |
|
||||||
[FONT_NORMAL] = 8, |
|
||||||
[FONT_DOUBLE] = 16, |
|
||||||
[FONT_BOLD] = 8 |
|
||||||
}; |
|
||||||
|
|
||||||
// setStr draws a string of characters, calling setChar with
|
|
||||||
// progressive coordinates until it's done.
|
|
||||||
// This function was grabbed from the SparkFun ColorLCDShield
|
|
||||||
// library. AND HEAVILY MODIFIED
|
|
||||||
void LCD_setStrEx(const char *dString, int x, int y, enum Color color, struct TextStyle style) |
|
||||||
{ |
|
||||||
struct Utf8Char uchar; |
|
||||||
struct Utf8Iterator iter; |
|
||||||
Utf8Iterator_Init(&iter, dString); |
|
||||||
|
|
||||||
int charw = char_widths[style.size]; |
|
||||||
int charh = char_heights[style.size]; |
|
||||||
int spacingx = char_spacings[style.size] + style.spacing_x; |
|
||||||
int spacingy = style.spacing_y; |
|
||||||
|
|
||||||
if (style.align != ALIGN_LEFT) { |
|
||||||
int line_len = 0; |
|
||||||
int skip = style.skip; |
|
||||||
while ((uchar = Utf8Iterator_Next(&iter)).uint) { |
|
||||||
if (skip > 0) { |
|
||||||
skip -= 1; |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (uchar.bytes[0] == '\n') { |
|
||||||
break; |
|
||||||
} |
|
||||||
line_len++; |
|
||||||
} |
|
||||||
if (style.align == ALIGN_CENTER) { |
|
||||||
x -= ((charw + spacingx) * line_len) / 2 - spacingx/2; |
|
||||||
} else { |
|
||||||
// right
|
|
||||||
x -= ((charw + spacingx) * line_len) - spacingx; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const int x0 = x; |
|
||||||
Utf8Iterator_Init(&iter, dString); |
|
||||||
|
|
||||||
int limit = style.limit; |
|
||||||
int skip = style.skip; |
|
||||||
bool wrap; |
|
||||||
while ((uchar = Utf8Iterator_Next(&iter)).uint) { |
|
||||||
if (skip > 0) { |
|
||||||
skip -= 1; |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
wrap = uchar.bytes[0] == '\n'; |
|
||||||
|
|
||||||
if (!wrap) LCD_setCharEx(uchar, x, y, color, style); |
|
||||||
|
|
||||||
if (limit > 0) { |
|
||||||
limit -= 1; |
|
||||||
if (limit == 0) return; |
|
||||||
} |
|
||||||
|
|
||||||
if (!wrap) { |
|
||||||
x += charw; |
|
||||||
if (style.bg) { |
|
||||||
for (int i = y; i < y + charh; i++) { |
|
||||||
for (int j = x; j < x + spacingx; j++) { |
|
||||||
LCD_setPixel(j, i, !color); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
x += spacingx; |
|
||||||
if (wrap || x > (LCD_WIDTH - charw)) { |
|
||||||
if (style.nowrap) break; |
|
||||||
if (style.wrap_to_0) { |
|
||||||
x = 0; |
|
||||||
} else { |
|
||||||
x = x0; |
|
||||||
} |
|
||||||
y += charh + spacingy; |
|
||||||
} |
|
||||||
|
|
||||||
// no more characters could be seen
|
|
||||||
if (y >= LCD_HEIGHT) { |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// This function will draw an array over the screen. (For now) the
|
|
||||||
// array must be the same size as the screen, covering the entirety
|
|
||||||
// of the display.
|
|
||||||
void LCD_setBitmapFullScreen(const uint8_t *bitArray) |
|
||||||
{ |
|
||||||
for (int i = 0; i < sizeof(LCD_displayMap); i++) { |
|
||||||
LCD_displayMap[i] = bitArray[i]; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void LCD_setBitmap(const struct BitmapImage *image, int x, int y, bool bg, enum Color color) |
|
||||||
{ |
|
||||||
// int byte_count = image->width * ((image->height + 7) / 8);
|
|
||||||
|
|
||||||
for (int xi = 0; xi < image->width; xi++) { |
|
||||||
for (int yi = 0; yi < image->height; yi++) { |
|
||||||
int idx = xi + (yi / 8) * image->width; |
|
||||||
// if (idx > byte_count) continue;
|
|
||||||
bool bit = image->bytes[idx] & (1 << (yi % 8)); |
|
||||||
if (bit) { |
|
||||||
LCD_setPixel(x + xi, y + yi, color); |
|
||||||
} |
|
||||||
else if (bg) { |
|
||||||
LCD_setPixel(x + xi, y + yi, !color); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// This function clears the entire display either white (0) or
|
|
||||||
// black (1).
|
|
||||||
// The screen won't actually clear until you call updateDisplay()!
|
|
||||||
void LCD_clearDisplay(enum Color bw) |
|
||||||
{ |
|
||||||
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) { |
|
||||||
if (bw) |
|
||||||
LCD_displayMap[i] = 0xFF; |
|
||||||
else |
|
||||||
LCD_displayMap[i] = 0; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void LCD_invertDisplayData() |
|
||||||
{ |
|
||||||
/* Indirect, swap bits in displayMap option: */ |
|
||||||
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) { |
|
||||||
LCD_displayMap[i] ^= 0xFF; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void LCD_setShadeOverlay() { |
|
||||||
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) { |
|
||||||
LCD_displayMap[i] |= i & 1 ? 0x88 : 0x22; |
|
||||||
} |
|
||||||
} |
|
@ -1,93 +0,0 @@ |
|||||||
/**
|
|
||||||
* TODO file description |
|
||||||
*
|
|
||||||
* Created on 2020/01/05. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef LCD_DRAWING_H |
|
||||||
#define LCD_DRAWING_H |
|
||||||
|
|
||||||
#include <stdint.h> |
|
||||||
#include <stdbool.h> |
|
||||||
|
|
||||||
#include "utf8.h" |
|
||||||
#include "bitmaps.h" |
|
||||||
|
|
||||||
enum Color { |
|
||||||
WHITE = 0, |
|
||||||
BLACK = 1, |
|
||||||
}; |
|
||||||
|
|
||||||
// This function sets a pixel on displayMap to your preferred
|
|
||||||
// color. 1=Black, 0= white.
|
|
||||||
void LCD_setPixel(int x, int y, enum Color bw); |
|
||||||
|
|
||||||
// setLine draws a line from x0,y0 to x1,y1 with the set color
|
|
||||||
void LCD_setLine(int x0, int y0, int x1, int y1, enum Color bw); |
|
||||||
|
|
||||||
// setRect will draw a rectangle from x0,y0 top-left corner to
|
|
||||||
// a x1,y1 bottom-right corner. Can be filled with the fill
|
|
||||||
// parameter, and colored with bw.
|
|
||||||
void LCD_setRect(int x0, int y0, int x1, int y1, bool fill, enum Color bw); |
|
||||||
|
|
||||||
// setCircle draws a circle centered around x0,y0 with a defined
|
|
||||||
// radius. The circle can be black or white. And have a line
|
|
||||||
// thickness ranging from 1 to the radius of the circle.
|
|
||||||
void LCD_setCircle (int x0, int y0, int radius, enum Color bw, int lineThickness); |
|
||||||
|
|
||||||
void LCD_setBitmapFullScreen(const uint8_t *bitArray); |
|
||||||
|
|
||||||
/*
|
|
||||||
FONT FUNCTIONS |
|
||||||
size = 1 ... normal |
|
||||||
size = 2 ... 2x |
|
||||||
size = 3 ... normal bold |
|
||||||
size | 0x80 ... clear background behind characters |
|
||||||
*/ |
|
||||||
|
|
||||||
enum TextSize { |
|
||||||
FONT_NORMAL = 0, |
|
||||||
FONT_DOUBLE = 1, |
|
||||||
FONT_BOLD = 2, |
|
||||||
}; |
|
||||||
|
|
||||||
enum TextAlign { |
|
||||||
ALIGN_LEFT = 0, |
|
||||||
ALIGN_CENTER = 1, |
|
||||||
ALIGN_RIGHT = 2, |
|
||||||
}; |
|
||||||
|
|
||||||
struct TextStyle { |
|
||||||
bool bg; //!< Fill the characters background with the opposite color
|
|
||||||
enum TextSize size; //!< Character size
|
|
||||||
enum TextAlign align; //!< Alignment (works only for single line)
|
|
||||||
int limit; //!< Number of characters to print from the string, if > 0
|
|
||||||
int skip; //!< Characters to skip before printing
|
|
||||||
int spacing_x; //!< Additional X spacing
|
|
||||||
int spacing_y; //!< Additional Y spacing
|
|
||||||
bool nowrap; //!< Stop painting when the right edge of the screen is reached
|
|
||||||
bool wrap_to_0; //!< wrap to 0 instead of the initial X coordinate
|
|
||||||
}; |
|
||||||
|
|
||||||
// setStr draws a string of characters, calling setChar with
|
|
||||||
// progressive coordinates until it's done.
|
|
||||||
// This function was grabbed from the SparkFun ColorLCDShield
|
|
||||||
// library.
|
|
||||||
void LCD_setStr(const char * dString, int x, int y, enum Color bw); |
|
||||||
|
|
||||||
void LCD_setStrEx(const char *dString, int x, int y, enum Color color, struct TextStyle style); |
|
||||||
|
|
||||||
// This function clears the entire display either white (0) or
|
|
||||||
// black (1).
|
|
||||||
// The screen won't actually clear until you call updateDisplay()!
|
|
||||||
void LCD_clearDisplay(enum Color bw); |
|
||||||
|
|
||||||
/* Invert colors (hard change in data; does NOT send to display immediately) */ |
|
||||||
void LCD_invertDisplayData(); |
|
||||||
|
|
||||||
void LCD_setBitmap(const struct BitmapImage *image, int x, int y, bool bg, enum Color color); |
|
||||||
|
|
||||||
/** draw dotted shadow over current content */ |
|
||||||
void LCD_setShadeOverlay(); |
|
||||||
|
|
||||||
#endif //LCD_DRAWING_H
|
|
@ -1,20 +0,0 @@ |
|||||||
/**
|
|
||||||
* UTF-8 capable bitmap font |
|
||||||
*
|
|
||||||
* Created on 2020/01/04. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef GFX_FONT_H |
|
||||||
#define GFX_FONT_H |
|
||||||
|
|
||||||
#include "font.h" |
|
||||||
#include "utf8.h" |
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
struct FontGraphic { |
|
||||||
uint8_t columns[5]; |
|
||||||
}; |
|
||||||
|
|
||||||
const struct FontGraphic *Font_GetSymbol(struct Utf8Char ch); |
|
||||||
|
|
||||||
#endif //GFX_FONT_H
|
|
@ -1,178 +0,0 @@ |
|||||||
#include <driver/gpio.h> |
|
||||||
#include <driver/spi_master.h> |
|
||||||
#include "nokia.h" |
|
||||||
#include <string.h> |
|
||||||
|
|
||||||
/* Pin definitions:
|
|
||||||
Most of these pins can be moved to any digital or analog pin. |
|
||||||
DN(MOSI)and SCLK should be left where they are (SPI pins). The |
|
||||||
LED (backlight) pin should remain on a PWM-capable pin. */ |
|
||||||
static const int scePin = 17; // SCE - Chip select, pin 3 on LCD.
|
|
||||||
static const int rstPin = 16; // RST - Reset, pin 4 on LCD.
|
|
||||||
static const int dcPin = 4; // DC - Data/Command, pin 5 on LCD.
|
|
||||||
static const int sdinPin = 15; // DN(MOSI) - Serial data, pin 6 on LCD.
|
|
||||||
static const int sclkPin = 13; // SCLK - Serial clock, pin 7 on LCD.
|
|
||||||
|
|
||||||
/* PCD8544-specific defines: */ |
|
||||||
#define LCD_COMMAND 0 |
|
||||||
#define LCD_DATA 1 |
|
||||||
|
|
||||||
#define DEFAULT_CONTRAST 48 |
|
||||||
|
|
||||||
static spi_device_handle_t hSPI; |
|
||||||
|
|
||||||
/* The displayMap variable stores a buffer representation of the
|
|
||||||
pixels on our display. There are 504 total bits in this array, |
|
||||||
same as how many pixels there are on a 84 x 48 display. |
|
||||||
|
|
||||||
Each byte in this array covers a 8-pixel vertical block on the |
|
||||||
display. Each successive byte covers the next 8-pixel column over |
|
||||||
until you reach the right-edge of the display and step down 8 rows. |
|
||||||
|
|
||||||
To update the display, we first have to write to this array, then |
|
||||||
call the updateDisplay() function, which sends this whole array |
|
||||||
to the PCD8544. |
|
||||||
|
|
||||||
Because the PCD8544 won't let us write individual pixels at a |
|
||||||
time, this is how we can make targeted changes to the display. */ |
|
||||||
uint8_t LCD_displayMap[LCD_WIDTH * LCD_HEIGHT / 8] = {}; |
|
||||||
|
|
||||||
// There are two memory banks in the LCD, data/RAM and commands.
|
|
||||||
// This function sets the DC pin high or low depending, and then
|
|
||||||
// sends the data byte
|
|
||||||
static void LCD_SendBytes(bool data_or_command, const uint8_t *data, size_t len) |
|
||||||
{ |
|
||||||
esp_err_t ret; |
|
||||||
spi_transaction_t t; |
|
||||||
if (len == 0) return; //no need to send anything
|
|
||||||
memset(&t, 0, sizeof(t)); //Zero out the transaction
|
|
||||||
t.length = len * 8; //Len is in bytes, transaction length is in bits.
|
|
||||||
t.tx_buffer = data; //Data
|
|
||||||
t.user = (void *) data_or_command; //D/C
|
|
||||||
ret = spi_device_polling_transmit(hSPI, &t); //Transmit!
|
|
||||||
assert(ret == ESP_OK); //Should have had no issues.
|
|
||||||
} |
|
||||||
|
|
||||||
// There are two memory banks in the LCD, data/RAM and commands.
|
|
||||||
// This function sets the DC pin high or low depending, and then
|
|
||||||
// sends the data byte
|
|
||||||
static void LCD_SendByte(bool data_or_command, uint8_t data) |
|
||||||
{ |
|
||||||
esp_err_t ret; |
|
||||||
spi_transaction_t t; |
|
||||||
memset(&t, 0, sizeof(t)); //Zero out the transaction
|
|
||||||
t.length = 8; // transaction length is in bits.
|
|
||||||
t.tx_data[0] = data; //Data
|
|
||||||
t.flags = SPI_TRANS_USE_TXDATA; |
|
||||||
t.user = (void *) data_or_command; //D/C
|
|
||||||
ret = spi_device_polling_transmit(hSPI, &t); //Transmit!
|
|
||||||
assert(ret == ESP_OK); //Should have had no issues.
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
// Helpful function to directly command the LCD to go to a
|
|
||||||
// specific x,y coordinate.
|
|
||||||
static void gotoXY(int x, int y) |
|
||||||
{ |
|
||||||
const uint8_t cmd[2] = { |
|
||||||
0x80 | x, |
|
||||||
0x40 | y, |
|
||||||
}; |
|
||||||
LCD_SendBytes(LCD_COMMAND, cmd, 2); |
|
||||||
} |
|
||||||
|
|
||||||
// This will actually draw on the display, whatever is currently
|
|
||||||
// in the displayMap array.
|
|
||||||
void LCD_updateDisplay() |
|
||||||
{ |
|
||||||
spi_device_acquire_bus(hSPI, portMAX_DELAY); |
|
||||||
gotoXY(0, 0); |
|
||||||
LCD_SendBytes(LCD_DATA, &LCD_displayMap[0], LCD_WIDTH * (LCD_HEIGHT / 8)); |
|
||||||
spi_device_release_bus(hSPI); |
|
||||||
} |
|
||||||
|
|
||||||
// Set contrast can set the LCD Vop to a value between 0 and 127.
|
|
||||||
// 40-60 is usually a pretty good range.
|
|
||||||
void LCD_setContrast(uint8_t contrast) |
|
||||||
{ |
|
||||||
spi_device_acquire_bus(hSPI, portMAX_DELAY); |
|
||||||
const uint8_t cmd[3] = { |
|
||||||
0x21, //Tell LCD that extended commands follow
|
|
||||||
0x80 | contrast,//Set LCD Vop (Contrast): Try 0xB1(good @ 3.3V) or 0xBF if your display is too dark
|
|
||||||
0x20,//Set display mode
|
|
||||||
}; |
|
||||||
LCD_SendBytes(LCD_COMMAND, cmd, 3); |
|
||||||
spi_device_release_bus(hSPI); |
|
||||||
} |
|
||||||
|
|
||||||
void LCD_invertDisplay(bool invert) |
|
||||||
{ |
|
||||||
LCD_SendByte(LCD_COMMAND, 0x0C | invert); |
|
||||||
} |
|
||||||
|
|
||||||
//This function is called (in irq context!) just before a transmission starts. It will
|
|
||||||
//set the D/C line to the value indicated in the user field.
|
|
||||||
void lcd_spi_pre_transfer_callback(spi_transaction_t *t) |
|
||||||
{ |
|
||||||
int dc = (int) t->user; |
|
||||||
gpio_set_level(dcPin, dc); |
|
||||||
} |
|
||||||
|
|
||||||
//This sends the magical commands to the PCD8544
|
|
||||||
void LCD_setup(void) |
|
||||||
{ |
|
||||||
gpio_config_t output = { |
|
||||||
.pin_bit_mask = (1 << scePin) | (1 << rstPin) | (1 << dcPin) | (1 << sdinPin) | (1 << sclkPin), |
|
||||||
.mode = GPIO_MODE_OUTPUT |
|
||||||
}; |
|
||||||
gpio_config(&output); |
|
||||||
|
|
||||||
esp_err_t ret; |
|
||||||
spi_bus_config_t buscfg = { |
|
||||||
.miso_io_num=-1, |
|
||||||
.mosi_io_num=sdinPin, |
|
||||||
.sclk_io_num=sclkPin, |
|
||||||
.quadwp_io_num=-1, |
|
||||||
.quadhd_io_num=-1, |
|
||||||
.max_transfer_sz=LCD_WIDTH * LCD_HEIGHT, // ??? this doesnt seem to do anything in the driver
|
|
||||||
}; |
|
||||||
spi_device_interface_config_t devcfg = { |
|
||||||
.clock_speed_hz=4 * 1000 * 1000, // Hz
|
|
||||||
// enable signal lead/lag
|
|
||||||
.cs_ena_pretrans = 0, |
|
||||||
.cs_ena_posttrans = 0, |
|
||||||
.mode=0, //SPI mode 0
|
|
||||||
.spics_io_num=scePin, //CS pin
|
|
||||||
.queue_size=1, // we use the polling mode, so this does not matter
|
|
||||||
.pre_cb=lcd_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line
|
|
||||||
}; |
|
||||||
|
|
||||||
//Initialize the SPI bus
|
|
||||||
ret = spi_bus_initialize(HSPI_HOST, &buscfg, 1); |
|
||||||
ESP_ERROR_CHECK(ret); |
|
||||||
//Attach the LCD to the SPI bus
|
|
||||||
ret = spi_bus_add_device(HSPI_HOST, &devcfg, &hSPI); |
|
||||||
ESP_ERROR_CHECK(ret); |
|
||||||
|
|
||||||
spi_device_acquire_bus(hSPI, portMAX_DELAY); |
|
||||||
{ |
|
||||||
//Reset the LCD to a known state
|
|
||||||
gpio_set_level(rstPin, 0); |
|
||||||
gpio_set_level(rstPin, 1); |
|
||||||
|
|
||||||
const uint8_t magic[6] = { |
|
||||||
0x21, // Tell LCD extended commands follow
|
|
||||||
0x80 + DEFAULT_CONTRAST, // Set LCD Vop (Contrast)
|
|
||||||
0x04, // Set Temp coefficent
|
|
||||||
0x14, // LCD bias mode 1:48 (try 0x13)
|
|
||||||
//We must send 0x20 before modifying the display control mode
|
|
||||||
0x20, // Clear extended option
|
|
||||||
0x0C, // Set display control, normal mode.
|
|
||||||
}; |
|
||||||
LCD_SendBytes(LCD_COMMAND, &magic[0], 6); |
|
||||||
} |
|
||||||
spi_device_release_bus(hSPI); |
|
||||||
|
|
||||||
// show the blank screen (display is in an undefined state after boot)
|
|
||||||
LCD_updateDisplay(); |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
/**
|
|
||||||
* Driver for Nokia 5110 SPI LCD display |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef GFX_NOKIA_H |
|
||||||
#define GFX_NOKIA_H |
|
||||||
|
|
||||||
/* Pin definitions:
|
|
||||||
Most of these pins can be moved to any digital or analog pin. |
|
||||||
DN(MOSI)and SCLK should be left where they are (SPI pins). The |
|
||||||
LED (backlight) pin should remain on a PWM-capable pin. */ |
|
||||||
// SCE - Chip select, pin 3 on LCD.
|
|
||||||
// RST - Reset, pin 4 on LCD.
|
|
||||||
// DC - Data/Command, pin 5 on LCD.
|
|
||||||
// DN(MOSI) - Serial data, pin 6 on LCD.
|
|
||||||
// SCLK - Serial clock, pin 7 on LCD.
|
|
||||||
// LED - Backlight LED, pin 8 on LCD.
|
|
||||||
|
|
||||||
/* 84x48 LCD Defines: */ |
|
||||||
#include <stdbool.h> |
|
||||||
#include <stdint.h> |
|
||||||
#include "utf8.h" |
|
||||||
#include "display_spec.h" |
|
||||||
|
|
||||||
// This will actually draw on the display, whatever is currently
|
|
||||||
// in the displayMap array.
|
|
||||||
void LCD_updateDisplay(); |
|
||||||
|
|
||||||
// Set contrast can set the LCD Vop to a value between 0 and 127.
|
|
||||||
// 40-60 is usually a pretty good range.
|
|
||||||
void LCD_setContrast(uint8_t contrast); |
|
||||||
|
|
||||||
/* Invert colors */ |
|
||||||
void LCD_invertDisplay(bool invert); |
|
||||||
|
|
||||||
//This sends the magical commands to the PCD8544
|
|
||||||
void LCD_setup(void); |
|
||||||
|
|
||||||
#endif // GFX_NOKIA_H
|
|
@ -1,122 +0,0 @@ |
|||||||
#include <stdint.h> |
|
||||||
#include "utf8.h" |
|
||||||
|
|
||||||
//
|
|
||||||
// Created by MightyPork on 2017/08/20.
|
|
||||||
//
|
|
||||||
// UTF-8 parser - collects bytes of a code point before writing them
|
|
||||||
// into a screen cell.
|
|
||||||
//
|
|
||||||
|
|
||||||
const struct Utf8Char EMPTY_CHAR = (struct Utf8Char) {.uint = 0}; |
|
||||||
|
|
||||||
// Code Points First Byte Second Byte Third Byte Fourth Byte
|
|
||||||
// U+0000 - U+007F 00 - 7F
|
|
||||||
// U+0080 - U+07FF C2 - DF 80 - BF
|
|
||||||
// U+0800 - U+0FFF E0 *A0 - BF 80 - BF
|
|
||||||
// U+1000 - U+CFFF E1 - EC 80 - BF 80 - BF
|
|
||||||
// U+D000 - U+D7FF ED 80 - *9F 80 - BF
|
|
||||||
// U+E000 - U+FFFF EE - EF 80 - BF 80 - BF
|
|
||||||
// U+10000 - U+3FFFF F0 *90 - BF 80 - BF 80 - BF
|
|
||||||
// U+40000 - U+FFFFF F1 - F3 80 - BF 80 - BF 80 - BF
|
|
||||||
// U+100000 - U+10FFFF F4 80 - *8F 80 - BF 80 - BF
|
|
||||||
|
|
||||||
size_t utf8_strlen(const char *text) |
|
||||||
{ |
|
||||||
// TODO optimize
|
|
||||||
struct Utf8Iterator iter; |
|
||||||
Utf8Iterator_Init(&iter, text); |
|
||||||
size_t num = 0; |
|
||||||
while ((Utf8Iterator_Next(&iter)).uint) { |
|
||||||
num++; |
|
||||||
} |
|
||||||
return num; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a received character |
|
||||||
*/ |
|
||||||
struct Utf8Char Utf8Parser_Handle(struct Utf8Parser *self, char c) |
|
||||||
{ |
|
||||||
uint8_t *bytes = self->buffer.bytes; |
|
||||||
|
|
||||||
uint8_t uc = (uint8_t) c; |
|
||||||
// collecting unicode glyphs...
|
|
||||||
if (uc & 0x80) { |
|
||||||
if (self->utf_len == 0) { |
|
||||||
bytes[0] = uc; |
|
||||||
self->utf_j = 1; |
|
||||||
|
|
||||||
// start
|
|
||||||
if (uc == 0xC0 || uc == 0xC1 || uc > 0xF4) { |
|
||||||
// forbidden start codes
|
|
||||||
goto fail; |
|
||||||
} |
|
||||||
|
|
||||||
if ((uc & 0xE0) == 0xC0) { |
|
||||||
self->utf_len = 2; |
|
||||||
} |
|
||||||
else if ((uc & 0xF0) == 0xE0) { |
|
||||||
self->utf_len = 3; |
|
||||||
} |
|
||||||
else if ((uc & 0xF8) == 0xF0) { |
|
||||||
self->utf_len = 4; |
|
||||||
} |
|
||||||
else { |
|
||||||
// chars over 127 that don't start unicode sequences
|
|
||||||
goto fail; |
|
||||||
} |
|
||||||
} |
|
||||||
else { |
|
||||||
if ((uc & 0xC0) != 0x80) { |
|
||||||
bytes[self->utf_j++] = uc; |
|
||||||
goto fail; |
|
||||||
} |
|
||||||
else { |
|
||||||
bytes[self->utf_j++] = uc; |
|
||||||
if (self->utf_j >= self->utf_len) { |
|
||||||
// check for bad sequences - overlong or some other problem
|
|
||||||
if (bytes[0] == 0xF4 && bytes[1] > 0x8F) goto fail; |
|
||||||
if (bytes[0] == 0xF0 && bytes[1] < 0x90) goto fail; |
|
||||||
if (bytes[0] == 0xED && bytes[1] > 0x9F) goto fail; |
|
||||||
if (bytes[0] == 0xE0 && bytes[1] < 0xA0) goto fail; |
|
||||||
|
|
||||||
// trap for surrogates - those break javascript
|
|
||||||
if (bytes[0] == 0xED && bytes[1] >= 0xA0 && bytes[1] <= 0xBF) goto fail; |
|
||||||
|
|
||||||
goto success; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
else { |
|
||||||
bytes[0] = uc; |
|
||||||
goto success; |
|
||||||
} |
|
||||||
|
|
||||||
return EMPTY_CHAR; |
|
||||||
|
|
||||||
success:; |
|
||||||
struct Utf8Char result = self->buffer; |
|
||||||
self->buffer.uint = 0; // erase the buffer
|
|
||||||
self->utf_len = 0; |
|
||||||
return result; |
|
||||||
|
|
||||||
fail: |
|
||||||
self->buffer.uint = 0; // erase the buffer
|
|
||||||
self->utf_len = 0; |
|
||||||
return EMPTY_CHAR; |
|
||||||
} |
|
||||||
|
|
||||||
struct Utf8Char Utf8Iterator_Next(struct Utf8Iterator *self) |
|
||||||
{ |
|
||||||
char c; |
|
||||||
struct Utf8Char uchar; |
|
||||||
while ((c = *self->source++) != 0) { |
|
||||||
uchar = Utf8Parser_Handle(&self->parser, c); |
|
||||||
if (uchar.uint) { |
|
||||||
return uchar; |
|
||||||
} |
|
||||||
} |
|
||||||
return EMPTY_CHAR; |
|
||||||
} |
|
@ -1,84 +0,0 @@ |
|||||||
/**
|
|
||||||
* UTF-8 string parsing and character iteration |
|
||||||
*
|
|
||||||
* Created on 2020/01/04. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef LIQUIDTYPE_UTF8_H |
|
||||||
#define LIQUIDTYPE_UTF8_H |
|
||||||
|
|
||||||
#include <stddef.h> |
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
/**
|
|
||||||
* UTF-8 encoded character. |
|
||||||
*/ |
|
||||||
struct Utf8Char { |
|
||||||
union { |
|
||||||
/** character bytes; padded by zero bytes if shorter than 4 */ |
|
||||||
uint8_t bytes[4]; |
|
||||||
/** u32 view of the bytes */ |
|
||||||
uint32_t uint; |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
/** UTF8 string parser internal state */ |
|
||||||
struct Utf8Parser { |
|
||||||
/** UTF-8 bytes buffer */ |
|
||||||
struct Utf8Char buffer; |
|
||||||
/** Currently collected UTF-8 character length */ |
|
||||||
uint8_t utf_len; |
|
||||||
/** Position in the current character */ |
|
||||||
uint8_t utf_j; |
|
||||||
}; |
|
||||||
|
|
||||||
static inline void Utf8Parser_Clear(struct Utf8Parser *self) { |
|
||||||
self->buffer.uint = 0; |
|
||||||
self->utf_j = 0; |
|
||||||
self->utf_len = 0; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Utf8 character iterator. |
|
||||||
* |
|
||||||
* Usage: |
|
||||||
* struct Utf8Iterator iter; |
|
||||||
* Utf8Iterator_Init(&iter, myString); |
|
||||||
* |
|
||||||
* union Utf8Char uchar; |
|
||||||
* while ((uchar = Utf8Iterator_Next(&iter)).uint) { |
|
||||||
* // do something with the char
|
|
||||||
* } |
|
||||||
* |
|
||||||
* // Free myString if needed, it is not mutated.
|
|
||||||
*/ |
|
||||||
struct Utf8Iterator { |
|
||||||
/* Characters to parse. The pointer is advanced as the iterator progresses. */ |
|
||||||
const char *source; |
|
||||||
struct Utf8Parser parser; |
|
||||||
}; |
|
||||||
|
|
||||||
static inline void Utf8Iterator_Init(struct Utf8Iterator *self, const char *source) { |
|
||||||
Utf8Parser_Clear(&self->parser); |
|
||||||
self->source = source; |
|
||||||
} |
|
||||||
|
|
||||||
size_t utf8_strlen(const char *text); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the next character from the iterator; Returns empty character if there are no more characters to parse. |
|
||||||
* |
|
||||||
* Invalid characters are skipped. |
|
||||||
*/ |
|
||||||
struct Utf8Char Utf8Iterator_Next(struct Utf8Iterator *self); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a character. |
|
||||||
* |
|
||||||
* The returned struct contains NIL (uint == 0) if no character is yet available. |
|
||||||
* |
|
||||||
* ASCII is passed through, utf-8 is collected and returned in one piece. |
|
||||||
*/ |
|
||||||
struct Utf8Char Utf8Parser_Handle(struct Utf8Parser *self, char c); |
|
||||||
|
|
||||||
#endif //LIQUIDTYPE_UTF8_H
|
|
@ -1,57 +0,0 @@ |
|||||||
/**
|
|
||||||
* Input event structural enum |
|
||||||
*
|
|
||||||
* Created on 2020/01/05. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef LIQUID_INPUT_EVENT_H |
|
||||||
#define LIQUID_INPUT_EVENT_H |
|
||||||
|
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
enum InputEvent_Kind { |
|
||||||
InputEventKind_Wheel, |
|
||||||
InputEventKind_Button, |
|
||||||
}; |
|
||||||
|
|
||||||
struct InputEvent { |
|
||||||
enum InputEvent_Kind kind; |
|
||||||
union { |
|
||||||
struct { |
|
||||||
// Wheel delta
|
|
||||||
int32_t delta; |
|
||||||
int8_t direction; |
|
||||||
} wheel; |
|
||||||
struct { |
|
||||||
// Button state
|
|
||||||
bool state; |
|
||||||
} button; |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
static inline struct InputEvent InputEvent_Wheel(int32_t delta) |
|
||||||
{ |
|
||||||
return (struct InputEvent) { |
|
||||||
.kind = InputEventKind_Wheel, |
|
||||||
.wheel = { |
|
||||||
.delta = delta, |
|
||||||
.direction = delta > 0 ? 1 : -1, |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Button event (push, release) |
|
||||||
* |
|
||||||
* @param state - pushed |
|
||||||
* @return event |
|
||||||
*/ |
|
||||||
static inline struct InputEvent InputEvent_Button(bool state) |
|
||||||
{ |
|
||||||
return (struct InputEvent) { |
|
||||||
.kind = InputEventKind_Button, |
|
||||||
.button = {.state = state}, |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
#endif //LIQUID_INPUT_EVENT_H
|
|
@ -0,0 +1,44 @@ |
|||||||
|
#include "scenes.h" |
||||||
|
#include "liquid.h" |
||||||
|
#include "../nokia.h" |
||||||
|
#include <malloc.h> |
||||||
|
|
||||||
|
struct private { |
||||||
|
int32_t pos; |
||||||
|
}; |
||||||
|
|
||||||
|
static struct SceneEvent Car_onInput(struct Scene *scene, struct InputEvent event) { |
||||||
|
struct private *priv = scene->private; |
||||||
|
switch (event.kind) { |
||||||
|
case InputEventKind_Wheel: |
||||||
|
priv->pos += event.wheel.delta; |
||||||
|
if (priv->pos < 0) priv->pos = 0; |
||||||
|
if (priv->pos > LCD_WIDTH-21) priv->pos = LCD_WIDTH-21; |
||||||
|
return SceneEvent_Repaint(); |
||||||
|
|
||||||
|
case InputEventKind_Button: |
||||||
|
if (event.button.state) { |
||||||
|
return SceneEvent_Close(0, NULL); |
||||||
|
} |
||||||
|
// fall through
|
||||||
|
default: |
||||||
|
return SceneEvent_None(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void Car_paint(struct Scene *scene) |
||||||
|
{ |
||||||
|
struct private *priv = scene->private; |
||||||
|
|
||||||
|
LCD_clearDisplay(0); |
||||||
|
LCD_setRect(priv->pos, LCD_HEIGHT/2-10, priv->pos+20,LCD_HEIGHT/2+10,0,1); |
||||||
|
LCD_updateDisplay(); |
||||||
|
} |
||||||
|
|
||||||
|
struct Scene *NewScene_Car(void) { |
||||||
|
struct Scene *scene = SCENE_SAFE_ALLOC(struct private); |
||||||
|
|
||||||
|
scene->onInput = Car_onInput; |
||||||
|
scene->paint = Car_paint; |
||||||
|
return scene; |
||||||
|
} |
@ -1,99 +0,0 @@ |
|||||||
/**
|
|
||||||
* Scene event structural enum |
|
||||||
*
|
|
||||||
* Created on 2020/01/05. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef LIQUID_SCENE_EVENT_H |
|
||||||
#define LIQUID_SCENE_EVENT_H |
|
||||||
|
|
||||||
// forward declaration
|
|
||||||
struct Scene; |
|
||||||
|
|
||||||
enum SceneEvent_Kind { |
|
||||||
SceneEventKind_Close, |
|
||||||
SceneEventKind_OpenChild, |
|
||||||
SceneEventKind_RequestRepaint, |
|
||||||
SceneEventKind_None, |
|
||||||
}; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Scene event, returned from some scene methods. |
|
||||||
* |
|
||||||
* Use the constructor functions to create this struct. |
|
||||||
*/ |
|
||||||
struct SceneEvent { |
|
||||||
/** Event kind enum */ |
|
||||||
enum SceneEvent_Kind kind; |
|
||||||
union { |
|
||||||
/* data for Close event kind */ |
|
||||||
struct { |
|
||||||
// Status code
|
|
||||||
int32_t status; |
|
||||||
// Return data on heap
|
|
||||||
void *data; |
|
||||||
} close; |
|
||||||
/* Data for Open event kind */ |
|
||||||
struct { |
|
||||||
// Scene (initialized, with options loaded in through the constructor)
|
|
||||||
struct Scene *scene; |
|
||||||
// Tag used by parent to identify the open child
|
|
||||||
uint32_t tag; |
|
||||||
} open; |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Create empty (null object) scene event. |
|
||||||
* |
|
||||||
* @return event |
|
||||||
*/ |
|
||||||
static inline struct SceneEvent SceneEvent_None(void) |
|
||||||
{ |
|
||||||
return (struct SceneEvent) { |
|
||||||
.kind = SceneEventKind_None, |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Request scene repaint |
|
||||||
* |
|
||||||
* @return event |
|
||||||
*/ |
|
||||||
static inline struct SceneEvent SceneEvent_Repaint(void) |
|
||||||
{ |
|
||||||
return (struct SceneEvent) { |
|
||||||
.kind = SceneEventKind_RequestRepaint |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Request a sub-scene to be opened |
|
||||||
* |
|
||||||
* @param child - child scene |
|
||||||
* @param tag - scene tag |
|
||||||
* @return event |
|
||||||
*/ |
|
||||||
static inline struct SceneEvent SceneEvent_OpenChild(struct Scene *child, uint32_t tag) { |
|
||||||
return (struct SceneEvent) { |
|
||||||
.kind = SceneEventKind_OpenChild, |
|
||||||
.open = { .scene = child, .tag=tag }, |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Close this scene, returning to parent. |
|
||||||
* |
|
||||||
* @param status - status number for the parent |
|
||||||
* @param data - heap-allocated data for parent, can be NULL; e.g. user input as string. |
|
||||||
* @return event |
|
||||||
*/ |
|
||||||
static inline struct SceneEvent SceneEvent_Close(int32_t status, void *data) |
|
||||||
{ |
|
||||||
return (struct SceneEvent) { |
|
||||||
.kind = SceneEventKind_Close, |
|
||||||
.close = {.status = status, .data=data}, |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
#endif //LIQUID_SCENE_EVENT_H
|
|
@ -0,0 +1,85 @@ |
|||||||
|
#include "scenes.h" |
||||||
|
#include "liquid.h" |
||||||
|
#include "../nokia.h" |
||||||
|
#include "../analog.h" |
||||||
|
#include <malloc.h> |
||||||
|
#include <stdio.h> |
||||||
|
|
||||||
|
struct private { |
||||||
|
int32_t pos; |
||||||
|
uint32_t timer_phase; |
||||||
|
uint32_t timer_prescale; |
||||||
|
}; |
||||||
|
|
||||||
|
static struct SceneEvent Root_onInput(struct Scene *scene, struct InputEvent event) { |
||||||
|
struct private *priv = scene->private; |
||||||
|
switch (event.kind) { |
||||||
|
case InputEventKind_Wheel: |
||||||
|
priv->pos += event.wheel.delta; |
||||||
|
break; |
||||||
|
|
||||||
|
case InputEventKind_Button: |
||||||
|
if (event.button.state) { |
||||||
|
return SceneEvent_OpenChild(NewScene_Car(), 0); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
return SceneEvent_Repaint(); |
||||||
|
} |
||||||
|
|
||||||
|
static struct SceneEvent Root_onTick(struct Scene *scene) { |
||||||
|
struct private *priv = scene->private; |
||||||
|
priv->timer_prescale += 1; |
||||||
|
|
||||||
|
if (priv->timer_prescale == 100) { |
||||||
|
priv->timer_prescale = 0; |
||||||
|
priv->timer_phase += 1; |
||||||
|
priv->timer_phase = priv->timer_phase & 0b11; // 0..3
|
||||||
|
|
||||||
|
return SceneEvent_Repaint(); |
||||||
|
} |
||||||
|
|
||||||
|
return SceneEvent_None(); |
||||||
|
} |
||||||
|
|
||||||
|
static void Root_paint(struct Scene *scene) |
||||||
|
{ |
||||||
|
struct private *priv = scene->private; |
||||||
|
|
||||||
|
LCD_clearDisplay(0); |
||||||
|
const char *header = "???"; |
||||||
|
switch (priv->timer_phase) { |
||||||
|
case 0: |
||||||
|
header = "ICE"; |
||||||
|
break; |
||||||
|
case 1: |
||||||
|
header = " COLD"; |
||||||
|
break; |
||||||
|
case 2: |
||||||
|
header = "COCA"; |
||||||
|
break; |
||||||
|
case 3: |
||||||
|
header = " COLA"; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
LCD_setStr(header, 20, 3, 1); |
||||||
|
|
||||||
|
LCD_setRect(0, 15, 83, 35, 1, 1); |
||||||
|
char buf[10]; |
||||||
|
sprintf(buf, "%3d", priv->pos); |
||||||
|
LCD_setStr(buf, 2, 17, 0); |
||||||
|
sprintf(buf, "%.0f C", analog_read()); |
||||||
|
LCD_setStr(buf, 2, 26, 0); |
||||||
|
LCD_updateDisplay(); |
||||||
|
} |
||||||
|
|
||||||
|
struct Scene *NewScene_Root(void) { |
||||||
|
struct Scene *scene = SCENE_SAFE_ALLOC(struct private); |
||||||
|
|
||||||
|
scene->onInput = Root_onInput; |
||||||
|
scene->paint = Root_paint; |
||||||
|
scene->onTick = Root_onTick; |
||||||
|
return scene; |
||||||
|
} |
@ -1,118 +0,0 @@ |
|||||||
/**
|
|
||||||
* Scene struct |
|
||||||
*
|
|
||||||
* Created on 2020/01/05. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef LIQUID_SCENE_TYPE_H |
|
||||||
#define LIQUID_SCENE_TYPE_H |
|
||||||
|
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
struct Scene; |
|
||||||
struct InputEvent; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Scene::onInput fp type - handle user input |
|
||||||
* |
|
||||||
* @param scene - self |
|
||||||
* @param event - the input event |
|
||||||
* @return follow-up scene event, can be SceneEvent_None() if not used. |
|
||||||
*/ |
|
||||||
typedef struct SceneEvent (*Scene_onInput_t)(struct Scene *scene, struct InputEvent event); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Scene::onChildReturn fp type |
|
||||||
* |
|
||||||
* @param scene - self |
|
||||||
* @param tag - child's tag |
|
||||||
* @param status - status code returned from the child |
|
||||||
* @param data - data returned from the child, must be heap-allocated if not NULL. |
|
||||||
* @return follow-up scene event, can be SceneEvent_None() if not used. |
|
||||||
*/ |
|
||||||
typedef struct SceneEvent (*Scene_onChildReturn_t)(struct Scene *scene, uint32_t tag, int32_t status, void *data); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Scene::onTick fp type |
|
||||||
* |
|
||||||
* @param scene - self |
|
||||||
* @return follow-up scene event, can be SceneEvent_None() if not used. |
|
||||||
*/ |
|
||||||
typedef struct SceneEvent (*Scene_onTick_t)(struct Scene *scene, uint32_t millis); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Scene::init fp type |
|
||||||
* |
|
||||||
* @param scene - self |
|
||||||
* @return follow-up scene event, can be SceneEvent_None() if not used. |
|
||||||
*/ |
|
||||||
typedef struct SceneEvent (*Scene_init_t)(struct Scene *scene); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Scene::paint fp type |
|
||||||
* |
|
||||||
* @param scene - self |
|
||||||
*/ |
|
||||||
typedef void (*Scene_paint_t)(struct Scene *scene); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Scene::deinit fp type. |
|
||||||
* Release internally allocated resources. |
|
||||||
* This is called by the GUI engine when the scene is closed. |
|
||||||
* |
|
||||||
* @param scene - self |
|
||||||
*/ |
|
||||||
typedef void (*Scene_deinit_t)(struct Scene *scene); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Scene instance in the framework |
|
||||||
*/ |
|
||||||
struct Scene { |
|
||||||
/**
|
|
||||||
* Tag given to the scene by its parent to identify its close event. |
|
||||||
*/ |
|
||||||
uint32_t tag; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Init function, called once after the scene has been opened. |
|
||||||
* Nullable field. |
|
||||||
*/ |
|
||||||
Scene_init_t init; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle input event. |
|
||||||
* Can return a follow-up scene event, e.g. repaint request. |
|
||||||
* Nullable field. |
|
||||||
*/ |
|
||||||
Scene_onInput_t onInput; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Child scene closed, handle its return value and data, if any. |
|
||||||
* Can return a follow-up scene event. |
|
||||||
* In any case, the scene will be re-painted, if it stays open and on top. |
|
||||||
* Nullable field. |
|
||||||
*/ |
|
||||||
Scene_onChildReturn_t onChildReturn; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a periodic tick (10ms). |
|
||||||
* Can return a follow-up scene event, e.g. repaint request. |
|
||||||
* Nullable field. |
|
||||||
*/ |
|
||||||
Scene_onTick_t onTick; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw the scene to the LCD buffer. |
|
||||||
* DO NOT write to the display yet, it will be done by the GUI engine. |
|
||||||
* Nullable field. |
|
||||||
*/ |
|
||||||
Scene_paint_t paint; |
|
||||||
|
|
||||||
/**
|
|
||||||
* Release internally allocated resources, if any. Called on close. |
|
||||||
* Nullable field. |
|
||||||
*/ |
|
||||||
Scene_deinit_t deinit; |
|
||||||
}; |
|
||||||
|
|
||||||
#endif //LIQUID_SCENE_TYPE_H
|
|
@ -0,0 +1,13 @@ |
|||||||
|
/**
|
||||||
|
* TODO file description |
||||||
|
*
|
||||||
|
* Created on 2020/01/03. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef REFLOWER_SCENES_H |
||||||
|
#define REFLOWER_SCENES_H |
||||||
|
|
||||||
|
struct Scene *NewScene_Root(void); |
||||||
|
struct Scene *NewScene_Car(void); |
||||||
|
|
||||||
|
#endif //REFLOWER_SCENES_H
|
@ -0,0 +1,509 @@ |
|||||||
|
#include <driver/gpio.h> |
||||||
|
#include <arch/cc.h> |
||||||
|
#include <driver/spi_master.h> |
||||||
|
#include "nokia.h" |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
/* Pin definitions:
|
||||||
|
Most of these pins can be moved to any digital or analog pin. |
||||||
|
DN(MOSI)and SCLK should be left where they are (SPI pins). The |
||||||
|
LED (backlight) pin should remain on a PWM-capable pin. */ |
||||||
|
static const int scePin = 17; // SCE - Chip select, pin 3 on LCD.
|
||||||
|
static const int rstPin = 16; // RST - Reset, pin 4 on LCD.
|
||||||
|
static const int dcPin = 4; // DC - Data/Command, pin 5 on LCD.
|
||||||
|
static const int sdinPin = 15; // DN(MOSI) - Serial data, pin 6 on LCD.
|
||||||
|
static const int sclkPin = 13; // SCLK - Serial clock, pin 7 on LCD.
|
||||||
|
|
||||||
|
/* PCD8544-specific defines: */ |
||||||
|
#define LCD_COMMAND 0 |
||||||
|
#define LCD_DATA 1 |
||||||
|
|
||||||
|
#define DEFAULT_CONTRAST 48 |
||||||
|
|
||||||
|
static spi_device_handle_t hSPI; |
||||||
|
|
||||||
|
/* Font table:
|
||||||
|
This table contains the hex values that represent pixels for a |
||||||
|
font that is 5 pixels wide and 8 pixels high. Each byte in a row |
||||||
|
represents one, 8-pixel, vertical column of a character. 5 bytes |
||||||
|
per character. */ |
||||||
|
static const uint8_t ASCII[][5] = { |
||||||
|
// First 32 characters (0x00-0x19) are ignored. These are
|
||||||
|
// non-displayable, control characters.
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00}, // 0x20
|
||||||
|
{0x00, 0x00, 0x5f, 0x00, 0x00}, // 0x21 !
|
||||||
|
{0x00, 0x07, 0x00, 0x07, 0x00}, // 0x22 "
|
||||||
|
{0x14, 0x7f, 0x14, 0x7f, 0x14}, // 0x23 #
|
||||||
|
{0x24, 0x2a, 0x7f, 0x2a, 0x12}, // 0x24 $
|
||||||
|
{0x23, 0x13, 0x08, 0x64, 0x62}, // 0x25 %
|
||||||
|
{0x36, 0x49, 0x55, 0x22, 0x50}, // 0x26 &
|
||||||
|
{0x00, 0x05, 0x03, 0x00, 0x00}, // 0x27 '
|
||||||
|
{0x00, 0x1c, 0x22, 0x41, 0x00}, // 0x28 (
|
||||||
|
{0x00, 0x41, 0x22, 0x1c, 0x00}, // 0x29 )
|
||||||
|
{0x14, 0x08, 0x3e, 0x08, 0x14}, // 0x2a *
|
||||||
|
{0x08, 0x08, 0x3e, 0x08, 0x08}, // 0x2b +
|
||||||
|
{0x00, 0x50, 0x30, 0x00, 0x00}, // 0x2c ,
|
||||||
|
{0x08, 0x08, 0x08, 0x08, 0x08}, // 0x2d -
|
||||||
|
{0x00, 0x60, 0x60, 0x00, 0x00}, // 0x2e .
|
||||||
|
{0x20, 0x10, 0x08, 0x04, 0x02}, // 0x2f /
|
||||||
|
{0x3e, 0x51, 0x49, 0x45, 0x3e}, // 0x30 0
|
||||||
|
{0x00, 0x42, 0x7f, 0x40, 0x00}, // 0x31 1
|
||||||
|
{0x42, 0x61, 0x51, 0x49, 0x46}, // 0x32 2
|
||||||
|
{0x21, 0x41, 0x45, 0x4b, 0x31}, // 0x33 3
|
||||||
|
{0x18, 0x14, 0x12, 0x7f, 0x10}, // 0x34 4
|
||||||
|
{0x27, 0x45, 0x45, 0x45, 0x39}, // 0x35 5
|
||||||
|
{0x3c, 0x4a, 0x49, 0x49, 0x30}, // 0x36 6
|
||||||
|
{0x01, 0x71, 0x09, 0x05, 0x03}, // 0x37 7
|
||||||
|
{0x36, 0x49, 0x49, 0x49, 0x36}, // 0x38 8
|
||||||
|
{0x06, 0x49, 0x49, 0x29, 0x1e}, // 0x39 9
|
||||||
|
{0x00, 0x36, 0x36, 0x00, 0x00}, // 0x3a :
|
||||||
|
{0x00, 0x56, 0x36, 0x00, 0x00}, // 0x3b ;
|
||||||
|
{0x08, 0x14, 0x22, 0x41, 0x00}, // 0x3c <
|
||||||
|
{0x14, 0x14, 0x14, 0x14, 0x14}, // 0x3d =
|
||||||
|
{0x00, 0x41, 0x22, 0x14, 0x08}, // 0x3e >
|
||||||
|
{0x02, 0x01, 0x51, 0x09, 0x06}, // 0x3f ?
|
||||||
|
{0x32, 0x49, 0x79, 0x41, 0x3e}, // 0x40 @
|
||||||
|
{0x7e, 0x11, 0x11, 0x11, 0x7e}, // 0x41 A
|
||||||
|
{0x7f, 0x49, 0x49, 0x49, 0x36}, // 0x42 B
|
||||||
|
{0x3e, 0x41, 0x41, 0x41, 0x22}, // 0x43 C
|
||||||
|
{0x7f, 0x41, 0x41, 0x22, 0x1c}, // 0x44 D
|
||||||
|
{0x7f, 0x49, 0x49, 0x49, 0x41}, // 0x45 E
|
||||||
|
{0x7f, 0x09, 0x09, 0x09, 0x01}, // 0x46 F
|
||||||
|
{0x3e, 0x41, 0x49, 0x49, 0x7a}, // 0x47 G
|
||||||
|
{0x7f, 0x08, 0x08, 0x08, 0x7f}, // 0x48 H
|
||||||
|
{0x00, 0x41, 0x7f, 0x41, 0x00}, // 0x49 I
|
||||||
|
{0x20, 0x40, 0x41, 0x3f, 0x01}, // 0x4a J
|
||||||
|
{0x7f, 0x08, 0x14, 0x22, 0x41}, // 0x4b K
|
||||||
|
{0x7f, 0x40, 0x40, 0x40, 0x40}, // 0x4c L
|
||||||
|
{0x7f, 0x02, 0x0c, 0x02, 0x7f}, // 0x4d M
|
||||||
|
{0x7f, 0x04, 0x08, 0x10, 0x7f}, // 0x4e N
|
||||||
|
{0x3e, 0x41, 0x41, 0x41, 0x3e}, // 0x4f O
|
||||||
|
{0x7f, 0x09, 0x09, 0x09, 0x06}, // 0x50 P
|
||||||
|
{0x3e, 0x41, 0x51, 0x21, 0x5e}, // 0x51 Q
|
||||||
|
{0x7f, 0x09, 0x19, 0x29, 0x46}, // 0x52 R
|
||||||
|
{0x46, 0x49, 0x49, 0x49, 0x31}, // 0x53 S
|
||||||
|
{0x01, 0x01, 0x7f, 0x01, 0x01}, // 0x54 T
|
||||||
|
{0x3f, 0x40, 0x40, 0x40, 0x3f}, // 0x55 U
|
||||||
|
{0x1f, 0x20, 0x40, 0x20, 0x1f}, // 0x56 V
|
||||||
|
{0x3f, 0x40, 0x38, 0x40, 0x3f}, // 0x57 W
|
||||||
|
{0x63, 0x14, 0x08, 0x14, 0x63}, // 0x58 X
|
||||||
|
{0x07, 0x08, 0x70, 0x08, 0x07}, // 0x59 Y
|
||||||
|
{0x61, 0x51, 0x49, 0x45, 0x43}, // 0x5a Z
|
||||||
|
{0x00, 0x7f, 0x41, 0x41, 0x00}, // 0x5b [
|
||||||
|
{0x02, 0x04, 0x08, 0x10, 0x20}, // 0x5c \ (keep this to escape the backslash)
|
||||||
|
{0x00, 0x41, 0x41, 0x7f, 0x00}, // 0x5d ]
|
||||||
|
{0x04, 0x02, 0x01, 0x02, 0x04}, // 0x5e ^
|
||||||
|
{0x40, 0x40, 0x40, 0x40, 0x40}, // 0x5f _
|
||||||
|
{0x00, 0x01, 0x02, 0x04, 0x00}, // 0x60 `
|
||||||
|
{0x20, 0x54, 0x54, 0x54, 0x78}, // 0x61 a
|
||||||
|
{0x7f, 0x48, 0x44, 0x44, 0x38}, // 0x62 b
|
||||||
|
{0x38, 0x44, 0x44, 0x44, 0x20}, // 0x63 c
|
||||||
|
{0x38, 0x44, 0x44, 0x48, 0x7f}, // 0x64 d
|
||||||
|
{0x38, 0x54, 0x54, 0x54, 0x18}, // 0x65 e
|
||||||
|
{0x08, 0x7e, 0x09, 0x01, 0x02}, // 0x66 f
|
||||||
|
{0x0c, 0x52, 0x52, 0x52, 0x3e}, // 0x67 g
|
||||||
|
{0x7f, 0x08, 0x04, 0x04, 0x78}, // 0x68 h
|
||||||
|
{0x00, 0x44, 0x7d, 0x40, 0x00}, // 0x69 i
|
||||||
|
{0x20, 0x40, 0x44, 0x3d, 0x00}, // 0x6a j
|
||||||
|
{0x7f, 0x10, 0x28, 0x44, 0x00}, // 0x6b k
|
||||||
|
{0x00, 0x41, 0x7f, 0x40, 0x00}, // 0x6c l
|
||||||
|
{0x7c, 0x04, 0x18, 0x04, 0x78}, // 0x6d m
|
||||||
|
{0x7c, 0x08, 0x04, 0x04, 0x78}, // 0x6e n
|
||||||
|
{0x38, 0x44, 0x44, 0x44, 0x38}, // 0x6f o
|
||||||
|
{0x7c, 0x14, 0x14, 0x14, 0x08}, // 0x70 p
|
||||||
|
{0x08, 0x14, 0x14, 0x18, 0x7c}, // 0x71 q
|
||||||
|
{0x7c, 0x08, 0x04, 0x04, 0x08}, // 0x72 r
|
||||||
|
{0x48, 0x54, 0x54, 0x54, 0x20}, // 0x73 s
|
||||||
|
{0x04, 0x3f, 0x44, 0x40, 0x20}, // 0x74 t
|
||||||
|
{0x3c, 0x40, 0x40, 0x20, 0x7c}, // 0x75 u
|
||||||
|
{0x1c, 0x20, 0x40, 0x20, 0x1c}, // 0x76 v
|
||||||
|
{0x3c, 0x40, 0x30, 0x40, 0x3c}, // 0x77 w
|
||||||
|
{0x44, 0x28, 0x10, 0x28, 0x44}, // 0x78 x
|
||||||
|
{0x0c, 0x50, 0x50, 0x50, 0x3c}, // 0x79 y
|
||||||
|
{0x44, 0x64, 0x54, 0x4c, 0x44}, // 0x7a z
|
||||||
|
{0x00, 0x08, 0x36, 0x41, 0x00}, // 0x7b {
|
||||||
|
{0x00, 0x00, 0x7f, 0x00, 0x00}, // 0x7c |
|
||||||
|
{0x00, 0x41, 0x36, 0x08, 0x00}, // 0x7d }
|
||||||
|
{0x10, 0x08, 0x08, 0x10, 0x08}, // 0x7e ~
|
||||||
|
{0x78, 0x46, 0x41, 0x46, 0x78}, // 0x7f DEL
|
||||||
|
}; |
||||||
|
|
||||||
|
/* The displayMap variable stores a buffer representation of the
|
||||||
|
pixels on our display. There are 504 total bits in this array, |
||||||
|
same as how many pixels there are on a 84 x 48 display. |
||||||
|
|
||||||
|
Each byte in this array covers a 8-pixel vertical block on the |
||||||
|
display. Each successive byte covers the next 8-pixel column over |
||||||
|
until you reach the right-edge of the display and step down 8 rows. |
||||||
|
|
||||||
|
To update the display, we first have to write to this array, then |
||||||
|
call the updateDisplay() function, which sends this whole array |
||||||
|
to the PCD8544. |
||||||
|
|
||||||
|
Because the PCD8544 won't let us write individual pixels at a |
||||||
|
time, this is how we can make targeted changes to the display. */ |
||||||
|
static uint8_t displayMap[LCD_WIDTH * LCD_HEIGHT / 8]; |
||||||
|
|
||||||
|
// There are two memory banks in the LCD, data/RAM and commands.
|
||||||
|
// This function sets the DC pin high or low depending, and then
|
||||||
|
// sends the data byte
|
||||||
|
static void LCD_SendBytes(bool data_or_command, const uint8_t *data, size_t len) |
||||||
|
{ |
||||||
|
esp_err_t ret; |
||||||
|
spi_transaction_t t; |
||||||
|
if (len == 0) return; //no need to send anything
|
||||||
|
memset(&t, 0, sizeof(t)); //Zero out the transaction
|
||||||
|
t.length = len * 8; //Len is in bytes, transaction length is in bits.
|
||||||
|
t.tx_buffer = data; //Data
|
||||||
|
t.user = (void *) data_or_command; //D/C
|
||||||
|
ret = spi_device_polling_transmit(hSPI, &t); //Transmit!
|
||||||
|
assert(ret == ESP_OK); //Should have had no issues.
|
||||||
|
} |
||||||
|
|
||||||
|
// There are two memory banks in the LCD, data/RAM and commands.
|
||||||
|
// This function sets the DC pin high or low depending, and then
|
||||||
|
// sends the data byte
|
||||||
|
static void LCD_SendByte(bool data_or_command, uint8_t data) |
||||||
|
{ |
||||||
|
esp_err_t ret; |
||||||
|
spi_transaction_t t; |
||||||
|
memset(&t, 0, sizeof(t)); //Zero out the transaction
|
||||||
|
t.length = 8; // transaction length is in bits.
|
||||||
|
t.tx_data[0] = data; //Data
|
||||||
|
t.flags = SPI_TRANS_USE_TXDATA; |
||||||
|
t.user = (void *) data_or_command; //D/C
|
||||||
|
ret = spi_device_polling_transmit(hSPI, &t); //Transmit!
|
||||||
|
assert(ret == ESP_OK); //Should have had no issues.
|
||||||
|
} |
||||||
|
|
||||||
|
// This function sets a pixel on displayMap to your preferred
|
||||||
|
// color. 1=Black, 0= white.
|
||||||
|
void LCD_setPixel(int x, int y, bool bw) |
||||||
|
{ |
||||||
|
// First, double check that the coordinate is in range.
|
||||||
|
if ((x >= 0) && (x < LCD_WIDTH) && (y >= 0) && (y < LCD_HEIGHT)) { |
||||||
|
uint8_t shift = y % 8; |
||||||
|
|
||||||
|
if (bw) // If black, set the bit.
|
||||||
|
displayMap[x + (y / 8) * LCD_WIDTH] |= 1 << shift; |
||||||
|
else // If white clear the bit.
|
||||||
|
displayMap[x + (y / 8) * LCD_WIDTH] &= ~(1 << shift); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// setLine draws a line from x0,y0 to x1,y1 with the set color.
|
||||||
|
// This function was grabbed from the SparkFun ColorLCDShield
|
||||||
|
// library.
|
||||||
|
void LCD_setLine(int x0, int y0, int x1, int y1, bool bw) |
||||||
|
{ |
||||||
|
int dy = y1 - y0; // Difference between y0 and y1
|
||||||
|
int dx = x1 - x0; // Difference between x0 and x1
|
||||||
|
int stepx, stepy; |
||||||
|
|
||||||
|
if (dy < 0) { |
||||||
|
dy = -dy; |
||||||
|
stepy = -1; |
||||||
|
} |
||||||
|
else |
||||||
|
stepy = 1; |
||||||
|
|
||||||
|
if (dx < 0) { |
||||||
|
dx = -dx; |
||||||
|
stepx = -1; |
||||||
|
} |
||||||
|
else |
||||||
|
stepx = 1; |
||||||
|
|
||||||
|
dy <<= 1; // dy is now 2*dy
|
||||||
|
dx <<= 1; // dx is now 2*dx
|
||||||
|
LCD_setPixel(x0, y0, bw); // Draw the first pixel.
|
||||||
|
|
||||||
|
if (dx > dy) { |
||||||
|
int fraction = dy - (dx >> 1); |
||||||
|
while (x0 != x1) { |
||||||
|
if (fraction >= 0) { |
||||||
|
y0 += stepy; |
||||||
|
fraction -= dx; |
||||||
|
} |
||||||
|
x0 += stepx; |
||||||
|
fraction += dy; |
||||||
|
LCD_setPixel(x0, y0, bw); |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
int fraction = dx - (dy >> 1); |
||||||
|
while (y0 != y1) { |
||||||
|
if (fraction >= 0) { |
||||||
|
x0 += stepx; |
||||||
|
fraction -= dy; |
||||||
|
} |
||||||
|
y0 += stepy; |
||||||
|
fraction += dx; |
||||||
|
LCD_setPixel(x0, y0, bw); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// setRect will draw a rectangle from x0,y0 top-left corner to
|
||||||
|
// a x1,y1 bottom-right corner. Can be filled with the fill
|
||||||
|
// parameter, and colored with bw.
|
||||||
|
// This function was grabbed from the SparkFun ColorLCDShield
|
||||||
|
// library.
|
||||||
|
void LCD_setRect(int x0, int y0, int x1, int y1, bool fill, bool bw) |
||||||
|
{ |
||||||
|
// check if the rectangle is to be filled
|
||||||
|
if (fill == 1) { |
||||||
|
int xDiff; |
||||||
|
|
||||||
|
if (x0 > x1) |
||||||
|
xDiff = x0 - x1; //Find the difference between the x vars
|
||||||
|
else |
||||||
|
xDiff = x1 - x0; |
||||||
|
|
||||||
|
while (xDiff > 0) { |
||||||
|
LCD_setLine(x0, y0, x0, y1, bw); |
||||||
|
|
||||||
|
if (x0 > x1) |
||||||
|
x0--; |
||||||
|
else |
||||||
|
x0++; |
||||||
|
|
||||||
|
xDiff--; |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
// best way to draw an unfilled rectangle is to draw four lines
|
||||||
|
LCD_setLine(x0, y0, x1, y0, bw); |
||||||
|
LCD_setLine(x0, y1, x1, y1, bw); |
||||||
|
LCD_setLine(x0, y0, x0, y1, bw); |
||||||
|
LCD_setLine(x1, y0, x1, y1, bw); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// setCircle draws a circle centered around x0,y0 with a defined
|
||||||
|
// radius. The circle can be black or white. And have a line
|
||||||
|
// thickness ranging from 1 to the radius of the circle.
|
||||||
|
// This function was grabbed from the SparkFun ColorLCDShield
|
||||||
|
// library.
|
||||||
|
void LCD_setCircle(int x0, int y0, int radius, bool bw, int lineThickness) |
||||||
|
{ |
||||||
|
for (int r = 0; r < lineThickness; r++) { |
||||||
|
int f = 1 - radius; |
||||||
|
int ddF_x = 0; |
||||||
|
int ddF_y = -2 * radius; |
||||||
|
int x = 0; |
||||||
|
int y = radius; |
||||||
|
|
||||||
|
LCD_setPixel(x0, y0 + radius, bw); |
||||||
|
LCD_setPixel(x0, y0 - radius, bw); |
||||||
|
LCD_setPixel(x0 + radius, y0, bw); |
||||||
|
LCD_setPixel(x0 - radius, y0, bw); |
||||||
|
|
||||||
|
while (x < y) { |
||||||
|
if (f >= 0) { |
||||||
|
y--; |
||||||
|
ddF_y += 2; |
||||||
|
f += ddF_y; |
||||||
|
} |
||||||
|
x++; |
||||||
|
ddF_x += 2; |
||||||
|
f += ddF_x + 1; |
||||||
|
|
||||||
|
LCD_setPixel(x0 + x, y0 + y, bw); |
||||||
|
LCD_setPixel(x0 - x, y0 + y, bw); |
||||||
|
LCD_setPixel(x0 + x, y0 - y, bw); |
||||||
|
LCD_setPixel(x0 - x, y0 - y, bw); |
||||||
|
LCD_setPixel(x0 + y, y0 + x, bw); |
||||||
|
LCD_setPixel(x0 - y, y0 + x, bw); |
||||||
|
LCD_setPixel(x0 + y, y0 - x, bw); |
||||||
|
LCD_setPixel(x0 - y, y0 - x, bw); |
||||||
|
} |
||||||
|
radius--; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// This function will draw a char (defined in the ASCII table
|
||||||
|
// near the beginning of this sketch) at a defined x and y).
|
||||||
|
// The color can be either black (1) or white (0).
|
||||||
|
void LCD_setChar(char character, int x, int y, bool bw) |
||||||
|
{ |
||||||
|
uint8_t column; // temp byte to store character's column bitmap
|
||||||
|
for (int i = 0; i < 5; i++) // 5 columns (x) per character
|
||||||
|
{ |
||||||
|
column = ASCII[character - 0x20][i]; |
||||||
|
for (int j = 0; j < 8; j++) // 8 rows (y) per character
|
||||||
|
{ |
||||||
|
if (column & (0x01 << j)) // test bits to set pixels
|
||||||
|
LCD_setPixel(x + i, y + j, bw); |
||||||
|
else |
||||||
|
LCD_setPixel(x + i, y + j, !bw); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// setStr draws a string of characters, calling setChar with
|
||||||
|
// progressive coordinates until it's done.
|
||||||
|
// This function was grabbed from the SparkFun ColorLCDShield
|
||||||
|
// library.
|
||||||
|
void LCD_setStr(const char *dString, int x, int y, bool bw) |
||||||
|
{ |
||||||
|
while (*dString != 0x00) // loop until null terminator
|
||||||
|
{ |
||||||
|
LCD_setChar(*dString++, x, y, bw); |
||||||
|
x += 5; |
||||||
|
for (int i = y; i < y + 8; i++) { |
||||||
|
LCD_setPixel(x, i, !bw); |
||||||
|
} |
||||||
|
x++; |
||||||
|
if (x > (LCD_WIDTH - 5)) // Enables wrap around
|
||||||
|
{ |
||||||
|
x = 0; |
||||||
|
y += 8; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// This function will draw an array over the screen. (For now) the
|
||||||
|
// array must be the same size as the screen, covering the entirety
|
||||||
|
// of the display.
|
||||||
|
// Also, the array must reside in FLASH and declared with PROGMEM.
|
||||||
|
void LCD_setBitmap(const char *bitArray) |
||||||
|
{ |
||||||
|
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) { |
||||||
|
char c = bitArray[i]; |
||||||
|
displayMap[i] = c; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// This function clears the entire display either white (0) or
|
||||||
|
// black (1).
|
||||||
|
// The screen won't actually clear until you call updateDisplay()!
|
||||||
|
void LCD_clearDisplay(bool bw) |
||||||
|
{ |
||||||
|
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) { |
||||||
|
if (bw) |
||||||
|
displayMap[i] = 0xFF; |
||||||
|
else |
||||||
|
displayMap[i] = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Helpful function to directly command the LCD to go to a
|
||||||
|
// specific x,y coordinate.
|
||||||
|
static void gotoXY(int x, int y) |
||||||
|
{ |
||||||
|
const uint8_t cmd[2] = { |
||||||
|
0x80 | x, |
||||||
|
0x40 | y, |
||||||
|
}; |
||||||
|
LCD_SendBytes(LCD_COMMAND, cmd, 2); |
||||||
|
} |
||||||
|
|
||||||
|
// This will actually draw on the display, whatever is currently
|
||||||
|
// in the displayMap array.
|
||||||
|
void LCD_updateDisplay() |
||||||
|
{ |
||||||
|
spi_device_acquire_bus(hSPI, portMAX_DELAY); |
||||||
|
gotoXY(0, 0); |
||||||
|
LCD_SendBytes(LCD_DATA, &displayMap[0], LCD_WIDTH * (LCD_HEIGHT / 8)); |
||||||
|
spi_device_release_bus(hSPI); |
||||||
|
} |
||||||
|
|
||||||
|
// Set contrast can set the LCD Vop to a value between 0 and 127.
|
||||||
|
// 40-60 is usually a pretty good range.
|
||||||
|
void LCD_setContrast(uint8_t contrast) |
||||||
|
{ |
||||||
|
spi_device_acquire_bus(hSPI, portMAX_DELAY); |
||||||
|
const uint8_t cmd[3] = { |
||||||
|
0x21, //Tell LCD that extended commands follow
|
||||||
|
0x80 | contrast,//Set LCD Vop (Contrast): Try 0xB1(good @ 3.3V) or 0xBF if your display is too dark
|
||||||
|
0x20,//Set display mode
|
||||||
|
}; |
||||||
|
LCD_SendBytes(LCD_COMMAND, cmd, 3); |
||||||
|
spi_device_release_bus(hSPI); |
||||||
|
} |
||||||
|
|
||||||
|
void LCD_invertDisplay(bool invert) |
||||||
|
{ |
||||||
|
LCD_SendByte(LCD_COMMAND, 0x0C | invert); |
||||||
|
} |
||||||
|
|
||||||
|
void LCD_invertDisplayData() |
||||||
|
{ |
||||||
|
/* Indirect, swap bits in displayMap option: */ |
||||||
|
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) { |
||||||
|
displayMap[i] ^= 0xFF; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//This function is called (in irq context!) just before a transmission starts. It will
|
||||||
|
//set the D/C line to the value indicated in the user field.
|
||||||
|
void lcd_spi_pre_transfer_callback(spi_transaction_t *t) |
||||||
|
{ |
||||||
|
int dc = (int) t->user; |
||||||
|
gpio_set_level(dcPin, dc); |
||||||
|
} |
||||||
|
|
||||||
|
//This sends the magical commands to the PCD8544
|
||||||
|
void LCD_setup(void) |
||||||
|
{ |
||||||
|
// clear the display array
|
||||||
|
LCD_clearDisplay(0); |
||||||
|
|
||||||
|
gpio_config_t output = { |
||||||
|
.pin_bit_mask = (1<<scePin) | (1<<rstPin) | (1<<dcPin) | (1<<sdinPin) | (1<<sclkPin), |
||||||
|
.mode = GPIO_MODE_OUTPUT |
||||||
|
}; |
||||||
|
gpio_config(&output); |
||||||
|
|
||||||
|
esp_err_t ret; |
||||||
|
spi_bus_config_t buscfg = { |
||||||
|
.miso_io_num=-1, |
||||||
|
.mosi_io_num=sdinPin, |
||||||
|
.sclk_io_num=sclkPin, |
||||||
|
.quadwp_io_num=-1, |
||||||
|
.quadhd_io_num=-1, |
||||||
|
.max_transfer_sz=LCD_WIDTH * LCD_HEIGHT, // ??? this doesnt seem to do anything in the driver
|
||||||
|
}; |
||||||
|
spi_device_interface_config_t devcfg = { |
||||||
|
.clock_speed_hz=4 * 1000 * 1000, // Hz
|
||||||
|
// enable signal lead/lag
|
||||||
|
.cs_ena_pretrans = 0, |
||||||
|
.cs_ena_posttrans = 0, |
||||||
|
.mode=0, //SPI mode 0
|
||||||
|
.spics_io_num=scePin, //CS pin
|
||||||
|
.queue_size=1, // we use the polling mode, so this does not matter
|
||||||
|
.pre_cb=lcd_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line
|
||||||
|
}; |
||||||
|
|
||||||
|
//Initialize the SPI bus
|
||||||
|
ret = spi_bus_initialize(HSPI_HOST, &buscfg, 1); |
||||||
|
ESP_ERROR_CHECK(ret); |
||||||
|
//Attach the LCD to the SPI bus
|
||||||
|
ret = spi_bus_add_device(HSPI_HOST, &devcfg, &hSPI); |
||||||
|
ESP_ERROR_CHECK(ret); |
||||||
|
|
||||||
|
spi_device_acquire_bus(hSPI, portMAX_DELAY); |
||||||
|
{ |
||||||
|
//Reset the LCD to a known state
|
||||||
|
gpio_set_level(rstPin, 0); |
||||||
|
gpio_set_level(rstPin, 1); |
||||||
|
|
||||||
|
const uint8_t magic[6] = { |
||||||
|
0x21, // Tell LCD extended commands follow
|
||||||
|
0x80 + DEFAULT_CONTRAST, // Set LCD Vop (Contrast)
|
||||||
|
0x04, // Set Temp coefficent
|
||||||
|
0x14, // LCD bias mode 1:48 (try 0x13)
|
||||||
|
//We must send 0x20 before modifying the display control mode
|
||||||
|
0x20, // Clear extended option
|
||||||
|
0x0C, // Set display control, normal mode.
|
||||||
|
}; |
||||||
|
LCD_SendBytes(LCD_COMMAND, &magic[0], 6); |
||||||
|
} |
||||||
|
spi_device_release_bus(hSPI); |
||||||
|
|
||||||
|
// show the blank screen (display is in indefined state after boot)
|
||||||
|
LCD_updateDisplay(); |
||||||
|
} |
||||||
|
|
||||||
|
|