Переглянути джерело

display: Show dummy weather station values.

Mathias Gottschlag 5 роки тому
джерело
коміт
62a7d7210a

+ 2
- 2
display/firmware/Cargo.lock Переглянути файл

@@ -400,12 +400,12 @@ dependencies = [
400 400
 [[package]]
401 401
 name = "tinygfx"
402 402
 version = "0.1.0"
403
-source = "git+https://github.com/mgottschlag/tinygfx#6560a8e340ce288b9d3be0fad4933a9e22a74417"
403
+source = "git+https://github.com/mgottschlag/tinygfx#e2363c5489bb74bd8abb7f9e2a63220880b5a66e"
404 404
 
405 405
 [[package]]
406 406
 name = "tinygfx-assets"
407 407
 version = "0.1.0"
408
-source = "git+https://github.com/mgottschlag/tinygfx#6560a8e340ce288b9d3be0fad4933a9e22a74417"
408
+source = "git+https://github.com/mgottschlag/tinygfx#e2363c5489bb74bd8abb7f9e2a63220880b5a66e"
409 409
 dependencies = [
410 410
  "freetype-rs 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
411 411
  "lodepng 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",

BIN
display/firmware/assets/ic_thermometer_black_36dp.png Переглянути файл


BIN
display/firmware/assets/ic_water_percent_black_36dp.png Переглянути файл


BIN
display/firmware/assets/ic_wifi_strength_alert_outline_black_36dp.png Переглянути файл


+ 23
- 9
display/firmware/build.rs Переглянути файл

@@ -18,18 +18,32 @@ fn main() {
18 18
     let epd_font = font.generate(
19 19
         "ROBOTO_30",
20 20
         30,
21
-        " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:.",
21
+        " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:.,%°ß↗",
22 22
         "::tinygfx",
23 23
     );
24
-
25 24
     f.write_all(epd_font.as_bytes()).unwrap();
26 25
 
27
-    let clock_image = assets::Image::load("assets/ic_alarm_black_36dp.png").unwrap();
26
+    let epd_font = font.generate(
27
+        "ROBOTO_22",
28
+        22,
29
+        " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:.,%°ß",
30
+        "::tinygfx",
31
+    );
32
+    f.write_all(epd_font.as_bytes()).unwrap();
28 33
 
29
-    f.write_all(
30
-        clock_image
31
-            .generate_bitmap("ALARM_CLOCK_36", "::tinygfx")
32
-            .as_bytes(),
33
-    )
34
-    .unwrap();
34
+    for (filename, name) in [
35
+        ("assets/ic_alarm_black_36dp.png", "ALARM_CLOCK_36"),
36
+        ("assets/ic_thermometer_black_36dp.png", "THERMOMETER_36"),
37
+        ("assets/ic_water_percent_black_36dp.png", "WATER_PERCENT_36"),
38
+        (
39
+            "assets/ic_wifi_strength_alert_outline_black_36dp.png",
40
+            "WIFI_STRENGTH_ALERT_36",
41
+        ),
42
+    ]
43
+    .iter()
44
+    {
45
+        let clock_image = assets::Image::load(filename).unwrap();
46
+        f.write_all(clock_image.generate_bitmap(name, "::tinygfx").as_bytes())
47
+            .unwrap();
48
+    }
35 49
 }

+ 347
- 9
display/firmware/src/display.rs Переглянути файл

@@ -4,10 +4,11 @@ use epd_waveshare::epd4in2::EPD4in2;
4 4
 use epd_waveshare::prelude::{DisplayStream, WaveshareDisplay};
5 5
 use mkl25z4_hal::time::CopyableMonoTimer;
6 6
 use tinygfx::color::BlackWhite::{self, Black, White};
7
-use tinygfx::{Frame, Rectangle, Renderer, Text};
7
+use tinygfx::image::{MonoBitmapImage, MonoImageData};
8
+use tinygfx::{Clip, Frame, MonoImage, Rectangle, Renderer, Text, TextAlignment};
8 9
 
9
-use super::assets::ROBOTO_30;
10
-use super::information::Information;
10
+use super::assets::*;
11
+use super::information::{Information, TemperatureHumidity};
11 12
 use super::pins::{
12 13
     DisplayBmeSpi, DisplayBusy, DisplayCs, DisplayDc, DisplayPins, DisplayPwr, DisplayRst,
13 14
 };
@@ -54,7 +55,7 @@ impl Display {
54 55
     pub fn update(
55 56
         &mut self,
56 57
         _display_state: &DisplayState,
57
-        _info: &Information,
58
+        info: &Information,
58 59
         spi: &mut DisplayBmeSpi,
59 60
         mut time: CopyableMonoTimer,
60 61
     ) {
@@ -66,14 +67,70 @@ impl Display {
66 67
         // TODO: Error handling.
67 68
         self.epd.wake_up(spi, &mut time).ok();
68 69
 
69
-        // TODO: Draw the display contents.
70
-        let r = Rectangle::new(10, 10, 100, 50, Black);
71
-        let t = Text::new(20, 100, "asdf", &ROBOTO_30, Black);
70
+        // TODO: Insert values.
71
+        let date = Text::new(8, 8, "Samstag, 25.04.2020", &ROBOTO_22, Black);
72
+        let mut time = Text::new(392, 8, "12:34", &ROBOTO_22, Black);
73
+        time.align(TextAlignment::Right);
74
+        let top_bar = Rectangle::new(0, 34, 400, 1, Black);
75
+
76
+        let tile_width = 400 / 2 - 1;
77
+        let tile_height = (300 - 35) / 2 - 1;
78
+
79
+        let outside_strings = info.outside.map(|x| TempHumStrings::new(x));
80
+        let inside_strings = info.inside.map(|x| TempHumStrings::new(x));
81
+        let bathroom_strings = info.bathroom.map(|x| TempHumStrings::new(x));
82
+        let pressure_string = info.pressure.map(|x| PressureString::new(x));
83
+
84
+        let inside_tile = TempHumTile::new(
85
+            "Innen",
86
+            inside_strings.as_ref(),
87
+            0,
88
+            35,
89
+            tile_width,
90
+            tile_height,
91
+        );
92
+        let outside_tile = TempHumTile::new(
93
+            "Außen",
94
+            outside_strings.as_ref(),
95
+            200,
96
+            35,
97
+            tile_width,
98
+            tile_height,
99
+        );
100
+        let bathroom_tile = TempHumTile::new(
101
+            "Bad",
102
+            bathroom_strings.as_ref(),
103
+            0,
104
+            35 + tile_height + 1,
105
+            tile_width,
106
+            tile_height,
107
+        );
108
+        let pressure_tile = PressureTile::new(
109
+            pressure_string.as_ref(),
110
+            200,
111
+            35 + tile_height + 1,
112
+            tile_width,
113
+            tile_height,
114
+        );
115
+
116
+        let vert_separator = Rectangle::new(8, 35 + tile_height, 392, 1, Black);
117
+        let hor_separator = Rectangle::new(tile_width, 35 + 8, 1, 300 - 35 - 16, Black);
118
+
72 119
         let mut frame = Frame::new(400, 300, |mut renderer| {
73 120
             let clip = renderer.full_frame();
74 121
             renderer.clear(White);
75
-            r.draw(clip, &mut renderer);
76
-            t.draw(clip, &mut renderer);
122
+
123
+            top_bar.draw(clip, &mut renderer);
124
+            date.draw(clip, &mut renderer);
125
+            time.draw(clip, &mut renderer);
126
+
127
+            vert_separator.draw(clip, &mut renderer);
128
+            hor_separator.draw(clip, &mut renderer);
129
+
130
+            inside_tile.draw(clip, &mut renderer);
131
+            outside_tile.draw(clip, &mut renderer);
132
+            bathroom_tile.draw(clip, &mut renderer);
133
+            pressure_tile.draw(clip, &mut renderer);
77 134
         });
78 135
         frame.mirror_x(true);
79 136
         frame.mirror_y(true);
@@ -87,6 +144,287 @@ impl Display {
87 144
     }
88 145
 }
89 146
 
147
+struct TempHumStrings {
148
+    temp: [u8; 9],
149
+    temp_length: usize,
150
+    hum: [u8; 6],
151
+    hum_length: usize,
152
+}
153
+
154
+impl TempHumStrings {
155
+    fn new(data: TemperatureHumidity) -> TempHumStrings {
156
+        // TODO: Refactor the printing code.
157
+        let (temp, temp_length) = {
158
+            let mut x = data.temperature;
159
+            let mut length = 0;
160
+            let mut buffer = [0u8; 9];
161
+            let mut cursor = &mut buffer[..];
162
+            if x < 0 {
163
+                cursor[0] = '-' as u8;
164
+                x = -x;
165
+                cursor = &mut cursor[1..];
166
+                length += 1;
167
+            }
168
+            let orig_x = x;
169
+            x = i32::min(x, 9999);
170
+            if orig_x >= 1000 {
171
+                cursor[0] = '0' as u8 + (x / 1000) as u8;
172
+                cursor = &mut cursor[1..];
173
+                x %= 1000;
174
+                length += 1;
175
+            }
176
+            if orig_x >= 100 {
177
+                cursor[0] = '0' as u8 + (x / 100) as u8;
178
+                cursor = &mut cursor[1..];
179
+                x %= 100;
180
+                length += 1;
181
+            }
182
+            cursor[0] = '0' as u8 + (x / 10) as u8;
183
+            cursor[1] = ',' as u8;
184
+            cursor[2] = '0' as u8 + (x % 10) as u8;
185
+            cursor[3] = 0xc2;
186
+            cursor[4] = 0xb0;
187
+            cursor[5] = 'C' as u8;
188
+            length += 6;
189
+            (buffer, length)
190
+        };
191
+        let (hum, hum_length) = {
192
+            let mut x = data.humidity;
193
+            let mut length = 0;
194
+            let mut buffer = [0u8; 6];
195
+            let mut cursor = &mut buffer[..];
196
+            x = u32::min(x, 1000);
197
+            let orig_x = x;
198
+            if orig_x >= 1000 {
199
+                cursor[0] = '1' as u8;
200
+                cursor = &mut cursor[1..];
201
+                x %= 1000;
202
+                length += 1;
203
+            }
204
+            if orig_x >= 100 {
205
+                cursor[0] = '0' as u8 + (x / 100) as u8;
206
+                cursor = &mut cursor[1..];
207
+                x %= 100;
208
+                length += 1;
209
+            }
210
+            cursor[0] = '0' as u8 + (x / 10) as u8;
211
+            cursor[1] = ',' as u8;
212
+            cursor[2] = '0' as u8 + (x % 10) as u8;
213
+            cursor[3] = '%' as u8;
214
+            length += 4;
215
+            (buffer, length)
216
+        };
217
+        TempHumStrings {
218
+            temp,
219
+            temp_length,
220
+            hum,
221
+            hum_length,
222
+        }
223
+    }
224
+
225
+    fn temperature<'a>(&'a self) -> &'a str {
226
+        unsafe {
227
+            // Safe, we did not use any non-UTF characters above.
228
+            core::str::from_utf8_unchecked(&self.temp[0..self.temp_length])
229
+        }
230
+    }
231
+
232
+    fn humidity<'a>(&'a self) -> &'a str {
233
+        unsafe {
234
+            // Safe, we did not use any non-UTF characters above.
235
+            core::str::from_utf8_unchecked(&self.hum[0..self.hum_length])
236
+        }
237
+    }
238
+}
239
+
240
+struct TempHumTile<'a> {
241
+    title: Text<'a, BlackWhite>,
242
+    data: Option<(
243
+        MonoImage<'static, MonoBitmapImage, BlackWhite>,
244
+        Text<'a, BlackWhite>,
245
+        MonoImage<'static, MonoBitmapImage, BlackWhite>,
246
+        Text<'a, BlackWhite>,
247
+    )>,
248
+    error: Option<MonoImage<'static, MonoBitmapImage, BlackWhite>>,
249
+}
250
+
251
+impl<'a> TempHumTile<'a> {
252
+    fn new(
253
+        title: &'a str,
254
+        data: Option<&'a TempHumStrings>,
255
+        left: i32,
256
+        top: i32,
257
+        width: i32,
258
+        height: i32,
259
+    ) -> Self {
260
+        let center = left + width / 2;
261
+
262
+        let mut title = Text::new(center, top + 10, title, &ROBOTO_30, Black);
263
+        title.align(TextAlignment::Center);
264
+
265
+        let mut tile = Self {
266
+            title,
267
+            data: None,
268
+            error: None,
269
+        };
270
+
271
+        if let Some(data) = data {
272
+            let temp_width =
273
+                ROBOTO_30.get_text_size(data.temperature()).0 + 1 + THERMOMETER_36.width();
274
+            let temp_icon = MonoImage::new(
275
+                center - (temp_width / 2) as i32,
276
+                top + 48,
277
+                &THERMOMETER_36,
278
+                Black,
279
+            );
280
+            let mut temperature = Text::new(
281
+                center + (temp_width - temp_width / 2) as i32,
282
+                top + 48,
283
+                data.temperature(),
284
+                &ROBOTO_30,
285
+                Black,
286
+            );
287
+            temperature.align(TextAlignment::Right);
288
+            let hum_width = ROBOTO_30.get_text_size(data.humidity()).0 + 1 + THERMOMETER_36.width();
289
+            let hum_icon = MonoImage::new(
290
+                center - (hum_width / 2) as i32,
291
+                top + 82,
292
+                &WATER_PERCENT_36,
293
+                Black,
294
+            );
295
+            let mut humidity = Text::new(
296
+                center + (hum_width - hum_width / 2) as i32,
297
+                top + 82,
298
+                data.humidity(),
299
+                &ROBOTO_30,
300
+                Black,
301
+            );
302
+            humidity.align(TextAlignment::Right);
303
+
304
+            tile.data = Some((temp_icon, temperature, hum_icon, humidity));
305
+        } else {
306
+            tile.error = Some(MonoImage::new(
307
+                center - (WIFI_STRENGTH_ALERT_36.width() / 2) as i32,
308
+                top + height / 2 - (WIFI_STRENGTH_ALERT_36.height() / 2) as i32,
309
+                &WIFI_STRENGTH_ALERT_36,
310
+                Black,
311
+            ));
312
+        }
313
+
314
+        tile
315
+    }
316
+
317
+    fn draw(&self, clip: Clip, renderer: &mut Renderer<BlackWhite>) {
318
+        self.title.draw(clip, renderer);
319
+        if let Some(data) = self.data.as_ref() {
320
+            data.0.draw(clip, renderer);
321
+            data.1.draw(clip, renderer);
322
+            data.2.draw(clip, renderer);
323
+            data.3.draw(clip, renderer);
324
+        }
325
+        if let Some(error) = self.error.as_ref() {
326
+            error.draw(clip, renderer);
327
+        }
328
+    }
329
+}
330
+
331
+struct PressureString {
332
+    buffer: [u8; 9],
333
+    length: usize,
334
+}
335
+
336
+impl PressureString {
337
+    fn new(pressure: u32) -> PressureString {
338
+        let mut x = pressure;
339
+        let mut length = 0;
340
+        let mut buffer = [0u8; 9];
341
+        let mut cursor = &mut buffer[..];
342
+        // Round to one decimal place.
343
+        x = (x + 5) / 10;
344
+        x = u32::min(x, 99999);
345
+        if pressure >= 10000 {
346
+            cursor[0] = '0' as u8 + (x / 10000) as u8;
347
+            cursor = &mut cursor[1..];
348
+            x %= 10000;
349
+            length += 1;
350
+        }
351
+        if pressure >= 1000 {
352
+            cursor[0] = '0' as u8 + (x / 1000) as u8;
353
+            cursor = &mut cursor[1..];
354
+            x %= 1000;
355
+            length += 1;
356
+        }
357
+        if pressure >= 100 {
358
+            cursor[0] = '0' as u8 + (x / 100) as u8;
359
+            cursor = &mut cursor[1..];
360
+            x %= 100;
361
+            length += 1;
362
+        }
363
+        cursor[0] = '0' as u8 + (x / 10) as u8;
364
+        cursor[1] = ',' as u8;
365
+        cursor[2] = '0' as u8 + (x % 10) as u8;
366
+        cursor[3] = 'h' as u8;
367
+        cursor[4] = 'P' as u8;
368
+        cursor[5] = 'a' as u8;
369
+        length += 6;
370
+        PressureString { buffer, length }
371
+    }
372
+
373
+    fn to_str<'a>(&'a self) -> &'a str {
374
+        unsafe {
375
+            // Safe, we did not use any non-UTF characters above.
376
+            core::str::from_utf8_unchecked(&self.buffer[0..self.length])
377
+        }
378
+    }
379
+}
380
+
381
+struct PressureTile<'a> {
382
+    title: Text<'a, BlackWhite>,
383
+    data: Option<Text<'a, BlackWhite>>,
384
+    error: Option<MonoImage<'static, MonoBitmapImage, BlackWhite>>,
385
+}
386
+
387
+impl<'a> PressureTile<'a> {
388
+    fn new(data: Option<&'a PressureString>, left: i32, top: i32, width: i32, height: i32) -> Self {
389
+        let center = left + width / 2;
390
+
391
+        let mut title = Text::new(center, top + 10, "Luftdruck", &ROBOTO_30, Black);
392
+        title.align(TextAlignment::Center);
393
+
394
+        let mut tile = Self {
395
+            title,
396
+            data: None,
397
+            error: None,
398
+        };
399
+
400
+        if let Some(data) = data {
401
+            let mut pressure = Text::new(center, top + 48, data.to_str(), &ROBOTO_30, Black);
402
+            pressure.align(TextAlignment::Center);
403
+
404
+            tile.data = Some(pressure);
405
+        } else {
406
+            tile.error = Some(MonoImage::new(
407
+                center - (WIFI_STRENGTH_ALERT_36.width() / 2) as i32,
408
+                top + height / 2 - (WIFI_STRENGTH_ALERT_36.height() / 2) as i32,
409
+                &WIFI_STRENGTH_ALERT_36,
410
+                Black,
411
+            ));
412
+        }
413
+
414
+        tile
415
+    }
416
+
417
+    fn draw(&self, clip: Clip, renderer: &mut Renderer<BlackWhite>) {
418
+        self.title.draw(clip, renderer);
419
+        if let Some(data) = self.data.as_ref() {
420
+            data.draw(clip, renderer);
421
+        }
422
+        if let Some(error) = self.error.as_ref() {
423
+            error.draw(clip, renderer);
424
+        }
425
+    }
426
+}
427
+
90 428
 struct TinyGfxStream<Draw> {
91 429
     frame: Frame<Draw, BlackWhite>,
92 430
     buffer: [u8; 400 / 8],

+ 12
- 1
display/firmware/src/information.rs Переглянути файл

@@ -3,5 +3,16 @@
3 3
 pub struct Information {
4 4
     pub time: u32,
5 5
     pub valid: bool,
6
-    // TODO
6
+
7
+    pub inside: Option<TemperatureHumidity>,
8
+    pub outside: Option<TemperatureHumidity>,
9
+    pub bathroom: Option<TemperatureHumidity>,
10
+
11
+    pub pressure: Option<u32>,
12
+}
13
+
14
+#[derive(Copy, Clone)]
15
+pub struct TemperatureHumidity {
16
+    pub temperature: i32,
17
+    pub humidity: u32,
7 18
 }

+ 13
- 2
display/firmware/src/main.rs Переглянути файл

@@ -16,7 +16,7 @@ use mkl25z4_hal::time::{CopyableMonoTimer, NonCopyableMonoTimer, U32Ext};
16 16
 use panic_semihosting as _;
17 17
 
18 18
 use display::{Display, DisplayState};
19
-use information::Information;
19
+use information::{Information, TemperatureHumidity};
20 20
 use pins::DisplayBmeSpi;
21 21
 
22 22
 #[rtfm::app(device = mkl25z4_hal::mkl25z4, peripherals = true)]
@@ -63,6 +63,10 @@ const APP: () = {
63 63
             info: Information {
64 64
                 time: 0,
65 65
                 valid: false,
66
+                inside: None,
67
+                outside: None,
68
+                bathroom: None,
69
+                pressure: None,
66 70
             },
67 71
             display_state: DisplayState{
68 72
                 // TODO
@@ -88,7 +92,7 @@ const APP: () = {
88 92
     }
89 93
 
90 94
     #[task(priority = 1, spawn = [update_display], resources = [info])]
91
-    fn fetch_update(ctx: fetch_update::Context) {
95
+    fn fetch_update(mut ctx: fetch_update::Context) {
92 96
         // Fetch local updates.
93 97
         // TODO
94 98
 
@@ -98,6 +102,13 @@ const APP: () = {
98 102
 
99 103
         // Fetch updates via radio.
100 104
         // TODO
105
+        ctx.resources.info.lock(|info| {
106
+            info.inside = Some(TemperatureHumidity {
107
+                temperature: -212,
108
+                humidity: 345,
109
+            });
110
+            info.pressure = Some(100000);
111
+        });
101 112
 
102 113
         // If the update failed, simply increment the time and place a hint that
103 114
         // the information might be invalid.

Завантаження…
Відмінити
Зберегти