Even more files

This commit is contained in:
2023-09-23 21:40:13 +02:00
parent 45ce330daf
commit c303359b27
23 changed files with 2094 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

1398
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

5
nvs.csv Normal file
View File

@@ -0,0 +1,5 @@
key,type,encoding,value
data,namespace,,data
ssid,data,hex2bin,776c616e41
password,data,hex2bin,537570657253696c656e6365
authmethod,data,hex2bin,57504132506572736f6e616c
1 key type encoding value
2 data namespace data
3 ssid data hex2bin 776c616e41
4 password data hex2bin 537570657253696c656e6365
5 authmethod data hex2bin 57504132506572736f6e616c

5
partition.csv Normal file
View File

@@ -0,0 +1,5 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,

4
rerun.sh Normal file
View File

@@ -0,0 +1,4 @@
cargo clean --release -p esp-idf-svc
cargo build --release --example nvs-example
espflash --partition-table partition.csv /dev/ttyUSB0 target/xtensa-esp32-espidf/release/examples/nvs-example
parttool.py --partition-table-file partition.csv --port /dev/ttyUSB0 write_partition --partition-name=nvs --input "nvs.bin"

3
run.sh Normal file
View File

@@ -0,0 +1,3 @@
espflash --partition-table partition.csv /dev/ttyUSB0 target/xtensa-esp32-espidf/release/rusty-espresso
parttool.py --partition-table-file partition.csv --port /dev/ttyUSB0 write_partition --partition-name=nvs --input "nvs.bin"
espmonitor --speed 115200 /dev/ttyUSB0 --bin target/xtensa-esp32-espidf/release/rusty-espresso

5
src/api_pid_get.json Normal file
View File

@@ -0,0 +1,5 @@
{
"target" : 96.2,
"current": 92.4,
"enabled": false
}

5
src/api_pid_post.json Normal file
View File

@@ -0,0 +1,5 @@
{
"target": 96.2,
"enabled": false
}

73
src/backup_manager.rs Normal file
View File

@@ -0,0 +1,73 @@
use std::cmp;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;
use std::thread;
use std::vec::Vec;
use esp_idf_svc::nvs::EspDefaultNvs;
use esp_idf_svc::nvs_storage::EspNvsStorage;
use embedded_svc::storage::Storage;
pub type BackupManagerTx = Sender<BackupManagerMessage>;
pub struct BackupManager {
pub tx: BackupManagerTx,
join_handle: thread::JoinHandle<()>,
}
struct BackupManagerInner {
}
pub enum BackupManagerMessage {
Save(String, Vec<u8>, Sender<bool>),
Load(String, Sender<Option<Vec<u8>>>),
GetNvs(Sender<Arc<EspDefaultNvs>>),
}
impl BackupManager {
pub fn new() -> Self {
let (tx, rx) = channel();
let mut inner = BackupManagerInner {
};
let jh = thread::spawn(move || inner.serve(rx));
BackupManager {
tx: tx,
join_handle: jh,
}
}
pub fn get_sender(&self) -> BackupManagerTx {
self.tx.clone()
}
}
impl BackupManagerInner {
fn serve(&mut self, rx: Receiver<BackupManagerMessage>) {
let mut nvs = Arc::new(EspDefaultNvs::new().unwrap());
let mut nvs_store = EspNvsStorage::new_default(Arc::clone(&nvs), "data", true).unwrap();
loop {
match rx.recv() {
Err(x) => {
// some error reading from the channel, bail
break;
}
Ok(msg) => match msg {
BackupManagerMessage::Save(key, value, resp) => {
let result = nvs_store.put_raw(key, value).unwrap();
resp.send(result).unwrap_or(());
},
BackupManagerMessage::Load(key, resp) => {
let value = nvs_store.get_raw(key).unwrap();
resp.send(value).unwrap_or(());
},
BackupManagerMessage::GetNvs(resp) => {
resp.send(Arc::clone(&nvs)).unwrap_or(())
},
}
} // match rx.recv()
} // loop
() // exit
}
}

24
src/index.html Normal file
View File

@@ -0,0 +1,24 @@
<html>
<head>
<style>
body {
font-family: serif;
text-align: center;
color: #321C0B;
background-color: #D5E4F6;
}
h1 {
color: #B15C1B;
}
h4 {
color: #2969B2;
}
</style>
<link rel="icon" type="image/png" href="favicon.png"></link>
<title>Rusty Espresso Controller</title>
</head>
<body>
<h1>Rusty Espresso Controller</h1>
<h4>&copy; 2021 Jakob Dalsgaard</h4>
<p>Check the official homepage for more information: <a href="https://rusty-espresso.dalsgaard.net/">https://rusty-espresso.dalsgaard.net/</a>.</p>
</body>

