fw-update-helper.cpp
Go to the documentation of this file.
1 // License: Apache 2.0. See LICENSE file in root directory.
2 // Copyright(c) 2017 Intel Corporation. All Rights Reserved.
3 #include <glad/glad.h>
4 
5 
6 #include <map>
7 #include <vector>
8 #include <string>
9 #include <thread>
10 #include <condition_variable>
11 
12 #include "fw-update-helper.h"
13 #include "model-views.h"
14 #include "viewer.h"
15 
16 #include "os.h"
17 
18 #ifdef INTERNAL_FW
19 #include "common/fw/D4XX_FW_Image.h"
20 #include "common/fw/SR3XX_FW_Image.h"
21 #include "common/fw/L5XX_FW_Image.h"
22 #else
23 #define FW_D4XX_FW_IMAGE_VERSION ""
24 #define FW_SR3XX_FW_IMAGE_VERSION ""
25 #define FW_L5XX_FW_IMAGE_VERSION ""
26 const char* fw_get_D4XX_FW_Image(int) { return NULL; }
27 const char* fw_get_SR3XX_FW_Image(int) { return NULL; }
28 const char* fw_get_L5XX_FW_Image(int) { return NULL; }
29 
30 #endif // INTERNAL_FW
31 
32 constexpr const char* recommended_fw_url = "https://dev.intelrealsense.com/docs/firmware-updates";
33 
34 namespace rs2
35 {
37  {
42  };
43 
45  {
46  auto pl = parse_product_line(id);
47  auto fv = get_available_firmware_version(pl);
48  return !(fv == "");
49  }
50 
52  {
53  if (id == "D400") return RS2_PRODUCT_LINE_D400;
54  else if (id == "SR300") return RS2_PRODUCT_LINE_SR300;
55  else if (id == "L500") return RS2_PRODUCT_LINE_L500;
56  else return -1;
57  }
58 
60  {
61  if (product_line == RS2_PRODUCT_LINE_D400) return FW_D4XX_FW_IMAGE_VERSION;
62  //else if (product_line == RS2_PRODUCT_LINE_SR300) return FW_SR3XX_FW_IMAGE_VERSION;
63  else if (product_line == RS2_PRODUCT_LINE_L500) return FW_L5XX_FW_IMAGE_VERSION;
64  else return "";
65  }
66 
67  std::map<int, std::vector<uint8_t>> create_default_fw_table()
68  {
70 
71  std::map<int, std::vector<uint8_t>> rv;
72 
73  if (strlen(FW_D4XX_FW_IMAGE_VERSION) && !allow_rc_firmware)
74  {
75  int size = 0;
76  auto hex = fw_get_D4XX_FW_Image(size);
77  auto vec = std::vector<uint8_t>(hex, hex + size);
78  rv[RS2_PRODUCT_LINE_D400] = vec;
79  }
80 
81  if (strlen(FW_SR3XX_FW_IMAGE_VERSION))
82  {
83  int size = 0;
84  auto hex = fw_get_SR3XX_FW_Image(size);
85  auto vec = std::vector<uint8_t>(hex, hex + size);
86  rv[RS2_PRODUCT_LINE_SR300] = vec;
87  }
88 
89  if (strlen(FW_L5XX_FW_IMAGE_VERSION))
90  {
91  int size = 0;
92  auto hex = fw_get_L5XX_FW_Image(size);
93  auto vec = std::vector<uint8_t>(hex, hex + size);
94  rv[RS2_PRODUCT_LINE_L500] = vec;
95  }
96 
97  return rv;
98  }
99 
100  std::vector<int> parse_fw_version(const std::string& fw)
101  {
102  std::vector<int> rv;
103  size_t pos = 0;
104  std::string delimiter = ".";
105  auto str = fw + delimiter;
106  while ((pos = str.find(delimiter)) != std::string::npos) {
107  auto s = str.substr(0, pos);
108  int val = std::stoi(s);
109  rv.push_back(val);
110  str.erase(0, pos + delimiter.length());
111  }
112  return rv;
113  }
114 
115  bool is_upgradeable(const std::string& curr, const std::string& available)
116  {
117  if (curr == "" || available == "") return false;
118 
119  size_t fw_string_size = 4;
120  auto c = parse_fw_version(curr);
121  auto a = parse_fw_version(available);
122  if (a.size() != fw_string_size || c.size() != fw_string_size)
123  return false;
124 
125  for (int i = 0; i < fw_string_size; i++) {
126  if (c[i] > a[i])
127  return false;
128  if (c[i] < a[i])
129  return true;
130  }
131  return false; //equle
132  }
133 
135  std::function<bool()> action, std::function<void()> cleanup,
136  std::chrono::system_clock::duration delta)
137  {
138  using namespace std;
139  using namespace std::chrono;
140 
141  auto start = system_clock::now();
142  auto now = system_clock::now();
143  do
144  {
145  try
146  {
147 
148  if (action()) return true;
149  }
150  catch (const error& e)
151  {
152  fail(error_to_string(e));
153  cleanup();
154  return false;
155  }
156  catch (const std::exception& ex)
157  {
158  fail(ex.what());
159  cleanup();
160  return false;
161  }
162  catch (...)
163  {
164  fail("Unknown error during update.\nPlease reconnect the camera to exit recovery mode");
165  cleanup();
166  return false;
167  }
168 
170  this_thread::sleep_for(milliseconds(100));
171  } while (now - start < delta);
172  return false;
173  }
174 
176  std::function<void()> cleanup,
177  invoker invoke)
178  {
179  std::string serial = "";
182  else
183  serial = _dev.query_sensors().front().get_info(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID);
184 
185  // Clear FW update related notification to avoid dismissing the notification on ~device_model()
186  // We want the notification alive during the whole process.
188  std::remove_if( _model.related_notifications.begin(),
190  []( std::shared_ptr< notification_model > n ) {
191  return n->is< fw_update_notification_model >();
193 
194  for (auto&& n : _model.related_notifications)
195  {
196  if (n->is< fw_update_notification_model >()
198  n->dismiss(false);
199  }
200 
201  _progress = 5;
202 
203  int next_progress = 10;
204 
205  update_device dfu{};
206 
207  if (auto upd = _dev.as<updatable>())
208  {
209  log("Backing-up camera flash memory");
210 
211  std::string log_backup_status;
212  try
213  {
214  auto flash = upd.create_flash_backup([&](const float progress)
215  {
216  _progress = int((ceil(progress * 5) / 5) * (30 - next_progress)) + next_progress;
217  });
218 
220  temp += serial + "." + get_timestamped_file_name() + ".bin";
221 
222  {
223  std::ofstream file(temp.c_str(), std::ios::binary);
224  file.write((const char*)flash.data(), flash.size());
225  log_backup_status = "Backup completed and saved as '" + temp + "'";
226  }
227  }
228  catch (const std::exception& e)
229  {
230  if (auto not_model_protected = get_protected_notification_model())
231  {
232  log_backup_status = "WARNING: backup failed; continuing without it...";
233  not_model_protected->output.add_log(RS2_LOG_SEVERITY_WARN,
234  __FILE__,
235  __LINE__,
236  log_backup_status + ", Error: " + e.what());
237  }
238  }
239  catch ( ... )
240  {
241  if (auto not_model_protected = get_protected_notification_model())
242  {
243  log_backup_status = "WARNING: backup failed; continuing without it...";
244  not_model_protected->add_log(log_backup_status + ", Unknown error occurred");
245  }
246  }
247 
248  log(log_backup_status);
249 
250  next_progress = 40;
251 
252  if (_is_signed)
253  {
254  log("Requesting to switch to recovery mode");
255 
256  // in order to update device to DFU state, it will be disconnected then switches to DFU state
257  // if querying devices is called while device still switching to DFU state, an exception will be thrown
258  // to prevent that, a blocking is added to make sure device is updated before continue to next step of querying device
259  upd.enter_update_state();
260  // Allow time for the device to disconnect before calling "query_devices"
261  std::this_thread::sleep_for(std::chrono::seconds(2));
262 
263  if (!check_for([this, serial, &dfu]() {
264  auto devs = _ctx.query_devices();
265 
266  for (uint32_t j = 0; j < devs.size(); j++)
267  {
268  try
269  {
270  auto d = devs[j];
271  if (d.is<update_device>())
272  {
274  {
275  if (serial == d.get_info(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID))
276  {
277  dfu = d;
278  return true;
279  }
280  }
281  }
282  }
283  catch (std::exception &e) {
284  if (auto not_model_protected = get_protected_notification_model())
285  {
286  not_model_protected->output.add_log(RS2_LOG_SEVERITY_WARN,
287  __FILE__,
288  __LINE__,
289  to_string() << "Exception caught in FW Update process-flow: " << e.what() << "; Retrying...");
290  }
291  }
292  catch (...) {}
293  }
294 
295  return false;
296  }, cleanup, std::chrono::seconds(60)))
297  {
298  fail("Recovery device did not connect in time!");
299  return;
300  }
301  }
302  }
303  else
304  {
305  dfu = _dev.as<update_device>();
306  }
307 
308  if (dfu)
309  {
310  _progress = next_progress;
311 
312  log("Recovery device connected, starting update");
313 
314  dfu.update(_fw, [&](const float progress)
315  {
316  _progress = int((ceil(progress * 10) / 10 * (90 - next_progress)) + next_progress);
317  });
318 
319  log("Firmware Download completed, await DFU transition event");
320  std::this_thread::sleep_for(std::chrono::seconds(3));
321  log("Firmware Update completed, waiting for device to reconnect");
322  }
323  else
324  {
325  auto upd = _dev.as<updatable>();
326  upd.update_unsigned(_fw, [&](const float progress)
327  {
328  _progress = int((ceil(progress * 10) / 10 * (90 - next_progress)) + next_progress);
329  });
330  log("Firmware Update completed, waiting for device to reconnect");
331  }
332 
333  if (!check_for([this, serial]() {
334  auto devs = _ctx.query_devices();
335 
336  for (uint32_t j = 0; j < devs.size(); j++)
337  {
338  try
339  {
340  auto d = devs[j];
341 
342  if (d.query_sensors().size() && d.query_sensors().front().supports(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID))
343  {
344  auto s = d.query_sensors().front().get_info(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID);
345  if (s == serial)
346  {
347  log("Discovered connection of the original device");
348  return true;
349  }
350  }
351  }
352  catch (...) {}
353  }
354 
355  return false;
356  }, cleanup, std::chrono::seconds(60)))
357  {
358  fail("Original device did not reconnect in time!");
359  return;
360  }
361 
362  log("Device reconnected succesfully!");
363 
364  _progress = 100;
365 
366  _done = true;
367  }
368 
369  void fw_update_notification_model::draw_content(ux_window& win, int x, int y, float t, std::string& error_message)
370  {
371  using namespace std;
372  using namespace chrono;
373 
374  ImGui::SetCursorScreenPos({ float(x + 9), float(y + 4) });
375 
376  ImVec4 shadow{ 1.f, 1.f, 1.f, 0.1f };
377  ImGui::GetWindowDrawList()->AddRectFilled({ float(x), float(y) },
378  { float(x + width), float(y + 25) }, ImColor(shadow));
379 
380  if (update_state != RS2_FWU_STATE_COMPLETE)
381  {
382  ImGui::Text("Firmware Update Recommended!");
383 
384  ImGui::SetCursorScreenPos({ float(x + 5), float(y + 27) });
385 
386  draw_text(get_title().c_str(), x, y, height - 50);
387 
388  ImGui::SetCursorScreenPos({ float(x + 9), float(y + height - 67) });
389 
391 
392  if (update_state == RS2_FWU_STATE_INITIAL_PROMPT)
393  ImGui::Text("Firmware updates offer critical bug fixes and\nunlock new camera capabilities.");
394  else
395  ImGui::Text("Firmware update is underway...\nPlease do not disconnect the device");
396 
398  }
399  else
400  {
401  //ImGui::PushStyleColor(ImGuiCol_Text, alpha(light_blue, 1.f - t));
402  ImGui::Text("Update Completed");
403  //ImGui::PopStyleColor();
404 
405  ImGui::SetCursorScreenPos({ float(x + 10), float(y + 35) });
408  ImGui::Text("%s", txt.c_str());
409  ImGui::PopFont();
410 
411  ImGui::SetCursorScreenPos({ float(x + 40), float(y + 35) });
412  ImGui::Text("Camera Firmware Updated Successfully");
413  }
414 
415  ImGui::SetCursorScreenPos({ float(x + 5), float(y + height - 25) });
416 
417  const auto bar_width = width - 115;
418 
419  if (update_manager)
420  {
421  if (update_state == RS2_FWU_STATE_INITIAL_PROMPT)
422  {
423  auto sat = 1.f + sin(duration_cast<milliseconds>(system_clock::now() - created_time).count() / 700.f) * 0.1f;
426  std::string button_name = to_string() << "Install" << "##fwupdate" << index;
427 
428  if (ImGui::Button(button_name.c_str(), { float(bar_width), 20.f }) || update_manager->started())
429  {
430  // stopping stream before starting fw update
431  auto fw_update_manager = dynamic_cast<firmware_update_manager*>(update_manager.get());
432  std::for_each(fw_update_manager->get_device_model().subdevices.begin(),
433  fw_update_manager->get_device_model().subdevices.end(),
434  [&](const std::shared_ptr<subdevice_model>& sm)
435  {
436  if (sm->streaming)
437  {
438  try
439  {
440  sm->stop(fw_update_manager->get_protected_notification_model());
441  }
442  catch (...)
443  {
444  // avoiding exception that can be sent by stop method
445  // this could happen if the sensor is not streaming and the stop method is called - for example
446  }
447  }
448  });
449 
450  auto _this = shared_from_this();
451  auto invoke = [_this](std::function<void()> action) {
452  _this->invoke(action);
453  };
454 
455  if (!update_manager->started())
456  update_manager->start(invoke);
457 
458  update_state = RS2_FWU_STATE_IN_PROGRESS;
459  enable_dismiss = false;
460  _progress_bar.last_progress_time = system_clock::now();
461  }
463 
464  if (ImGui::IsItemHovered())
465  {
466  ImGui::SetTooltip("%s", "New firmware will be flashed to the device");
467  }
468  }
469  else if (update_state == RS2_FWU_STATE_IN_PROGRESS)
470  {
471  if (update_manager->done())
472  {
473  update_state = RS2_FWU_STATE_COMPLETE;
474  pinned = false;
475  _progress_bar.last_progress_time = last_interacted = system_clock::now();
476  }
477 
478  if (!expanded)
479  {
480  if (update_manager->failed())
481  {
482  update_manager->check_error(error_message);
483  update_state = RS2_FWU_STATE_FAILED;
484  pinned = false;
485  dismiss(false);
486  }
487 
488  draw_progress_bar(win, bar_width);
489 
490  ImGui::SetCursorScreenPos({ float(x + width - 105), float(y + height - 25) });
491 
492  string id = to_string() << "Expand" << "##" << index;
494  if (ImGui::Button(id.c_str(), { 100, 20 }))
495  {
496  expanded = true;
497  }
498 
500  }
501  }
502  }
503  else
504  {
505  std::string button_name = to_string() << "Learn More..." << "##" << index;
506 
507  if (ImGui::Button(button_name.c_str(), { float(bar_width), 20 }))
508  {
510  }
511  if (ImGui::IsItemHovered())
512  {
513  win.link_hovered();
514  ImGui::SetTooltip("%s", "Internet connection required");
515  }
516  }
517  }
518 
520  {
521  if (update_manager->started() && update_state == RS2_FWU_STATE_INITIAL_PROMPT)
522  update_state = RS2_FWU_STATE_IN_PROGRESS;
523 
527 
528  ImGui::PushStyleColor(ImGuiCol_WindowBg, { 0, 0, 0, 0 });
532 
536 
537  std::string title = "Firmware Update";
538  if (update_manager->failed()) title += " Failed";
539 
540  ImGui::OpenPopup(title.c_str());
541  if (ImGui::BeginPopupModal(title.c_str(), nullptr, flags))
542  {
544  std::string progress_str = to_string() << "Progress: " << update_manager->get_progress() << "%";
545  ImGui::Text("%s", progress_str.c_str());
546 
548 
549  draw_progress_bar(win, 490);
550 
552  auto s = update_manager->get_log();
553  ImGui::InputTextMultiline("##fw_update_log", const_cast<char*>(s.c_str()),
556 
558  if (visible || update_manager->done() || update_manager->failed())
559  {
560  if (ImGui::Button("OK", ImVec2(120, 0)))
561  {
562  if (update_manager->done() || update_manager->failed())
563  {
564  update_state = RS2_FWU_STATE_FAILED;
565  pinned = false;
566  dismiss(false);
567  }
568  expanded = false;
570  }
571  }
572  else
573  {
579  ImGui::Button("OK", ImVec2(120, 0));
581  }
582 
583  ImGui::EndPopup();
584  }
585 
588 
589  error_message = "";
590  }
591 
593  {
594  if (update_state != RS2_FWU_STATE_COMPLETE) return 150;
595  else return 65;
596  }
597 
599  {
601 
603 
604  ImVec4 c;
605 
606  if (update_state == RS2_FWU_STATE_COMPLETE)
607  {
608  c = alpha(saturate(light_blue, 0.7f), 1 - t);
610  }
611  else
612  {
613  c = alpha(sensor_bg, 1 - t);
615  }
616  }
617 
619  std::shared_ptr<firmware_update_manager> manager, bool exp)
620  : process_notification_model(manager)
621  {
622  enable_expand = false;
623  expanded = exp;
624  if (expanded) visible = false;
625 
626  message = name;
629  pinned = true;
630  forced = true;
631  }
632 }
void draw_text(int x, int y, const char *text)
Definition: rendering.h:717
std::vector< std::shared_ptr< notification_model > > related_notifications
Definition: model-views.h:816
IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val)
Definition: imgui.cpp:4650
static const ImVec4 transparent
Definition: model-views.h:44
GLenum GLuint GLenum GLsizei const GLchar * message
static const ImVec4 white
Definition: model-views.h:45
IMGUI_API void AddRectFilled(const ImVec2 &a, const ImVec2 &b, ImU32 col, float rounding=0.0f, int rounding_corners=0x0F)
Definition: imgui_draw.cpp:814
virtual void set_color_scheme(float t) const
GLuint GLuint end
GLint y
GLenum GLuint GLenum severity
IMGUI_API void SetTooltip(const char *fmt,...) IM_PRINTFARGS(1)
Definition: imgui.cpp:3288
GLuint const GLchar * name
ImVec4 saturate(const ImVec4 &a, float f)
std::vector< sensor > query_sensors() const
Definition: rs_device.hpp:25
bool check_for(std::function< bool()> action, std::function< void()> cleanup, std::chrono::system_clock::duration delta)
GLdouble s
IMGUI_API bool InputTextMultiline(const char *label, char *buf, size_t buf_size, const ImVec2 &size=ImVec2(0, 0), ImGuiInputTextFlags flags=0, ImGuiTextEditCallback callback=NULL, void *user_data=NULL)
Definition: imgui.cpp:8223
static const ImVec4 light_grey
Definition: model-views.h:40
Definition: imgui.h:88
static config_file & instance()
Definition: rs-config.cpp:80
static const ImVec4 light_blue
Definition: model-views.h:38
device_list query_devices() const
Definition: rs_context.hpp:112
const char * fw_get_L5XX_FW_Image(int)
std::string get_folder_path(special_folder f)
Definition: os.cpp:247
static const textual_icon throphy
Definition: model-views.h:258
void update_unsigned(const std::vector< uint8_t > &image, int update_mode=RS2_UNSIGNED_UPDATE_MODE_UPDATE) const
Definition: rs_device.hpp:233
def progress(args)
Definition: log.py:43
std::string get_timestamped_file_name()
Definition: os.cpp:237
fw_update_notification_model(std::string name, std::shared_ptr< firmware_update_manager > manager, bool expaned)
Definition: cah-model.h:10
GLsizei const GLchar *const * string
d
Definition: rmse.py:171
GLdouble n
Definition: glext.h:1966
e
Definition: rmse.py:177
firmware_update_ui_state
constexpr const char * recommended_fw_url
GLuint index
void draw_content(ux_window &win, int x, int y, float t, std::string &error_message) override
GLdouble t
GLboolean GLboolean GLboolean GLboolean a
GLuint GLfloat * val
T get_or_default(const char *key, T def) const
Definition: rs-config.h:65
std::vector< int > parse_fw_version(const std::string &fw)
std::function< void(std::function< void()>)> invoker
std::string error_to_string(const error &e)
Definition: rendering.h:1583
GLdouble f
IMGUI_API ImDrawList * GetWindowDrawList()
Definition: imgui.cpp:5045
IMGUI_API void PopStyleVar(int count=1)
Definition: imgui.cpp:4675
GLsizeiptr size
#define FW_SR3XX_FW_IMAGE_VERSION
static const ImVec4 regular_blue
Definition: model-views.h:39
const GLubyte * c
Definition: glext.h:12690
GLdouble x
void open_url(const char *url)
Definition: os.cpp:58
void process_flow(std::function< void()> cleanup, invoker invoke) override
unsigned int uint32_t
Definition: stdint.h:80
#define RS2_PRODUCT_LINE_D400
Definition: rs_context.h:94
const char * fw_get_SR3XX_FW_Image(int)
const char * get_info(rs2_camera_info info) const
Definition: rs_device.hpp:79
GLint GLsizei GLsizei height
static const ImVec4 sensor_bg
Definition: model-views.h:51
GLbitfield flags
void draw_expanded(ux_window &win, std::string &error_message) override
IMGUI_API void SetCursorPosX(float x)
Definition: imgui.cpp:5101
GLuint start
GLint j
static const ImVec4 sensor_header_light_blue
Definition: model-views.h:50
IMGUI_API void Text(const char *fmt,...) IM_PRINTFARGS(1)
Definition: imgui.cpp:5223
IMGUI_API bool Button(const char *label, const ImVec2 &size=ImVec2(0, 0))
Definition: imgui.cpp:5573
Definition: imgui.h:98
void log(std::string line)
#define RS2_PRODUCT_LINE_L500
Definition: rs_context.h:96
rs2_notification_category category
Definition: notifications.h:74
IMGUI_API void EndPopup()
Definition: imgui.cpp:3489
action
Definition: enums.py:62
const char * fw_get_D4XX_FW_Image(int)
ImVec4 alpha(const ImVec4 &v, float a)
IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4 &col)
Definition: imgui.cpp:4599
bool supports(rs2_camera_info info) const
Definition: rs_device.hpp:66
IMGUI_API void PushFont(ImFont *font)
Definition: imgui.cpp:4539
bool is_recommended_fw_available(std::string id)
std::map< int, std::vector< uint8_t > > create_default_fw_table()
int parse_product_line(std::string id)
#define FW_D4XX_FW_IMAGE_VERSION
const GLuint GLenum const void * binary
Definition: glext.h:1882
GLint GLsizei count
void set_color_scheme(float t) const override
IMGUI_API bool BeginPopupModal(const char *name, bool *p_open=NULL, ImGuiWindowFlags extra_flags=0)
Definition: imgui.cpp:3465
float rs2_vector::* pos
IMGUI_API void SetCursorScreenPos(const ImVec2 &pos)
Definition: imgui.cpp:5127
int stoi(const std::string &value)
#define NULL
Definition: tinycthread.c:47
int i
std::shared_ptr< notifications_model > get_protected_notification_model()
std::string get_available_firmware_version(int product_line)
IMGUI_API void CloseCurrentPopup()
Definition: imgui.cpp:3408
ImFont * get_large_font() const
Definition: ux-window.h:62
IMGUI_API void OpenPopup(const char *str_id)
Definition: imgui.cpp:3343
bool is_upgradeable(const std::string &curr, const std::string &available)
void fail(std::string error)
#define FW_L5XX_FW_IMAGE_VERSION
#define RS2_PRODUCT_LINE_SR300
Definition: rs_context.h:95
void link_hovered()
Definition: ux-window.cpp:166
IMGUI_API bool IsItemHovered()
Definition: imgui.cpp:3200
std::vector< uint8_t > _fw
static const char * allow_rc_firmware
Definition: model-views.h:135
GLint GLsizei width
IMGUI_API void PopFont()
Definition: imgui.cpp:4549
IMGUI_API void PopStyleColor(int count=1)
Definition: imgui.cpp:4609
T as() const
Definition: rs_device.hpp:129
std::string to_string(T value)


librealsense2
Author(s): Sergey Dorodnicov , Doron Hirshberg , Mark Horn , Reagan Lopez , Itay Carpis
autogenerated on Mon May 3 2021 02:47:15