[{"data":1,"prerenderedAt":76},["ShallowReactive",2],{"content:\u002Farticles:1010":3},{"item":4,"md":17,"html":18,"menu":19,"excerpt":30,"related":31},{"title":5,"len":6,"tags":7,"id":11,"time":12,"modifyTime":13,"encrypt":14,"showComments":15,"customSlug":16},"树莓派监控摄像头制作",1673,[8,9,10],"树莓派","python","前端",1010,1669896775319,1679559691141,false,true,"",null,"\u003Cp data-marked-line=\"1\">&emsp;&emsp;前段时间买了台小主机，替换树莓派做云盘\u002F下载器，也安装了jellyfin和aria2等其他自建服务，性能比树莓派强很多。于是树莓派又吃灰了，这次用它做个监控摄像头。\u003C\u002Fp>\n\u003Ch1 data-marked-line=\"2\">\u003Csup class=\"fake-head\" id=\"%E5%A4%96%E8%A7%82\">\u003C\u002Fsup>\u003Ca class=\"header-link\" href=\"#%E5%A4%96%E8%A7%82\">外观\u003C\u002Fa>\u003C\u002Fh1>\u003Cp data-marked-line=\"3\">\u003Cspan class=\"image-container\">\u003Cimg data-viewer alt=\"外观\" title=\"外观\" src=\"https:\u002F\u002Fr2-blog.yunyuyuan.net\u002FrkxFVR64wqP5QbI.jpg\"\u002F>\u003Csmall class=\"desc\">外观\u003C\u002Fsmall>\u003C\u002Fspan>\n\u003Cdiv class=\"embed-media youtube\">\n                      \u003Ciframe src=\"https:\u002F\u002Fwww.youtube.com\u002Fembed\u002F2XSPOunkE6M\" title=\"演示\" frameborder=\"0\" allow=\"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen>\u003C\u002Fiframe>\n                      \u003Csmall class=\"desc\">演示\u003C\u002Fsmall>\n                  \u003C\u002Fdiv>\u003C\u002Fp>\n\u003Ch1 data-marked-line=\"5\">\u003Csup class=\"fake-head\" id=\"%E5%88%B6%E4%BD%9C\">\u003C\u002Fsup>\u003Ca class=\"header-link\" href=\"#%E5%88%B6%E4%BD%9C\">制作\u003C\u002Fa>\u003C\u002Fh1>\u003Cp data-marked-line=\"6\">&emsp;&emsp;制作过程我没有记录下来，需要的物品如下：\u003C\u002Fp>\n\u003Cul data-marked-line=\"7\">\n\u003Cli>树莓派 x1\u003C\u002Fli>\n\u003Cli>树莓派专用摄像头 x1\u003C\u002Fli>\n\u003Cli>MG995舵机(180度) x2\u003C\u002Fli>\n\u003Cli>雪糕棍若干\u003C\u002Fli>\n\u003Cli>热熔胶枪+热熔胶棒\u003C\u002Fli>\n\u003Cli>5v电源+杜邦线若干\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp data-marked-line=\"14\">&emsp;&emsp;用外接电源给两个舵机供电，需要注意的是外接电源和树莓派必须共地，否则PWM不会起作用。两个舵机的信号线接树莓派gpio的\u003Cstrong>第\u003C\u002Fstrong>16和18个针。\u003C\u002Fp>\n\u003Ch1 data-marked-line=\"15\">\u003Csup class=\"fake-head\" id=\"%E4%BB%A3%E7%A0%81\">\u003C\u002Fsup>\u003Ca class=\"header-link\" href=\"#%E4%BB%A3%E7%A0%81\">代码\u003C\u002Fa>\u003C\u002Fh1>\u003Cp data-marked-line=\"16\">&emsp;&emsp;详细代码在\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fyunyuyuan\u002Fpi-monitor\" target=\"_blank\">Github仓库\u003C\u002Fa>。前端用的\u003Ccode>react\u003C\u002Fcode>，后端是\u003Ccode>fastapi\u003C\u002Fcode>起服务，\u003Ccode>RPI.GPIO\u003C\u002Fcode>控制PWN，\u003Ccode>picamera\u003C\u002Fcode>控制摄像头。用websocket保持连接。\n&emsp;&emsp;支持多个终端同时观看：\u003C\u002Fp>\n\u003Cpre data-marked-line=\"18\" data-lang=\"python\" data-shiki>\u003Ccode class=\"language-python\">\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#7F848E;--shiki-dark-font-style:italic\"># 输出\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">output: StreamingOutput\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#61AFEF\">@app\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#ABB2BF\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#61AFEF\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#98C379\">\"\u002Fstream\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#D19A66\">{pwd}\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#98C379\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\">async\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\"> def\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#61AFEF\"> stream\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">(\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-light-font-style:inherit;--shiki-dark:#D19A66;--shiki-dark-font-style:italic\">pwd\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">, \u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-light-font-style:inherit;--shiki-dark:#D19A66;--shiki-dark-font-style:italic\">background_tasks\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">: fastapi.background.BackgroundTasks):\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#7F848E;--shiki-dark-font-style:italic\">    # 校验密码\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">    check \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#56B6C2\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#61AFEF\"> check_pwd\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">(pwd)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\"> check:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\"> check\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#7F848E;--shiki-dark-font-style:italic\">    # 做一个generator，用于自主控制关闭\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\">    def\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#61AFEF\"> streamer\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">():\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\">        while\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#D19A66\"> True\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\">            with\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\"> output.condition:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#7F848E;--shiki-dark-font-style:italic\">                # picamera输出，参考的官方示例，此处省略\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">                output.condition.\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#61AFEF\">wait\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">                frame \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#56B6C2\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\"> output.frame\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\">                yield\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\">b\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#98C379\">'--FRAME\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#56B6C2\">\\r\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#98C379\">'\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#56B6C2\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\"> b\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#98C379\">'Content-Type: image\u002Fjpeg\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#56B6C2\">\\r\\n\\r\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#98C379\">'\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#56B6C2\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\"> frame \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#56B6C2\">+\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\"> b\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#98C379\">'\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#56B6C2\">\\r\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#98C379\">'\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">    g \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#56B6C2\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#61AFEF\"> streamer\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#7F848E;--shiki-dark-font-style:italic\">    # http断开时，关闭generator，防止卡死\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">    background_tasks.\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#61AFEF\">add_task\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\">lambda\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-light-font-style:inherit;--shiki-dark:#D19A66;--shiki-dark-font-style:italic\"> g\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">: g.\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#61AFEF\">close\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">(), g)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#C678DD\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#61AFEF\"> StreamingResponse\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">(g, \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic\">media_type\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#56B6C2\">=\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#98C379\">\"multipart\u002Fx-mixed-replace;boundary=FRAME\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#ABB2BF\">)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp data-marked-line=\"41\">&emsp;&emsp;python的\u003Ccode>async\u003C\u002Fcode>和\u003Ccode>yield\u003C\u002Fcode>还是比较令人费解的，我研究了很久fastapi如何支持多个http stream连接，一开始只能一个连接，而且服务端会被block。查了一些资料，\u003Ccode>StreamingResponse\u003C\u002Fcode>既可以异步，也可以多线程，把\u003Ccode>def streamer()\u003C\u002Fcode>前面的\u003Ccode>async\u003C\u002Fcode>去掉就行了。\n&emsp;&emsp;\u003Ccode>background_tasks.add_task(lambda g: g.close(), g)\u003C\u002Fcode>在连接断开时关闭generator。相比于\u003Ccode>flask\u003C\u002Fcode>把所有连接都当子线程处理(个人猜测)，async的方式明显更好，用户可以自己控制异步还是多线程。\u003C\u002Fp>\n\u003Cp data-marked-line=\"44\">&emsp;&emsp;树莓派最近涨价地厉害，苦了爱折腾的人。虽然这个监控摄像头没啥用，而且花了好几天时间，但自己动手，完成后的小小成就感还是挺让人满足的\u003Cimg src=\"\u002Fsticker\u002Faru\u002F54.png\" alt=\"sticker\"\u002F>\u003C\u002Fp>\n",[20,24,27],{"size":21,"text":22,"url":23},"big","外观","%E5%A4%96%E8%A7%82",{"size":21,"text":25,"url":26},"制作","%E5%88%B6%E4%BD%9C",{"size":21,"text":28,"url":29},"代码","%E4%BB%A3%E7%A0%81","前段时间买了台小主机，替换树莓派做云盘\u002F下载器，也安装了jellyfin和aria2等其他自建服务，性能比树莓派强很多。于是树莓派又吃灰了，这次用它做个监控摄像头。 外观 制作 制作过程我没有记录下来，需要的物品如下： 树莓派 x1 树莓派专用摄像头 x1 MG995舵机(180度) x2 雪糕棍若…",[32,42,53,62,69],{"item":33,"count":41},{"title":34,"len":35,"tags":36,"id":38,"time":39,"modifyTime":40,"encrypt":14,"showComments":15,"customSlug":16},"树莓派桌面小屏",1678,[8,37,9],"vue",8442,1645457432535,1685333435027,2,{"item":43,"count":52},{"title":44,"len":45,"tags":46,"id":48,"time":49,"modifyTime":50,"showComments":15,"encrypt":14,"customSlug":51},"关于本站的重构 (又双叒叕重构了)",1503,[10,37,47],"杂谈",8500,1741528184917,1759579038298,"about-renovate",1,{"item":54,"count":52},{"title":55,"len":56,"tags":57,"id":59,"time":60,"modifyTime":61,"showComments":15,"encrypt":14,"customSlug":16},"用React实现跟随鼠标的3D透视效果",863,[10,58],"react",6856,1731479185023,1734350101562,{"item":63,"count":52},{"title":64,"len":65,"tags":66,"id":67,"time":68,"modifyTime":68,"encrypt":14,"showComments":15,"customSlug":16},"vue3用于管理axios异步请求一致性的功能函数",3227,[37,10],9820,1725014139074,{"item":70,"count":52},{"title":71,"len":72,"tags":73,"id":74,"time":75,"modifyTime":75,"encrypt":14,"showComments":15,"customSlug":16},"Nuxt3创建SSE server以及client的简明教程",1905,[10,37],5449,1720261091923,1793351207983]