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