-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy patholed_functions.py
More file actions
264 lines (232 loc) · 8.12 KB
/
oled_functions.py
File metadata and controls
264 lines (232 loc) · 8.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
from time import ticks_ms, ticks_diff
import ujson as json
import random
import framebuf
import settings_store
# Default face sets (fallback if core json missing)
DEFAULT_FACES = {
"happy": ['(^_^)', '(^_^)', "('-')", "('-')", "('-')", '(^_^)'],
"really_happy": ['(^o^)', '(^o^)', '(*^_^*)', '(*^_^*)', '(*^_^*)', '(^o^)'],
"curious": ['(o_o)', '(o_o)', '(-_-?)', '(-_-?)', '(-_-?)', '(._.)', '(._.)', '(._.)', '(-_-?)', '(-_-?)', '(-_-?)', '(o_o)'],
"concerned": ['(>_<)', '(>_<)', '(._.)', '(._.)', '(._.)', '(>_<)'],
"sad": ['(T_T)', '(T_T)', '(;_;)'],
"sleepy": ['(-_-)', '(-_-)', '(u_u)'],
"mischief": ['(¬‿¬)', '(¬‿¬)', '(^_~)'],
"surprised": ['(O_O)', '(O_O)', '(o_O)'],
"angry": ['(>_<)', '(>_<)', '(>: [)'],
"cool": ['(-_-)', '(-_-)', '(B-)'],
"love": ['(^3^)', '(^3^)', '(^.^)'],
"headpat": ['(^_^)', '(^_^)', '(^_^*)'],
"shake": ['(@_@)', '(@_@)', '(x_x)', '(x_x)', '(x_x)', '(O_o)'],
}
_core = {}
FACES = {}
DEFAULT_UPSIDE = False
def _load_core():
core_type = settings_store.get_core_type()
files_to_try = []
if core_type == 'Custom':
files_to_try.append('custom_core.json')
files_to_try.append('default_core.json')
data = None
for fname in files_to_try:
try:
with open(fname, 'r') as f:
data = json.load(f)
break # Stop after finding a file
except Exception:
continue
if not isinstance(data, dict):
data = {}
faces = data.get('faces', {})
merged_faces = dict(DEFAULT_FACES)
merged_faces.update(faces)
data['faces'] = merged_faces
return data
def reload_core():
global _core, FACES, DEFAULT_UPSIDE
_core = _load_core()
FACES = _core.get('faces', dict(DEFAULT_FACES))
DEFAULT_UPSIDE = _core.get('display', {}).get('upside_down', False)
# Initial load
reload_core()
# Small text helper that respects upside_down
def _text(oled, text, x, y, upside_down=False, color=1):
if not text:
return
if not upside_down:
oled.text(text, x, y, color)
return
w = len(text) * 8
h = 8
buf = bytearray(w * h // 8)
fb = framebuf.FrameBuffer(buf, w, h, framebuf.MONO_VLSB)
fb.text(text, 0, 0, 1)
for i in range(w):
for j in range(h):
if fb.pixel(i, j):
fx = 128 - (x + (i + 1))
fy = 64 - (y + (j + 1))
if 0 <= fx < 128 and 0 <= fy < 64:
oled.pixel(fx, fy, color)
# --- Animation state ---
_last_blink_time = 0
_blinking = False
_next_blink_interval = None
_shake_start = None
_headpat_start = None
# --- Timing constants (ms) ---
BLINK_DURATION = 140
SHAKE_DURATION = 2000
HEADPAT_DURATION = 1200
SWAY_PERIOD = 1800
def _get_blink_interval():
return random.randint(3000, 6000)
def _translate_emoji_blink(face):
return (face
.replace('^', '-')
.replace('o', '-')
.replace('O', '-')
.replace('x', '-')
.replace('_', '-'))
def _draw_ascii(oled, text, x, y, scale=2, upside_down=False):
"""Draw scaled ASCII text on oled"""
char_width = 8 * len(text)
char_height = 8
temp_buf = bytearray(char_width * char_height // 8)
temp_fb = framebuf.FrameBuffer(temp_buf, char_width, char_height, framebuf.MONO_VLSB)
temp_fb.text(text, 0, 0, 1)
for i in range(char_width):
for j in range(char_height):
if temp_fb.pixel(i, j):
if upside_down:
# Flip both x and y coordinates for 180 degree rotation
flip_x = 128 - (x + (i + 1) * scale)
flip_y = 64 - (y + (j + 1) * scale)
oled.fill_rect(flip_x, flip_y, scale, scale, 1)
else:
oled.fill_rect(x + i*scale, y + j*scale, scale, scale, 1)
def _centered_x(face, scale=2):
w = len(face) * 8 * scale
return max((128 - w) // 2, 0)
def _seq(name):
return FACES.get(name) or DEFAULT_FACES.get(name) or ['(._.)']
def get_face_and_x(mood, now, anim_state):
if mood == "happy":
seq = _seq('happy')
idx = (now // 2000) % len(seq)
face = seq[idx]
elif mood == "really_happy":
seq = _seq('really_happy')
idx = (now // 1700) % len(seq)
face = seq[idx]
elif mood == "shake":
seq = _seq('shake')
phase_len = 210
frame = ((ticks_diff(now, anim_state.get("start", now)) // phase_len) % min(3, len(seq)))
face = seq[frame]
offset_seq = [-11, 0, 10]
x = _centered_x(face) + offset_seq[frame % len(offset_seq)]
return face, x
elif mood == "headpat":
seq = _seq('headpat')
elapsed = ticks_diff(now, anim_state.get("start", now))
if len(seq) < 2:
face = seq[0]
x = _centered_x(face)
return face, x
phase = (elapsed // 400) % 2
face = seq[phase]
x = _centered_x(face) + (2 if phase else -3)
return face, x
else:
seq = _seq(mood if mood in FACES else 'curious')
period = SWAY_PERIOD
try:
from math import sin, pi
t = (now % period) / period
swing = int(7 * sin(2 * pi * t))
except Exception:
swing = 0
idx = (now // 2600) % len(seq)
face = seq[idx]
x = _centered_x(face) + swing
return face, x
x = _centered_x(face)
return face, x
def update_oled(oled, mood="happy", value=None, upside_down=False, debug_mode=False, **kwargs):
if mood == "text":
line = kwargs.get("line")
x = kwargs.get("x", 0)
y = kwargs.get("y")
if y is None:
if line is not None:
y = (line - 1) * 10
else:
y = 0 # Default to top if no y or line is provided
text_to_display = str(value) if value is not None else ""
color = kwargs.get("color", 1)
_text(oled, text_to_display, x, y, upside_down, color)
return
global _last_blink_time, _blinking, _next_blink_interval, _shake_start, _headpat_start
now = ticks_ms()
anim_state = {}
if mood == "shake":
if _shake_start is None:
_shake_start = now
anim_state["start"] = _shake_start
if ticks_diff(now, _shake_start) > SHAKE_DURATION:
_shake_start = None
mood = "happy"
else:
_shake_start = None
if mood == "headpat":
if _headpat_start is None:
_headpat_start = now
anim_state["start"] = _headpat_start
if ticks_diff(now, _headpat_start) > HEADPAT_DURATION:
_headpat_start = None
mood = "happy"
else:
_headpat_start = None
blinkable = mood not in ("shake", "headpat")
if _next_blink_interval is None:
_next_blink_interval = _get_blink_interval()
if blinkable:
if ticks_diff(now, _last_blink_time) > _next_blink_interval:
_blinking = True
_last_blink_time = now
_next_blink_interval = _get_blink_interval()
if _blinking and ticks_diff(now, _last_blink_time) > BLINK_DURATION:
_blinking = False
face, x = get_face_and_x(mood, now, anim_state)
if blinkable and _blinking:
face = _translate_emoji_blink(face)
oled.fill(0)
_draw_ascii(oled, face, x, 20, 2, upside_down)
# --- Status overlay (DBG + mute) ---
parts = []
if debug_mode:
parts.append("DBG")
if settings_store.is_muted():
parts.append("M") # Single letter muted indicator
if parts:
status = " ".join(parts)
if upside_down:
# Bottom-left when upside down
_text(oled, status, 0, 56, True)
else:
# Top-right when normal orientation
_text(oled, status, 128 - len(status)*8, 0, False)
oled.show()
def demo_emotions(oled):
from time import sleep_ms
loop = [
"happy", "curious", "mischief",
"surprised", "cool", "sad", "shake", "headpat"
]
for mood in loop:
frames = 26 if mood in ("shake", "headpat") else 18
for _ in range(frames):
update_oled(oled, mood)
sleep_ms(68)