{"id":479817,"date":"2026-05-15T07:17:04","date_gmt":"2026-05-15T07:17:04","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=479817"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=479817","title":{"rendered":"\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043f\u043e\u0441\u0451\u043b\u043e\u043a \u0447.3: LED-\u044d\u043a\u0440\u0430\u043d \u0438 \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0422\u0440\u0435\u0442\u044c\u044f (\u0438 \u043f\u043e\u043a\u0430 \u0447\u0442\u043e \u0437\u0430\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f) \u0447\u0430\u0441\u0442\u044c \u043e\u0431 \u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e-\u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0445 DIY-\u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0432 \u043f\u043e\u0441\u0451\u043b\u043a\u0435 (<a href=\"https:\/\/habr.com\/ru\/articles\/1033652\/\" rel=\"noopener noreferrer nofollow\">\u0447\u0430\u0441\u0442\u044c 1<\/a>, <a href=\"https:\/\/habr.com\/ru\/articles\/1033804\/\" rel=\"noopener noreferrer nofollow\">\u0447\u0430\u0441\u0442\u044c 2<\/a>). \u0420\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043f\u0440\u043e \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u043d\u044b\u0435 \u044d\u043a\u0440\u0430\u043d\u044b \u0438 \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442 \u0434\u043b\u044f \u0434\u0435\u0442\u0441\u043a\u043e\u0439 \u043f\u043b\u043e\u0449\u0430\u0434\u043a\u0438.<\/p>\n<p>\u041e\u0434\u043d\u0430\u0436\u0434\u044b \u0432\u044b\u044f\u0441\u043d\u0438\u043b\u043e\u0441\u044c, \u0447\u0442\u043e \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430 \u0432\u044a\u0435\u0437\u0434\u0435 \u0432 \u043f\u043e\u0441\u0435\u043b\u043e\u043a \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0441\u0442\u0435\u043d\u0434, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c, \u043a\u0442\u043e \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u043e\u0441\u0435\u043b\u043e\u043a, \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043d\u044b\u0435 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u044b \u0438 \u0442\u043e\u043c\u0443 \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0435. \u0423\u0432\u0435\u0440\u0435\u043d, \u043c\u043d\u043e\u0433\u0438\u0435 \u0432\u0438\u0434\u0435\u043b\u0438 \u0442\u0430\u043a\u0438\u0435 \u0441\u0442\u0435\u043d\u0434\u044b \u0438 \u0432 \u043f\u043e\u0441\u0451\u043b\u043a\u0430\u0445 \u0438 \u0432 \u043c\u043d\u043e\u0433\u043e\u043a\u0432\u0430\u0440\u0442\u0438\u0440\u043d\u044b\u0445 \u0434\u043e\u043c\u0430\u0445.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/68c\/224\/f34\/68c224f34d5ea27c3bc365c03f390bad.png\" alt=\"\u0422\u0438\u043f\u0438\u0447\u043d\u044b\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0441\u0442\u0435\u043d\u0434. \u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430.\" title=\"\u0422\u0438\u043f\u0438\u0447\u043d\u044b\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0441\u0442\u0435\u043d\u0434. \u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430.\" width=\"473\" height=\"458\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/68c\/224\/f34\/68c224f34d5ea27c3bc365c03f390bad.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/68c\/224\/f34\/68c224f34d5ea27c3bc365c03f390bad.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0422\u0438\u043f\u0438\u0447\u043d\u044b\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0441\u0442\u0435\u043d\u0434. \u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430.<\/figcaption><\/div>\n<\/figure>\n<p>\u0412\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0441\u0442\u0435\u043d\u0434\u044b \u0432 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0435 \u0441\u0432\u043e\u0435\u043c \u0443\u0436\u0430\u0441\u043d\u043e, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438\u043c\u0438 \u043d\u0435\u0443\u0434\u043e\u0431\u043d\u043e (\u0430 \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0438\u043a\u0442\u043e \u0438 \u043d\u0435 \u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f), \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u0438\u0434\u0435\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0435, \u043d\u043e \u0432 \u0432\u0438\u0434\u0435 \u044d\u043a\u0440\u0430\u043d\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u043b\u0430\u0441\u044c \u0431\u044b \u043f\u043e\u043b\u0435\u0437\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f.<\/p>\n<p>\u041f\u0435\u0440\u0432\u0430\u044f \u043c\u044b\u0441\u043b\u044c \u0431\u044b\u043b\u0430: \u0434\u043e\u0431\u044b\u0442\u044c \u043a\u0430\u043a\u043e\u0439-\u043d\u0438\u0431\u0443\u0434\u044c \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 \u0438 \u043f\u043e\u0432\u0435\u0441\u0438\u0442\u044c \u0435\u0433\u043e \u043f\u043e\u0434 \u043d\u0430\u0432\u0435\u0441\u043e\u043c. \u041d\u043e \u0432\u044b\u044f\u0441\u043d\u0438\u043b\u043e\u0441\u044c, \u0447\u0442\u043e \u043c\u043e\u0434\u0435\u043b\u0438, \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u044b\u0435 \u043e\u0442 \u0432\u043b\u0430\u0433\u0438, \u0441\u0442\u043e\u044f\u0442 \u043e\u0447\u0435\u043d\u044c \u0434\u043e\u0440\u043e\u0433\u043e, \u043d\u0430 \u0441\u043e\u043b\u043d\u0446\u0435 \u0438\u0445 \u0432\u0438\u0434\u043d\u043e \u043f\u043b\u043e\u0445\u043e, \u0430 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0443 \u043d\u0438\u0445 \u0441\u0438\u043b\u044c\u043d\u043e \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u043d\u043e. <\/p>\n<p>\u0412\u043d\u0430\u0447\u0430\u043b\u0435 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b\u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c <a href=\"https:\/\/habr.com\/ru\/companies\/vk\/articles\/395519\/\" rel=\"noopener noreferrer nofollow\">\u044d\u043a\u0440\u0430\u043d \u0438\u0437 \u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u043d\u043e\u0439 \u043b\u0435\u043d\u0442\u044b<\/a>, \u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u0441\u0442\u0430\u043b\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0447\u0442\u043e \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043e\u043d \u043d\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442: \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0432 \u0441\u043e\u043b\u043d\u0435\u0447\u043d\u0443\u044e \u043f\u043e\u0433\u043e\u0434\u0443 \u0432\u0441\u0435 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u043e \u043c\u0435\u0436\u0434\u0443 \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u0430\u043c\u0438 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0447\u0435\u0440\u043d\u044b\u043c, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442, \u043d\u0443\u0436\u043d\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430\u043a\u043b\u0430\u0434\u043a\u0443 \u0441 \u043e\u0442\u0432\u0435\u0440\u0441\u0442\u0438\u044f\u043c\u0438 \u043f\u043e\u0434 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434. \u041a \u0442\u043e\u043c\u0443 \u0436\u0435, \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u043b\u043e\u0441\u044c \u043e\u0447\u0435\u043d\u044c \u043d\u0438\u0437\u043a\u0438\u043c, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442, \u044d\u043a\u0440\u0430\u043d \u0434\u043e\u043b\u0436\u0435\u043d \u0438\u043c\u0435\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0440\u0430\u0437\u043c\u0435\u0440\u044b \u0447\u0442\u043e\u0431\u044b \u043d\u0430 \u043d\u0435\u0433\u043e \u0432\u043b\u0435\u0437\u043b\u0438 \u0445\u043e\u0442\u044f \u0431\u044b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u043b\u043e\u0432 \u0442\u0435\u043a\u0441\u0442\u0430. \u0421\u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u0430 \u0443 \u043d\u0430\u0441 \u043d\u0435 \u0431\u044b\u043b\u043e.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/7a2\/52b\/87b\/7a252b87bef91379c484551c50544770.png\" alt=\"\u041f\u0435\u0440\u0432\u044b\u0435 \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f\u044b \u044d\u043a\u0440\u0430\u043d\u043e\u0432\" title=\"\u041f\u0435\u0440\u0432\u044b\u0435 \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f\u044b \u044d\u043a\u0440\u0430\u043d\u043e\u0432\" width=\"1000\" height=\"570\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/7a2\/52b\/87b\/7a252b87bef91379c484551c50544770.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/7a2\/52b\/87b\/7a252b87bef91379c484551c50544770.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u041f\u0435\u0440\u0432\u044b\u0435 \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f\u044b \u044d\u043a\u0440\u0430\u043d\u043e\u0432<\/figcaption><\/div>\n<\/figure>\n<p>\u041d\u043e \u043d\u0430 \u0443\u043b\u0438\u0446\u0430\u0445 \u0436\u0435 \u0447\u0430\u0441\u0442\u043e \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0442\u0441\u044f \u0440\u0435\u043a\u043b\u0430\u043c\u043d\u044b\u0435 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u0432 \u0432\u0438\u0434\u0435 \u044d\u043a\u0440\u0430\u043d\u043e\u0432, \u043a\u0430\u043a-\u0442\u043e \u0438\u0445 \u0438\u0437\u0433\u043e\u0442\u0430\u0432\u043b\u0438\u0432\u0430\u044e\u0442, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442, \u0441\u043c\u043e\u0436\u0435\u043c \u0438\u0437\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0438 \u043c\u044b. \u0412\u044b\u044f\u0441\u043d\u0438\u043b\u043e\u0441\u044c, \u0447\u0442\u043e \u044d\u043a\u0440\u0430\u043d\u044b, \u0434\u0430\u0436\u0435 \u0441\u0430\u043c\u044b\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0435, \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f \u0438\u0437 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439. \u041c\u043e\u0434\u0443\u043b\u0438 \u044d\u0442\u0438 \u0431\u044b\u0432\u0430\u044e\u0442 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u0438 \u0443\u043b\u0438\u0447\u043d\u044b\u0435, \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a\u0438\u0445 4 \u0448\u0442\u0443\u043a\u0438 \u0438 \u0431\u044b\u043b\u0438 <a href=\"https:\/\/aliexpress.ru\/item\/33011750395.html\" rel=\"noopener noreferrer nofollow\">\u0434\u043e\u0431\u044b\u0442\u044b \u043d\u0430 \u0430\u043b\u0438\u044d\u043a\u0441\u043f\u0440\u0435\u0441\u0441\u0435<\/a>.<\/p>\n<p>\u0412\u0441\u0442\u0430\u043b \u0432\u043e\u043f\u0440\u043e\u0441: \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0441\u0442\u0443\u043f\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435\u043e \u043d\u0430 \u044d\u0442\u0438\u0445 \u043c\u043e\u0434\u0443\u043b\u044f\u0445? \u041e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u0441 \u044d\u0442\u0438\u043c \u0432\u043f\u043e\u043b\u043d\u0435 \u0441\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0434\u0430\u0436\u0435 <a href=\"https:\/\/github.com\/mrcodetastic\/ESP32-HUB75-MatrixPanel-DMA\" rel=\"noopener noreferrer nofollow\">\u043c\u0438\u043a\u0440\u043e\u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b \u0442\u0438\u043f\u0430 ESP32<\/a>.<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/6a060121ae768ce0ec9fd54e\" data-style=\"\" id=\"6a060121ae768ce0ec9fd54e\" width=\"\" data-habr-games=\"\"><\/div>\n<p>\u0427\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u043d\u043e\u043c \u043c\u043e\u0434\u0443\u043b\u0435, \u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0438 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445. \u041c\u043e\u0434\u0443\u043b\u0438 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u0448\u043b\u0435\u0439\u0444\u043e\u0432 \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u044d\u043a\u0440\u0430\u043d \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u0430. \u0421\u043b\u0438\u0448\u043a\u043e\u043c \u0434\u043b\u0438\u043d\u043d\u044b\u0435 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c\u0441\u044f, \u0442\u043e\u0433\u0434\u0430 \u044d\u043a\u0440\u0430\u043d \u0434\u0435\u043b\u044f\u0442 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0445 \u0446\u0435\u043f\u043e\u0447\u0435\u043a \u0438 \u0434\u0435\u043b\u044f\u0442 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0443 \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430 (\u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u044e\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u044d\u043a\u0440\u0430\u043d\u043e\u0432).<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/609\/27b\/099\/60927b0996d04a5fe07b522aa68d81e3.png\" alt=\"\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430 \u0438\u0437 4-\u0445 \u0437\u043c\u0435\u0435\u043a \u043f\u043e 8 \u043c\u043e\u0434\u0443\u043b\u0435\u0439. \u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430\" title=\"\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430 \u0438\u0437 4-\u0445 \u0437\u043c\u0435\u0435\u043a \u043f\u043e 8 \u043c\u043e\u0434\u0443\u043b\u0435\u0439. \u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430\" width=\"800\" height=\"250\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/609\/27b\/099\/60927b0996d04a5fe07b522aa68d81e3.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/609\/27b\/099\/60927b0996d04a5fe07b522aa68d81e3.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430 \u0438\u0437 4-\u0445 \u0437\u043c\u0435\u0435\u043a \u043f\u043e 8 \u043c\u043e\u0434\u0443\u043b\u0435\u0439. \u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430<\/figcaption><\/div>\n<\/figure>\n<p>\u0412 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435, \u043d\u0430 \u044d\u0442\u043e\u043c \u0443\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0441\u044f: \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0438 \u043e\u043d \u0431\u044b \u0438\u0445 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u043b \u043f\u043e \u043a\u0440\u0443\u0433\u0443. \u041d\u043e \u0437\u0430\u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u043e\u043b\u044c\u0448\u0435\u0433\u043e: \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u044d\u043a\u0440\u0430\u043d \u043a \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0435\u0442\u0438 \u043f\u043e \u043f\u0440\u043e\u0432\u043e\u0434\u0443 (\u043d\u0430 \u0443\u043b\u0438\u0446\u0435 wi-fi \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u043b\u043e\u0445\u043e), \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438, \u0438 \u0441\u0430\u043c\u043e\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0435: \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u043d\u0443\u0436\u043d\u044b\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u0440\u0435\u0430\u0433\u0438\u0440\u0443\u044f \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f. \u041f\u0440\u0438\u0435\u0445\u0430\u043b\u0430 \u043a \u0448\u043b\u0430\u0433\u0431\u0430\u0443\u043c\u0443 \u043c\u0430\u0448\u0438\u043d\u0430 &#8212; \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432 \u043d\u043e\u043c\u0435\u0440, \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043d\u044f\u0442\u044c, \u0438\u0437 \u043a\u0430\u043a\u043e\u0433\u043e \u043e\u043d\u0430 \u0434\u043e\u043c\u0430 \u0438 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0435\u0439 \u043a\u0430\u043a\u043e\u0435-\u043d\u0438\u0431\u0443\u0434\u044c \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435.<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/6a05f6acba48ddb87526166b\" data-style=\"\" id=\"6a05f6acba48ddb87526166b\" width=\"\" data-habr-games=\"\"><\/div>\n<p>\u0411\u044b\u043b\u0430 \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0430 \u0438\u043c\u0435\u044e\u0449\u0430\u044f\u0441\u044f raspberry pi 3b+: \u0434\u043b\u044f \u043d\u0435\u0435 \u043d\u0430\u0448\u043b\u0430\u0441\u044c <a href=\"https:\/\/github.com\/hzeller\/rpi-rgb-led-matrix?ysclid=mp5r9ggqzr473313488\" rel=\"noopener noreferrer nofollow\">\u043e\u0442\u043b\u0438\u0447\u043d\u0430\u044f \u0433\u043e\u0442\u043e\u0432\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u043e \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u043d\u044b\u043c\u0438 \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438<\/a>. RPi \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043a \u044d\u043a\u0440\u0430\u043d\u0443 <a href=\"https:\/\/github.com\/hzeller\/rpi-rgb-led-matrix\/blob\/master\/wiring.md\" rel=\"noopener noreferrer nofollow\">\u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0435\u0439<\/a>, \u0434\u0430\u043b\u0435\u0435 \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u043d\u0430\u0443\u0447\u043d\u043e\u0433\u043e \u0442\u044b\u043a\u0430 \u0431\u044b\u043b\u0438 \u043f\u043e\u0434\u043e\u0431\u0440\u0430\u043d\u044b \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043f\u0440\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e, \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0438 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u0418\u0437 \u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u043e\u0433\u043e: \u0431\u044b\u043b\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0440\u0435\u0433\u0443\u043b\u0438\u0440\u043e\u0432\u043a\u0430 \u044f\u0440\u043a\u043e\u0441\u0442\u0438 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c \u0441\u0443\u0442\u043e\u043a, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u0440\u0435\u043c\u044f \u0432\u043e\u0441\u0445\u043e\u0434\u0430 \u0438 \u0437\u0430\u043a\u0430\u0442\u0430.<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import timeimport threadingimport mathimport osimport loggingfrom rgbmatrix import RGBMatrix, RGBMatrixOptionsfrom PIL import Image, ImageDraw, ImageFontfrom datetime import datetime, timedeltaimport pytzimport sysimport jsontry:    from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer    from urlparse import urlparse, parse_qs    import SocketServerexcept ImportError:    from http.server import BaseHTTPRequestHandler, HTTPServer    from urllib.parse import urlparse, parse_qs    import socketserver as SocketServerdef get_brightness():    # Use pytz for timezone handling in Python 2.7    moscow_tz = pytz.timezone('Europe\/Moscow')    now = datetime.now(moscow_tz)    # Get day of year    day_of_year = now.timetuple().tm_yday        # Approximate calculation for Moscow (latitude ~55.75)    # This is a simplified calculation - for production consider using a proper library        # Solar declination angle (simplified)    declination = 23.45 * math.sin(math.radians(360.0\/365.0 * (day_of_year - 81)))        # Hour angle for sunrise\/sunset    lat_rad = math.radians(55.212300)  # Moscow latitude        # Sunset hour angle    sunset_hour_angle = math.degrees(math.acos(-math.tan(lat_rad) * math.tan(math.radians(declination))))        # Sunrise and sunset in hours from solar noon    sunrise_hours = 12.0 - sunset_hour_angle\/15.0    sunset_hours = 12.0 + sunset_hour_angle\/15.0        # Apply equation of time correction (simplified)    B = math.radians(360.0\/365.0 * (day_of_year - 81))    equation_of_time = 9.87 * math.sin(2*B) - 7.53 * math.cos(B) - 1.5 * math.sin(B)        sunrise_hours -= equation_of_time\/60.0    sunset_hours -= equation_of_time\/60.0        # Create datetime objects for sunrise and sunset    sunrise_time = now.replace(hour=int(sunrise_hours),                                minute=int((sunrise_hours % 1) * 60),                               second=0, microsecond=0) + timedelta(minutes=30)    sunset_time = now.replace(hour=int(sunset_hours),                               minute=int((sunset_hours % 1) * 60),                              second=0, microsecond=0) + timedelta(minutes=30)    # Calculate transition periods    sunrise_start = sunrise_time - timedelta(minutes=30)    sunrise_end = sunrise_time + timedelta(minutes=30)    sunset_start = sunset_time - timedelta(minutes=30)    sunset_end = sunset_time + timedelta(minutes=30)        # Determine the current period    if sunrise_start &lt;= now &lt;= sunrise_end:        return 60    elif sunset_start &lt;= now &lt;= sunset_end:        return 60    elif sunrise_end &lt;= now &lt;= sunset_start:        return 100    else:        return 30# \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043b\u043e\u0433\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044flogging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)# \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f RGB \u043c\u0430\u0442\u0440\u0438\u0446\u044boptions = RGBMatrixOptions()options.rows = 32options.cols = 64options.chain_length = 2options.parallel = 2options.hardware_mapping = 'regular'options.multiplexing = 1options.gpio_slowdown = 2options.scan_mode = 1options.pwm_lsb_nanoseconds = 600options.show_refresh_rate = Falseoptions.brightness = get_brightness()matrix = RGBMatrix(options=options)# \u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0448\u0440\u0438\u0444\u0442\u043e\u0432 - \u041e\u0411\u042a\u042f\u0412\u041b\u042f\u0415\u041c \u0413\u041b\u041e\u0411\u0410\u041b\u042c\u041d\u041etry:    FONT = ImageFont.truetype(\"\/home\/pi\/DejaVuSans.ttf\", 12)    FONT_LARGE = ImageFont.truetype(\"\/home\/pi\/DejaVuSans.ttf\", 18)    FONT_XL = ImageFont.truetype(\"\/home\/pi\/DejaVuSans.ttf\", 25)except:    # \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0448\u0440\u0438\u0444\u0442 \u0435\u0441\u043b\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d    FONT = ImageFont.load_default()    FONT_LARGE = ImageFont.load_default()    FONT_XL = ImageFont.load_default()# \u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0438\u0441\u043f\u043b\u0435\u0435\u043cclass DisplayState:    NORMAL = 0    SHOW_DEBT = 1    SHOW_VOTE = 2    SHOW_175 = 3class DisplayControl:    def __init__(self):        self.state = DisplayState.NORMAL        self.debt_end_time = 0        self.message_end_time = 0        self.debt_duration = 10        self.message_duration = 10        self.last_update = time.time()        self.current_image = None        self.next_change_time = 0        self.current_image_index = 0        self.image_display_duration = 15        self.images = []        self.default_debt_duration = 5display = DisplayControl()def load_image(path):    \"\"\"\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f.\"\"\"    try:        image = Image.open(path)        if image.mode != 'RGB':            image = image.convert('RGB')        return image.resize((matrix.width, matrix.height))    except Exception as e:        logger.error(\"Error loading image %s: %s\", path, str(e))        # \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439        image = Image.new(\"RGB\", (matrix.width, matrix.height), \"red\")        draw = ImageDraw.Draw(image)        draw.text((10, 10), os.path.basename(path), font=FONT, fill=\"white\")        return imagedef draw_text(xy, text, color=\"white\", bg_color=\"black\"):    \"\"\"\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0441 \u0442\u0435\u043a\u0441\u0442\u043e\u043c.\"\"\"    image = Image.new(\"RGB\", (matrix.width, matrix.height), bg_color)    draw = ImageDraw.Draw(image)    # \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0440 \u0442\u0435\u043a\u0441\u0442\u0430    #text = u'\u041e\u043f\u043b\u0430\u0442\u0438\u0442\u0435\\n   \u0434\u043e\u043b\u0433\u0438'    text_width, text_height = FONT.getsize(text)    draw.text(xy, text, font=FONT, fill=color)    return imagedef draw_debt_screen(remaining_time):    \"\"\"\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u044d\u043a\u0440\u0430\u043d\u0430 \u0441 \u0434\u043e\u043b\u0433\u043e\u043c.\"\"\"    image = Image.new(\"RGB\", (matrix.width, matrix.height), \"black\")    draw = ImageDraw.Draw(image)        center_x, center_y = 30, matrix.height \/\/ 2    radius = 20        # \u0424\u043e\u043d\u043e\u0432\u044b\u0439 \u043a\u0440\u0443\u0433    draw.ellipse([(center_x - radius, center_y - radius),                  (center_x + radius, center_y + radius)], outline=(0, 0, 0))        # \u041f\u0440\u043e\u0433\u0440\u0435\u0441\u0441    progress = 360 * (1.02 - remaining_time \/ display.debt_duration)        # \u041e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0430 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430    for angle in range(int(progress), 360, 1):        start_x = center_x + (radius-4) * math.cos(math.radians(angle - 90))        start_y = center_y + (radius-4) * math.sin(math.radians(angle - 90))        end_x = center_x + (radius+4) * math.cos(math.radians(angle - 90))        end_y = center_y + (radius+4) * math.sin(math.radians(angle - 90))        draw.line([(start_x, start_y), (end_x, end_y)], fill=(255, 0, 0), width=2)        # \u0422\u0435\u043a\u0441\u0442 \u0441\u0447\u0435\u0442\u0447\u0438\u043a\u0430    countdown_text = str(int(remaining_time) + 1)        if hasattr(FONT_LARGE, 'getsize'):        text_width, text_height = FONT_LARGE.getsize(countdown_text)    else:        text_width, text_height = draw.textsize(countdown_text, font=FONT_LARGE)        draw.text((center_x - text_width \/\/ 2, center_y - text_height \/\/ 2 - 3),              countdown_text, font=FONT_LARGE, fill=(255, 201, 135))        # \u0422\u0435\u043a\u0441\u0442 \"\u041e\u043f\u043b\u0430\u0442\u0438\u0442\u0435 \u0434\u043e\u043b\u0433\u0438\"    debt_text = u'\u041e\u043f\u043b\u0430\u0442\u0438\u0442\u0435\\n   \u0434\u043e\u043b\u0433\u0438'    draw.text((60, 15), debt_text, font=FONT, fill=(255, 201, 135))        return imagedef draw_speed_limit(speed_limit):    \"\"\"\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u044d\u043a\u0440\u0430\u043d\u0430 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435\u043c \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438.\"\"\"    image = Image.new(\"RGB\", (matrix.width, matrix.height), \"black\")    draw = ImageDraw.Draw(image)        center_x, center_y = 26, matrix.height \/\/ 2    radius = 20        # \u041a\u0440\u0430\u0441\u043d\u044b\u0439 \u043a\u0440\u0443\u0433    for angle in range(0, 360, 1):        start_x = center_x + (radius-2) * math.cos(math.radians(angle - 90))        start_y = center_y + (radius-2) * math.sin(math.radians(angle - 90))        end_x = center_x + (radius+2) * math.cos(math.radians(angle - 90))        end_y = center_y + (radius+2) * math.sin(math.radians(angle - 90))        draw.line([(start_x, start_y), (end_x, end_y)], fill=(255, 0, 0), width=2)        # \u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438    speed_text = str(speed_limit)        if hasattr(FONT_XL, 'getsize'):        text_width, text_height = FONT_XL.getsize(speed_text)    else:        text_width, text_height = draw.textsize(speed_text, font=FONT_XL)        draw.text((center_x - text_width \/\/ 2 + 1, center_y - text_height \/\/ 2 - 3),              speed_text, font=FONT_XL, fill=(255, 201, 135))        # \u041f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0430\u044e\u0449\u0438\u0439 \u0442\u0435\u043a\u0441\u0442    lines = [u'\u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435!', u'\u041d\u0430 \u0434\u043e\u0440\u043e\u0433\u0430\u0445', u'\u0434\u0435\u0442\u0438']    current_h = 7        for line in lines:        text_width, text_height = FONT.getsize(line)        draw.text((58 + (65 - text_width) \/ 2, current_h), line, font=FONT, fill=(255, 201, 135))        current_h += 17        return imagedef fade_between_images(img1, img2, steps=10, delay=0.05):    \"\"\"\u041f\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043c\u0435\u0436\u0434\u0443 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u043c\u0438.\"\"\"    for step in range(steps + 1):        alpha = step \/ float(steps)        blended = Image.blend(img1, img2, alpha)        matrix.SetImage(blended)        time.sleep(delay)def update_display():    \"\"\"\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0438\u0441\u043f\u043b\u0435\u044f.\"\"\"    now = time.time()        # \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435    if not hasattr(update_display, 'initialized'):        display.images = []        # \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f        display.images.append(('image', load_image(\"\/home\/pi\/rpi-fb-matrix\/rpi-rgb-led-matrix\/bindings\/python\/samples\/logo1.gif\"), 10))        display.images.append(('image', draw_speed_limit(20), 30))        display.images.append(('image', load_image(\"\/home\/pi\/rpi-fb-matrix\/rpi-rgb-led-matrix\/bindings\/python\/samples\/logo2.gif\"), 10))        display.images.append(('image', draw_speed_limit(20), 30))        display.images.append(('image', draw_text((5, 7), u'\u041f\u043e \u0432\u0441\u0435\u043c \u0432\u043e\u043f\u0440\u043e\u0441\u0430\u043c:\\n+7(111)110-11-11\\n (\u0441 9 \u0434\u043e 18, \u0432\u0442-\u0441\u0431)', color=(255, 201, 135), bg_color=\"black\"), 5))        display.images.append(('image', draw_text((20, 7), u'   \u041d\u0430\u0448 \u0441\u0430\u0439\u0442:\\n  poselok.ru\\n \u043f\u043e\u0441\u0435\u043b\u043e\u043a.\u0440\u0444', color=(255, 201, 135), bg_color=\"black\"), 5))                display.current_image = display.images[0][1]        matrix.SetImage(display.current_image)        display.next_change_time = now + display.images[0][2]        update_display.initialized = True        # \u0420\u0435\u0436\u0438\u043c \u043f\u043e\u043a\u0430\u0437\u0430 \u0434\u043e\u043b\u0433\u0430    if display.state == DisplayState.SHOW_DEBT:        remaining_time = display.debt_end_time - now        if remaining_time &gt; 0:            display.current_image = draw_debt_screen(remaining_time)            matrix.SetImage(display.current_image)            return        else:            display.state = DisplayState.NORMAL            display.current_image_index = 0            last_image = display.current_image            display.current_image = display.images[0][1]            display.next_change_time = now + display.images[0][2]            fade_between_images(last_image, display.current_image)            return        # \u0420\u0435\u0436\u0438\u043c \u043f\u043e\u043a\u0430\u0437\u0430 \u0434\u043e\u043b\u0433\u0430    if display.state == DisplayState.SHOW_VOTE:        remaining_time = display.message_end_time - now        if remaining_time &gt; 0:            display.current_image = draw_vote_screen(remaining_time)            matrix.SetImage(display.current_image)            return        else:            display.state = DisplayState.NORMAL            display.current_image_index = 0            last_image = display.current_image            display.current_image = display.images[0][1]            display.next_change_time = now + display.images[0][2]            fade_between_images(last_image, display.current_image)            return      # \u0420\u0435\u0436\u0438\u043c 175    if display.state == DisplayState.SHOW_175:        remaining_time = display.message_end_time - now        if remaining_time &gt; 0:            return        else:            display.state = DisplayState.NORMAL            display.current_image_index = 0            last_image = display.current_image            display.current_image = display.images[0][1]            display.next_change_time = now + display.images[0][2]            fade_between_images(last_image, display.current_image)            return        # \u041d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0441\u043c\u0435\u043d\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439    if now &gt;= display.next_change_time:        matrix.brightness = get_brightness()                display.current_image_index = (display.current_image_index + 1) % len(display.images)        last_image = display.current_image        display.current_image = display.images[display.current_image_index][1]        display.next_change_time = now + display.images[display.current_image_index][2]        fade_between_images(last_image, display.current_image)def display_loop():    \"\"\"\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0446\u0438\u043a\u043b \u0434\u0438\u0441\u043f\u043b\u0435\u044f.\"\"\"    while True:        update_display()        time.sleep(0.1)# HTTP \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043aclass RequestHandler(BaseHTTPRequestHandler):    def _set_headers(self, content_type='application\/json'):        self.send_response(200)        self.send_header('Content-type', content_type)        self.send_header('Access-Control-Allow-Origin', '*')        self.end_headers()        def do_GET(self):        parsed_path = urlparse(self.path)        query = parse_qs(parsed_path.query)                if parsed_path.path == '\/showPayDebt':            duration = 0            try:                if 'duration' in query:                    duration = int(query['duration'][0])            except:                duration = display.default_debt_duration                        if duration &lt;= 0:                duration = display.default_debt_duration                        logger.info(\"Received showPayDebt request with duration %d seconds\", duration)            display.state = DisplayState.SHOW_DEBT            display.debt_duration = duration            display.debt_end_time = time.time() + duration                        response = {                \"status\": \"success\",                \"message\": \"Pay debt message showing for %d seconds\" % duration,                \"duration\": duration            }                        self._set_headers()            self.wfile.write(json.dumps(response))                    elif parsed_path.path == '\/showVote':            duration = 0            try:                if 'duration' in query:                    duration = int(query['duration'][0])            except:                duration = 5                        if duration &lt;= 0:                duration = 5                        logger.info(\"Received showVote request with duration %d seconds\", duration)            display.state = DisplayState.SHOW_VOTE            display.message_duration = duration            display.message_end_time = time.time() + duration                        response = {                \"status\": \"success\",                \"message\": \"Vote message showing for %d seconds\" % duration,                \"duration\": duration            }                        self._set_headers()            self.wfile.write(json.dumps(response))                    elif parsed_path.path == '\/health':            response = {                \"status\": \"healthy\",                \"current_image\": display.current_image_index if hasattr(display, 'current_image_index') else -1,                \"total_images\": len(display.images) if hasattr(display, 'images') else 0            }                        self._set_headers()            self.wfile.write(json.dumps(response))                    else:            self.send_response(404)            self.end_headers()            self.wfile.write(\"Not Found\")        def do_POST(self):        self.do_GET()        def log_message(self, format, *args):        logger.info(\"%s - %s\" % (self.address_string(), format % args))def run_server(port=5000):    \"\"\"\u0417\u0430\u043f\u0443\u0441\u043a HTTP \u0441\u0435\u0440\u0432\u0435\u0440\u0430.\"\"\"    server_address = ('', port)    httpd = HTTPServer(server_address, RequestHandler)    logger.info('Starting HTTP server on port %d...', port)    httpd.serve_forever()if __name__ == \"__main__\":    # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043f\u043e\u0442\u043e\u043a \u0434\u0438\u0441\u043f\u043b\u0435\u044f    display_thread = threading.Thread(target=display_loop)    display_thread.daemon = True    display_thread.start()        # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c HTTP \u0441\u0435\u0440\u0432\u0435\u0440    try:        run_server(5000)    except KeyboardInterrupt:        logger.info(\"Shutting down server...\")        matrix.Clear()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<\/div>\n<\/details>\n<p>\u041a\u043e\u0440\u043f\u0443\u0441 \u0441\u0434\u0435\u043b\u0430\u043d \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e: \u0444\u0430\u043d\u0435\u0440\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0437\u0430\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u044b LED \u043c\u043e\u0434\u0443\u043b\u0438, \u0432\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 \u0432\u043d\u0443\u0442\u0440\u044c \u0440\u0430\u043c\u043a\u0438 \u0438\u0437 \u0434\u0435\u0440\u0435\u0432\u044f\u043d\u043d\u044b\u0445 \u0434\u043e\u0441\u043e\u043a, \u0441\u043e\u0431\u0440\u0430\u043d\u043e \u0432\u0441\u0435 \u043d\u0430 \u0441\u0430\u043c\u043e\u0440\u0435\u0437\u044b \u0438 \u043f\u043e\u043a\u0440\u0430\u0448\u0435\u043d\u043e \u0432 \u0447\u0435\u0440\u043d\u044b\u0439 \u043c\u0430\u0442\u043e\u0432\u044b\u0439. <\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9df\/5ae\/e09\/9df5aee0931b94c8e8e8658456c54f45.png\" alt=\"\u0427\u0435\u043c \u043f\u0440\u043e\u0449\u0435 \u0433\u0440\u0430\u0444\u0438\u043a\u0430, \u0442\u0435\u043c \u043b\u0443\u0447\u0448\u0435 \u043e\u043d\u0430 \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0441\u044f \u043d\u0430 \u0442\u0430\u043a\u043e\u043c \u044d\u043a\u0440\u0430\u043d\u0435. \u0427\u0435\u0440\u043d\u044b\u0439 \u0444\u043e\u043d \u043e\u0447\u0435\u043d\u044c \u0436\u0435\u043b\u0430\u0442\u0435\u043b\u0435\u043d.\" title=\"\u0427\u0435\u043c \u043f\u0440\u043e\u0449\u0435 \u0433\u0440\u0430\u0444\u0438\u043a\u0430, \u0442\u0435\u043c \u043b\u0443\u0447\u0448\u0435 \u043e\u043d\u0430 \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0441\u044f \u043d\u0430 \u0442\u0430\u043a\u043e\u043c \u044d\u043a\u0440\u0430\u043d\u0435. \u0427\u0435\u0440\u043d\u044b\u0439 \u0444\u043e\u043d \u043e\u0447\u0435\u043d\u044c \u0436\u0435\u043b\u0430\u0442\u0435\u043b\u0435\u043d.\" width=\"978\" height=\"549\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/9df\/5ae\/e09\/9df5aee0931b94c8e8e8658456c54f45.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9df\/5ae\/e09\/9df5aee0931b94c8e8e8658456c54f45.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0427\u0435\u043c \u043f\u0440\u043e\u0449\u0435 \u0433\u0440\u0430\u0444\u0438\u043a\u0430, \u0442\u0435\u043c \u043b\u0443\u0447\u0448\u0435 \u043e\u043d\u0430 \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0441\u044f \u043d\u0430 \u0442\u0430\u043a\u043e\u043c \u044d\u043a\u0440\u0430\u043d\u0435. \u0427\u0435\u0440\u043d\u044b\u0439 \u0444\u043e\u043d \u043e\u0447\u0435\u043d\u044c \u0436\u0435\u043b\u0430\u0442\u0435\u043b\u0435\u043d.<\/figcaption><\/div>\n<\/figure>\n<p><strong>\u0418\u0433\u0440\u043e\u0432\u043e\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442<\/strong><\/p>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439 \u044d\u043a\u0440\u0430\u043d \u0440\u0435\u0448\u0438\u043b\u0438 \u043f\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043d\u0430 \u043c\u0435\u0441\u0442\u043d\u043e\u0439 \u043f\u043b\u043e\u0449\u0430\u0434\u0438, \u0440\u044f\u0434\u043e\u043c \u0441 \u0434\u0435\u0442\u0441\u043a\u043e\u0439 \u043f\u043b\u043e\u0449\u0430\u0434\u043a\u043e\u0439. \u0426\u0435\u043b\u044c \u0442\u0430 \u0436\u0435: \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u0442\u043e\u043c\u0443 \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0435. \u0418 \u0442\u0443\u0442 \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043b\u0430\u0441\u044c \u0441\u0442\u0430\u0440\u0430\u044f \u043c\u043e\u044f \u0438\u0434\u0435\u044f: \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0443\u043b\u0438\u0447\u043d\u044b\u0439 \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043c\u043e\u0433\u043b\u0438 \u0431\u044b \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0436\u0435\u043b\u0430\u044e\u0449\u0438\u0435. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0445\u043e\u0442\u0435\u043b \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u043c\u044b\u0435 \u043d\u043e\u0433\u0430\u043c\u0438 \u043a\u043d\u043e\u043f\u043a\u0438, \u043d\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u043d\u0430 \u0442\u0430\u043a\u043e\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u043a\u0435: \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u0443\u043b\u044c\u0442\u0440\u0430\u0437\u0432\u0443\u043a\u043e\u0432\u043e\u0433\u043e \u0434\u0430\u0442\u0447\u0438\u043a\u0430 \u0437\u0430\u043c\u0435\u0440\u044f\u0442\u044c \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043e \u0438\u0433\u0440\u043e\u043a\u0430 \u0438 \u0434\u0430\u043b\u0435\u0435 \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442\u044c \u0438 \u043e\u0442\u0445\u043e\u0434\u0438\u0442\u044c, \u0430 \u043f\u043e \u044d\u043a\u0440\u0430\u043d\u0443 \u0431\u0443\u0434\u0435\u0442 \u0434\u0432\u0438\u0433\u0430\u0442\u044c\u0441\u044f \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f kinect \u043d\u0430 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u043a\u0430\u0445.<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/6a0613bcba48ddb875262621\" data-style=\"\" id=\"6a0613bcba48ddb875262621\" width=\"\" data-habr-games=\"\"><\/div>\n<p>\u041d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0438\u0433\u0440\u0430, \u043a\u043e\u0433\u0434\u0430 \u0438\u0433\u0440\u043e\u043a \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043e\u043a\u043e\u043b\u043e 1.5 \u043c\u0435\u0442\u0440\u0430 \u043a \u044d\u043a\u0440\u0430\u043d\u0443 \u0438 \u0441\u0442\u043e\u0438\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434, \u0432 \u044d\u0442\u043e \u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0431\u0430\u0440. \u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0434 \u044d\u043a\u0440\u0430\u043d\u043e\u043c \u043d\u0438\u043a\u043e\u0433\u043e \u043d\u0435\u0442 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u0440\u0435\u043c\u044f, \u0438\u0433\u0440\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044f, \u0438 \u044d\u043a\u0440\u0430\u043d \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043a \u043f\u043e\u043a\u0430\u0437\u0443 \u0441\u043b\u0430\u0439\u0434\u0448\u043e\u0443 \u0438\u0437 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a. \u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441: \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0433\u0440\u043e\u043a\u0430 \u0438 \u043f\u0440\u0438\u043a\u0440\u0443\u0442\u0438\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0440\u0435\u043a\u043e\u0440\u0434\u043e\u0432. <\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/6a05aa5a0664deb84ae79dfd\" data-style=\"\" id=\"6a05aa5a0664deb84ae79dfd\" width=\"\" data-habr-games=\"\"><\/div>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0439. \u041d\u0435\u043c\u043d\u043e\u0433\u043e \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u043a\u0438 \u0434\u043b\u044f \u0441\u0433\u043b\u0430\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u0439.<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import serialimport timeimport collectionsfrom typing import Optional, List, Unionclass UARTDistanceSensor:        def __init__(self, filter_size: int = 5):        self.port = '\/dev\/ttyACM0'        self.baudrate = 9600        self.timeout = 0.1        self.filter_size = filter_size                # Initialize reading history        self.reading_history = collections.deque(maxlen=filter_size)        self.reading_history_total = collections.deque(maxlen=filter_size)        self.last_valid_distance = -1.0                # Serial connection        self.serial_conn: Optional[serial.Serial] = None                # Error tracking        self.error_count = 0        self.max_errors = 10            def connect(self) -&gt; bool:        try:            self.serial_conn = serial.Serial(                port=self.port,                baudrate=self.baudrate,                timeout=self.timeout,                bytesize=serial.EIGHTBITS,                parity=serial.PARITY_NONE,                stopbits=serial.STOPBITS_ONE            )                        # Allow time for serial port to initialize            time.sleep(2)                        # Clear any buffered data            if self.serial_conn.in_waiting:                self.serial_conn.reset_input_buffer()                            print(f\"UART connected on {self.port} at {self.baudrate} baud\")            return True                    except (serial.SerialException, OSError) as e:            print(f\"Failed to connect to UART on {self.port}: {e}\")            self.serial_conn = None            return False        def _extract_distance(self, line: str) -&gt; Optional[float]:        \"\"\"        Extract distance value from sensor output line                Args:            line: Sensor output string (e.g., \"Distance: 115.8 cm\")                    Returns:            float: Distance in cm, or None if extraction failed        \"\"\"        try:            # Remove whitespace and split by common delimiters            line = line.strip()            # Look for patterns like \"Distance: 115.8 cm\" or \"115.8 cm\"            if \"Distance:\" in line:                # Extract number after \"Distance:\"                parts = line.split(\"Distance:\")                if len(parts) &gt; 1:                    number_part = parts[1].strip()            else:                number_part = line                        # Extract the first number from the string            import re            matches = re.findall(r\"[-+]?\\d*\\.\\d+|\\d+\", number_part)                        if matches:                distance = float(matches[0])                                # Validate distance range (adjust as needed for your sensor)                if 0.0 &lt;= distance &lt;= 300.0:  # Assuming max 10m range                    return distance                else:                    return -1            else:                return -1                        except (ValueError, IndexError, AttributeError) as e:            return -1        def get_raw_distance(self) -&gt; Optional[float]:        \"\"\"        Read and parse raw distance from sensor                Returns:            float: Distance in cm, or None if reading failed        \"\"\"        if self.serial_conn is None or not self.serial_conn.is_open:            if not self.connect():                return -1                try:            # Read a line from the serial port            if self.serial_conn.in_waiting:                line = self.serial_conn.readline().decode('utf-8', errors='ignore')                if line:                    distance = self._extract_distance(line)                    if distance &gt; -1:                        self.error_count = 0  # Reset error counter on success                        return distance                    else:                        if self.error_count &lt; 99:                             self.error_count += 1                        if self.error_count &gt;= self.max_errors:                            print(f\"Warning: UART - {self.error_count} consecutive read errors\")                        return -1                    except (serial.SerialException, UnicodeDecodeError, OSError) as e:            self.error_count += 1            print(f\"Error reading from UART: {e}\")                        # Try to reconnect if we have too many errors            if self.error_count &gt;= self.max_errors:                self.disconnect()                time.sleep(1)                self.connect()                            return -1        def median_filter(self, readings: List[float]) -&gt; float:        \"\"\"Apply median filter to readings\"\"\"        if not readings:            return -1.0                # Remove None values        valid_readings = [r for r in readings if r &gt; -1]                if not valid_readings:            return -1.0                # Sort readings and get median        sorted_readings = sorted(valid_readings)        n = len(sorted_readings)                if n % 2 == 1:            # Odd number of elements            median = sorted_readings[n \/\/ 2]        else:            # Even number of elements            median = (sorted_readings[n \/\/ 2 - 1] + sorted_readings[n \/\/ 2]) \/ 2.0                    return median        def moving_average_filter(self, readings: List[float]) -&gt; float:        \"\"\"Apply moving average filter to readings\"\"\"        if not readings:            return -1.0                # Remove None values        valid_readings = [r for r in readings if r &gt; -1]                if not valid_readings:            return self.last_valid_distance if self.last_valid_distance &gt; 0 else -1.0                # Remove outliers based on last valid distance        filtered_readings = []        for reading in valid_readings:            if self.last_valid_distance &gt; 0:                # Allow 30cm jumps maximum (adjust as needed)                filtered_readings.append(reading)            else:                filtered_readings.append(reading)                if not filtered_readings:            return self.last_valid_distance if self.last_valid_distance &gt; 0 else -1.0                # Calculate weighted average (recent readings have more weight)        total = 0.0        weight_sum = 0        for i, reading in enumerate(filtered_readings):            weight = i + 1  # Linear weighting (recent = higher weight)            total += reading * weight            weight_sum += weight                    return total \/ float(weight_sum)        def get_distance(self, use_filter: str = 'average') -&gt; float:        \"\"\"        Get smoothed distance reading                Args:            use_filter: 'median', 'average', or 'raw'                    Returns:            float: Distance in cm, or -1.0 if error        \"\"\"        # Get raw reading        raw_distance = self.get_raw_distance()                # Track raw distance for debugging        self.raw_distance = raw_distance                # Add to history if valid        self.reading_history_total.append(raw_distance)                if raw_distance &gt; 0:            self.reading_history.append(raw_distance)                # If we don't have enough history, return raw or last valid        if len(self.reading_history) &lt; self.filter_size:            if raw_distance &gt; 0:                self.last_valid_distance = raw_distance                return raw_distance            else:                return self.last_valid_distance if self.last_valid_distance &gt; 0 else -1.0                # Apply selected filter        if use_filter == 'median':            filtered = self.median_filter(list(self.reading_history))        elif use_filter == 'average':            filtered = self.moving_average_filter(list(self.reading_history))        else:  # 'raw'            filtered = raw_distance if raw_distance &gt; 0 else self.last_valid_distance                # Update last valid distance if we got a good reading        if filtered &gt;= 0:            self.last_valid_distance = filtered                    return filtered        def get_reading_quality(self) -&gt; int:        \"\"\"Get quality indicator of readings (0-100%)\"\"\"        if len(self.reading_history_total) == 0:            return 0                # Count valid readings        valid_count = sum(1 for reading in self.reading_history_total                          if reading &gt; 0)                return int((valid_count \/ float(len(self.reading_history_total))) * 100)        def flush_buffer(self) -&gt; None:        \"\"\"Clear serial input buffer\"\"\"        if self.serial_conn and self.serial_conn.is_open:            self.serial_conn.reset_input_buffer()        def disconnect(self) -&gt; None:        \"\"\"Close serial connection\"\"\"        if self.serial_conn and self.serial_conn.is_open:            self.serial_conn.close()            print(f\"UART disconnected\")        def cleanup(self) -&gt; None:        \"\"\"Clean up resources\"\"\"        self.disconnect()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u0441\u0430\u043c\u043e\u0439 \u0438\u0433\u0440\u044b<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import tracebackimport timeimport randomimport mathimport RPi.GPIO as GPIOfrom rgbmatrix import RGBMatrix, RGBMatrixOptions, graphicsfrom PIL import Image, ImageDraw, ImageFontimport sysimport osimport collectionsimport sqlite3from datetime import datetime, timedeltaimport pytzimport mathfrom distance_sensor import *show_debug = False#show_debug = Truedef get_brightness():    # Use pytz for timezone handling    moscow_tz = pytz.timezone('Europe\/Moscow')    now = datetime.now(moscow_tz)    # Get day of year    day_of_year = now.timetuple().tm_yday        # Approximate calculation for Moscow (latitude ~55.75)    # This is a simplified calculation - for production consider using a proper library        # Solar declination angle (simplified)    declination = 23.45 * math.sin(math.radians(360.0\/365.0 * (day_of_year - 81)))        # Hour angle for sunrise\/sunset    lat_rad = math.radians(55.212300)  # Moscow latitude        # Sunset hour angle    sunset_hour_angle = math.degrees(math.acos(-math.tan(lat_rad) * math.tan(math.radians(declination))))        # Sunrise and sunset in hours from solar noon    sunrise_hours = 12.0 - sunset_hour_angle\/15.0    sunset_hours = 12.0 + sunset_hour_angle\/15.0        # Apply equation of time correction (simplified)    B = math.radians(360.0\/365.0 * (day_of_year - 81))    equation_of_time = 9.87 * math.sin(2*B) - 7.53 * math.cos(B) - 1.5 * math.sin(B)        sunrise_hours -= equation_of_time\/60.0    sunset_hours -= equation_of_time\/60.0        # Create datetime objects for sunrise and sunset    sunrise_time = now.replace(hour=int(sunrise_hours),                                minute=int((sunrise_hours % 1) * 60),                               second=0, microsecond=0) + timedelta(minutes=30)    sunset_time = now.replace(hour=int(sunset_hours),                               minute=int((sunset_hours % 1) * 60),                              second=0, microsecond=0) + timedelta(minutes=30)    # Calculate transition periods    sunrise_start = sunrise_time - timedelta(minutes=30)    sunrise_end = sunrise_time + timedelta(minutes=30)    sunset_start = sunset_time - timedelta(minutes=30)    sunset_end = sunset_time + timedelta(minutes=30)        # Determine the current period    if sunrise_start &lt;= now &lt;= sunrise_end:        return 60    elif sunset_start &lt;= now &lt;= sunset_end:        return 60    elif sunrise_end &lt;= now &lt;= sunset_start:        return 100    else:        return 30class GameScores:    def __init__(self, db='\/game-db\/scores.db'):        self.conn = sqlite3.connect(db)        self.c = self.conn.cursor()        self.c.execute('CREATE TABLE IF NOT EXISTS scores (score INT, date TEXT)')        self.conn.commit()        def save_score(self, score):        date = datetime.now().strftime('%Y-%m-%d %H:%M:%S')        self.c.execute('SELECT MAX(score) FROM scores')        max_score = self.c.fetchone()[0] or 0        is_high = score &gt;= max_score        self.c.execute('INSERT INTO scores VALUES (?,?)', (score, date))        self.conn.commit()        return is_high        def get_top_scores(self, n=5):        self.c.execute('SELECT score FROM scores ORDER BY score DESC LIMIT ?', (n,))        return [r[0] for r in self.c.fetchall()]        def close(self):        self.conn.close()class Game:       def __init__(self, display, sensors):        self.display = display        self.sensors = sensors        self.screen_width = 128        self.screen_height = 64                # Game states        self.STATE_SLIDESHOW = 1        self.STATE_PLAYING = 2        self.STATE_GAME_OVER = 3        self.STATE_HIGH_SCORES = 4                self.state = self.STATE_SLIDESHOW        self.state_start_time = time.time()        self.calibration_start_time = time.time()        self.last_game_over_time = time.time()        self.calibration_good_time = 0        # Slideshow variables        self.slideshow_images = []        self.current_slide_index = 0        self.slide_start_time = time.time()        self.fade_state = 0  # 0: normal, 1-100: fading out, 101-200: fading in        self.fade_alpha = 0        self.next_slide_image = None        self.load_slideshow_images()                # Game play variables        self.score = 0        self.start_time = 0        self.game_time = 0        self.speed_multiplier = 0.8  # Slower start        self.speed_increase_timer = 0                # Calibration variables        self.calibration_start_time = 0        self.calibration_good_time = 0                # Spaceship properties (square shape)        self.ship_width = 8        self.ship_height = 8        self.ship_x = 15  # Fixed horizontal position        self.ship_y = self.screen_height \/\/ 2  # Start in middle        self.ship_speed_y = 0                # Asteroids - EASIER SETTINGS        self.asteroids = []        self.asteroid_spawn_timer = 0        self.asteroid_spawn_delay = 1.5  # Slower spawn rate        self.max_asteroids = 3  # Fewer asteroids at once                # Colors        self.color_ship = (0, 255, 0)  # Green        self.color_asteroid = (255, 100, 0)  # Orange        self.color_text = (255, 255, 255)  # White        self.color_game_over = (255, 0, 0)  # Red        self.color_distance_bar = (0, 100, 255)  # Blue        self.color_calibration = (0, 200, 200)  # Cyan        self.color_countdown = (255, 255, 0)  # Yellow        self.color_green = (0, 255, 0)        self.color_yellow = (255, 255, 0)        self.color_red = (255, 0, 0)        self.color_white = (255, 255, 255)        self.color_blue = (0, 0, 255)        # Distance calibration (120-170 cm usable range)        self.min_distance = 100        self.max_distance = 150        self.raw_distance = -1        self.last_distance = -1        self.last_quality = 0        self.scores = GameScores()        self.is_high_score = False            def load_slideshow_images(self):        \"\"\"Load images for slideshow with display times\"\"\"        # Define image paths and display times in seconds        image_config = [            (\"\/root\/ledPi\/logo1.gif\", 10),            (\"\/root\/ledPi\/logo2.gif\", 5)#            (\"\/root\/ledPi\/9-may.gif\", 30)            #(\"\/root\/ledPi\/logo-newyear.gif\", 10),            #(\"\/root\/ledPi\/logo-vote.gif\", 10)        ]                for image_path, display_time in image_config:            try:                img = Image.open(image_path)                # Resize to fit display if needed                if img.size != (self.screen_width, self.screen_height):                    img = img.resize((self.screen_width, self.screen_height), Image.LANCZOS)                self.slideshow_images.append({                    'image': img.convert('RGB'),                    'display_time': display_time                })                print(f\"Loaded image: {image_path}\")            except Exception as e:                print(\"Error loading image\", e)                # Create a placeholder if image fails to load                placeholder = Image.new('RGB', (self.screen_width, self.screen_height),                                       color=(random.randint(0, 100), random.randint(0, 100), random.randint(0, 100)))                draw = ImageDraw.Draw(placeholder)                draw.text((10, 20), os.path.basename(image_path), fill=(255, 255, 255))                self.slideshow_images.append({                    'image': placeholder,                    'display_time': display_time                })    def reset_game(self):        \"\"\"Reset game to initial state\"\"\"        self.score = 0        self.start_time = time.time()        self.game_time = 0        self.speed_multiplier = 0.8  # Slower start        self.speed_increase_timer = time.time()                self.ship_y = self.screen_height \/\/ 2        self.ship_speed_y = self.screen_height \/\/ 2                self.asteroids = []        self.asteroid_spawn_timer = time.time()        self.asteroid_spawn_delay = 1.5  # Slower spawn rate                self.state = self.STATE_SLIDESHOW        self.state_start_time = time.time()        self.calibration_start_time = time.time()        self.last_game_over_time = time.time()        self.calibration_good_time = 0            def map_distance_to_y(self, distance):        \"\"\"Map distance reading to screen Y position (120-170 cm range)\"\"\"        if distance &lt; 0:            return self.last_valid_distance                    # Clamp distance to usable range        if distance &lt; self.min_distance:            clamped_dist = self.min_distance        elif distance &gt; self.max_distance:            clamped_dist = self.max_distance        else:            clamped_dist = distance                # Normalize (120cm -&gt; top of screen, 170cm -&gt; bottom of screen)        normalized = (clamped_dist - self.min_distance) \/ (self.max_distance - self.min_distance)                # Map to screen coordinates (with margin)        margin = 5        min_y = margin        max_y = self.screen_height - margin - self.ship_height                # Calculate Y position        y_pos = min_y + int(normalized * (max_y - min_y))                # Keep within bounds        if y_pos &lt; min_y:            y_pos = min_y        elif y_pos &gt; max_y:            y_pos = max_y                    return y_pos        def map_distance_to_speed_y(self, distance):        \"\"\"Map distance reading to screen Y position (120-170 cm range)\"\"\"        if distance &lt; 0:            return 0                    # Clamp distance to usable range        if distance &lt; self.min_distance:            clamped_dist = self.min_distance        elif distance &gt; self.max_distance:            clamped_dist = self.max_distance        else:            clamped_dist = distance                # Normalize (120cm -&gt; top of screen, 170cm -&gt; bottom of screen)        normalized = (clamped_dist - self.min_distance) \/ (self.max_distance - self.min_distance)                # Map to screen coordinates (with margin)        max_speed_y = 5                # Calculate Y position        speed_y = max_speed_y * (normalized - 0.5)                   return speed_y        def create_asteroid(self):        \"\"\"Create a new asteroid - fewer and smaller\"\"\"        if len(self.asteroids) &gt;= self.max_asteroids:            return                    asteroid = {            'x': self.screen_width + 10,            'y': random.randint(5, self.screen_height - 5),            'size': random.randint(2, 4),  # Smaller asteroids            'speed': random.uniform(1.0, 2.0) * self.speed_multiplier,  # Slower            'type': random.choice(['small', 'medium'])        }                # Adjust color based on size        if asteroid['size'] &lt;= 3:            asteroid['color'] = (180, 180, 180)  # Light gray for small        else:            asteroid['color'] = (200, 120, 50)   # Orange-brown for medium                    self.asteroids.append(asteroid)        def update_asteroids(self):        \"\"\"Update asteroid positions\"\"\"        current_time = time.time()                # Spawn new asteroids (slower rate)        if (current_time - self.asteroid_spawn_timer &gt; self.asteroid_spawn_delay and             len(self.asteroids) &lt; self.max_asteroids):            self.create_asteroid()            self.asteroid_spawn_timer = current_time                # Update existing asteroids        asteroids_to_remove = []        for i, asteroid in enumerate(self.asteroids):            asteroid['x'] -= asteroid['speed']                        # Remove if off screen            if asteroid['x'] &lt; -20:                asteroids_to_remove.append(i)                self.score += 5  # Less points for dodging                # Remove off-screen asteroids        for i in sorted(asteroids_to_remove, reverse=True):            del self.asteroids[i]                # Gradually increase game speed over time (slower increase)        if current_time - self.speed_increase_timer &gt; 12:  # Every 12 seconds            self.speed_multiplier = min(3.0, self.speed_multiplier * 1.1)  # Slower increase            self.asteroid_spawn_delay = max(0.8, self.asteroid_spawn_delay * 0.95)  # Minor spawn increase            self.max_asteroids = min(5, self.max_asteroids + 1)  # Gradually add more asteroids            self.speed_increase_timer = current_time        def check_collision(self):        \"\"\"Check for collisions between ship and asteroids\"\"\"        ship_left = self.ship_x        ship_right = self.ship_x + self.ship_width        ship_top = self.ship_y        ship_bottom = self.ship_y + self.ship_height                for asteroid in self.asteroids:            asteroid_left = asteroid['x'] - asteroid['size']            asteroid_right = asteroid['x'] + asteroid['size']            asteroid_top = asteroid['y'] - asteroid['size']            asteroid_bottom = asteroid['y'] + asteroid['size']                        # Simple AABB collision detection            if (ship_right &gt; asteroid_left and                 ship_left &lt; asteroid_right and                 ship_bottom &gt; asteroid_top and                 ship_top &lt; asteroid_bottom):                return True                return False        def draw_distance_bar(self, draw, current_distance):        \"\"\"Draw distance indicator bar on right side\"\"\"        bar_width = 0        bar_x = 0        bar_height = 64        bar_y = 0                if current_distance &gt;= 0 and self.min_distance &lt;= current_distance &lt;= self.max_distance and self.last_quality &gt;= 20:            # Calculate fill height            normalized = (current_distance - self.min_distance) \/ (self.max_distance - self.min_distance)            fill_height = int(bar_height * normalized)            fill_y = bar_y + fill_height            draw.rectangle([bar_x, fill_y, bar_x + bar_width, bar_y + bar_height],                           fill=self.color_distance_bar)        else:            # Calculate fill height            draw.rectangle([bar_x, 0, bar_x + bar_width, 63],                           fill=(100, 0, 0))        def update(self):        \"\"\"Update game state based on current state\"\"\"        # Get distance reading        distance = self.sensors[0].get_distance()        quality = self.sensors[0].get_reading_quality()        error_count = self.sensors[0].error_count        self.last_distance = distance        self.last_quality = quality         self.error_count = error_count        print('self.last_distance', self.last_distance)        #print(\"quality: \", quality, 'distance', distance)        if self.state == self.STATE_SLIDESHOW:            self.update_slideshow()                       elif self.state == self.STATE_PLAYING:            # Update ship position based on distance            if distance &gt;= 0:                self.ship_speed_y = self.map_distance_to_speed_y(distance)                margin = 5                min_y = margin                max_y = self.screen_height - margin - self.ship_height                self.ship_y += self.ship_speed_y                if self.ship_y &gt; max_y:                    self.ship_y = max_y                if self.ship_y &lt; min_y:                    self.ship_y = min_y                        # Update asteroids            self.update_asteroids()                        # Check for collisions            if self.check_collision():                self.game_time = time.time() - self.start_time                self.state = self.STATE_GAME_OVER                self.state_start_time = time.time()                self.final_score = self.score                self.is_high_score = self.scores.save_score(self.final_score)                                # Update score based on survival time            self.score = int((time.time() - self.start_time) * 10)        elif self.state == self.STATE_GAME_OVER:            # Show game over screen for 5 seconds            if ((not self.is_high_score and time.time() - self.state_start_time &gt;= 5) or (self.is_high_score and time.time() - self.state_start_time &gt;= 20)):                self.state = self.STATE_HIGH_SCORES                self.state_start_time = time.time()                self.last_game_over_time = time.time()        elif self.state == self.STATE_HIGH_SCORES:            # Show high scores screen for 5 seconds, then return to slideshow            if time.time() - self.state_start_time &gt;= 60:                self.state = self.STATE_SLIDESHOW                self.state_start_time = time.time()                self.current_slide_index = 0                self.slide_start_time = time.time()                self.fade_state = 0        if self.state in (self.STATE_SLIDESHOW, self.STATE_HIGH_SCORES):            # Check if distance is in usable range            if distance &gt;= self.min_distance and distance &lt;= self.max_distance and error_count &lt; 30:                if self.calibration_good_time == 0:                    self.calibration_good_time = time.time()                elif ((time.time() - self.calibration_good_time &gt;= 5) or (time.time() - self.calibration_good_time &gt;= 5 and time.time() - self.last_game_over_time &lt;= 60)):                    # Good for 5 seconds, start countdown                    self.reset_game()                    self.state = self.STATE_PLAYING                    self.state_start_time = time.time()            else:                #print(\"dist\", distance, \"qual\", quality)                self.calibration_good_time = 0    def update_slideshow(self):        \"\"\"Update slideshow state\"\"\"        current_time = time.time()        #print((self.fade_alpha, self.fade_state))                if not self.slideshow_images:            return                    current_slide = self.slideshow_images[self.current_slide_index]        display_time = current_slide['display_time']                # Handle fading between slides        if self.fade_state == 0:            # Normal display - check if time to fade out            if current_time - self.slide_start_time &gt;= display_time - 1.0:  # Start fade 1 second before end                self.fade_state = 1                self.fade_alpha = 0        elif 1 &lt;= self.fade_state &lt;= 100:            # Fading out current slide            self.fade_alpha = self.fade_state            self.fade_state += 4  # Adjust fade speed                        if self.fade_state &gt; 100:                self.fade_state = 101                # Prepare next slide                next_index = (self.current_slide_index + 1) % len(self.slideshow_images)                self.next_slide_image = self.slideshow_images[next_index]['image']        elif 101 &lt;= self.fade_state &lt;= 200:            # Fading in next slide            self.fade_alpha = self.fade_state - 101            self.fade_state += 4  # Adjust fade speed                        if self.fade_state &gt; 200:                # Transition complete                self.current_slide_index = (self.current_slide_index + 1) % len(self.slideshow_images)                self.slide_start_time = current_time                self.fade_state = 0                self.fade_alpha = 0                self.next_slide_image = None    def draw_ship(self, draw):        \"\"\"Draw the square spaceship\"\"\"        # Main body (square)        draw.rectangle([self.ship_x, self.ship_y,                        self.ship_x + self.ship_width,                        self.ship_y + self.ship_height],                       fill=self.color_ship)                # Cockpit (small square in center)        cockpit_size = 4        cockpit_x = self.ship_x + (self.ship_width - cockpit_size) \/\/ 2        cockpit_y = self.ship_y + (self.ship_height - cockpit_size) \/\/ 2        draw.rectangle([cockpit_x, cockpit_y,                        cockpit_x + cockpit_size,                        cockpit_y + cockpit_size],                       fill=(0, 100, 0))                # Engine exhaust (small squares)        exhaust_x = self.ship_x - 2        for i in range(2):            draw.rectangle([exhaust_x, self.ship_y + 2 + i*3,                            exhaust_x + 1, self.ship_y + 3 + i*3],                           fill=(255, 100 + i*30, 0))        def draw_asteroids(self, draw):        \"\"\"Draw all asteroids\"\"\"        for asteroid in self.asteroids:            # Draw asteroid as a circle            left = asteroid['x'] - asteroid['size']            top = asteroid['y'] - asteroid['size']            right = asteroid['x'] + asteroid['size']            bottom = asteroid['y'] + asteroid['size']                        # Main asteroid body            draw.ellipse([left, top, right, bottom],                         fill=asteroid['color'])        def draw_hud(self, draw):        \"\"\"Draw heads-up display during gameplay\"\"\"        # Score        score_text = str(self.score)        draw.text((100, 2), score_text, font=self.display.font_small,                  fill=self.color_text)            def draw_distance(self, draw):        \"\"\"Draw heads-up display during gameplay\"\"\"        # Score        draw.rectangle([8, 2, 58, 24], fill=(0, 0, 0))        score_text = str(int(self.last_distance)) + \" \" + str(int(self.error_count))        draw.text((10, 2), score_text, font=self.display.font_small,                  fill=self.color_text)        score_text = str(int(self.sensors[0].raw_distance))        draw.text((10, 14), score_text, font=self.display.font_small,                  fill=self.color_text)    def draw_calibration_screen(self, draw, distance):               # Status indicator        if (distance &gt;= self.min_distance and distance &lt;= self.max_distance):            # Progress bar for 5-second hold            if self.calibration_good_time &gt; 0:                hold_time = time.time() - self.calibration_good_time                if time.time() - self.last_game_over_time &lt;= 60:                    progress = min(1.0, max(hold_time, 0.0) \/ 5.0)                else:                    progress = min(1.0, max(hold_time - 2.0, 0.0) \/ 15.0)                bar_width = 128                bar_height = 1                bar_x = 0                bar_y = 63                  # Progress                fill_width = int(bar_width * progress)                draw.rectangle([bar_x, bar_y, bar_x + bar_width, bar_y + bar_height],                               fill=(0, 0, 0))                bar_color = (int(255 * progress), int(201 * progress), int(135 * progress))                if self.state == self.STATE_HIGH_SCORES:                    bar_color = (int(100 * progress), int(100 * progress), int(100 * progress))                draw.rectangle([bar_x, bar_y, bar_x + fill_width, bar_y + bar_height], fill=bar_color)        def draw_game_over_screen(self):        \"\"\"Draw game over screen\"\"\"        # Background        draw = self.display.draw        draw.rectangle((0, 0, self.screen_width, self.screen_height),                       fill=(0, 0, 0))        if self.is_high_score:            img = Image.open(\"\/root\/ledPi\/crowns.gif\").convert('RGB')            self.display.image.paste(img, (0, 0))        # Score        score_text = \"{}\".format(self.final_score)        # Get text size using textbbox (Pillow 8.0+)        bbox = draw.textbbox((0, 0), score_text, font=self.display.font_xlarge)        score_width = bbox[2] - bbox[0]        score_height = bbox[3] - bbox[1]        draw.text(((self.screen_width - score_width) \/\/ 2, 10),                  score_text, font=self.display.font_xlarge,                  fill=(100, 100, 100))            def draw_high_scores_screen(self):        \"\"\"Draw game over screen\"\"\"        # Background        draw = self.display.draw        draw.rectangle((0, 0, self.screen_width, self.screen_height),                       fill=(0, 0, 0))                high_score_list = self.scores.get_top_scores(10)        color = (100, 100, 100)        current_line = 0        #print(high_score_list)        for cur_score in high_score_list[:5]:            score_text = str(current_line+1) + '. ' + str(cur_score)            # Get text size using textbbox (Pillow 8.0+)            bbox = draw.textbbox((0, 0), score_text, font=self.display.font_hscore)            score_width = bbox[2] - bbox[0]            score_height = bbox[3] - bbox[1]            if current_line == 0:                color = (255, 215, 0)            elif current_line == 1:                color = (197, 201, 199)            elif current_line == 2:                color = (205, 127, 50)            else:                color = (100, 100, 100)            draw.text(((self.screen_width - score_width) \/\/ 2 - 30, -1 + current_line * 12),                  score_text, font=self.display.font_hscore,                  fill=color)            current_line += 1        current_line = 0        for cur_score in high_score_list[5:]:            score_text = str(current_line+6) + '. ' + str(cur_score)            # Get text size using textbbox (Pillow 8.0+)            bbox = draw.textbbox((0, 0), score_text, font=self.display.font_hscore)            score_width = bbox[2] - bbox[0]            score_height = bbox[3] - bbox[1]            draw.text(((self.screen_width - score_width) \/\/ 2 + 30, -1 + current_line * 12),                  score_text, font=self.display.font_hscore,                  fill=color)            current_line += 1            def draw_slideshow_screen(self, draw):                   current_slide = self.slideshow_images[self.current_slide_index]        black_img = Image.new('RGB', (self.screen_width, self.screen_height), (0, 0, 0))                if self.fade_state == 0:            # Normal display            draw.rectangle((0, 0, self.screen_width, self.screen_height), fill=(0, 0, 0))            self.display.image.paste(current_slide['image'], (0, 0))        elif 1 &lt;= self.fade_state &lt;= 100:            # Fading out            alpha = self.fade_state \/ 100.0            current_img = current_slide['image'].copy()            blended = Image.blend(black_img, current_img, 1.0 - alpha)            self.display.image.paste(blended, (0, 0))        elif 101 &lt;= self.fade_state &lt;= 200 and self.next_slide_image:            # Fading in            alpha = (200.0 - self.fade_state) \/ 100.0            blended = Image.blend(black_img, self.next_slide_image, 1.0 - alpha)            self.display.image.paste(blended, (0, 0))        def draw_playing_screen(self, draw, distance):        \"\"\"Draw gameplay screen\"\"\"        # Space background        draw.rectangle((0, 0, self.screen_width, self.screen_height),                       fill=(5, 5, 20))                # Stars        for _ in range(15):  # Fewer stars            x = random.randint(0, self.screen_width)            y = random.randint(0, self.screen_height)            brightness = random.randint(150, 255)            self.display.draw.point((x, y), fill=(brightness, brightness, 255))                # Game elements        self.draw_asteroids(draw)        self.draw_ship(draw)        self.draw_hud(draw)        self.draw_distance_bar(draw, distance)        def draw(self):        \"\"\"Draw the entire game frame based on current state\"\"\"        # Clear canvas        #self.display.draw.rectangle((0, 0, self.screen_width, self.screen_height),         #                           fill=(0, 0, 0))                self.display.matrix.brightness = get_brightness()        if self.state == self.STATE_SLIDESHOW:            self.draw_slideshow_screen(self.display.draw)                    elif self.state == self.STATE_GAME_OVER:            self.draw_game_over_screen()        elif self.state == self.STATE_HIGH_SCORES:            self.draw_high_scores_screen()        elif self.state == self.STATE_PLAYING:            self.draw_playing_screen(self.display.draw, self.last_distance)        if self.state in (self.STATE_SLIDESHOW, self.STATE_HIGH_SCORES):            self.draw_calibration_screen(self.display.draw, self.last_distance)        if show_debug:            self.draw_distance(self.display.draw)        # Update matrix        self.display.matrix.SetImage(self.display.image.convert('RGB'))class DistanceDisplay:    \"\"\"Class to display on RGB LED matrix (128x64)\"\"\"        def __init__(self, rows=64, cols=128, chain_length=2, parallel=1):        \"\"\"Initialize RGB matrix display for 128x64\"\"\"        self.rows = rows        self.cols = cols                # Configuration for 128x64 matrix        options = RGBMatrixOptions()        options.rows = 32        options.cols = 64        options.chain_length = 2        options.parallel = 2        options.hardware_mapping = 'regular'        options.multiplexing = 1        options.gpio_slowdown = 2        options.scan_mode = 1        options.pwm_lsb_nanoseconds = 600        options.show_refresh_rate = False        options.brightness = get_brightness()                # Create matrix object        self.matrix = RGBMatrix(options = options)                # Create canvas        self.image = Image.new(\"RGB\", (cols, rows))        self.draw = ImageDraw.Draw(self.image)                # Try to load fonts        try:            self.font_xlarge = ImageFont.truetype(\"\/root\/ledPi\/Squary.ttf\", 60)            self.font_hscore = ImageFont.truetype(\"\/root\/ledPi\/Squary.ttf\", 24)            self.font_large = ImageFont.truetype(\"\/root\/ledPi\/FreeSansBold.ttf\", 20)            self.font_small = ImageFont.truetype(\"\/root\/ledPi\/FreeSans.ttf\", 12)            self.font_tiny = ImageFont.truetype(\"\/root\/ledPi\/FreeSans.ttf\", 10)        except:            self.font_large = ImageFont.load_default()            self.font_small = ImageFont.load_default()            self.font_tiny = ImageFont.load_default()                # Colors        self.color_green = (0, 255, 0)        self.color_yellow = (255, 255, 0)        self.color_red = (255, 0, 0)        self.color_white = (255, 255, 255)        self.color_blue = (0, 0, 255)        def clear(self):        \"\"\"Clear the display\"\"\"        self.matrix.Clear()        def cleanup(self):        \"\"\"Clean up display resources\"\"\"        self.matrix.Clear()def main():    \"\"\"Main application function\"\"\"            # Matrix configuration for 128x64    MATRIX_ROWS = 64    MATRIX_COLS = 128    MATRIX_CHAIN = 2    MATRIX_PARALLEL = 1        # Game update interval    UPDATE_INTERVAL = 0.05  # 20 FPS    try:        # Initialize sensor        sensor = UARTDistanceSensor()#        sensor2 = AJSR04MSensor(0, 21, 'right')                # Initialize display        display = DistanceDisplay(rows=MATRIX_ROWS,                                  cols=MATRIX_COLS,                                 chain_length=MATRIX_CHAIN,                                 parallel=MATRIX_PARALLEL)                # Initialize game (starts in splash screen)        game = Game(display, [sensor])                # Main game loop        while True:            try:                # Update game state                game.update()                                # Draw game frame                game.draw()                                # Wait before next frame                time.sleep(UPDATE_INTERVAL)                            except KeyboardInterrupt:                print(\"\\nGame stopped by user.\")                break            except Exception as e:                print(f\"Error in game loop: {e}\")                traceback.print_exc()                time.sleep(0.1)                    except KeyboardInterrupt:        print(\"\\nApplication terminated by user.\")    except Exception as e:        print(f\"Fatal error: {e}\")    finally:        print(\"\\nCleaning up resources...\")        try:            display.clear()            sensor.cleanup()        except:            passif __name__ == \"__main__\":    main()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<\/div>\n<\/details>\n<p>\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043e\u0447\u0435\u043d\u044c \u0431\u043e\u043b\u044c\u0448\u0438\u0435, \u0435\u0441\u0442\u044c \u043a\u0443\u0434\u0430 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0442\u044c\u0441\u044f. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0440\u0435\u043a\u043e\u0440\u0434\u0430 \u0434\u0435\u043b\u0430\u0442\u044c \u0444\u043e\u0442\u043e \u043f\u043e\u0431\u0435\u0434\u0438\u0442\u0435\u043b\u044f \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0435\u0433\u043e \u0432 \u0433\u0440\u0443\u043f\u043f\u0443 \u0432 \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0435. \u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0437\u043e\u0432 \u0438 \u043f\u043e\u0434\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u0440\u0435\u0430\u043a\u0446\u0438\u044e \u044d\u043a\u0440\u0430\u043d\u0430 \u043d\u0430 \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u0435 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0440\u043e\u0441\u0442\u0430: \u043f\u043e-\u0440\u0430\u0437\u043d\u043e\u043c\u0443 \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u0442\u0435\u0439 \u0438 \u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0445. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442\u0438, \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438 \u0442\u0435\u043a\u0441\u0442, \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 \u0444\u043e\u0442\u043e \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0434\u043e\u0448\u0435\u043b (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043a\u0430\u043a\u043e\u0439-\u043d\u0438\u0431\u0443\u0434\u044c \u043a\u043e\u043c\u043f\u043b\u0438\u043c\u0435\u043d\u0442, \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u043f\u043e\u043b, \u0432\u043e\u0437\u0440\u0430\u0441\u0442, \u043e\u0434\u0435\u0436\u0434\u0443 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430). \u0415\u0441\u0442\u044c \u043f\u0440\u043e\u0432\u043e\u0434\u043d\u043e\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443, \u0437\u043d\u0430\u0447\u0438\u0442, \u0432\u0441\u0435 \u044d\u0442\u043e \u0441\u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u0431\u0435\u0437 \u0437\u0430\u0434\u0435\u0440\u0436\u0435\u043a. \u0421\u0434\u0435\u043b\u0430\u0442\u044c \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c \u0447\u0435\u0440\u0435\u0437 \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440 \u0438\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435, \u043f\u043e\u0437\u0434\u0440\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u0433\u043e-\u0442\u043e \u0441 \u0434\u043d\u0435\u043c \u0440\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u0438 \u0442\u043e\u043c\u0443 \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0435).<\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u043c \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u0431\u044b\u043b\u0430 \u043d\u0430\u043b\u0430\u0434\u043a\u0430 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0443\u043b\u044c\u0442\u0440\u0430\u0437\u0432\u0443\u043a\u043e\u0432\u043e\u0433\u043e \u0434\u0430\u0442\u0447\u0438\u043a\u0430. \u0421\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u043d\u044b\u0435 \u043c\u043e\u0434\u0443\u043b\u0438, \u0441\u0443\u0434\u044f \u043f\u043e \u0432\u0441\u0435\u043c\u0443, \u0441\u043e\u0437\u0434\u0430\u044e\u0442 \u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u043c\u0435\u0445\u0438 \u0432 \u0446\u0435\u043f\u044f\u0445 raspberry pi, \u0438 \u0432 \u0438\u0442\u043e\u0433\u0435 \u0434\u0430\u0442\u0447\u0438\u043a \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0435\u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e. \u042d\u0442\u0430 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u044c \u0437\u0430\u043c\u0435\u0442\u043d\u0430 \u0434\u0430\u0436\u0435 \u0433\u043b\u0430\u0437\u0443: \u043a\u043e\u0433\u0434\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0440\u0435\u0437\u043a\u043e \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f, \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442 \u043f\u0440\u044b\u0433\u0430\u0442\u044c, \u043a\u043e\u0433\u0434\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u0430, \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442 \u0432 \u043d\u043e\u0440\u043c\u0443. \u0427\u0442\u043e \u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0438 \u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b, \u043d\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u0440\u0435\u0448\u0438\u043b \u043a\u0430\u0440\u0434\u0438\u043d\u0430\u043b\u044c\u043d\u043e: \u0432\u0437\u044f\u043b \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e arduino, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u0434\u0430\u0442\u0447\u0438\u043a\u0430 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u0438\u0445 \u0447\u0435\u0440\u0435\u0437 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u0432\u044b\u0432\u043e\u0434 \u0432 serial port \u043d\u0430 raspberry pi, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e USB.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/5e9\/402\/3a4\/5e94023a4c018c3d2001541b7305fea4.png\" alt=\"\u0421\u043b\u0435\u0432\u0430 Raspberry Pi \u0431\u0435\u0437 \u043a\u043e\u0440\u043f\u0443\u0441\u0430, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0430\u044f \u043a \u0434\u0432\u0443\u043c \u0440\u044f\u0434\u0430\u043c \u043f\u043e \u0434\u0432\u0430 LED-\u043c\u043e\u0434\u0443\u043b\u044f. \u0421\u043f\u0440\u0430\u0432\u0430 -- arduino, \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u0441 \u0443\u043b\u044c\u0442\u0440\u0430\u0437\u0432\u0443\u043a\u043e\u0432\u043e\u0433\u043e \u0434\u0430\u0442\u0447\u0438\u043a\u0430. \u041f\u043e\u0441\u0435\u0440\u0435\u0434\u0438\u043d\u0435 \u0431\u043b\u043e\u043a \u043f\u0438\u0442\u0430\u043d\u0438\u044f 5\u0412 \u0434\u043b\u044f LED \u043c\u043e\u0434\u0443\u043b\u0435\u0439.\" title=\"\u0421\u043b\u0435\u0432\u0430 Raspberry Pi \u0431\u0435\u0437 \u043a\u043e\u0440\u043f\u0443\u0441\u0430, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0430\u044f \u043a \u0434\u0432\u0443\u043c \u0440\u044f\u0434\u0430\u043c \u043f\u043e \u0434\u0432\u0430 LED-\u043c\u043e\u0434\u0443\u043b\u044f. \u0421\u043f\u0440\u0430\u0432\u0430 -- arduino, \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u0441 \u0443\u043b\u044c\u0442\u0440\u0430\u0437\u0432\u0443\u043a\u043e\u0432\u043e\u0433\u043e \u0434\u0430\u0442\u0447\u0438\u043a\u0430. \u041f\u043e\u0441\u0435\u0440\u0435\u0434\u0438\u043d\u0435 \u0431\u043b\u043e\u043a \u043f\u0438\u0442\u0430\u043d\u0438\u044f 5\u0412 \u0434\u043b\u044f LED \u043c\u043e\u0434\u0443\u043b\u0435\u0439.\" width=\"1000\" height=\"563\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/5e9\/402\/3a4\/5e94023a4c018c3d2001541b7305fea4.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/5e9\/402\/3a4\/5e94023a4c018c3d2001541b7305fea4.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0421\u043b\u0435\u0432\u0430 Raspberry Pi \u0431\u0435\u0437 \u043a\u043e\u0440\u043f\u0443\u0441\u0430, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0430\u044f \u043a \u0434\u0432\u0443\u043c \u0440\u044f\u0434\u0430\u043c \u043f\u043e \u0434\u0432\u0430 LED-\u043c\u043e\u0434\u0443\u043b\u044f. \u0421\u043f\u0440\u0430\u0432\u0430 &#8212; arduino, \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u0441 \u0443\u043b\u044c\u0442\u0440\u0430\u0437\u0432\u0443\u043a\u043e\u0432\u043e\u0433\u043e \u0434\u0430\u0442\u0447\u0438\u043a\u0430. \u041f\u043e\u0441\u0435\u0440\u0435\u0434\u0438\u043d\u0435 \u0431\u043b\u043e\u043a \u043f\u0438\u0442\u0430\u043d\u0438\u044f 5\u0412 \u0434\u043b\u044f LED \u043c\u043e\u0434\u0443\u043b\u0435\u0439.<\/figcaption><\/div>\n<\/figure>\n<p>\u0423\u043b\u044c\u0442\u0440\u0430\u0437\u0432\u0443\u043a\u043e\u0432\u043e\u0439 \u0434\u0430\u0442\u0447\u0438\u043a \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043b\u043e\u0441\u044c \u0432\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0432 \u043e\u0434\u043d\u0443 \u0438\u0437 &#171;\u043d\u043e\u0433&#187;, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u0442\u043e\u0438\u0442 \u044d\u043a\u0440\u0430\u043d, \u043d\u043e \u043f\u043e\u0447\u0435\u043c\u0443-\u0442\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438\u0441\u044c \u043d\u0435\u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u044b\u043c\u0438. \u041f\u0440\u0438\u0447\u0438\u043d\u044b \u044d\u0442\u043e\u0433\u043e \u043c\u043d\u0435 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b, \u0432\u0438\u0434\u0438\u043c\u043e, \u043a\u0430\u043a-\u0442\u043e \u0432\u043b\u0438\u044f\u043b \u0442\u043e\u0442 \u0444\u0430\u043a\u0442 \u0447\u0442\u043e \u043d\u043e\u0433\u0438 \u0441\u0434\u0435\u043b\u0430\u043d\u044b \u0438\u0437 \u043c\u0435\u0442\u0430\u043b\u043b\u0430. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0430\u0442\u0447\u0438\u043a \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u043f\u043e\u0434 \u043a\u043e\u0440\u043f\u0443\u0441\u043e\u043c. \u041d\u0435\u043a\u0440\u0430\u0441\u0438\u0432\u043e, \u0437\u0430\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/629\/efe\/e5b\/629efee5be113553c1925ed032b84b60.png\" alt=\"\u0423\u043b\u044c\u0442\u0440\u0430\u0437\u0432\u0443\u043a\u043e\u0432\u043e\u0439 \u0434\u0430\u0442\u0447\u0438\u043a\" title=\"\u0423\u043b\u044c\u0442\u0440\u0430\u0437\u0432\u0443\u043a\u043e\u0432\u043e\u0439 \u0434\u0430\u0442\u0447\u0438\u043a\" width=\"987\" height=\"550\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/629\/efe\/e5b\/629efee5be113553c1925ed032b84b60.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/629\/efe\/e5b\/629efee5be113553c1925ed032b84b60.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0423\u043b\u044c\u0442\u0440\u0430\u0437\u0432\u0443\u043a\u043e\u0432\u043e\u0439 \u0434\u0430\u0442\u0447\u0438\u043a<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d78\/d3c\/5f6\/d78d3c5f6231da5ce6336c28eb48d020.png\" alt=\"\u0412\u0438\u0434 \u0441\u0437\u0430\u0434\u0438, \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0440\u0435\u0448\u0435\u0442\u043a\u0430 \u0434\u043b\u044f \u043e\u0445\u043b\u0430\u0436\u0434\u0435\u043d\u0438\u044f.\" title=\"\u0412\u0438\u0434 \u0441\u0437\u0430\u0434\u0438, \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0440\u0435\u0448\u0435\u0442\u043a\u0430 \u0434\u043b\u044f \u043e\u0445\u043b\u0430\u0436\u0434\u0435\u043d\u0438\u044f.\" width=\"1000\" height=\"563\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/d78\/d3c\/5f6\/d78d3c5f6231da5ce6336c28eb48d020.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d78\/d3c\/5f6\/d78d3c5f6231da5ce6336c28eb48d020.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0412\u0438\u0434 \u0441\u0437\u0430\u0434\u0438, \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0440\u0435\u0448\u0435\u0442\u043a\u0430 \u0434\u043b\u044f \u043e\u0445\u043b\u0430\u0436\u0434\u0435\u043d\u0438\u044f.<\/figcaption><\/div>\n<\/figure>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u044f \u0437\u0430\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u044e \u0440\u0430\u0441\u0441\u043a\u0430\u0437 \u043e \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u044f\u0445, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0432 \u043d\u0430\u0448\u0435\u043c \u043f\u043e\u0441\u0451\u043b\u043a\u0435. \u0411\u044b\u043b\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e \u0435\u0449\u0435 \u043c\u043d\u043e\u0433\u043e \u0432\u0441\u0435\u0433\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0433\u043e, \u043d\u043e \u043d\u0435 \u0442\u0430\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0433\u043e (\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u0434\u043b\u044f \u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u043e\u0431\u043e\u0440\u043e\u0442\u0430 \u0438 \u043e\u0442\u0447\u0435\u0442\u043d\u043e\u0441\u0442\u0438 \u0422\u0421\u041d, \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u043e\u0440 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u043d\u0430 \u041a\u041f\u041f, \u043f\u043e\u0434\u0441\u0447\u0435\u0442 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0443\u0447\u0430\u0441\u0442\u043a\u043e\u0432 \u0434\u043e\u0440\u043e\u0433\u0438). \u0415\u0441\u043b\u0438 \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u043e\u0443\u0447\u0430\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0432 \u0436\u0438\u0437\u043d\u0438 \u0441\u0432\u043e\u0435\u0433\u043e \u0434\u043e\u043c\u0430\/\u043f\u043e\u0441\u0435\u043b\u043a\u0430 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u0438\u0437 \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0433\u043e, \u0438\u043b\u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u0442\u044c \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443, \u0441\u043c\u0435\u043b\u043e \u043e\u0431\u0440\u0430\u0449\u0430\u0439\u0442\u0435\u0441\u044c, \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c \u043f\u043e\u043c\u043e\u0447\u044c.<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1035352\/\">https:\/\/habr.com\/ru\/articles\/1035352\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0422\u0440\u0435\u0442\u044c\u044f (\u0438 \u043f\u043e\u043a\u0430 \u0447\u0442\u043e \u0437\u0430\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f) \u0447\u0430\u0441\u0442\u044c \u043e\u0431 \u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e-\u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0445 DIY-\u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0432 \u043f\u043e\u0441\u0451\u043b\u043a\u0435 (\u0447\u0430\u0441\u0442\u044c 1, \u0447\u0430\u0441\u0442\u044c 2). \u0420\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043f\u0440\u043e \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u043d\u044b\u0435 \u044d\u043a\u0440\u0430\u043d\u044b \u0438 \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442 \u0434\u043b\u044f \u0434\u0435\u0442\u0441\u043a\u043e\u0439 \u043f\u043b\u043e\u0449\u0430\u0434\u043a\u0438.\u041e\u0434\u043d\u0430\u0436\u0434\u044b \u0432\u044b\u044f\u0441\u043d\u0438\u043b\u043e\u0441\u044c, \u0447\u0442\u043e \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430 \u0432\u044a\u0435\u0437\u0434\u0435 \u0432 \u043f\u043e\u0441\u0435\u043b\u043e\u043a \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0441\u0442\u0435\u043d\u0434, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c, \u043a\u0442\u043e \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u043e\u0441\u0435\u043b\u043e\u043a, \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043d\u044b\u0435 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u044b \u0438 \u0442\u043e\u043c\u0443 \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0435. \u0423\u0432\u0435\u0440\u0435\u043d, \u043c\u043d\u043e\u0433\u0438\u0435 \u0432\u0438\u0434\u0435\u043b\u0438 \u0442\u0430\u043a\u0438\u0435 \u0441\u0442\u0435\u043d\u0434\u044b \u0438 \u0432 \u043f\u043e\u0441\u0451\u043b\u043a\u0430\u0445 \u0438 \u0432 \u043c\u043d\u043e\u0433\u043e\u043a\u0432\u0430\u0440\u0442\u0438\u0440\u043d\u044b\u0445 \u0434\u043e\u043c\u0430\u0445.\u0422\u0438\u043f\u0438\u0447\u043d\u044b\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0441\u0442\u0435\u043d\u0434. \u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430.\u0412\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0441\u0442\u0435\u043d\u0434\u044b \u0432 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0435 \u0441\u0432\u043e\u0435\u043c \u0443\u0436\u0430\u0441\u043d\u043e, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438\u043c\u0438 \u043d\u0435\u0443\u0434\u043e\u0431\u043d\u043e (\u0430 \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0438\u043a\u0442\u043e \u0438 \u043d\u0435 \u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f), \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u0438\u0434\u0435\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0435, \u043d\u043e \u0432 \u0432\u0438\u0434\u0435 \u044d\u043a\u0440\u0430\u043d\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u043b\u0430\u0441\u044c \u0431\u044b \u043f\u043e\u043b\u0435\u0437\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f.\u041f\u0435\u0440\u0432\u0430\u044f \u043c\u044b\u0441\u043b\u044c \u0431\u044b\u043b\u0430: \u0434\u043e\u0431\u044b\u0442\u044c \u043a\u0430\u043a\u043e\u0439-\u043d\u0438\u0431\u0443\u0434\u044c \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 \u0438 \u043f\u043e\u0432\u0435\u0441\u0438\u0442\u044c \u0435\u0433\u043e \u043f\u043e\u0434 \u043d\u0430\u0432\u0435\u0441\u043e\u043c. \u041d\u043e \u0432\u044b\u044f\u0441\u043d\u0438\u043b\u043e\u0441\u044c, \u0447\u0442\u043e \u043c\u043e\u0434\u0435\u043b\u0438, \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u044b\u0435 \u043e\u0442 \u0432\u043b\u0430\u0433\u0438, \u0441\u0442\u043e\u044f\u0442 \u043e\u0447\u0435\u043d\u044c \u0434\u043e\u0440\u043e\u0433\u043e, \u043d\u0430 \u0441\u043e\u043b\u043d\u0446\u0435 \u0438\u0445 \u0432\u0438\u0434\u043d\u043e \u043f\u043b\u043e\u0445\u043e, \u0430 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0443 \u043d\u0438\u0445 \u0441\u0438\u043b\u044c\u043d\u043e \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u043d\u043e. \u0412\u043d\u0430\u0447\u0430\u043b\u0435 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b\u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u043a\u0440\u0430\u043d \u0438\u0437 \u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u043d\u043e\u0439 \u043b\u0435\u043d\u0442\u044b, \u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u0441\u0442\u0430\u043b\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0447\u0442\u043e \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043e\u043d \u043d\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442: \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0432 \u0441\u043e\u043b\u043d\u0435\u0447\u043d\u0443\u044e \u043f\u043e\u0433\u043e\u0434\u0443 \u0432\u0441\u0435 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u043e \u043c\u0435\u0436\u0434\u0443 \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u0430\u043c\u0438 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0447\u0435\u0440\u043d\u044b\u043c, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442, \u043d\u0443\u0436\u043d\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430\u043a\u043b\u0430\u0434\u043a\u0443 \u0441 \u043e\u0442\u0432\u0435\u0440\u0441\u0442\u0438\u044f\u043c\u0438 \u043f\u043e\u0434 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434. \u041a \u0442\u043e\u043c\u0443 \u0436\u0435, \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u043b\u043e\u0441\u044c \u043e\u0447\u0435\u043d\u044c \u043d\u0438\u0437\u043a\u0438\u043c, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442, \u044d\u043a\u0440\u0430\u043d \u0434\u043e\u043b\u0436\u0435\u043d \u0438\u043c\u0435\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0440\u0430\u0437\u043c\u0435\u0440\u044b \u0447\u0442\u043e\u0431\u044b \u043d\u0430 \u043d\u0435\u0433\u043e \u0432\u043b\u0435\u0437\u043b\u0438 \u0445\u043e\u0442\u044f \u0431\u044b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u043b\u043e\u0432 \u0442\u0435\u043a\u0441\u0442\u0430. \u0421\u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u0430 \u0443 \u043d\u0430\u0441 \u043d\u0435 \u0431\u044b\u043b\u043e.\u041f\u0435\u0440\u0432\u044b\u0435 \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f\u044b \u044d\u043a\u0440\u0430\u043d\u043e\u0432\u041d\u043e \u043d\u0430 \u0443\u043b\u0438\u0446\u0430\u0445 \u0436\u0435 \u0447\u0430\u0441\u0442\u043e \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0442\u0441\u044f \u0440\u0435\u043a\u043b\u0430\u043c\u043d\u044b\u0435 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u0432 \u0432\u0438\u0434\u0435 \u044d\u043a\u0440\u0430\u043d\u043e\u0432, \u043a\u0430\u043a-\u0442\u043e \u0438\u0445 \u0438\u0437\u0433\u043e\u0442\u0430\u0432\u043b\u0438\u0432\u0430\u044e\u0442, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442, \u0441\u043c\u043e\u0436\u0435\u043c \u0438\u0437\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0438 \u043c\u044b. \u0412\u044b\u044f\u0441\u043d\u0438\u043b\u043e\u0441\u044c, \u0447\u0442\u043e \u044d\u043a\u0440\u0430\u043d\u044b, \u0434\u0430\u0436\u0435 \u0441\u0430\u043c\u044b\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0435, \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f \u0438\u0437 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439. \u041c\u043e\u0434\u0443\u043b\u0438 \u044d\u0442\u0438 \u0431\u044b\u0432\u0430\u044e\u0442 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u0438 \u0443\u043b\u0438\u0447\u043d\u044b\u0435, \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a\u0438\u0445 4 \u0448\u0442\u0443\u043a\u0438 \u0438 \u0431\u044b\u043b\u0438 \u0434\u043e\u0431\u044b\u0442\u044b \u043d\u0430 \u0430\u043b\u0438\u044d\u043a\u0441\u043f\u0440\u0435\u0441\u0441\u0435.\u0412\u0441\u0442\u0430\u043b \u0432\u043e\u043f\u0440\u043e\u0441: \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0441\u0442\u0443\u043f\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435\u043e \u043d\u0430 \u044d\u0442\u0438\u0445 \u043c\u043e\u0434\u0443\u043b\u044f\u0445? \u041e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u0441 \u044d\u0442\u0438\u043c \u0432\u043f\u043e\u043b\u043d\u0435 \u0441\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0434\u0430\u0436\u0435 \u043c\u0438\u043a\u0440\u043e\u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b \u0442\u0438\u043f\u0430 ESP32.\u0427\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u043d\u043e\u043c \u043c\u043e\u0434\u0443\u043b\u0435, \u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0438 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445. \u041c\u043e\u0434\u0443\u043b\u0438 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u0448\u043b\u0435\u0439\u0444\u043e\u0432 \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u044d\u043a\u0440\u0430\u043d \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u0430. \u0421\u043b\u0438\u0448\u043a\u043e\u043c \u0434\u043b\u0438\u043d\u043d\u044b\u0435 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c\u0441\u044f, \u0442\u043e\u0433\u0434\u0430 \u044d\u043a\u0440\u0430\u043d \u0434\u0435\u043b\u044f\u0442 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0445 \u0446\u0435\u043f\u043e\u0447\u0435\u043a \u0438 \u0434\u0435\u043b\u044f\u0442 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0443 \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430 (\u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u044e\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u044d\u043a\u0440\u0430\u043d\u043e\u0432).\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430 \u0438\u0437 4-\u0445 \u0437\u043c\u0435\u0435\u043a \u043f\u043e 8 \u043c\u043e\u0434\u0443\u043b\u0435\u0439. \u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430\u0412 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435, \u043d\u0430 \u044d\u0442\u043e\u043c \u0443\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0441\u044f: \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0438 \u043e\u043d \u0431\u044b \u0438\u0445 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u043b \u043f\u043e \u043a\u0440\u0443\u0433\u0443. \u041d\u043e \u0437\u0430\u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u043e\u043b\u044c\u0448\u0435\u0433\u043e: \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u044d\u043a\u0440\u0430\u043d \u043a \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0435\u0442\u0438 \u043f\u043e \u043f\u0440\u043e\u0432\u043e\u0434\u0443 (\u043d\u0430 \u0443\u043b\u0438\u0446\u0435 wi-fi \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u043b\u043e\u0445\u043e), \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438, \u0438 \u0441\u0430\u043c\u043e\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0435: \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u043d\u0443\u0436\u043d\u044b\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u0440\u0435\u0430\u0433\u0438\u0440\u0443\u044f \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f. \u041f\u0440\u0438\u0435\u0445\u0430\u043b\u0430 \u043a \u0448\u043b\u0430\u0433\u0431\u0430\u0443\u043c\u0443 \u043c\u0430\u0448\u0438\u043d\u0430 &#8212; \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432 \u043d\u043e\u043c\u0435\u0440, \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043d\u044f\u0442\u044c, \u0438\u0437 \u043a\u0430\u043a\u043e\u0433\u043e \u043e\u043d\u0430 \u0434\u043e\u043c\u0430 \u0438 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0435\u0439 \u043a\u0430\u043a\u043e\u0435-\u043d\u0438\u0431\u0443\u0434\u044c \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435.\u0411\u044b\u043b\u0430 \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0430 \u0438\u043c\u0435\u044e\u0449\u0430\u044f\u0441\u044f raspberry pi 3b+: \u0434\u043b\u044f \u043d\u0435\u0435 \u043d\u0430\u0448\u043b\u0430\u0441\u044c \u043e\u0442\u043b\u0438\u0447\u043d\u0430\u044f \u0433\u043e\u0442\u043e\u0432\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u043e \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u043d\u044b\u043c\u0438 \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438. RPi \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043a \u044d\u043a\u0440\u0430\u043d\u0443 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0435\u0439, \u0434\u0430\u043b\u0435\u0435 \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u043d\u0430\u0443\u0447\u043d\u043e\u0433\u043e \u0442\u044b\u043a\u0430 \u0431\u044b\u043b\u0438 \u043f\u043e\u0434\u043e\u0431\u0440\u0430\u043d\u044b \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043f\u0440\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e, \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0438 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u0418\u0437 \u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u043e\u0433\u043e: \u0431\u044b\u043b\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0440\u0435\u0433\u0443\u043b\u0438\u0440\u043e\u0432\u043a\u0430 \u044f\u0440\u043a\u043e\u0441\u0442\u0438 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c \u0441\u0443\u0442\u043e\u043a, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u0440\u0435\u043c\u044f \u0432\u043e\u0441\u0445\u043e\u0434\u0430 \u0438 \u0437\u0430\u043a\u0430\u0442\u0430.\u041a\u043e\u0434 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044bimport timeimport threadingimport mathimport osimport loggingfrom rgbmatrix import RGBMatrix, RGBMatrixOptionsfrom PIL import Image, ImageDraw, ImageFontfrom datetime import datetime, timedeltaimport pytzimport sysimport jsontry:    from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer    from urlparse import urlparse, parse_qs    import SocketServerexcept ImportError:    from http.server import BaseHTTPRequestHandler, HTTPServer    from urllib.parse import urlparse, parse_qs    import socketserver as SocketServerdef get_brightness():    # Use pytz for timezone handling in Python 2.7    moscow_tz = pytz.timezone(&#8216;Europe\/Moscow&#8217;)    now = datetime.now(moscow_tz)    # Get day of year    day_of_year = now.timetuple().tm_yday        # Approximate calculation for Moscow (latitude ~55.75)    # This is a simplified calculation &#8212; for production consider using a proper library        # Solar declination angle (simplified)    declination = 23.45 * math.sin(math.radians(360.0\/365.0 * (day_of_year &#8212; 81)))        # Hour angle for sunrise\/sunset    lat_rad = math.radians(55.212300)  # Moscow latitude        # Sunset hour angle    sunset_hour_angle = math.degrees(math.acos(-math.tan(lat_rad) * math.tan(math.radians(declination))))        # Sunrise and sunset in hours from solar noon    sunrise_hours = 12.0 &#8212; sunset_hour_angle\/15.0    sunset_hours = 12.0 + sunset_hour_angle\/15.0        # Apply equation of time correction (simplified)    B = math.radians(360.0\/365.0 * (day_of_year &#8212; 81))    equation_of_time = 9.87 * math.sin(2*B) &#8212; 7.53 * math.cos(B) &#8212; 1.5 * math.sin(B)        sunrise_hours -= equation_of_time\/60.0    sunset_hours -= equation_of_time\/60.0        # Create datetime objects for sunrise and sunset    sunrise_time = now.replace(hour=int(sunrise_hours),                                minute=int((sunrise_hours % 1) * 60),                               second=0, microsecond=0) + timedelta(minutes=30)    sunset_time = now.replace(hour=int(sunset_hours),                               minute=int((sunset_hours % 1) * 60),                              second=0, microsecond=0) + timedelta(minutes=30)    # Calculate transition periods    sunrise_start = sunrise_time &#8212; timedelta(minutes=30)    sunrise_end = sunrise_time + timedelta(minutes=30)    sunset_start = sunset_time &#8212; timedelta(minutes=30)    sunset_end = sunset_time + timedelta(minutes=30)        # Determine the current period    if sunrise_start &lt;= now &lt;= sunrise_end:        return 60    elif sunset_start &lt;= now &lt;= sunset_end:        return 60    elif sunrise_end &lt;= now &lt;= sunset_start:        return 100    else:        return 30# \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043b\u043e\u0433\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044flogging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)# \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f RGB \u043c\u0430\u0442\u0440\u0438\u0446\u044boptions = RGBMatrixOptions()options.rows = 32options.cols = 64options.chain_length = 2options.parallel = 2options.hardware_mapping = &#8216;regular&#8217;options.multiplexing = 1options.gpio_slowdown = 2options.scan_mode = 1options.pwm_lsb_nanoseconds = 600options.show_refresh_rate = Falseoptions.brightness = get_brightness()matrix = RGBMatrix(options=options)# \u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0448\u0440\u0438\u0444\u0442\u043e\u0432 &#8212; \u041e\u0411\u042a\u042f\u0412\u041b\u042f\u0415\u041c \u0413\u041b\u041e\u0411\u0410\u041b\u042c\u041d\u041etry:    FONT = ImageFont.truetype(&#171;\/home\/pi\/DejaVuSans.ttf&#187;, 12)    FONT_LARGE = ImageFont.truetype(&#171;\/home\/pi\/DejaVuSans.ttf&#187;, 18)    FONT_XL = ImageFont.truetype(&#171;\/home\/pi\/DejaVuSans.ttf&#187;, 25)except:    # \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0448\u0440\u0438\u0444\u0442 \u0435\u0441\u043b\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d    FONT = ImageFont.load_default()    FONT_LARGE = ImageFont.load_default()    FONT_XL = ImageFont.load_default()# \u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0438\u0441\u043f\u043b\u0435\u0435\u043cclass DisplayState:    NORMAL = 0    SHOW_DEBT = 1    SHOW_VOTE = 2    SHOW_175 = 3class DisplayControl:    def __init__(self):        self.state = DisplayState.NORMAL        self.debt_end_time = 0        self.message_end_time = 0        self.debt_duration = 10        self.message_duration = 10        self.last_update = time.time()        self.current_image = None        self.next_change_time = 0        self.current_image_index = 0        self.image_display_duration = 15        self.images = []        self.default_debt_duration = 5display = DisplayControl()def load_image(path):    &#171;&#187;&#187;\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f.&#187;&#187;&#187;    try:        image = Image.open(path)        if image.mode != &#8216;RGB&#8217;:            image = image.convert(&#8216;RGB&#8217;)        return image.resize((matrix.width, matrix.height))    except Exception as e:        logger.error(&#171;Error loading image %s: %s&#187;, path, str(e))        # \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439        image = Image.new(&#171;RGB&#187;, (matrix.width, matrix.height), &#171;red&#187;)        draw = ImageDraw.Draw(image)        draw.text((10, 10), os.path.basename(path), font=FONT, fill=&#187;white&#187;)        return imagedef draw_text(xy, text, color=&#187;white&#187;, bg_color=&#187;black&#187;):    &#171;&#187;&#187;\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0441 \u0442\u0435\u043a\u0441\u0442\u043e\u043c.&#187;&#187;&#187;    image = Image.new(&#171;RGB&#187;, (matrix.width, matrix.height), bg_color)    draw = ImageDraw.Draw(image)    # \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0440 \u0442\u0435\u043a\u0441\u0442\u0430    #text = u&#8217;\u041e\u043f\u043b\u0430\u0442\u0438\u0442\u0435\\n   \u0434\u043e\u043b\u0433\u0438&#8217;    text_width, text_height = FONT.getsize(text)    draw.text(xy, text, font=FONT, fill=color)    return imagedef draw_debt_screen(remaining_time):    &#171;&#187;&#187;\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u044d\u043a\u0440\u0430\u043d\u0430 \u0441 \u0434\u043e\u043b\u0433\u043e\u043c.&#187;&#187;&#187;    image = Image.new(&#171;RGB&#187;, (matrix.width, matrix.height), &#171;black&#187;)    draw = ImageDraw.Draw(image)        center_x, center_y = 30, matrix.height \/\/ 2    radius = 20        # \u0424\u043e\u043d\u043e\u0432\u044b\u0439 \u043a\u0440\u0443\u0433    draw.ellipse([(center_x &#8212; radius, center_y &#8212; radius),                  (center_x + radius, center_y + radius)], outline=(0, 0, 0))        # \u041f\u0440\u043e\u0433\u0440\u0435\u0441\u0441    progress = 360 * (1.02 &#8212; remaining_time \/ display.debt_duration)        # \u041e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0430 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430    for angle in range(int(progress), 360, 1):        start_x = center_x + (radius-4) * math.cos(math.radians(angle &#8212; 90))        start_y = center_y + (radius-4) * math.sin(math.radians(angle &#8212; 90))        end_x = center_x + (radius+4) * math.cos(math.radians(angle &#8212; 90))        end_y = center_y + (radius+4) * math.sin(math.radians(angle &#8212; 90))        draw.line([(start_x, start_y), (end_x, end_y)], fill=(255, 0, 0), width=2)        # \u0422\u0435\u043a\u0441\u0442 \u0441\u0447\u0435\u0442\u0447\u0438\u043a\u0430    countdown_text = str(int(remaining_time) + 1)        if hasattr(FONT_LARGE, &#8216;getsize&#8217;):        text_width, text_height = FONT_LARGE.getsize(countdown_text)    else:        text_width, text_height = draw.textsize(countdown_text, font=FONT_LARGE)        draw.text((center_x &#8212; text_width \/\/ 2, center_y &#8212; text_height \/\/ 2 &#8212; 3),              countdown_text, font=FONT_LARGE, fill=(255, 201, 135))        # \u0422\u0435\u043a\u0441\u0442 &#171;\u041e\u043f\u043b\u0430\u0442\u0438\u0442\u0435 \u0434\u043e\u043b\u0433\u0438&#187;    debt_text = u&#8217;\u041e\u043f\u043b\u0430\u0442\u0438\u0442\u0435\\n   \u0434\u043e\u043b\u0433\u0438&#8217;    draw.text((60, 15), debt_text, font=FONT, fill=(255, 201, 135))        return imagedef draw_speed_limit(speed_limit):    &#171;&#187;&#187;\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u044d\u043a\u0440\u0430\u043d\u0430 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435\u043c \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438.&#187;&#187;&#187;    image =&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-479817","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/479817","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=479817"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/479817\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=479817"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=479817"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=479817"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}