1
src/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod s11n;

209
src/pid_manager.rs Normal file
View File

@@ -0,0 +1,209 @@
use std::cmp;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;
use std::thread;
use std::vec::Vec;
use std::collections::HashMap;
use quick_protobuf::{Writer, MessageWrite};
use std::borrow::Cow;
use std::convert::Into;
use crate::s11n::converter;
use crate::s11n::pb::pid1;
use crate::backup_manager::BackupManagerTx;
pub type PidManagerTx = Sender<PidManagerMessage>;
pub struct PidManager {
pub tx: PidManagerTx,
join_handle: thread::JoinHandle<()>,
}
struct PidManagerInner {
library: PidDataLibrary,
current_pid_data: Option<PidData>,
nvs_store: BackupManagerTx,
}
#[derive(Clone)]
struct PidData {
p: f64,
i: f64,
d: f64,
}
#[derive(Clone)]
struct PidDataLibraryItem {
item_name: String,
data: PidData,
}
#[derive(Clone)]
struct PidDataLibrary {
items: HashMap<u16, PidDataLibraryItem>,
}
pub enum PidManagerMessage {
CreatePidData(String, Sender<Option<u16>>),
DeletePidData(u16),
UpdatePidDataName(u16, String),
UpdatePidData(u16, PidData),
SetActivePidData(u16),
GetLibraryItemsList(Sender<Vec<(u16, String)>>),
GetLibraryItem(u16, Sender<Option<PidDataLibraryItem>>),
}
impl PidManager {
pub fn new(nvs: &BackupManagerTx) -> Self {
let (tx, rx) = channel();
let mut inner = PidManagerInner {
library: PidDataLibrary {
items: HashMap::new(),
},
current_pid_data: None,
nvs_store: nvs.clone(),
};
let jh = thread::spawn(move || inner.serve(rx));
PidManager {
tx: tx,
join_handle: jh,
}
}
pub fn get_sender(&self) -> PidManagerTx {
self.tx.clone()
}
}
impl PidManagerInner {
fn serve(&mut self, rx: Receiver<PidManagerMessage>) {
loop {
match rx.recv() {
Err(x) => {
// some error reading from the channel, bail
break;
}
Ok(msg) => match msg {
PidManagerMessage::CreatePidData(name, resp) => {
// we only allow 16 sets of piddata
if self.library.items.len() >= 16 {
resp.send(None).unwrap_or(());
} else {
// get current max item_id and increment by 1
let new_id = self.library.items.keys().max().unwrap_or(&0) + 1;
let item = PidDataLibraryItem {
item_name: name.clone(),
data: PidData {
p: 0.0f64,
i: 0.0f64,
d: 0.0f64,
},
};
self.library.items.insert(new_id.clone(), item);
resp.send(Some(new_id.clone())).unwrap_or(());
}
},
PidManagerMessage::DeletePidData(id) => {
self.library.items.remove(&id);
},
PidManagerMessage::UpdatePidDataName(id, name) => {
if let Some(item) = self.library.items.get_mut(&id) {
item.item_name = name;
}
},
PidManagerMessage::UpdatePidData(id, data) => {
if let Some(library_item) = self.library.items.get_mut(&id) {
library_item.data = data;
}
},
PidManagerMessage::SetActivePidData(id) => {
if let Some(pid_data) = self.library.items.get(&id) {
self.current_pid_data = Some(pid_data.data.clone());
}
},
PidManagerMessage::GetLibraryItemsList(sender) => {
let res = self
.library
.items
.iter()
.map(|(k, v)| (*k, v.item_name.clone()))
.collect();
sender.send(res).unwrap_or(());
},
PidManagerMessage::GetLibraryItem(id, sender) => {
if let Some(item) = self.library.items.get(&id) {
sender.send(Some(item.clone())).unwrap_or(());
} else {
sender.send(None).unwrap_or(());
}
},
}, // end of enum match
} // end of Err/Some match
} // end of loop
() // exit
}
fn backup(&mut self) {
let v: pid1::PidDataLibrary = self.library.to_owned().into();
let mut vb = Vec::new();
vb.extend([0x00, 0x01]);
vb.extend(converter::serialise(v));
// sender.send(vb);
}
fn restore(&mut self, bytesv: &Vec<u8>) {
if let converter::Input::Pid1(pb) = converter::parse(&bytesv) {
self.library = PidDataLibrary::from(pb);
} else {
// do nothing
}
}
}
impl<'a> From<pid1::PidData<'a>> for PidDataLibraryItem {
fn from(v: pid1::PidData<'a>) -> Self {
PidDataLibraryItem {
item_name: v.name.to_string(),
data: PidData {
p: v.p,
i: v.i,
d: v.d,
}
}
}
}
impl<'a> From<pid1::PidDataLibrary<'a>> for PidDataLibrary {
fn from(v: pid1::PidDataLibrary<'a>) -> Self {
PidDataLibrary {
items: v.items.iter().fold(HashMap::new(), |mut acc, (k, v)| { acc.insert(*k as u16, PidDataLibraryItem::from(v.to_owned())); acc }),
}
}
}
impl<'a> Into<pid1::PidData<'a>> for PidDataLibraryItem {
fn into(self) -> pid1::PidData<'a> {
pid1::PidData {
name: Cow::Owned(self.item_name),
p: self.data.p,
i: self.data.i,
d: self.data.d,
created: 0,
modified: 0,
}
}
}
impl<'a> Into<pid1::PidDataLibrary<'a>> for PidDataLibrary {
fn into(self) -> pid1::PidDataLibrary<'a> {
pid1::PidDataLibrary {
items: self.items.iter().fold(HashMap::new(), |mut acc, (k, v)| { acc.insert(*k as u32, v.to_owned().into()); acc }),
}
}
}

