1 /*
2 * Copyright (c) 2024 Glenn Andrews
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include "main.h"
8 #include <zephyr/kernel.h>
9 #include "smf_calculator_thread.h"
10 #include <zephyr/drivers/display.h>
11 #include <lvgl.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <lvgl_input_device.h>
15 #include <zephyr/logging/log.h>
16
17 #define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
18 LOG_MODULE_REGISTER(main_app);
19
20 K_MSGQ_DEFINE(output_msgq, CALCULATOR_STRING_LENGTH, 2, 1);
21
22 #define CALCULATOR_BUTTON_LABEL_LENGTH 4
23 struct calculator_button {
24 char label[CALCULATOR_BUTTON_LABEL_LENGTH];
25 struct calculator_event event;
26 };
27
28 /**
29 * Important: this enum MUST be in the order of the buttons on the screen
30 * otherwise the mapping from button index to function will break
31 */
32 enum calculator_ui_buttons {
33 BUTTON_CANCEL_ENTRY = 0,
34 BUTTON_CANCEL,
35 BUTTON_7,
36 BUTTON_8,
37 BUTTON_9,
38 BUTTON_DIVIDE,
39 BUTTON_4,
40 BUTTON_5,
41 BUTTON_6,
42 BUTTON_MULTIPLY,
43 BUTTON_1,
44 BUTTON_2,
45 BUTTON_3,
46 BUTTON_SUBTRACT,
47 BUTTON_0,
48 BUTTON_DECIMAL_POINT,
49 BUTTON_EQUALS,
50 BUTTON_ADD,
51 };
52
53 struct calculator_button buttons[] = {
54 [BUTTON_CANCEL_ENTRY] = {.label = "CE",
55 .event = {.event_id = CANCEL_ENTRY, .operand = 'E'}},
56 [BUTTON_CANCEL] = {.label = "C", .event = {.event_id = CANCEL_BUTTON, .operand = 'C'}},
57 [BUTTON_ADD] = {.label = "+", .event = {.event_id = OPERATOR, .operand = '+'}},
58 [BUTTON_SUBTRACT] = {.label = "-", .event = {.event_id = OPERATOR, .operand = '-'}},
59 [BUTTON_MULTIPLY] = {.label = "*", .event = {.event_id = OPERATOR, .operand = '*'}},
60 [BUTTON_DIVIDE] = {.label = "/", .event = {.event_id = OPERATOR, .operand = '/'}},
61 [BUTTON_DECIMAL_POINT] = {.label = ".",
62 .event = {.event_id = DECIMAL_POINT, .operand = '.'}},
63 [BUTTON_EQUALS] = {.label = "=", .event = {.event_id = EQUALS, .operand = '='}},
64 [BUTTON_0] = {.label = "0", .event = {.event_id = DIGIT_0, .operand = '0'}},
65 [BUTTON_1] = {.label = "1", .event = {.event_id = DIGIT_1_9, .operand = '1'}},
66 [BUTTON_2] = {.label = "2", .event = {.event_id = DIGIT_1_9, .operand = '2'}},
67 [BUTTON_3] = {.label = "3", .event = {.event_id = DIGIT_1_9, .operand = '3'}},
68 [BUTTON_4] = {.label = "4", .event = {.event_id = DIGIT_1_9, .operand = '4'}},
69 [BUTTON_5] = {.label = "5", .event = {.event_id = DIGIT_1_9, .operand = '5'}},
70 [BUTTON_6] = {.label = "6", .event = {.event_id = DIGIT_1_9, .operand = '6'}},
71 [BUTTON_7] = {.label = "7", .event = {.event_id = DIGIT_1_9, .operand = '7'}},
72 [BUTTON_8] = {.label = "8", .event = {.event_id = DIGIT_1_9, .operand = '8'}},
73 [BUTTON_9] = {.label = "9", .event = {.event_id = DIGIT_1_9, .operand = '9'}},
74 };
75
76 /* Where the result is printed */
77 static lv_obj_t *result_label;
78
79 /**
80 * LVGL v8.4 is not thread safe, so use a msgq to pass updates back
81 * to the thread that calls lv_task_handler()
82 */
update_display(const char * output)83 void update_display(const char *output)
84 {
85 while (k_msgq_put(&output_msgq, output, K_NO_WAIT) != 0) {
86 k_msgq_purge(&output_msgq);
87 }
88 }
89
lv_btn_matrix_click_callback(lv_event_t * e)90 static void lv_btn_matrix_click_callback(lv_event_t *e)
91 {
92 lv_event_code_t code = lv_event_get_code(e);
93 lv_obj_t *obj = lv_event_get_target(e);
94
95 if (code == LV_EVENT_PRESSED) {
96 uint32_t id;
97 int rc;
98
99 id = lv_btnmatrix_get_selected_btn(obj);
100 if (id >= ARRAY_SIZE(buttons)) {
101 LOG_ERR("Invalid button: %d", id);
102 return;
103 }
104
105 rc = post_calculator_event(&buttons[id].event, K_FOREVER);
106 if (rc != 0) {
107 LOG_ERR("could not post to msgq: %d", rc);
108 }
109 }
110 }
111
setup_display(void)112 static int setup_display(void)
113 {
114 const struct device *display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
115
116 if (!device_is_ready(display_dev)) {
117 LOG_ERR("Device not ready, aborting setup");
118 return -ENODEV;
119 }
120
121 static const char *const btnm_map[] = {buttons[BUTTON_CANCEL_ENTRY].label,
122 buttons[BUTTON_CANCEL].label,
123 "\n",
124 buttons[BUTTON_7].label,
125 buttons[BUTTON_8].label,
126 buttons[BUTTON_9].label,
127 buttons[BUTTON_DIVIDE].label,
128 "\n",
129 buttons[BUTTON_4].label,
130 buttons[BUTTON_5].label,
131 buttons[BUTTON_6].label,
132 buttons[BUTTON_MULTIPLY].label,
133 "\n",
134 buttons[BUTTON_1].label,
135 buttons[BUTTON_2].label,
136 buttons[BUTTON_3].label,
137 buttons[BUTTON_SUBTRACT].label,
138 "\n",
139 buttons[BUTTON_0].label,
140 buttons[BUTTON_DECIMAL_POINT].label,
141 buttons[BUTTON_EQUALS].label,
142 buttons[BUTTON_ADD].label,
143 "\n",
144 ""};
145
146 lv_obj_t *btn_matrix = lv_btnmatrix_create(lv_scr_act());
147
148 lv_btnmatrix_set_map(btn_matrix, (const char **)btnm_map);
149 lv_obj_align(btn_matrix, LV_ALIGN_BOTTOM_MID, 0, 0);
150 lv_obj_set_size(btn_matrix, lv_pct(CALC_BTN_WIDTH_PCT), lv_pct(CALC_BTN_HEIGHT_PCT));
151 lv_obj_add_event_cb(btn_matrix, lv_btn_matrix_click_callback, LV_EVENT_ALL, NULL);
152
153 result_label = lv_label_create(lv_scr_act());
154 lv_obj_set_width(result_label, lv_pct(CALC_RESULT_WIDTH_PCT));
155 lv_obj_set_style_text_align(result_label, LV_TEXT_ALIGN_RIGHT, 0);
156 lv_obj_align(result_label, LV_ALIGN_TOP_MID, 0, lv_pct(CALC_RESULT_OFFSET_PCT));
157
158 static lv_style_t style_shadow;
159
160 lv_style_init(&style_shadow);
161 lv_style_set_shadow_width(&style_shadow, 5);
162 lv_style_set_shadow_spread(&style_shadow, 2);
163 lv_style_set_shadow_color(&style_shadow, lv_palette_main(LV_PALETTE_GREY));
164 lv_obj_add_style(result_label, &style_shadow, 0);
165 update_display("0");
166
167 lv_task_handler();
168 display_blanking_off(display_dev);
169
170 return 0;
171 }
172
main(void)173 int main(void)
174 {
175 printk("SMF Desk Calculator Demo\n");
176
177 int rc = setup_display();
178
179 if (rc != 0) {
180 return rc;
181 }
182
183 while (1) {
184 char output[CALCULATOR_STRING_LENGTH];
185
186 if (k_msgq_get(&output_msgq, output, K_MSEC(50)) == 0) {
187 lv_label_set_text(result_label, output);
188 }
189
190 lv_task_handler();
191 }
192 return 0;
193 }
194