Lamp-Da 0.1
A compact lantern project
Loading...
Searching...
No Matches
simulator.h
Go to the documentation of this file.
1
5#ifndef SIMULATOR_H
6#define SIMULATOR_H
7
8#include <cstring>
9#include <iostream>
10
11// include this first
12#include "src/system/global.h"
13
14#include "parameter_parser.h"
15#include "simulator_state.h"
16
17// see LMBD_LAMP_TYPE__${UPPER_SIM_NAME} hard-coded in simulator/Makefile
18#include "src/user/constants.h"
19
20#include <SFML/Graphics.hpp>
21#include <SFML/Graphics/Color.hpp>
22#include <SFML/System/Time.hpp>
23#include <SFML/Window/Keyboard.hpp>
24
26// #define LMBD_DEBUG_SIMU_REALCOLORS
27
28namespace simulator {
29// (_LampTy from simulator_state.h)
30constexpr int ledW = _LampTy::maxWidth;
31constexpr int ledH = _LampTy::maxOverflowHeight;
32constexpr int ledCount = _LampTy::ledCount;
33
34constexpr float fLedW = _LampTy::maxWidthFloat;
35constexpr float fResidueW = 1 / (2 * fLedW - 2 * floor(fLedW) - 1);
36} // namespace simulator
37
38#include "src/user/functions.h"
40
42// handle simulation of voltage and current in the system
44
45#include <thread>
46
47namespace simulator {
48
49//
50// simulator core loop
51//
52
53template<typename T> struct simulator
54{
55 static constexpr uint screenWidth = 1920;
56 static constexpr uint screenHeight = 1080;
57 static constexpr char title[] = "lampda simulator";
58
59 static constexpr uint brightnessWidth = 480;
60
61 static int run()
62 {
63 T simu;
64 sf::Clock time;
65
66 // brightness tracker
67 std::array<uint16_t, brightnessWidth> brightnessTracker {};
68 std::array<sf::RectangleShape, brightnessWidth> dots;
69
70 // matrix shift
71 int fakeXorigin = 0;
72 int fakeXend = 0;
73
74 // pixel shapes
75 std::array<sf::RectangleShape, _LampTy::ledCount> shapes;
76
77 // init font
78 sf::Font font;
79 bool enableFont = false;
80 bool enableText = false;
81 if (font.loadFromFile("simulator/resources/DejaVuSansMono.ttf"))
82 {
83 enableFont = true;
84 }
85 else
86 {
87 fprintf(stderr, "DejaVuSansMono.ttf not found, disabling text...");
88 }
89
90 // render window
91 sf::RenderWindow window(sf::VideoMode({screenWidth, screenHeight}), title);
92
93 sf::CircleShape buttonMask(simu.buttonSize);
94 sf::CircleShape indicator(simu.buttonSize + simu.buttonMargin);
95 buttonMask.setFillColor(sf::Color::Black);
96
97 sf::Vector2<int> mousePos = sf::Mouse::getPosition(window);
98 const float indCoordX = simu.buttonLeftPos;
99 const float indCoordY = (simu.ledSizePx + simu.ledPaddingPx) * (ledH + 3);
100
101 // TODO: #156 move mock-specific parts to another process
102 // ======================================================
103
104 // mock run the threads (do not give each subprocess a thread)
105 mock_registers::shouldStopThreads = false;
106
107 // load initial values
109 // start electrical simulation,
110 start_electrical_mock();
111
112 // force init of time clock
113 time_mocks::reset();
114
115 // Main program setup
117
118 bool lostFocus = false;
119
120 // =================================================
121
122 // reference to global state
123 auto& state = globals::state;
124
125 uint64_t skipframe = 0;
126 while (window.isOpen())
127 {
128 uint64_t mouseClick = 0;
129
130 mousePos = sf::Mouse::getPosition(window);
131 enableText = enableFont && sf::Mouse::isButtonPressed(sf::Mouse::Button::Left);
132 enableText |= state.verbose;
133
134 // read events, prevent windows lockup
135 sf::Event event;
136 while (window.pollEvent(event))
137 {
138 if (event.type == sf::Event::Closed)
139 {
140 window.close();
141 break;
142 }
143 else if (event.type == sf::Event::LostFocus)
144 lostFocus = true;
145 else if (event.type == sf::Event::GainedFocus)
146 lostFocus = false;
147
148 if (not lostFocus)
149 {
150 if (event.type == sf::Event::KeyPressed)
151 {
152 auto kpressed = event.key.code;
153 switch (kpressed)
154 {
155 default:
156 state.lastKeyPressed = 0;
157 break;
158 case sf::Keyboard::Key::P: // p == pause
159 state.lastKeyPressed = 'p';
160 break;
161 case sf::Keyboard::Key::V: // v == verbose
162 state.lastKeyPressed = 'v';
163 break;
164 case sf::Keyboard::Key::T: // t == tick +pause
165 state.lastKeyPressed = 't';
166 break;
167 case sf::Keyboard::Key::H: // h == higher speed
168 state.lastKeyPressed = 'h';
169 break;
170 case sf::Keyboard::Key::G: // g == glower speeds
171 state.lastKeyPressed = 'g';
172 break;
173 case sf::Keyboard::Key::J: // j == shift matrix left
174 state.lastKeyPressed = 'j';
175 break;
176 case sf::Keyboard::Key::K: // k == shift matrix right
177 state.lastKeyPressed = 'k';
178 break;
179 case sf::Keyboard::Key::D:
180 state.lastKeyPressed = 'd';
181 break;
182 case sf::Keyboard::Key::U:
183 state.lastKeyPressed = 'u';
184 break;
185 case sf::Keyboard::Key::I:
186 state.lastKeyPressed = 'i';
187 break;
188 case sf::Keyboard::Key::R:
189 state.lastKeyPressed = 'r';
190 break;
191 case sf::Keyboard::Key::C:
192 state.lastKeyPressed = 'c';
193 break;
194 case sf::Keyboard::Key::Q:
195 state.lastKeyPressed = 'q';
196 break;
197 }
198
199 break;
200 }
201 else if (event.type == sf::Event::KeyReleased)
202 {
203 // tick forward (once) and pause
204 if (state.lastKeyPressed == 't')
205 {
206 state.paused = false;
207 state.tickAndPause = 2;
208 }
209
210 // pause event
211 if (state.lastKeyPressed == 'p')
212 {
213 state.paused = !state.paused;
214 }
215
216 // verbose event
217 if (state.lastKeyPressed == 'v')
218 {
219 state.verbose = !state.verbose;
220 }
221
222 // slower simulation
223 if (state.lastKeyPressed == 'g')
224 {
225 if (state.slowTimeFactor > 1.50f)
226 {
227 state.slowTimeFactor *= 0.95f;
228 }
229 else if (state.slowTimeFactor > 1.0f)
230 {
231 state.slowTimeFactor = ceil(state.slowTimeFactor * 20 - 1) / 20;
232 }
233 else
234 {
235 state.slowTimeFactor = std::max<float>(0.1f, state.slowTimeFactor - 0.05f);
236 }
237 fprintf(stderr, "slower %f\n", state.slowTimeFactor);
238 }
239
240 // faster simulation
241 if (state.lastKeyPressed == 'h')
242 {
243 if (state.slowTimeFactor > 1.20f)
244 {
245 state.slowTimeFactor *= 1.05f;
246 }
247 else if (state.slowTimeFactor > 1.0f)
248 {
249 state.slowTimeFactor = ceil(state.slowTimeFactor * 20 + 1) / 20;
250 }
251 else
252 {
253 state.slowTimeFactor += 0.05f;
254 }
255 fprintf(stderr, "faster %f\n", state.slowTimeFactor);
256 }
257
258 // shift forward XY display
259 if (state.lastKeyPressed == 'k')
260 {
261 fakeXorigin += 1;
262 if (fakeXorigin > ledW - 1)
263 fakeXorigin = 0;
264 fakeXend = std::max<int>(fakeXorigin - std::min<int>(4, ledW - 1 - fakeXorigin), 0);
265 }
266
267 // shift backward XY display
268 if (state.lastKeyPressed == 'j')
269 {
270 fakeXorigin -= 1;
271 if (fakeXorigin < 0)
272 fakeXorigin = ledW - 1;
273 fakeXend = std::max<int>(fakeXorigin - std::min<int>(4, ledW - 1 - fakeXorigin), 0);
274 }
275
276 // reset key pressed
277 state.lastKeyPressed = 0;
278 break;
279 }
280 else if (event.type == sf::Event::MouseButtonPressed)
281 {
282 float dx = indCoordX - mousePos.x + simu.buttonSize;
283 float dy = indCoordY - mousePos.y + simu.buttonSize;
284 float norm = simu.buttonSize;
285
286 if (dx * dx + dy * dy < norm * norm)
287 {
288 mouseClick += 3;
289 }
290 break;
291 }
292 }
293 }
294
295 // update simulator global state
296 state.isButtonPressed = (not lostFocus) and sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Space);
297 if (mouseClick > 0)
298 {
299 mouseClick -= 1;
300 state.isButtonPressed = true;
301 }
302
303 if (state.tickAndPause > 0)
304 {
305 if (state.tickAndPause == 1)
306 {
307 state.paused = true;
308 }
309 state.tickAndPause -= 1;
310 }
311
312 // TODO: #156 move mock-specific parts to another process
313 // ======================================================
314
315 if (!state.paused)
316 {
317 // deep sleep, exit
318 if (mock_registers::isDeepSleep)
319 {
320 // close all threads
321 window.close();
322 break;
323 }
324
325 // update simu parameters
327 // update the callbacks of the gpios (simulate interrupts)
328 mock_gpios::update_callbacks();
329
330 // main program loop
331 ::lampda::main_loop(mock_registers::addedAlgoDelay);
332 }
333
334 const bool isOutputEnabled = is_output_enabled();
336 {
337#ifdef LMBD_LAMP_TYPE__INDEXABLE
338 // brightness on the indexale lamp
340 static curve_t brightnessCurve({curve_t::point_t {0, ::lampda::minimumAllowedBrightness_8},
341 curve_t::point_t {::lampda::brightness::absoluteMaximumBrightness, 255}});
342 state.brightness = brightnessCurve.sample(::lampda::logic::brightness::get_brightness());
343
344 const bool isVoltageHighEnough = mock_electrical::outputVoltage > 11.5;
345 for (size_t I = 0; I < _LampTy::ledCount; ++I)
346 {
347 state.colorBuffer[I] =
348 isOutputEnabled ?
349 (isVoltageHighEnough ? ::lampda::user::_private::strip.getRawPixelColor(I) : 0xffffff) :
350 0;
351 }
352#endif
353 }
354 else
355 {
356 state.brightness =
357 (::lampda::logic::brightness::get_brightness() * 255) / ::lampda::brightness::absoluteMaximumBrightness;
358
359 for (size_t I = 0; I < _LampTy::ledCount; ++I)
360 state.colorBuffer[I] = isOutputEnabled ? 0xffff00 : 0;
361 }
362
363 state.indicatorColor = mock_indicator::get_color();
364 // ======================================================
365
366 const float ledSz = simu.ledSizePx;
367 const auto ledPadSz = simu.ledPaddingPx + simu.ledSizePx;
368 const auto ledOffset = ledSz / _LampTy::shiftPeriod;
369
370 const float Xbase = _LampTy::extraShiftTotal > 0 ? 0 : -_LampTy::extraShiftTotal;
371 const float Xextra = _LampTy::extraShiftTotal < 0 ? 0 : _LampTy::extraShiftTotal;
372
373 // draw slightly grey background to hightlight turned off LEDs
374 window.clear(sf::Color(13, 12, 11));
375
376 for (int Ypos = 0; Ypos < ledH; ++Ypos)
377 {
378 int realRowSize = ledW;
379 if (_LampTy::allDeltaResiduesY[Ypos])
380 realRowSize += 1;
381
382 for (int fXpos = -fakeXorigin; fXpos < realRowSize - fakeXend; ++fXpos)
383 {
384 int Yoff = 0;
385 int Xoff = 0;
386
387 int Xpos = fXpos;
388 if (fXpos < 0)
389 {
390 Xpos = realRowSize + fXpos;
391 Yoff = 1;
392
393 if (_LampTy::allDeltaResiduesY[Ypos])
394 Xoff = 1;
395 }
396
397 size_t I = ::lampda::modes::to_strip(Xpos, Ypos);
398 auto& shape = shapes[I];
399
400 auto realPos = ::lampda::modes::strip_to_XY(I);
401 if (realPos.x != Xpos || realPos.y != Ypos)
402 continue;
403
404 shape.setSize({ledSz, ledSz});
405
406 int rXpos = fXpos + fakeXorigin + Xoff;
407 int rYpos = Ypos + Yoff;
408 int shiftR = _LampTy::extraShiftResiduesY[Ypos];
409 int shiftL = Ypos + shiftR + 1;
410
411 if (_LampTy::shiftResidue == 1 && _LampTy::shiftPerTurn < 0.1)
412 shiftR = 0;
413
414 float shapeX = ledSz + rXpos * ledPadSz;
415 shapeX += ledOffset * Xbase;
416 shapeX += ledOffset * (shiftL % _LampTy::shiftPeriod);
417 shapeX -= (ledOffset - simu.ledPaddingPx / 2) * (Yoff - shiftR);
418 shapeX -= Xoff * simu.ledPaddingPx;
419
420 // make the origin more obvious
421 if (fXpos >= 0)
422 shapeX += simu.ledPaddingPx;
423
424 float shapeY = rYpos * ledPadSz;
425 shape.setPosition({shapeX, shapeY});
426
427 const uint32_t color = state.colorBuffer[I];
428 float b = (color & 0xff);
429 float g = ((color >> 8) & 0xff);
430 float r = ((color >> 16) & 0xff);
431
432#ifndef LMBD_DEBUG_SIMU_REALCOLORS
433 if (state.brightness != 255)
434 {
435 // scale by brightness
436 r = (uint16_t(r) * state.brightness + state.brightness) >> 8;
437 g = (uint16_t(g) * state.brightness + state.brightness) >> 8;
438 b = (uint16_t(b) * state.brightness + state.brightness) >> 8;
439 }
440#endif
441
442 shape.setFillColor(sf::Color(r, g, b));
443 window.draw(shape);
444
445 // display text information if mouse is pressed
446 if (enableText)
447 {
448 int MouseYpos = mousePos.y;
449 int MouseXpos = mousePos.x;
450
451 auto shapeXY = shape.getPosition();
452 float dx = shapeXY.x + ledSz / 2 - MouseXpos;
453 float dy = shapeXY.y + ledSz / 2 - MouseYpos;
454
455 // all shapes too far from cursor, does not display info
456 if (abs(2 * dx) > ledPadSz || abs(2 * dy) > ledPadSz)
457 continue;
458
459 // display (X,Y) on bottom-right
460 float largestX = ledSz + (ledW + 1 + fakeXorigin - fakeXend) * ledPadSz + ledOffset * (Xextra + Xbase);
461 float largestY = (ledH + Yoff) * ledPadSz + 8;
462 {
463 auto str = "(" + std::to_string(Xpos) + ", " + std::to_string(Ypos) + ")";
464
465 sf::Text text(str, font, 12);
466 sf::Vector2f where(largestX, largestY);
467 text.setPosition(where);
468 window.draw(text);
469 }
470
471 // display Ypos on the right
472 {
473 auto str = std::to_string(Ypos);
474 if (_LampTy::allDeltaResiduesY[Ypos])
475 str += " *";
476
477 sf::Text text(str, font, 12);
478 sf::Vector2f where(largestX, shapeXY.y + ledSz / 4);
479 text.setPosition(where);
480 window.draw(text);
481 }
482
483 // display Xpos on the bottom
484 {
485 auto str = std::to_string(Xpos);
486 if (Xpos == ledW)
487 str += " *";
488
489 sf::Text text(str, font, 12);
490 sf::Vector2f where(shapeXY.x, largestY);
491 text.setPosition(where);
492 window.draw(text);
493 }
494
495 // display misc info next to button
496 {
497 auto str = "(" + std::to_string(int(r)) + ", ";
498 str += std::to_string(int(g)) + ", ";
499 str += std::to_string(int(b)) + ")";
500 str += ", " + std::to_string(I);
501
502 sf::Text text(str, font, 12);
503 sf::Vector2f where(indCoordX + simu.buttonSize * 3, indCoordY - 24.f);
504 text.setPosition(where);
505 window.draw(text);
506
507 // (avoid superposed text)
508 enableText = false;
509 }
510 }
511 }
512 }
513
514 // display the indicator button
515 const uint32_t indicatorColor = state.indicatorColor;
516 float b = (indicatorColor & 0xff);
517 float g = ((indicatorColor >> 8) & 0xff);
518 float r = ((indicatorColor >> 16) & 0xff);
519 indicator.setFillColor(sf::Color(r, g, b));
520 indicator.setPosition({indCoordX, indCoordY});
521 buttonMask.setPosition({indCoordX + simu.buttonMargin, indCoordY + simu.buttonMargin});
522 window.draw(indicator);
523 window.draw(buttonMask);
524
525 // track brightness & display dot curve
526 float now = (time.getElapsedTime().asMilliseconds() * simu.fps) / 1000.f;
527 uint32_t trackerPos = now / simu.brightnessRate;
528
529 trackerPos = trackerPos % brightnessTracker.size();
530 brightnessTracker[trackerPos] = state.brightness;
531 brightnessTracker[(trackerPos + 1) % brightnessTracker.size()] = 0;
532
533 float xDotsOrigin = indCoordX + simu.buttonSize * 2 + simu.buttonMargin * 2 + 16.f;
534 float yDotsOrigin = indCoordY + simu.brightnessScale;
535
536 for (size_t I = 0; I < brightnessTracker.size(); ++I)
537 {
538 auto& dot = dots[I];
539
540 float v = (brightnessTracker[I] * simu.brightnessScale) / 256;
541 float x = xDotsOrigin + I;
542 float y = yDotsOrigin - v;
543 dot.setSize({1, 1});
544 dot.setPosition({x, y});
545 dot.setFillColor(sf::Color(0, 0xff, 0));
546 window.draw(dot);
547 }
548
549 // draw window
550 window.display();
551 }
552
553 stop_electrical_mock();
554
555 return 0;
556 }
557};
558
559} // namespace simulator
560
561#endif
Given a set of points, will fit multiple linear segments to it.
Definition: curves.h:35
General electrical simulation of the Lampda board.
Define specific needed user functions.
Main input point of the whole program.
Handle the physical simulation paremeters of a real lamp.
brightness_t get_brightness()
Return the current brightness value (in range 0 - brightness::absoluteMaximumBrightness)....
Definition: brightness_handle.cpp:39
@ indexable
Equivalent to LMBD_LAMP_TYPE__INDEXABLE.
static constexpr uint16_t to_strip(uint16_t, uint16_t)
convert grid coordinates to strip index
Definition: lamp_type.hpp:1104
static constexpr XYTy strip_to_XY(uint16_t n)
convert strip index to grid coordinates
Definition: lamp_type.hpp:1136
void main_setup()
Setup of the program, call once on systel start.
Definition: global.cpp:83
void main_loop(const uint32_t addedDelay)
Run the main program loop.
Definition: global.cpp:235
GlobalSimStateTy state
Store the global simulation state.
Definition: simulator_state.cpp:7
Simulator dedicated namespace.
Definition: BQ25713_mock.h:24
void read_and_update_parameters()
Read parameters from the parameter file, and update the simulation.
Definition: parameter_parser.h:24
Handle the modification of the simulation parameters by a file.
Contain some simulation state.
static constexpr float maxWidthFloat
Width as a precise floating point number, equal to stripXCoordinates.
Definition: lamp_type.hpp:373
static constexpr uint16_t maxOverflowHeight
Larger height, taken as the absolute maximum Y coordinate, overflowing.
Definition: lamp_type.hpp:397
static constexpr LampTypes flavor
Which lamp flavor is currently used by the implementation?
Definition: lamp_type.hpp:340
static constexpr uint16_t maxWidth
(indexable) Width of "pixel space" w/ lamp taken as a LED matrix
Definition: lamp_type.hpp:370
static constexpr uint16_t ledCount
(indexable) Count of indexable LEDs on the lamp
Definition: lamp_type.hpp:357
User defined constants, relative to specific lamp types.
Define useful functions.