43
src/s11n/converter.rs Normal file
View File

@@ -0,0 +1,43 @@
use crate::s11n::pb;
use quick_protobuf::{deserialize_from_slice, serialize_into_vec, Reader, Writer, MessageWrite};
pub enum Input<'a> {
Wifi1(pb::wifi1::WifiData<'a>),
Pid1(pb::pid1::PidDataLibrary<'a>),
Empty,
}
pub fn parse<'a>(b: &'a Vec<u8>) -> Input<'a> {
if b.len() < 2 {
return Input::Empty;
}
let header: u16 = (b[0] as u16) << 8 | (b[0] as u16);
match header {
1 => { // this is a PidLibrary version 1
if let Ok(v) = deserialize_from_slice(&b[2..]) {
Input::Pid1(v)
} else {
Input::Empty
}
},
2 => { // this is a WifiLibrary version 1
if let Ok(v) = deserialize_from_slice(&b[2..]) {
Input::Wifi1(v)
}
else {
Input::Empty
}
},
_ => { // everything else...
Input::Empty
}
}
}
pub fn serialise<T: MessageWrite> (p: T) -> Vec<u8> {
serialize_into_vec(&p).unwrap()
}

3
src/s11n/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod converter;
// Automatically generated mod.rs
pub mod pb;

3
src/s11n/pb/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
// Automatically generated mod.rs
pub mod pid1;
pub mod wifi1;

103
src/s11n/pb/pid1.rs Normal file
View File

