Merge pull request 'Custom error implementation for peach-oled' (#34) from lean_refactor into main

Reviewed-on: #34
This commit is contained in:
glyph 2021-11-24 09:16:19 +00:00
commit 1dc740eeae
5 changed files with 143 additions and 150 deletions

12
Cargo.lock generated
View File

@ -2640,19 +2640,17 @@ dependencies = [
[[package]] [[package]]
name = "peach-oled" name = "peach-oled"
version = "0.1.3" version = "0.1.4"
dependencies = [ dependencies = [
"embedded-graphics", "embedded-graphics",
"env_logger 0.6.2", "env_logger 0.9.0",
"jsonrpc-core 11.0.0", "jsonrpc-core 18.0.0",
"jsonrpc-http-server 11.0.0", "jsonrpc-http-server 18.0.0",
"jsonrpc-test 11.0.0", "jsonrpc-test 18.0.0",
"linux-embedded-hal", "linux-embedded-hal",
"log 0.4.14", "log 0.4.14",
"nix 0.11.1", "nix 0.11.1",
"serde 1.0.130", "serde 1.0.130",
"serde_json",
"snafu 0.4.4",
"ssd1306", "ssd1306",
"tinybmp", "tinybmp",
] ]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-oled" name = "peach-oled"
version = "0.1.3" version = "0.1.4"
authors = ["Andrew Reid <gnomad@cryptolab.net>"] authors = ["Andrew Reid <gnomad@cryptolab.net>"]
edition = "2018" edition = "2018"
description = "Write and draw to OLED display using JSON-RPC over HTTP." description = "Write and draw to OLED display using JSON-RPC over HTTP."
@ -27,18 +27,16 @@ travis-ci = { repository = "peachcloud/peach-oled", branch = "master" }
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
jsonrpc-core = "11.0.0"
jsonrpc-http-server = "11.0.0"
linux-embedded-hal = "0.2.2"
embedded-graphics = "0.4.7" embedded-graphics = "0.4.7"
tinybmp = "0.1.0" env_logger = "0.9"
ssd1306 = "0.2.6" jsonrpc-core = "18"
serde = { version = "1.0.87", features = ["derive"] } jsonrpc-http-server = "18"
serde_json = "1.0.39" linux-embedded-hal = "0.2.2"
log = "0.4.0" log = "0.4"
env_logger = "0.6.1" serde = { version = "1", features = ["derive"] }
snafu = "0.4.1"
nix="0.11" nix="0.11"
ssd1306 = "0.2.6"
tinybmp = "0.1.0"
[dev-dependencies] [dev-dependencies]
jsonrpc-test = "11.0.0" jsonrpc-test = "18"

View File

@ -1,6 +1,6 @@
# peach-oled # peach-oled
[![Build Status](https://travis-ci.com/peachcloud/peach-oled.svg?branch=master)](https://travis-ci.com/peachcloud/peach-oled) ![Generic badge](https://img.shields.io/badge/version-0.1.3-<COLOR>.svg) [![Build Status](https://travis-ci.com/peachcloud/peach-oled.svg?branch=master)](https://travis-ci.com/peachcloud/peach-oled) ![Generic badge](https://img.shields.io/badge/version-0.1.4-<COLOR>.svg)
OLED microservice module for PeachCloud. Write to a 128x64 OLED display with SDD1306 driver (I2C) using [JSON-RPC](https://www.jsonrpc.org/specification) over http. OLED microservice module for PeachCloud. Write to a 128x64 OLED display with SDD1306 driver (I2C) using [JSON-RPC](https://www.jsonrpc.org/specification) over http.

View File

@ -1,44 +1,68 @@
use std::error; use std::{error, fmt};
use jsonrpc_core::{types::error::Error, ErrorCode}; use jsonrpc_core::types::error::Error as JsonRpcError;
use linux_embedded_hal as hal; use jsonrpc_core::ErrorCode;
use snafu::Snafu; use linux_embedded_hal::i2cdev::linux::LinuxI2CError;
pub type BoxError = Box<dyn error::Error>; #[derive(Debug)]
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum OledError { pub enum OledError {
#[snafu(display("Failed to create interface for I2C device: {}", source))]
I2CError { I2CError {
source: hal::i2cdev::linux::LinuxI2CError, source: LinuxI2CError,
}, },
#[snafu(display("Coordinate {} out of range {}: {}", coord, range, value))]
InvalidCoordinate { InvalidCoordinate {
coord: String, coord: String,
range: String, range: String,
value: i32, value: i32,
}, },
InvalidFontSize {
// TODO: implement for validate() in src/lib.rs font: String,
#[snafu(display("Font size invalid: {}", font))] },
InvalidFontSize { font: String }, InvalidString {
len: usize,
#[snafu(display("String length out of range 0-21: {}", len))] },
InvalidString { len: usize }, MissingParameter {
source: JsonRpcError,
#[snafu(display("Missing expected parameter: {}", e))] },
MissingParameter { e: Error }, ParseError {
source: JsonRpcError,
#[snafu(display("Failed to parse parameter: {}", e))] },
ParseError { e: Error },
} }
impl From<OledError> for Error { impl error::Error for OledError {}
impl fmt::Display for OledError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
OledError::ParseError { ref source } => {
write!(f, "Failed to parse parameter: {}", source)
}
OledError::MissingParameter { ref source } => {
write!(f, "Missing expected parameter: {}", source)
}
OledError::InvalidString { len } => {
write!(f, "String length out of range 0-21: {}", len)
}
OledError::InvalidFontSize { ref font } => {
write!(f, "Invalid font size: {}", font)
}
OledError::InvalidCoordinate {
ref coord,
ref range,
value,
} => {
write!(f, "Coordinate {} out of range {}: {}", coord, range, value)
}
OledError::I2CError { ref source } => {
write!(f, "Failed to create interface for I2C device: {}", source)
}
}
}
}
impl From<OledError> for JsonRpcError {
fn from(err: OledError) -> Self { fn from(err: OledError) -> Self {
match &err { match &err {
OledError::I2CError { source } => Error { OledError::I2CError { source } => JsonRpcError {
code: ErrorCode::ServerError(-32000), code: ErrorCode::ServerError(-32000),
message: format!("Failed to create interface for I2C device: {}", source), message: format!("Failed to create interface for I2C device: {}", source),
data: None, data: None,
@ -47,7 +71,7 @@ impl From<OledError> for Error {
coord, coord,
value, value,
range, range,
} => Error { } => JsonRpcError {
code: ErrorCode::ServerError(-32001), code: ErrorCode::ServerError(-32001),
message: format!( message: format!(
"Validation error: coordinate {} out of range {}: {}", "Validation error: coordinate {} out of range {}: {}",
@ -55,18 +79,18 @@ impl From<OledError> for Error {
), ),
data: None, data: None,
}, },
OledError::InvalidFontSize { font } => Error { OledError::InvalidFontSize { font } => JsonRpcError {
code: ErrorCode::ServerError(-32002), code: ErrorCode::ServerError(-32002),
message: format!("Validation error: {} is not an accepted font size. Use 6x8, 6x12, 8x16 or 12x16 instead", font), message: format!("Validation error: {} is not an accepted font size. Use 6x8, 6x12, 8x16 or 12x16 instead", font),
data: None, data: None,
}, },
OledError::InvalidString { len } => Error { OledError::InvalidString { len } => JsonRpcError {
code: ErrorCode::ServerError(-32003), code: ErrorCode::ServerError(-32003),
message: format!("Validation error: string length {} out of range 0-21", len), message: format!("Validation error: string length {} out of range 0-21", len),
data: None, data: None,
}, },
OledError::MissingParameter { e } => e.clone(), OledError::MissingParameter { source } => source.clone(),
OledError::ParseError { e } => e.clone(), OledError::ParseError { source } => source.clone(),
} }
} }
} }

View File

@ -6,23 +6,23 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use embedded_graphics::coord::Coord; use embedded_graphics::{
use embedded_graphics::fonts::{Font12x16, Font6x12, Font6x8, Font8x16}; coord::Coord,
use embedded_graphics::image::Image1BPP; fonts::{Font12x16, Font6x12, Font6x8, Font8x16},
use embedded_graphics::prelude::*; image::Image1BPP,
prelude::*,
};
use hal::I2cdev; use hal::I2cdev;
use jsonrpc_core::{types::error::Error, IoHandler, Params, Value}; use jsonrpc_core::{types::error::Error, IoHandler, Params, Value};
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder}; use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
use linux_embedded_hal as hal; use linux_embedded_hal as hal;
use log::{debug, error, info}; use log::{debug, error, info};
use serde::Deserialize; use serde::Deserialize;
use snafu::{ensure, ResultExt}; use ssd1306::{prelude::*, Builder};
use ssd1306::prelude::*;
use ssd1306::Builder;
use crate::error::{BoxError, I2CError, InvalidCoordinate, InvalidString, OledError}; use crate::error::OledError;
//define the Graphic struct for receiving draw commands // define the Graphic struct for receiving draw commands
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Graphic { pub struct Graphic {
bytes: Vec<u8>, bytes: Vec<u8>,
@ -32,7 +32,7 @@ pub struct Graphic {
y_coord: i32, y_coord: i32,
} }
//define the Msg struct for receiving write commands // define the Msg struct for receiving write commands
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Msg { pub struct Msg {
x_coord: i32, x_coord: i32,
@ -41,86 +41,61 @@ pub struct Msg {
font_size: String, font_size: String,
} }
//definte the On struct for receiving power on/off commands // definte the On struct for receiving power on/off commands
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct On { pub struct On {
on: bool, on: bool,
} }
fn validate(m: &Msg) -> Result<(), OledError> { fn validate(msg: &Msg) -> Result<(), OledError> {
ensure!( if msg.string.len() > 21 {
m.string.len() <= 21, Err(OledError::InvalidString {
InvalidString { len: msg.string.len(),
len: m.string.len() })
} } else if msg.x_coord < 0 || msg.x_coord > 128 {
); Err(OledError::InvalidCoordinate {
ensure!(
m.x_coord >= 0,
InvalidCoordinate {
coord: "x".to_string(), coord: "x".to_string(),
range: "0-128".to_string(), range: "0-128".to_string(),
value: m.x_coord, value: msg.x_coord,
} })
); } else if msg.y_coord < 0 || msg.y_coord > 147 {
Err(OledError::InvalidCoordinate {
ensure!(
m.x_coord < 129,
InvalidCoordinate {
coord: "x".to_string(),
range: "0-128".to_string(),
value: m.x_coord,
}
);
ensure!(
m.y_coord >= 0,
InvalidCoordinate {
coord: "y".to_string(), coord: "y".to_string(),
range: "0-47".to_string(), range: "0-47".to_string(),
value: m.y_coord, value: msg.y_coord,
} })
); } else {
Ok(())
ensure!( }
m.y_coord < 148,
InvalidCoordinate {
coord: "y".to_string(),
range: "0-47".to_string(),
value: m.y_coord,
}
);
Ok(())
} }
pub fn run() -> Result<(), BoxError> { pub fn run() -> Result<(), OledError> {
info!("Starting up."); info!("Starting up.");
debug!("Creating interface for I2C device."); debug!("Creating interface for I2C device.");
let i2c = I2cdev::new("/dev/i2c-1").context(I2CError)?; let i2c = I2cdev::new("/dev/i2c-1").map_err(|source| OledError::I2CError { source })?;
let mut disp: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into(); let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into();
info!("Initializing the display."); info!("Initializing the display.");
disp.init().unwrap_or_else(|_| { display.init().unwrap_or_else(|_| {
error!("Problem initializing the OLED display."); error!("Problem initializing the OLED display.");
process::exit(1); process::exit(1);
}); });
debug!("Flushing the display."); debug!("Flushing the display.");
disp.flush().unwrap_or_else(|_| { display.flush().unwrap_or_else(|_| {
error!("Problem flushing the OLED display."); error!("Problem flushing the OLED display.");
process::exit(1); process::exit(1);
}); });
let oled = Arc::new(Mutex::new(disp)); let oled = Arc::new(Mutex::new(display));
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
info!("Creating JSON-RPC I/O handler."); info!("Creating JSON-RPC I/O handler.");
let mut io = IoHandler::default(); let mut io = IoHandler::default();
io.add_method("clear", move |_| { io.add_sync_method("clear", move |_| {
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Clearing the display."); info!("Clearing the display.");
oled.clear(); oled.clear();
@ -134,21 +109,20 @@ pub fn run() -> Result<(), BoxError> {
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("draw", move |params: Params| { io.add_sync_method("draw", move |params: Params| {
let g: Result<Graphic, Error> = params.parse(); let graphic: Graphic = params.parse()?;
let g: Graphic = g?;
// TODO: add simple byte validation function // TODO: add simple byte validation function
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Drawing image to the display."); info!("Drawing image to the display.");
let im = let image = Image1BPP::new(&graphic.bytes, graphic.width, graphic.height)
Image1BPP::new(&g.bytes, g.width, g.height).translate(Coord::new(g.x_coord, g.y_coord)); .translate(Coord::new(graphic.x_coord, graphic.y_coord));
oled.draw(im.into_iter()); oled.draw(image.into_iter());
Ok(Value::String("success".into())) Ok(Value::String("success".into()))
}); });
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("flush", move |_| { io.add_sync_method("flush", move |_| {
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Flushing the display."); info!("Flushing the display.");
oled.flush().unwrap_or_else(|_| { oled.flush().unwrap_or_else(|_| {
@ -160,9 +134,9 @@ pub fn run() -> Result<(), BoxError> {
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("ping", |_| Ok(Value::String("success".to_string()))); io.add_sync_method("ping", |_| Ok(Value::String("success".to_string())));
io.add_method("power", move |params: Params| { io.add_sync_method("power", move |params: Params| {
let o: Result<On, Error> = params.parse(); let o: Result<On, Error> = params.parse();
let o: On = o?; let o: On = o?;
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
@ -180,37 +154,36 @@ pub fn run() -> Result<(), BoxError> {
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("write", move |params: Params| { io.add_sync_method("write", move |params: Params| {
info!("Received a 'write' request."); info!("Received a 'write' request.");
let m: Result<Msg, Error> = params.parse(); let msg = params.parse()?;
let m: Msg = m?; validate(&msg)?;
validate(&m)?;
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Writing to the display."); info!("Writing to the display.");
if m.font_size == "6x8" { if msg.font_size == "6x8" {
oled.draw( oled.draw(
Font6x8::render_str(&m.string) Font6x8::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} else if m.font_size == "6x12" { } else if msg.font_size == "6x12" {
oled.draw( oled.draw(
Font6x12::render_str(&m.string) Font6x12::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} else if m.font_size == "8x16" { } else if msg.font_size == "8x16" {
oled.draw( oled.draw(
Font8x16::render_str(&m.string) Font8x16::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} else if m.font_size == "12x16" { } else if msg.font_size == "12x16" {
oled.draw( oled.draw(
Font12x16::render_str(&m.string) Font12x16::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} }
@ -255,7 +228,7 @@ mod tests {
fn rpc_success() { fn rpc_success() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_success_response", |_| { io.add_sync_method("rpc_success_response", |_| {
Ok(Value::String("success".into())) Ok(Value::String("success".into()))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
@ -269,7 +242,7 @@ mod tests {
fn rpc_internal_error() { fn rpc_internal_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_internal_error", |_| Err(Error::internal_error())); io.add_sync_method("rpc_internal_error", |_| Err(Error::internal_error()));
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
}; };
@ -287,7 +260,7 @@ mod tests {
fn rpc_i2c_io_error() { fn rpc_i2c_io_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_i2c_io_error", |_| { io.add_sync_method("rpc_i2c_io_error", |_| {
let io_err = IoError::new(ErrorKind::PermissionDenied, "oh no!"); let io_err = IoError::new(ErrorKind::PermissionDenied, "oh no!");
let source = LinuxI2CError::Io(io_err); let source = LinuxI2CError::Io(io_err);
Err(Error::from(OledError::I2CError { source })) Err(Error::from(OledError::I2CError { source }))
@ -310,7 +283,7 @@ mod tests {
fn rpc_i2c_nix_error() { fn rpc_i2c_nix_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_i2c_nix_error", |_| { io.add_sync_method("rpc_i2c_nix_error", |_| {
let nix_err = NixError::InvalidPath; let nix_err = NixError::InvalidPath;
let source = LinuxI2CError::Nix(nix_err); let source = LinuxI2CError::Nix(nix_err);
Err(Error::from(OledError::I2CError { source })) Err(Error::from(OledError::I2CError { source }))
@ -326,14 +299,14 @@ mod tests {
}"# }"#
); );
} }
*/ */
// test to ensure correct InvalidCoordinate error response // test to ensure correct InvalidCoordinate error response
#[test] #[test]
fn rpc_invalid_coord() { fn rpc_invalid_coord() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_coord", |_| { io.add_sync_method("rpc_invalid_coord", |_| {
Err(Error::from(OledError::InvalidCoordinate { Err(Error::from(OledError::InvalidCoordinate {
coord: "x".to_string(), coord: "x".to_string(),
range: "0-128".to_string(), range: "0-128".to_string(),
@ -357,7 +330,7 @@ mod tests {
fn rpc_invalid_fontsize() { fn rpc_invalid_fontsize() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_fontsize", |_| { io.add_sync_method("rpc_invalid_fontsize", |_| {
Err(Error::from(OledError::InvalidFontSize { Err(Error::from(OledError::InvalidFontSize {
font: "24x32".to_string(), font: "24x32".to_string(),
})) }))
@ -379,7 +352,7 @@ mod tests {
fn rpc_invalid_string() { fn rpc_invalid_string() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_string", |_| { io.add_sync_method("rpc_invalid_string", |_| {
Err(Error::from(OledError::InvalidString { len: 22 })) Err(Error::from(OledError::InvalidString { len: 22 }))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
@ -399,15 +372,15 @@ mod tests {
fn rpc_invalid_params() { fn rpc_invalid_params() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_params", |_| { io.add_sync_method("rpc_invalid_params", |_| {
let e = Error { let source = Error {
code: ErrorCode::InvalidParams, code: ErrorCode::InvalidParams,
message: String::from("invalid params"), message: String::from("invalid params"),
data: Some(Value::String( data: Some(Value::String(
"Invalid params: invalid type: null, expected struct Msg.".into(), "Invalid params: invalid type: null, expected struct Msg.".into(),
)), )),
}; };
Err(Error::from(OledError::MissingParameter { e })) Err(Error::from(OledError::MissingParameter { source }))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
}; };
@ -427,13 +400,13 @@ mod tests {
fn rpc_parse_error() { fn rpc_parse_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_parse_error", |_| { io.add_sync_method("rpc_parse_error", |_| {
let e = Error { let source = Error {
code: ErrorCode::ParseError, code: ErrorCode::ParseError,
message: String::from("Parse error"), message: String::from("Parse error"),
data: None, data: None,
}; };
Err(Error::from(OledError::ParseError { e })) Err(Error::from(OledError::ParseError { source }))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
}; };