@@ 4,6 4,7 @@ use pokemon::pokedex::{
self, LearnCondition, LearnableMove, Move, MoveListChunk, Pokemon, StatData, Type,
};
use serde_json::Value;
+use std::ascii::AsciiExt;
use std::collections::HashMap;
use std::fs::read_dir;
use std::io::{prelude::*, BufReader, BufWriter, Cursor};
@@ 182,11 183,10 @@ fn parse_move(json: Value) -> Result<Move, &'static str> {
fn parse_pokemon(json: &Value, bitmap: &[u8; 578]) -> Result<Pokemon, &'static str> {
let name = match json["name"].as_str() {
Some(n) => {
- let mut i = 0;
- let mut name = ['\0'; 12];
- for c in n.chars() {
- name[i] = c;
- i += 1;
+ let mut name = [08; 12];
+ let tmp = n.as_bytes();
+ for (i, c) in tmp.iter().enumerate() {
+ name[i] = c.to_ascii_uppercase();
}
name
}
@@ 6,7 6,7 @@ pub mod pokedex {
#[derive(Encode, Decode, Debug)]
pub struct Pokemon {
#[n(0)] pub id: u8,
- #[n(1)] pub name: [char; 12],
+ #[n(1)] pub name: [u8; 12],
#[n(2)] pub type_primary: Type,
#[n(3)] pub type_secondary: Option<Type>,
@@ 20,7 20,7 @@ pub mod pokedex {
#[n(11)] pub special_defense: StatData,
#[n(12)] pub speed: StatData,
- #[b(13)] pub sprite: ByteArray<578>,
+ #[n(13)] pub sprite: ByteArray<578>,
}
#[derive(Encode, Decode, Debug)]
@@ 3,51 3,143 @@
#[macro_use(block)]
extern crate nb;
+use bitbang_hal;
use embedded_graphics::fonts::Text;
use embedded_graphics::image::Image;
-use embedded_graphics::style::TextStyleBuilder;
+use embedded_graphics::primitives::Rectangle;
+use embedded_graphics::style::{PrimitiveStyleBuilder, TextStyleBuilder};
+use embedded_graphics::{pixelcolor::BinaryColor, prelude::*};
+use embedded_picofont::FontPico;
use esp8266_hal::ehal::digital::v2::InputPin;
-use esp8266_hal::gpio::{PushPull, Gpio15, Output};
+use esp8266_hal::flash::ESPFlash;
+use esp8266_hal::gpio::{
+ Gpio0, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio2, Input, Output, PullDown, PullUp, PushPull,
+};
use esp8266_hal::prelude::*;
use esp8266_hal::target::Peripherals;
-use esp8266_hal::time::{Nanoseconds, KiloHertz};
+use esp8266_hal::time::{KiloHertz, Nanoseconds};
use esp8266_hal::timer::Timer1;
+use minicbor::decode;
use panic_halt as _;
-use bitbang_hal;
+use pokemon::pokedex::Pokemon;
use sh1106::{prelude::*, Builder};
-use embedded_picofont::FontPico;
-use embedded_graphics::{
- pixelcolor::BinaryColor,
- prelude::*,
-};
use tinybmp::Bmp;
+enum Buttons {
+ LEFT,
+ UP,
+ RIGHT,
+ DOWN,
+ A,
+ B,
+}
+
+impl Buttons {
+ fn to_index(&self) -> usize {
+ match self {
+ Buttons::LEFT => 0,
+ Buttons::UP => 1,
+ Buttons::RIGHT => 2,
+ Buttons::DOWN => 3,
+ Buttons::A => 4,
+ Buttons::B => 5,
+ }
+ }
+}
+
+struct ButtonStates {
+ pressed: [bool; 6],
+ was_consumed: [bool; 6],
+}
+
+impl ButtonStates {
+ fn new() -> ButtonStates {
+ return ButtonStates {
+ pressed: [false; 6],
+ was_consumed: [false; 6],
+ };
+ }
+ fn is_pressed(&self, button: Buttons) -> bool {
+ return self.pressed[button.to_index()] && !self.was_consumed[button.to_index()];
+ }
+ fn consume(&mut self, button: Buttons) -> bool {
+ if self.pressed[button.to_index()] && !self.was_consumed[button.to_index()] {
+ self.was_consumed[button.to_index()] = true;
+ return true;
+ }
+ return false;
+ }
+ fn update(&mut self, button: Buttons, pressed: bool) {
+ if pressed && !self.pressed[button.to_index()] {
+ self.pressed[button.to_index()] = true;
+ self.was_consumed[button.to_index()] = false;
+ } else if !pressed && self.pressed[button.to_index()] {
+ self.pressed[button.to_index()] = false;
+ }
+ }
+}
+
+fn update_buttons(
+ states: &mut ButtonStates,
+ mut left: Gpio16<Input<PullDown>>,
+ up: &Gpio14<Input<PullUp>>,
+ right: &Gpio13<Input<PullUp>>,
+ down: &Gpio12<Input<PullUp>>,
+ a: &Gpio2<Input<PullUp>>,
+ b: &Gpio0<Input<PullUp>>,
+) -> Gpio16<Input<PullDown>> {
+ // Easy pins first.
+ states.update(Buttons::UP, up.is_low().unwrap_or(false));
+ states.update(Buttons::RIGHT, right.is_low().unwrap_or(false));
+ states.update(Buttons::DOWN, down.is_low().unwrap_or(false));
+ states.update(Buttons::A, a.is_low().unwrap_or(false));
+ states.update(Buttons::B, b.is_low().unwrap_or(false));
+
+ // Gpio16 is a bit weird. It does low when pressed, and we need to pull
+ // it up afterwards ourselves. GPIO pins are modelled using ownership
+ // rules, so we need to take the action pin as a move in our args, then
+ // give it back once we're done with it.
+ let pressed = left.is_low().unwrap();
+ states.update(Buttons::LEFT, pressed);
+ if pressed {
+ let mut output = left.into_push_pull_output();
+ output.set_high().ok();
+ left = output.into_floating_input();
+ }
+ return left;
+}
+
#[entry]
fn main() -> ! {
let dp = Peripherals::take().unwrap();
let pins = dp.GPIO.split();
// Button configuration!
- // 16 is left. it must be floating, and pulled high after input.
- // 0 is b
- // 2 is a
- // 14 is up
- // 12 is down
- // 13 is right
- let mut configuring = pins.gpio16.into_push_pull_output();
- configuring.set_low().ok();
- let mut button = configuring.into_floating_input();
+ let mut btns = ButtonStates::new();
+ let mut btn_left = pins.gpio16.into_floating_input();
+ let btn_up = pins.gpio14.into_pull_up_input();
+ let btn_right = pins.gpio13.into_pull_up_input();
+ let btn_down = pins.gpio12.into_pull_up_input();
+ let btn_a = pins.gpio2.into_pull_up_input();
+ let btn_b = pins.gpio0.into_pull_up_input();
// Neopixel configuration!
let mut neopixel = pins.gpio15.into_push_pull_output();
let (mut timer1, mut timer2) = dp.TIMER.timers();
neopixel.set_low().unwrap();
+ // Flash configuration!
+ let mut storage = dp.SPI0.flash();
+ let mut magic_buf = [0u8; 8];
+ storage.read(0x200000, &mut magic_buf).unwrap();
+ let magic1 = u32::from_be_bytes(magic_buf[0..4].try_into().unwrap());
+ let magic2 = u32::from_be_bytes(magic_buf[4..8].try_into().unwrap());
+
// Display configuration!
let scl = pins.gpio5.into_open_drain_output(); // d1
let sda = pins.gpio4.into_open_drain_output(); // d2
- // Timer must be double the rate of the desired clock rate. Standard clock rates
- // are 100KHz, and 400 KHz.
+ // Timer must be double the rate of the desired clock rate. Standard clock rates
+ // are 100KHz, and 400 KHz.
timer2.start(KiloHertz(200));
let i2c = bitbang_hal::i2c::I2cBB::new(scl, sda, timer2);
let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into();
@@ 55,19 147,71 @@ fn main() -> ! {
display.init().unwrap();
display.flush().ok();
- let text_style = TextStyleBuilder::new(FontPico).text_color(BinaryColor::On).build();
- Text::new("I AM GAY!", Point::new(0, 0)).into_styled(text_style).draw(&mut display).ok();
+ let clear_style = PrimitiveStyleBuilder::new()
+ .fill_color(BinaryColor::Off)
+ .build();
+ let text_style = TextStyleBuilder::new(FontPico)
+ .text_color(BinaryColor::On)
+ .build();
- let image_data = include_bytes!("../mockup.bmp");
- let image = Bmp::from_slice(image_data).unwrap();
- let mut real_image = Image::new(&image, Point::zero());
- real_image.translate_mut(Point::new(0,0));
- real_image.draw(&mut display).ok();
display.flush().ok();
- loop {}
+ let mut pokemon_id = 1;
+ let mut pokemon: Pokemon;
+ let mut has_drawn = false;
+ loop {
+ btn_left = update_buttons(
+ &mut btns, btn_left, &btn_up, &btn_right, &btn_down, &btn_a, &btn_b,
+ );
+
+ if btns.consume(Buttons::UP) {
+ pokemon_id += 1;
+ has_drawn = false
+ }
+ if btns.consume(Buttons::DOWN) {
+ pokemon_id -= 1;
+ has_drawn = false
+ }
+ if pokemon_id <= 0 {
+ pokemon_id += 151;
+ } else if pokemon_id > 151 {
+ pokemon_id -= 151;
+ }
+
+ if !has_drawn {
+ Rectangle::new(Point::new(0, 0), Point::new(60, 60)).into_styled(clear_style).draw(&mut display).ok();
+
+ has_drawn = true;
+ let res = read_pokemon(pokemon_id, &mut storage);
+ if res.is_err() {
+ Text::new("ERROR", Point::new(0, 0))
+ .into_styled(text_style)
+ .draw(&mut display)
+ .ok();
+ let ReadError(text) = res.err().unwrap();
+ Text::new(text, Point::new(0, 6))
+ .into_styled(text_style)
+ .draw(&mut display)
+ .ok();
+ display.flush().ok();
+ continue;
+ }
+
+ pokemon = res.unwrap();
+ Text::new(core::str::from_utf8(&pokemon.name).unwrap(), Point::new(0, 0))
+ .into_styled(text_style)
+ .draw(&mut display)
+ .ok();
+ let image = Bmp::from_slice(pokemon.sprite.as_ref()).unwrap();
+ let mut real_image = Image::new(&image, Point::zero());
+ real_image.translate_mut(Point::new(0,6));
+ real_image.draw(&mut display).ok();
+ display.flush().ok();
+ timer1.delay_ms(200);
+ }
+ }
-/* loop {
+ /* loop {
configuring = button.into_push_pull_output();
configuring.set_high().ok();
button = configuring.into_floating_input();
@@ 77,11 221,59 @@ fn main() -> ! {
} else {
show_colour(&mut timer1, &mut neopixel, 0x00, 0x00, 0x0f);
}
- timer1.delay_ms(50);
} */
}
-fn show_colour(timer: &mut Timer1, pin: &mut Gpio15<Output<PushPull>>, red: u8, green: u8, blue: u8) {
+const DATA_OFFSET: u32 = 0x200000;
+const DATA_BUFFER_SIZE: usize = 2048;
+
+#[derive(Debug)]
+struct ReadError(&'static str);
+
+fn read_pokemon(id: u8, flash: &mut ESPFlash) -> Result<Pokemon, ReadError> {
+ // First read this id's row in the lookup table.
+ let mut table_entry = [0u8; 8];
+ let row_offset = match DATA_OFFSET.checked_add(u32::from(id) * 8) {
+ Some(i) => i,
+ None => return Err(ReadError("row offset out of bounds")),
+ };
+ if flash.read(row_offset, &mut table_entry).is_err() {
+ return Err(ReadError("read row failed"));
+ }
+
+ // Parse the row data.
+ let offset = u32::from_be_bytes(table_entry[0..4].try_into().unwrap());
+ let size: usize = match u32::from_be_bytes(table_entry[4..8].try_into().unwrap()).try_into() {
+ Ok(i) => i,
+ Err(_) => return Err(ReadError("failed to convert size")),
+ };
+
+ // Prefer to return an error than to panic.
+ if size > DATA_BUFFER_SIZE {
+ return Err(ReadError("data larger than buffer"));
+ }
+
+ // Now we can read the data that the row was pointing to.
+ let mut buf = [0u8; DATA_BUFFER_SIZE];
+ let data_offset = match DATA_OFFSET.checked_add(offset) {
+ Some(i) => i,
+ None => return Err(ReadError("data offset overflowed")),
+ };
+ flash.read(data_offset, &mut buf[..size]);
+
+ match minicbor::decode(&buf) {
+ Ok(data) => return Ok(data),
+ Err(_) => return Err(ReadError("failed to decode data")),
+ }
+}
+
+fn show_colour(
+ timer: &mut Timer1,
+ pin: &mut Gpio15<Output<PushPull>>,
+ red: u8,
+ green: u8,
+ blue: u8,
+) {
timer.start(Nanoseconds(400));
show_component(timer, pin, green);
show_component(timer, pin, red);