@@ -0,0 +1,103 @@
// Automatically generated rust module for 'pid1.proto' file
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(unused_imports)]
#![allow(unknown_lints)]
#![allow(clippy::all)]
#![cfg_attr(rustfmt, rustfmt_skip)]
use std::borrow::Cow;
use std::collections::HashMap;
type KVMap<K, V> = HashMap<K, V>;
use quick_protobuf::{MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result};
use quick_protobuf::sizeofs::*;
use super::super::*;
#[derive(Debug, Default, PartialEq, Clone)]
pub struct PidData<'a> {
pub name: Cow<'a, str>,
pub created: u64,
pub modified: u64,
pub p: f64,
pub i: f64,
pub d: f64,
}
impl<'a> MessageRead<'a> for PidData<'a> {
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
let mut msg = Self::default();
while !r.is_eof() {
match r.next_tag(bytes) {
Ok(34) => msg.name = r.read_string(bytes).map(Cow::Borrowed)?,
Ok(40) => msg.created = r.read_uint64(bytes)?,
Ok(48) => msg.modified = r.read_uint64(bytes)?,
Ok(81) => msg.p = r.read_double(bytes)?,
Ok(89) => msg.i = r.read_double(bytes)?,
Ok(97) => msg.d = r.read_double(bytes)?,
Ok(t) => { r.read_unknown(bytes, t)?; }
Err(e) => return Err(e),
}
}
Ok(msg)
}
}
impl<'a> MessageWrite for PidData<'a> {
fn get_size(&self) -> usize {
0
+ if self.name == "" { 0 } else { 1 + sizeof_len((&self.name).len()) }
+ if self.created == 0u64 { 0 } else { 1 + sizeof_varint(*(&self.created) as u64) }
+ if self.modified == 0u64 { 0 } else { 1 + sizeof_varint(*(&self.modified) as u64) }
+ if self.p == 0f64 { 0 } else { 1 + 8 }
+ if self.i == 0f64 { 0 } else { 1 + 8 }
+ if self.d == 0f64 { 0 } else { 1 + 8 }
}
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
if self.name != "" { w.write_with_tag(34, |w| w.write_string(&**&self.name))?; }
if self.created != 0u64 { w.write_with_tag(40, |w| w.write_uint64(*&self.created))?; }
if self.modified != 0u64 { w.write_with_tag(48, |w| w.write_uint64(*&self.modified))?; }
if self.p != 0f64 { w.write_with_tag(81, |w| w.write_double(*&self.p))?; }
if self.i != 0f64 { w.write_with_tag(89, |w| w.write_double(*&self.i))?; }
if self.d != 0f64 { w.write_with_tag(97, |w| w.write_double(*&self.d))?; }
Ok(())
}
}
#[derive(Debug, Default, PartialEq, Clone)]
pub struct PidDataLibrary<'a> {
pub items: KVMap<u32, pb::pid1::PidData<'a>>,
}
impl<'a> MessageRead<'a> for PidDataLibrary<'a> {
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
let mut msg = Self::default();
while !r.is_eof() {
match r.next_tag(bytes) {
Ok(34) => {
let (key, value) = r.read_map(bytes, |r, bytes| Ok(r.read_uint32(bytes)?), |r, bytes| Ok(r.read_message::<pb::pid1::PidData>(bytes)?))?;
msg.items.insert(key, value);
}
Ok(t) => { r.read_unknown(bytes, t)?; }
Err(e) => return Err(e),
}
}
Ok(msg)
}
}
impl<'a> MessageWrite for PidDataLibrary<'a> {
fn get_size(&self) -> usize {
0
+ self.items.iter().map(|(k, v)| 1 + sizeof_len(2 + sizeof_varint(*(k) as u64) + sizeof_len((v).get_size()))).sum::<usize>()
}
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
for (k, v) in self.items.iter() { w.write_with_tag(34, |w| w.write_map(2 + sizeof_varint(*(k) as u64) + sizeof_len((v).get_size()), 8, |w| w.write_uint32(*k), 18, |w| w.write_message(v)))?; }
Ok(())
}
}

148
src/s11n/pb/wifi1.rs Normal file
View File

