Add peach-buttons and peach-oled
This commit is contained in:
parent
d8803e9974
commit
9704a78149
4
peach-buttons/.cargo/config
Normal file
4
peach-buttons/.cargo/config
Normal file
@ -0,0 +1,4 @@
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
linker = "aarch64-linux-gnu-gcc"
|
||||
objcopy = { path ="aarch64-linux-gnu-objcopy" }
|
||||
strip = { path ="aarch64-linux-gnu-strip" }
|
2
peach-buttons/.gitignore
vendored
Normal file
2
peach-buttons/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
**/*.rs.bk
|
7
peach-buttons/.travis.yml
Normal file
7
peach-buttons/.travis.yml
Normal file
@ -0,0 +1,7 @@
|
||||
language: rust
|
||||
rust:
|
||||
- nightly
|
||||
before_script:
|
||||
- rustup component add clippy
|
||||
script:
|
||||
- cargo clippy -- -D warnings
|
1460
peach-buttons/Cargo.lock
generated
Normal file
1460
peach-buttons/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
peach-buttons/Cargo.toml
Normal file
40
peach-buttons/Cargo.toml
Normal file
@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "peach-buttons"
|
||||
version = "0.1.3"
|
||||
authors = ["Andrew Reid <gnomad@cryptolab.net>"]
|
||||
edition = "2018"
|
||||
description = "peach-buttons is a GPIO microservice for handling button presses, implementing a JSON-RPC server with Publish-Subscribe extension. Each button press results in a JSON-RPC request being sent over websockets to any subscribers. A button code for the pressed button is sent with the request to subscribers, allowing state-specific actions to be taken."
|
||||
homepage = "https://opencollective.com/peachcloud"
|
||||
repository = "https://github.com/peachcloud/peach-buttons"
|
||||
readme = "README.md"
|
||||
license = "AGPL-3.0-only"
|
||||
publish = false
|
||||
|
||||
[package.metadata.deb]
|
||||
depends = "$auto"
|
||||
extended-description = """\
|
||||
peach-buttons is a GPIO microservice for handling button presses, \
|
||||
implementing a JSON-RPC server with Publish-Subscribe extention. \
|
||||
Each button press results in a JSON-RPC request being sent over websockets \
|
||||
to any subscribers."""
|
||||
maintainer-scripts="debian"
|
||||
systemd-units = { unit-name = "peach-buttons" }
|
||||
assets = [
|
||||
["target/release/peach-buttons", "usr/bin/", "755"],
|
||||
["README.md", "usr/share/doc/peach-buttons/README", "644"],
|
||||
]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "peachcloud/peach-buttons", branch = "master" }
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
crossbeam-channel = "0.3"
|
||||
env_logger = "0.6"
|
||||
gpio-cdev = "0.2"
|
||||
jsonrpc-core = "11"
|
||||
jsonrpc-ws-server = "11"
|
||||
jsonrpc-pubsub = "11"
|
||||
jsonrpc-test = "11"
|
||||
log = "0.4"
|
||||
snafu = "0.4"
|
106
peach-buttons/README.md
Normal file
106
peach-buttons/README.md
Normal file
@ -0,0 +1,106 @@
|
||||
# peach-buttons
|
||||
|
||||
[![Build Status](https://travis-ci.com/peachcloud/peach-buttons.svg?branch=master)](https://travis-ci.com/peachcloud/peach-buttons) ![Generic badge](https://img.shields.io/badge/version-0.1.3-<COLOR>.svg)
|
||||
|
||||
GPIO microservice module for handling button presses. `peach-buttons` implements a JSON-RPC server with [Publish-Subscribe extension](https://docs.rs/jsonrpc-pubsub/11.0.0/jsonrpc_pubsub/). Each button press results in a JSON-RPC request being sent over websockets to any subscribers. A button code for the pressed button is sent with the request to subscribers, allowing state-specific actions to be taken.
|
||||
|
||||
A subscriber implementation for this microservice can be found in the [peach-menu repo](https://github.com/peachcloud/peach-menu).
|
||||
|
||||
_Note: This module is a work-in-progress._
|
||||
|
||||
### Pin to Button to Button Code Mappings
|
||||
|
||||
```
|
||||
4 => Center => 0,
|
||||
27 => Left => 1,
|
||||
23 => Right => 2,
|
||||
17 => Up => 3,
|
||||
22 => Down => 4,
|
||||
5 => A => 5,
|
||||
6 => B => 6
|
||||
```
|
||||
|
||||
_Note: `peach-buttons` utilizes the GPIO character device ABI. This API, stabilized with Linux v4.4, deprecates the legacy sysfs interface to GPIOs that is planned to be removed from the upstream kernel after year 2020._
|
||||
|
||||
### Environment
|
||||
|
||||
The JSON-RPC WS server address and port can be configured with the `PEACH_BUTTONS_SERVER` environment variable:
|
||||
|
||||
`export PEACH_BUTTONS_SERVER=127.0.0.1:5000`
|
||||
|
||||
When not set, the value defaults to `127.0.0.1:5111`.
|
||||
|
||||
Logging is made availabe with `env_logger`:
|
||||
|
||||
`export RUST_LOG=info`
|
||||
|
||||
Other logging levels include `debug`, `warn` and `error`.
|
||||
|
||||
### Setup
|
||||
|
||||
Clone this repo:
|
||||
|
||||
`git clone https://github.com/peachcloud/peach-buttons.git`
|
||||
|
||||
Move into the repo and compile:
|
||||
|
||||
`cd peach-buttons`
|
||||
`cargo build --release`
|
||||
|
||||
Run the binary with sudo:
|
||||
|
||||
`sudo ./target/release/peach-buttons`
|
||||
|
||||
### Debian Packaging
|
||||
|
||||
A `systemd` service file and Debian maintainer scripts are included in the `debian` directory, allowing `peach-buttons` to be easily bundled as a Debian package (`.deb`). The `cargo-deb` [crate](https://crates.io/crates/cargo-deb) can be used to achieve this.
|
||||
|
||||
Install `cargo-deb`:
|
||||
|
||||
`cargo install cargo-deb`
|
||||
|
||||
Move into the repo:
|
||||
|
||||
`cd peach-buttons`
|
||||
|
||||
Build the package:
|
||||
|
||||
`cargo deb`
|
||||
|
||||
The output will be written to `target/debian/peach-buttons_0.1.0_arm64.deb` (or similar).
|
||||
|
||||
Build the package (aarch64):
|
||||
|
||||
`cargo deb --target aarch64-unknown-linux-gnu`
|
||||
|
||||
Install the package as follows:
|
||||
|
||||
`sudo dpkg -i target/debian/peach-buttons_0.1.0_arm64.deb`
|
||||
|
||||
The service will be automatically enabled and started.
|
||||
|
||||
Uninstall the service:
|
||||
|
||||
`sudo apt-get remove peach-buttons`
|
||||
|
||||
Remove configuration files (not removed with `apt-get remove`):
|
||||
|
||||
`sudo apt-get purge peach-buttons`
|
||||
|
||||
### Testing Subscription
|
||||
|
||||
Request:
|
||||
|
||||
`{"id":1,"jsonrpc":"2.0","method":"subscribe_buttons"}`
|
||||
|
||||
Response:
|
||||
|
||||
`{"jsonrpc":"2.0","result":1,"id":1}`
|
||||
|
||||
Event:
|
||||
|
||||
`{"jsonrpc":"2.0","method":"button_press","params":[0]}`
|
||||
|
||||
### Licensing
|
||||
|
||||
AGPL-3.0
|
27
peach-buttons/debian/peach-buttons.service
Normal file
27
peach-buttons/debian/peach-buttons.service
Normal file
@ -0,0 +1,27 @@
|
||||
[Unit]
|
||||
Description=GPIO microservice for handling button presses. Implements a JSON-RPC server with Publish-Subscribe extension.
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=peach-buttons
|
||||
Group=gpio-user
|
||||
Environment="RUST_LOG=error"
|
||||
ExecStart=/usr/bin/peach-buttons
|
||||
Restart=always
|
||||
CapabilityBoundingSet=~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYS_BOOT CAP_SYS_TIME CAP_KILL CAP_WAKE_ALARM CAP_LINUX_IMMUTABLE CAP_BLOCK_SUSPEND CAP_LEASE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_RAWIO CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_* CAP_FOWNER CAP_IPC_OWNER CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_AUDIT_*
|
||||
InaccessibleDirectories=/home
|
||||
LockPersonality=yes
|
||||
NoNewPrivileges=yes
|
||||
PrivateTmp=yes
|
||||
PrivateUsers=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectSystem=yes
|
||||
ReadOnlyDirectories=/var
|
||||
RestrictAddressFamilies=~AF_INET6 AF_UNIX
|
||||
SystemCallFilter=~@reboot @clock @debug @module @mount @swap @resources @privileged
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
25
peach-buttons/src/error.rs
Normal file
25
peach-buttons/src/error.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use std::{error, str};
|
||||
|
||||
use jsonrpc_core::{types::error::Error, ErrorCode};
|
||||
use snafu::Snafu;
|
||||
|
||||
pub type BoxError = Box<dyn error::Error>;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub(crate)))]
|
||||
pub enum ButtonError {
|
||||
#[snafu(display("Invalid parameters. Subscription rejected"))]
|
||||
RejectSubscription,
|
||||
}
|
||||
|
||||
impl From<ButtonError> for Error {
|
||||
fn from(err: ButtonError) -> Self {
|
||||
match &err {
|
||||
ButtonError::RejectSubscription => Error {
|
||||
code: ErrorCode::ParseError,
|
||||
message: "Invalid parameters. Subscription request rejected".to_string(),
|
||||
data: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
63
peach-buttons/src/interrupt.rs
Normal file
63
peach-buttons/src/interrupt.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use std::{cell::Cell, process, thread, time::Duration};
|
||||
|
||||
use crossbeam_channel::{tick, Sender};
|
||||
use gpio_cdev::{Chip, LineRequestFlags};
|
||||
use log::{debug, error, info};
|
||||
|
||||
// initialize gpio pin and poll for state
|
||||
// send button code to "subscribe_buttons" rpc method for sink notification
|
||||
pub fn interrupt_handler(pin: u32, button_code: u8, button_name: String, s: Sender<u8>) {
|
||||
thread::spawn(move || {
|
||||
debug!("Creating handle for GPIO chip.");
|
||||
let mut chip = Chip::new("/dev/gpiochip0").unwrap_or_else(|err| {
|
||||
error!("Failed to create handle for GPIO chip: {}", err);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
debug!("Creating handle for GPIO line at given pin.");
|
||||
let input = chip.get_line(pin).unwrap_or_else(|err| {
|
||||
error!(
|
||||
"Failed to create handle for GPIO line at pin {}: {}",
|
||||
pin, err
|
||||
);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
let line_handle = input
|
||||
.request(LineRequestFlags::INPUT, 0, &button_name)
|
||||
.unwrap_or_else(|err| {
|
||||
error!("Failed to gain kernel access for pin {}: {}", pin, err);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
let ticker = tick(Duration::from_millis(2));
|
||||
let mut counter = Cell::new(0);
|
||||
let mut switch = Cell::new(0);
|
||||
|
||||
info!(
|
||||
"Initating polling loop for {} button on pin {}",
|
||||
button_name, pin
|
||||
);
|
||||
loop {
|
||||
ticker.recv().unwrap();
|
||||
let value = line_handle
|
||||
.get_value()
|
||||
.expect("Failed to get current state of this line from the kernel");
|
||||
match value {
|
||||
0 => counter.set(0),
|
||||
1 => *counter.get_mut() += 1,
|
||||
_ => (),
|
||||
}
|
||||
if counter.get() == 10 {
|
||||
if switch.get() == 0 {
|
||||
*switch.get_mut() += 1
|
||||
} else {
|
||||
debug!("Sending button code: {}", button_code);
|
||||
s.send(button_code).unwrap_or_else(|err| {
|
||||
error!("Failed to send button_code to publisher: {}", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
119
peach-buttons/src/lib.rs
Normal file
119
peach-buttons/src/lib.rs
Normal file
@ -0,0 +1,119 @@
|
||||
mod error;
|
||||
mod interrupt;
|
||||
|
||||
use std::{env, result::Result, sync::Arc, thread};
|
||||
|
||||
use crossbeam_channel::bounded;
|
||||
use jsonrpc_core::futures::Future;
|
||||
use jsonrpc_core::*;
|
||||
use jsonrpc_pubsub::{PubSubHandler, Session, Subscriber, SubscriptionId};
|
||||
#[allow(unused_imports)]
|
||||
use jsonrpc_test as test;
|
||||
use jsonrpc_ws_server::{RequestContext, ServerBuilder};
|
||||
use log::{debug, error, info, warn};
|
||||
|
||||
use crate::error::{BoxError, ButtonError::RejectSubscription};
|
||||
use crate::interrupt::*;
|
||||
|
||||
pub fn run() -> Result<(), BoxError> {
|
||||
info!("Starting up.");
|
||||
|
||||
debug!("Creating channel for message passing.");
|
||||
let (s, r) = bounded(0);
|
||||
|
||||
let pin = vec![4, 27, 23, 17, 22, 5, 6];
|
||||
let code = vec![0, 1, 2, 3, 4, 5, 6];
|
||||
let name = vec!["center", "left", "right", "up", "down", "#5", "#6"];
|
||||
|
||||
debug!("Setting up interrupt handlers.");
|
||||
for i in 0..7 {
|
||||
interrupt_handler(pin[i], code[i], name[i].to_string(), s.clone());
|
||||
}
|
||||
|
||||
debug!("Creating pub-sub handler.");
|
||||
let mut io = PubSubHandler::new(MetaIoHandler::default());
|
||||
|
||||
io.add_subscription(
|
||||
"button_press",
|
||||
(
|
||||
"subscribe_buttons",
|
||||
move |params: Params, _, subscriber: Subscriber| {
|
||||
debug!("Received subscription request.");
|
||||
if params != Params::None {
|
||||
subscriber
|
||||
.reject(Error::from(RejectSubscription))
|
||||
.unwrap_or_else(|_| {
|
||||
error!("Failed to send rejection error for subscription request.");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let r1 = r.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let sink = subscriber
|
||||
.assign_id_async(SubscriptionId::Number(1))
|
||||
.wait()
|
||||
.unwrap();
|
||||
|
||||
info!("Listening for button code from gpio events.");
|
||||
loop {
|
||||
let button_code: u8 = r1.recv().unwrap();
|
||||
info!("Received button code: {}.", button_code);
|
||||
match sink
|
||||
.notify(Params::Array(vec![Value::Number(button_code.into())]))
|
||||
.wait()
|
||||
{
|
||||
Ok(_) => info!("Publishing button code to subscriber over ws."),
|
||||
Err(_) => {
|
||||
warn!("Failed to publish button code.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
("remove_buttons", |_id: SubscriptionId, _| {
|
||||
// unsubscribe
|
||||
futures::future::ok(Value::Bool(true))
|
||||
}),
|
||||
);
|
||||
|
||||
let ws_server =
|
||||
env::var("PEACH_BUTTONS_SERVER").unwrap_or_else(|_| "127.0.0.1:5111".to_string());
|
||||
|
||||
info!("Starting JSON-RPC server on {}.", ws_server);
|
||||
let server = ServerBuilder::with_meta_extractor(io, |context: &RequestContext| {
|
||||
Arc::new(Session::new(context.sender()))
|
||||
})
|
||||
.start(
|
||||
&ws_server
|
||||
.parse()
|
||||
.expect("Invalid WS address and port combination"),
|
||||
)
|
||||
.expect("Unable to start RPC server");
|
||||
|
||||
info!("Listening for requests.");
|
||||
server.wait().unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rpc_success() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_success_response", |_| {
|
||||
Ok(Value::String("success".into()))
|
||||
});
|
||||
test::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(rpc.request("rpc_success_response", &()), r#""success""#);
|
||||
}
|
||||
}
|
12
peach-buttons/src/main.rs
Normal file
12
peach-buttons/src/main.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use std::process;
|
||||
|
||||
use log::error;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
if let Err(e) = peach_buttons::run() {
|
||||
error!("Application error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
4
peach-oled/.cargo/config
Normal file
4
peach-oled/.cargo/config
Normal file
@ -0,0 +1,4 @@
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
linker = "aarch64-linux-gnu-gcc"
|
||||
objcopy = { path ="aarch64-linux-gnu-objcopy" }
|
||||
strip = { path ="aarch64-linux-gnu-strip" }
|
2
peach-oled/.gitignore
vendored
Normal file
2
peach-oled/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
**/*.rs.bk
|
7
peach-oled/.travis.yml
Normal file
7
peach-oled/.travis.yml
Normal file
@ -0,0 +1,7 @@
|
||||
language: rust
|
||||
rust:
|
||||
- nightly
|
||||
before_script:
|
||||
- rustup component add clippy
|
||||
script:
|
||||
- cargo clippy -- -D warnings
|
1603
peach-oled/Cargo.lock
generated
Normal file
1603
peach-oled/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
peach-oled/Cargo.toml
Normal file
42
peach-oled/Cargo.toml
Normal file
@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "peach-oled"
|
||||
version = "0.1.3"
|
||||
authors = ["Andrew Reid <gnomad@cryptolab.net>"]
|
||||
edition = "2018"
|
||||
description = "Write and draw to OLED display using JSON-RPC over HTTP."
|
||||
homepage = "https://opencollective.com/peachcloud"
|
||||
repository = "https://github.com/peachcloud/peach-oled"
|
||||
readme = "README.md"
|
||||
license = "AGPL-3.0-only"
|
||||
publish = false
|
||||
|
||||
[package.metadata.deb]
|
||||
depends = "$auto"
|
||||
extended-description = """\
|
||||
peach-oled allows writing and drawing to a 128x64 pixel OLED display \
|
||||
with SDD1306 driver (I2C) using JSON-RPC over HTTP."""
|
||||
maintainer-scripts="debian"
|
||||
systemd-units = { unit-name = "peach-oled" }
|
||||
assets = [
|
||||
["target/release/peach-oled", "usr/bin/", "755"],
|
||||
["README.md", "usr/share/doc/peach-oled/README", "644"],
|
||||
]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "peachcloud/peach-oled", branch = "master" }
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
jsonrpc-core = "11.0.0"
|
||||
jsonrpc-http-server = "11.0.0"
|
||||
jsonrpc-test = "11.0.0"
|
||||
linux-embedded-hal = "0.2.2"
|
||||
embedded-graphics = "0.4.7"
|
||||
tinybmp = "0.1.0"
|
||||
ssd1306 = "0.2.6"
|
||||
serde = { version = "1.0.87", features = ["derive"] }
|
||||
serde_json = "1.0.39"
|
||||
log = "0.4.0"
|
||||
env_logger = "0.6.1"
|
||||
snafu = "0.4.1"
|
||||
nix="0.11"
|
165
peach-oled/README.md
Normal file
165
peach-oled/README.md
Normal file
@ -0,0 +1,165 @@
|
||||
# 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)
|
||||
|
||||
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.
|
||||
|
||||
![Close-up, black-and-white photo of an Adafruit 128x64 1.3" OLED Bonnet. The circuit board features a 5-way joystick on the left side, two push-buttons on the right side (labelled #5 and #6), and a central OLED display. The display shows text reading: "PeachCloud" on the first line and "IP: 192.168.0.8" on the third line. A circle is displayed beneath the two lines of text and is horizontally-centered".](docs/images/peachcloud_oled.jpg)
|
||||
|
||||
### JSON-RPC API
|
||||
|
||||
| Method | Parameters | Description |
|
||||
| --- | --- | --- |
|
||||
| `clear` | | Clear the display buffer |
|
||||
| `draw` | `bytes`, `width`, `height`, `x_coord`, `y_coord` | Draw graphic to display buffer for given byte array, dimensions and co-ordinates |
|
||||
| `flush` | | Flush the display |
|
||||
| `ping` | | Respond with `success` if microservice is running |
|
||||
| `power` | `on` | Toggle the display (memory is retained while off) |
|
||||
| `write` | `x_coord`, `y_coord`, `string`, `font_size` | Write message to display buffer for given co-ordinates using given font size |
|
||||
|
||||
| Font Sizes |
|
||||
| --- |
|
||||
| `6x8` |
|
||||
| `6x12` |
|
||||
| `8x16` |
|
||||
| `12x16` |
|
||||
|
||||
### Environment
|
||||
|
||||
The JSON-RPC HTTP server address and port can be configured with the `PEACH_OLED_SERVER` environment variable:
|
||||
|
||||
`export PEACH_OLED_SERVER=127.0.0.1:5000`
|
||||
|
||||
When not set, the value defaults to `127.0.0.1:5112`.
|
||||
|
||||
Logging is made available with `env_logger`:
|
||||
|
||||
`export RUST_LOG=info`
|
||||
|
||||
Other logging levels include `debug`, `warn` and `error`.
|
||||
|
||||
### Setup
|
||||
|
||||
Clone this repo:
|
||||
|
||||
`git clone https://github.com/peachcloud/peach-oled.git`
|
||||
|
||||
Move into the repo and compile:
|
||||
|
||||
`cd peach-oled`
|
||||
`cargo build --release`
|
||||
|
||||
Run the binary:
|
||||
|
||||
`./target/release/peach-oled`
|
||||
|
||||
### Debian Packaging
|
||||
|
||||
A `systemd` service file and Debian maintainer scripts are included in the `debian` directory, allowing `peach-oled` to be easily bundled as a Debian package (`.deb`). The `cargo-deb` [crate](https://crates.io/crates/cargo-deb) can be used to achieve this.
|
||||
|
||||
Install `cargo-deb`:
|
||||
|
||||
`cargo install cargo-deb`
|
||||
|
||||
Move into the repo:
|
||||
|
||||
`cd peach-oled`
|
||||
|
||||
Build the package:
|
||||
|
||||
`cargo deb`
|
||||
|
||||
The output will be written to `target/debian/peach-oled_0.1.0_arm64.deb` (or similar).
|
||||
|
||||
Build the package (aarch64):
|
||||
|
||||
`cargo deb --target aarch64-unknown-linux-gnu`
|
||||
|
||||
Install the package as follows:
|
||||
|
||||
`sudo dpkg -i target/debian/peach-oled_0.1.0_arm64.deb`
|
||||
|
||||
The service will be automatically enabled and started.
|
||||
|
||||
Uninstall the service:
|
||||
|
||||
`sudo apt-get remove peach-oled`
|
||||
|
||||
Remove configuration files (not removed with `apt-get remove`):
|
||||
|
||||
`sudo apt-get purge peach-oled`
|
||||
|
||||
### Example Usage
|
||||
|
||||
**Write Text to the OLED Display**
|
||||
|
||||
With microservice running, open a second terminal window and use `curl` to call server methods:
|
||||
|
||||
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "write", "params" : {"x_coord": 0, "y_coord": 0, "string": "Welcome to PeachCloud", "font_size": "6x8" }, "id":1 }' 127.0.0.1:5112`
|
||||
|
||||
Server responds with:
|
||||
|
||||
`{"jsonrpc":"2.0","result":success","id":1}`
|
||||
|
||||
OLED will remain blank because no flush command has been issued.
|
||||
|
||||
Write to the second line of the display:
|
||||
|
||||
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "write", "params" : {"x_coord": 0, "y_coord": 8, "string": "Born in cypherspace", "font_size": "6x12" }, "id":1 }' 127.0.0.1:5112`
|
||||
|
||||
Flush the display:
|
||||
|
||||
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "flush", "id":1 }' 127.0.0.1:5112`
|
||||
|
||||
OLED display shows:
|
||||
|
||||
`Welcome to PeachCloud!`
|
||||
`Born in cypherspace`
|
||||
|
||||
Validation checks are performed for all three parameters: `x_coord`, `y_coord` and `string`. An appropriate error is returned if the validation checks are not satisfied:
|
||||
|
||||
`{"jsonrpc":"2.0","error":{"code":1,"message":"Validation error: coordinate x out of range 0-128: 129."},"id":1}`
|
||||
|
||||
`{"jsonrpc":"2.0","error":{"code":1,"message":"validation error","data":"y_coord not in range 0-57"},"id":1}`
|
||||
|
||||
`{"jsonrpc":"2.0","error":{"code":1,"message":"Validation error: string length 47 out of range 0-21."},"id":1}`
|
||||
|
||||
An error is returned if one or all of the expected parameters are not supplied:
|
||||
|
||||
`{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params: missing field `font_size`."},"id":1}`
|
||||
|
||||
-----
|
||||
|
||||
**Draw Graphic to the OLED Display**
|
||||
|
||||
With microservice running, open a second terminal window and use `curl` to call server methods:
|
||||
|
||||
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "draw", "params" : {"bytes": [30, 0, 33, 0, 64, 128, 128, 64, 140, 64, 140, 64, 128, 64, 64, 128, 33, 0, 30, 0], "width": 10, "height": 10, "x_coord": 32, "y_coord": 32}, "id":1 }' 127.0.0.1:5112`
|
||||
|
||||
Server responds with:
|
||||
|
||||
`{"jsonrpc":"2.0","result":success","id":1}`
|
||||
|
||||
OLED will remain blank because no flush command has been issued.
|
||||
|
||||
Flush the display:
|
||||
|
||||
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "flush", "id":1 }' 127.0.0.1:5112`
|
||||
|
||||
OLED display shows a 10x10 graphic of a dot inside a circle.
|
||||
|
||||
No validation checks are currently performed on the parameters of the `draw` RPC, aside from type-checks when the parameters are parsed.
|
||||
|
||||
-----
|
||||
|
||||
**Clear the Display**
|
||||
|
||||
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "clear", "id":1 }' 127.0.0.1:5112`
|
||||
|
||||
Server responds with:
|
||||
|
||||
`{"jsonrpc":"2,0","result":"success","id":1}`
|
||||
|
||||
### Licensing
|
||||
|
||||
AGPL-3.0
|
27
peach-oled/debian/peach-oled.service
Normal file
27
peach-oled/debian/peach-oled.service
Normal file
@ -0,0 +1,27 @@
|
||||
[Unit]
|
||||
Description=JSON-RPC microservice for writing and drawing to an OLED display over HTTP.
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=peach-oled
|
||||
Group=i2c
|
||||
Environment="RUST_LOG=error"
|
||||
ExecStart=/usr/bin/peach-oled
|
||||
Restart=always
|
||||
CapabilityBoundingSet=~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYS_BOOT CAP_SYS_TIME CAP_KILL CAP_WAKE_ALARM CAP_LINUX_IMMUTABLE CAP_BLOCK_SUSPEND CAP_LEASE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_RAWIO CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_* CAP_FOWNER CAP_IPC_OWNER CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_AUDIT_*
|
||||
InaccessibleDirectories=/home
|
||||
LockPersonality=yes
|
||||
NoNewPrivileges=yes
|
||||
PrivateTmp=yes
|
||||
PrivateUsers=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectSystem=yes
|
||||
ReadOnlyDirectories=/var
|
||||
RestrictAddressFamilies=~AF_INET6 AF_UNIX
|
||||
SystemCallFilter=~@reboot @clock @debug @module @mount @swap @resources @privileged
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
BIN
peach-oled/docs/images/peachcloud_oled.jpg
Normal file
BIN
peach-oled/docs/images/peachcloud_oled.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
72
peach-oled/src/error.rs
Normal file
72
peach-oled/src/error.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use std::error;
|
||||
|
||||
use jsonrpc_core::{types::error::Error, ErrorCode};
|
||||
use linux_embedded_hal as hal;
|
||||
use snafu::Snafu;
|
||||
|
||||
pub type BoxError = Box<dyn error::Error>;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub(crate)))]
|
||||
pub enum OledError {
|
||||
#[snafu(display("Failed to create interface for I2C device: {}", source))]
|
||||
I2CError {
|
||||
source: hal::i2cdev::linux::LinuxI2CError,
|
||||
},
|
||||
|
||||
#[snafu(display("Coordinate {} out of range {}: {}", coord, range, value))]
|
||||
InvalidCoordinate {
|
||||
coord: String,
|
||||
range: String,
|
||||
value: i32,
|
||||
},
|
||||
|
||||
// TODO: implement for validate() in src/lib.rs
|
||||
#[snafu(display("Font size invalid: {}", font))]
|
||||
InvalidFontSize { font: String },
|
||||
|
||||
#[snafu(display("String length out of range 0-21: {}", len))]
|
||||
InvalidString { len: usize },
|
||||
|
||||
#[snafu(display("Missing expected parameter: {}", e))]
|
||||
MissingParameter { e: Error },
|
||||
|
||||
#[snafu(display("Failed to parse parameter: {}", e))]
|
||||
ParseError { e: Error },
|
||||
}
|
||||
|
||||
impl From<OledError> for Error {
|
||||
fn from(err: OledError) -> Self {
|
||||
match &err {
|
||||
OledError::I2CError { source } => Error {
|
||||
code: ErrorCode::ServerError(-32000),
|
||||
message: format!("Failed to create interface for I2C device: {}", source),
|
||||
data: None,
|
||||
},
|
||||
OledError::InvalidCoordinate {
|
||||
coord,
|
||||
value,
|
||||
range,
|
||||
} => Error {
|
||||
code: ErrorCode::ServerError(-32001),
|
||||
message: format!(
|
||||
"Validation error: coordinate {} out of range {}: {}",
|
||||
coord, range, value
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
OledError::InvalidFontSize { font } => Error {
|
||||
code: ErrorCode::ServerError(-32002),
|
||||
message: format!("Validation error: {} is not an accepted font size. Use 6x8, 6x12, 8x16 or 12x16 instead", font),
|
||||
data: None,
|
||||
},
|
||||
OledError::InvalidString { len } => Error {
|
||||
code: ErrorCode::ServerError(-32003),
|
||||
message: format!("Validation error: string length {} out of range 0-21", len),
|
||||
data: None,
|
||||
},
|
||||
OledError::MissingParameter { e } => e.clone(),
|
||||
OledError::ParseError { e } => e.clone(),
|
||||
}
|
||||
}
|
||||
}
|
448
peach-oled/src/lib.rs
Normal file
448
peach-oled/src/lib.rs
Normal file
@ -0,0 +1,448 @@
|
||||
mod error;
|
||||
|
||||
use std::{
|
||||
env, process,
|
||||
result::Result,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use embedded_graphics::coord::Coord;
|
||||
use embedded_graphics::fonts::{Font12x16, Font6x12, Font6x8, Font8x16};
|
||||
use embedded_graphics::image::Image1BPP;
|
||||
use embedded_graphics::prelude::*;
|
||||
use hal::I2cdev;
|
||||
use jsonrpc_core::{types::error::Error, IoHandler, Params, Value};
|
||||
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
|
||||
#[allow(unused_imports)]
|
||||
use jsonrpc_test as test;
|
||||
use linux_embedded_hal as hal;
|
||||
use log::{debug, error, info};
|
||||
use serde::Deserialize;
|
||||
use snafu::{ensure, ResultExt};
|
||||
use ssd1306::prelude::*;
|
||||
use ssd1306::Builder;
|
||||
|
||||
use crate::error::{BoxError, I2CError, InvalidCoordinate, InvalidString, OledError};
|
||||
|
||||
//define the Graphic struct for receiving draw commands
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Graphic {
|
||||
bytes: Vec<u8>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
x_coord: i32,
|
||||
y_coord: i32,
|
||||
}
|
||||
|
||||
//define the Msg struct for receiving write commands
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Msg {
|
||||
x_coord: i32,
|
||||
y_coord: i32,
|
||||
string: String,
|
||||
font_size: String,
|
||||
}
|
||||
|
||||
//definte the On struct for receiving power on/off commands
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct On {
|
||||
on: bool,
|
||||
}
|
||||
|
||||
fn validate(m: &Msg) -> Result<(), OledError> {
|
||||
ensure!(
|
||||
m.string.len() <= 21,
|
||||
InvalidString {
|
||||
len: m.string.len()
|
||||
}
|
||||
);
|
||||
|
||||
ensure!(
|
||||
m.x_coord >= 0,
|
||||
InvalidCoordinate {
|
||||
coord: "x".to_string(),
|
||||
range: "0-128".to_string(),
|
||||
value: m.x_coord,
|
||||
}
|
||||
);
|
||||
|
||||
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(),
|
||||
range: "0-47".to_string(),
|
||||
value: m.y_coord,
|
||||
}
|
||||
);
|
||||
|
||||
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> {
|
||||
info!("Starting up.");
|
||||
|
||||
debug!("Creating interface for I2C device.");
|
||||
let i2c = I2cdev::new("/dev/i2c-1").context(I2CError)?;
|
||||
|
||||
let mut disp: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into();
|
||||
|
||||
info!("Initializing the display.");
|
||||
disp.init().unwrap_or_else(|_| {
|
||||
error!("Problem initializing the OLED display.");
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
debug!("Flushing the display.");
|
||||
disp.flush().unwrap_or_else(|_| {
|
||||
error!("Problem flushing the OLED display.");
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
let oled = Arc::new(Mutex::new(disp));
|
||||
let oled_clone = Arc::clone(&oled);
|
||||
|
||||
info!("Creating JSON-RPC I/O handler.");
|
||||
let mut io = IoHandler::default();
|
||||
|
||||
io.add_method("clear", move |_| {
|
||||
let mut oled = oled_clone.lock().unwrap();
|
||||
info!("Clearing the display.");
|
||||
oled.clear();
|
||||
info!("Flushing the display.");
|
||||
oled.flush().unwrap_or_else(|_| {
|
||||
error!("Problem flushing the OLED display.");
|
||||
process::exit(1);
|
||||
});
|
||||
Ok(Value::String("success".into()))
|
||||
});
|
||||
|
||||
let oled_clone = Arc::clone(&oled);
|
||||
|
||||
io.add_method("draw", move |params: Params| {
|
||||
let g: Result<Graphic, Error> = params.parse();
|
||||
let g: Graphic = g?;
|
||||
// TODO: add simple byte validation function
|
||||
let mut oled = oled_clone.lock().unwrap();
|
||||
info!("Drawing image to the display.");
|
||||
let im =
|
||||
Image1BPP::new(&g.bytes, g.width, g.height).translate(Coord::new(g.x_coord, g.y_coord));
|
||||
oled.draw(im.into_iter());
|
||||
Ok(Value::String("success".into()))
|
||||
});
|
||||
|
||||
let oled_clone = Arc::clone(&oled);
|
||||
|
||||
io.add_method("flush", move |_| {
|
||||
let mut oled = oled_clone.lock().unwrap();
|
||||
info!("Flushing the display.");
|
||||
oled.flush().unwrap_or_else(|_| {
|
||||
error!("Problem flushing the OLED display.");
|
||||
process::exit(1);
|
||||
});
|
||||
Ok(Value::String("success".into()))
|
||||
});
|
||||
|
||||
let oled_clone = Arc::clone(&oled);
|
||||
|
||||
io.add_method("ping", |_| Ok(Value::String("success".to_string())));
|
||||
|
||||
io.add_method("power", move |params: Params| {
|
||||
let o: Result<On, Error> = params.parse();
|
||||
let o: On = o?;
|
||||
let mut oled = oled_clone.lock().unwrap();
|
||||
if o.on {
|
||||
info!("Turning the display on.");
|
||||
} else {
|
||||
info!("Turnin the display off.");
|
||||
}
|
||||
oled.display_on(o.on).unwrap_or_else(|_| {
|
||||
error!("Problem turning the display on.");
|
||||
process::exit(1);
|
||||
});
|
||||
Ok(Value::String("success".into()))
|
||||
});
|
||||
|
||||
let oled_clone = Arc::clone(&oled);
|
||||
|
||||
io.add_method("write", move |params: Params| {
|
||||
info!("Received a 'write' request.");
|
||||
let m: Result<Msg, Error> = params.parse();
|
||||
let m: Msg = m?;
|
||||
validate(&m)?;
|
||||
|
||||
let mut oled = oled_clone.lock().unwrap();
|
||||
|
||||
info!("Writing to the display.");
|
||||
if m.font_size == "6x8" {
|
||||
oled.draw(
|
||||
Font6x8::render_str(&m.string)
|
||||
.translate(Coord::new(m.x_coord, m.y_coord))
|
||||
.into_iter(),
|
||||
);
|
||||
} else if m.font_size == "6x12" {
|
||||
oled.draw(
|
||||
Font6x12::render_str(&m.string)
|
||||
.translate(Coord::new(m.x_coord, m.y_coord))
|
||||
.into_iter(),
|
||||
);
|
||||
} else if m.font_size == "8x16" {
|
||||
oled.draw(
|
||||
Font8x16::render_str(&m.string)
|
||||
.translate(Coord::new(m.x_coord, m.y_coord))
|
||||
.into_iter(),
|
||||
);
|
||||
} else if m.font_size == "12x16" {
|
||||
oled.draw(
|
||||
Font12x16::render_str(&m.string)
|
||||
.translate(Coord::new(m.x_coord, m.y_coord))
|
||||
.into_iter(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Value::String("success".into()))
|
||||
});
|
||||
|
||||
let http_server =
|
||||
env::var("PEACH_OLED_SERVER").unwrap_or_else(|_| "127.0.0.1:5112".to_string());
|
||||
|
||||
info!("Starting JSON-RPC server on {}.", http_server);
|
||||
let server = ServerBuilder::new(io)
|
||||
.cors(DomainsValidation::AllowOnly(vec![
|
||||
AccessControlAllowOrigin::Null,
|
||||
]))
|
||||
.start_http(
|
||||
&http_server
|
||||
.parse()
|
||||
.expect("Invalid HTTP address and port combination"),
|
||||
)
|
||||
.expect("Unable to start RPC server");
|
||||
|
||||
info!("Listening for requests.");
|
||||
server.wait();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use hal::i2cdev::linux::LinuxI2CError;
|
||||
use jsonrpc_core::ErrorCode;
|
||||
use nix::Error as NixError;
|
||||
use std::io::Error as IoError;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
// test to ensure correct success response
|
||||
#[test]
|
||||
fn rpc_success() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_success_response", |_| {
|
||||
Ok(Value::String("success".into()))
|
||||
});
|
||||
test::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(rpc.request("rpc_success_response", &()), r#""success""#);
|
||||
}
|
||||
|
||||
// test to ensure correct internal error response
|
||||
#[test]
|
||||
fn rpc_internal_error() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_internal_error", |_| Err(Error::internal_error()));
|
||||
test::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rpc.request("rpc_internal_error", &()),
|
||||
r#"{
|
||||
"code": -32603,
|
||||
"message": "Internal error"
|
||||
}"#
|
||||
);
|
||||
}
|
||||
|
||||
// test to ensure correct I2CError error response (io::Error variant)
|
||||
#[test]
|
||||
fn rpc_i2c_io_error() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_i2c_io_error", |_| {
|
||||
let io_err = IoError::new(ErrorKind::PermissionDenied, "oh no!");
|
||||
let source = LinuxI2CError::Io(io_err);
|
||||
Err(Error::from(OledError::I2CError { source }))
|
||||
});
|
||||
test::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rpc.request("rpc_i2c_io_error", &()),
|
||||
r#"{
|
||||
"code": -32000,
|
||||
"message": "I2C device error: oh no!"
|
||||
}"#
|
||||
);
|
||||
}
|
||||
|
||||
// test to ensure correct I2CError error response (nix::Error variant)
|
||||
#[test]
|
||||
fn rpc_i2c_nix_error() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_i2c_nix_error", |_| {
|
||||
let nix_err = NixError::InvalidPath;
|
||||
let source = LinuxI2CError::Nix(nix_err);
|
||||
Err(Error::from(OledError::I2CError { source }))
|
||||
});
|
||||
test::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rpc.request("rpc_i2c_nix_error", &()),
|
||||
r#"{
|
||||
"code": -32000,
|
||||
"message": "I2C device error: Invalid path"
|
||||
}"#
|
||||
);
|
||||
}
|
||||
|
||||
// test to ensure correct InvalidCoordinate error response
|
||||
#[test]
|
||||
fn rpc_invalid_coord() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_invalid_coord", |_| {
|
||||
Err(Error::from(OledError::InvalidCoordinate {
|
||||
coord: "x".to_string(),
|
||||
range: "0-128".to_string(),
|
||||
value: 321,
|
||||
}))
|
||||
});
|
||||
test::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rpc.request("rpc_invalid_coord", &()),
|
||||
r#"{
|
||||
"code": -32001,
|
||||
"message": "Validation error: coordinate x out of range 0-128: 321"
|
||||
}"#
|
||||
);
|
||||
}
|
||||
|
||||
// test to ensure correct InvalidFontSize error response
|
||||
#[test]
|
||||
fn rpc_invalid_fontsize() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_invalid_fontsize", |_| {
|
||||
Err(Error::from(OledError::InvalidFontSize {
|
||||
font: "24x32".to_string(),
|
||||
}))
|
||||
});
|
||||
test::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rpc.request("rpc_invalid_fontsize", &()),
|
||||
r#"{
|
||||
"code": -32002,
|
||||
"message": "Validation error: 24x32 is not an accepted font size. Use 6x8, 6x12, 8x16 or 12x16 instead"
|
||||
}"#
|
||||
);
|
||||
}
|
||||
|
||||
// test to ensure correct InvalidString error response
|
||||
#[test]
|
||||
fn rpc_invalid_string() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_invalid_string", |_| {
|
||||
Err(Error::from(OledError::InvalidString { len: 22 }))
|
||||
});
|
||||
test::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rpc.request("rpc_invalid_string", &()),
|
||||
r#"{
|
||||
"code": -32003,
|
||||
"message": "Validation error: string length 22 out of range 0-21"
|
||||
}"#
|
||||
);
|
||||
}
|
||||
|
||||
// test to ensure correct invalid parameters error response
|
||||
#[test]
|
||||
fn rpc_invalid_params() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_invalid_params", |_| {
|
||||
let e = Error {
|
||||
code: ErrorCode::InvalidParams,
|
||||
message: String::from("invalid params"),
|
||||
data: Some(Value::String(
|
||||
"Invalid params: invalid type: null, expected struct Msg.".into(),
|
||||
)),
|
||||
};
|
||||
Err(Error::from(OledError::MissingParameter { e }))
|
||||
});
|
||||
test::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rpc.request("rpc_invalid_params", &()),
|
||||
r#"{
|
||||
"code": -32602,
|
||||
"message": "invalid params",
|
||||
"data": "Invalid params: invalid type: null, expected struct Msg."
|
||||
}"#
|
||||
);
|
||||
}
|
||||
|
||||
// test to ensure correct parse error response
|
||||
#[test]
|
||||
fn rpc_parse_error() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_parse_error", |_| {
|
||||
let e = Error {
|
||||
code: ErrorCode::ParseError,
|
||||
message: String::from("Parse error"),
|
||||
data: None,
|
||||
};
|
||||
Err(Error::from(OledError::ParseError { e }))
|
||||
});
|
||||
test::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rpc.request("rpc_parse_error", &()),
|
||||
r#"{
|
||||
"code": -32700,
|
||||
"message": "Parse error"
|
||||
}"#
|
||||
);
|
||||
}
|
||||
}
|
14
peach-oled/src/main.rs
Normal file
14
peach-oled/src/main.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use std::process;
|
||||
|
||||
use log::error;
|
||||
|
||||
fn main() {
|
||||
// initialize the logger
|
||||
env_logger::init();
|
||||
|
||||
// handle errors returned from `run`
|
||||
if let Err(e) = peach_oled::run() {
|
||||
error!("Application error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user