@@ -0,0 +1,148 @@
// Automatically generated rust module for 'wifi1.proto' file
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(unused_imports)]
#![allow(unknown_lints)]
#![allow(clippy::all)]
#![cfg_attr(rustfmt, rustfmt_skip)]
use std::borrow::Cow;
use quick_protobuf::{MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result};
use quick_protobuf::sizeofs::*;
use super::super::*;
#[derive(Debug, Default, PartialEq, Clone)]
pub struct WifiData<'a> {
pub ssid: Cow<'a, str>,
pub password: Cow<'a, str>,
pub auth: pb::wifi1::mod_WifiData::AuthMethod,
}
impl<'a> MessageRead<'a> for WifiData<'a> {
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
let mut msg = Self::default();
while !r.is_eof() {
match r.next_tag(bytes) {
Ok(34) => msg.ssid = r.read_string(bytes).map(Cow::Borrowed)?,
Ok(42) => msg.password = r.read_string(bytes).map(Cow::Borrowed)?,
Ok(48) => msg.auth = r.read_enum(bytes)?,
Ok(t) => { r.read_unknown(bytes, t)?; }
Err(e) => return Err(e),
}
}
Ok(msg)
}
}
impl<'a> MessageWrite for WifiData<'a> {
fn get_size(&self) -> usize {
0
+ if self.ssid == "" { 0 } else { 1 + sizeof_len((&self.ssid).len()) }
+ if self.password == "" { 0 } else { 1 + sizeof_len((&self.password).len()) }
+ if self.auth == pb::wifi1::mod_WifiData::AuthMethod::PLAIN { 0 } else { 1 + sizeof_varint(*(&self.auth) as u64) }
}
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
if self.ssid != "" { w.write_with_tag(34, |w| w.write_string(&**&self.ssid))?; }
if self.password != "" { w.write_with_tag(42, |w| w.write_string(&**&self.password))?; }
if self.auth != pb::wifi1::mod_WifiData::AuthMethod::PLAIN { w.write_with_tag(48, |w| w.write_enum(*&self.auth as i32))?; }
Ok(())
}
}
pub mod mod_WifiData {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum AuthMethod {
PLAIN = 1,
WEP = 2,
WPA = 3,
WPA2Personal = 4,
WPAWPA2Personal = 5,
WPA2Enterprise = 6,
WPA3Personal = 7,
WPA2WPA3Personal = 8,
WAPIPersonal = 9,
}
impl Default for AuthMethod {
fn default() -> Self {
AuthMethod::PLAIN
}
}
impl From<i32> for AuthMethod {
fn from(i: i32) -> Self {
match i {
1 => AuthMethod::PLAIN,
2 => AuthMethod::WEP,
3 => AuthMethod::WPA,
4 => AuthMethod::WPA2Personal,
5 => AuthMethod::WPAWPA2Personal,
6 => AuthMethod::WPA2Enterprise,
7 => AuthMethod::WPA3Personal,
8 => AuthMethod::WPA2WPA3Personal,
9 => AuthMethod::WAPIPersonal,
_ => Self::default(),
}
}
}
impl<'a> From<&'a str> for AuthMethod {
fn from(s: &'a str) -> Self {
match s {
"PLAIN" => AuthMethod::PLAIN,
"WEP" => AuthMethod::WEP,
"WPA" => AuthMethod::WPA,
"WPA2Personal" => AuthMethod::WPA2Personal,
"WPAWPA2Personal" => AuthMethod::WPAWPA2Personal,
"WPA2Enterprise" => AuthMethod::WPA2Enterprise,
"WPA3Personal" => AuthMethod::WPA3Personal,
"WPA2WPA3Personal" => AuthMethod::WPA2WPA3Personal,
"WAPIPersonal" => AuthMethod::WAPIPersonal,
_ => Self::default(),
}
}
}
}
#[derive(Debug, Default, PartialEq, Clone)]
pub struct WifiDataLibrary<'a> {
pub version: u32,
pub data: Vec<pb::wifi1::WifiData<'a>>,
}
impl<'a> MessageRead<'a> for WifiDataLibrary<'a> {
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
let mut msg = Self::default();
while !r.is_eof() {
match r.next_tag(bytes) {
Ok(8) => msg.version = r.read_uint32(bytes)?,
Ok(34) => msg.data.push(r.read_message::<pb::wifi1::WifiData>(bytes)?),
Ok(t) => { r.read_unknown(bytes, t)?; }
Err(e) => return Err(e),
}
}
Ok(msg)
}
}
impl<'a> MessageWrite for WifiDataLibrary<'a> {
fn get_size(&self) -> usize {
0
+ if self.version == 0u32 { 0 } else { 1 + sizeof_varint(*(&self.version) as u64) }
+ self.data.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::<usize>()
}
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
if self.version != 0u32 { w.write_with_tag(8, |w| w.write_uint32(*&self.version))?; }
for s in &self.data { w.write_with_tag(34, |w| w.write_message(s))?; }
Ok(())
}
}

17
src/s11n/pid1.proto Normal file
View File

@@ -0,0 +1,17 @@
syntax = "proto3";
package pb.pid1;
message PidData {
string name = 4;
uint64 created = 5;
uint64 modified = 6;
double p = 10;
double i = 11;
double d = 12;
}
message PidDataLibrary {
map<uint32, PidData> items = 4;
}

25
src/s11n/wifi1.proto Normal file
View File

@@ -0,0 +1,25 @@
syntax = "proto3";
package pb.wifi1;
message WifiData {
string ssid = 4;
string password = 5;
enum AuthMethod {
PLAIN = 1;
WEP = 2;
WPA = 3;
WPA2Personal = 4;
WPAWPA2Personal = 5;
WPA2Enterprise = 6;
WPA3Personal = 7;
WPA2WPA3Personal = 8;
WAPIPersonal = 9;
}
AuthMethod auth = 6;
}
message WifiDataLibrary {
uint32 version = 1;
repeated WifiData data = 4;
}

BIN
src/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

12
src/static/styles.css Normal file
View File

@@ -0,0 +1,12 @@
body {
font-family: serif;
text-align: center;
color: #321C0B;
background-color: #D5E4F6;
}
h1 {
color: #B15C1B;
}
h4 {
color: #2969B2;
}

BIN
src/static/styles.css.gz Normal file

Binary file not shown.

7
src/web_pid_get.html Normal file
View File

@@ -0,0 +1,7 @@
<html>
<body>
<h1>Heureka!</h1>
<p>Counter is: {{ counter }}</p>
</body>
</html>