<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Posts on I&#39;m 5km</title>
		<link>https://blog.5km.studio/post/</link>
		<description>Recent content in Posts on I&#39;m 5km</description>
		<generator>Hugo -- gohugo.io</generator>
		<language>en</language>
		<copyright>This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.</copyright>
		<lastBuildDate>Fri, 08 Sep 2023 16:30:23 +0800</lastBuildDate>
		<atom:link href="https://blog.5km.studio/post/index.xml" rel="self" type="application/rss+xml" />
		
		<item>
			<title>Poetry 安装 SSL 验证失败问题探究</title>
			<link>https://blog.5km.studio/2023/09/08/poetry-ssl-verified-failed-error/</link>
			<pubDate>Fri, 08 Sep 2023 16:30:23 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2023/09/08/poetry-ssl-verified-failed-error/</guid>
			<description>今天安装 python 的包管理工具 poetry 遇到了如下的 SSL 验证失败问题，研究了一下原因并解决了安装问题，记录并分享之。 # 安装 poetry curl -sSL https://install.python-poetry.org | python3 - # 遇到错误如下，这里省</description>
			<content type="html"><![CDATA[<p>今天安装 python 的包管理工具 <a href="https://python-poetry.org/docs/#installing-with-the-official-installer">poetry</a> 遇到了如下的 SSL 验证失败问题，研究了一下原因并解决了安装问题，记录并分享之。</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 安装 poetry</span>
curl -sSL https://install.python-poetry.org <span class="p">|</span> python3 -

<span class="c1"># 遇到错误如下，这里省略绝大部分内容，突出错误</span>
...
ssl.SSLCertVerificationError: <span class="o">[</span>SSL: CERTIFICATE_VERIFY_FAILED<span class="o">]</span> certificate verify failed: unable to get <span class="nb">local</span> issuer certificate <span class="o">(</span>_ssl.c:992<span class="o">)</span>
...
</code></pre></div><h2 id="解决-poetry-安装问题">解决 poetry 安装问题</h2>
<p>之前在执行某些脚本时也遇到过这个原因，比较暴力的解决方式就是不验证，因为我此时的目标是安装 <code>poetry</code>，我简单看了下安装脚本内容，定位到是下载 <a href="https://pypi.org/pypi/poetry/json">https://pypi.org/pypi/poetry/json</a> 这个 json 文件时使用了 urllib 库出现的 SSL 问题，比较暴力的方式就是将 ssl 全局关闭验证，改造命令如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 1. 下载脚本</span>
<span class="c1"># 2. 脚本内容最前面加上设置验证的 python 代码</span>
<span class="c1"># 3. 执行脚本安装 poetry</span>
curl -sSL https://install.python-poetry.org <span class="p">|</span> sed <span class="s1">&#39;1s;^;import ssl\nssl._create_default_https_context = ssl._create_unverified_context\n;&#39;</span> <span class="p">|</span> python3 -
</code></pre></div><blockquote>
<p>🔖 <strong>思考</strong></p>
<p>暴力的解决思路就是关闭验证，这里是 python，其它语言或者工具相应的思路关闭 SSL 验证即可！</p>
</blockquote>
<h2 id="分析原因">分析原因</h2>
<p>看脚本内容是试图创建一个安全的 HTTPS 连接，但在验证服务器 SSL 证书时失败了。这可能是由多种原因导致的，包括:</p>
<ol>
<li>服务器的 SSL 证书过期：如果服务器的 SSL 证书已过期，就无法完成验证，会导致这个错误。</li>
<li>证书颁发机构不被信任：如果 SSL 证书是由一个不在你的操作系统信任的证书颁发机构发出的，那么在尝试创建安全连接时就会出现这个错误。</li>
<li>连接的服务器不是证书中列出的服务器：如果你试图连接到的服务器与 SSL 证书中列出的服务器不匹配（例如，证书是为 &ldquo;<a href="http://www.example.com">www.example.com</a>&rdquo; 发出的，但你正在尝试连接到 &ldquo;<a href="http://www.somethingelse.com">www.somethingelse.com</a>&rdquo;），那么就会出现这个错误。</li>
<li>系统时间或日期错误：如果你的系统日期或时间设置错误，可能会导致 SSL 证书验证失败。</li>
<li>中间人攻击：虽然不太可能，但理论上这个错误也可能由网络攻击或劫持引发（如中间人攻击）。</li>
</ol>
<p>一项项分析。</p>
<h3 id="证书过期">证书过期</h3>
<p>理论上讲，<u><a href="https://pypi.org">https://pypi.org</a></u>的证书不太可能过期，但还是看一下吧，🤣</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="nb">echo</span> <span class="p">|</span> openssl s_client -servername pypi.org -connect pypi.org:443 2&gt;/dev/null <span class="p">|</span> openssl x509 -noout -dates
</code></pre></div><p>结果是</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="nv">notBefore</span><span class="o">=</span>Apr <span class="m">29</span> 19:53:38 <span class="m">2023</span> GMT
<span class="nv">notAfter</span><span class="o">=</span>May <span class="m">30</span> 19:53:37 <span class="m">2024</span> GMT
</code></pre></div><p>看时间是今年 4 月～明年 5 月，所以不是这个原因。</p>
<h3 id="证书机构不被信任">证书机构不被信任</h3>
<p>先查看证书机构：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="nb">echo</span> <span class="p">|</span> openssl s_client -servername pypi.org -connect pypi.org:443 2&gt;/dev/null <span class="p">|</span> openssl x509 -noout -issuer
</code></pre></div><p>得到结果是：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="nv">issuer</span><span class="o">=</span><span class="nv">C</span> <span class="o">=</span> BE, <span class="nv">O</span> <span class="o">=</span> GlobalSign nv-sa, <span class="nv">CN</span> <span class="o">=</span> GlobalSign Atlas R3 DV TLS CA <span class="m">2023</span> Q2
</code></pre></div><p>我使用的是 macOS，所以去<u>钥匙串访问</u> app 中查找，发现找不到同名的证书：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/ssl-verified-failed-zipic-20230908-170143.png" alt=""></p>
<h3 id="服务器不一致">服务器不一致</h3>
<p>使用下面命令查看证书和服务器是否一致：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="nb">echo</span> <span class="p">|</span> openssl s_client -servername pypi.org -connect pypi.org:443 2&gt;/dev/null <span class="p">|</span> openssl x509 -noout -subject
</code></pre></div><p>得到结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="nv">subject</span><span class="o">=</span><span class="nv">CN</span> <span class="o">=</span> pypi.org
</code></pre></div><p>这个也没问题。</p>
<blockquote>
<p>🐛 补充</p>
<p>我一开始以为是代理的问题，但是开关代理结果都一样不是代理导致的 SSL 验证异常。</p>
</blockquote>
<h2 id="写在最后">写在最后</h2>
<p>SSL 验证失败，非必须验证的情况下，可以使用本文提到的关闭验证的方式（比如能保证链接是公开公正的官方网站），毕竟我们的目标是其它的而不是从根本上解决验证问题（目标感很重要），希望本文对您有所帮助！</p>
]]></content>
		</item>
		
		<item>
			<title>Pake 打包 Claude2</title>
			<link>https://blog.5km.studio/2023/09/02/pake-claude/</link>
			<pubDate>Sat, 02 Sep 2023 23:40:54 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2023/09/02/pake-claude/</guid>
			<description>尽管最近 ChatGPT 的关注度有所降低，但我们无法否认，以 ChatGPT 为代表的大型语言模型确实极大提高了我们个人创作的效率。当前我正在使用的稳定产品主要包括 ChatGPT 和 C</description>
			<content type="html"><![CDATA[<p>尽管最近 ChatGPT 的关注度有所降低，但我们无法否认，以 ChatGPT 为代表的大型语言模型确实极大提高了我们个人创作的效率。当前我正在使用的稳定产品主要包括 <a href="https://chat.openai.com/chat">ChatGPT</a> 和 <a href="https://claude.ai/chat/">Claude2</a>。关于这两款产品的具体使用方法，并不是本文想要深入讨论的主题。本文的重点是，我是如何利用 <a href="https://github.com/tw93/Pake">🤱🏻Pake</a>（这是一个出色的工具 🔥）将 Claude 打包为本地应用，以便于个人频繁使用。</p>
<h2 id="pake-介绍">Pake 介绍</h2>
<p><a href="https://github.com/tw93/Pake">🤱🏻Pake</a> 是一款利用 Rust 轻松构建轻量级多端桌面应用的工具。使用 Pake CLI 工具可以快速构建指定网址 URL 的工具，Github 介绍页面中也展示了多个用 Pake 打包的示例应用，可以说作者相当贴心了！比如：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/pake-example-20230902-235256.jpg" alt="Pake Packages Examples"></p>
<blockquote>
<h1 id="当然不止如图-4-个自己去-httpsgithubcomtw93pake-查看吧">当然不止如图 4 个，自己去 <a href="https://github.com/tw93/Pake">https://github.com/tw93/Pake</a> 查看吧！</h1>
</blockquote>
<h2 id="pake-安装">Pake 安装</h2>
<blockquote>
<h5 id="我这里以-macos-为例其它系统自己尝试即可安装过程也不难参考documenthttpsgithubcomtw93pakeblobmasterbinreadmemd">我这里以 macOS 为例，其它系统自己尝试即可，安装过程也不难，参考<a href="https://github.com/tw93/Pake/blob/master/bin/README.md">Document</a>！</h5>
</blockquote>
<p>首先保证你已经安装了 node 和 npm 包管理工具，这里推荐使用 <a href="https://github.com/nvm-sh/nvm">nvm</a> 安装 node。node 安装成功后通常 npm 就可以使用，执行以下命令安装 <code>pake-cli</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">npm install -g pake-cli
</code></pre></div><p>之后启动新的 shell 就能使用 pake 了，可以通过 <code>pake --help</code> 查看帮助信息。</p>
<h2 id="icon-准备">Icon 准备</h2>
<p>我按照 macOS 的 App Icon 的设计规范设计了 Claude 图标，如下:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/claude-20230903-000131.png" alt="claude logo" width="256px" /></p>
<p>当然 macOS 下需要 icns 格式的图标，推荐使用 <a href="https://apps.apple.com/cn/app/image2icon-%E5%88%B6%E4%BD%9C%E8%87%AA%E5%B7%B1%E7%9A%84%E5%9B%BE%E6%A0%87/id992115977?mt=12">Image2Icon</a> 将 PNG 转换导出 icns，将上面 👆 的 png 图片拖入工具，点击导出选择 icns 即可，我导出的 icns 图标如下：</p>
<p><a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/claude.icns-20230903-000546.zip">claude.icns.zip</a></p>
<blockquote>
<h3 id="如果是在-macos-下打包的话使用我制作的-claude-icns-图标即可">如果是在 macOS 下打包的话，使用我制作的 claude icns 图标即可！</h3>
</blockquote>
<h2 id="使用-pake-打包">使用 Pake 打包</h2>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">pake https://claude.ai/chat/ --name claude --transparent --icon claude.icns
</code></pre></div><p>我这里没有指定自定义的窗口宽高，只是指定了名称、启用沉浸式标题和指定图标，当然也可以指定其他的参数，参考<a href="https://github.com/tw93/Pake/blob/master/bin/README.md#cli-usage">Pake Document - CLI Usage</a></p>
<blockquote>
<h2 id="打包过程需要下载-rust-的相关包网络需要魔法-">打包过程需要下载 Rust 的相关包，网络需要魔法 🪄</h2>
</blockquote>
<p>打包后的应用如下：</p>
<p><a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/claude.app-20230903-001435.zip">claude.app.zip</a></p>
<p>应用运行后如下图所示：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/claudeApp-20230903-003839.png" alt="claude app"></p>
<h2 id="总结">总结</h2>
<p><a href="https://github.com/tw93/Pake">🤱🏻Pake</a> 为各类 web 应用的打包提供了极大便利，值得使用和推荐 🚀！如果有什么问题欢迎留言讨论。</p>
]]></content>
		</item>
		
		<item>
			<title>Privacy Policy For TimeGo App</title>
			<link>https://blog.5km.studio/2023/04/29/timego-privacy-policy/</link>
			<pubDate>Sat, 29 Apr 2023 18:42:30 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2023/04/29/timego-privacy-policy/</guid>
			<description>This Privacy Policy is meant to help you understand how we handle information about you when you use our app (TimeGo). We respect your privacy rights and are committed to protecting your privacy. Please read the following carefully to understand our privacy policy. 1. No Data Collection TimeGo does not collect any personal information, device information, or other identifiable information about you. The app operates independently on your device and</description>
			<content type="html"><![CDATA[<p>This Privacy Policy is meant to help you understand how we handle information about you when you use our app (<strong>TimeGo</strong>).</p>
<p>We respect your privacy rights and are committed to protecting your privacy. Please read the following carefully to understand our privacy policy.</p>
<h2 id="1-no-data-collection">1. No Data Collection</h2>
<p><strong>TimeGo</strong> does not collect any personal information, device information, or other identifiable information about you. The app operates independently on your device and does not send your data to us or any third parties.</p>
<h2 id="2-user-generated-content-and-data">2. User-Generated Content and Data</h2>
<p>Data and content generated by you while using <strong>TimeGo</strong> are stored only on your device. We do not access, collect, store, or otherwise process your data. Any app-related settings and data remain within your device unless you voluntarily sync or share with other services.</p>
<h2 id="3-sharing-with-third-parties">3. Sharing with Third Parties</h2>
<p>As we do not collect any of your data, we do not share your data with any third parties.</p>
<h2 id="4-legal-requirements">4. Legal Requirements</h2>
<p>We comply with applicable laws and regulations. If a legal requirement requires us to disclose your information, we will do so in accordance with the legal requirement.</p>
<h2 id="5-contact-us">5. Contact Us</h2>
<p>If you have any questions, comments, or suggestions about this Privacy Policy, please contact us through the following means:</p>
<p><strong>Email:</strong> <code>5km@smslit.cn</code> or <code>okooo5km@gmail.com</code></p>
<h2 id="6-changes-to-the-privacy-policy">6. Changes to the Privacy Policy</h2>
<p>We may update this Privacy Policy from time to time to reflect your feedback and changes to our services. When we change this policy, we will update the policy effective date on this page. If the changes have a significant impact on your rights, we will notify you in a more prominent way (e.g., through a prominent notice or email notification). Therefore, please check this policy regularly to learn how we handle your information.</p>
<h1 id="隐私政策中文版">隐私政策（中文版）</h1>
<p>本隐私政策旨在帮助您了解我们如何处理与您相关的信息，当您使用我们的应用 <strong>时光(TimeGo)</strong> 时。</p>
<p>我们充分尊重您的隐私权益，并对您的隐私承担义务。请仔细阅读以下内容以了解我们的隐私政策。</p>
<h2 id="1-未收集数据">1. 未收集数据</h2>
<p><strong>时光(TimeGo)</strong> 并未收集您的任何个人信息、设备信息或其他可识别您身份的信息。应用在您的设备上独立运行，不会向我们或任何第三方发送您的数据。</p>
<h2 id="2-用户生成的内容和数据">2. 用户生成的内容和数据</h2>
<p>您在使用 <strong>时光(TimeGo)</strong> 时生成的数据和内容仅存储在您的设备上。我们不会访问、收集、存储或以其他方式处理您的数据。任何与应用相关的设置和数据都将保持在您的设备内，除非您主动与其他服务同步或共享。</p>
<h2 id="3-与第三方分享">3. 与第三方分享</h2>
<p>由于我们不收集您的任何数据，我们也不会与任何第三方共享您的数据。</p>
<h2 id="4-法定规定">4. 法定规定</h2>
<p>我们会遵守适用的法律、法规，如果法定规定需要我们披露您的信息，我们将按照法定要求进行披露。</p>
<h2 id="5-联系我们">5. 联系我们</h2>
<p>如果您对本隐私政策有任何疑问、意见或建议，请通过以下方式联系我们：</p>
<p><strong>电子邮箱：</strong><code>5km@smslit.cn</code> 或 <code>okooo5km@gmail.com</code></p>
<h2 id="6-隐私政策修改">6. 隐私政策修改</h2>
<p>我们可能会不时更新此隐私政策以反映您的反馈以及我们的服务的变化。在我们更改此政策时，我们会更新此页面上的政策生效日期。如果更改对您的权利产生重大影响，我们将用更明显的方式（例如通过突出提示或发送电子邮件通知）告知您。因此，请定期查看本政策以了解我们如何处理您的信息。</p>
]]></content>
		</item>
		
		<item>
			<title>慢下来思考一下</title>
			<link>https://blog.5km.studio/2023/04/14/slowdown-contemplation/</link>
			<pubDate>Fri, 14 Apr 2023 09:13:16 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2023/04/14/slowdown-contemplation/</guid>
			<description>互联网各路信息泛滥，尤其是 AI ，扑面而来，眼花缭乱之际，发现焦虑和浮躁早已缠身，应该慢下脚步好好思考一下了：我们到底想要什么、能做什么、该怎么</description>
			<content type="html"><![CDATA[<p>互联网各路信息泛滥，尤其是 AI ，扑面而来，眼花缭乱之际，发现焦虑和浮躁早已缠身，应该慢下脚步好好思考一下了：我们到底想要什么、能做什么、该怎么做！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/bg-20230414-100516.png" alt=""></p>
]]></content>
		</item>
		
		<item>
			<title>chatGPT 信息大爆炸后 LLM 相关信息整理</title>
			<link>https://blog.5km.studio/2023/03/17/chatgpt-info/</link>
			<pubDate>Fri, 17 Mar 2023 16:47:53 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2023/03/17/chatgpt-info/</guid>
			<description>整理这篇文章源于火爆的 chatGPT，随着对 chatGPT 的了解又让我对更早火出圈的文本生成图片产生了浓厚兴趣，比如百度文心一格、midjourney，</description>
			<content type="html"><![CDATA[<p>整理这篇文章源于火爆的 chatGPT，随着对 chatGPT 的了解又让我对更早火出圈的文本生成图片产生了浓厚兴趣，比如百度文心一格、midjourney，微软也在 New Bing 发布一个月左右之后的今天(2023年03月21日) 发布了 <a href="https://www.bing.com/create">Image Creator</a>预览版，微软这是杀疯了！不过文章内容还是侧重 chatGPT 相关信息，会稍微夹杂着一些其他 AIGC 的内容。</p>
<p>ChatGPT是一个人工智能聊天机器人（非常强大的工具），由OpenAI开发并于2022年11月发布。它基于OpenAI的GPT-3系列大型语言模型，并使用监督学习和强化学习技术进行了微调。它可以与用户进行自然的文本对话，回答后续问题，承认错误，挑战错误的前提，拒绝不恰当的请求，可回答的问题几乎涉及全知识领域。可以通过合理的 Prompt 限定他的角色从而将它转变为特定领域的工具。</p>
<h2 id="chatgpt是什么">chatGPT是什么</h2>
<p>可以观看下面的视频详细了解一下这个比较颠覆性的工具：</p>
<p><a href="https://www.bilibili.com/video/BV1MY4y1R7EN?vd_source=fbe8ecd1547e7e909fd660f4a2b27cef">【渐构】万字科普GPT-4为什么会颠覆人类社会</a></p>
<blockquote>
<p>使用<a href="https://b.jimmylv.cn/?ref=opengpt">哔哔终结者 BibiGPT</a> (这个工具就是基于 chatGPT)生成上面视频的大纲：</p>
<p>本视频科普了ChatGPT-4技术的原理、制造过程、能力及未来影响，并探讨了如何维持未来竞争力。</p>
<ul>
<li>🤖 ChatGPT-4是一种生成语言模型，通过单字接龙回答问题。</li>
<li>🧠 它具有自回归生成的能力，只需提供上文就能生成下一个字。</li>
<li>📚 训练ChatGPT-4的方法是让它遵照所给的学习材料，调整其通用模型。</li>
<li>💡 ChatGPT-4的目的不是记忆，而是学习提问和回答的通用规律（泛化）。</li>
<li>🚀 ChatGPT-4的生成模型可以创造不存在的文本，在自然语言处理领域有巨大潜力。</li>
</ul>
</blockquote>
<h2 id="使用-chatgpt">使用 chatGPT</h2>
<h3 id="方式一使用官方原版">方式一：使用官方原版</h3>
<p>OpenAI 并没有开放给中国国内账户，要使用官方版本的 chatGPT 需要魔法，注册账号也比较繁琐，目前 OpenAI 只接受 Gmail 用户的注册，而且在注册的最后一步需要国外的电话进行验证码验证，具体步骤参考下面两个链接（需要<strong>科学的上网</strong>🤦）</p>
<ul>
<li><a href="https://cloud.tencent.com/developer/article/2239344?areaSource=&amp;traceId=">2023如何成功解决国内邮箱注册ChatGPT显示注册不可用请重试的问题</a></li>
<li><a href="https://share.weiyun.com/5VAf4rF0">https://share.weiyun.com/5VAf4rF0</a></li>
</ul>
<blockquote>
<p>使用原版账号的好处就是:</p>
<ol>
<li>注册账号后可以使用 API，而且默认会有 5 美元的免费额度，目前基于 chatGPT API 的应用如雨后春笋般疯狂增多，后面后列举一些。</li>
<li>可以申请试用 GPT-4 的 API 试用，chatGPT 的 Plus 用户可以直接使用 GPT4 模型版本的，但一定问答次数限制。</li>
</ol>
</blockquote>
<h3 id="方式二国内可使用的镜像">方式二：国内可使用的镜像</h3>
<ol>
<li>「⭐⭐⭐」<a href="https://chat.theb.ai/">BAI Chatopen in new window</a>：站长投入很多资金来运营，超级稳</li>
<li>「⭐⭐」<a href="https://chat.yqcloud.top/">yqcloudopen in new window</a>：速度还可以，稳定</li>
<li>「⭐⭐」<a href="https://www.tdchat.com/">tdchatopen in new window</a>：反应快，但输出长度受限</li>
<li>「⭐⭐」<a href="https://chat.forchange.cn/">forchangeopen in new window</a>：官方皮肤，目前无限制。</li>
<li>「⭐⭐」<a href="https://chat.geekr.cool/">GeekChatopen in new window</a>：支持语音和文字进行交互</li>
<li>「⭐⭐」<a href="https://chat.jingran.vip/">jingranopen in new window</a>：稍微有些慢</li>
<li>「⭐⭐」<a href="https://chat.geekr.cool/">GeekChatopen in new window</a>：支持语音和文字进行交互</li>
<li>「⭐⭐」<a href="https://trychatgp.com/">trychatgpopen in new window</a>：目前无限制，速度还可以</li>
<li>「⭐⭐」<a href="https://fastgpt.app/">fastgptopen in new window</a>：用的是 ChatGPT 网站的皮肤，反应很迅速【每天5次】</li>
<li>「⭐⭐」<a href="https://chat.okis.dev/">Chat Chat</a>：每天有一定额度。</li>
</ol>
<h3 id="方式三应用">方式三：应用</h3>
<h4 id="ai0x0httpsai0x0com"><a href="https://ai0x0.com/">AI0x0</a></h4>
<p><a href="https://github.com/mushan0x0/AI0x0.com">https://github.com/mushan0x0/AI0x0.com</a></p>
<p>超强 AI 聚合工具。</p>
<h4 id="使用微软的-new-bing">使用微软的 New Bing</h4>
<p>这个工具也只对美国开放，而且需要申请试用，目前有最新的方式如下视频：</p>
<p><a href="https://www.bilibili.com/video/BV1mg4y147k1?vd_source=fbe8ecd1547e7e909fd660f4a2b27cef">New Bing无需魔法最新办法</a></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/image-20230317152835084-20230317-152836.png" alt="image-20230317152835084" style="zoom:25%;" /></p>
<h4 id="视频总结-哔哔终结者-bibigpthttpsbjimmylvcnrefopengpt">视频总结-<a href="https://b.jimmylv.cn/?ref=opengpt">哔哔终结者 BibiGPT</a></h4>
<p><a href="https://b.jimmylv.cn/?ref=opengpt">https://b.jimmylv.cn/?ref=opengpt</a></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/image-20230317152951758-20230317-152952.png" alt="image-20230317152951758" style="zoom:25%;" /></p>
<h4 id="opengpt">OpenGPT</h4>
<p><a href="https://open-gpt.app/">https://open-gpt.app/</a></p>
<p>可以为 chatGPT 创建不同的应用，其实就是通过一定指令限定 chatGPT 的角色，每天有一定的限制次数。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/image-20230317153104415-20230317-153105.png" alt="image-20230317153104415" style="zoom: 25%;" /></p>
<h4 id="notion-ai">Notion AI</h4>
<p><a href="https://www.notion.so/product/ai">https://www.notion.so/product/ai</a></p>
<p>[点击我看介绍视频](<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/Notion">https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/Notion</a> AI -  - 2023-03-17 15-33-01-20230317-153337.mp4)</p>
<h4 id="ai-translator">AI Translator</h4>
<p><a href="https://github.com/yetone/openai-translator">https://github.com/yetone/openai-translator</a></p>
<blockquote>
<p>需要有自己的账号并且生成 API-KEY，软件会调用 OpenAI 的 API（收费），需要<strong>魔法上网</strong></p>
</blockquote>
<h4 id="poe">Poe</h4>
<p><a href="https://poe.com/">https://poe.com/</a></p>
<blockquote>
<p>需要<strong>魔法上网</strong></p>
</blockquote>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/image-20230317154534907-20230317-154535.png" alt="image-20230317154534907" style="zoom: 33%;" /></p>
<h4 id="opencat">OpenCat</h4>
<p>支持 Apple 全平台：iOS、macOS、iPadOS，自行在商店搜索安装。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/image-20230317155416180-20230317-155416.png" alt="image-20230317155416180" style="zoom:33%;" /></p>
<blockquote>
<p>需要有自己的账号并且生成 API-KEY，软件会调用 OpenAI 的 API（收费），需要<strong>魔法上网</strong></p>
</blockquote>
<h4 id="chatchat">chatchat</h4>
<p><a href="https://chat.okis.dev/?mode=chat">https://chat.okis.dev/?mode=chat</a></p>
<p>使用 AI 解锁下一代对话体验，支持上传 PDF 解读分析问答、支持代码提问转换，也支持对话式聊天。每日都会有免 API 使用的额度，可以直接使用。后续会一直维护和开发新功能。而且永远不会收费。</p>
<h4 id="warp">warp</h4>
<p><a href="https://www.warp.dev/">https://www.warp.dev/</a></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/SCR-20230320-mney-20230320-140404.png" alt=""></p>
<p>号称 21 世纪的终端工具，Warp是一款由Cloudflare开发的终端工具，它可以改善你的网络连接速度并保护你的隐私。这一工具通过在本地端和云端建立加密通信隧道来提高联网速度和加密用户的连接。它的主要特点有：</p>
<ul>
<li>高速： Warp可以通过 Cloudflare 的网络优化算法消除网络瓶颈，从而加速网络连接速度。此外，他们还提供了全球服务器设置，以提供更快的分布式网络。</li>
<li>加密隐私： Warp使用了现代密码学技术来加密用户的网络连接，从而在可能的情况下防止拦截和监视。此外，它不会存储数据，这也保障了用户的隐私。</li>
<li>易于使用：Warp的设置非常简单，只需下载安装程序并创建一个账户即可使用。它还提供了简单、安全的链接管理，使用户可以轻松地控制其网络特性。</li>
<li>强大的扩展性：通过 Cloudflare 的网络和代理技术，Warp可以随着您的需求和使用需求的增长而不断扩展。</li>
</ul>
<p>总之，Warp是一款提供快速、可靠、加密的网络专用工具，适用范围广泛。</p>
<h4 id="cursor">cursor</h4>
<p><a href="https://www.cursor.so/">https://www.cursor.so/</a></p>
<p>一款可以使用 <strong>GPT-4</strong> 编写代码的代码编辑器。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/SCR-20230320-tyob-20230320-231014.png" alt=""></p>
<h4 id="cubox">Cubox</h4>
<p><a href="https://cubox.pro/">https://cubox.pro/</a></p>
<p>在信息碎片中重获专注，一站式信息收集、阅读、管理和回顾，善用网络碎片构建个人知识库。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/SCR-20230321-izof-zipic-20230321-095010.png" alt=""></p>
<p>阅读<a href="https://help.cubox.pro/reader/assistant/">Cubox AI 阅读助手</a>了解更多内容。</p>
<h4 id="ai-color">AI Color</h4>
<p><a href="https://aicolors.co/">https://aicolors.co/</a></p>
<p>设计师会用到的一块 AI 色卡工具。</p>
<h4 id="深度导航">深度导航</h4>
<p><a href="https://www.deepdh.com/ai">https://www.deepdh.com/ai</a></p>
<p>有人整理的各类 AI 工具，供参考！</p>
<p><strong>类似的工具有很多，这里就不一一列举了。</strong></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/IMG_5428-20230322-123928.JPG" alt=""></p>
<blockquote>
<p>注：不知道上图是谁整理的，感谢他的分享。</p>
</blockquote>
<blockquote>
<p>要用好工具，Prompt 很关键，可以从<a href="https://prompts.fresns.cn/">ChatGPT 提示语</a> 找灵感！</p>
</blockquote>
<h3 id="方式四调用第三方-api">方式四：调用第三方 API</h3>
<h4 id="1-steamship">1. steamship</h4>
<p><a href="https://www.steamship.com/">https://www.steamship.com/</a></p>
<p>GPT-4免费用，无需官方账号，SteamShip 已开放 GPT-4 的模型接口，只需要注册SteamShip 账号，无需付费几行代码直接就能调用 GPT-4。</p>
<h2 id="最新动态">最新动态</h2>
<h3 id="github-copilot-x">GitHub Copilot X</h3>
<p>微软最近的动作除了 AI 就是 AI，这不 2023年03月21日晚，guthub 推出的 Copilot 智能代码助手直接融合 chatGPT 发布了个 <a href="https://github.com/features/preview/copilot-x">GitHub Copilot X</a>，可惜我没用过 Copilot，据说很厉害！</p>
<h3 id="nvidia-gtc-2023">Nvidia GTC 2023</h3>
<p>Nvidia GTC 2023，就在 2023年03月21日晚11点老黄正式开启 GTC 大会，在 keynote 中发布了 <a href="https://github.com/NVIDIA/NeMo">Nemo</a>、<a href="https://www.nvidia.cn/gpu-cloud/picasso/">Picasso</a>和<a href="https://www.nvidia.cn/gpu-cloud/bionemo/">Bionemo</a>，以及一众硬件，其中 DGX AI 超级计算机交付给了 OpenAI 可以为 chatGPT 进行超强加速。</p>
<ol>
<li>
<p>NeMo</p>
<p>NeMo是一个用于对话式人工智能的工具包，由NVIDIA开发1。它主要为从事自动语音识别（ASR）、文本到语音合成（TTS）、大型语言模型（LLMs）和自然语言处理（NLP）的研究人员而设计1。NeMo的主要目标是帮助研究人员重用之前的工作（代码和预训练模型），并快速构建新的应用。NeMo最新的版本是1.13.0，于2022年12月16日发布。NeMo还有一个文本处理模块，用于ASR和TTS的数据预处理和后处理。</p>
</li>
<li>
<p>Picasso</p>
<p>Picasso是NVIDIA在GTC 2023发布的一个新的基于云的平台，用于在创意应用中从文本生成 AI 支持的视觉内容，用于构建和部署生成AI工具。</p>
</li>
<li>
<p>Bionemo</p>
<p>BioNeMo 是一款基于 NVIDIA NeMo Megatron 构建的 AI 赋能药物研发云服务和框架，用于在超级计算规模下训练和部署大型生物分子 Transformer AI 模型。服务包括预训练 LLM、对蛋白质、DNA、RNA 和化学的通用文件格式的原生支持，还提供可供 SMILES（用于分子结构）和 FASTA（用于氨基酸和核苷酸序列）使用的数据加载器。BioNeMo 框架也可供下载，以便您可以在自己的基础架构上运行。</p>
</li>
</ol>
<h3 id="microsoft-image-creator">Microsoft Image Creator</h3>
<p><a href="https://www.bing.com/create">https://www.bing.com/create</a></p>
<p>微软杀疯了，2023年3月21日又发布了生成图片的预览版应用 Image Creator！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/SCR-20230321-uqdk-20230322-000014.jpeg" alt=""></p>
<h3 id="谷歌-bard">谷歌 Bard</h3>
<p><a href="https://bard.google.com/">https://bard.google.com/</a></p>
<p>2023年03月21日，今天 google Bard 开放申请。一起排个队吧！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/40791679445618_.pic-20230322-084331.png" alt=""></p>
<h3 id="microsoft-365-copilot">Microsoft 365 Copilot</h3>
<p><a href="https://www.bilibili.com/video/BV1kk4y1b7zn/?buvid=Z24BCD244A3FC02547D2ABF31EB0695C4882&amp;is_story_h5=false&amp;mid=YQOYYfzPEOfFBcdg1OPRkw%3D%3D&amp;p=1&amp;plat_id=116&amp;share_from=ugc&amp;share_medium=iphone&amp;share_plat=ios&amp;share_session_id=1D6B5CDC-5656-468E-9FB2-B04578D66D1C&amp;share_source=WEIXIN&amp;share_tag=s_i&amp;timestamp=1679024918&amp;unique_k=RclBbJq&amp;up_id=1628263351">点我看发布会</a></p>
<p>[点我看宣传片](【Microsoft 365 Copilot 简介——一种全新的工作方式】https://www.bilibili.com/video/BV1aM411W7Yb?vd_source=fbe8ecd1547e7e909fd660f4a2b27cef)</p>
<p>3月17日凌晨，微软发布集成 GPT4 的 Microsoft 365 Copilot，将无缝集成在用户每天使用的应用当中，帮助保持工作的流畅性，从繁琐事务作中解放出来，从而专注于手头工作。</p>
<ul>
<li><strong>Copilot in Word</strong>能够在人们工作时与他们一起撰写、编辑、总结和创作。</li>
<li><strong>Copilot in PowerPoint</strong>能够在创作过程中，通过自然语言命令将想法转化为设计好的演示文稿。</li>
<li><strong>Copilot in Excel</strong> 能够帮助用户释放洞察、识别趋势，或在短时间内创建专业型式的数据可视化。</li>
<li><strong>Copilot in Outlook</strong>能够帮助用户整合并管理收件箱，从而节约出更多时间用于实际沟通。</li>
<li><strong>Copilot in Teams</strong>能够直接从对话上下文中提供实时摘要和待办事项，提高会议效率。</li>
<li><strong>Copilot in Power Platform</strong>通过在Power Apps和Power Virtual Agents中引入两项新功能，能够让各种技能水平的开发人员利用低代码工具加速和简化开发。</li>
<li><strong>Business Chat</strong>汇集了来自文档、演示文稿、电子邮件、日历、笔记和联系人的数据，能够帮助用户总结聊天内容、撰写电子邮件、查找关键日期，甚至根据其他项目文件制定计划。</li>
</ul>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/image-20230317162455411-20230317-162456.png" alt="image-20230317162455411" style="zoom:25%;" /></p>
<h3 id="百度文心一言">百度文心一言</h3>
<p>3月16日下午两点发布，未公开试用，只是小范围试用。</p>
<p><a href="https://live.baidu.com/m/media/pclive/pchome/live.html?room_id=8117393980&amp;source=search">文心一言发布会</a></p>
<ul>
<li>文学创作</li>
<li>数理逻辑</li>
<li>中文理解</li>
<li>商业文案创作</li>
<li>多模态生成</li>
</ul>
<blockquote>
<p>看评测对比 chatGPT(GPT-3.5)/GPT4 ，也没有想象中拉胯，也有做的更好的点，但还是跟 GPT 有差距。</p>
</blockquote>
<p>文心一言已经开放接口试用申请：<a href="https://cloud.baidu.com/survey_summit/wenxin.html">文心一言云服务邀您测试</a></p>
<h3 id="gpt4-横空出世">GPT4 横空出世</h3>
<p>3月15日凌晨，OpenAI 发布 GPT4 模型，正式买入多模态阶段，可以理解图片，同时综合能力吊打 GPT3.5（chatGPT 使用的模型）</p>
<p><a href="https://www.bilibili.com/video/BV1454y1T7Fe?vd_source=fbe8ecd1547e7e909fd660f4a2b27cef">发布会视频</a></p>
<p><strong>GPT-4</strong> 简短版总结</p>
<ul>
<li>GPT-4是一个大型多模态模型（Large Multimodal Model），能够接受图像和文本输入，并输出文本。</li>
<li>实验表明，GPT-4 在各种专业和学术考试中表现出了与人类水平相当的性能（human-level performance）。例如，它通过了模拟律师考试，且分数在应试者的前 10% 左右；相比之下，GPT-3.5 的得分在倒数 10% 左右。</li>
<li>GPT-4的训练稳定性是史无前例的，这得益于对抗性测试计划和来自于ChatGPT的经验教训，对 GPT-4 进行迭代调整，从而在真实性、可控性等方面取得了有史以来最好的结果。</li>
<li>在过去的两年里，OpenAI重建了整个深度学习堆栈，并与Azure共同设计了一台超级计算机以便于应付他们的工作负载。 将继续专注于可靠的扩展，进一步完善方法，以帮助其实现更强大的提前预测性能和规划未来的能力，这对安全至关重要。</li>
<li><strong>OpenAI首先发布了GPT-4的文本输入功能，图像输入功能敬请期待</strong>。</li>
<li>OpenAI还开源了OpenAI Evals，这是他们的自动化评估AI模型性能的框架，任何人都可以提交他们模型的缺陷以帮助改进。</li>
<li>OpenAI 正在通过 ChatGPT 和 API（有候补名单）发布 GPT-4 的文本输入功能。图像输入功能方面，为了获得更广泛的可用性，OpenAI 正在与其他公司展开合作。</li>
<li>OpenAI 还在为机器学习模型设计的传统基准上评估了 GPT-4。GPT-4 大大优于现有的大型语言模型，以及大多数 SOTA 模型</li>
</ul>
<h3 id="商汤书生v25">商汤书生v2.5</h3>
<p>2023年3月14日，商汤科技发布多模态多任务通用大模型“书生（INTERN）2.5”。</p>
<p><a href="https://mp.weixin.qq.com/s/3pu6uwRmSx1sleUBmreTmQ">https://mp.weixin.qq.com/s/3pu6uwRmSx1sleUBmreTmQ</a></p>
<h2 id="大模型">大模型</h2>
<h3 id="1-百度文心">1. 百度文心</h3>
<p><a href="https://wenxin.baidu.com/">https://wenxin.baidu.com/</a></p>
<h3 id="2-商汤-opengvlab">2. 商汤 OPENGVLab</h3>
<p><a href="https://opengvlab.shlab.org.cn/home">https://opengvlab.shlab.org.cn/home</a></p>
<h3 id="3-清华-chatglm">3. 清华 ChatGLM</h3>
<p>单卡可部署。</p>
<p><a href="https://github.com/THUDM/ChatGLM-6B">https://github.com/THUDM/ChatGLM-6B</a></p>
<h3 id="4-openchatkit">4. OpenChatKit</h3>
<p><a href="https://github.com/togethercomputer/OpenChatKit">https://github.com/togethercomputer/OpenChatKit</a></p>
<h3 id="5-stanford-alpaca">5. Stanford Alpaca</h3>
<p><a href="https://github.com/tatsu-lab/stanford_alpaca">https://github.com/tatsu-lab/stanford_alpaca</a></p>
<h3 id="6-alpaca-lora-羊驼-lora">6. Alpaca-Lora (羊驼-Lora)</h3>
<p><a href="https://github.com/tloen/alpaca-lora">https://github.com/tloen/alpaca-lora</a></p>
<p>轻量级 ChatGPT 的开源实现, 它使用 Lora (Low-rank Adaptation) 技术在 Meta 的 LLaMA 7B 模型上微调，只需要训练很小一部分参数就可以获得媲美 Standford Alpaca 模型的效果。</p>
<h3 id="7-belle">7. Belle</h3>
<p><a href="https://github.com/LianjiaTech/BELLE">https://github.com/LianjiaTech/BELLE</a></p>
<p>弥补斯坦福70亿参数「羊驼」短板，精通中文的大模型。</p>
<h3 id="8-gpt4all">8. GPT4ALL</h3>
<p><a href="https://github.com/nomic-ai/gpt4all">GPT4ALL</a></p>
<p>Demo, data and code to train an assistant-style large language model with ~800k GPT-3.5-Turbo Generations based on LLaMa。</p>
<p>持续更新中&hellip;</p>
]]></content>
		</item>
		
		<item>
			<title>Apple silicon 平台安装 x86_64 架构应用包</title>
			<link>https://blog.5km.studio/2023/02/02/arm64-mac-homebrew-install-x86-package/</link>
			<pubDate>Thu, 02 Feb 2023 16:58:25 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2023/02/02/arm64-mac-homebrew-install-x86-package/</guid>
			<description>最近开发工作中需要在 Apple Silicon 平台中交叉编译 x86_64 的包，因为有 x86_64 的库依赖需要安装，就想到了使用 homebrew 进行安装，就是本文内容了。 主要会用到： Rosetta Homebrew 前言 你能找到</description>
			<content type="html"><![CDATA[<p>最近开发工作中需要在 Apple Silicon 平台中交叉编译 x86_64 的包，因为有 x86_64 的库依赖需要安装，就想到了使用 homebrew 进行安装，就是本文内容了。</p>
<p>主要会用到：</p>
<ul>
<li>Rosetta</li>
<li>Homebrew</li>
</ul>
<h2 id="前言">前言</h2>
<p>你能找到这篇文章，想必你是了解现在 macOS 平台下应用（比如 APP）的分类：</p>
<ul>
<li>Universal（通用）</li>
<li>Apple Silicon（Apple 芯片）</li>
<li>Intel</li>
</ul>
<p>以 App 为例，我们可以找到一个 app，看一下它的信息就能知道这个 app 是哪种分类：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/SCR-20230202-ny1-20230202-171548.png" alt=""></p>
<p>Safari 就是<strong>通用</strong>的，钉钉是<strong>Apple 芯片</strong>的，图压是<strong>Intel</strong>的，其实 Intel 就是 <strong>x86_64</strong> 架构的。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/KXJXhS-20230202-172020.png" alt=""></p>
<p>尽管我们在 Apple Silicon 的平台（比如我使用的是 M1 Pro 的 MBP）使用默认方式安装的 homebrew 是无法正常安装 x86_64 架构的应用，但我们又想通过 homebrew 的方式安装，下面就开始搞起！</p>
<h2 id="安装-rosetta-2httpssupportapplecomzh-cnht211861">安装 <a href="https://support.apple.com/zh-cn/HT211861">Rosetta 2</a></h2>
<p>一行命令搞定：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">/usr/sbin/softwareupdate --install-rosetta
</code></pre></div><h2 id="为-universal-或者-apple-silicon-应用安装-homebrew">为 universal 或者 Apple Silicon 应用安装 Homebrew</h2>
<p>按照官方的方式安装即可，比如：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">/bin/bash -c <span class="s2">&#34;</span><span class="k">$(</span>curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh<span class="k">)</span><span class="s2">&#34;</span>
</code></pre></div><p>通常使用这个会安装到 <code>/opt/homebrew/bin/brew</code>。</p>
<p>使用 homebrew：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">brew install xxx
brew list
</code></pre></div><h2 id="为-intel-应用安装-homebrew">为 Intel 应用安装 homebrew</h2>
<p>这是本文重点，通常在 Intel 的 macOS 下，brew 会安装到 <code>/usr/local/homebrew</code> 下，我们手动安装一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 下载 homebrew</span>
<span class="nb">cd</span> ~/Downloads
mkdir homebrew
curl -L https://github.com/Homebrew/brew/tarball/master <span class="p">|</span> tar xz --strip <span class="m">1</span> -C homebrew

<span class="c1"># 移动到目标位置</span>
sudo mv ~/Downloads/homebrew /usr/local/homebrew
</code></pre></div><p>修改 shell 配置文件，bash 就修改 <code>~/.bashrc</code>，zsh 的话就修改 <code>~/.zshrc</code>，在最后添加以下内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 修改 PATH 变量</span>
<span class="c1"># for intel x86_64 brew</span>
<span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="nv">$HOME</span>/bin:/usr/local/bin:<span class="nv">$PATH</span>
<span class="c1"># 建立别名</span>
<span class="nb">alias</span> <span class="nv">xbrew</span><span class="o">=</span><span class="s1">&#39;arch -x86_64 /usr/local/homebrew/bin/brew&#39;</span>
</code></pre></div><p>重新开启新的终端，就可以使用 xbrew 安装 x86_64 的软件或库了。</p>
<p>使用 homebrew：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">xbrew install xxx
xbrew list
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>通过 Nvchad 更方便地使用 Neovim</title>
			<link>https://blog.5km.studio/2022/05/02/neovim-config/</link>
			<pubDate>Mon, 02 May 2022 21:00:50 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2022/05/02/neovim-config/</guid>
			<description>前段时间有朋友推荐了一个 neovim 的配置集工具 Nvchad，考虑到两点个人原因（一是最近写代码比较少，用进废退；另一个是年纪大了记忆力下降了），有必</description>
			<content type="html"><![CDATA[<p>前段时间有朋友推荐了一个 neovim 的配置集工具 <a href="https://nvchad.github.io">Nvchad</a>，考虑到两点个人原因（一是最近写代码比较少，用进废退；另一个是年纪大了记忆力下降了），有必要记录一下配置过程。</p>
<h2 id="环境">环境</h2>
<ul>
<li>硬件：14寸 M1 Pro 版 Macbook Pro 2021 款；</li>
<li>系统：macOS 12.3.1</li>
<li>终端工具：iterm2</li>
<li>包管理工具：<a href="https://brew.sh">brew</a></li>
</ul>
<h2 id="准备工具">准备工具</h2>
<ol>
<li>
<p>使用 brew 安装 neovim</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">brew install neovim
</code></pre></div></li>
<li>
<p>使用 brew 安装 <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a></p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">brew install ripgrep
</code></pre></div></li>
</ol>
<h2 id="安装-nvchar">安装 Nvchar</h2>
<p>执行以下命令安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">git clone https://github.com/NvChad/NvChad ~/.config/nvim --depth <span class="m">1</span>
nvim +<span class="s1">&#39;hi NormalFloat guibg=#1e222a&#39;</span> +PackerSync
</code></pre></div><h2 id="配置-nvchad">配置 Nvchad</h2>
<blockquote>
<p>后续配置文件操作均以 <code>~/.config/nvim</code>作为基础目录，即执行终端命令的 PWD 均为此目录。</p>
</blockquote>
<h3 id="创建自定义文件">创建自定义文件</h3>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="c1"># 创建自定义文件目录</span>
mkdir lua/custom
<span class="c1"># 复制示例初始化文件</span>
cp examples/init.lua lua/custom/init.lua
<span class="c1"># 复制示例配置文件</span>
cp examples/chadrc.lua lua/custom/chadrc.lua
</code></pre></div><h3 id="安装代码高亮支持">安装代码高亮支持</h3>
<p><strong>Nvchad</strong> 套件安装了 <a href="https://github.com/nvim-treesitter/nvim-treesitter">TreeSitter</a> 插件以支持更丰富的代码高亮特性，所以我们可以根据自己的需求安装所需语言的高亮支持，支持语言列表可以去插件网址查，打开 neovim，执行命令</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">:TSInstall css html http javascript vue vim json latex llvm make markdown python toml yaml c cpp swift lua dockerfile go gomod bash cmake 
</code></pre></div><h3 id="设置主题">设置主题</h3>
<p>最近用的比较多的颜色主题是 <strong>Gruvbox</strong>，<strong>Nvchad</strong>也支持，打开 neovim 使用快捷键调出颜色主题选择框，快捷键为<code>&lt;leader&gt;</code>+<code>th</code>，选择 gruvbox 敲回车键，按下<code>Esc</code>推出主题设置窗口，颜色主题立即生效。</p>
<blockquote>
<p><strong>Tips</strong>：这里需要注意的是，设置主题的前提是已创建自定义的文件！</p>
</blockquote>
<h3 id="配置快捷键">配置快捷键</h3>
<p>在 <code>lua/init.lua</code> 中配置快捷键，替换文件内容为如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-lua" data-lang="lua"><span class="c1">-- example file i.e lua/custom/init.lua</span>

<span class="c1">-- MAPPINGS</span>
<span class="kd">local</span> <span class="n">map</span> <span class="o">=</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;core.utils&#34;</span><span class="p">).</span><span class="n">map</span>

<span class="n">map</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;cc&#34;</span><span class="p">,</span> <span class="s2">&#34;:Telescope &lt;CR&gt;&#34;</span><span class="p">)</span>

<span class="n">map</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;q&#34;</span><span class="p">,</span> <span class="s2">&#34;:q &lt;CR&gt;&#34;</span><span class="p">)</span>
<span class="n">map</span><span class="p">(</span><span class="s2">&#34;i&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;q&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;ESC&gt; :q &lt;CR&gt;&#34;</span><span class="p">)</span>
<span class="n">map</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;w&#34;</span><span class="p">,</span> <span class="s2">&#34;:w &lt;CR&gt;&#34;</span><span class="p">)</span>
<span class="n">map</span><span class="p">(</span><span class="s2">&#34;i&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;w&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;ESC&gt; :w &lt;CR&gt;&#34;</span><span class="p">)</span>
<span class="n">map</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;qq&#34;</span><span class="p">,</span> <span class="s2">&#34;:x &lt;CR&gt;&#34;</span><span class="p">)</span>
<span class="n">map</span><span class="p">(</span><span class="s2">&#34;i&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;qq&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;ESC&gt; :x &lt;CR&gt;&#34;</span><span class="p">)</span>

<span class="c1">-- require(&#34;my autocmds file&#34;) or just declare them here</span>
</code></pre></div><p>这里添加了我常用的快捷键用于保存文件和退出文件。</p>
<h3 id="开启-dashboard">开启 Dashboard</h3>
<p>新增目录<code>lua/custom/plugins</code>，并添加文件<code>lua/custom/plugins/init.lua</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">mkdir lua/custom/plugins
touch lua/custom/plugins/init.lua
</code></pre></div><p>文件内容添加如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-lua" data-lang="lua"><span class="kr">return</span> <span class="p">{</span>
	<span class="p">[</span><span class="s2">&#34;goolord/alpha-nvim&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">disable</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p><code>lua/custom/chadrc.lua</code>内容添加如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-lua" data-lang="lua"><span class="kd">local</span> <span class="n">userPlugins</span> <span class="o">=</span> <span class="n">require</span> <span class="s2">&#34;custom.plugins&#34;</span>

<span class="n">M.plugins</span> <span class="o">=</span> <span class="p">{</span>
   <span class="n">user</span> <span class="o">=</span> <span class="n">userPlugins</span>
<span class="p">}</span>
</code></pre></div><p>配置开启 Dashboard，保存后，nvim中执行命令<code>:PackerSync</code>，会同步插件安装<code>goolord/alpha-nvim</code>，重新打开 nvim 就会显示 dashboard。</p>
<h3 id="配置-lsp">配置 LSP</h3>
<p>LSP（Language Server Protocl）用于提供代码编写的很多智能特性，需要配置一下。</p>
<h4 id="安装-williambomanhttpsgithubcomwilliambomannvim-lsp-installerhttpsgithubcomwilliambomannvim-lsp-installer">安装 <a href="https://github.com/williamboman">williamboman</a>/<strong><a href="https://github.com/williamboman/nvim-lsp-installer">nvim-lsp-installer</a></strong></h4>
<p>编辑文件 <code>lua/custom/plugins/init.lua</code> 添加内容后整体如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-lua" data-lang="lua"><span class="kr">return</span> <span class="p">{</span>
  <span class="p">[</span><span class="s2">&#34;goolord/alpha-nvim&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
     <span class="n">disable</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
  <span class="p">},</span>
  <span class="p">[</span><span class="s2">&#34;williamboman/nvim-lsp-installer&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>nvim 中执行命令 <code>:PackerSync</code>，会自动安装插件 <a href="https://github.com/williamboman/nvim-lsp-installer">williamboman/nvim-lsp-installer</a>。</p>
<h4 id="添加配置文件-lspconfiglua">添加配置文件 lspconfig.lua</h4>
<p>在 <code>lua/custom/plugins</code> 中添加文件 <code>lua/custom/plugins/lspconfig.lua</code>，文件内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-lua" data-lang="lua"><span class="c1">-- custom.plugins.lspconfig </span>

<span class="kd">local</span> <span class="n">M</span> <span class="o">=</span> <span class="p">{}</span>

<span class="n">M.setup_lsp</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">attach</span><span class="p">,</span> <span class="n">capabilities</span><span class="p">)</span>
   <span class="kd">local</span> <span class="n">lsp_installer</span> <span class="o">=</span> <span class="n">require</span> <span class="s2">&#34;nvim-lsp-installer&#34;</span>

   <span class="n">lsp_installer.settings</span> <span class="p">{</span>
      <span class="n">ui</span> <span class="o">=</span> <span class="p">{</span>
         <span class="n">icons</span> <span class="o">=</span> <span class="p">{</span>
            <span class="n">server_installed</span> <span class="o">=</span> <span class="s2">&#34;﫟&#34;</span> <span class="p">,</span>
            <span class="n">server_pending</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
            <span class="n">server_uninstalled</span> <span class="o">=</span> <span class="s2">&#34;✗&#34;</span><span class="p">,</span>
         <span class="p">},</span>
      <span class="p">},</span>
   <span class="p">}</span>

   <span class="n">lsp_installer.on_server_ready</span><span class="p">(</span><span class="kr">function</span><span class="p">(</span><span class="n">server</span><span class="p">)</span>
      <span class="kd">local</span> <span class="n">opts</span> <span class="o">=</span> <span class="p">{</span>
         <span class="n">on_attach</span> <span class="o">=</span> <span class="n">attach</span><span class="p">,</span>
         <span class="n">capabilities</span> <span class="o">=</span> <span class="n">capabilities</span><span class="p">,</span>
         <span class="n">settings</span> <span class="o">=</span> <span class="p">{},</span>
      <span class="p">}</span>

      <span class="n">server</span><span class="p">:</span><span class="n">setup</span><span class="p">(</span><span class="n">opts</span><span class="p">)</span>
      <span class="n">vim.cmd</span> <span class="s">[[ do User LspAttachBuffers ]]</span>
   <span class="kr">end</span><span class="p">)</span>
<span class="kr">end</span>

<span class="kr">return</span> <span class="n">M</span>
</code></pre></div><p>在 <code>lua/custom/chadrc.lua</code> 中指定 lspconfig 的配置文件为上述文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-lua" data-lang="lua"><span class="n">M.plugins</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">user</span> <span class="o">=</span> <span class="n">userPlugins</span><span class="p">,</span>
  <span class="n">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">lspconfig</span> <span class="o">=</span> <span class="p">{</span>
      <span class="n">setup_lspconf</span> <span class="o">=</span> <span class="s2">&#34;custom.plugins.lspconfig&#34;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><h4 id="安装各种语言-ls">安装各种语言 LS</h4>
<p>打开 nvim，执行命令<code>:LspInstallInfo</code>，就会打开一个安装配置窗口，按<code>上下方向键</code>选择对应语言的 LS，选择后按 <code>i</code>键即可添加支持。</p>
<h3 id="常规配置">常规配置</h3>
<p>需要将 vim 中常规配置使用 lua 配置到 neovim。</p>
<p><code>lua/custom/chadrc.lua</code>中添加以下内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-lua" data-lang="lua"><span class="n">M.options</span> <span class="o">=</span> <span class="p">{</span>
   <span class="n">user</span> <span class="o">=</span> <span class="kr">function</span> <span class="p">()</span>
      <span class="n">vim.opt</span><span class="p">.</span><span class="n">number</span> <span class="o">=</span> <span class="kc">true</span>
      <span class="n">vim.opt</span><span class="p">.</span><span class="n">relativenumber</span> <span class="o">=</span> <span class="kc">true</span>
      <span class="n">vim.opt</span><span class="p">.</span><span class="n">encoding</span> <span class="o">=</span> <span class="s2">&#34;utf-8&#34;</span>
      <span class="n">vim.opt</span><span class="p">.</span><span class="n">fileencodings</span> <span class="o">=</span> <span class="s2">&#34;utf-8,ucs-bom,gb18030,gbk,gb2312,cp936&#34;</span>
      <span class="n">vim.opt</span><span class="p">.</span><span class="n">autoindent</span> <span class="o">=</span> <span class="kc">true</span>
      <span class="n">vim.opt</span><span class="p">.</span><span class="n">shiftwidth</span> <span class="o">=</span> <span class="mi">4</span>
      <span class="n">vim.opt</span><span class="p">.</span><span class="n">tabstop</span> <span class="o">=</span> <span class="mi">4</span>
      <span class="n">vim.opt</span><span class="p">.</span><span class="n">softtabstop</span> <span class="o">=</span> <span class="mi">4</span>
      <span class="n">vim.opt</span><span class="p">.</span><span class="n">cursorline</span> <span class="o">=</span> <span class="kc">true</span>
      <span class="n">vim.opt</span><span class="p">.</span><span class="n">mouse</span> <span class="o">=</span> <span class="s2">&#34;a&#34;</span>

      <span class="c1">-- File extension specific tabbing</span>
      <span class="n">vim.cmd</span> <span class="s">[[ autocmd Filetype sql,yaml,yml,toml,javascript,html,jinja.html,css,markdown,md,xml,vue,vue.html.javascript.css setlocal expandtab tabstop=2 shiftwidth=2 softtabstop=2 ]]</span>
      <span class="n">vim.cmd</span> <span class="s">[[ autocmd Filetype lua setlocal expandtab tabstop=3 shiftwidth=3 softtabstop=3 ]]</span>
   <span class="kr">end</span>
<span class="p">}</span>
</code></pre></div><p>保存重启 neovim 即刻生效。</p>
<h3 id="配置格式化和语法提示">配置格式化和语法提示</h3>
<h4 id="安装插件-null-ls">安装插件 null-ls</h4>
<p><code>lua/custom/plugins/init.lua</code>中添加<strong>null-ls</strong>相关内容，如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-lua" data-lang="lua"> <span class="p">[</span><span class="s2">&#34;jose-elias-alvarez/null-ls.nvim&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
      <span class="n">after</span> <span class="o">=</span> <span class="s2">&#34;nvim-lspconfig&#34;</span><span class="p">,</span>
      <span class="n">config</span> <span class="o">=</span> <span class="kr">function</span><span class="p">()</span>
         <span class="n">require</span><span class="p">(</span><span class="s2">&#34;custom.plugins.null-ls&#34;</span><span class="p">).</span><span class="n">setup</span><span class="p">()</span>
      <span class="kr">end</span><span class="p">,</span>
 <span class="p">}</span>

<span class="c1">-- load it after nvim-lspconfig cuz we lazy loaded lspconfig</span>
</code></pre></div><p>添加文件<code>lua/custom/plugins/null-ls.lua</code>，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-lua" data-lang="lua"><span class="kd">local</span> <span class="n">null_ls</span> <span class="o">=</span> <span class="n">require</span> <span class="s2">&#34;null-ls&#34;</span>
<span class="kd">local</span> <span class="n">b</span> <span class="o">=</span> <span class="n">null_ls.builtins</span>

<span class="kd">local</span> <span class="n">sources</span> <span class="o">=</span> <span class="p">{</span>

   <span class="n">b.formatting</span><span class="p">.</span><span class="n">prettierd.with</span> <span class="p">{</span> <span class="n">filetypes</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;html&#34;</span><span class="p">,</span> <span class="s2">&#34;markdown&#34;</span><span class="p">,</span> <span class="s2">&#34;css&#34;</span> <span class="p">}</span> <span class="p">},</span>
   <span class="n">b.formatting</span><span class="p">.</span><span class="n">deno_fmt</span><span class="p">,</span>

   <span class="c1">-- Lua</span>
   <span class="n">b.formatting</span><span class="p">.</span><span class="n">stylua</span><span class="p">,</span>
   <span class="n">b.diagnostics</span><span class="p">.</span><span class="n">luacheck.with</span> <span class="p">{</span> <span class="n">extra_args</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;--global vim&#34;</span> <span class="p">}</span> <span class="p">},</span>

   <span class="c1">-- Shell</span>
   <span class="n">b.formatting</span><span class="p">.</span><span class="n">shfmt</span><span class="p">,</span>
   <span class="n">b.diagnostics</span><span class="p">.</span><span class="n">shellcheck.with</span> <span class="p">{</span> <span class="n">diagnostics_format</span> <span class="o">=</span> <span class="s2">&#34;#{m} [#{c}]&#34;</span> <span class="p">},</span>
<span class="p">}</span>

<span class="kd">local</span> <span class="n">M</span> <span class="o">=</span> <span class="p">{}</span>

<span class="n">M.setup</span> <span class="o">=</span> <span class="kr">function</span><span class="p">()</span>
   <span class="n">null_ls.setup</span> <span class="p">{</span>
      <span class="n">debug</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
      <span class="n">sources</span> <span class="o">=</span> <span class="n">sources</span><span class="p">,</span>

      <span class="c1">-- format on save</span>
      <span class="n">on_attach</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">client</span><span class="p">)</span>
         <span class="kr">if</span> <span class="n">client.resolved_capabilities</span><span class="p">.</span><span class="n">document_formatting</span> <span class="kr">then</span>
            <span class="n">vim.cmd</span> <span class="s2">&#34;autocmd BufWritePre &lt;buffer&gt; lua vim.lsp.buf.formatting_sync()&#34;</span>
         <span class="kr">end</span>
      <span class="kr">end</span><span class="p">,</span>
   <span class="p">}</span>
<span class="kr">end</span>

<span class="kr">return</span> <span class="n">M</span>
</code></pre></div><p>保存两个文件后，执行 <code>:PackerSync</code>，会安装 <strong>null-ls</strong>，重新打开后就可以格式化代码了，保存的时候会自动进行格式化。</p>
<p>至此，Neovim 的 Nvchad 基本可用，目前还存在一个小问题就是 lua 的语法分析的配置项需要完善，还需要屏蔽掉配置文件中显示 <strong>vim</strong> 未定义的问题，等后面有时间会补充上。</p>
]]></content>
		</item>
		
		<item>
			<title>30天挑战完成第一次</title>
			<link>https://blog.5km.studio/2022/01/22/the-first-30-days-challengy/</link>
			<pubDate>Sat, 22 Jan 2022 20:30:34 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2022/01/22/the-first-30-days-challengy/</guid>
			<description>从发起第一次 30 天挑战已经过去了快五十天了，其实第一次早就成功地完成了，今天的文章算是补上第一次成功后的记录。 我和水木都完成了第一次 30 天挑战，</description>
			<content type="html"><![CDATA[<p>从发起第一次 30 天挑战已经过去了快五十天了，其实第一次早就成功地完成了，今天的文章算是补上第一次成功后的记录。</p>
<p>我和水木都完成了第一次 30 天挑战，但我中途给水木发过 30 元红包了，不过最终还好，终究还是完成了，期间因为机缘巧合慢慢变成了使用 figma 绘制插画，最终 30 天的成果如下：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/20220122-204814.png"/><figcaption>
            <h4>第一次30天挑战成果</h4>
        </figcaption>
</figure>

<p>从完成到今天中间发生了很多事情，耽搁了博客中发文记录，很好，成为“鸽王”真是指日可待！年底了，还是挺忙的，不过这周好了很多，能有自己的时间了。那么问题来了，第二个 30 天挑战什么时候开始呢？我还没想好，目前来看精力不够，最近两天得多睡觉好好休息，不然亚健康要亚到底了。。。</p>
]]></content>
		</item>
		
		<item>
			<title>铝厂键盘F60S换新衣-微光</title>
			<link>https://blog.5km.studio/2021/12/06/new-keyboard-keycap-shimmer/</link>
			<pubDate>Mon, 06 Dec 2021 21:10:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2021/12/06/new-keyboard-keycap-shimmer/</guid>
			<description>最近发现买的 iQunix 键盘 F60S 键帽打油很厉害，需要再次清理，考虑到最近没怎么购物，看着水木隔几天取个快递，我觉得我也得消费消费了，直接买一套新的键帽换</description>
			<content type="html"><![CDATA[<p>最近发现买的 iQunix 键盘 F60S 键帽打油很厉害，需要再次清理，考虑到最近没怎么购物，看着水木隔几天取个快递，我觉得我也得消费消费了，直接买一套新的键帽换上得了，懒到极致。</p>
<p>因为之前一直有看油管上有一位韩国的机械键盘 DIY 大神——<a href="https://www.youtube.com/c/3ildcat">:3ildcat</a>——的视频，在他视频中经常出现 XDA 球帽，非常 NICE，所以这次我决定就入手 XDA 键帽，挑来挑去选中了一款叫<code>微光</code>的复古键帽。</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/20211206212641.jpg" width="560px"/><figcaption>
            <h4>微光XDA键帽</h4>
        </figcaption>
</figure>

<p>上面图中展示的是一整套的键帽，再看看换键帽前的 F60S，有点脏，可以看到键帽侧面站上了粉尘颗粒，上表面也出现打油现象，油性皮肤真难伺候，实在不是我的风格，哈哈：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/20211206213153.jpg" width="560px"/><figcaption>
            <h4>F60S原有样貌</h4>
        </figcaption>
</figure>

<p>先把键帽拆下来，清理下键帽下面隐藏的XXX，清理之后未穿衣服的 F60S：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/20211206213611.jpg" width="560px"/><figcaption>
            <h4>裸奔F60S</h4>
        </figcaption>
</figure>

<p>赶快给他穿上衣服：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/20211206213821.jpg" width="560px"/><figcaption>
            <h4>微光 F60S</h4>
        </figcaption>
</figure>

<p>我拿着安装好键帽的键盘去问水木，键盘怎么样，结果来了句“键盘的廉价感”，我说这是“复古风”，她竟表示理解。。。这什么逻辑，这个混色还是挺好看的，啊哈哈，球帽用起来的好处可能就是对于新手用键盘误触会减小，总体来说，这一套键帽还是很喜欢的，清爽复古风～</p>
]]></content>
		</item>
		
		<item>
			<title>30天挑战?</title>
			<link>https://blog.5km.studio/2021/12/05/announce-30days-challenge/</link>
			<pubDate>Sun, 05 Dec 2021 09:30:34 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2021/12/05/announce-30days-challenge/</guid>
			<description>&lt;p&gt;昨天想起之前在网上看到的“60 天养成一个习惯”的说法，我就突发奇想，我不用养成什么习惯，既然意志力不够强，坚持长期做一件事那么难，为什么不考虑短期坚持做一件小事呢？&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/20211205101725.jpeg&#34; width=&#34;50%&#34;/&gt;&lt;figcaption&gt;
            &lt;h4&gt;远方&lt;/h4&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;</description>
			<content type="html"><![CDATA[<p>昨天想起之前在网上看到的“60 天养成一个习惯”的说法，我就突发奇想，我不用养成什么习惯，既然意志力不够强，坚持长期做一件事那么难，为什么不考虑短期坚持做一件小事呢？</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/20211205101725.jpeg" width="50%"/><figcaption>
            <h4>远方</h4>
        </figcaption>
</figure>

<p>当然网上还有一大堆说法，什么<code>21天成长计划</code>、<code>30天养成一个习惯</code>、<code>60天xxx</code>，这些宣传套话背后应该是有一定道理的，包括<code>21天入门xxx</code>，然后我就萌生了 <code>30天挑战</code> 计划，这个计划有以下原则：</p>
<ol>
<li>每天坚持做一件小事情，它不能占用超过 1 个小时的时间，否则视为挑战失败，最好 30 分钟左右就能完成；</li>
<li>最好早上做，如果不能，晚上睡得多晚都得做完，30 天内有 1 次补做机会，之后不做直接视为挑战失败；</li>
<li>补做一次向共同挑战者发送 30 元 🧧，即刻发！</li>
<li>挑战失败给共同挑战者发送 30 * 30 元 🧧！</li>
<li>每天完成成果要让共同挑战者检验或者发到博客或者其他社交平台！</li>
</ol>
<blockquote>
<h1 id="备注">备注：</h1>
<ul>
<li>
<p>如果完成一个 30 天挑战后，继续下一个;</p>
</li>
<li>
<p>每个挑战内容最好不一样！</p>
</li>
</ul>
</blockquote>
<p>然后我把这个想法告诉了水木，她作为我的第一个共同挑战者，当然自己决定要做什么事情。水木不怎么玩社交平台分享，那只能由我亲自检查她每天的完成情况了，我最起码会分享到博客 <code>Daily</code> 中， <code>30天挑战</code> 分别为：</p>
<table>
<thead>
<tr>
<th>日期</th>
<th>挑战项</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>2021 年 12 月</td>
<td>30 天挑战 - 每天设计</td>
<td>使用 figma 完成一个小的设计作品，可以是图标、UI 框架组件、插画等</td>
</tr>
<tr>
<td>2022 年 06 月</td>
<td>30 天挑战 - 每天唱歌</td>
<td>唱一首歌的片段，没有其它限制，甚至可以是之前已经会的，毕竟不怎么会唱歌，老了后笑话自己</td>
</tr>
<tr>
<td>2022 年 07 月</td>
<td>30 天挑战 - 每天 BBOX</td>
<td>记录一段 BBOX 片段，N 久没有玩过了，就当练习</td>
</tr>
<tr>
<td>2022 年 08 月</td>
<td>30 天挑战 - 每天写字</td>
<td>ipad 上写字</td>
</tr>
<tr>
<td>2022 年 09 月</td>
<td>30 天挑战 - 每天念白</td>
<td>借用别人写的词</td>
</tr>
<tr>
<td>2022 年 10 月</td>
<td>30 天挑战 - 每天设计</td>
<td>使用 mastergo 绘制小动物系列</td>
</tr>
<tr>
<td>2022 年 11 月</td>
<td>30 天挑战 - 每天写文</td>
<td>原创写一句话，或者写一首诗</td>
</tr>
</tbody>
</table>
<p>会持续更新，那么问题来了，<code>30 天挑战</code>这个事能长期坚持吗。。。</p>
<p>共同监督，共同期待，共勉！</p>]]></content>
		</item>
		
		<item>
			<title>PaddleDetection 模型训练笔记</title>
			<link>https://blog.5km.studio/2021/04/15/paddle-detection-train-log/</link>
			<pubDate>Thu, 15 Apr 2021 11:31:38 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2021/04/15/paddle-detection-train-log/</guid>
			<description>本文写的应该比较细了，首先介绍环境的搭建，然后以一个算法为例具体讲解训练过程，其他算法类似，而且 PaddleDetection 官方文档写的也很详细，有问题可以第一时间查</description>
			<content type="html"><![CDATA[<p>本文写的应该比较细了，首先介绍环境的搭建，然后以一个算法为例具体讲解训练过程，其他算法类似，而且 PaddleDetection 官方文档写的也很详细，有问题可以第一时间查看官方的文档！</p>
<p>本文描述的是使用 Nvidia GPU 的操作步骤。</p>
<h2 id="1-环境搭建">1. 环境搭建</h2>
<ul>
<li>为了考虑 python 的兼容性，使用最新版本的 <strong>python 3.7</strong>，即 python <strong>3.7.9</strong>。</li>
<li>使用更轻量级的 conda 工具包 <a href="https://docs.conda.io/en/latest/miniconda.html"><strong>Miniconda</strong></a> 来管理虚拟环境。</li>
</ul>
<blockquote>
<p>如果已经有了 python 的虚拟环境工具，比如 anaconda，这里推荐 anaconda 或 miniconda，可以直接跳过 <code>1.1 小节</code>！</p>
</blockquote>
<p>后续无特殊说明，均在有 Nvidia GPU 的服务器上操作，当然也可以在本地有显卡的 linux 环境下操作。</p>
<h3 id="11-python-环境">1.1 python 环境</h3>
<ol start="2">
<li>
<p>远程连接服务器 <strong>remote-server</strong>，这里 <strong>remote-server</strong> 为远程服务器：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">ssh remote-server
</code></pre></div></li>
<li>
<p>打开 <a href="https://docs.conda.io/en/latest/miniconda.html"><strong>Miniconda</strong></a> 页面，找到最新的 64 位的 linux 下的安装包复制链接，写本文时的 64位 linux 版本为：<code>https://repo.anaconda.com/miniconda/Miniconda3-py39_4.9.2-Linux-x86_64.sh</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">wget https://repo.anaconda.com/miniconda/Miniconda3-py39_4.9.2-Linux-x86_64.sh
</code></pre></div></li>
<li>
<p>为下载的 miniconda 包添加运行权限：<code>chmod +x Miniconda3-py39_4.9.2-Linux-x86_64.sh</code></p>
</li>
<li>
<p>运行安装包启动安装流程：<code>./Miniconda3-py39_4.9.2-Linux-x86_64.sh</code>，随后按 <code>Enter</code>键，后打开一个协议文件</p>
<ul>
<li>
<p>按 <code>Ctrl+d</code> 组合键进行翻页，一直翻页，会有提示问是否接受协议内容 - <code>Do you accept the license terms?[yes|no]</code>，输入 <code>yes</code>按回车键 <code>Enter</code> 即可</p>
</li>
<li>
<p>之后后询问你安装位置，默认是在自己 <code>HOME</code> 目录下的 <code>miniconda3</code> 中，这里我一般是加个 <code>.</code> 隐藏目录，所以我一般使用用户目录下的 <code>.miniconda3</code>即 <code>/home/tianye/.miniconda3</code>，根据自己的情况改成自己的 <code>HOME</code> 即可，然后继续 <code>Enter</code>，等待安装即可 ⌛️</p>
</li>
<li>
<p>一段时间后会继续有提示问题 - <code>Do you wish the installer to initialize Miniconda3 ny running conda init? [yes|no]</code> 这里输入 <strong>yes</strong> 直接回车</p>
</li>
<li>
<p><code>Ctrl + d</code>断开 SSH 连接，重新连接远程机器 <code>ssh remote-server</code>，此时就能使用 conda 了，<code>which conda</code> 应该就能看打印出 <strong>conda</strong> 的安装路径；</p>
</li>
<li>
<p>为了加速 conda 安装包的速度，可以配置使用国内的清华源：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
conda config --set show_channel_urls yes
</code></pre></div></li>
</ul>
</li>
</ol>
<h2 id="12-paddle-环境">1.2 paddle 环境</h2>
<h3 id="121-创建虚拟环境">1.2.1 创建虚拟环境</h3>
<p>重新连接远程机器后，默认会激活 conda 的 base 环境，默认是 python 3.9 的环境，我们要使用 <strong>python 3.7.9</strong> ，所以需要新建一个虚拟环境，执行命令 <code>conda create -n paddle python=3.7.9 </code></p>
<ul>
<li><code>-n paddle</code>指定虚拟环境名称为 <strong>paddle</strong>，这个名称随意，最好是自己能记住，用于区分不同的环境</li>
<li><code>python=3.7.9</code>环境中使用 <strong>3.7.9</strong> 版本的 python</li>
</ul>
<p>稍等 ⌛️ 一段时间后会有提示，回车直接默认 yes 即可，再 ⌛️ 一段时间即可完成虚拟环境的安装，会有提示如何激活和退出虚拟环境：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># To activate this environment, use</span>
<span class="c1">#</span>
<span class="c1">#     $ conda activate paddle</span>
<span class="c1">#</span>
<span class="c1"># To deactivate an active environment, use</span>
<span class="c1">#</span>
<span class="c1">#     $ conda deactivate</span>
</code></pre></div><blockquote>
<h1 id="加速-pip-包安装速度">加速 pip 包安装速度</h1>
<ol>
<li>更新 pip：<code>pip install --upgrade pip</code></li>
<li>配置使用国内安装源，这里使用 aliyun 的源：<code>pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/</code></li>
</ol>
</blockquote>
<p>下面的步骤主要是安装 PaddleDetection 2.1 版本的环境，可以参考对应版本的环境安装说明，这里 2.1 的是 <a href="https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.1/docs/tutorials/INSTALL_cn.md">docs/tutorials/INSTALL_cn.md</a>，如果是其它版本可以对应选择分支，找到 <code>docs/tutorials/INSTALL_cn.md</code>。</p>
<h3 id="122-安装-paddlepaddle">1.2.2 安装 paddlepaddle</h3>
<p>激活虚拟环境：<code>conda activate paddle</code>，使用 <code>which</code> 命令查看 python 安装目录：</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/python-20210412110120.png" style="zoom:50%;">
<p>首先确实使用 cpu 还是 GPU，这里我们使用 Nvidia 的 GPU ，cuda 10.1 版本，根据  <a href="https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.1/docs/tutorials/INSTALL_cn.md">docs/tutorials/INSTALL_cn.md</a> 确定安装 paddlepaddle 的命令如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># CUDA10.1</span>
python -m pip install paddlepaddle-gpu<span class="o">==</span>2.1.0.post101 -f https://paddlepaddle.org.cn/whl/mkl/stable.html
</code></pre></div><blockquote>
<p>如果 CUDA 是其它版本，可以去 <a href="https://www.paddlepaddle.org.cn/install/quick?docurl=https://blog.5km.studio/documentation/docs/zh/install/pip/windows-pip.html">PaddlePaddle官网</a>找对应计算平台的安装命令。</p>
</blockquote>
<h3 id="123-验证安装">1.2.3 验证安装</h3>
<p>安装完成后您可以使用 <code>python</code> 进入 python 解释器，输入<code>import paddle</code> ，再输入 <code>paddle.utils.run_check()</code></p>
<p>如果出现<code>PaddlePaddle is installed successfully!</code>，说明您已成功安装。</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/verify-20210412114137.png" style="zoom:50%;">
<h2 id="2-目标检测模型训练">2. 目标检测模型训练</h2>
<blockquote>
<h1 id="这里以-ppyolo-算法的模型训练为例使用的数据集为-voc-数据格式使用-coco-格式的数据集训练方式后面如果有涉及也会记录相关操作">这里以 ppyolo 算法的模型训练为例，使用的数据集为 <strong>VOC</strong> 数据格式，使用 <strong>COCO</strong> 格式的数据集训练方式后面如果有涉及也会记录相关操作。</h1>
</blockquote>
<h3 id="21-下载-paddledetection">2.1 下载 PaddleDetection</h3>
<p>使用最新的 <a href="https://github.com/PaddlePaddle/PaddleDetection.git"><strong>PaddleDetection</strong></a> 代码进行目标检测，使用 git clone 到服务器适当目录即可，比如用户目录下 <code>paddle</code> 目录下：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 如果没有 paddle 目录可以新建</span>
mkdir ~/paddle
<span class="c1"># 进入 paddle 目录</span>
<span class="nb">cd</span> paddle
<span class="c1"># 克隆 PaddleDetection 仓库</span>
git clone https://github.com/PaddlePaddle/PaddleDetection.git
<span class="c1"># 可以查看下 PaddleDetection 的 1 层级目录结构</span>
tree -L <span class="m">1</span> PaddleDetection
</code></pre></div><blockquote>
<h1 id="此文撰写时使用的仓库的默认分支即-release21">此文撰写时使用的仓库的默认分支，即 <strong>release/2.1</strong></h1>
</blockquote>
<ul>
<li><strong>configs</strong>：训练的配置文件，包含各种常用的算法，我们可以复制创建自己的配置文件，进行新的训练任务</li>
<li><strong>dataset</strong>：存放标注数据，一般不要将自己的数据添加到实际的 git 跟踪中</li>
<li><strong>demo</strong>：存放一些 demo 图像，可以将我们的测试样本放到这个目录中</li>
<li><strong>output</strong>：这里一般用来存放训练导出的模型以及 demo 图像的推理结果图像</li>
<li><strong>ppdet</strong>：基于 paddlepaddle 框架的目标检测的基本库</li>
<li><strong>slim</strong>：裁剪蒸馏文件的存放位置</li>
<li><strong>tools</strong>：存放基本的模型训练、评估、推理、裁剪、生成锚框、转换数据集格式等工具脚本</li>
<li><strong>vdl_dir</strong>：可视化深度学习服务所需的文件，用于可视化训练过程</li>
</ul>
<blockquote>
<h1 id="为了更好的管理自己的配置文件建议使用好-git这里我们可以建立自己的分支比如我这里建立-5km-分支">为了更好的管理自己的配置文件，建议使用好 git，这里我们可以建立自己的分支，比如我这里建立 <strong>5km</strong> 分支！</h1>
</blockquote>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 进入 PaddleDetection 目录</span>
<span class="nb">cd</span> PaddleDetection
<span class="c1"># 创建并切换到 5km 分支</span>
git checkout -b 5km
</code></pre></div><h3 id="22-数据集处理">2.2 数据集处理</h3>
<p>可以使用 <a href="https://pypi.org/project/vocgo/"><strong>vocgo</strong></a> 切分数据集，VOC 数据集中一般会有两个目录，以数据组帮忙标注的银行大厅人员的数据为例，简单讲解下 vocgo 使用。</p>
<p>这里数据放在了我的用户下的 <code>data/bank</code> 中，首先进入这个目录：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 进入数据目录</span>
<span class="nb">cd</span> ~/data/bank
<span class="c1"># 激活虚拟环境</span>
conda activate paddle
</code></pre></div><h4 id="221-安装-vocgo">2.2.1 安装 vocgo</h4>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">python3 -m pip install vocgo
</code></pre></div><h4 id="222-查看标注类别">2.2.2 查看标注类别</h4>
<p>一般情况下， VOC 数据的目录结构如下：</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84-20210413141523.png" style="zoom:50%;">
<p>使用命令 <code>vocgo list .</code>即可查看当前目录下数据中标注的类别都有哪些：</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/vocgo-list-20210413142022.png" style="zoom:50%;">
<ul>
<li>包含了 5 种类别的目标，也分别统计了每种类别的目标个数</li>
<li>同时也统计了有、没有检测目标的图像个数</li>
</ul>
<blockquote>
<h2 id="命令中指的是当前目录">命令中**.**指的是当前目录</h2>
</blockquote>
<h4 id="223-切分数据">2.2.3 切分数据</h4>
<p>可以使用命令 <code>vocgo split --ratio 0.9 . </code>对当前目录中的标注数据进行切分，其中训练集图像与验证集图像按照 比例 <code>0.9</code> 进行划分，执行命令后会提示输入切分数据文件的导出目录，这里我们指定为 <strong>all</strong>:</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/split-data-20210413143652.png" style="zoom:50%;">
<p>会在指定导出目录中生成相应的标注文件。</p>
<p>如果我们只想筛选其中的 <strong>customer</strong>、<strong>security</strong>、<strong>teller</strong>三个类别训练模型，可以通过命令<code>vocgo split --labels=customer,security,teller --ratio 0.9 .</code>进行数据筛选和切分，执行命令后指定导出目录为 <code>human</code> ：</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/split-data--20210413144210.png" style="zoom:50%;">
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 查看导出数据的类别</span>
➜ cat human/label_list.txt
customer
security
teller
</code></pre></div><p>同样我们可以使用 <code>vocgo list human</code> 查看新导出切分数据集的类别信息：</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/new-data-list-20210413144550.png" style="zoom:50%;">
<blockquote>
<h1 id="为了不占用多余的数据空间这里建议将我们的数据目录建立软链接到-paddledetection-的-dataset-目录下">为了不占用多余的数据空间，这里建议将我们的数据目录建立软链接到 <strong>PaddleDetection</strong> 的 <strong>dataset</strong> 目录下！</h1>
</blockquote>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 将 bank 目录链接到 ~/paddle/PaddleDetection/dataset/bank</span>
ln -s ~/data/bank ~/paddle/PaddleDetection/dataset/bank
<span class="c1"># 建议将新增的 bank 软链接添加到 PaddleDetection 仓库的 .gitignore 中</span>
<span class="nb">echo</span> <span class="s2">&#34;dataset/bank&#34;</span> &gt;&gt; ~/paddle/PaddleDetection/.gitignore
</code></pre></div><blockquote>
<h3 id="最好是把自己添加的数据集目录都追加到-paddledetection-的忽略文件中就像上面的第二条命令">最好是把自己添加的数据集目录都追加到 <strong>PaddleDetection</strong> 的忽略文件中！就像上面的第二条命令。</h3>
</blockquote>
<h3 id="23-建立配置文件">2.3 建立配置文件</h3>
<p>我们这里使用百度 paddlepaddle 团队自研算法训练模型。</p>
<p>仓库中自带的 ppyolo 配置文件在 <code>configs/ppyolo</code>中，当然我们可以直接修改已经有的配置文件，但是为了不影响原有的参考文件，我们最好是复制一份，重命名一下，比如这里我们使用的是 <code>configs/ppyolo/ppyolo_voc.yml</code> ，我们要用 [2.2.3 小节](#2.2.3 切分数据) 中的切分出的 human 数据进行训练，我们暂且重命名为<code>ppyolo_bank_human_voc.yml</code>吧：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 复制新的配置文件</span>
cp configs/ppyolo/ppyolo_voc.yml configs/ppyolo/ppyolo_bank_human_voc.yml
</code></pre></div><p>下面开始修改配置文件，这里只简单说几项关键的参数。首先我们明确一下要使用几个 GPU，比如我们使用 4 个卡去训练模型（<strong>GPU_num</strong>=<strong>4</strong>）。</p>
<h4 id="231-修改-batch_size">2.3.1 修改 batch_size</h4>
<p>这个指的是一次显卡加载的图像样本数量，这受到显存的限制，默认使用的是 <strong>12</strong>，默认值对应的是 <strong>32GB</strong> 显存的卡 <strong>Tesla V100</strong>，我们的卡显存是 <strong>12GB</strong> 的 <strong>2080Ti</strong>，所以需要调小一点，比如使用 <strong>4</strong>，有一定的线性关系，当看着还有显存的时候，可以慢慢上调，比如 <strong>6</strong>，这里我们就使用 <strong>4</strong>。这个在 <code>TrainReader</code>的<code>batch_size</code>设置。</p>
<h4 id="232-修改-max_iters">2.3.2 修改 max_iters</h4>
<p>ppyolo 中不使用 <code>epoch_size</code> 表示迭代，而是使用 <code>max_iters</code>表示，这里我们默认 <code>epoch_size=120</code>，可以使用以下关系式去设置参数值：</p>
<p>$$
iters_{max}=\frac{Num_{image} \times Size_{epoch}}{Num_{GPU} \times Size_{batch}}
$$</p>
<p>在 [2.2.3 小节](#2.2.3 切分数据) 切分数据的时候，可以看到 <strong>human</strong> 数据集中图像数量（<code>image_num</code>）是 <strong>4926</strong>， [2.3.1 小节](#2.3.1 修改 batch_size) 中调整 <code>batch_size</code> 为 <strong>4</strong>，<strong>GPU_num</strong> 为 4，最后计算如下：</p>
<p>$$
iters_{max}=\frac{4926 \times 120}{4 \times 4}=36945
$$</p>
<p>这里我们就取个整 <strong>36000</strong> 吧！</p>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="c"># image_num * epoch_size / GPU_num / batch_size</span><span class="w">
</span><span class="w"></span><span class="c"># 4926 * 120 / 4 / 4 =&gt; 36000</span><span class="w">
</span><span class="w"></span><span class="nt">max_iters</span><span class="p">:</span><span class="w"> </span><span class="m">36000</span><span class="w">
</span></code></pre></div><h4 id="233-调整日志打印间隔">2.3.3 调整日志打印间隔</h4>
<p>为了更细的看到迭代过程中的结果，将 <code>log_smooth_window</code>和<code>log_iter</code>由原来的 <strong>20</strong> 改为 <strong>5</strong>。</p>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="nt">log_smooth_window</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span><span class="w"></span><span class="nt">log_iter</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></code></pre></div><h4 id="234-设置快照间隔-snapshot_iter">2.3.4 设置快照间隔 snapshot_iter</h4>
<p>此参数表示每多少次保存一版模型，这里设置 <strong>3000</strong>，最终算下来能保存 <strong>12</strong> 个。</p>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="nt">snapshot_iter</span><span class="p">:</span><span class="w"> </span><span class="m">3000</span><span class="w">
</span></code></pre></div><h4 id="235-设置模型目录">2.3.5 设置模型目录</h4>
<p>此参数为设置模型权重文件的最终名称，其实是变相的设置输出目录，一般是设置在 <strong>output</strong> 下，即：</p>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="nt">save_dir</span><span class="p">:</span><span class="w"> </span><span class="l">output</span><span class="w">
</span></code></pre></div><blockquote>
<h1 id="训练过程中会在指定的-save_dir-下生成与配置文件同名目录---ppyolo_bank_human_voc中间过程即-234-小节234-设置快照间隔-snapshot_iter-中设置保存的每一版模型都会存储到该目录中">训练过程中，会在指定的 <strong>save_dir</strong> 下生成与配置文件同名目录 - <code>ppyolo_bank_human_voc</code>，中间过程即 [2.3.4 小节](#2.3.4 设置快照间隔 snapshot_iter) 中设置保存的每一版模型都会存储到该目录中。</h1>
</blockquote>
<p>这里要特别说一下 <code>weights</code> 参数，这个参数一般用于模型转换和模型推理，用于指定转换的输入模型，模型训练完成后最终的模型名称就是 <strong>model_final</strong>，我们可以指定为下面的配置：</p>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="nt">weights</span><span class="p">:</span><span class="w"> </span><span class="l">output/ppyolo_bank_human_voc/model_final</span><span class="w">
</span></code></pre></div><h4 id="236-设置类别个数">2.3.6 设置类别个数</h4>
<p>yolo 系的算法在数据集的配置中都会设置 <code>with_background</code> 为 <code>false</code> 也就是不包含背景，那么依照 [2.2.3 小节](#2.2.3 切分数据) 最后查看 human 切分数据的时候的类别个数为准，也就是 <strong>3</strong> 个：</p>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="nt">num_classes</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></code></pre></div><h4 id="237-设置学习率参数">2.3.7 设置学习率参数</h4>
<ol>
<li>按照百度传达的经验，调整学习率应该依据于使用 GPU 的个数 <strong>GPU_num</strong> 和设置的 <strong>batch_size</strong>，原始的 <code>base_lr=0.01</code>为使用 8 卡，同时 <code>batch_size</code> 设置为 <strong>12</strong> 时设置的值，因为我们使用卡数量为 <strong>4</strong>，<strong>batch_size</strong> 为 <strong>4</strong>，所以最终设置 <code>base_lr</code>为 <strong>0.0025</strong>即可。</li>
<li>设置学习率调整的 <code>schedulers</code> 参数，其中<code>PiecewiseDecay</code> 的 <code>milestones</code> 设置依赖于 [2.3.2 小节](#2.3.2 设置 max_iters)中的 <code>max_iters</code> 值，两个值一般分别按比例 <strong>0.8</strong> 和 <strong>0.88</strong>（大约，周边取整即可)，所以对应 <strong>36000</strong> 的话，应该是 <strong>28800</strong> 和 <strong>32000</strong>。</li>
<li>同样需要按比例调整 <code>LinearWarmup</code>的 <code>steps</code>，也是依据 [2.3.2 小节](#2.3.2 设置 max<em>iters) 中的 <code>max_iters</code> 值，一般比例是 <strong>17.5</strong> 左右，计算取整即可，这里 $\frac{iters</em>{max}}{17.5}=\frac{36000}{17.5}\approx2100$，所以最终配置。</li>
</ol>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="nt">LearningRate</span><span class="p">:</span><span class="w">
</span><span class="w">  </span><span class="nt">base_lr</span><span class="p">:</span><span class="w"> </span><span class="m">0.0025</span><span class="w">
</span><span class="w">  </span><span class="nt">schedulers</span><span class="p">:</span><span class="w">
</span><span class="w">    </span>- !<span class="l">PiecewiseDecay</span><span class="w">
</span><span class="w">      </span><span class="nt">gamma</span><span class="p">:</span><span class="w"> </span><span class="m">0.1</span><span class="w">
</span><span class="w">      </span><span class="nt">milestones</span><span class="p">:</span><span class="w">
</span><span class="w">        </span>- <span class="m">28800</span><span class="w">
</span><span class="w">        </span>- <span class="m">32000</span><span class="w">
</span><span class="w">    </span>- !<span class="l">LinearWarmup</span><span class="w">
</span><span class="w">      </span><span class="nt">start_factor</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="l">.</span><span class="w">
</span><span class="w">      </span><span class="nt">steps</span><span class="p">:</span><span class="w"> </span><span class="m">2100</span><span class="w">
</span></code></pre></div><h4 id="238-设置-reader">2.3.8 设置 Reader</h4>
<ol>
<li>
<p>设置 <code>_Reader_</code>，默认值是相对于配置文件下的 <code>ppyolo_reader.yml</code>文件，以后有可能我们还会修改里面的内容，所我们也复制这个文件，新文件命名为 <code>ppyolo_bank_human_reader.yml</code>，同时更新 <code>_Reader_</code> 的值为 <code>ppyolo_bank_human_reader.yml</code>。</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 复制 reader 文件</span>
cp configs/ppyolo/ppyolo_reader.yml configs/ppyolo/ppyolo_bank_human_reader.yml
</code></pre></div></li>
<li>
<p>设置数据集目录，需要对训练集、评估集的 <code>dataset_dir</code> 、<code>anno_path</code></p>
<ul>
<li><code>dataset_dir</code> 数据集的目录路径，相对于 <strong>PaddleDetection</strong> 的目录设置，训练集和评估集都是 <code>dataset/bank/human</code></li>
<li><code>anno_path</code> 对应的切分数据文件，训练集的为 <code>train.txt</code>，评估集的为 <code>valid.txt</code></li>
</ul>
</li>
<li>
<p>设置使用我们自己生成的 <strong>label_list</strong> 文件，所以需要把 <code>TrainReader</code>、<code>EvalReader</code>、<code>TestReader</code> 的 <code>dataset</code> 下的 <code>use_default_label</code> 设置为 <code>false</code> 。</p>
</li>
<li>
<p>对应 [2.3.1 小节](# 设置 batch_size) 中的 <code>batch_size</code> 就是在 <code>TrainReader</code> 中设置，值为 <strong>4</strong>。</p>
</li>
</ol>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="nt">_READER_</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;ppyolo_bank_human_reader.yml&#34;</span><span class="w">
</span><span class="w"></span><span class="nt">TrainReader</span><span class="p">:</span><span class="w">
</span><span class="w">  </span><span class="nt">dataset</span><span class="p">:</span><span class="w"> </span>!<span class="l">VOCDataSet</span><span class="w">
</span><span class="w">    </span><span class="nt">dataset_dir</span><span class="p">:</span><span class="w"> </span><span class="l">dataset/bank/human</span><span class="w">
</span><span class="w">    </span><span class="nt">anno_path</span><span class="p">:</span><span class="w"> </span><span class="l">train.txt</span><span class="w">
</span><span class="w">    </span><span class="nt">use_default_label</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span><span class="w">    </span><span class="nt">with_background</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span><span class="w">  </span><span class="nt">mixup_epoch</span><span class="p">:</span><span class="w"> </span><span class="m">350</span><span class="w">
</span><span class="w">  </span><span class="nt">batch_size</span><span class="p">:</span><span class="w"> </span><span class="m">4</span><span class="w">
</span><span class="w">
</span><span class="w"></span><span class="nt">EvalReader</span><span class="p">:</span><span class="w">
</span><span class="w">  </span><span class="nt">inputs_def</span><span class="p">:</span><span class="w">
</span><span class="w">    </span><span class="nt">fields</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;image&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;im_size&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;im_id&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;gt_bbox&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;gt_class&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;is_difficult&#34;</span><span class="p">]</span><span class="w">
</span><span class="w">    </span><span class="nt">num_max_boxes</span><span class="p">:</span><span class="w"> </span><span class="m">50</span><span class="w">
</span><span class="w">  </span><span class="nt">dataset</span><span class="p">:</span><span class="w"> </span>!<span class="l">VOCDataSet</span><span class="w">
</span><span class="w">    </span><span class="nt">dataset_dir</span><span class="p">:</span><span class="w"> </span><span class="l">dataset/bank/human</span><span class="w">
</span><span class="w">    </span><span class="nt">anno_path</span><span class="p">:</span><span class="w"> </span><span class="l">valid.txt</span><span class="w">
</span><span class="w">    </span><span class="nt">use_default_label</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span><span class="w">    </span><span class="nt">with_background</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span><span class="w">
</span><span class="w"></span><span class="nt">TestReader</span><span class="p">:</span><span class="w">
</span><span class="w">  </span><span class="nt">dataset</span><span class="p">:</span><span class="w"> </span>!<span class="l">ImageFolder</span><span class="w">
</span><span class="w">    </span><span class="nt">use_default_label</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span><span class="w">    </span><span class="nt">with_background</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></code></pre></div><h4 id="239-设置-anchor">2.3.9 设置 Anchor</h4>
<ol>
<li>
<p>这里需要使用 <strong>PaddleDetection</strong> 提供的工具脚本 <code>tools/anchor_cluster.py</code>聚类生成对应 <strong>human</strong> 数据的 Anchor 数据：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">python tools/anchor_cluster.py -c configs/ppyolo/ppyolo_bank_human_voc.yml -n <span class="m">9</span> -s <span class="m">608</span> -m v2 -i <span class="m">1000</span>
</code></pre></div><ul>
<li><code>-c</code> 指定使用的配置文件，这里我们的就是刚刚修改好的配置文件 <code>configs/ppyolo/ppyolo_bank_human_voc.yml</code></li>
<li><code>-n 9</code> 生成 9 组尺寸</li>
<li><code>-s 608</code> 指定图像训练尺寸</li>
<li><code>-m v2</code> 使用默认的 v2 版本方法</li>
<li><code>-i 1000</code> 迭代次数</li>
</ul>
<blockquote>
<h2 id="其实这里的--c-之后的很多选项我也不知道具体明确的意思后面有时间在探究然后补充到这里先按照默认的选项生成">其实这里的 -c 之后的<strong>很多选项我也不知道具体</strong>明确的<strong>意思</strong>，后面有时间在探究然后补充到这里，先按照默认的选项生成！</h2>
</blockquote>
<p>执行上边脚本后会打印 9 组尺寸，用于替换配置文件中原有配置即可，比如我这里打印的是：</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/anchor_cluster-20210414140006.png" style="zoom:50%;">
</li>
<li>
<p>需要替换配置文件 <code>configs/ppyolo/ppyolo_bank_human_voc.yml</code>中 <code>YOLOv3Head</code>的 <code>anchors</code> 为上面打印的 9 个尺寸列表。</p>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="nt">YOLOv3Head</span><span class="p">:</span><span class="w">
</span><span class="w">  </span><span class="nt">anchor_masks</span><span class="p">:</span><span class="w"> </span><span class="p">[[</span><span class="m">6</span><span class="p">,</span><span class="w"> </span><span class="m">7</span><span class="p">,</span><span class="w"> </span><span class="m">8</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="m">3</span><span class="p">,</span><span class="w"> </span><span class="m">4</span><span class="p">,</span><span class="w"> </span><span class="m">5</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="m">1</span><span class="p">,</span><span class="w"> </span><span class="m">2</span><span class="p">]]</span><span class="w">
</span><span class="w">  </span><span class="nt">anchors</span><span class="p">:</span><span class="w">
</span><span class="w">    </span><span class="p">[</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">20</span><span class="p">,</span><span class="w"> </span><span class="m">44</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">28</span><span class="p">,</span><span class="w"> </span><span class="m">108</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">38</span><span class="p">,</span><span class="w"> </span><span class="m">158</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">78</span><span class="p">,</span><span class="w"> </span><span class="m">123</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">55</span><span class="p">,</span><span class="w"> </span><span class="m">221</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">108</span><span class="p">,</span><span class="w"> </span><span class="m">152</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">79</span><span class="p">,</span><span class="w"> </span><span class="m">268</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">118</span><span class="p">,</span><span class="w"> </span><span class="m">326</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">156</span><span class="p">,</span><span class="w"> </span><span class="m">352</span><span class="p">],</span><span class="w">
</span><span class="w">    </span><span class="p">]</span><span class="w">
</span></code></pre></div></li>
<li>
<p>因为配置项 <code>YOLOv3Loss</code> 的 <code>use_fine_grained_loss</code> 配置为了 <code>true</code> 所以还需要设置 <code>configs/ppyolo/ppyolo_bank_human_reader.yml</code> 中的 <strong>anchor</strong> 配置，具体找到 <code>TrainReader</code> -<code>batch_transforms</code>-<code>!Gt2YoloTarget</code> 修改 <code>anchors</code> 即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="c"># Gt2YoloTarget is only used when use_fine_grained_loss set as true,</span><span class="w">
</span><span class="w"></span><span class="c"># this operator will be deleted automatically if use_fine_grained_loss</span><span class="w">
</span><span class="w"></span><span class="c"># is set as false</span><span class="w">
</span><span class="w"></span>- !<span class="l">Gt2YoloTarget</span><span class="w">
</span><span class="w">  </span><span class="nt">anchor_masks</span><span class="p">:</span><span class="w"> </span><span class="p">[[</span><span class="m">6</span><span class="p">,</span><span class="w"> </span><span class="m">7</span><span class="p">,</span><span class="w"> </span><span class="m">8</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="m">3</span><span class="p">,</span><span class="w"> </span><span class="m">4</span><span class="p">,</span><span class="w"> </span><span class="m">5</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="m">1</span><span class="p">,</span><span class="w"> </span><span class="m">2</span><span class="p">]]</span><span class="w">
</span><span class="w">  </span><span class="c"># anchors: [[22, 47], [29, 112], [41, 170],</span><span class="w">
</span><span class="w">  </span><span class="c">#           [75, 123], [62, 243], [106, 148],</span><span class="w">
</span><span class="w">  </span><span class="c">#           [101, 278], [121, 384], [155, 328]]</span><span class="w">
</span><span class="w">  </span><span class="nt">anchors</span><span class="p">:</span><span class="w">
</span><span class="w">    </span><span class="p">[</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">20</span><span class="p">,</span><span class="w"> </span><span class="m">44</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">28</span><span class="p">,</span><span class="w"> </span><span class="m">108</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">38</span><span class="p">,</span><span class="w"> </span><span class="m">158</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">78</span><span class="p">,</span><span class="w"> </span><span class="m">123</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">55</span><span class="p">,</span><span class="w"> </span><span class="m">221</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">108</span><span class="p">,</span><span class="w"> </span><span class="m">152</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">79</span><span class="p">,</span><span class="w"> </span><span class="m">268</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">118</span><span class="p">,</span><span class="w"> </span><span class="m">326</span><span class="p">],</span><span class="w">
</span><span class="w">      </span><span class="p">[</span><span class="m">156</span><span class="p">,</span><span class="w"> </span><span class="m">352</span><span class="p">],</span><span class="w">
</span><span class="w">    </span><span class="p">]</span><span class="w">
</span></code></pre></div></li>
</ol>
<blockquote>
<h3 id="到这里完成了基本的配置文件的修改下一步就可以进行模型训练了">到这里完成了基本的配置文件的修改，下一步就可以进行模型训练了！</h3>
<h1 id="本小节简单介绍了基本的常规需要调整的参数后面如果有收获会继续丰富这个小节关于参数的调整内容">本小节简单介绍了基本的常规需要调整的参数，后面如果有收获会继续丰富这个小节关于参数的调整内容。</h1>
</blockquote>
<h3 id="24-模型训练">2.4 模型训练</h3>
<p>得益于强大的 <strong>PaddleDetection</strong> 我们只需要简单的一条命令就可以开启模型训练。</p>
<h4 id="241-准备工作">2.4.1 准备工作</h4>
<p>训练过程一般是很长时间，然而我们 ssh 链接一旦断开就会把运行的训练程序停掉，为了防止这种情况的发生，我们一般远程 SSH 链接机器后，还会使用 <a href="https://baike.baidu.com/item/tmux/10234491?fr=aladdin">tmux</a> 创建会话，在新的会话中执行我们的命令，只要不是 <strong>tmux</strong> 挂掉或者我们自己 kill 程序，一般情况下我们的程序就会由 tmux 进行托管执行，即使我们断开了 SSH ，重新连接机器后，连接上 tmux 的会话也能看到自己也还在执行的程序，下面简单说一下我的操作过程：</p>
<ol>
<li>
<p>创建会话，会话名称为 <strong>paddle</strong></p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">tmux new -s paddle
</code></pre></div><p>这样就会打开一个会话并激活一个窗口 shell。</p>
</li>
<li>
<p>我一般会把窗口重命名，还会创建一个专门监控显卡和其他硬件资源的窗口，使用当前的窗口创建即可：</p>
<ul>
<li>
<p>重命名窗口为 <strong>monitor</strong>：按下快捷键 <code>ctrl</code>+<code>b</code>，然后按 <code>,</code> 键就会在最低边可以编辑修改名称，改为 <strong>monitor</strong> 回车即可</p>
</li>
<li>
<p>这样回到 shell 之，执行命令 <code>watch -n 0.5 nvidia-smi</code>，这样就会每 0.5 秒刷新一次显卡状态</p>
</li>
<li>
<p>快捷键 <code>ctrl</code>+<code>b</code>，然后按 <code>%</code> 键，会在右侧竖分出一个新的窗格，我们可以打开一个 top 命令随时查看 CPU、memery 和 proc 情况，我这里使用的是 gotop 命令，更好看一些，直接运行命令 <code>gotop</code>，你可以运行 <code>top</code> 或者 <code>htop</code>，这样我们的监控窗口就做好了，晒一下我的监控窗口：</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/monitor-20210414150134.jpg" style="zoom:50%;">
</li>
<li>
<p>快捷键 <code>ctrl</code>+<code>b</code>，然后按 <code>c</code> 键，此时会创建新的窗口，按下快捷键 <code>ctrl</code>+<code>b</code>，然后按 <code>,</code> 键就会在最低边可以编辑修改名称，改为 <strong>train</strong> 回车，这样我们的窗口都准备好了，窗口切换的快捷键是：快捷键 <code>ctrl</code>+<code>b</code>，然后按 <code>p</code> 或 <code>n</code></p>
<ul>
<li>其中 <code>p</code> 代表 <strong>previous</strong> 表示切换到前一个窗口</li>
<li>其中 <code>n</code> 代表 <strong>next</strong> 表示切换到前一个窗口</li>
</ul>
</li>
</ul>
</li>
<li>
<p>然后进入工作目录，并激活虚拟环境</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 进入 PaddleDetection 目录</span>
<span class="nb">cd</span> paddle/PaddleDetection
<span class="c1"># 激活虚拟环境</span>
conda activate paddle
</code></pre></div></li>
</ol>
<h4 id="242-开始训练">2.4.2 开始训练</h4>
<p>假设我们使用 0、1、2、3 号卡训练模型，那么执行命令</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="nv">CUDA_VISIBLE_DEVICES</span><span class="o">=</span>0,1,2,3 python tools/train.py -c configs/ppyolo/ppyolo_bank_human_voc.yml --eval -o <span class="nv">use_gpu</span><span class="o">=</span><span class="nb">true</span> --use_vdl<span class="o">=</span>True --vdl_log_dir<span class="o">=</span>vdl_dir/scalar
</code></pre></div><ul>
<li><code>CUDA_VISIBLE_DEVICES=0,1,2,3</code> 指定环境变量，告诉 <strong>ppdect</strong> 使用 0、1、2、3 号卡进行多卡训练</li>
<li><code>tools/train.py</code> 训练工具脚本</li>
<li><code>-c configs/ppyolo/ppyolo_bank_human_voc.yml</code> 使用我们刚做好的配置文件</li>
<li><code>--eval</code> 每次快照保存模型的时候进行一次评估</li>
<li><code>-o use_gpu=true</code> 覆盖配置文件中的指定配置项的值，这里是指定要使用 gpu</li>
<li><code>--use_vdl=True</code> 配置开启深度学习可视化，可以看一些 loss 曲线，可视化训练过程中关键指标的变化</li>
<li><code>--vdl_log_dir=vdl_dir/scalar</code> 指定可视化日志输出位置</li>
</ul>
<p>正常的情况下，经过 ⌛️ 一段时间我们就能看到迭代过程中的日志打印了，类似于：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="o">[</span>INFO 2021-04-14 14:29:09,065 train.py:286<span class="o">]</span> iter: 765, lr: 0.000607, <span class="s1">&#39;loss_xy&#39;</span>: <span class="s1">&#39;1.455415&#39;</span>, <span class="s1">&#39;loss_wh&#39;</span>: <span class="s1">&#39;1.337777&#39;</span>, <span class="s1">&#39;loss_obj&#39;</span>: <span class="s1">&#39;11.151718&#39;</span>, <span class="s1">&#39;loss_cls&#39;</span>: <span class="s1">&#39;2.540418&#39;</span>, <span class="s1">&#39;loss_iou&#39;</span>: <span class="s1">&#39;4.378272&#39;</span>, <span class="s1">&#39;loss_iou_aware&#39;</span>: <span class="s1">&#39;0.087677&#39;</span>, <span class="s1">&#39;loss&#39;</span>: <span class="s1">&#39;20.988634&#39;</span>, eta: 6:41:24, batch_cost: 0.68355 sec, ips: 5.85182 images/sec
</code></pre></div><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/train-20210414150402.png" style="zoom:50%;">
<p>可以查看日志打印观察 <strong>loss</strong> 是否是收敛的。</p>
<p>训练起来之后可以按快捷键 快捷键 <code>ctrl</code>+<code>b</code>，然后按 <code>p</code> 返回到 <strong>monitor</strong> 窗口，查看硬件资源使用情况，比如显卡，可以看一下显存占用，可以作为依据调整配置文件中<code> batch_size</code>。</p>
<h4 id="243-可视化曲线">2.4.3 可视化曲线</h4>
<p>为了更直观的观看训练情况，我们可以开启 <strong>VDL</strong> 的服务查看动态的曲线变化。这里有个小技巧，如果直接在 ssh 连接机器的 shell 中执行启动可视化服务的命令，因为服务器的端口并未开放，我们在自己本地是无法访问的，最简单的坚决方式是使用 <strong>vscode</strong>，vscode 能自动转发连接服务器上自己启动的端口服务，这里就不讲如何使用 vscode 远程连接服务器了，自行百度。</p>
<p>使用 vscode 远程连接上面训练任务开启的机器，打开 <code>paddle/PaddleDetectioj</code> 文件夹，打开一个终端窗口，如果没有激活我们创建的 <strong>paddle</strong> 虚拟环境的话，我们自己激活即可 <code>conda activate paddle</code> ，然后执行以下命令启动服务，指定 vdl 的日志目录与训练命令中对应起来：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">visualdl --logdir vdl_dir/scalar/
</code></pre></div><p>执行命令后，vscode 就会检测到新的端口服务并提示完成转发：</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/visual-dl-20210414152231.png"  style="zoom:50%;">
<p>点击**「在浏览器中打开」**按钮即可在浏览器打开我们的可视化服务的页面，便可以查看各种曲线了：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/vdl-20210414152903.png" alt=""></p>
<h4 id="244-后台运行">2.4.4 后台运行</h4>
<p>我们可以把 tmux 会话推到后台，使用快捷键 <code>ctrl</code>+<code>b</code>，然后按下 <code>d</code> 即可退出 tmux。</p>
<p>如果想要重新连接我们训练的会话，执行命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">tmux a -t paddle
</code></pre></div><ul>
<li><code>paddle</code> 就是我们的会话名称</li>
<li><code>a</code> 是 <strong>attach</strong> 的缩写</li>
</ul>
<h4 id="245-等待完成">2.4.5 等待完成</h4>
<p>训练需要很长时间，具体大约的时间，在打印的日志中有体现，比如：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="o">[</span>INFO 2021-04-14 14:29:09,065 train.py:286<span class="o">]</span> iter: 765, lr: 0.000607, <span class="s1">&#39;loss_xy&#39;</span>: <span class="s1">&#39;1.455415&#39;</span>, <span class="s1">&#39;loss_wh&#39;</span>: <span class="s1">&#39;1.337777&#39;</span>, <span class="s1">&#39;loss_obj&#39;</span>: <span class="s1">&#39;11.151718&#39;</span>, <span class="s1">&#39;loss_cls&#39;</span>: <span class="s1">&#39;2.540418&#39;</span>, <span class="s1">&#39;loss_iou&#39;</span>: <span class="s1">&#39;4.378272&#39;</span>, <span class="s1">&#39;loss_iou_aware&#39;</span>: <span class="s1">&#39;0.087677&#39;</span>, <span class="s1">&#39;loss&#39;</span>: <span class="s1">&#39;20.988634&#39;</span>, eta: 6:41:24, batch_cost: 0.68355 sec, ips: 5.85182 images/sec
</code></pre></div><p>其中 <code>eta</code> 就是评估的可能耗时。</p>
<blockquote>
<h3 id="去干点其它的事情就行了可以偶尔连接机器连接-tmux-会话查看训练情况有可能会出问题的">去干点其它的事情就行了，可以偶尔连接机器连接 tmux 会话查看训练情况，有可能会出问题的！</h3>
</blockquote>
<h3 id="25-模型推理">2.5 模型推理</h3>
<h4 id="251-一张图像推理">2.5.1 一张图像推理</h4>
<p>推理很简单，使用以下命令即可测试一张图像的推理：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="nv">CUDA_VISIBLE_DEVICES</span><span class="o">=</span><span class="m">0</span> python tools/infer.py -c configs/ppyolo/ppyolo_bank_human_voc.yml -o <span class="nv">use_gpu</span><span class="o">=</span><span class="nb">true</span> --infer_img<span class="o">=</span>demo/demo.jpg
</code></pre></div><ul>
<li><code>CUDA_VISIBLE_DEVICES=0 </code> 声明要使用的显卡编号，比如这里指定使用 0 号卡</li>
<li><code>-c configs/ppyolo/ppyolo_bank_human_voc.yml</code> 使用我们刚做好的配置文件</li>
<li><code>-o use_gpu=true</code> 覆盖配置文件中的指定配置项的值，这里是指定要使用 gpu</li>
<li><code>--infer_img</code> 指定要测试的图像</li>
</ul>
<blockquote>
<h2 id="这里注意的是根据训练配置推理会默认加载使用配置项-weights-指定的模型这里就是-outputppyolo_bank_human_vocmodel_final详见235-设置模型目录235-设置模型目录-">这里注意的是，根据训练配置，推理会默认加载使用配置项 <strong>weights</strong> 指定的模型，这里就是 <strong>output/ppyolo_bank_human_voc/model_final</strong>，详见[2.3.5 设置模型目录](#2.3.5 设置模型目录) 。</h2>
</blockquote>
<h4 id="252-多张图像推理">2.5.2 多张图像推理</h4>
<p>如果要推理一批图像，可以将图像放置在一个目录中，比如是 <code>./demo/bank</code>，使用 <code>infer.py</code> 时指定图像目录即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="nv">CUDA_VISIBLE_DEVICES</span><span class="o">=</span><span class="m">0</span> python tools/infer.py -c configs/ppyolo/ppyolo_bank_human_voc.yml -o <span class="nv">use_gpu</span><span class="o">=</span><span class="nb">true</span> --infer_dir<span class="o">=</span>demo/bank
</code></pre></div><ul>
<li><code>--infer_dir</code> 指定要测试的图像目录</li>
</ul>
<h4 id="253-其它选项">2.5.3 其它选项</h4>
<p>可以查看 <code>tools/infer.py</code> 最后的代码，自行查看。</p>
<h3 id="26-模型部署">2.6 模型部署</h3>
<p>步骤比较清晰，要看是预测部署还是服务部署方式。</p>
<h4 id="261-预测部署">2.6.1 预测部署</h4>
<h5 id="2611-导出模型">2.6.1.1 导出模型</h5>
<p>使用以下命令导出模型文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">python tools/export_model.py -c configs/ppyolo/ppyolo_bank_human_voc.yml -o <span class="nv">use_gpu</span><span class="o">=</span><span class="nb">true</span> --output_dir<span class="o">=</span>./inference_model
</code></pre></div><ul>
<li><code>tools/export_model.py</code> 导出模型的工具脚本；</li>
<li><code>-c configs/ppyolo/ppyolo_bank_human_voc.yml</code> 指定配置文件</li>
<li><code>--output_dir=./inference_model</code> 指定导出目录，会在此指定目录下生成与配置文件同名的目录存放导出模型文件</li>
</ul>
<p>导出模型结构：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">➜ tree inference_model/ppyolo_bank_human_voc
inference_model/ppyolo_bank_human_voc
├── infer_cfg.yml
├── __model__
└── __params__

<span class="m">0</span> directories, <span class="m">3</span> files
</code></pre></div><p>可以看到只有三个文件：</p>
<ul>
<li><code>infer_cfg.yml</code> 中能看到模型的关键配置信息和类别信息</li>
<li><code>__model__</code> 描述网络结构</li>
<li><code>__params__</code> 存储网络结构中的权重参数数据</li>
</ul>
<h5 id="2612-预测">2.6.1.2 预测</h5>
<p>使用 <code>deploy/python/infer.py</code> 这个工具脚本推理即可，<code>deploy/python</code> 目录下三个 python 脚本，只需要这三个文件和上面导出的模型文件即可独立于 PaddleDetection 完成推理预测，支持分析指定图像以及视频文件。</p>
<ul>
<li>
<p>分析图像</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">python deploy/python/infer.py <span class="se">\
</span><span class="se"></span>         --model_dir inference_model/ppyolo_bank_human_voc <span class="se">\
</span><span class="se"></span>         --image_file demo/xxx.jpg <span class="se">\
</span><span class="se"></span>         --output_dir output/bank_human/ppyolo_bank_human_voc <span class="se">\
</span><span class="se"></span>         --use_gpu True <span class="se">\
</span><span class="se"></span>         --threshold 0.5
</code></pre></div></li>
<li>
<p>分析视频</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">python deploy/python/infer.py <span class="se">\
</span><span class="se"></span>         --model_dir inference_model/ppyolo_bank_human_voc <span class="se">\
</span><span class="se"></span>         --video_file demo/bank_human/chlorine_bottle/demo_chlorine_bottle_ch24.mp4 <span class="se">\
</span><span class="se"></span>         --output_dir output/bank_human/ppyolo_bank_human_voc <span class="se">\
</span><span class="se"></span>         --use_gpu True <span class="se">\
</span><span class="se"></span>         --threshold 0.5
</code></pre></div></li>
</ul>
<h4 id="261-服务部署">2.6.1 服务部署</h4>
<p>如果我还喘气，可能还会更新。。。</p>
]]></content>
		</item>
		
		<item>
			<title>使用 mmdectection 简单训练一个 faster-rcnn 模型</title>
			<link>https://blog.5km.studio/2020/12/11/mmdetection-train-fastercnn-coco/</link>
			<pubDate>Fri, 11 Dec 2020 14:19:33 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2020/12/11/mmdetection-train-fastercnn-coco/</guid>
			<description>今天整理下怎么简单使用 mmdetection (使用 pytorch) 训练一个 Faster-RCNN 模型，笔者使用的是一个 COCO 格式的数据集。 一、准备环境及仓库 创建开发环境 # 创建新的 conda 环境 conda create -n open-mmlab python=3.7 -y #</description>
			<content type="html"><![CDATA[<p>今天整理下怎么简单使用 <strong>mmdetection</strong> (使用 pytorch) 训练一个 Faster-RCNN 模型，笔者使用的是一个 <a href="https://cocodataset.org/">COCO</a> 格式的数据集。</p>
<h2 id="一准备环境及仓库">一、准备环境及仓库</h2>
<ol>
<li>
<p>创建开发环境</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 创建新的 conda 环境</span>
conda create -n open-mmlab <span class="nv">python</span><span class="o">=</span>3.7 -y
<span class="c1"># 激活 conda 环境</span>
conda activate open-mmlab
</code></pre></div></li>
<li>
<p>安装 pytorch</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">conda install <span class="nv">pytorch</span><span class="o">==</span>1.6.0 <span class="nv">torchvision</span><span class="o">==</span>0.7.0 <span class="nv">cudatoolkit</span><span class="o">=</span>10.1 -c pytorch -y
</code></pre></div></li>
<li>
<p>安装 mmcv-full</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">pip install https://download.openmmlab.com/mmcv/dist/latest/torch1.6.0/cu101/mmcv_full-latest%2Btorch1.6.0%2Bcu101-cp37-cp37m-manylinux1_x86_64.whl
</code></pre></div></li>
<li>
<p>clone 仓库</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">git clone https://github.com/open-mmlab/mmdetection.git
<span class="nb">cd</span> mmdetection
</code></pre></div></li>
<li>
<p>安装仓库中开发需要的依赖</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">pip install -r requirements/build.txt
pip install -v -e .  <span class="c1"># or &#34;python setup.py develop&#34;</span>
</code></pre></div></li>
</ol>
<h2 id="二准备数据集">二、准备数据集</h2>
<p>下载 coco 格式的数据集，或者自己标注制作 coco 格式的数据集，比如我们的数据集的目录是 <code>/path/to/your/dataset</code>（<strong>绝对路径</strong>），需要在 mmdetection 中建立 data 目录，在其下面建立数据集目录的软链接：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 此时在 mmdetection 目录下，建立 data 目录，如果已经存在可以省略此步</span>
mkdir data
<span class="c1"># 建立数据集软链接</span>
ln -s /path/to/your/dataset data/mydetaset
</code></pre></div><blockquote>
<p>建立软链接时使用的目录路径必须是<strong>绝对路径</strong></p>
</blockquote>
<p>看一下目录结构，这里是 coco 格式，都差不多，以我手头的一个数据集为例，目录结构如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">$ tree -L <span class="m">2</span> data/mydetaset                          
data/mydetaset
├── annotations
│   ├── test_image_info.json
│   ├── train1.json
│   ├── train2.json
│   ├── val1.json
│   └── val2.json
└── images
    ├── <span class="nb">test</span>
    ├── train1
    ├── train2
    ├── val1
    └── val2

<span class="m">7</span> directories, <span class="m">5</span> files
</code></pre></div><h2 id="三修改配置文件">三、修改配置文件</h2>
<p>这里以 <code>configs/faster_rcnn/faster_rcnn_x101_64x4d_fpn_2x_coco.py</code> 为例，直接修改配置适配训练任务。</p>
<p>打开这个配置文件发现是引用其它的配置文件，一步步的找到相应的配置文件进行修改：</p>
<ol>
<li>
<p><strong>configs/<em>base</em>/models/faster_rcnn_r50_fpn.py</strong></p>
<p>这里主要修改检测目标类型个数，比如我修改为 2 个，修改 <strong>46</strong> 行的 <code>num_classes</code> 的值为 2 即可。</p>
</li>
<li>
<p><strong>configs/<em>base</em>/schedules/schedule_2x.py</strong></p>
<p>这里我要使用一张显卡进行训练，所以这里需要调整学习率为原来的 $\frac{1}{8}$ ，修改第 2 行的  <code>lr</code> 即可，修改为 $0.025$。</p>
<blockquote>
<p>原来的值一般是针对 8 卡训练的，因为这里使用一张卡，所以这里调整为原来的 $\frac{1}{8}$</p>
</blockquote>
</li>
<li>
<p><strong>configs/<em>base</em>/datasets/coco_detection.py</strong></p>
<p>需要修改数据集相关的配置，主要是路径及取样数</p>
<ul>
<li>
<p><strong>data_root</strong>：这里要修改为上面我们创建的数据集的软链接，为 <code>data/mydetaset/</code></p>
<blockquote>
<p>这里的目录最后要带着 <code>/</code></p>
</blockquote>
</li>
<li>
<p><strong>samples_per_gpu</strong>：如果显卡显存有限，这里就调成 1 吧</p>
</li>
<li>
<p><strong>train</strong>：需要对应数据集修改<code>  img_prefix</code> 和 <code>ann_file</code>，以上边准备的数据集为例，修改标注文件和图像目录，同样需要注意最后带上 <code>/</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">train</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span>
  <span class="nb">type</span><span class="o">=</span><span class="n">dataset_type</span><span class="p">,</span>
  <span class="n">ann_file</span><span class="o">=</span><span class="n">data_root</span> <span class="o">+</span> <span class="s1">&#39;annotations/train1.json&#39;</span><span class="p">,</span>
  <span class="n">img_prefix</span><span class="o">=</span><span class="n">data_root</span> <span class="o">+</span> <span class="s1">&#39;images/train1/&#39;</span><span class="p">,</span>
  <span class="n">pipeline</span><span class="o">=</span><span class="n">train_pipeline</span><span class="p">),</span>
</code></pre></div></li>
<li>
<p><strong>val</strong>：需要对应数据集修改<code>  img_prefix</code> 和 <code>ann_file</code>，以上边准备的数据集为例，修改标注文件和图像目录，同样需要注意最后带上 <code>/</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">val</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span>
  <span class="nb">type</span><span class="o">=</span><span class="n">dataset_type</span><span class="p">,</span>
  <span class="n">ann_file</span><span class="o">=</span><span class="n">data_root</span> <span class="o">+</span> <span class="s1">&#39;annotations/val1.json&#39;</span><span class="p">,</span>
  <span class="n">img_prefix</span><span class="o">=</span><span class="n">data_root</span> <span class="o">+</span> <span class="s1">&#39;images/val1/&#39;</span><span class="p">,</span>
  <span class="n">pipeline</span><span class="o">=</span><span class="n">test_pipeline</span><span class="p">),</span>
</code></pre></div></li>
<li>
<p><strong>train</strong>：需要对应数据集修改<code>  img_prefix</code> 和 <code>ann_file</code>，以上边准备的数据集为例，修改标注文件和图像目录，同样需要注意最后带上 <code>/</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">val</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span>
  <span class="nb">type</span><span class="o">=</span><span class="n">dataset_type</span><span class="p">,</span>
  <span class="n">ann_file</span><span class="o">=</span><span class="n">data_root</span> <span class="o">+</span> <span class="s1">&#39;annotations/val1.json&#39;</span><span class="p">,</span>
  <span class="n">img_prefix</span><span class="o">=</span><span class="n">data_root</span> <span class="o">+</span> <span class="s1">&#39;images/val1/&#39;</span><span class="p">,</span>
  <span class="n">pipeline</span><span class="o">=</span><span class="n">test_pipeline</span><span class="p">),</span>
</code></pre></div></li>
</ul>
</li>
</ol>
<h2 id="四修改代码中的问题">四、修改代码中的问题</h2>
<p>加载数据集的时候，可以修改数据集加载器类中的代码，实现自动加载正确的类别信息及所有的数据，否则会无法成加载数据倒是训练失败。</p>
<p>修改文件 <code>mmdet/datasets/coco.py</code> 中初始化数据中 categories 的加载实现，很简单，只需要去掉 <code>self.coco.get_cat_ids()</code> 方法中的传参，第 <strong>58</strong> 行代码修改为：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="bp">self</span><span class="o">.</span><span class="n">cat_ids</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">coco</span><span class="o">.</span><span class="n">get_cat_ids</span><span class="p">()</span>
</code></pre></div><p>另外，保证一致性，还是修改一下 <code>self.CLASSES</code>吧，按照正确顺序写上所有类型即可，这里就不展示了。</p>
<h2 id="五训练">五、训练</h2>
<p>使用工具脚本 <code>tools/train.py</code> 即可开启一个简单的训练任务，记得指定 <strong>gpu</strong>：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">python tools/train.py configs/faster_rcnn/faster_rcnn_x101_64x4d_fpn_2x_coco.py --gpu-ids <span class="m">1</span> --work-dir output
</code></pre></div><ul>
<li><strong>configs/faster_rcnn/faster_rcnn_x101_64x4d_fpn_2x_coco.py</strong>：指定使用的配置，就是上面决定使用的那个</li>
<li><strong>&ndash;gpu-ids</strong>：指定使用 1 号卡</li>
<li><strong>&ndash;work-dir</strong>：指定模型训练日志及中间 epoch 文件或其它文件的存储目录为 <code>output</code></li>
</ul>
]]></content>
		</item>
		
		<item>
			<title>Paddle Inference 推理动态链接库 libnvinfer.so 问题</title>
			<link>https://blog.5km.studio/2020/12/03/Paddle-Inference-libnvinfer.so/</link>
			<pubDate>Thu, 03 Dec 2020 13:30:33 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2020/12/03/Paddle-Inference-libnvinfer.so/</guid>
			<description>执行推理时会提示 TensorRT 的动态链接库缺失的问题： TensorRT dynamic library (libnvinfer.so) that Paddle depends on is not configured correctly. (error code is libnvinfer.so: cannot open shared object file: No such file or directory) Suggestions: Check if TensorRT is installed correctly and its version is matched with paddlepaddle you installed. Configure TensorRT dynamic library environment variables as</description>
			<content type="html"><![CDATA[<p>执行推理时会提示 <strong>TensorRT</strong> 的动态链接库缺失的问题：</p>
<blockquote>
<p>TensorRT dynamic library (libnvinfer.so) that Paddle depends on is not configured correctly. (error code is libnvinfer.so: cannot open shared object file: No such file or directory)
Suggestions:</p>
<ol>
<li>Check if TensorRT is installed correctly and its version is matched with paddlepaddle you installed.</li>
<li>Configure TensorRT dynamic library environment variables as follows:</li>
</ol>
<ul>
<li>Linux: set LD_LIBRARY_PATH by <code>export LD_LIBRARY_PATH=...</code></li>
<li>Windows: set PATH by `set PATH=XXX;&mdash;&mdash;&mdash;&ndash;  Running Arguments &mdash;&mdash;&mdash;&ndash;</li>
</ul>
</blockquote>
<p>其实这个问题并没有影响我们模型的推理结果，强迫症不允许有任何警告，还是解决下吧！临时解决可以采用离线安装方式。</p>
<h2 id="源安装方式">源安装方式</h2>
<p>参考：<a href="https://docs.nvidia.com/deeplearning/tensorrt/install-guide/index.html#installing-debian">Install TensorRT - 4.1. Debian Installation</a></p>
<blockquote>
<p>后面有时间会稍微整理一下！</p>
</blockquote>
<h2 id="离线包安装方式">离线包安装方式</h2>
<p>这个方法比较适用于无法添加 nvidia 相关开发包仓库源的场景：</p>
<ul>
<li>不是唯一的方式，也不是最好的方式</li>
<li>而且不是很灵活，具有版本针对性，针对服务器已安装 <strong>Cuda</strong> <code>10.0</code> 的版本</li>
</ul>
<h3 id="安装相关依赖包">安装相关依赖包</h3>
<p>上面已经说了具有版本针对性是因为 <strong>Cuda</strong> 版本是<code>10.0</code>，下载的依赖包都是针对这个版本的，<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/TensorRT-debs.zip">点我</a>可下载，文件列表如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">ls -l /data/nvidia-debs/TensorRT-debs 
总用量 255M
-rw-rw-r-- <span class="m">1</span> tianye tianye  29M Sep <span class="m">19</span>  <span class="m">2018</span> cuda-cublas-10-0_10.0.130-1_amd64.deb
-rw-rw-r-- <span class="m">1</span> tianye tianye 107K Sep <span class="m">19</span>  <span class="m">2018</span> cuda-cudart-10-0_10.0.130-1_amd64.deb
-rw-rw-r-- <span class="m">1</span> tianye tianye  18K Sep <span class="m">19</span>  <span class="m">2018</span> cuda-license-10-0_10.0.130-1_amd64.deb
-rw-rw-r-- <span class="m">1</span> tianye tianye 157M Oct <span class="m">28</span>  <span class="m">2019</span> libcudnn7_7.6.5.32-1+cuda10.0_amd64.deb
-rw-r--r-- <span class="m">1</span> tianye tianye  66M Dec  <span class="m">3</span> 12:33 libnvinfer7_7.0.0-1+cuda10.0_amd64.deb
-rw-r--r-- <span class="m">1</span> tianye tianye 2.1M Dec  <span class="m">3</span> 12:33 libnvinfer-plugin7_7.0.0-1+cuda10.0_amd64.deb
-rw-r--r-- <span class="m">1</span> tianye tianye 588K Dec  <span class="m">3</span> 12:33 libnvonnxparsers7_7.0.0-1+cuda10.0_amd64.deb
-rw-r--r-- <span class="m">1</span> tianye tianye 761K Dec  <span class="m">3</span> 12:33 libnvparsers7_7.0.0-1+cuda10.0_amd64.deb
</code></pre></div><p>我这里上面 <strong>deb</strong> 包下载后都是放在 <code>/data/nvidia-debs/TensorRT-debs</code> 下的，您可以放在其它地方，安装即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">sudo dpkg -i /data/nvidia-debs/TensorRT-debs/*.deb
</code></pre></div><blockquote>
<p>上面的相关依赖包是在 nvidia 源仓库下载的，可以在官网页面中找到相应版本的依赖包，这里需要安装的只是:</p>
<ul>
<li>libnvinfer7</li>
<li>libnvinfer-plugin7</li>
<li>libnvonnxparsers7</li>
<li>libnvparsers7</li>
</ul>
<p>安装过程中发现它们依赖于其它包，所以你看到目录下的其它包都是依赖，<strong>都</strong>得安装！</p>
<p>deb 包仓库地址(进入页面按系统进入即可)：</p>
<ol>
<li><strong>cuda</strong>：https://developer.download.nvidia.com/compute/cuda/repos</li>
<li><strong>machine-learning</strong>：https://developer.download.nvidia.com/compute/machine-learning/repos</li>
</ol>
</blockquote>
<h3 id="添加动态链接库软链接">添加动态链接库软链接</h3>
<p>上面安装后的动态链接库都在<code>/usr/lib/x86_64-linux-gnu</code> 下，虽然这个目录已经在 <code>LD_LIBRARY_PATH</code> 中了，但运行推理还是找不到的，因为名字不对，找不到，所以需要创建相应的软链接，这一步很简单，因为目录下已经有相应的带版本的软链接文件了，所以复制改名即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">sudo cp /usr/lib/x86_64-linux-gnu/libcudnn.so.7 /usr/lib/x86_64-linux-gnu/libcudnn.so
sudo cp /usr/lib/x86_64-linux-gnu/libnvinfer.so.7 /usr/lib/x86_64-linux-gnu/libnvinfer.so
sudo cp /usr/lib/x86_64-linux-gnu/libnvinfer_plugin.so.7 /usr/lib/x86_64-linux-gnu/libnvinfer_plugin.so
sudo cp /usr/lib/x86_64-linux-gnu/libnvparsers.so.7 /usr/lib/x86_64-linux-gnu/libnvparsers.so
sudo cp /usr/lib/x86_64-linux-gnu/libnvcaffe_parser.so.7 /usr/lib/x86_64-linux-gnu/libnvcaffe_parser.so
</code></pre></div><p>再次推理发现就没有警告了。</p>
]]></content>
		</item>
		
		<item>
			<title>nginx 实现 vue &#43; flask 前后端分离部署</title>
			<link>https://blog.5km.studio/2020/11/28/vue-flask-deploy-with-nginx/</link>
			<pubDate>Sat, 28 Nov 2020 15:04:55 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2020/11/28/vue-flask-deploy-with-nginx/</guid>
			<description>随着项目的进展，我们发现单靠后端托管部署 vue 构建的静态文件的方式已无法满足我们的开发需求，所以此文要讲的内容应运而生，借用 nginx 简单实现 vue 和 flask 前后</description>
			<content type="html"><![CDATA[<p>随着项目的进展，我们发现单靠后端托管部署 vue 构建的静态文件的方式已无法满足我们的开发需求，所以此文要讲的内容应运而生，借用 nginx 简单实现 vue 和 flask 前后端项目的分离部署。</p>
<p>本文将以一个简单的 web 应用例子以清晰地讲述部署方式。</p>
<h2 id="准备工作">准备工作</h2>
<h3 id="安装-nginx">安装 nginx</h3>
<p>网上有很多资料参考，搜索相应系统的安装方式进行安装即可。</p>
<blockquote>
<p>ubuntu 可以借用包管理工具 <code>apt</code> 或 <code>apt-get</code> 进行安装，当然也可以使用 docker 进行启动，这里不做过多延伸。</p>
</blockquote>
<h3 id="前后端开发环境">前后端开发环境</h3>
<p>以笔者使用的机器为例，描述的版本号为机器使用的各工具版本。</p>
<ol>
<li>前端开发环境：
<ul>
<li><strong>node</strong>：<code>12.16.3</code></li>
<li><strong>yarn</strong>: <code>1.22.10</code></li>
</ul>
</li>
<li>后端开发环境：
<ul>
<li><strong>python</strong>: <code>3.6.10</code></li>
<li><strong>flask</strong>: <code>1.1.2</code></li>
</ul>
</li>
</ol>
<h3 id="demo">Demo</h3>
<p>以一个简单的 demo 讲述部署，功能很简单，前端输入两个数，发给后端，后端计算后返回前端，前端页面显示结果，即一款简单的整数加法器。</p>
<h4 id="前端工程">前端工程</h4>
<p><a href="https://gitee.com/smslit/vue-flask-deploy-demo-frontend.git">vue-flask-deploy-demo-frontend</a></p>
<h4 id="后端工程">后端工程</h4>
<p><a href="https://gitee.com/smslit/vue-flask-deploy-demo-backend.git">vue-flask-deploy-demo-backend</a></p>
<h2 id="第一次部署">第一次部署</h2>
<h3 id="部署后端服务">部署后端服务</h3>
<p>后端服务启动到 <code>10086</code> 端口，使用 <code>gunicorn</code>启动：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">gunciorn -b 127.0.0.1:10086 main:app
</code></pre></div><blockquote>
<p>不暴露后端服务，只能 <strong>localhost</strong> 进行访问！</p>
</blockquote>
<p>可以使用 <strong>systemctl</strong> 管理后端服务，需要撰写 <strong>service</strong> 文件，放置到 <code>/etc/systemd/system</code> 下，比如这里最终的文件是 <code>/etc/systemd/system/demo.service</code>，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="k">[Unit]</span>
<span class="na">Description</span> <span class="o">=</span> <span class="s">vue-flask-deploy-demo</span>
<span class="na">After</span> <span class="o">=</span> <span class="s">network.target</span>

<span class="k">[Service]</span>
<span class="na">User</span> <span class="o">=</span> <span class="s">xxx</span>
<span class="na">Group</span> <span class="o">=</span> <span class="s">xxx</span>

<span class="na">WorkingDirectory</span> <span class="o">=</span> <span class="s">/data/home/xxx/vue-flask-deploy-demo/vue-flask-deploy-demo-backend</span>
<span class="na">ExecStart</span> <span class="o">=</span> <span class="s">/data/home/xxx/.local/share/virtualenvs/vue-flask-deploy-demo-backend--ctYQkX6/bin/gunicorn -b 127.0.0.1:10086 main:app</span>
<span class="na">Type</span> <span class="o">=</span> <span class="s">simple</span>

<span class="k">[Install]</span>
<span class="na">WantedBy</span> <span class="o">=</span> <span class="s">multi-user.target</span>
</code></pre></div><ul>
<li><strong>User</strong>: 改为自己的用户名；</li>
<li><strong>Group</strong>: 改为自己的用户组名称；</li>
<li><strong>WorkingDirectory</strong>：后端项目目录，使用<strong>绝对路径</strong></li>
<li><strong>ExecStart</strong>：后端服务启动在本地 10086 的命令，其中 gunicorn 要使用<strong>绝对路径</strong></li>
</ul>
<p>重载一下服务文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">sudo systemctl daemon-reload 
</code></pre></div><p>如果需要服务器重启后自启动服务，需要：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">sudo systemctl <span class="nb">enable</span> demo.service
</code></pre></div><p>启动服务：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">sudo systemctl start demo
</code></pre></div><h3 id="构建前端项目">构建前端项目</h3>
<ol>
<li>
<p>构建前端项目</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">yarn build
</code></pre></div></li>
<li>
<p>将构建后的文件全部拷到 <code>/data/www/demo</code> 下：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 创建目录</span>
mkdir -p /data/www/demo
<span class="c1"># 拷贝构建生成的文件到目录</span>
cp -r dist/* /data/www/demo/
</code></pre></div></li>
</ol>
<blockquote>
<p>其中, <code>/data/www/demo</code> 为要放置前端静态文件的目录，根据自身情况而定！</p>
</blockquote>
<h3 id="nginx-配置文件">nginx 配置文件</h3>
<p>自定义的 nginx 配置文件可以放在路径 <code>/etc/nginx/conf.d</code> 下，所以我们第一步需要先在其下添加一个配置文件，比如 <code>demo.conf</code>，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-nginx" data-lang="nginx"><span class="k">upstream</span> <span class="s">backend</span> <span class="p">{</span>
  	<span class="c1"># 定义后端服务，这里为 10086
</span><span class="c1"></span>    <span class="kn">server</span> <span class="n">127.0.0.1</span><span class="p">:</span><span class="mi">10086</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span>  <span class="mi">20804</span><span class="p">;</span>

    <span class="c1"># 前端项目build dist 文件部署
</span><span class="c1"></span>		<span class="kn">location</span> <span class="s">/</span> <span class="p">{</span>
        <span class="c1"># 1. 前端项目构建出的文件放置位置
</span><span class="c1"></span>        <span class="kn">root</span> <span class="s">/data/www/demo</span><span class="p">;</span>
        <span class="c1"># 2. 解决 vue 采用 history 模式时前端路由页面刷新 404 的错误
</span><span class="c1"></span>        <span class="kn">try_files</span> <span class="nv">$uri</span> <span class="nv">$uri/</span> <span class="s">/index.html</span> <span class="s">last</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1"># 3. 后端 api/static文件代理，没有后端可忽略这个
</span><span class="c1"></span>    <span class="kn">location</span> <span class="p">~</span><span class="sr">/(api|static)/</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://backend</span><span class="p">;</span>
        <span class="c1"># 4. 增大数据体大小限制，防止 413 错误；
</span><span class="c1"></span>        <span class="kn">client_max_body_size</span> <span class="mi">1024m</span><span class="p">;</span>
    <span class="p">}</span>

	<span class="c1"># 5. IP 白名单
</span><span class="c1"></span>    <span class="c1"># - 允许局域网段 192.168.xxx.xxx 机器访问
</span><span class="c1"></span>    <span class="c1"># allow 192.168.0.0/16;
</span><span class="c1"></span>    <span class="c1"># deny all;
</span><span class="c1"></span><span class="p">}</span>
</code></pre></div><p>这里解释一下配置：</p>
<ol>
<li>其中 <strong>root</strong> 后的内容指定前端项目的构建文件存放目录，比如这里为 <code>/data/www/demo</code></li>
<li>解决前端页面在某一个路由下人为进行页面刷新导致 <strong>404</strong> 错误的问题</li>
<li>后端 API 转发代理</li>
<li>nginx 数据体大小限制默认为 <code>2MB</code>，这里我们修改为 <code>1GB</code>，根据情况修改</li>
<li>配置 IP 白名单可以进行访问限制</li>
</ol>
<blockquote>
<p>这里这样配置的目标有<strong>两个</strong>：</p>
<ul>
<li>对外只暴露一个端口即 20804</li>
<li>前后端项目的分离部署，包括静态文件目录</li>
</ul>
</blockquote>
<h3 id="nginx-重新加载配置">nginx 重新加载配置</h3>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">sudo nginx -s reload
</code></pre></div><p>此时，我们的项目就可以访问了！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/demo-20201128151942.png" alt=""></p>
<h2 id="项目升级部署">项目升级部署</h2>
<p>后续的开发中，会经常进行升级，就非常简单了。</p>
<h3 id="前端项目">前端项目</h3>
<p>只需在本机进行构建，然后把文件覆盖到服务器的前端文件目录即可</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 构建项目</span>
yarn build
<span class="c1"># 上传文件</span>
scp -r -P <span class="m">20800</span> dist/* xxx@xxx.xxx.xxx.xxx:/data/www/demo/
</code></pre></div><blockquote>
<ul>
<li>scp 命令将构建的前端文件拷贝到服务器的部署目录下。</li>
<li>前端项目更新升级，不再需要后端重启服务</li>
</ul>
</blockquote>
<h3 id="后端项目">后端项目</h3>
<p>服务器拉取部署分支（develop 或 master）最新代码，然后重启服务即可。</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 拉取最新代码</span>
git pull
<span class="c1"># 重启后端服务</span>
sudo systemctl restart demo
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>同域名下不同端口的 web 服务 session 冲突导致用户登录互顶的问题</title>
			<link>https://blog.5km.studio/2020/10/24/same-domain-different-port-session-conflict/</link>
			<pubDate>Sat, 24 Oct 2020 10:59:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2020/10/24/same-domain-different-port-session-conflict/</guid>
			<description>好久没更新文章了，就先从整了个开发中遇到的小问题再开始吧！ 日常开发中经常会在同域名或 IP 下在不同端口部署不同的 web 应用服务，但如果恰好每个服务都</description>
			<content type="html"><![CDATA[<p>好久没更新文章了，就先从整了个开发中遇到的小问题再开始吧！</p>
<p>日常开发中经常会在同域名或 IP 下在不同端口部署不同的 web 应用服务，但如果恰好每个服务都有用户登录功能，且采用 session 的安全认证方式，就会出现两个应用登录信息互顶的问题，具体表现为：已经登录了 A 应用，结果再去登录 B 应用，回头去 A 应用的页面发现登录信息失效了，本文就看一下这个问题。</p>
<!-- more -->
<h2 id="说明">说明</h2>
<p>web 项目使用基于 python 的 web 框架 flask 实现。</p>
<h2 id="探索">探索</h2>
<h3 id="复现问题">复现问题</h3>
<p>我以自己的项目为例：</p>
<ul>
<li><strong>应用 A</strong>：localhost:20702</li>
<li><strong>应用 B</strong>：localhost:20709</li>
</ul>
<p>首先打开项目 A 的网页并登录，然后打开浏览器的网页检查器看一下，cookie 信息中 session id 叫 <strong>session</strong>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/%E6%88%AA%E5%B1%8F2020-10-24%2011.42.39_%E5%89%AF%E6%9C%AC-20201024131254.jpg" alt=""></p>
<div class="highlight"><pre class="chroma"><code class="language-text" data-lang="text">session:&#34;eyJ1c2VybmFtZSI6InRpYW55ZTEifQ.X5OddA.Tt8ayJhPp8KHcBdzRzU72I6U-SI&#34;
</code></pre></div><p>同样，再打开项目 B 的网页并登录，然后同样打开浏览器网页检查器看一下 cookie 信息，也有一个 session id 为 <strong>session</strong> 的信息：</p>
<div class="highlight"><pre class="chroma"><code class="language-text" data-lang="text">session:&#34;eyJ1c2VybmFtZSI6InRpYW55ZSJ9.X5OaxQ.IltnUYo4-bO9JcrNxBJYPc1hxWs&#34;
</code></pre></div><p>回过头去看项目 A 的网页发现，原来的 cookie 信息变了，还是有 session 的信息，但是值变了：</p>
<div class="highlight"><pre class="chroma"><code class="language-text" data-lang="text">session:&#34;eyJ1c2VybmFtZSI6InRpYW55ZSJ9.X5OexQ.OC7TtbfCLfuUtwsS0D9nbK6mh5w&#34;
</code></pre></div><p>导致项目 A 的页面登录失效了。</p>
<h3 id="排查原因">排查原因</h3>
<p>上面在检查器中可以明确看到 session 信息，cookie 中仅仅出现的是域名 localhost 下的一个 session 信息，id 为 <strong>session</strong>，怎么回事？两个项目共用了一个 session 信息。</p>
<p>经过了解知道：</p>
<blockquote>
<p>对于浏览器来说，cookie 不区分端口，因此对于同一个域名，它的所有端口都是共享 cookie</p>
</blockquote>
<h2 id="解决">解决</h2>
<p>所以解决方法很简单，修改后端默认的 session id 名称为各自项目的，只要不相同就可以了。可以在 Flask 应用的配置中修改配置项 <code>SESSION_COOKIE_NAME</code>，其默认为 <code>session</code>，因为两个项目的均没有配置这个，都使用 <strong>session</strong> 作为了 session id，造成了冲突，我们添加这个配置项改为相关的应用名称即可，比如我的 Flask 项目中可以通过以下多种方式修改此配置：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="c1"># app 为 Flask 实例</span>
<span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="s2">&#34;SESSION_COOKIE_NAME&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;session-demo1&#34;</span>
</code></pre></div><p>或者</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="c1"># 单独定义配置对象</span>
<span class="k">class</span> <span class="nc">ProductionConfig</span><span class="p">():</span>
    <span class="n">SESSION_COOKIE_NAME</span> <span class="o">=</span> <span class="s2">&#34;session-demo1&#34;</span>

<span class="c1"># 从对象读入配置内容，app 为 Flask 实例</span>
<span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">from_object</span><span class="p">(</span><span class="n">ProductionConfig</span><span class="p">)</span>
</code></pre></div><p>两个项目各自修改 <code>SESSION_COOKIE_NAME</code> 为不同值之后，即可解决 session 冲突问题了！Nice！</p>
<blockquote>
<p>如果使用其它语言或 web 框架，一定可以找到这个 session cookie name 的配置项！</p>
</blockquote>
<p>希望能帮到您！</p>
]]></content>
		</item>
		
		<item>
			<title>python3.6 的 Ubuntu16.04 Docker 镜像制作</title>
			<link>https://blog.5km.studio/2020/05/12/docker-ubuntu16.04-python3.6/</link>
			<pubDate>Tue, 12 May 2020 13:32:23 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2020/05/12/docker-ubuntu16.04-python3.6/</guid>
			<description>出于某些原因，公司使用 ubuntu16.04(xenial) 和 python3.6，为了方便部署写的 web 应用或 AI 分析服务，尤其是某些情况下的离线部署，有必要搞个 Docker 镜像。因为公司使用</description>
			<content type="html"><![CDATA[<p>出于某些原因，公司使用 <strong>ubuntu16.04(xenial)</strong> 和 <strong>python3.6</strong>，为了方便部署写的 web 应用或 AI 分析服务，尤其是某些情况下的离线部署，有必要搞个 Docker 镜像。因为公司使用环境大部分类似，有必要先弄一个满足基本需求的镜像，后续再定制相应环境镜像即可。</p>
<p>本文主要讲一下制作这个基础镜像的过程，灰常简单。</p>
<h2 id="需求">需求</h2>
<p>先理一下我们的需求：</p>
<ul>
<li>ubuntu16.04(xenial)，apt-get 使用国内源，安装基本的工具</li>
<li>最新的 python3.6，目前最新的为 python3.6</li>
<li>最新的 pip3，而且要更改配置为国内的 pypi 源</li>
</ul>
<h2 id="准备工作">准备工作</h2>
<p>我们使用 Dockerfile 制作镜像，这里需要准备一些文件，所以先创建个目录，在此目录下进行操作，比如目录是 <code>docker_ubuntu_python3.6</code></p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">mkdir docker_ubuntu_python3.6
<span class="c1"># 进入目录</span>
<span class="nb">cd</span> docker_ubuntu_python3.6
</code></pre></div><p>此目录下后面我们需要创建三个文件，最好是不要包含其它文件：</p>
<ul>
<li>sources.list</li>
<li>pip.conf</li>
<li>Dockerfile</li>
</ul>
<p><strong>注：</strong></p>
<p>这个目录不要再有其它文件，否则还要将多余的文件加到 .dockerignore</p>
<h3 id="sourceslist">sources.list</h3>
<p>为更改镜像中的 apt-get 源为国内源而准备。</p>
<p>新建文件 <strong>sources.list</strong>，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">deb-src http://archive.ubuntu.com/ubuntu xenial main restricted <span class="c1">#Added by software-properties</span>
deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted multiverse universe <span class="c1">#Added by software-properties</span>
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted multiverse universe <span class="c1">#Added by software-properties</span>
deb http://mirrors.aliyun.com/ubuntu/ xenial universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe
deb http://mirrors.aliyun.com/ubuntu/ xenial multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse <span class="c1">#Added by software-properties</span>
deb http://archive.canonical.com/ubuntu xenial partner
deb-src http://archive.canonical.com/ubuntu xenial partner
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted multiverse universe <span class="c1">#Added by software-properties</span>
deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-security multiverse
</code></pre></div><p>这里使用的是阿里云 ubuntu16.04 的源，可选择其它源。</p>
<h3 id="pipconf">pip.conf</h3>
<p>为加速 pip 安装第三方依赖的速度而准备。</p>
<p>创建文件 <strong>pip.conf</strong>，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="k">[global]</span>
<span class="na">index-url</span> <span class="o">=</span> <span class="s">https://pypi.tuna.tsinghua.edu.cn/simple</span>
<span class="k">[install]</span>
<span class="na">trusted-host</span><span class="o">=</span><span class="s">pypi.tuna.tsinghua.edu.cn</span>
<span class="na">disable-pip-version-check</span> <span class="o">=</span> <span class="s">true</span>
<span class="na">timeout</span> <span class="o">=</span> <span class="s">6000</span>
</code></pre></div><p>这里使用的清华的 pypi 源，可选择使用其它源！</p>
<h3 id="dockerfile">Dockerfile</h3>
<p>最后是制作 Docker 镜像的  Dokerfile，新建文件，名为 <strong>Dockerfile</strong>，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="k">FROM</span><span class="s"> ubuntu:xenial AS ubuntu16_04-with-python3_6</span><span class="err">
</span><span class="err"></span><span class="k">LABEL</span> <span class="nv">author</span><span class="o">=</span><span class="s2">&#34;5km&#34;</span><span class="err">
</span><span class="err"></span><span class="c"># 用ubuntu国内源替换默认源</span><span class="err">
</span><span class="err"></span><span class="k">RUN</span> rm /etc/apt/sources.list<span class="err">
</span><span class="err"></span><span class="k">COPY</span> sources.list /etc/apt/sources.list<span class="err">
</span><span class="err"></span><span class="c"># 安装python3.6必要的包。源镜像太精简了，ip ifconfig之类的都没有。后续安装python pip也需要一些。但是build_essential似乎不必须，先去了。如果后面安装numpy之类需要gcc了，再加上</span><span class="err">
</span><span class="err"></span><span class="k">RUN</span> apt-get update <span class="se">\
</span><span class="se"></span>    <span class="o">&amp;&amp;</span> apt-get install -y libapt-pkg5.0 apt-transport-https iproute2 net-tools ca-certificates curl wget software-properties-common <span class="se">\
</span><span class="se"></span>    <span class="o">&amp;&amp;</span> apt-get clean<span class="err">
</span><span class="err"></span><span class="c"># 安装python3.6 来自第三方</span><span class="err">
</span><span class="err"></span><span class="k">RUN</span> add-apt-repository ppa:deadsnakes/ppa <span class="se">\
</span><span class="se"></span>    <span class="o">&amp;&amp;</span> apt-get update <span class="se">\
</span><span class="se"></span>    <span class="o">&amp;&amp;</span> apt-get install -y python3.6 python3.6-dev python3-pip <span class="se">\
</span><span class="se"></span>    <span class="o">&amp;&amp;</span> apt-get clean<span class="err">
</span><span class="err"></span><span class="c"># 和自带的3.5共存</span><span class="err">
</span><span class="err"></span><span class="k">RUN</span> update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.5 <span class="m">1</span> <span class="se">\
</span><span class="se"></span>    <span class="o">&amp;&amp;</span> update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 <span class="m">2</span> <span class="se">\
</span><span class="se"></span>    <span class="o">&amp;&amp;</span> update-alternatives --config python3<span class="err">
</span><span class="err"></span><span class="c"># 更改 pip 源为清华源，加速</span><span class="err">
</span><span class="err"></span><span class="k">RUN</span> mkdir /root/.pip<span class="err">
</span><span class="err"></span><span class="k">COPY</span> pip.conf /root/.pip/pip.conf<span class="err">
</span><span class="err"></span><span class="c"># 更新 pip3</span><span class="err">
</span><span class="err"></span><span class="k">RUN</span> pip3 install --upgrade pip <span class="se">\
</span><span class="se"></span>    <span class="o">&amp;&amp;</span> rm -rf /root/.cache/pip<span class="err">
</span><span class="err"></span><span class="c">#print()时在控制台正常显示中文</span><span class="err">
</span><span class="err"></span><span class="k">ENV</span> <span class="nv">PYTHONIOENCODING</span><span class="o">=</span>utf-8<span class="err">
</span></code></pre></div><p>内容中有注释，所以很容易明白过程！！！</p>
<p>准备工作大功告成，看一下当前目录下的文件结构：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">irecog@irecog <span class="o">[</span>01:59:36 PM<span class="o">]</span> <span class="o">[</span>~/docker_ubuntu_python3.6<span class="o">]</span>
-&gt; % tree .
.
├── Dockerfile
├── pip.conf
└── sources.list

<span class="m">0</span> directories, <span class="m">3</span> files
</code></pre></div><h2 id="制作镜像">制作镜像</h2>
<p>一条命令，完成制作：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">docker build -t ubuntu16_04-with-python3_6 .
</code></pre></div><p>理论上不到一杯咖啡过后，就能看到制作完成的镜像：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">irecog@irecog <span class="o">[</span>02:00:40 PM<span class="o">]</span> <span class="o">[</span>~/docker_ubuntu_python3.6<span class="o">]</span>
-&gt; % docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             SIZE
ubuntu16_04-with-python3_6   latest              7883d49a0394        <span class="m">47</span> minutes ago      623MB
</code></pre></div><h2 id="过程回顾">过程回顾</h2>
<p>下面是整个操作过程的 asciinema 录制，可参考录制进行操作：</p>
<script id="asciicast-329370" src="https://asciinema.org/a/329370.js?speed=3&autoplay=1" async></script>
<p>如果上面过程录制加载很慢，可自行安装 <a href="https://asciinema.org/">asciinema</a> ，使用 asciinema 播放 <a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/docker-ubuntu1604-python36-20200512143204.json">docker-ubuntu1604-python36.json</a></p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">asciinema play docker-ubuntu1604-python36.json
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>博客现已支持 Asciinema</title>
			<link>https://blog.5km.studio/2020/05/01/asciinema/</link>
			<pubDate>Fri, 01 May 2020 08:10:24 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2020/05/01/asciinema/</guid>
			<description>前几天好朋友分享给我一个工具，叫 asciinema。经了解，发现这真是个好东西！ 这玩意儿能记录在终端的命令操作过程，最终记录文件并不是视频文</description>
			<content type="html"><![CDATA[<p>前几天好朋友分享给我一个工具，叫 <a href="https://asciinema.org">asciinema</a>。经了解，发现这真是个好东西！</p>
<p>这玩意儿能记录在终端的命令操作过程，最终记录文件并不是视频文件，而是一个 json 文件。</p>
<p>工具除了录制还能播放此文件，就是解析这个 json 文件中的内容，里面记录了每个关键时刻的 <code>stdin</code> 和 <code>stdout</code>，所以在播放的时候其实就是输入输出的过程，当然没这么简单，里面还记录了终端的一些信息，比如终端的行数和列数、颜色等信息。</p>
<p>所以，播放记录的时候还可以复制粘贴里面的内容。这东西太适合做命令行操作分享的时候使用了，所以又深入研究了下。</p>
<p>官方提供了播放记录文件的 WEB 支持，只需一个 js 文件和 css 文件，使用标签 <code>asciinema-player</code> 就能在 HTML 中展示记录文件了，而且还能换主题。花了几分钟，在自己的博客中简单添加了 asciinema 的支持！</p>
<p><strong>BUT！BUT！BUT！</strong> <strong>@yyrcd</strong> 提醒下，发现js 文件竟然与评论系统 valine 的 js 不兼容，如果加载了 <code>asciinema-player.js</code> 评论就不能正常加载显示了，本来想着可以自己本地管理要分享的录制，那还是使用 asciinema 官网管理吧，官方提供了 script 嵌入方式的分享途径，也能达到自己想要的效果，且不会造成不兼容的现象，就是加载的会慢一点，就先这样吧！</p>
<p>以后分享命令行操作就用这玩意儿了，舒服～</p>
<p>比如我要在本页展示官方的 demo ，就可以页面中加入：</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;asciicast-326538&#34;</span> <span class="na">loop</span><span class="o">=</span><span class="s">&#34;loop&#34;</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://asciinema.org/a/326538.js&#34;</span> <span class="na">async</span> <span class="na">data-speed</span><span class="o">=</span><span class="s">&#34;3&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</code></pre></div><p>效果如下：</p>
<script id="asciicast-326538" loop="loop" src="https://asciinema.org/a/326538.js" async data-speed="3"></script>
]]></content>
		</item>
		
		<item>
			<title>使用 pyenv 和 pipenv 管理 python版本及虚拟环境</title>
			<link>https://blog.5km.studio/2020/02/21/pyenv_pipenv/</link>
			<pubDate>Fri, 21 Feb 2020 15:57:16 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2020/02/21/pyenv_pipenv/</guid>
			<description>如果你已经无法容忍机器上杂乱的 python 版本，pyenv 是一味良药，可以尝一下，辅以 pipenv 管理虚拟环境疗效极佳，让“洁癖”的你身心健康！ 本文适用于 macOS 下的</description>
			<content type="html"><![CDATA[<p>如果你已经无法容忍机器上杂乱的 python 版本，<strong>pyenv</strong> 是一味良药，可以尝一下，辅以 pipenv 管理虚拟环境疗效极佳，让“洁癖”的你身心健康！</p>
<p>本文适用于 macOS 下的 python 开发。</p>
<h2 id="pyenv">pyenv</h2>
<p>pyenv 可以用来管理不同版本的 python</p>
<h3 id="安装">安装</h3>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">brew install pyenv
</code></pre></div><h3 id="配置">配置</h3>
<p>在使用的 shell 配置文件中加 pyenv 相关内容</p>
<ul>
<li>使用 bash 的话，就编辑 <code>.bashrc</code> 文件</li>
<li>使用 zsh 的话，编辑 <code>.zshrc</code> 文件</li>
</ul>
<p>配置内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="nb">export</span> <span class="nv">PYENV_ROOT</span><span class="o">=</span>~/.pyenv
<span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="nv">$PYENV_ROOT</span>/shims:<span class="nv">$PATH</span>
<span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span>pyenv init -<span class="k">)</span><span class="s2">&#34;</span>
</code></pre></div><h3 id="安装-1">安装</h3>
<h4 id="查看-python-可用版本">查看 python 可用版本</h4>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">pyenv install --list
</code></pre></div><h4 id="安装某个版本">安装某个版本</h4>
<p>比如安装 3.6.8</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">pyenv install 3.6.8
</code></pre></div><p>会发现极慢，此时可以自己到 python 官网下载相应版本的 tar.xz 包，比如这里就下载 <code>Python-3.6.8.tar.xz</code>，然后放到用户目录下的 <code>.pyenv/cache</code> 下，如果目录不存在就自己创建，pyenv 安装就会先下载源码包到这个目录。</p>
<h3 id="卸载">卸载</h3>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">pyenv uninstall 3.6.8
</code></pre></div><h3 id="查看已安装的版本">查看已安装的版本</h3>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">pyenv versions
</code></pre></div><p>这个命令同时也会查看当前使用的版本。</p>
<h3 id="切换-python-版本">切换 python 版本</h3>
<h4 id="全局">全局</h4>
<p>作用域在全局</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">pyenv global 3.6.8
</code></pre></div><h4 id="局部">局部</h4>
<p>作用域在一个工作目录</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">pyenv <span class="nb">local</span> 3.6.8
</code></pre></div><p>进入某个目录，执行上面的命令后，每当进入这个目录就会自动切换 python 为指定的版本。</p>
<p><code>which python</code> 可以查看 python 的目录是：<code>~/.pyenv/shims/</code></p>
<h2 id="pipenv">pipenv</h2>
<p>可以参考博文 <a href="https://www.smslit.top/2018/10/18/pipenv/">一起使用 pipenv</a></p>
<h2 id="虚拟环境">虚拟环境</h2>
<p>某个工程使用 <code>3.6.8</code> 的 python：</p>
<ol>
<li>进入工程目录 <code>cd project1</code></li>
<li>切换 python 版本 <code>pyenv local 3.6.8</code></li>
<li>创建虚拟环境 <code>pipenv --python /Users/5km/.pyenv/shims/python</code></li>
<li>安装依赖 <code>pipenv install --skip-lock</code></li>
</ol>
]]></content>
		</item>
		
		<item>
			<title>Ant Design Vue 工程配置笔记</title>
			<link>https://blog.5km.studio/2019/11/11/ant-design-vue-note-start/</link>
			<pubDate>Mon, 11 Nov 2019 11:04:25 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/11/11/ant-design-vue-note-start/</guid>
			<description>工程配置 创建工程 # 安装 Vue # npm install -g @vue/cli $ vue creat antd-demo 排除 vscode 配置忽略 vscode 打开上面创建的工程，修改 .gitignore 文件内容为： .DS_Store node_modules /dist # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* # Editor directories and files .idea -</description>
			<content type="html"><![CDATA[<h2 id="工程配置">工程配置</h2>
<h3 id="创建工程">创建工程</h3>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell"><span class="c1"># 安装 Vue</span>
<span class="c1"># npm install -g @vue/cli</span>
$ vue creat antd-demo
</code></pre></div><h3 id="排除-vscode-配置忽略">排除 vscode 配置忽略</h3>
<p>vscode 打开上面创建的工程，修改 <code>.gitignore</code> 文件内容为：</p>
<div class="highlight"><pre class="chroma"><code class="language-diff" data-lang="diff">.DS_Store
node_modules
/dist

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
<span class="gd">- .vscode
</span><span class="gd"></span><span class="gi">+ .vscode/*
</span><span class="gi"></span>*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

<span class="gi">+ !.vscode/settings.json
</span></code></pre></div><h3 id="修改-vscode-工程配置">修改 vscode 工程配置</h3>
<p>修改文件 <code>.vscode/settings.json</code> ，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-json" data-lang="json"><span class="p">{</span>
  <span class="err">//</span> <span class="err">vscode默认启用了根据文件类型自动设置tabsize的选项</span>
  <span class="nt">&#34;editor.detectIndentation&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
  <span class="err">//</span> <span class="err">重新设定tabsize</span>
  <span class="nt">&#34;editor.tabSize&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
  <span class="err">//</span> <span class="err">#每次保存的时候自动格式化</span>
  <span class="nt">&#34;editor.formatOnSave&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="err">//</span> <span class="err">#每次保存的时候将代码按eslint格式进行修复</span>
  <span class="nt">&#34;eslint.autoFixOnSave&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="err">//</span> <span class="err">添加</span> <span class="err">vue</span> <span class="err">支持</span>
  <span class="nt">&#34;eslint.validate&#34;</span><span class="p">:</span> <span class="p">[</span>
    <span class="s2">&#34;javascript&#34;</span><span class="p">,</span>
    <span class="s2">&#34;javascriptreact&#34;</span><span class="p">,</span>
    <span class="p">{</span>
      <span class="nt">&#34;language&#34;</span><span class="p">:</span> <span class="s2">&#34;vue&#34;</span><span class="p">,</span>
      <span class="nt">&#34;autoFix&#34;</span><span class="p">:</span> <span class="kc">true</span>
    <span class="p">}</span>
  <span class="p">],</span>
  <span class="err">//</span> <span class="err">#让prettier使用eslint的代码格式进行校验</span>
  <span class="nt">&#34;prettier.eslintIntegration&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="err">//</span> <span class="err">#去掉代码结尾的分号</span>
  <span class="nt">&#34;prettier.semi&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
  <span class="err">//</span> <span class="err">#使用带引号替代双引号</span>
  <span class="nt">&#34;prettier.singleQuote&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="err">//</span> <span class="err">#让函数(名)和后面的括号之间加个空格</span>
  <span class="nt">&#34;javascript.format.insertSpaceBeforeFunctionParenthesis&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="err">//</span> <span class="err">#这个按用户自身习惯选择</span>
  <span class="nt">&#34;vetur.format.defaultFormatter.html&#34;</span><span class="p">:</span> <span class="s2">&#34;js-beautify-html&#34;</span><span class="p">,</span>
  <span class="err">//</span> <span class="err">#让vue中的js按编辑器自带的ts格式进行格式化</span>
  <span class="nt">&#34;vetur.format.defaultFormatter.js&#34;</span><span class="p">:</span> <span class="s2">&#34;vscode-typescript&#34;</span><span class="p">,</span>
  <span class="nt">&#34;vetur.format.defaultFormatterOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
    <span class="nt">&#34;js-beautify-html&#34;</span><span class="p">:</span> <span class="p">{</span>
      <span class="nt">&#34;wrap_attributes&#34;</span><span class="p">:</span> <span class="s2">&#34;force-aligned&#34;</span>
      <span class="err">//</span> <span class="err">#vue组件中html代码格式化样式</span>
    <span class="p">}</span>
  <span class="p">},</span>
<span class="p">}</span>
</code></pre></div><h3 id="安装-ant-design-vue">安装 Ant Design Vue</h3>
<p>从 yarn 或 npm 安装并引入 ant-design-vue。</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">$ yarn add ant-design-vue
</code></pre></div><h3 id="安装-antd-导入插件">安装 antd 导入插件</h3>
<p><a href="https://github.com/ant-design/babel-plugin-import">babel-plugin-import</a> 是一个用于按需加载组件代码和样式的 babel 插件（<a href="https://www.antdv.com/docs/vue/getting-started-cn/#%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD">原理</a>）。</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">$ yarn add babel-plugin-import --dev
</code></pre></div><p>修改<code>babel.config.js</code>文件，配置 babel-plugin-import</p>
<div class="highlight"><pre class="chroma"><code class="language-diff" data-lang="diff"> module.exports = {
  presets: [&#34;@vue/app&#34;],
<span class="gi">+  plugins: [
</span><span class="gi">+    [
</span><span class="gi">+      &#34;import&#34;,
</span><span class="gi">+      { libraryName: &#34;ant-design-vue&#34;, libraryDirectory: &#34;es&#34;, style: true }
</span><span class="gi">+    ]
</span><span class="gi">+  ]
</span><span class="gi"></span>};
</code></pre></div><h2 id="简单测试">简单测试</h2>
<p>修改 <code>main.js</code> 文件如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-diff" data-lang="diff">  import Vue from &#39;vue&#39;
<span class="gi">+ import { Button } from &#39;ant-design-vue&#39;;
</span><span class="gi"></span>  import App from &#39;./App&#39;

<span class="gi">+ Vue.component(Button.name, Button)
</span><span class="gi"></span>
  Vue.config.productionTip = false

  new Vue({
    render: h =&gt; h(App)
  }).$mount(&#34;#app&#34;);
</code></pre></div><p>修改 <code>src/App.vue</code>的 template 内容。</p>
<div class="highlight"><pre class="chroma"><code class="language-diff" data-lang="diff">&lt;template&gt;
  &lt;div id=&#34;app&#34;&gt;
    &lt;img src=&#34;./assets/logo.png&#34;&gt;
<span class="gd">-    &lt;HelloWorld msg=&#34;Welcome to Your Vue.js App&#34;/&gt;
</span><span class="gd"></span><span class="gi">+    &lt;a-button type=&#34;primary&#34;&gt;Button&gt;&lt;/a-button&gt;
</span><span class="gi"></span>  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
  
<span class="gd">- import HelloWorld from &#39;./components/HelloWorld.vue&#39;
</span><span class="gd"></span>
export default {
  name: &#39;app&#39;,
  components: {
<span class="gd">-     HelloWorld
</span><span class="gd"></span>  }
}
&lt;/script&gt;
...
</code></pre></div><p>此时运行 <code>yarn serve</code> 会出现错误。</p>
<h3 id="core-js-错误">core-js 错误</h3>
<p>出现如下错误提示：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">yarn serve                                         
yarn run v1.19.1
$ vue-cli-service serve
 INFO  Starting development server...
98% after emitting CopyPlugin

 ERROR  Failed to compile with <span class="m">1</span> errors                                                                                                              10:34:18 AM

This dependency was not found:

* core-js/modules/es.function.name in ./src/main.js

To install it, you can run: npm install --save core-js/modules/es.function.name
</code></pre></div><p>解决：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">$ yarn add core-js
</code></pre></div><h3 id="less-loader-错误">less-loader 错误</h3>
<p>再次运行仍然有错误：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">yarn serve
yarn run v1.19.1
$ vue-cli-service serve
 INFO  Starting development server...
98% after emitting CopyPlugin

 ERROR  Failed to compile with <span class="m">2</span> errors                                                                                                              10:36:34 AM

Failed to resolve loader: less-loader
You may need to install it.
Failed to resolve loader: less-loader
You may need to install it.
</code></pre></div><h4 id="解决">解决</h4>
<h5 id="安装-less-和-less-loader">安装 less 和 less-loader</h5>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">$ npm i less less-loader --save-dev
</code></pre></div><p>添加配置文件 <code>vue.config.js</code> ，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-jsx" data-lang="jsx"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
  <span class="nx">css</span><span class="o">:</span> <span class="p">{</span>
    <span class="nx">loaderOptions</span><span class="o">:</span> <span class="p">{</span>
      <span class="nx">less</span><span class="o">:</span> <span class="p">{</span>
        <span class="nx">javascriptEnabled</span><span class="o">:</span> <span class="kc">true</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>此时运行，正常的话就可以查看页面了。</p>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之状态栏小工具分别响应鼠标左右键单击</title>
			<link>https://blog.5km.studio/2019/10/24/macOS-dev-menu-mouse/</link>
			<pubDate>Thu, 24 Oct 2019 07:30:12 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/10/24/macOS-dev-menu-mouse/</guid>
			<description>上一篇文章 macOS 开发之菜单栏形式的状态栏小工具 讲述了如何开发菜单栏形式的状态栏小工具，大家可能已经发现，我们无论使用鼠标左键还是右键单击菜单栏图</description>
			<content type="html"><![CDATA[<p>上一篇文章 <a href="/2019/10/22/macOS-dev-basic-nsmenu/">macOS 开发之菜单栏形式的状态栏小工具</a> 讲述了如何开发菜单栏形式的状态栏小工具，大家可能已经发现，我们无论使用鼠标左键还是右键单击菜单栏图标，都会弹出我们添加的菜单，考虑到我们可能会需要分别对鼠标左右键单击菜单栏按钮进行不同的处理，所以写此文讲解一下如何实现。</p>
<!-- more -->
<h2 id="工程">工程</h2>
<p>我们继续使用上一篇文章最后完成的工程：</p>
<p><a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022195552-l6UN33.zip">MenuToolDemo</a></p>
<h2 id="探索">探索</h2>
<p>为了展示左键和右键点击处理的区别，指定不同的行为：</p>
<ul>
<li>左键单击：显示一个提示窗口</li>
<li>右键单击：弹出之前的菜单</li>
</ul>
<p>我们第一反应就是，为按钮设置点击事件直接分开处理就可以了，我们试一下。</p>
<ol>
<li>
<p>打开文件 <code>AppDelegate.swift</code>，修改之前工程中指定 button 图标的代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="k">if</span> <span class="kd">let</span> <span class="nv">button</span> <span class="p">=</span> <span class="n">statusItem</span><span class="p">.</span><span class="n">button</span> <span class="p">{</span>
    <span class="n">button</span><span class="p">.</span><span class="n">image</span> <span class="p">=</span> <span class="n">NSImage</span><span class="p">(</span><span class="n">named</span><span class="p">:</span> <span class="s">&#34;StatusIcon&#34;</span><span class="p">)</span>
    <span class="n">button</span><span class="p">.</span><span class="n">action</span> <span class="p">=</span> <span class="k">#selector</span><span class="p">(</span><span class="n">mouseClickHandler</span><span class="p">)</span>
    <span class="n">button</span><span class="p">.</span><span class="n">sendAction</span><span class="p">(</span><span class="n">on</span><span class="p">:</span> <span class="p">[.</span><span class="n">leftMouseUp</span><span class="p">,</span> <span class="p">.</span><span class="n">rightMouseUp</span><span class="p">])</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>在方法 <code>applicationDidFinishLaunching(_ aNotification:)</code> 后面添加鼠标点击处理方法 <code>mouseClickHandler</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">mouseClickHandler</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="kd">let</span> <span class="nv">event</span> <span class="p">=</span> <span class="n">NSApp</span><span class="p">.</span><span class="n">currentEvent</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="n">event</span><span class="p">.</span><span class="n">type</span> <span class="p">{</span>
        <span class="k">case</span> <span class="p">.</span><span class="n">leftMouseUp</span><span class="p">:</span>
            <span class="bp">print</span><span class="p">(</span><span class="s">&#34;左键&#34;</span><span class="p">)</span>
        <span class="k">default</span><span class="p">:</span>
            <span class="bp">print</span><span class="p">(</span><span class="s">&#34;右键&#34;</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></li>
</ol>
<p>运行程序，是不是发现，左键和右键单击还都是显示菜单，而没有打印出 <strong>左键</strong> 和 <strong>右键</strong>，这是为什么呢？</p>
<p>我们尝试把下面的代码注释掉：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="n">statusItem</span><span class="p">.</span><span class="n">menu</span> <span class="p">=</span> <span class="n">menu</span>
</code></pre></div><p>再次运行程序，Amazing！是不是左键单击就打印 <strong>左键</strong> 右键单击就打印出 <strong>右键</strong>！所以我们能想到的原因就是<strong>当指定 <code>menu</code> 后，系统会忽略按钮的事件设置，都只是弹出菜单</strong>。</p>
<h2 id="思路">思路</h2>
<p>有了上面的探索，是不是就有思路了！目标中右键还是要显示菜单的，所以我们可以在右键单击的时候指定 <strong>menu</strong>，菜单关闭的时候将 <code>statusItem</code> 的 <code>menu</code> 设置为 <strong>nil</strong>，这样就保证常态下，menu 是不指定的，就可以响应我们设置的左右键单击的处理了。</p>
<h2 id="实现">实现</h2>
<ol>
<li>
<p>删除 <code>applicationDidFinishLaunching</code> 中的 <code>statusItem.menu = menu</code></p>
</li>
<li>
<p>修改 <code>mouseClickHandler</code> 的处理如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">mouseClickHandler</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="kd">let</span> <span class="nv">event</span> <span class="p">=</span> <span class="n">NSApp</span><span class="p">.</span><span class="n">currentEvent</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="n">event</span><span class="p">.</span><span class="n">type</span> <span class="p">{</span>
        <span class="k">case</span> <span class="p">.</span><span class="n">leftMouseUp</span><span class="p">:</span>
            <span class="c1">// 使用警告窗口示意左键单击</span>
            <span class="kd">let</span> <span class="nv">alert</span> <span class="p">=</span> <span class="n">NSAlert</span><span class="p">()</span>
            <span class="n">alert</span><span class="p">.</span><span class="n">messageText</span> <span class="p">=</span> <span class="s">&#34;鼠标事件&#34;</span>
            <span class="n">alert</span><span class="p">.</span><span class="n">informativeText</span> <span class="p">=</span> <span class="s">&#34;左键单击&#34;</span>
            <span class="n">alert</span><span class="p">.</span><span class="n">addButton</span><span class="p">(</span><span class="n">withTitle</span><span class="p">:</span> <span class="s">&#34;关闭&#34;</span><span class="p">)</span>
            <span class="n">alert</span><span class="p">.</span><span class="n">window</span><span class="p">.</span><span class="n">titlebarAppearsTransparent</span> <span class="p">=</span> <span class="kc">true</span>
            <span class="n">alert</span><span class="p">.</span><span class="n">runModal</span><span class="p">()</span>
        <span class="k">default</span><span class="p">:</span>
            <span class="n">statusItem</span><span class="p">.</span><span class="n">menu</span> <span class="p">=</span> <span class="n">menu</span>
            <span class="n">statusItem</span><span class="p">.</span><span class="n">button</span><span class="p">?.</span><span class="n">performClick</span><span class="p">(</span><span class="kc">nil</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>保证菜单关闭的时候，将 <code>statusItem</code> 的 <code>menu</code> 设置为 nil，扩展一下 <strong>AppDelegate</strong>，遵循 <code>NSMenuDelegate</code> 在方法 Menu 关闭后指定 nil：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="nc">AppDelegate</span><span class="p">:</span> <span class="n">NSMenuDelegate</span> <span class="p">{</span>
    <span class="c1">// 为了保证按钮的单击事件设置有效，menu要去除</span>
    <span class="kd">func</span> <span class="nf">menuDidClose</span><span class="p">(</span><span class="kc">_</span> <span class="n">menu</span><span class="p">:</span> <span class="n">NSMenu</span><span class="p">)</span> <span class="p">{</span>
        <span class="kc">self</span><span class="p">.</span><span class="n">statusItem</span><span class="p">.</span><span class="n">menu</span> <span class="p">=</span> <span class="kc">nil</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>需要指定 <code>menu</code> 的代理为 AppDelegate，在 <code>applicationDidFinishLaunching</code> 方法中添加下面的代码:</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="n">menu</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
</code></pre></div></li>
</ol>
<p>此时运行程序，鼠标左键单击菜单栏按钮，便会弹出警告窗口，右键单击就能显示菜单了。</p>
<video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191024094833-3CiYyz.mp4" width="100%" controls="controls">
<p>工程打包如下：</p>
<p><a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191024095445-Rhex8j.zip">MenuToolDemo2</a></p>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之菜单栏形式的状态栏小工具</title>
			<link>https://blog.5km.studio/2019/10/22/macOS-dev-basic-nsmenu/</link>
			<pubDate>Tue, 22 Oct 2019 16:46:21 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/10/22/macOS-dev-basic-nsmenu/</guid>
			<description>不知道大家有没有用过菜单栏形式的状态栏工具，类似于之前写的 NSPopover 的工具在系统顶栏占用一个图标，不同的是点击之后弹出的不是弹窗而是一个菜单，就像下</description>
			<content type="html"><![CDATA[<p>不知道大家有没有用过菜单栏形式的状态栏工具，类似于之前写的 NSPopover 的工具在系统顶栏占用一个图标，不同的是点击之后弹出的不是弹窗而是一个菜单，就像下面截图展示的工具，本文就讲一下如何实现。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022165922-Nf83mL.jpg" alt=""></p>
<!-- more -->
<h2 id="平台">平台</h2>
<ul>
<li>macOS 10.15</li>
<li>Xcode 11.1</li>
<li>Swift 5.1</li>
</ul>
<p>本文使用上述平台实现验证，版本不同可能有些差异，但基本思路一致。</p>
<h2 id="工程新建及配置">工程新建及配置</h2>
<ul>
<li>
<p>打开xcode新建工程， <strong>macOS</strong> -&gt; <strong>App</strong> -&gt; <strong>Next</strong>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022175405-tIrq9m.png" alt=""></p>
</li>
<li>
<p>输入工程名称：<code>MenuToolDemo</code>，<strong>language</strong> 选择<code>Swift</code>，<strong>User Interface</strong> 选择 <strong>SwiftUI</strong>(本文不会用到 SwiftUI)点击 Next：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022175405-tIrq9m.png" alt=""></p>
</li>
<li>
<p>选择合适的目录，点击 <strong>create</strong> 即可打开创建的新工程；</p>
</li>
<li>
<p>点击运行按钮，可以看到程序运行，出现一个显示 <strong>hello world</strong> 的窗口，同时dock上出现了应用图标，这不是我们想要的，设置一下不显示它们：</p>
<ul>
<li>
<p>工程导航栏选中工程<code>MenuToolDemo</code>，打开<code>Info</code>标签页;</p>
</li>
<li>
<p>可以看到<code>Custom macOS application Target Properties</code>组，添加新的配置<code>Application is agent(UI Element)</code>，布尔属性，值为 <strong>YES</strong>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022175506-TsRWW4.png" alt=""></p>
</li>
</ul>
</li>
<li>
<p>重新运行程序，可以看到已经不显示Dock图标；</p>
</li>
<li>
<p>删除 <strong>ContentView.swift</strong> 文件；</p>
</li>
<li>
<p>打开文件<code>AppDelegate.swift</code>，删掉两个地方代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kd">var</span> <span class="nv">window</span><span class="p">:</span> <span class="n">NSWindow</span><span class="p">!</span>
</code></pre></div><div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="c1">// Create the SwiftUI view that provides the window contents.</span>
<span class="kd">let</span> <span class="nv">contentView</span> <span class="p">=</span> <span class="n">ContentView</span><span class="p">()</span>

<span class="c1">// Create the window and set the content view.</span>
<span class="n">window</span> <span class="p">=</span> <span class="n">NSWindow</span><span class="p">(</span>
    <span class="n">contentRect</span><span class="p">:</span> <span class="n">NSRect</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">width</span><span class="p">:</span> <span class="mi">480</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="mi">300</span><span class="p">),</span>
    <span class="n">styleMask</span><span class="p">:</span> <span class="p">[.</span><span class="n">titled</span><span class="p">,</span> <span class="p">.</span><span class="n">closable</span><span class="p">,</span> <span class="p">.</span><span class="n">miniaturizable</span><span class="p">,</span> <span class="p">.</span><span class="n">resizable</span><span class="p">,</span> <span class="p">.</span><span class="n">fullSizeContentView</span><span class="p">],</span>
    <span class="n">backing</span><span class="p">:</span> <span class="p">.</span><span class="n">buffered</span><span class="p">,</span> <span class="k">defer</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
<span class="n">window</span><span class="p">.</span><span class="n">center</span><span class="p">()</span>
<span class="n">window</span><span class="p">.</span><span class="n">setFrameAutosaveName</span><span class="p">(</span><span class="s">&#34;Main Window&#34;</span><span class="p">)</span>
<span class="n">window</span><span class="p">.</span><span class="n">contentView</span> <span class="p">=</span> <span class="n">NSHostingView</span><span class="p">(</span><span class="n">rootView</span><span class="p">:</span> <span class="n">contentView</span><span class="p">)</span>
<span class="n">window</span><span class="p">.</span><span class="n">makeKeyAndOrderFront</span><span class="p">(</span><span class="kc">nil</span><span class="p">)</span>
</code></pre></div></li>
<li>
<p>再次运行程序，主窗口也不显示了，连菜单栏也木有了，不要着急，咱继续。</p>
</li>
</ul>
<h2 id="添加状态栏按钮">添加状态栏按钮</h2>
<p>打开文件<code>AppDelegate.swift</code>，在类中添加属性，这一步是创建一个状态栏按钮，设置宽度属性<code>NSStatusItem.squareLength</code>，代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kd">let</span> <span class="nv">statusItem</span> <span class="p">=</span> <span class="n">NSStatusBar</span><span class="p">.</span><span class="n">system</span><span class="p">.</span><span class="n">statusItem</span><span class="p">(</span><span class="n">withLength</span><span class="p">:</span> <span class="n">NSStatusItem</span><span class="p">.</span><span class="n">squareLength</span><span class="p">)</span>
</code></pre></div><p>状态栏按钮总该需要一个图标吧！打开<code>Assets.xcassets</code>，右击显示<code>AppIcon</code>下方的空白区，选择<code>New Image Set</code>，重命名为<code>statusIcon</code>，当然这个名字随便定，选中这个图集，会看到右侧有配置区，配置图集按照<code>Template Image</code>渲染。</p>
<p>看到有三个虚线框空白区，这就是图片区，状态栏按钮的图片基本大小为 $18px\times18px$ ，还需2倍和3倍的适用于视网膜屏幕的mac，像素分别是 $36px\times36px$ 和 $54px\times54px$ ，可以使用以下我提供的图标：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024309821129.png" width="18px"><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2018062915302434352614.png" width="36px"><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024344990828.png" width="54px"></p>
<p>分别将图拖到对应位置：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022175555-Ag8b6R.png" alt=""></p>
<p>找到<code>applicationDidFinishLaunching </code>在其中添加以下代码，为状态栏按钮配置图标和行为：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="k">if</span> <span class="kd">let</span> <span class="nv">button</span> <span class="p">=</span> <span class="n">statusItem</span><span class="p">.</span><span class="n">button</span> <span class="p">{</span>
    <span class="n">button</span><span class="p">.</span><span class="n">image</span> <span class="p">=</span> <span class="n">NSImage</span><span class="p">(</span><span class="n">named</span><span class="p">:</span> <span class="s">&#34;StatusIcon&#34;</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>此时运行程序会看到状态栏中出现了我们定义的按钮，当然此时鼠标单击没有任何动作发生。</p>
<blockquote>
<p>前面设置图片集渲染方式为<code>Template Image</code>，是为了适配不同的状态栏主题，因为macOS还有个暗黑主题不是？</p>
</blockquote>
<p>两种主题下的效果如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022182946-PD6ygW.png" alt=""></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022182916-NRA6ZI.png" alt=""></p>
<h2 id="添加菜单">添加菜单</h2>
<p>打开 <strong>Main.storyboard</strong> 文件，使用以下两种方式中的任意一种，打开控件库窗口：</p>
<ul>
<li>
<p>按下快捷键 <code>Command</code> + <code>Shift</code> + <code>L</code></p>
</li>
<li>
<p>点击 Xcode 窗口右上角的 ➕ 按钮</p>
</li>
</ul>
<p>在搜索框中输入 <code>Menu</code>，就会检索到 NSMenu 控件：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022183632-vRDig6.png" alt=""></p>
<p>鼠标左键在控件单击不松拖放到文件 <code>Main.storyboard</code> 的左边栏 <strong>First Responder</strong> 下面：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022184124-fhMo9C.png" alt=""></p>
<p>按下快捷键 <code>Ctrl</code> + <code>Option</code> + <code>Command</code> + <code>Enter</code>(⌃⌥⌘⏎) 打开 <strong>Assitant</strong> 编辑器：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022184927-uusDC1.png" alt=""></p>
<p>按住 <code>Ctrl</code> 键，鼠标左键按住 Menu 控件不松拖动至辅助编辑器的文件 <strong>AppDelegate.swift</strong> 中，在弹出的属性添加弹窗中输入属性名 <code>menu</code>，点击 Connect 就会看到 <strong>AppDelegate.swift</strong> 中出现 menu 属性：</p>
<video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022190313-oUeSVx.mp4" controls="controls" width="100%">
<p>打开文件 <code>AppDelegate.swift</code> ，在上面配置按钮图标的代码上面添加以下代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="n">statusItem</span><span class="p">.</span><span class="n">menu</span> <span class="p">=</span> <span class="n">menu</span>
</code></pre></div><p>运行程序，鼠标左键和右键单击菜单栏按钮都会弹出我们添加的按钮，包含 item1、item2 和 item3。</p>
<h2 id="配置菜单">配置菜单</h2>
<p>下面我们看一下菜单即菜单项的基本使用。</p>
<h3 id="菜单项基本属性">菜单项基本属性</h3>
<p>打开文件 <strong>main.storyboard</strong>，单击 menu，选中 <strong>Item1</strong>，可以在右侧属性检查器(Attributes Inspector) 中看到各个属性。选中 Item2 和 Item3，按 <code>Delete</code> 键删除，选中 Item1</p>
<h4 id="名称---title">名称 - Title</h4>
<p>修改 Item1 的属性检查器的 <strong>Title</strong> ，比如修改成 <strong>退出</strong>，就会修改此菜单项现实的名称。</p>
<h4 id="状态---state">状态 - State</h4>
<p>State 表示选中状态（check），三种状态：on、off、mixed。当设置为 <strong>on</strong> 的时候会看到菜单项前出现一个对号，这里设置为 <strong>off</strong> 即可。</p>
<h4 id="图标---image">图标 - Image</h4>
<p>可以设置通用的图标 Image，同时也可以设置各种状态的图标。这里我们只设置 <strong>Image</strong> 为 <strong>NSStopProgressFreestandingTemplate</strong>，编译运行：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022193003-HEef84.png" alt=""></p>
<h4 id="行为---action">行为 - Action</h4>
<p>你会好奇为什么上面运行 <strong>退出</strong> 是灰色的，那是因为我们还没为它指定行为，类似于前面绑定 menu 属性的操作，同样的操作，只不过这次是按住 <code>Ctrl</code> 键的同时，鼠标左键单击 <strong>退出</strong> 菜单项不松拖动到辅助编辑器的 <code>AppDelegate.swift</code> 文件中，绑定一个名叫 <code>quitApp</code> 的 action。</p>
<p>实现 action 的功能为退出应用，最终 <code>quitApp </code> 方法如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">quitApp</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">NSApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">terminate</span><span class="p">(</span><span class="kc">self</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>编译运行程序，单击菜单栏的按钮可以看到 <strong>退出</strong> 菜单项已经不是灰色的了，可以点击，点击后便退出程序。</p>
<h4 id="快捷键">快捷键</h4>
<p>每个菜单项执行行为可以绑定快捷键的，就是设置在属性检查起中的 <strong>Key Equivalent</strong> 属性，我们这里设置为 <strong>Q</strong>，运行程序，可以看到菜单项名称后面多了一个 Q，当显示菜单时按下 Q 键，等同于点击了 <strong>退出</strong>。</p>
<h3 id="多级菜单">多级菜单</h3>
<p>多级菜单也很容易实现。</p>
<ul>
<li>
<p>添加新的菜单项</p>
<p>打开文件 <code>main.storyboard</code> ，按下快捷键 <code>Command</code> + <code>Shift</code> + <code>L</code> ，在控件库弹窗输入 <strong>Menu item</strong>，拖动控件 <strong>Menu Item</strong> 到 <strong>退出</strong> 上方添加。</p>
</li>
<li>
<p>威刚添加的菜单项添加菜单</p>
<p>打开控件库弹窗，拖动 <strong>Menu</strong> 控件到刚刚添加的新的菜单项 Item 上，此时可以看到 Item 后面出现了新的菜单</p>
</li>
</ul>
<p>运行程序，点击菜单栏按钮，鼠标放到菜单项 <strong>Item</strong> 便可以看到第二级菜单：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022195239-sZCEwG.png" alt=""></p>
<h2 id="总结">总结</h2>
<p>本文简单讲了菜单形式菜单栏工具的基本实现，推一反三便可以实现更丰富的小工具了。最终实现的工程文件已经打包在下面：</p>
<p><a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191022195552-l6UN33.zip">MenuToolDemo</a></p>
]]></content>
		</item>
		
		<item>
			<title>macbook pro 换脸成功</title>
			<link>https://blog.5km.studio/2019/10/18/mbp-screen/</link>
			<pubDate>Fri, 18 Oct 2019 10:13:52 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/10/18/mbp-screen/</guid>
			<description>关注我的小伙伴应该也知道 long long ago 我因为自己的愚蠢行为让 MBP 的屏幕进水了，导致它的脸 - 屏幕变成了条纹脸，显示出现一些灰色横向条纹分布整个屏幕，底部</description>
			<content type="html"><![CDATA[<p>关注我的小伙伴应该也知道 long long ago 我因为自己的愚蠢行为让 MBP 的屏幕进水了，导致它的脸 - 屏幕变成了条纹脸，显示出现一些灰色横向条纹分布整个屏幕，底部最密集，造孽呀！没错，它需要得到救赎，就由我来帮它做换脸手术吧！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191018104714-0v0def.jpg" alt=""></p>
<!-- more -->
<h2 id="购买新脸">购买新脸</h2>
<p>去过 Apple 实体店，换屏幕需要四千多大洋，我去，有点猛烈，为了节流，十里决定亲自帮 MBP 换脸，木有办法，需要现便宜买张合适的脸，从某宝网上花了不到两千大洋买了一个屏幕总成，整个上盖包含屏幕，A1707 深空灰版本：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191018105601-4XfdSf.jpg" alt=""></p>
<h2 id="拆屏幕">拆屏幕</h2>
<p>不得不说，小心翼翼，生怕把后盖弄变形，单单后盖拆了很久，不过掌握了诀窍。MBP 做工极佳，看到内部的设计也会明白为什么卖这么贵了！为了拆屏幕，可是动了不少螺丝和结构，还好的是，不需要拆主板！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191018111142-ggDM4B.jpg" alt=""></p>
<h2 id="安装屏幕">安装屏幕</h2>
<p>屏幕取下的时候挺费劲的，往上卡屏幕也得小心，再按照倒序还原螺丝和结构的位置就 OK 了：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191018112349-FEAIqy.jpg" alt=""></p>
<p>最后开机，完美点亮屏幕</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20191018112421-WBnF7U.jpg" alt=""></p>
<h2 id="总结">总结</h2>
<ul>
<li>拆机过程中发现电池有鼓包现象，这有点恐怖呀，算了先不理会！我好像拆完后盖，没有先把电池电源断开，好险，没有发生什么问题！😅</li>
<li>换完脸之后，第一次开机极慢，据说这是正常现象，我重复重启几次之后就正常了</li>
<li>可以开心地拿着 MBP 出去浪了</li>
</ul>
]]></content>
		</item>
		
		<item>
			<title>PlatformIO IDE(VSCode) - stm32cube 框架的工程</title>
			<link>https://blog.5km.studio/2019/08/24/platformio-stm32-cubemx/</link>
			<pubDate>Sat, 24 Aug 2019 19:21:06 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/08/24/platformio-stm32-cubemx/</guid>
			<description>前些天知乎网友有问到 PIO 中 stm32 的 cubemx 框架工程中闪灯程序不能正常工作的问题，猜测可能是没有进行系统时钟配置导致的。本文就以 LED 闪烁为例讲一下如何新建一</description>
			<content type="html"><![CDATA[<p>前些天知乎网友有问到 PIO 中 stm32 的 cubemx 框架工程中闪灯程序不能正常工作的问题，猜测可能是没有进行系统时钟配置导致的。本文就以 LED 闪烁为例讲一下如何新建一个 <strong>stm32cube</strong> 框架的工程，并且将 LED 点亮。</p>
<h2 id="测试平台">测试平台</h2>
<ul>
<li>PlatformIO IDE (VSCode)</li>
<li>stm32f103c8t6 最小系统板，板载 LED 连接在 <strong>PC13</strong> 管脚，低电平点亮</li>
<li>stlink v2 仿真调试器</li>
</ul>
<p>测试目标：创建 <strong>stm32cube</strong> 框架的工程项目，实现 LED 周期闪烁的代码</p>
<h2 id="创建工程项目">创建工程项目</h2>
<p>这一步很简单，注意选择框架 <strong>stm32cube</strong>^[stm32cube 对应的就是 cubemx 库，详见<a href="https://docs.platformio.org/en/latest/frameworks/stm32cube.html">STM32Cube</a>]，开发板选择 <strong>STM32F103C8(20k RAM. 64k Flash)(Generic)</strong>，名称随便，最后点击创建即可：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190824193953-b9xwHn.png" alt=""></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190824194009-9G7nyQ.png" alt=""></p>
<h2 id="添加例程代码">添加例程代码</h2>
<p>按照 <a href="https://docs.platformio.org/en/latest/tutorials/ststm32/stm32cube_debugging_unit_testing.html#adding-code-to-the-generated-project">STM32Cube HAL and Nucleo-F401RE: debugging and unit testing - Adding Code to the Generated Project</a> ^[<a href="https://docs.platformio.org/en/latest/tutorials/ststm32/stm32cube_debugging_unit_testing.html#tutorial-stm32cube-debugging-unit-testing">STM32Cube HAL and Nucleo-F401RE: debugging and unit testing</a>] 描述的过程添加适合自己开发板的代码（修改对应的 LED 管脚即可）。</p>
<p>右键 src 文件夹，新建两个文件，一个是 <code>main.h</code> 另一个是 <code>main.c</code>：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190824195932-qoIR8c.png" alt=""></p>
<p>向 <code>mian.h</code> 中添加以下代码，如果您的板载 LED 连接到其它管脚，请相应修改关于 LED 的三个宏定义：</p>
<div class="highlight"><pre class="chroma"><code class="language-cpp" data-lang="cpp"><span class="cp">#ifndef MAIN_H
</span><span class="cp">#define MAIN_H
</span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf">&#34;stm32f1xx_hal.h&#34;</span><span class="cp">
</span><span class="cp"></span>
<span class="cp">#define LED_PIN GPIO_PIN_13
</span><span class="cp">#define LED_GPIO_PORT GPIOC
</span><span class="cp">#define LED_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
</span><span class="cp"></span>
<span class="cp">#endif  </span><span class="c1">// MAIN_H
</span></code></pre></div><p>向 <code>mian.c</code> 中添加以下代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&#34;main.h&#34;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">LED_Init</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">HAL_Init</span><span class="p">();</span>
  <span class="n">LED_Init</span><span class="p">();</span>

  <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// LED 翻转
</span><span class="c1"></span>    <span class="n">HAL_GPIO_TogglePin</span><span class="p">(</span><span class="n">LED_GPIO_PORT</span><span class="p">,</span> <span class="n">LED_PIN</span><span class="p">);</span>
    <span class="n">HAL_Delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">LED_Init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// LED GPIO 初始化
</span><span class="c1"></span>  <span class="n">LED_GPIO_CLK_ENABLE</span><span class="p">();</span>
  <span class="n">GPIO_InitTypeDef</span> <span class="n">GPIO_InitStruct</span><span class="p">;</span>
  <span class="n">GPIO_InitStruct</span><span class="p">.</span><span class="n">Pin</span> <span class="o">=</span> <span class="n">LED_PIN</span><span class="p">;</span>
  <span class="n">GPIO_InitStruct</span><span class="p">.</span><span class="n">Mode</span> <span class="o">=</span> <span class="n">GPIO_MODE_OUTPUT_PP</span><span class="p">;</span>
  <span class="n">GPIO_InitStruct</span><span class="p">.</span><span class="n">Pull</span> <span class="o">=</span> <span class="n">GPIO_PULLUP</span><span class="p">;</span>
  <span class="n">GPIO_InitStruct</span><span class="p">.</span><span class="n">Speed</span> <span class="o">=</span> <span class="n">GPIO_SPEED_FREQ_HIGH</span><span class="p">;</span>
  <span class="n">HAL_GPIO_Init</span><span class="p">(</span><span class="n">LED_GPIO_PORT</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">GPIO_InitStruct</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">SysTick_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="n">HAL_IncTick</span><span class="p">();</span> <span class="p">}</span>
</code></pre></div><p>确保把 stm32f103c8t6 的最小系统板通过 stlink 连接到电脑 usb，编译并上传程序：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190824222127-q3S1ZX.png" alt=""></p>
<p><strong>what？</strong> LED 竟然没有如期的闪动起来!</p>
<h2 id="解决问题">解决问题</h2>
<p>代码都正确呀，为什么程序没有正常工作呢?首先怀疑对象是系统时钟，用示波器测试了一下芯片的高速晶振管脚，竟然没有起振，哎呀，我们确实没有添加系统时钟配置的代码，再次对照一下用 <strong>STMCubeMX</strong>^[STM32CubeMX is a graphical tool that allows a very easy configuration of STM32 microcontrollers and microprocessors, as well as the generation of the corresponding initialization C code for the Arm® Cortex®-M core or a partial Linux® Device Tree for Arm® Cortex®-A core), through a step-by-step process.——<a href="https://www.st.com/en/development-tools/stm32cubemx.html">STM32CubeMX</a>] 生成的普通项目的源码，确实我们的工程中缺少了对于时钟的配置。</p>
<p>为了解决这个问题，参考了 STMCubeMX 项目，笔者建议除了添加时钟的配置之外，还要加一些可能以后会用到的处理函数。这里我们添加两个新的文件 <code>sys_basic.h</code> 和 <code>sys_basic.c</code>。</p>
<p>文件 <code>sys_basic.h</code> 内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#ifndef SYS_BASIC_H
</span><span class="cp">#define SYS_BASIC_H
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">_Error_Handler</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">,</span> <span class="kt">int</span><span class="p">);</span>
<span class="cp">#define Error_Handler() _Error_Handler(__FILE__, __LINE__)
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">SystemClock_Config</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

<span class="cp">#endif
</span></code></pre></div><p>文件 <code>sys_basic.c</code> 内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&#34;sys_basic.h&#34;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&#34;stm32f1xx_hal.h&#34;</span><span class="cp">
</span><span class="cp"></span>
<span class="cm">/** System Clock Configuration
</span><span class="cm"> */</span>
<span class="kt">void</span> <span class="nf">SystemClock_Config</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">RCC_OscInitTypeDef</span> <span class="n">RCC_OscInitStruct</span><span class="p">;</span>
  <span class="n">RCC_ClkInitTypeDef</span> <span class="n">RCC_ClkInitStruct</span><span class="p">;</span>

  <span class="cm">/**Initializes the CPU, AHB and APB busses clocks
</span><span class="cm">   */</span>
  <span class="n">RCC_OscInitStruct</span><span class="p">.</span><span class="n">OscillatorType</span> <span class="o">=</span> <span class="n">RCC_OSCILLATORTYPE_HSE</span><span class="p">;</span>
  <span class="n">RCC_OscInitStruct</span><span class="p">.</span><span class="n">HSEState</span> <span class="o">=</span> <span class="n">RCC_HSE_ON</span><span class="p">;</span>
  <span class="n">RCC_OscInitStruct</span><span class="p">.</span><span class="n">HSEPredivValue</span> <span class="o">=</span> <span class="n">RCC_HSE_PREDIV_DIV1</span><span class="p">;</span>
  <span class="n">RCC_OscInitStruct</span><span class="p">.</span><span class="n">HSIState</span> <span class="o">=</span> <span class="n">RCC_HSI_ON</span><span class="p">;</span>
  <span class="n">RCC_OscInitStruct</span><span class="p">.</span><span class="n">PLL</span><span class="p">.</span><span class="n">PLLState</span> <span class="o">=</span> <span class="n">RCC_PLL_ON</span><span class="p">;</span>
  <span class="n">RCC_OscInitStruct</span><span class="p">.</span><span class="n">PLL</span><span class="p">.</span><span class="n">PLLSource</span> <span class="o">=</span> <span class="n">RCC_PLLSOURCE_HSE</span><span class="p">;</span>
  <span class="n">RCC_OscInitStruct</span><span class="p">.</span><span class="n">PLL</span><span class="p">.</span><span class="n">PLLMUL</span> <span class="o">=</span> <span class="n">RCC_PLL_MUL9</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">HAL_RCC_OscConfig</span><span class="p">(</span><span class="o">&amp;</span><span class="n">RCC_OscInitStruct</span><span class="p">)</span> <span class="o">!=</span> <span class="n">HAL_OK</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">_Error_Handler</span><span class="p">(</span><span class="n">__FILE__</span><span class="p">,</span> <span class="n">__LINE__</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="cm">/**Initializes the CPU, AHB and APB busses clocks
</span><span class="cm">   */</span>
  <span class="n">RCC_ClkInitStruct</span><span class="p">.</span><span class="n">ClockType</span> <span class="o">=</span> <span class="n">RCC_CLOCKTYPE_HCLK</span> <span class="o">|</span> <span class="n">RCC_CLOCKTYPE_SYSCLK</span> <span class="o">|</span>
                                <span class="n">RCC_CLOCKTYPE_PCLK1</span> <span class="o">|</span> <span class="n">RCC_CLOCKTYPE_PCLK2</span><span class="p">;</span>
  <span class="n">RCC_ClkInitStruct</span><span class="p">.</span><span class="n">SYSCLKSource</span> <span class="o">=</span> <span class="n">RCC_SYSCLKSOURCE_PLLCLK</span><span class="p">;</span>
  <span class="n">RCC_ClkInitStruct</span><span class="p">.</span><span class="n">AHBCLKDivider</span> <span class="o">=</span> <span class="n">RCC_SYSCLK_DIV1</span><span class="p">;</span>
  <span class="n">RCC_ClkInitStruct</span><span class="p">.</span><span class="n">APB1CLKDivider</span> <span class="o">=</span> <span class="n">RCC_HCLK_DIV2</span><span class="p">;</span>
  <span class="n">RCC_ClkInitStruct</span><span class="p">.</span><span class="n">APB2CLKDivider</span> <span class="o">=</span> <span class="n">RCC_HCLK_DIV1</span><span class="p">;</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">HAL_RCC_ClockConfig</span><span class="p">(</span><span class="o">&amp;</span><span class="n">RCC_ClkInitStruct</span><span class="p">,</span> <span class="n">FLASH_LATENCY_2</span><span class="p">)</span> <span class="o">!=</span> <span class="n">HAL_OK</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">_Error_Handler</span><span class="p">(</span><span class="n">__FILE__</span><span class="p">,</span> <span class="n">__LINE__</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="cm">/**Configure the Systick interrupt time
</span><span class="cm">   */</span>
  <span class="n">HAL_SYSTICK_Config</span><span class="p">(</span><span class="n">HAL_RCC_GetHCLKFreq</span><span class="p">()</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">);</span>

  <span class="cm">/**Configure the Systick
</span><span class="cm">   */</span>
  <span class="n">HAL_SYSTICK_CLKSourceConfig</span><span class="p">(</span><span class="n">SYSTICK_CLKSOURCE_HCLK</span><span class="p">);</span>

  <span class="cm">/* SysTick_IRQn interrupt configuration */</span>
  <span class="n">HAL_NVIC_SetPriority</span><span class="p">(</span><span class="n">SysTick_IRQn</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>

<span class="cm">/**
</span><span class="cm"> * @brief  This function is executed in case of error occurrence.
</span><span class="cm"> * @param  None
</span><span class="cm"> * @retval None
</span><span class="cm"> */</span>
<span class="kt">void</span> <span class="nf">_Error_Handler</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span> <span class="n">file</span><span class="p">,</span> <span class="kt">int</span> <span class="n">line</span><span class="p">)</span> <span class="p">{</span>
  <span class="cm">/* USER CODE BEGIN Error_Handler_Debug */</span>
  <span class="cm">/* User can add his own implementation to report the HAL error return state */</span>
  <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
  <span class="p">}</span>
  <span class="cm">/* USER CODE END Error_Handler_Debug */</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">NMI_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{}</span>

<span class="kt">void</span> <span class="nf">HardFault_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">MemManage_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">BusFault_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">UsageFault_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">SVC_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{}</span>

<span class="kt">void</span> <span class="nf">DebugMon_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{}</span>

<span class="kt">void</span> <span class="nf">PendSV_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{}</span>
</code></pre></div><p>时钟初始化在函数 <code>SystemClock_Config</code> 中实现，这里时钟源选择使用 8M 高速晶振、9倍频等等常用的时钟配置，在 <code>main.c</code> 中包含头文件 <code>sys_basic.h</code> 并在主函数 <code>HAL_Init();</code> 后加入时钟初始化函数调用 <code>SystemClock_Config();</code>。</p>
<p>此时编译上传程序，可以看到 LED 如期灵动了起来！</p>
<h2 id="调试程序">调试程序</h2>
<p>调试程序与使用 Arduino 框架时一致，需要在配置文件中指定调试工具，这里使用的是 stlink ，所以 <code>paltformio.ini</code> 的内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="k">[env:genericSTM32F103C8]</span>
<span class="na">platform</span> <span class="o">=</span> <span class="s">ststm32</span>
<span class="na">board</span> <span class="o">=</span> <span class="s">genericSTM32F103C8</span>
<span class="na">framework</span> <span class="o">=</span> <span class="s">stm32cube</span>
<span class="na">debug_tool</span> <span class="o">=</span> <span class="s">stlink</span>
</code></pre></div><p>点击 F5 按键便可以进行程序调试了！</p>
<h2 id="总结">总结</h2>
<p>本文简单讲解了创建 stm32cube 框架的 PIO 工程的过程、遇到的问题及解决方法，希望对初次使用 PIO 开发 stm32cube 项目的同学有所帮助！</p>
]]></content>
		</item>
		
		<item>
			<title>PlatformIO IDE(VSCode) - 实现 VS1838B 红外接收驱动</title>
			<link>https://blog.5km.studio/2019/08/21/platformio-irremote-driver/</link>
			<pubDate>Wed, 21 Aug 2019 10:51:18 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/08/21/platformio-irremote-driver/</guid>
			<description>最近做一个项目需要用到红外接收，项目工程针对的是 STM32F103C8T6 平台，使用的是 Arduino 框架，想着不重复造轮子，结果第三方红外库都没有同时满足 ST STM32 平台 和 Arduino 框架两个</description>
			<content type="html"><![CDATA[<p>最近做一个项目需要用到红外接收，项目工程针对的是 <code>STM32F103C8T6</code> 平台，使用的是 Arduino 框架，想着不重复造轮子，结果第三方红外库都没有同时满足 ST STM32 平台 和 Arduino 框架两个条件的，索性就自己造个轮子呗，也不难！</p>
<h2 id="硬件连接">硬件连接</h2>
<p>本文的实现驱动的硬件条件如下：</p>
<ul>
<li>STM32F103C8T6 最小系统板</li>
<li>stlink v2 仿真调试器</li>
<li>VS1838B 红外接收头</li>
<li>常规的红外遥控器</li>
</ul>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190821125056-eMMm6g.jpg" alt=""></p>
<p>VS1838B 有三个管脚，VCC 、 GND 和 OUT，搭建的硬件电路很简单，不需要其他的配套电路，直接连接单片机管脚和电源就能正常使用，这里我将管脚连接到 <strong>PA0</strong> 管脚。</p>
<h2 id="红外相关知识">红外相关知识</h2>
<p>要想编写红外驱动需要了解红外接收器输出得信号特点，根据 VS1838B 输出信号有的放矢的设计信号解析驱动。</p>
<p>笔者手头的红外遥控器采用 NEC 编码协议^[<a href="https://blog.csdn.net/yu__jia/article/details/52912762">
NEC红外线编码协议</a>]，其中红外发射芯片会自动扫描矩阵键盘电路，根据不同的键码，生成一串二进制数据，再按每位的二进制数据用相应的红外光信号发出。遥控器上的一个按键按下后的 <strong>bit</strong> 数据:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190821135123-cVuJ0l.png" alt=""></p>
<p>引导码用于标识一个键码数据的传输开始，将传输 32 位数据，共四个字节，按照先低位后高位传输。第一个字节为用户编码，用于标识遥控器的厂家，第二个字节为用户编吗的反码，用于校验用户编码。第三个字节是按键编码用于区别按下的键，第四个字节为按键编码的反码，用于校验按键编码, 从而确保红外线传输的数据的有效性。</p>
<p>引导码、位数据 0 和位数据 1 信号特征明显，可以参考如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190821143223-HkqRTp.png" alt=""></p>
<p>上面就是红外要发射数据的格式了，真正发射光信号的时候，其实是将待发射的数据调制到一个 38KHz 的光信号中的，红外接收头比如 VS1838B，接收到这个信号后会对信号进行信号放大、带通滤波、解调、波形整形最终还原出原始的 NEC 协议按键编码，但是要注意的是从 VS1838B 输出的信号是与原始编码反相的^[<a href="https://blog.csdn.net/jklinux/article/details/73498067">35 红外接收头在linux内核里的驱动</a>]，通俗讲就是高低电平是反着的。</p>
<h2 id="驱动设计思路">驱动设计思路</h2>
<p>为了找到解析 VS1838B 输出信号的方法，我们还是得先分析一下引导码、位数据 0 和 位数据 1 的特征，其实我们只要找到能区分这三个信号的方法即可。</p>
<p>观察上一小节信号特征图，可以发现三种信号最明显的区别就是低电平时间：</p>
<table>
<thead>
<tr>
<th>信号</th>
<th>低电平时间(ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td>引导码</td>
<td>4.5ms</td>
</tr>
<tr>
<td>b0</td>
<td>0.56ms</td>
</tr>
<tr>
<td>b1</td>
<td>1.68ms</td>
</tr>
</tbody>
</table>
<p>但是我们知道 VS1838B 输出信号是与图中高低信号相反的，所以针对 VS1838B 的输出信号，我们只需要判断每段高电平的时间就可以区分出引导码、位数据 0 和 位数据 1。</p>
<p>一组红外数据的信号是以引导码开始的，所以正常状态下一旦判断到是引导码，下面就应该通过区分位数据信号来保存位数据生成字节数据了，需要连续分辨 32 位数据是 0 或 1即可。</p>
<p>为了保证及时的判断管脚电平的变化，这里采用 IO 外部中断的方式，在中断子程序中实现解析逻辑，解析数据之前关键的一点还是得到高电平时间，将外部中断触发方式设置为电平变化触发，也就是无论电平下降沿还是上升沿都会触发中断。</p>
<p>中断处理逻辑中，加入状态机的处理形式，可分两种状态：</p>
<ul>
<li><strong>正常状态</strong>：等待引导码</li>
<li><strong>接收数据状态</strong>：解析 0、1 数据</li>
</ul>
<p>最终结果存储在一个 32 位整型数据中，那么整个中断的处理逻辑步骤可参考如下：</p>
<ol>
<li>判断进入中断时管脚电平
<ul>
<li>如果是高电平，记录此刻时间，退出中断</li>
<li>如果是低电平，用此刻时间减去记录的时间就得到了高电平脉冲宽度（单位 us），然后进行第 2 步</li>
</ul>
</li>
<li>判断此时状态：
<ul>
<li>如果是<strong>正常状态</strong>，脉宽如果大于 4000us 说明是引导码，此时将状态切换为<strong>接收数据状态</strong>，结果置为 0，然后退出中断</li>
<li>如果是<strong>接收数据状态</strong>，判断脉宽如果在 1120us 和 2240us 之间说明是 1 ，将结果的对应位置 1 ，然后判断是否接受完所有位：
<ul>
<li>如果是，对结果进行校验（利用反码），如果正确进行数据处理</li>
<li>如果没有，退出中断</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="驱动程序实现">驱动程序实现</h2>
<p>根据上述思路设计了红外接收驱动程序，以库的形式对程序封装，声明对象只需指定管脚和处理回调函数即可，在 PlatformIO IDE 项目工程目录 lib 中新建 IrRemote 目录，目录下放置源代码 irRemote.h 和 irRemote.cpp。理论上只要是使用 Arduino 框架，并且管脚支持 IO 外部中断，均可以正常使用。</p>
<h3 id="irremoteh">irRemote.h</h3>
<div class="highlight"><pre class="chroma"><code class="language-cpp" data-lang="cpp"><span class="cp">#ifndef IRREMOTE_H_
</span><span class="cp">#define IRREMOTE_H_
</span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf">&#34;Arduino.h&#34;</span><span class="cp">
</span><span class="cp"></span>
<span class="k">typedef</span> <span class="nf">void</span> <span class="p">(</span><span class="o">*</span><span class="n">IrRemoteCBPtr</span><span class="p">)(</span><span class="kt">uint32_t</span><span class="p">);</span>

<span class="k">enum</span> <span class="nc">IrRemoteState</span> <span class="p">{</span> <span class="n">WAITING</span><span class="p">,</span> <span class="n">READING</span> <span class="p">};</span>

<span class="cm">/**
</span><span class="cm"> * @brief 红外接收结果联合体
</span><span class="cm"> *
</span><span class="cm"> * 定义红外接收联合体是为了可以从原生的 32 位数据，轻松获取字节数据
</span><span class="cm"> */</span>
<span class="k">union</span> <span class="nc">IrRemoteResult</span> <span class="p">{</span>
  <span class="kt">uint32_t</span> <span class="n">value</span><span class="p">;</span>
  <span class="kt">uint8_t</span> <span class="n">bytes</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
<span class="p">};</span>

<span class="k">class</span> <span class="nc">IrRemote</span> <span class="p">{</span>
 <span class="k">public</span><span class="o">:</span>
  <span class="cm">/**
</span><span class="cm">   * @brief 红外类的构造函数
</span><span class="cm">   *
</span><span class="cm">   * 指定红外的管脚和回调函数
</span><span class="cm">   *
</span><span class="cm">   * @param pin 连接红外数据输出的管脚
</span><span class="cm">   * @param callback
</span><span class="cm">   * 收到数据后要执行的回调函数，回调函数具有一个参数用于传递数据结果
</span><span class="cm">   */</span>
  <span class="n">IrRemote</span><span class="p">(</span><span class="kt">uint8_t</span> <span class="n">pin</span><span class="p">,</span> <span class="n">IrRemoteCBPtr</span> <span class="n">callback</span><span class="p">);</span>

  <span class="cm">/**
</span><span class="cm">   * @brief 红外数据接收处理函数
</span><span class="cm">   *
</span><span class="cm">   * 分析红外输出电平及持续时间，解析得到数据
</span><span class="cm">   */</span>
  <span class="kt">void</span> <span class="nf">handler</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

  <span class="cm">/**
</span><span class="cm">   * @brief 启动红外接收
</span><span class="cm">   *
</span><span class="cm">   * 开启红外数据解析逻辑
</span><span class="cm">   */</span>
  <span class="kt">void</span> <span class="nf">begin</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

  <span class="cm">/**
</span><span class="cm">   * @brief 关闭红外接收
</span><span class="cm">   *
</span><span class="cm">   * 关闭红外数据解析逻辑
</span><span class="cm">   */</span>
  <span class="kt">void</span> <span class="nf">end</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

 <span class="k">private</span><span class="o">:</span>
  <span class="kt">uint8_t</span> <span class="n">pin</span><span class="p">;</span>             <span class="c1">// 连接红外输出的管脚
</span><span class="c1"></span>  <span class="kt">uint32_t</span> <span class="n">time</span><span class="p">;</span>           <span class="c1">// 上一次 FALLING 的时刻，单位 us
</span><span class="c1"></span>  <span class="kt">uint32_t</span> <span class="n">width</span><span class="p">;</span>          <span class="c1">// 脉冲宽度，单位 us
</span><span class="c1"></span>  <span class="kt">uint32_t</span> <span class="n">count</span><span class="p">;</span>          <span class="c1">// 数据接收位数计数
</span><span class="c1"></span>  <span class="n">IrRemoteState</span> <span class="n">state</span><span class="p">;</span>     <span class="c1">// 处理状态
</span><span class="c1"></span>  <span class="n">IrRemoteResult</span> <span class="n">result</span><span class="p">;</span>   <span class="c1">// 收到的数据结果
</span><span class="c1"></span>  <span class="n">IrRemoteCBPtr</span> <span class="n">callback</span><span class="p">;</span>  <span class="c1">// 收到数据的回调函数
</span><span class="c1"></span>
  <span class="cm">/**
</span><span class="cm">   * @brief 红外接收结果校验
</span><span class="cm">   *
</span><span class="cm">   * 校验红外接收结果，最后两个字节互为反码即为正确结果
</span><span class="cm">   *
</span><span class="cm">   * @param result 解析得到的红外接收结果
</span><span class="cm">   */</span>
  <span class="kt">bool</span> <span class="nf">check</span><span class="p">(</span><span class="n">IrRemoteResult</span> <span class="n">result</span><span class="p">);</span>
<span class="p">};</span>

<span class="cp">#endif
</span></code></pre></div><h3 id="irremotecpp">irRemote.cpp</h3>
<div class="highlight"><pre class="chroma"><code class="language-cpp" data-lang="cpp"><span class="cp">#include</span> <span class="cpf">&#34;irRemote.h&#34;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">nullCB</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">_</span><span class="p">)</span> <span class="p">{}</span>

<span class="kt">void</span> <span class="nf">irRxHandler</span><span class="p">(</span><span class="n">IrRemote</span> <span class="o">*</span><span class="n">ir</span><span class="p">)</span> <span class="p">{</span> <span class="n">ir</span><span class="o">-&gt;</span><span class="n">handler</span><span class="p">();</span> <span class="p">}</span>

<span class="n">IrRemote</span><span class="o">::</span><span class="n">IrRemote</span><span class="p">(</span><span class="kt">uint8_t</span> <span class="n">pin</span><span class="p">,</span> <span class="n">IrRemoteCBPtr</span> <span class="n">callback</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">this</span><span class="o">-&gt;</span><span class="n">pin</span> <span class="o">=</span> <span class="n">pin</span><span class="p">;</span>
  <span class="k">this</span><span class="o">-&gt;</span><span class="n">callback</span> <span class="o">=</span> <span class="n">callback</span><span class="p">;</span>

  <span class="n">pinMode</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">pin</span><span class="p">,</span> <span class="n">INPUT</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">IrRemote</span><span class="o">::</span><span class="n">handler</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">digitalRead</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">pin</span><span class="p">)</span> <span class="o">==</span> <span class="n">HIGH</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="o">-&gt;</span><span class="n">time</span> <span class="o">=</span> <span class="n">micros</span><span class="p">();</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">this</span><span class="o">-&gt;</span><span class="n">width</span> <span class="o">=</span> <span class="n">micros</span><span class="p">()</span> <span class="o">-</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">time</span><span class="p">;</span>
    <span class="k">switch</span> <span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">state</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">case</span> <span class="nl">WAITING</span><span class="p">:</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">width</span> <span class="o">&gt;</span> <span class="mi">4000</span> <span class="o">&amp;&amp;</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">width</span> <span class="o">&lt;</span> <span class="mi">10000</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">state</span> <span class="o">=</span> <span class="n">READING</span><span class="p">;</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">result</span><span class="p">.</span><span class="n">value</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">break</span><span class="p">;</span>
      <span class="k">case</span> <span class="nl">READING</span><span class="p">:</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">width</span> <span class="o">&gt;</span> <span class="mi">1120</span> <span class="o">&amp;&amp;</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">width</span> <span class="o">&lt;</span> <span class="mi">2240</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">result</span><span class="p">.</span><span class="n">value</span> <span class="o">|=</span> <span class="p">(</span><span class="mh">0x00000001</span> <span class="o">&lt;&lt;</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">count</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">count</span><span class="o">++</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">count</span> <span class="o">&gt;=</span> <span class="mi">32</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">state</span> <span class="o">=</span> <span class="n">WAITING</span><span class="p">;</span>
          <span class="k">if</span> <span class="p">(</span><span class="n">check</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">result</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">detachInterrupt</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">pin</span><span class="p">);</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">callback</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">result</span><span class="p">.</span><span class="n">value</span><span class="p">);</span>
            <span class="n">attachInterrupt</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">pin</span><span class="p">,</span> <span class="p">(</span><span class="n">voidArgumentFuncPtr</span><span class="p">)</span><span class="n">irRxHandler</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span>
                            <span class="n">CHANGE</span><span class="p">);</span>
          <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">break</span><span class="p">;</span>
      <span class="k">default</span><span class="o">:</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">IrRemote</span><span class="o">::</span><span class="n">begin</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">this</span><span class="o">-&gt;</span><span class="n">state</span> <span class="o">=</span> <span class="n">WAITING</span><span class="p">;</span>
  <span class="n">attachInterrupt</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">pin</span><span class="p">,</span> <span class="p">(</span><span class="n">voidArgumentFuncPtr</span><span class="p">)</span><span class="n">irRxHandler</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="n">CHANGE</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">IrRemote</span><span class="o">::</span><span class="n">end</span><span class="p">()</span> <span class="p">{</span> <span class="n">detachInterrupt</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">pin</span><span class="p">);</span> <span class="p">}</span>

<span class="kt">bool</span> <span class="n">IrRemote</span><span class="o">::</span><span class="n">check</span><span class="p">(</span><span class="n">IrRemoteResult</span> <span class="n">result</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">((</span><span class="n">result</span><span class="p">.</span><span class="n">bytes</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">+</span> <span class="n">result</span><span class="p">.</span><span class="n">bytes</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">==</span> <span class="mh">0xFF</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
      <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">bytes</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">result</span><span class="p">.</span><span class="n">bytes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="mh">0xFF</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><h2 id="测试驱动">测试驱动</h2>
<p>编写一个例程测试一下驱动，当收到数据的时候通过串口把数据打印出来，<code>main.cpp</code> 内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-cpp" data-lang="cpp"><span class="cp">#include</span> <span class="cpf">&lt;Arduino.h&gt;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&#34;irRemote.h&#34;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">test</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">result</span><span class="p">)</span> <span class="p">{</span> <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">HEX</span><span class="p">);</span> <span class="p">}</span>

<span class="n">IrRemote</span> <span class="nf">ir</span><span class="p">(</span><span class="n">PB0</span><span class="p">,</span> <span class="n">test</span><span class="p">);</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// 启动红外接收
</span><span class="c1"></span>  <span class="n">ir</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{}</span>

</code></pre></div><p>将连接了 VS1838B 的 stm32f103c8t6 最小系统板通过 stlink 连接电脑同时将其 usb 直连到电脑，编译上传程序，待运行以后，打开串口调试助手，对着 VS1838B 按下红外遥控按钮，便可以看到对应的 32 位原数据(从高位到低位四个字节分别是按键编码反码、按键编码、用户编码反码、用户编码)：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190821162128-Mce9i5.png" alt=""></p>
<h2 id="总结">总结</h2>
<p>本文讲的红外驱动实现思路对于 NEC 协议的红外数据解析应该是通用的，笔者因为使用了 Arduino 框架，所以是以 Arduino 为基础设计的驱动程序，当然不使用 Arduino 框架也可以按照实现思路进行设计对应的驱动，思路可行就可以！希望本文介绍的思路能够帮到您！</p>
]]></content>
		</item>
		
		<item>
			<title>PlatformIO IDE(VSCode) 基本使用 - 使用第三方库</title>
			<link>https://blog.5km.studio/2019/08/19/platformio-libraries/</link>
			<pubDate>Mon, 19 Aug 2019 10:49:25 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/08/19/platformio-libraries/</guid>
			<description>在实际做一个项目的时候，为了提高效率我们会首选不重复造轮子，所以可能会用到第三方库，而 PlatformIO 为我们整理和提供了一些即装即用的第三方库，本文笔者就</description>
			<content type="html"><![CDATA[<p>在实际做一个项目的时候，为了提高效率我们会首选不重复造轮子，所以可能会用到第三方库，而 PlatformIO 为我们整理和提供了一些即装即用的第三方库，本文笔者就带大家了解一下在 PlatformIO IDE (VSCode) 中如何使用和管理这些库。</p>
<h2 id="前言">前言</h2>
<p>本文将使用 Arduino Leonardo 讲解，以文章 <a href="/2019/08/18/platformio-newproject/">PlatformIO IDE(VSCode) 基本使用 - 新建项目</a> 中新建的控制 LED 闪烁的工程（blink）为例。</p>
<p>这个工程中 LED 的控制是阻塞式的，因为使用了 <code>delay</code> 方法，就是死等延时这造成了运算资源的浪费，有没有更好的方法控制 LED 按周期闪烁，而不阻塞呢？笔者很自然地想到了用定时器控制 led 就行啦，所以这就有了我们的新的需求，有没有第三方库支持定时器操作呢！</p>
<p>PIO 提供了很好的方式帮助我们查找和管理第三方库，在下一小节跟笔者一起看一下如何找到我们需要的第三方库。</p>
<h2 id="第三方库的管理">第三方库的管理</h2>
<p>首先我们打开 blink 工程，然后打开 PIO Home 页面，点击左侧的 <strong>Libraries</strong> 标签打开库的标签页，我们会看到库的标签页中有四个标签页分别是：</p>
<ul>
<li><strong>Registry</strong>：仓库，在这里我们可以查找我们需要的第三方库，也能看到按照下载量排序的关键词标签，也能看到最近库的下载排名</li>
<li><strong>Installed</strong>：在这里我们能看到曾经安装的库，并对它们进行管理</li>
<li><strong>Build-in</strong>：在这里我们能看到安装的平台和框架中内建的库</li>
<li><strong>Updates</strong>：在这里我们能看到需要更新的库</li>
</ul>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190819165858-rZXhxO.png" alt=""></p>
<h3 id="库的查找">库的查找</h3>
<p>按照上面提到的需求，我们在 <strong>Registry</strong> 标签页中查找库，我们在搜索框中搜索定时器对应的英文 <code>Timer</code>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190819170522-tktUXt.png" alt=""></p>
<p>可以看到会列出搜索到的结果，搜索优先会按照框架进行关键词搜索，我们看一下具体的一个条目，比如第一个库 <strong>Ticker</strong>，每个条目包含的信息如下：</p>
<ul>
<li>名称及功能描述</li>
<li>适用的框架</li>
<li>适用的平台</li>
<li>关键词标签</li>
<li>其它：下载量、例程数</li>
</ul>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190819171521-GCYNkM.png" alt=""></p>
<p>根据第一个描述就符合我们的需求呀，我们点击一下进入库的页面：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190819172308-nxocy5.png" alt=""></p>
<p>可以看到有五个标签页，这里简单说明一下：</p>
<ul>
<li><strong>Examples</strong>：展示库中包含的例程，有的包含多个，可以点击下拉列表选择不同的例程查看代码</li>
<li><strong>Installtion</strong>：展示如何在项目工程中使用库，在 <code>platformio.ini</code> 文件中如何配置</li>
<li><strong>Headers</strong>：库中包含的头文件</li>
<li><strong>Manifest</strong>：这个一般不用关心，展示的是这个库在 PIO 仓库中的配置信息</li>
<li><strong>Changelog</strong>：更新日志</li>
</ul>
<h3 id="库的安装">库的安装</h3>
<p>安装库一般是两种形式，一种是全局安装，其他项目工程也能够使用，另一种是只安装到指定项目工程中。</p>
<p>点击 <strong>Install</strong> 会默认全局安装，这样其它的工程项目也能使用而不用重复安装。</p>
<p>另外，也可以点击 Install 按钮右边的 <code>...</code> 按钮完成指定位置的安装：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190819200737-zYvJxn.png" alt=""></p>
<p>这里我们采用全局安装，点击 <code>Install</code> 按钮即可，库安装完成后会有弹窗提示。</p>
<h3 id="库的卸载">库的卸载</h3>
<p>有时有些库可能不再会使用，如果想卸载怎么操作？找到 Librairies 的 Installed 标签页，就会看到我们刚才安装的 <strong>Ticker</strong> 库，可以看到 <code>Uninstall</code> 按钮，点击它按照提示操作即可完成卸载，点击 <code>Reveal</code> 按钮会打开安装库的目录：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190819201650-LtB1dT.png" alt=""></p>
<h3 id="库的更新">库的更新</h3>
<p>PIO Home 启动后会检测库的更新，如果库有新的版本发布，就会在 Libraries - Updates 页面出现待更新的库，点击 <code>Update</code> 按钮即可实现更新！</p>
<h2 id="库的使用">库的使用</h2>
<h3 id="配置">配置</h3>
<p>使用第三方库的方式会在库的页面的 <code>Installtion</code> 标签页展示，比如 <strong>Ticker</strong> 库的：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190819202421-yNf4mA.png" alt=""></p>
<p>需要在 <code>paltformio.ini</code> 文件中添加 <code>lib_deps</code> 项，指定库即可，主要有三种方式：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="na">lib_deps</span> <span class="o">=</span>
<span class="c1"># 使用库的名称</span>
    <span class="na">Ticker</span>

<span class="c1"># ... 或者使用库的 ID</span>
    <span class="na">1586</span>

<span class="c1"># ... 或者使用指定版本的库</span>
    <span class="na">Ticker@3.1.5</span>
    
<span class="c1"># Semantic Versioning Rules</span>
<span class="c1"># http://docs.platformio.org/page/userguide/lib/cmd_install.html#description</span>
<span class="c1"># Ticker@^3.1.5</span>
<span class="c1"># Ticker@~3.1.5</span>
<span class="c1"># Ticker@&gt;=3.1.5</span>
</code></pre></div><p>我们这里采用第一种方式，最终 <strong>blink</strong> 项目的 <code>platformio.ini</code> 内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="p">[</span><span class="nl">env</span><span class="p">:</span><span class="n">leonardo</span><span class="p">]</span>
<span class="n">platform</span> <span class="o">=</span> <span class="n">atmelavr</span>
<span class="n">board</span> <span class="o">=</span> <span class="n">leonardo</span>
<span class="n">framework</span> <span class="o">=</span> <span class="n">arduino</span>
<span class="n">lib_deps</span> <span class="o">=</span>
  <span class="cp"># Using a library name
</span><span class="cp"></span>  <span class="n">Ticker</span>
</code></pre></div><p>这里要注意的是，一个项目工程极有可能会使用多个库，这是有两种语法配置：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="c1">; one line definition (comma + space)</span>
<span class="k">[env:myenv]</span>
<span class="na">lib_deps</span> <span class="o">=</span> <span class="s">LIBRARY_1, LIBRARY_2, LIBRARY_N</span>

<span class="c1">; multi-line definition</span>
<span class="k">[env:myenv2]</span>
<span class="na">lib_deps</span> <span class="o">=</span><span class="s">
</span><span class="s">  LIBRARY_1
</span><span class="s">  LIBRARY_2
</span><span class="s">  LIBRARY_N</span>
</code></pre></div><h3 id="使用库">使用库</h3>
<p>这样我们就可以在工程中源代码中包含库的头文件然后使用库了，因为毕竟是别人的写的库，所以应该先看一下例程的用法，然后根据自己的需求实现自己需要的功能。</p>
<p>比如还是实现 1s 切换一次 LED 状态实现等的闪烁，只需要编写相应的处理函数，定义新的 Ticker 指定处理函数和时间参数即可，代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-cpp" data-lang="cpp"><span class="cp">#include</span> <span class="cpf">&lt;Arduino.h&gt;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&lt;Ticker.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">blink</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">static</span> <span class="kt">uint32_t</span> <span class="n">state</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="n">state</span> <span class="o">^=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">LED_BUILTIN</span><span class="p">,</span> <span class="n">state</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">Ticker</span> <span class="nf">timer</span><span class="p">(</span><span class="n">blink</span><span class="p">,</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">MILLIS</span><span class="p">);</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">LED_BUILTIN</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
  <span class="n">timer</span><span class="p">.</span><span class="n">start</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">timer</span><span class="p">.</span><span class="n">update</span><span class="p">();</span>
  <span class="c1">//放置其它处理操作
</span><span class="c1"></span><span class="p">}</span>
</code></pre></div><p>编译上传程序，顺利的话同样实现了 LED 的秒闪烁。</p>
<h2 id="结语">结语</h2>
<p>本文只是简单的说了库管理和使用的基本常用操作，可以满足基本使用需求了，如果需要了解更多内容，可以参考：<a href="https://docs.platformio.org/en/latest/librarymanager/index.html">Library Manager</a>。</p>
]]></content>
		</item>
		
		<item>
			<title>PlatformIO IDE(VSCode) 基本使用 - 新建项目</title>
			<link>https://blog.5km.studio/2019/08/18/platformio-newproject/</link>
			<pubDate>Sun, 18 Aug 2019 17:02:01 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/08/18/platformio-newproject/</guid>
			<description>PlatformIO IDE (VSCode) 可以帮助我们更好地使用 PlatforIO，一个 MCU 项目的开始那就是新建，本文就讲解一下如果使用 PlatformIO IDE 新建一个 MCU 的项目。 基本概念 在使用 PlatformIO 的过</description>
			<content type="html"><![CDATA[<p>PlatformIO IDE (VSCode) 可以帮助我们更好地使用 PlatforIO，一个 MCU 项目的开始那就是新建，本文就讲解一下如果使用 PlatformIO IDE 新建一个 MCU 的项目。</p>
<h2 id="基本概念">基本概念</h2>
<p>在使用 PlatformIO 的过程中经常会遇到一些词，比如 Platform 、 Framworks 以及 Boards，在新建项目之前有必要先说明一下，这些具体都代表了什么！</p>
<h3 id="platform">Platform</h3>
<p>直译的话就是 <strong>平台</strong>，具体就是指的芯片平台，再详细一点那就是各个公司具体的系列芯片的开发平台了。目前为止 PIO^[PIO 是 PlatformIO 的简称，后面系列文章会经常使用这个简称] 针对支持的平台都有以下功能支撑：</p>
<ul>
<li>支持指定框架的基于脚本的编译构建系统</li>
<li>针对各公司常规开发板的预配置</li>
<li>提供多架构的构建工具及相关工具链的支持</li>
</ul>
<p>PIO 目前支持的平台分为嵌入式和桌面两大类。</p>
<ul>
<li>
<p>嵌入式平台</p>
<ul>
<li><a href="https://docs.platformio.org/en/latest/platforms/aceinna_imu.html">Aceinna IMU</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/atmelavr.html">Atmel AVR</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/atmelsam.html">Atmel SAM</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/espressif32.html">Espressif 32</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/espressif8266.html">Espressif 8266</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/freescalekinetis.html">Freescale Kinetis</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/infineonxmc.html">Infineon XMC</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/intel_arc32.html">Intel ARC32</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/intel_mcs51.html">Intel MCS-51 (8051)</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/kendryte210.html">Kendryte K210</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/lattice_ice40.html">Lattice iCE40</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/maxim32.html">Maxim 32</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/microchippic32.html">Microchip PIC32</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/nordicnrf51.html">Nordic nRF51</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/nordicnrf52.html">Nordic nRF52</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/nxplpc.html">NXP LPC</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/riscv_gap.html">RISC-V GAP</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/samsung_artik.html">Samsung ARTIK</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/sifive.html">SiFive</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/siliconlabsefm32.html">Silicon Labs EFM32</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/ststm32.html">ST STM32</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/ststm8.html">ST STM8</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/teensy.html">Teensy</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/timsp430.html">TI MSP430</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/titiva.html">TI TIVA</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/wiznet7500.html">WIZNet W7500</a></li>
</ul>
</li>
<li>
<p>桌面平台</p>
<ul>
<li><a href="https://docs.platformio.org/en/latest/platforms/native.html">Native</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/linux_arm.html">Linux ARM</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/linux_i686.html">Linux i686</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/linux_x86_64.html">Linux x86_64</a></li>
<li><a href="https://docs.platformio.org/en/latest/platforms/windows_x86.html">Windows x86</a></li>
</ul>
</li>
</ul>
<p>上面也有前面文章中使用到的 ST STM32，Atmel AVR 一般指的就是 Arduino 系列开发板使用的芯片，说到 Arduino ，它其实就是一个完整的生态，提供开源的硬件开发板的设计以及 Arduino 统一的软件开发包就是所谓的 Frameworks。</p>
<h3 id="frameworks">Frameworks</h3>
<p>上面已经提到 Frameworks 其实就是类似于 SDK 的一个东西，全世界最有名的 Arduino 有自家的一套 SDK，那就是 Arduino，框架基本特征就是提供一整套一致的 API 的集合，本质是一个官方或第三方提供的软件库。</p>
<p>因为 Arduino 极具影响力，所以很多芯片平台也都有了自己的 Arduino 框架，现在 Arduino 框架平台支持的平台有很多：Atmel AVR、Atmel SAM、Espressif 32、Espressif 8266、Infineon XMC、Intel ARC32、Kendryte K210、Microchip PIC32、Nordic nRF51、Nordic nRF52、T STM32、ST STM8 、Teensy、TI MSP430	、TI TIVA ^[更多关于 Arduino 框架的信息参考：https://docs.platformio.org/en/latest/frameworks/arduino.html]。</p>
<p>反过来讲，一个平台有可能支持多个不同开发框架，比如 <strong>ST STM32</strong> 支持的开发框架有：Arduino、CMSIS、libOpenCM3、mbed、SPL、STM32Cube^[更多关于 stm32 单片机开发平台的信息参考：https://docs.platformio.org/en/latest/platforms/ststm32.html]。</p>
<p>PIO 支持的框架也是有很多，总有适合你的：</p>
<ul>
<li>Arduino</li>
<li>ARTIK SDK</li>
<li>CMSIS</li>
<li>ESP8266 Non-OS SDK</li>
<li>ESP8266 RTOS SDK</li>
<li>ESP-IDF</li>
<li>Freedom E SDK</li>
<li>Kendryte FreeRTOS SDK</li>
<li>Kendryte Standalone SDK</li>
<li>libOpenCM3</li>
<li>mbed</li>
<li>PULP OS</li>
<li>Pumbaa</li>
<li>Simba</li>
<li>SPL</li>
<li>STM32Cube</li>
<li>Tizen RT</li>
<li>WiringPi</li>
</ul>
<p>详见：<a href="https://docs.platformio.org/en/latest/frameworks/index.html">https://docs.platformio.org/en/latest/frameworks/index.html</a></p>
<h3 id="boards">Boards</h3>
<p>PIO 支持绝大部分流行的开发板，比如 Arduino 的全系开发板、STM32 的 Nucleo 和 Discovery 系列评估板。有太多太多了，这里就没必要一一罗列了，可以查看下面的链接了解更多关于开发板的支持列表：</p>
<p><a href="https://docs.platformio.org/en/latest/boards/index.html">https://docs.platformio.org/en/latest/boards/index.html</a></p>
<p>PIO 针对支持的开发板都提供完整的项目生成脚本，新建项目从未如此快捷和简单！</p>
<h2 id="新建项目">新建项目</h2>
<p>这里我们以新建一个 <strong>Arduino Leonardo</strong> 的项目为例。</p>
<ul>
<li>
<p>打开 PIO Home：打开新的 VSCode 的窗口，点击左下角状态栏的第三个小房子按钮，就能打开 PIO Home 页面了：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190818190630-mBE3x7.jpg" alt=""></p>
</li>
<li>
<p>点击项目操作按钮的 <strong>+ New Project</strong>，在弹窗中分别填入或选择 Name、Board、Framework 点击 Finish：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190818192413-24rRf1.jpg" alt=""></p>
</li>
<li>
<p>稍作等待，PIO 会自动根据选择的 Board 和 Framework 配置工程并且下载需要用到的编译工具，需要的编译依赖什么的 PIO 通通帮我们搞定，一段时间过后项目工程就新建完成了，打开 <code>src</code> 文件夹下的 <code>main.cpp</code> 如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190818193026-lz9O9C.png" alt=""></p>
</li>
</ul>
<h2 id="项目文件结构">项目文件结构</h2>
<p>项目文件结构要了解一下，方便后期自己手动管理项目。</p>
<ul>
<li><strong>.pio</strong>，存放工程编译产生的文件</li>
<li><strong>.vscode</strong>, 存放针对工程定制化的 vscode 配置文件</li>
<li><strong>include</strong>，存放统一管理的 h 头文件</li>
<li><strong>lib</strong>，存放自己编写的库文件</li>
<li><strong>src</strong>，存放工程项目的 C/C++ 源文件</li>
<li><strong>test</strong>，存放工程项目的测试文件，一般用不到</li>
<li><strong>.gitignore</strong>，git 仓库的忽略文件，方便 git 进行工程项目的版本控制</li>
<li><strong>travis.yml</strong>，持续集成的配置文件，一般用不到</li>
<li><strong>platformio.ini</strong>，项目的核心配置文件，这个会经常用到，所以得了解其中可用的配置项^[详见：<a href="https://docs.platformio.org/en/latest/projectconf.html">“platformio.ini” (Project Configuration File)</a>]</li>
</ul>
<h2 id="编译和上传">编译和上传</h2>
<p>新建了项目工程之后，就是进行开发了，必然要用到编译代码，要将编译生成的二进制文件上传到开发板的芯片中。</p>
<p>为了看到效果，我们编写一段让板载 LED 闪动的实现：</p>
<div class="highlight"><pre class="chroma"><code class="language-c++" data-lang="c++"><span class="cp">#include</span> <span class="cpf">&lt;Arduino.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// put your setup code here, to run once:
</span><span class="c1"></span>  <span class="n">pinMode</span><span class="p">(</span><span class="n">LED_BUILTIN</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// put your main code here, to run repeatedly:
</span><span class="c1"></span>  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">LED_BUILTIN</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
  <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">LED_BUILTIN</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
  <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div><p>在这之前有必要了解一下 PIO 的工具栏按钮：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190818195923-OYxNoh.jpg" alt=""></p>
<p><strong>Tip:</strong></p>
<p>如果使用 Arduino 框架代码中，一定要包含头文件 <code>Arduino.h</code>。</p>
<h3 id="编译项目工程">编译项目工程</h3>
<p>将上面的代码录入 <code>src</code> 目录下的 <code>main.cpp</code> 文件中，看文件名就知道使用的是 c++ 编程语言了。多种方式可以触发编译，比如笔者经常用的两种方式：</p>
<ul>
<li>点击上面截图底部中工具栏的第二个对号图标</li>
<li>按下组合键 <code>CTRL</code> + <code>ALT</code> + <code>B</code></li>
</ul>
<p>还有两种方式可以触发编译任务：</p>
<ul>
<li>按下组合键 <code>CTRL</code> + <code>SHIFT</code> + <code>P</code> (macOS 下是 <code>COMMAND</code> + <code>SHIFT</code> + <code>P</code>)，然后输入关键字 <strong>platformio</strong>，选择 <strong>Build</strong></li>
<li>点击窗口左边栏的蚂蚁头的图标会打开 PlatformIO 的工具栏，点击其中的 <strong>Build</strong></li>
</ul>
<p>顺利的话，触发了编译，很快就会在终端窗口提示编译完成：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">&gt; Executing task: platformio run &lt;

Processing leonardo <span class="o">(</span>platform: atmelavr<span class="p">;</span> board: leonardo<span class="p">;</span> framework: arduino<span class="o">)</span>
------------------------------------------------------------------------------------
Verbose mode can be enabled via <span class="sb">`</span>-v, --verbose<span class="sb">`</span> option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelavr/leonardo.html
PLATFORM: Atmel AVR 1.15.0 &gt; Arduino Leonardo
HARDWARE: ATMEGA32U4 16MHz, 2.50KB RAM, 28KB Flash
PACKAGES: toolchain-atmelavr 1.50400.190710 <span class="o">(</span>5.4.0<span class="o">)</span>, framework-arduinoavr 4.1.1
LDF: Library Dependency Finder -&gt; http://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found <span class="m">7</span> compatible libraries
Scanning dependencies...
No dependencies
Checking size .pio/build/leonardo/firmware.elf
Memory Usage -&gt; http://bit.ly/pio-memory-usage
DATA:    <span class="o">[=</span>         <span class="o">]</span>   5.8% <span class="o">(</span>used <span class="m">149</span> bytes from <span class="m">2560</span> bytes<span class="o">)</span>
PROGRAM: <span class="o">[=</span>         <span class="o">]</span>  14.4% <span class="o">(</span>used <span class="m">4130</span> bytes from <span class="m">28672</span> bytes<span class="o">)</span>
<span class="o">============================</span> <span class="o">[</span>SUCCESS<span class="o">]</span> Took 0.66 <span class="nv">seconds</span> <span class="o">============================</span>
</code></pre></div><h3 id="上传">上传</h3>
<p>先把手头的 Arduino Leonardo 开发板与电脑连接，直接触发上传任务即可，甚至不用自己去选择接口，PlatformIO 会自动查找与电脑连接的端口进行判断然后上传程序，笔者经常会用到的触发方式：</p>
<ul>
<li>组合键 <code>CTRL</code> + <code>ALT</code> + <code>U</code></li>
<li>点击窗口状态栏中 PIO 工具栏的第三个按钮<strong>→</strong></li>
</ul>
<p>第一次上传程序的时候，PIO 往往会先根据板子芯片对应的平台选择合适的上传工具包，所以第一次上传可能需要等一会儿，顺利的话也会在终端窗口看到 <strong>Sucess</strong> 的消息，同时会看到 Leonardo 板载 LED 开始闪烁：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20190818232501-Z84ehL.mp4" controls="controls" width="100%">Blink</video></p>
<h2 id="总结">总结</h2>
<p>PIO 让新建芯片开发的项目工程变得异常简单，只需几小步就可以完成，另外本文也简单说了一下程序的编译和上传操作，同样简单方便！希望本文能够帮助想要体会 PlatformIO 魅力的朋友！</p>
]]></content>
		</item>
		
		<item>
			<title>PlatformIO IDE(VScode) 下调试 STM32 平台程序</title>
			<link>https://blog.5km.studio/2019/08/05/platformio-debug/</link>
			<pubDate>Mon, 05 Aug 2019 17:17:42 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/08/05/platformio-debug/</guid>
			<description>PlatformIO 系列文章 stm32开发新方式-platformio 中最后提到说 PlatformIO 限制了免费用户调试程序的功能，最近发现 PlatformIO 已经免费开发程序调试功能，所以现在</description>
			<content type="html"><![CDATA[<p><strong>PlatformIO</strong> 系列文章 <a href="/2017/11/08/stm32InPIO-start/#调试配置"><strong>stm32开发新方式-platformio</strong></a> 中最后提到说 PlatformIO 限制了免费用户调试程序的功能，最近发现 PlatformIO 已经免费开发程序调试功能，所以现在我们可以使用内建支持更好的方式调试我们的 ST MCU 的程序了，那今天我们就聊一下这一块内容。</p>
<p>官方称其为 PIO Unified Debugger，很好理解，就是统一的调试器，意思是凡是 PIO 支持的可调式的 MCU 开发平台均可以使用统一功能的调试器，PIO 跨平台的特性仍然支持程序调试，也就是说在 windows、linux和 macOS 下均可以得到相同的开发体验。</p>
<blockquote>
<p>You should have PIO Account to work with PIO Unified Debugger. A registration is FREE.</p>
</blockquote>
<p>官方说明免费使用这个调试功能需要登录一个 PIO 账户，这个很简单嘛，注册一个然后在 VSCode 的 PIO IDE 中登录即可，这可能是唯一的限制了！</p>
<h2 id="功能特性">功能特性</h2>
<p>官方文档中提到 VScode 搭建的 PIO 开发环境可以提供最强大的调试功能特性，包括以下：</p>
<ul>
<li><strong>局部、全局和静态变量的浏览器</strong> (Local, Global, and Static Variable Explorer)</li>
<li><strong>条件断点</strong> (Conditional Breakpoints)</li>
<li><strong>表达式监控</strong> (Expressions and Watchpoints)</li>
<li><strong>通用寄存器检阅</strong> (Generic Registers)</li>
<li><strong>外设寄存器检阅</strong> (Peripheral Registers)</li>
<li><strong>内存数据检阅器</strong> (Memory Viewer)</li>
<li><strong>汇编检阅</strong> (Disassembly)</li>
<li><strong>多线程支持</strong> (Multi-thread support)</li>
<li><strong>调试会话的热重启和激活</strong> (A hot restart of an active debugging session)</li>
</ul>
<p>以下为 <strong>genericSTM32F103C8</strong> 开发板使用 Arduino 开发框架时调试程序时的界面：</p>
<p><img src="http://ww3.sinaimg.cn/large/006tNc79ly1g5ukpd0n9wj31hc0u0q80.jpg" alt=""></p>
<h2 id="实践">实践</h2>
<p>下面我们就以 <strong>genericSTM32F103C8</strong> 开发板使用 Arduino 开发框架为例讲解一下，调试程序是多么的简单。</p>
<h3 id="前提条件">前提条件</h3>
<h4 id="开发环境">开发环境</h4>
<ul>
<li>已经安装 vscode</li>
<li>vscode 已经安装 PlatformIO IDE 插件</li>
</ul>
<p>上述条件满足后，打开 vscode 新的窗口，会如下图中所示，左边栏多出 PlatformIO 的logo，以及最下边状态栏左边有个小房子的图标：</p>
<p><img src="http://ww3.sinaimg.cn/large/006tNc79ly1g5ul9bsjubj31hc0u0go1.jpg" alt=""></p>
<h4 id="硬件">硬件</h4>
<ul>
<li>一个 stm32f103c8t6 的最小系统板</li>
<li>stlink v2 调试器</li>
</ul>
<h4 id="pio-账户">PIO 账户</h4>
<p>如果没有 PIO 账户那就先注册一个，打开 vscode 点击上图中提到的小房子的图标就会打开 PlatformIO 的管理页面，点击右上角的 PIO Account 图标</p>
<p><img src="http://ww2.sinaimg.cn/large/006tNc79ly1g5uliezdtej302i02c0hn.jpg" alt=""></p>
<p>此时会跳转到 PIO 账户登录页面，登录按钮下面有一个链接 <strong>create a new one</strong> ，点击一下即可进入注册页面：</p>
<p><img src="http://ww2.sinaimg.cn/large/006tNc79ly1g5ulnmveigj316i0u0wfn.jpg" alt=""></p>
<p>输入自己的邮箱，点击注册按钮即可完成注册， PlatformIO 会自动生成一个密码发到你的邮箱，返回到登录页面输入邮箱和密码登录即可。</p>
<h3 id="新建项目">新建项目</h3>
<p>返回到 Home 页面，点击 New Project 按钮开始新建项目，这里项目名称定义为 <strong>c8t6Blink</strong>，一个控制 LED 闪烁的简单例子，Board 就选 <strong>STM32F103C8(20k RAM. 64k Flash)(Generic)</strong> ，然后使用 <strong>Arduino</strong> 框架，最后点击 Finish 即可创建项目：</p>
<p><img src="http://ww4.sinaimg.cn/large/006tNc79ly1g5um2h72phj316i0u0q4y.jpg" alt=""></p>
<p>初次创建可能需要时间会长一点，会下载编译需要的一些依赖工具包，以后再创建相同平台项目的时候就很快了。</p>
<h3 id="编写程序">编写程序</h3>
<p>手头上这个最小系统板上的 LED 是 PB0 管脚控制的，硬件设计是低电平 LED 点亮，所以一个简单的闪灯程序如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&lt;Arduino.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="cp">#define LED_PIN PB0
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// put your setup code here, to run once:
</span><span class="c1"></span>  <span class="n">pinMode</span><span class="p">(</span><span class="n">LED_PIN</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// put your main code here, to run repeatedly:
</span><span class="c1"></span>  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">LED_PIN</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
  <span class="n">delay</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">LED_PIN</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
  <span class="n">delay</span><span class="p">(</span><span class="mi">900</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div><p>点击底部状态栏左边的小对号图标就可以进行编译，编译完成后点击右边向右的箭头图标就可以烧写程序，初次上传烧写需要时间长一些，PIO 需要下载一些相关工具包，以后就是正常上传程序了，完成后就能看到程序正常运行，LED 闪了起来。</p>
<h3 id="调试程序">调试程序</h3>
<p>按下 <strong>F5</strong> 按键即可触发程序调试，本以为会出现类似本篇文章第一张图的样子，结果会出现以下错误：</p>
<div class="highlight"><pre class="chroma"><code class="language-shell" data-lang="shell">&gt; Executing task: platformio debug &lt;

Error: Please specify <span class="sb">`</span>debug_port<span class="sb">`</span> <span class="k">for</span> environment
终端进程已终止，退出代码: <span class="m">1</span>

终端将被任务重用，按任意键关闭。
</code></pre></div><p>为什么会出现这个错误？原因很简单，PIO 不知道我们是用什么调试工具进行程序调试，所以需要在项目配置文件 <code>platformio.ini</code> 中指明一下我们使用 stlink 进行调试：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="k">[env:genericSTM32F103C8]</span>
<span class="na">platform</span> <span class="o">=</span> <span class="s">ststm32</span>
<span class="na">board</span> <span class="o">=</span> <span class="s">genericSTM32F103C8</span>
<span class="na">framework</span> <span class="o">=</span> <span class="s">arduino</span>
<span class="na">debug_tool</span> <span class="o">=</span> <span class="s">stlink</span>
</code></pre></div><p>此时点击 <strong>F5</strong> 就可以正常触发调试了，可以看到启动调试程序默认停到 main 中第一句。</p>
<ul>
<li>顶部会看到调试工具栏，可以实现调试会话的重启、程序启停、执行等操作按钮</li>
<li>可以在代码编辑器行号前面单击添加或删除断点</li>
<li>左侧调试工具区点击 <strong>PERIPHERALS</strong> 栏，可以查看所有外设寄存器配置，方便验证寄存器配置正确与否</li>
<li><strong>MEMORY</strong> 栏可以添加内存地址来查看指定字节的数据，方便验证指针数据</li>
<li><strong>监视</strong> 中可以添加监测变量，随时查看变量变化</li>
<li><strong>变量</strong> 中展示了所有当前断点可检阅的全局、局部以及静态变量</li>
<li><strong>调用堆栈</strong> 中展示了函数的调用层次</li>
<li>调试控制台的最下方可以输入 <strong>adb</strong> 命令，从而实现更强大的调试操作</li>
<li><strong>DISASSEMBLY</strong> 栏可以控制切换汇编窗口方便查看汇编代码</li>
</ul>
<p><img src="http://ww1.sinaimg.cn/large/006tNc79ly1g5ung40o74j31hc0u0wkt.jpg" alt=""></p>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之 Windows 和 WindowController</title>
			<link>https://blog.5km.studio/2019/04/01/macOS-dev-windows-and-windowcontroller/</link>
			<pubDate>Mon, 01 Apr 2019 08:20:10 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/04/01/macOS-dev-windows-and-windowcontroller/</guid>
			<description>本文带大家一起简单学习如何开发一个基于文档的 Cocoa 应用(document based Cocoa App)，并学习如何使用模态窗口(Modal Windows) 以及 macOS Sierra 支持的标签窗口</description>
			<content type="html"><![CDATA[<p>本文带大家一起简单学习如何开发一个基于文档的 Cocoa 应用(document based Cocoa App)，并学习如何使用模态窗口(Modal Windows) 以及 macOS Sierra 支持的标签窗口(tabbed interface)。</p>
<h1 id="开发平台">开发平台</h1>
<ul>
<li>macOS 10.14.4</li>
<li>Swift 5</li>
<li>xcode 10.2</li>
</ul>
<h1 id="概述">概述</h1>
<p>所有 macOS 程序要呈现用户界面都是以 Windows 作为容器的，当然除了纯粹的命令行工具和菜单栏小工具。Windows 定义了 App 在屏幕中的活动区域，在这个区域内允许用户进行易于理解的多任务交互操作。macOS 应用最终可分为以下几种：</p>
<ul>
<li>
<p>单窗口的工具，比如计算器</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-single-Window.png" width="210px"/>
</figure>

</li>
<li>
<p>单窗口的 library-style 应用，比如照片应用(Photos.app)</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-library-Style1.png" width="560px"/>
</figure>

</li>
<li>
<p>基于文档的多窗口应用，比如文本编辑应用(TextEdit.app)</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-document-based1.png" width="640px"/>
</figure>

</li>
</ul>
<p>不管属于哪一类的应用，几乎每一个 macOS 应用都是使用 MVC (Model-View-Controller) 构建的，这是 macOS 开发中核心的开发模式。</p>
<p>Cocoa 应用中，一个窗口是 <code>NSWindow</code> 类的一个实例(window 就是 view 的容器)，其与控制器紧密配合工作，而控制器是 <code>NSWIndowController</code> 的一个实例。在一个设计良好的应用中会发现通常窗口和控制器是一一对应的，而 MVC 模式中的第三个组成部分——模型(model)的使用会根据应用的类型和设计而不同。</p>
<p>在本文中我们会创建一个有点像 TextEdit 的应用，是 document based 的，我管它叫 <strong>5kmEditor</strong>，这个名字随便，只要您喜欢啥都行！在我们开发过程中，将会涉及到以下内容：</p>
<ul>
<li>窗口 和 窗口控制器</li>
<li>document 架构</li>
<li>NSTextView</li>
<li>模态窗口</li>
<li>菜单栏和菜单项</li>
</ul>
<h1 id="搞起">搞起</h1>
<p>启动 Xcode，按快捷键 <code>Shift</code> + <code>Command</code> + <code>n</code> ，新建工程，选择 macOS -&gt; Cocoa App，点击 Next：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-03-31%2012.48.44.png" alt=""></p>
<p>新出现的窗口中，勾选 <code>Create Document-Based Application</code>，应用的名称随意取，比如 <strong>5kmEditor</strong>，然后 <code>Document Extention</code> 这一项指定文件的扩展名，其决定以后应用保存文件的扩展名，比如十里指定的是 <code>5km</code>，可以不勾选包含测试，点击 Next：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-03-31%2020.56.55.png" alt=""></p>
<p>选择合适的目录，点击 <code>Create</code> 创建即可，创建成功后编译运行，你会看到类似下面的窗口：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-03-31%2020.59.52.png" alt=""></p>
<p>此时，按快捷键 <code>Command</code> + <code>n</code> 或者菜单栏点击 File -&gt; New，可以创建新的窗口如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-multi-windwos.png" alt=""></p>
<h1 id="document-based-应用如何工作">Document-Based 应用如何工作</h1>
<p>上面也看到了这类应用的样子，下面花几分钟一起看一下 Document-Based 应用如何工作的。</p>
<h2 id="document-的结构">Document 的结构</h2>
<p>一个 Document 其实就是 <code>NSDocument</code> 的一个实例对象，文档模型 <code>NSDocument</code> 作为模型保存了文档的数据，同时负责文档窗口控制器 <code>NSWindowController</code> 的创建管理，它管理文档数据，负责文档打开时数据读取的管理和文档对象管理的数据保存到文件的处理，而文件有可能是在硬盘也有可能在 iCloud，均支持。</p>
<p>创建关联的 <code>NSWindowController</code> 负责展示文档的内容，内容视图最终相应处理用户对文档操作的各种交互事件。</p>
<p><code>NSDocumentController</code> 是一个单例对象，主要负责文档模型 <code>NSDocument</code> 的管理工作，维护系统中所有的文档模型 <code>NSDocument</code> 列表，控制多个文档窗口的激活切换，同时跟踪当前活动文档对象，最终结构图如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-document-arch.png" alt=""></p>
<h2 id="禁用-document-的保存和打开">禁用 Document 的保存和打开</h2>
<p>Document-Based 应用的 Documnent 架构支持文件的保存和打开，但是需要定义 <code>Document</code> 类中自己具体实现，本文暂不涉及这部分内容，所以先禁用 Document 的保存和打开。</p>
<p>打开文件 <code>Document.swift</code>，会发现有两个函数是空的：</p>
<ul>
<li><code>data(ofType:)</code>: 用于写文件</li>
<li><code>read(from:ofType:)</code>: 用于读取文件</li>
</ul>
<p>同时还有一个函数是 <code>autosavesInPlace()-&gt;Bool</code>，更改返回值为 <code>false</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">override</span> <span class="kd">class</span> <span class="nc">var</span> <span class="n">autosavesInPlace</span><span class="p">:</span> <span class="nb">Bool</span> <span class="p">{</span>
    <span class="k">return</span> <span class="kc">false</span>
<span class="p">}</span>
</code></pre></div><p>这样我们首先禁用了自动保存功能，下面我们需要禁用菜单栏 File 中的保存和打开菜单项。在这之前，我们运行程序，点击 File -&gt; Open ，竟然会弹出打开文件的窗口，很神奇，我们并没有实现打开呀，为什么会出现文件打开窗口呢？</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-03-31%2023.23.37.png" alt=""></p>
<p>其实是因为 Open 的菜单项绑定了具体的 Action，Action中实现了这些，所以我们只需要断开菜单项与 Action 的链接就可以禁用掉菜单项，视觉上的表现就是菜单栏相应菜单项会变灰色不可用。</p>
<p>打开 <code>Main.storyboard</code> ，找到 <code>Application Scene</code>，点击菜单栏中 File，选择其中的 <code>Open</code>，右击会看到所有的连接，点击 Sent Action 中连接右侧的 <code>x</code> 号，即可断开连接：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-03-31%2023.34.42-1.png" alt=""></p>
<p>同样的操作，分别将 <code>Save...</code>、<code>Save As...</code> 和 <code>Reverrt to Saved</code> 与相应的 Action 断开连接。</p>
<p>然后删除 <code>Open Recent</code> 菜单项，最后我们重写一下 <code>save(withDelegate:didSave:contexInfo:)</code> 方法，后面我们会用到，主要是添加一个错误🙅‍提醒窗口，打开 <code>Document.swift</code> 文件，在 <code>Document</code> 类中添加重写方法如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">override</span> <span class="kd">func</span> <span class="nf">save</span><span class="p">(</span><span class="n">withDelegate</span> <span class="n">delegate</span><span class="p">:</span> <span class="nb">Any</span><span class="p">?,</span> <span class="n">didSave</span> <span class="n">didSaveSelector</span><span class="p">:</span> <span class="nb">Selector</span><span class="p">?,</span> <span class="n">contextInfo</span><span class="p">:</span> <span class="n">UnsafeMutableRawPointer</span><span class="p">?)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nv">userInfo</span> <span class="p">=</span> <span class="p">[</span><span class="n">NSLocalizedDescriptionKey</span><span class="p">:</span> <span class="s">&#34;Sorry, no saving implemented in this post. Click &#39;Do not save&#39; to quit!&#34;</span><span class="p">]</span>
    <span class="kd">let</span> <span class="nv">error</span> <span class="p">=</span> <span class="n">NSError</span><span class="p">(</span><span class="n">domain</span><span class="p">:</span> <span class="n">NSOSStatusErrorDomain</span><span class="p">,</span> <span class="n">code</span><span class="p">:</span> <span class="n">unimpErr</span><span class="p">,</span> <span class="n">userInfo</span><span class="p">:</span> <span class="n">userInfo</span><span class="p">)</span>
    <span class="kd">let</span> <span class="nv">alert</span> <span class="p">=</span> <span class="n">NSAlert</span><span class="p">(</span><span class="n">error</span><span class="p">:</span> <span class="n">error</span><span class="p">)</span>
    <span class="n">alert</span><span class="p">.</span><span class="n">runModal</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div><p>运行程序，此时会看到菜单栏的相应菜单项已经禁用：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-03-31%2023.41.27.png" alt=""></p>
<h1 id="窗口显示">窗口显示</h1>
<p>上面新建文件的时候，我们发现新建文档的窗口完全覆盖了之前的文档窗口，然而这不是我们想要的结果，本节就聊一下怎么合理布局窗口的位置。进行改造前，我们需要新建一个 <code>NSWindowController</code> 的子类，然后添加相应的代码实现我们预期的功能。</p>
<h2 id="新建-nswindowcontroller-的子类">新建 NSWindowController 的子类</h2>
<p>工程导航栏选择 <code>5kmEditor</code>，按下快捷键 <code>Command</code> + <code>n</code>，就会弹出新建文件的导航窗口，选择 macOS -&gt; Source -&gt; Cocoa Class，点击 Next</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-03-31%2023.58.08.png" alt=""></p>
<p>取名为 <code>WindowController</code>，选择继承自 <code>NSWindowController</code>，不要勾选 <code>Also Create XIB file for user interface</code>，语言选择 <code>Swift</code> ，点击 Next，之后默认然后点击 Create 即可创建。</p>
<p>下一步需要确保 storyboard 中的 window 的控制器是我们刚定义的 <code>WindowController</code> 的实例，打开 <code>Main.storyboard</code> 点击 <code>Window Controller Scene</code> 中的 <code>Window Controller</code>，按快捷键 <code>Option</code> + <code>Command</code> + <code>3</code> ，在右侧显示的 <code>Identity Inspector</code> 中配置 Custom Class 为 <code>WindowController</code>，也就是刚刚创建的类：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2000.08.04.png" alt=""></p>
<h2 id="层叠窗口">层叠窗口</h2>
<p>现在我们可以使新建的窗口层叠显示而不是覆盖显示，打开新建的 <code>WindowController.swift</code> 文件，添加以下代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">required</span> <span class="kd">init</span><span class="p">?(</span><span class="n">coder</span><span class="p">:</span> <span class="n">NSCoder</span><span class="p">)</span> <span class="p">{</span>
    <span class="kc">super</span><span class="p">.</span><span class="kd">init</span><span class="p">(</span><span class="n">coder</span><span class="p">:</span> <span class="n">coder</span><span class="p">)</span>
    <span class="n">shouldCascadeWindows</span> <span class="p">=</span> <span class="kc">true</span>
<span class="p">}</span>
</code></pre></div><p>只需要设置 <code>shouldCascadeWindows</code> 为 true 就可以实现层叠效果，运行程序测试一下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-cascade_windows.png" alt=""></p>
<h2 id="以标签页显示">以标签页显示</h2>
<p>层叠效果很不错，但我们可以尝试一下其它的方式，比如从 macOS Sierra 开始新增加的 tabbed Windows，简单说就是新建的窗口以标签页显示。</p>
<p>打开 <code>Main.storyboard</code>，选中 <code>Window Controller scene</code> 下的 <code>Window</code> ，然后打开 Inspector 栏的 <code>Attributes Inspector</code> (可以按快捷键 <code>Option</code> + <code>Command</code> + <code>4</code>)，找到 <code>Tabbing Mode</code>，更改它的值为 <code>Preferred</code>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2006.59.56.png" alt=""></p>
<p>运行程序，然后按快捷键 <code>Command</code> + <code>n</code> 新建窗口，可以看到新的窗口以标签页的方式显示了：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2007.04.58.png" alt=""></p>
<p>当我们运行程序的时候，macOS 会根据当前屏幕大小和应用请求窗口的大来决定应用窗口的显示位置和实际的大小，下面我们将学习两种方式控制应用窗口的显示位置和实际大小。</p>
<h2 id="使用-interface-builder-设置窗口显示位置">使用 Interface Builder 设置窗口显示位置</h2>
<p>首先我们需要先使用 Interface Builder 设置窗口的初始位置。</p>
<p>打开 <code>Main.storyboard</code>，选中 <code>Window Controller scene</code> 下的 <code>Window</code> ，然后打开 Inspector 栏的 <code>Size Inspector</code> (可以按快捷键 <code>Option</code> + <code>Command</code> + <code>5</code>)，找到 Initial Position，运行程序后的窗口就是按照这个设置初始位置的：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-31-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2007.28.32.jpg" alt=""></p>
<p>其中 x 代表窗口到屏幕左边缘的距离，y 代表窗口到屏幕底边的距离，单位是 px，在 macOS 中应用的坐标原点在左下角，这与 iOS 中是不同的（iOS 使用的是 flipped 坐标系，其原点在左上角）。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-01-Window-position-1.png" alt=""></p>
<p>我们可以点击上面 Size Inspector 中的窗口位置预览图中的红色 constrains，这会决定 macOS 显示应用窗口位置的设定，点击红色 Constrains 可以打开或关闭相关限制，同时会看到下面两个下拉框得值会改变，比如这里：</p>
<ul>
<li>取消上边和右边的红色限制，此时会看到下面两个下拉框的值分别变成 <code>Fixed From Left</code> 和 <code>Fixed From Bottom</code></li>
<li>设置初始的位置：x -&gt; 200, y -&gt; 200</li>
</ul>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-01-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2009.45.16.png" alt=""></p>
<p>此时重新编译运行应用，你会发现不管屏幕多大，只要尺寸允许范围内，应用的窗口会显示在离屏幕左边和底边均为 200 px 的位置。</p>
<p>⚠️: macOS 会记住 app 的窗口显示位置，所以需要先把应用完全退出，然后再编译运行就能看到修改的效果！</p>
<h2 id="代码实现对窗口显示位置的设置">代码实现对窗口显示位置的设置</h2>
<p>代码实现的话，需要在 window 加载之后进行设置，在 WindowsController 中重写的 windowDidLoad 方法中添加相关代码。</p>
<p>这次我们来点特别的，我们设置窗口显示在离屏幕顶边和左边均为 150px 的位置，打开 <code>WindowController.swift</code> 文件，在 <code>WindowController</code> 类中修改 windowDidLoad 方法内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">override</span> <span class="kd">func</span> <span class="nf">windowDidLoad</span><span class="p">()</span> <span class="p">{</span>
    <span class="kc">super</span><span class="p">.</span><span class="n">windowDidLoad</span><span class="p">()</span>
    
    <span class="k">if</span> <span class="kd">let</span> <span class="nv">window</span> <span class="p">=</span> <span class="n">window</span><span class="p">,</span> <span class="kd">let</span> <span class="nv">screen</span> <span class="p">=</span> <span class="n">window</span><span class="p">.</span><span class="n">screen</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nv">offsetFromLeftOfScreen</span> <span class="p">=</span> <span class="n">CGFloat</span><span class="p">(</span><span class="mi">150</span><span class="p">)</span>
        <span class="kd">let</span> <span class="nv">offsetFromTopOFScreen</span> <span class="p">=</span> <span class="n">CGFloat</span><span class="p">(</span><span class="mi">150</span><span class="p">)</span>
        <span class="kd">let</span> <span class="nv">screenFrame</span> <span class="p">=</span> <span class="n">screen</span><span class="p">.</span><span class="n">visibleFrame</span>
        <span class="kd">let</span> <span class="nv">offsetFromBottomOfScreen</span> <span class="p">=</span> <span class="n">screenFrame</span><span class="p">.</span><span class="n">maxY</span> <span class="o">-</span> <span class="n">window</span><span class="p">.</span><span class="n">frame</span><span class="p">.</span><span class="n">height</span> <span class="o">-</span> <span class="n">offsetFromTopOFScreen</span>
        <span class="n">window</span><span class="p">.</span><span class="n">setFrameOrigin</span><span class="p">(</span><span class="n">CGPoint</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">offsetFromLeftOfScreen</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="n">offsetFromBottomOfScreen</span><span class="p">))</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>上面代码主要完成以下工作：</p>
<ul>
<li>获取需要用到的 <code>NSWindow</code> 和 <code>NSScreen</code> 的实例</li>
<li>得到 screen 的 <code>visibleFrame</code></li>
<li>通过离顶边的距离计算得到离底边距离</li>
<li>设置 window 的远点坐标为 (offsetFromLeftOfScreen, offsetFromBottomOfScreen)</li>
</ul>
<p>选中之前显示的应用窗口，<code>Command</code> + <code>q</code> 完全退出应用，然后回到 Xcode 编译运行应用，会看到应用的窗口如期显示在指定的位置：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-01-positionDemo-1.png" alt=""></p>
<h1 id="变身超-mini-富文本处理工具">变身超 mini 富文本处理工具</h1>
<p>Cocoa 有很多可以添加到 window 中的牛🐂的功能性 UI 控件，在本节我们将会用到 <code>NSTextView</code>，在这之前，我们需要了解 <code>NSWindow</code> 的 content view。</p>
<h2 id="content-view">content view</h2>
<p><code>contentView</code> 位于 window 中视图层次的根级，在这个视图中我们可以放置所有界面元素。另外，我们还能替换默认的 <code>contentView</code> 为我们自定义的视图，在这里我们就不做相关操作了，以后我们可能会用到！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-01-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2010.58.22.png" alt=""></p>
<h2 id="添加-text-view">添加 Text View</h2>
<p>打开 <code>Main.storyboard</code> 文件，找到 View Contorller Scene 下的 View Controller，其下的 View 中有个控价 <code>Your document contents here</code> ，将其删除，然后我们添加 Text View:</p>
<ul>
<li>
<p>按快捷键 <code>Shift</code> + <code>Command</code> + <code>l</code> 打开 <code>Object Library</code></p>
</li>
<li>
<p>搜索 Text View</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-01-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2019.34.32.png" alt=""></p>
</li>
<li>
<p>将 <code>Rich Document Content Text View</code> 拖入 Content View 中</p>
</li>
<li>
<p>调整 <code>Rich Document Content Text View</code> 的大小和位置，最终使其四边分别与 <code>contentView</code> 的边缘贴齐</p>
</li>
<li>
<p>选中刚添加的 Text View 控件，然后点击底边的 <code>Resolve Auto Layout Issues</code> 按钮，选中 <code>Reset To Suggested Constrains</code></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-01-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2020.38.53.png" alt=""></p>
</li>
<li>
<p>添加限制之后的样子如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-01-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2020.39.14.png" alt=""></p>
</li>
</ul>
<p>编译运行，可以看到刚添加的 Text View 了：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-01-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2020.39.25.png" alt=""></p>
<p>在窗口的 Text View 中可以添加进行文本编辑了，也支持常用的快捷键，比如复制、粘贴、剪切、撤销、重做等。窗口中也出现了一组工具栏，支持字体设置、简单的段落设置等，同时菜单栏的 Format 的菜单项功能也是可用的，还支持查找替换。这一小节我们没有添加任何代码，就完成了一个简单的富文本编辑工具了，是不是炒🐔煎🍳！</p>
<h2 id="撤销和重做">撤销和重做</h2>
<p>在窗口中添加部分文本，已经可以完成基本的富文本编辑功能了，但是此时还不支持撤销和重做，我们需要添加支持。</p>
<ul>
<li>打开 <code>Main.storyboard</code> 文件，依次找到 <code>View Controller</code> -&gt; <code>View</code> -&gt; <code>Scroll View - Text View</code> -&gt; <code>Clip View</code>  -&gt; <code>Text View</code>，选中 <code>Text View</code></li>
<li>按下快捷键 <code>Option</code> + <code>Command</code> + <code>4</code> 打开 Attribute Inspector，勾选 <code>Undo</code> 复选框</li>
</ul>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-01-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2020.45.04.png" alt=""></p>
<p>此时运行程序，就支持 Undo 和 Redo 了！</p>
<p>在文本框添加文本以后，我们点击窗口关闭按钮，此时会提醒要不要保存文档：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-01-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2020.40.49.png" alt=""></p>
<p>点击 save 按钮，会弹出一个警告窗口：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-01-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-01%2020.02.36.png" alt=""></p>
<p>是不是对里面的内容很熟悉，这就是前面添加的 save 方法中的错误信息。</p>
<h1 id="模态窗口modal-window">模态窗口(Modal Window)</h1>
<p>模态窗口是一种特殊的窗口，一旦显示就会独占用户的所有操作事件，一直到它被关闭，其它窗口才能响应用户的操作。</p>
<p>显示模态窗口有三种方法：</p>
<ul>
<li>以一个普通窗口的形式显示，使用 <code>NSApplication.runModal(for:)</code> 触发显示</li>
<li>以 Modal sheet 的形式显示， 调用 <code>NSWindow.beginSheet(_:completionHandler:)</code> 显示窗口</li>
<li>通过模态会话的形式，本文暂不涉及这种高级的方法</li>
</ul>
<p>其实，文档的保存和打开窗口就是模态窗帘的好例子，就像上面关闭窗口时弹出的提示保存的窗口，它出现在窗口的顶部，这就是 Modal Sheet，在本文也不讲这种模态窗口，下面我们一起实现一个显示字数和段落统计的模态窗口，它是以一个正常窗口形式显示的。</p>
<h2 id="添加一个新的窗口">添加一个新的窗口</h2>
<p>打开 <code>Main.storyboard</code> 文件，按快捷键 <code>Shift</code> + <code>Command</code> + <code>l</code> 打开 Object Library，搜索 Window Controller，拖拽 <code>Window Controller</code> 进入画布，这会生成两个场景：Window Controller Scene 和 View Controller Scene。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2008.46.15.jpg" alt=""></p>
<p>选中刚添加的 WIndow Controller Scene 中的 Window，按快捷键 <code>Option</code> + <code>Command</code> + <code>5</code>，打开 Size Inspector，调整其宽为 300，高为 150。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2008.54.23.png" alt=""></p>
<p>继续选中 Window，按快捷键 <code>Option</code> + <code>Command</code> + <code>4</code> 打开 Atrribute Inspector，取消 Close、Resize 和 Minimise 控件复选框的勾选，设置标题为 <code>Word Count</code>。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2009.02.30.png" alt=""></p>
<p>窗口 Close 按钮会造成一个 bug：当点击这个按钮后虽然窗口已经关闭，但是应用因为没有调用 <code>stopModal</code> 方法而一直保留在模态状态，这就很尴尬了！</p>
<p>另外，不保留 Minimise 和 Resize 按钮是为了遵循 Apple 的 <a href="https://developer.apple.com/design/human-interface-guidelines/macos/overview/themes/">Human Interface Guidelines (HIG)</a>。</p>
<p>选中新添加的 View Controller Scene 中的 View，按下快捷键 <code>Option</code> + <code>Command</code> + <code>5</code> 打开 Size Inspector，设置宽为 300 高为 150。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2009.10.18.png" alt=""></p>
<h2 id="配置-word-count-窗口">配置 Word Count 窗口</h2>
<p><code>Shift</code> + <code>Command</code> + <code>l</code> 打开 Object Library 拖拽 4 个 label 到 View 中。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2009.18.39.png" alt=""></p>
<p>改变四个 label 的标题分别为：<strong>Word Count</strong>、<strong>Paragraph Count</strong>、<strong>0</strong> 和 <strong>0</strong>，同时设置它们都是右对齐，调整它们的宽为 120，这里我们不涉及自动布局，可能会出现几个警告，先不管它们。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2009.37.07.png" alt=""></p>
<p>从 Object Library 推拽一个 Push Button 到 View 中，更改其标题为 OK，手动调整所有控件布局到合适的位置。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2009.41.34.png" alt=""></p>
<h2 id="创建-word-count-的-view-controller-的类">创建 Word Count 的 View Controller 的类</h2>
<p><code>Command</code> + <code>n</code> 会打开一个文件新建的导航窗口，我们选择 macOS -&gt; Source -&gt; Cocoa Class，新出现的窗口中输入类的名称为 <code>WordCountViewController</code>，<strong>Subclass of</strong> 设置为 <code>NSViewController</code>，取消勾选 <code>Also create XIB for user interface</code></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2009.49.32.png" alt=""></p>
<p>点击 Next 创建新的文件。</p>
<p>打开 <code>Main.storyboard</code>，选中新添加的 View Controller，按快捷键 <code>Option</code> + <code>Command</code> + <code>3</code> 打开 Identity Inspector，选择 class 为刚添加的 <code>WordCountViewController</code> 类。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2009.52.17.png" alt=""></p>
<h2 id="绑定计数-label-与-view-controller">绑定计数 label 与 View Controller</h2>
<p>打开新建的 <code>WordCountViewController.swift</code> 文件，在 WordCountViewCOntroller 中添加属性如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@objc</span> <span class="kr">dynamic</span> <span class="kd">var</span> <span class="nv">wordCount</span><span class="p">:</span> <span class="nb">Int</span> <span class="p">=</span> <span class="mi">0</span>
<span class="kr">@objc</span> <span class="kr">dynamic</span> <span class="kd">var</span> <span class="nv">paragraphCount</span><span class="p">:</span> <span class="nb">Int</span> <span class="p">=</span> <span class="mi">0</span>
</code></pre></div><p>⚠️：两个属性添加了 <code>@objc dynamic</code> 修饰符是为了有效实现 <code>Cocoa Bindings</code>^[Cocoa Bindings 是 UI 开发中一个强大的技术，主要用于数据与 UI 的绑定，可以阅读 <a href="https://www.raywenderlich.com/141297/cocoa-bindings-macos"> Cocoa Bindings on macOS </a> 了解更多相关内容，后面有时间十里会专门写一篇相关的文章与大家一起学习！]，否则绑定无效运行时会报错。</p>
<p>打开 <code>Main.storyboard</code> 选中与 Word Count 的 label 相对应的数字 label，按下快捷键 <code>Option</code> + <code>Command</code> + <code>7</code> 打开 Bindings Inspector:</p>
<ul>
<li>点击 <strong>Value</strong> 左边的小三角，展开 <strong>Value</strong></li>
<li><strong>Bind to</strong> 的下拉框选择 <code>Word Count View Controller</code></li>
<li>勾选 <strong>Bind to</strong></li>
<li><strong>Model Key Path</strong> 输入 <code>wordCount</code></li>
</ul>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2010.11.47.png" alt=""></p>
<p>同样的步骤，与 Paragraph Count 的 label 相对应的数字 label 绑定到 <code>paragraphCount</code>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2010.17.58.png" alt=""></p>
<p>下一步设置 Window Controller 的 <strong>Storyboard ID</strong>。</p>
<p>选择 <strong>Word Count Window</strong> 的 <strong>Window Controller</strong>，然后按快捷键 <code>Option</code> + <code>Command</code> + <code>3</code> 打开 Identity Inspector，更改 <strong>Storyboard ID</strong> 的值为 <code>Word Count Window Controller</code>。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2010.20.24.png" alt=""></p>
<h2 id="显示和关闭模态窗口">显示和关闭模态窗口</h2>
<p>前面的准备工作做足了，那本节讲讲如何召唤和轰走模态窗口。</p>
<h3 id="出来吧模态窗口">出来吧，模态窗口</h3>
<p>打开 <strong>ViewController.swift</strong> 文件，在类中添加以下属性：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@IBOutlet</span> <span class="kd">var</span> <span class="nv">text</span><span class="p">:</span> <span class="n">NSTextView</span><span class="p">!</span>
</code></pre></div><p>同时添加以下方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showWordCountWindow</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">AnyObject</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 1</span>
    <span class="kd">let</span> <span class="nv">storyboard</span> <span class="p">=</span> <span class="n">NSStoryboard</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="s">&#34;Main&#34;</span><span class="p">,</span> <span class="n">bundle</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
    <span class="kd">let</span> <span class="nv">wordCountWindowController</span> <span class="p">=</span> <span class="n">storyboard</span><span class="p">.</span><span class="n">instantiateController</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">&#34;Word Count Window Controller&#34;</span><span class="p">)</span> <span class="k">as</span><span class="p">!</span> <span class="n">NSWindowController</span>

    <span class="k">if</span> <span class="kd">let</span> <span class="nv">wordCountWindow</span> <span class="p">=</span> <span class="n">wordCountWindowController</span><span class="p">.</span><span class="n">window</span><span class="p">,</span> <span class="kd">let</span> <span class="nv">textStorage</span> <span class="p">=</span> <span class="n">text</span><span class="p">.</span><span class="n">textStorage</span> <span class="p">{</span>

        <span class="c1">// 2</span>
        <span class="kd">let</span> <span class="nv">wordCountViewController</span> <span class="p">=</span> <span class="n">wordCountWindow</span><span class="p">.</span><span class="n">contentViewController</span> <span class="k">as</span><span class="p">!</span> <span class="n">WordCountViewController</span>
        <span class="n">wordCountViewController</span><span class="p">.</span><span class="n">wordCount</span> <span class="p">=</span> <span class="n">textStorage</span><span class="p">.</span><span class="n">words</span><span class="p">.</span><span class="bp">count</span>
        <span class="n">wordCountViewController</span><span class="p">.</span><span class="n">paragraphCount</span> <span class="p">=</span> <span class="n">textStorage</span><span class="p">.</span><span class="n">paragraphs</span><span class="p">.</span><span class="bp">count</span>

        <span class="c1">// 3</span>
        <span class="n">NSApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">runModal</span><span class="p">(</span><span class="k">for</span><span class="p">:</span> <span class="n">wordCountWindow</span><span class="p">)</span>
        <span class="c1">// 4</span>
        <span class="n">wordCountWindow</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>打开 <strong>Main.storyboard</strong> 文件，选中添加 Text View 的 View Controller，按住 <code>Ctrl</code> 键，点击 View Controller 按钮，不松手拖动至 Text View 上松手，此时会弹出一个绑定选择框，里面就包含了我们刚添加的 text 属性，点击它，这就完成了 Text View 控件与 text 属性的绑定</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2010.34.58.png" alt=""></p>
<p>上面添加的方法，这里一步一步的说明一下：</p>
<ol>
<li>使用之前配置的 <strong>Storyboard ID</strong> 实例化一个 <strong>Word Count Window Controller</strong> 对象</li>
<li>从 text view 的 storage 对象中获取字数统计和段落统计，将值设置到 <strong>wordCountViewController</strong> 的两个属性 <code>wordCount</code> 和 <code>paragraphCount</code></li>
<li>模态方式显示 word count 窗口</li>
<li>一旦模态状态结束就关闭模态窗口，这里需要注意，只要模态不结束这一句就不会执行</li>
</ol>
<h3 id="消失吧模态窗口">消失吧，模态窗口</h3>
<p>这里我们需要添加结束模态的实现，打开文件 <strong>WordCountViewController.swift</strong>，添加以下方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">dismissWordCountWindow</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="n">NSButton</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">NSApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">stopModal</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div><p>下面我们将此方法与上面添加的 OK 按钮进行绑定。</p>
<p>打开 <code>Main.storyboard</code>，选中 OK 按钮，点击它，同时按住 <code>Ctrl</code> 键，拖动鼠标至 <strong>Word Count View Controller</strong> 的按钮上，在弹出的绑定窗口上选择刚添加的方法 <code>dismissWordCountWindow</code> 即可完成绑定。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2011.01.15.png" alt=""></p>
<h3 id="添加召唤模态的符咒">添加召唤模态的符咒</h3>
<p>这里我们以菜单栏的菜单项的方式触发模态窗口。</p>
<p>打开 <strong>Main.storyboard</strong> 文件，找到 <strong>Main Menu</strong>，点击展开 <strong>Edit</strong>，然后进行以下操作：</p>
<ol>
<li><code>Shift</code> + <code>Command</code> + <code>l</code> 打开 Object Library，搜索 <code>Menu Item</code>，拖动到最下面的位置，添加一个新的菜单项，选中它</li>
<li><code>Option</code> + <code>Command</code> + <code>4</code> 打开它的 <strong>Attribute Inspector</strong>，更改标题为 <strong>Word Count</strong>，同时配置快捷键为 <strong>⌘K</strong></li>
</ol>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2011.20.01.png" alt=""></p>
<p>下面我们需要为其绑定上面定义的方法 <code>showWordCountWindow</code>，点击菜单项 <strong>Word Count</strong>，同时按住 <code>Ctrl</code> 键，拖动至 <strong>Application Scene</strong> 下的 <strong>First Responder</strong> 上松手，在弹出的列表中找到方法 <code>showWordCountWindow</code>，选择它，这就完成了触发模态的绑定：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2011.30.40-1.png" alt=""></p>
<h3 id="召唤模态">召唤模态</h3>
<p>编译运行程序，在窗口中输入一些内容，比如：</p>
<blockquote>
<p>望岳</p>
<p>唐代：杜甫</p>
<p>岱宗夫如何？齐鲁青未了。</p>
<p>造化钟神秀，阴阳割昏晓。</p>
<p>荡胸生曾云，决眦入归鸟。</p>
<p>会当凌绝顶，一览众山小。</p>
</blockquote>
<p>菜单栏 Edit -&gt; Word Count (或者按快捷键 <code>Command</code> + <code>k</code>) 就能打开统计字数的模态窗口。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202019-04-02%2013.13.18.png" alt=""></p>
<p>点击 OK 就可以“轰走”模态窗口了。</p>
<h1 id="总结">总结</h1>
<p><a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-04-02-5kmEditor.zip">点我</a>可以下载本文中的工程。本文涉及到了以下内容：</p>
<ul>
<li>MVC 的设计模式</li>
<li>多窗口应用的实现</li>
<li>Interface Builder 和 代码 两种方式控制窗口位置</li>
<li>控件与类属性的绑定，控件 Action 与类方法的绑定</li>
<li>窗口形式的 macOS 的常规开发姿势</li>
<li>如何代码控制显示模态窗口</li>
<li>富文本编辑的简单实现</li>
</ul>
<p>希望对大家学习 macOS 开发有所帮助！感谢您的阅读！</p>
<h1 id="参考">参考</h1>
<p><a href="https://www.raywenderlich.com/613-windows-and-windowcontroller-tutorial-for-macos">Windows and WindowController Tutorial for macOS</a></p>
]]></content>
		</item>
		
		<item>
			<title>PlatformIO 是什么</title>
			<link>https://blog.5km.studio/2019/03/30/what-is-pio/</link>
			<pubDate>Sat, 30 Mar 2019 17:45:14 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/03/30/what-is-pio/</guid>
			<description>PlatformIO is an open source ecosystem for IoT development. Cross-platform IDE and unified debugger. Remote unit testing and firmware updates. PlatformIO 是一个用于物联网开发的开源生态系统。它提供跨平台的开发环境和统一的调试器，还支持远程单元测试和固件</description>
			<content type="html"><![CDATA[<blockquote>
<p>PlatformIO is an open source ecosystem for IoT development.</p>
<p>Cross-platform IDE and unified debugger.</p>
<p>Remote unit testing and firmware updates.</p>
</blockquote>
<p><a href="https://platformio.org">PlatformIO</a> 是一个用于物联网开发的开源生态系统。它提供跨平台的开发环境和统一的调试器，还支持远程单元测试和固件更新&hellip;</p>
<h1 id="platformio-简介">PlatformIO 简介</h1>
<figure class="right"><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-30-platformio-ide-vscode.png"/>
</figure>

<p>PlatformIO 是独立于平台运行的，实际上它只依赖于 python，然而 python 在 macOS、linux 和 windows 都能完美适配. 也就是说 PlatformIO 的工程从一个电脑很容易迁移到另一个电脑，只需要拷贝再使用 PlatformIO 就能完美打开，不管团队中的成员使用什么操作系统 PlatformIO 可以让工程共享变得异常简单. 除此之外, PlatformIO 不仅可以在笔记本和台式机上运行，同样可以运行在没有显示桌面的服务器。PlatformIO 的核心(PlatformIO Core) 就是一个终端程序, 它能配合您喜欢的多款云 IDE、桌面 IDE 或者 通用代码编辑器构建 PlatformIO 的 IDE，比如 Atom, CLion, Eclipse, Emacs, NetBeans, Qt Creator, Sublime Text, VIM, Visual Studio, VSCode, etc.</p>
<p>目前官方推荐的 IDE 使用方案就是 VSCode + PlatformIO IDE 插件，界面如右图：</p>
<ul>
<li>PIO 统一的调试器，可以零配置的对支持硬件调试的的嵌入式开发板进行调试工作，调试器支持很多的架构和开发平台</li>
<li>跨平台的代码构建系统对系统软件没有额外的依赖: 600+ 嵌入式开发板, 30+ 开发平台, 15+ 框架</li>
<li>C/C++ 智能代码补全，语法检查，快速重构以及代码跳转满足快速专业的开发需求</li>
<li>VSCode 提供多工程和文件管理的支持和统一而流畅的使用体验，并且支持多种色彩主题，总有您喜欢和适合您的</li>
<li>内建的终端支持 PlatformIO Core 命令行工具，并且支持强大的串口调试器</li>
</ul>
<p>尽管 PlatformIO 可以运行在不同的操作系统中，但从开发的角度看更重要的是它支持多少开发板和单片机。概括来说: PlatformIO 支持大约 600+ 个<a href="https://docs.platformio.org/en/latest/boards/index.html">开发板</a>和市面上流行的支持跨平台的 30 个<a href="https://docs.platformio.org/en/latest/platforms/index.html#">开发平台</a>。</p>
<ul>
<li>支持的嵌入式开发板列表: <a href="https://docs.platformio.org/en/latest/boards/index.html">Boards</a></li>
<li>支持的单片机平台和桌面平台: <a href="https://docs.platformio.org/en/latest/platforms/index.html#">Platforms</a></li>
</ul>
<p>同时，PlatformIO 还提供大量的开发库，目前超过了 6000 个，为了方便新手入门开发，他们也同样提供代码例程。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-30-support.png" alt=""></p>
<h1 id="platformio-解决的问题">PlatformIO 解决的问题</h1>
<ul>
<li>嵌入式开发的世界让很多人望而却步的主要原因是针对某一个单片机或开发板开发环境复杂的配置过程：交叉编译链工具下载、安装和配置，另外还得使用厂商独有的 IDE (有时还不是免费的) ，还有有时还不得不使用 IDE 只支持的 Windows</li>
<li>多个硬件平台 (单片机, 开发板) 需要不同交叉编译链工具和开发环境, 所有相应的开发人员需要花费大量精力在配置新的开发环境上</li>
<li>为了学习如何使用常规的传感器或执行器件等外设，需要花时间查找合适的库和例程</li>
<li>每个团队成员可能会使用不用的操作系统，这就为他们之间共享成果形成阻碍（当然国内现状是公司逼着员工使用统一的操作系统，哈哈🤣）</li>
</ul>
<h1 id="platformio-如何工作">PlatformIO 如何工作</h1>
<p>这里不深入说 PlatformIO 的实现细节, 使用 PlatformIO 时工程的工作流程如下：</p>
<ul>
<li>用户在 “platformio.ini” (工程配置文件) 中指定开发平台（开发板）</li>
<li>根据配置文件中的开发板（一个工程可以配置多个开发板），PlatformIO 会自动下载并安装对应的交叉编译链工具以及调试工具.</li>
<li>用户编写代码，PlatformIO 来保证所有指定开发板的编译、调试和上传工作</li>
</ul>
<h1 id="platformio-需要用户进行的选择">PlatformIO 需要用户进行的选择</h1>
<ul>
<li>决定使用什么系统运行 PlatformIO 来进行单片机开发，需要进行相应系统的安装工作</li>
<li>选择使用什么编辑器进行代码编写，可以是极简通用的代码编辑器，也可以您喜欢的桌面 IDE</li>
<li>专注于代码开发，显著简化使用开发平台和单片机的过程</li>
</ul>
<h1 id="platformio-的相关报道">PlatformIO 的相关报道</h1>
<blockquote>
<p>通常情况下不同的单片机需要不同的开发工具，比如 Arduino 使用的是 <strong>Arduino IDE</strong>，有些高级用户为了更好的进行代码工程管理，也会配置基于 Eclipse 的图形界面的开发环境。有时同步支持不同的单片机比较困难，您可能觉得如果有一个统一的开发工具那该多好呀！ PlatformIO 就是这么一个开源的进行单片机嵌入式开发的生态工具。</p>
<p>PlatformIO 是一个跨平台的代码构建工具和库管理工具，并且支持像 Arduino 和 MBED 这样的平台。他们关心可以跨 macOS、windows 和 linux 三个平台工作的工具链、调试器和开发平台，它支持超过 200 个的开发板和超过 15 种的开发平台以及 10 中开发框架，所以绝大多数流行的开发板都是包含其中的。他们在收集整理开发库上倾注了大量精力，以至于您的工程可以使用上百种开发库。另外，也有很多代码例程提供方便您入门单片机开发。PlatformIO 最初以命令行的形态进行开发，现如今它可以完美配合其它的 IDE 进行使用，比如 Eclipse、Visual Studio，最近他们最新发布了基于 Atom 的 PlatformIO 开发环境。</p>
</blockquote>
<h1 id="platformio-的荣誉">PlatformIO 的荣誉</h1>
<p>PlatformIO 曾提名 <a href="https://www.postscapes.com/internet-of-things-software-guide/">2015/16 IOT 年度最佳开发软件和工具奖</a>。</p>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之 Swift 的 Codable</title>
			<link>https://blog.5km.studio/2019/03/24/macOS-dev-swift-codable/</link>
			<pubDate>Sun, 24 Mar 2019 16:49:16 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/03/24/macOS-dev-swift-codable/</guid>
			<description>近日研究了一下如何将自定义类型数据持久化，在研究过程中发现 Swift 的 Codable 真的很方便，觉得有必要写一写这个 Codable，在本文一起探讨一下以下三个方</description>
			<content type="html"><![CDATA[<p>近日研究了一下如何将自定义类型数据持久化，在研究过程中发现 Swift 的 Codable 真的很方便，觉得有必要写一写这个 <strong>Codable</strong>，在本文一起探讨一下以下三个方面：</p>
<ul>
<li>什么是 Swift 的 Codable</li>
<li>怎么使用 Codable</li>
<li>Codable 给我们带来什么便利</li>
</ul>
<h1 id="开发平台">开发平台</h1>
<ul>
<li>macOS 10.14.4</li>
<li>Swift 5</li>
<li>xcode 10.2</li>
</ul>
<h1 id="swift-的-codable">Swift 的 Codable</h1>
<blockquote>
<p>In a nutshell, Encoding is the process of transforming your own custom type, class or struct to external data representation type like JSON or plist or something else &amp; Decoding is the process of transforming external data representation type like JSON or plist to your own custom type, class or struct.</p>
</blockquote>
<h2 id="可以使用的协议">可以使用的协议</h2>
<p>Swift 的标准库中包含了用于自定义类型(结构体、类)与其它表示形式(JSON、Property List 或 二进制)的数据之间相互转换的协议：</p>
<ul>
<li>
<p><strong>Encodable</strong>: 用于自定义类型向 JSON 或 Property List 的转换，协议包含一个方法</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">encode</span><span class="p">(</span><span class="n">to</span><span class="p">:)</span> 
</code></pre></div></li>
<li>
<p><strong>Decodable</strong>: 用于自 JSON 或 Property List 数据向自定义类型的转换，协议包含一个方法</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">init</span><span class="p">(</span><span class="n">from</span><span class="p">:)</span>
</code></pre></div></li>
<li>
<p><strong>Codable</strong>: 包含 Encodable 和 Decodable 两方面的转换，其定义如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">typealias</span> <span class="n">Codable</span> <span class="p">=</span> <span class="n">Decodable</span> <span class="o">&amp;</span> <span class="n">Encodable</span>
</code></pre></div></li>
</ul>
<p>使用 Codable 协议 的 Encodable 和 Decodable，可以让我们轻松实现自定义数据类型的序列化以及得到相应数据类型的实例对象。按照字面意思，我们后面将数据类型实例向 JSON 或 Property List 转换的过程称为编码，反之，称为解码！</p>
<h2 id="遵循协议的类型">遵循协议的类型</h2>
<p>如果我们想要实现自定义类型或数据模型的编码和解码，必须遵循 Codable 协议！Swift 基本的内建类型已经是 Codable 的了，比如 <code>String</code>、<code>Int</code>、<code>Double</code>、<code>Date</code> 和 <code>Data</code>。另外像 <code>Array</code>、<code>Dictionary</code> 和 <code>Optional</code> 也都是遵循 Codable 协议的，可以进行编码和解码。</p>
<p>如下自定义的结构体 Person 和 Team，遵循 Codable 协议，同时结构体的所有属性要么是标准的 Codable 类型，要么包含 Codable 类型：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">struct</span> <span class="nc">Person</span> <span class="p">:</span> <span class="n">Codable</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nv">id</span><span class="p">:</span> <span class="nb">Int</span>
    <span class="kd">var</span> <span class="nv">name</span><span class="p">:</span> <span class="nb">String</span>
    <span class="kd">var</span> <span class="nv">age</span><span class="p">:</span> <span class="nb">Int</span>
    <span class="kd">var</span> <span class="nv">isMale</span><span class="p">:</span> <span class="nb">Bool</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="nc">Team</span><span class="p">:</span> <span class="n">Codable</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nv">master</span><span class="p">:</span> <span class="n">Person</span>
    <span class="kd">var</span> <span class="nv">memebers</span><span class="p">:</span> <span class="p">[</span><span class="n">Person</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div><h2 id="codable-类型的编解码">Codable 类型的编解码</h2>
<p>对于 Codable 的类型，需要使用相应的编码器对我们的数据进行编码和解码，具体细节可以参考后面的小节，有两种编码器可用：</p>
<ul>
<li><strong>JSON</strong>
<ul>
<li><code>JSONEncoder</code>: 将 Codable 类型数据编码为 JSON 数据</li>
<li><code>JSONDecoder</code>: 将 JSON 数据解码为指定的 Codable 类型数据</li>
</ul>
</li>
<li><strong>Property List</strong>
<ul>
<li><code>PropertyListEncoder</code>: 将 Codable 类型数据编码为 plist 数据</li>
<li><code>PropertyListDecoder</code>: 将 plist 数据解码为指定的 Codable 类型数据</li>
</ul>
</li>
</ul>
<h1 id="codable-的使用">Codable 的使用</h1>
<p>下面我们就一起研究一下 Codable 的使用，这里我们只尝试 Codable 类型数据与 JSON 数据的编码解码的实现。</p>
<h2 id="编码和解码">编码和解码</h2>
<h3 id="使用-jsonencoder-编码">使用 <code>JSONEncoder</code> 编码</h3>
<p>非常简单，只需调用 <code>JSONEncoder</code> 的 <code>encode(_:)</code> 方法就能将 Codable 类型转换为 JSON 数据：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">jack</span> <span class="p">=</span> <span class="n">Person</span><span class="p">(</span><span class="n">id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="s">&#34;Jack&#34;</span><span class="p">,</span> <span class="n">age</span><span class="p">:</span> <span class="mi">12</span><span class="p">,</span> <span class="n">isMale</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">jackData</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">JSONEncoder</span><span class="p">().</span><span class="n">encode</span><span class="p">(</span><span class="n">jack</span><span class="p">)</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="nb">String</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">jackData</span><span class="p">,</span> <span class="n">encoding</span><span class="p">:</span> <span class="p">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>可以看到打印出转换得到的 JSON 字符串数据：</p>
<div class="highlight"><pre class="chroma"><code class="language-Json" data-lang="Json"><span class="p">{</span><span class="nt">&#34;age&#34;</span><span class="p">:</span><span class="mi">12</span><span class="p">,</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="nt">&#34;isMale&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Jack&#34;</span><span class="p">}</span>
</code></pre></div><p>是不是很方便，解码同样如此简单！</p>
<h3 id="使用-jsondecoder-解码">使用 <code>JSONDecoder</code> 解码</h3>
<p>只需要调用 <code>JSONDecoder</code> 实例的 <code>decode(_:from:)</code> 方法就能将 JSON 对象转换得到指定类型的实例。</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">jsonString</span> <span class="p">=</span> <span class="s">&#34;&#34;&#34;
</span><span class="s">{
</span><span class="s">    &#34;</span><span class="n">id</span><span class="s">&#34;: 2,
</span><span class="s">    &#34;</span><span class="n">name</span><span class="s">&#34;: &#34;</span><span class="n">lucy</span><span class="s">&#34;,
</span><span class="s">    &#34;</span><span class="n">age</span><span class="s">&#34;: 11,
</span><span class="s">    &#34;</span><span class="n">isMale</span><span class="s">&#34;: false
</span><span class="s">}
</span><span class="s">&#34;&#34;&#34;</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">json</span> <span class="p">=</span> <span class="n">jsonString</span><span class="p">.</span><span class="n">data</span><span class="p">(</span><span class="n">using</span><span class="p">:</span> <span class="p">.</span><span class="n">utf8</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nv">lucy</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">JSONDecoder</span><span class="p">().</span><span class="n">decode</span><span class="p">(</span><span class="n">Person</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="n">json</span><span class="p">)</span>
    <span class="bp">print</span><span class="p">(</span><span class="n">lucy</span><span class="p">!)</span>
<span class="p">}</span>
</code></pre></div><p>打印结果，如您所料，将 JSON 字符串成功的解码为了 Person 实例对象：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">Person</span><span class="p">(</span><span class="n">id</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="s">&#34;lucy&#34;</span><span class="p">,</span> <span class="n">age</span><span class="p">:</span> <span class="mi">11</span><span class="p">,</span> <span class="n">isMale</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
</code></pre></div><h3 id="codable-使用的基本步骤">Codable 使用的基本步骤</h3>
<p>综上过程，我们可以总结一下，要使用 Swift 的特性其实很简单，分两步：</p>
<ul>
<li>自定义类型遵循 Codable 协议</li>
<li>使用编码器实现自定义类型数据的编码和解码</li>
</ul>
<h2 id="使用-codingkeys-来选择部分属性是-codable-的">使用 CodingKeys 来选择部分属性是 Codable 的</h2>
<p>也许此时此刻您可能会想：</p>
<ul>
<li>如果不想把自定义数据所有的属性编码到 JSON 该怎么办？</li>
<li>如果 JSON 数据中的键名与自定义类型中的属性名不一致怎么办？</li>
</ul>
<p>请放心，您想到的，Apple 同样照顾到了！就是本节要讲的 <code>CodingKeys</code></p>
<blockquote>
<p>Codable types can declare a special nested enumeration named CodingKeys that conforms to the CodingKey protocol. When this enumeration is present, its cases serve as the authoritative list of properties that must be included when instances of a codable type are encoded or decoded. The names of the enumeration cases should match the names you&rsquo;ve given to the corresponding properties in your type.</p>
</blockquote>
<p><code>CodingKeys</code> 是我们在自定义数据类型中定义的枚举，有以下两点要求：</p>
<ul>
<li>枚举元素类型是 String，并且遵循 CodingKey 协议、</li>
<li>枚举元素的名称必须与自定义类型中的属性名称保持一致</li>
</ul>
<p>那么我们回过头来看一下前面的两个问题怎么解决：</p>
<ul>
<li>CodingKeys 中的元素与自定义数据类型中的属性名称对应，只要删除对应属性的枚举元素就可以实现编码时对应属性的忽略，这样就解决了第一个问题，但是要注意，不编码的属性必须赋予默认值</li>
<li>为 CodingKeys 中枚举元素自定义 String 值与 JSON 数据中的键名对应起来，就能解决第二个问题。</li>
</ul>
<p>假如想为 Person 结构体添加一个 <code>description</code> 的属性，同时不想让它参与编码和解码，另外 JSON 数据中的键名是中文的，我们可以重构 Person 类：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">struct</span> <span class="nc">Person</span> <span class="p">:</span> <span class="n">Codable</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nv">id</span><span class="p">:</span> <span class="nb">Int</span>
    <span class="kd">var</span> <span class="nv">name</span><span class="p">:</span> <span class="nb">String</span>
    <span class="kd">var</span> <span class="nv">age</span><span class="p">:</span> <span class="nb">Int</span>
    <span class="kd">var</span> <span class="nv">isMale</span><span class="p">:</span> <span class="nb">Bool</span>
    <span class="kd">var</span> <span class="nv">description</span><span class="p">:</span> <span class="nb">String</span> <span class="p">=</span> <span class="s">&#34;person&#34;</span>
    
    <span class="kd">enum</span> <span class="nc">CodingKeys</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="n">CodingKey</span> <span class="p">{</span>
        <span class="k">case</span> <span class="n">id</span> <span class="p">=</span> <span class="s">&#34;身份证号&#34;</span>
        <span class="k">case</span> <span class="n">name</span> <span class="p">=</span> <span class="s">&#34;姓名&#34;</span>
        <span class="k">case</span> <span class="n">age</span> <span class="p">=</span> <span class="s">&#34;年龄&#34;</span>
        <span class="k">case</span> <span class="n">isMale</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>我们看一下将自定义数据转换为 JSON 会是怎样的：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">tim</span> <span class="p">=</span> <span class="n">Person</span><span class="p">(</span><span class="n">id</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="s">&#34;tim&#34;</span><span class="p">,</span> <span class="n">age</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="n">isMale</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">description</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">)</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">timData</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">JSONEncoder</span><span class="p">().</span><span class="n">encode</span><span class="p">(</span><span class="n">tim</span><span class="p">)</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="nb">String</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">timData</span><span class="p">,</span> <span class="n">encoding</span><span class="p">:</span> <span class="p">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>编码得到的 JSON 数据如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Json" data-lang="Json"><span class="p">{</span><span class="nt">&#34;姓名&#34;</span><span class="p">:</span><span class="s2">&#34;tim&#34;</span><span class="p">,</span><span class="nt">&#34;isMale&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;年龄&#34;</span><span class="p">:</span><span class="mi">10</span><span class="p">,</span><span class="nt">&#34;身份证号&#34;</span><span class="p">:</span><span class="mi">3</span><span class="p">}</span>
</code></pre></div><h2 id="自定义-encode-和-decode">自定义 encode 和 decode</h2>
<p>我们定义一个 Size 结构体、Point 结构体和 Rect 结构体如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">struct</span> <span class="nc">Size</span><span class="p">:</span> <span class="n">Codable</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nv">width</span><span class="p">:</span> <span class="nb">Double</span>
    <span class="kd">var</span> <span class="nv">height</span><span class="p">:</span> <span class="nb">Double</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="nc">Point</span><span class="p">:</span> <span class="n">Codable</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nv">x</span><span class="p">:</span> <span class="nb">Double</span>
    <span class="kd">var</span> <span class="nv">y</span><span class="p">:</span> <span class="nb">Double</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="nc">Rect</span><span class="p">:</span> <span class="n">Codable</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nv">position</span><span class="p">:</span> <span class="n">Point</span>
    <span class="kd">var</span> <span class="nv">size</span><span class="p">:</span> <span class="n">Size</span>
<span class="p">}</span>
</code></pre></div><p>我们利用一开始定义的 Rect 结构体声明一个 rect，坐标在原点，宽高都为 2.0，并将其转换为 JSON 数据：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">rect</span> <span class="p">=</span> <span class="n">Rect</span><span class="p">(</span><span class="n">position</span><span class="p">:</span> <span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">),</span> <span class="n">size</span><span class="p">:</span> <span class="n">Size</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">))</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">rectData</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">JSONEncoder</span><span class="p">().</span><span class="n">encode</span><span class="p">(</span><span class="n">rect</span><span class="p">)</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="nb">String</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">rectData</span><span class="p">,</span> <span class="n">encoding</span><span class="p">:</span> <span class="p">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>team 对应的 JSON 字符串如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Json" data-lang="Json"><span class="p">{</span><span class="nt">&#34;position&#34;</span><span class="p">:{</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">},</span><span class="nt">&#34;size&#34;</span><span class="p">:{</span><span class="nt">&#34;width&#34;</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span><span class="nt">&#34;height&#34;</span><span class="p">:</span><span class="mi">2</span><span class="p">}}</span>
</code></pre></div><p>但是呢，十里不想让 JSON 数据中 x 和 y 嵌套在 positon 中，也不想 width 和 height 嵌套在 size 中，而是像下面的样子：</p>
<div class="highlight"><pre class="chroma"><code class="language-Json" data-lang="Json"><span class="p">{</span>
    <span class="nt">&#34;x&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="nt">&#34;y&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="nt">&#34;width&#34;</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">,</span>
    <span class="nt">&#34;height&#34;</span><span class="p">:</span> <span class="mf">2.0</span>
<span class="p">}</span>
</code></pre></div><p>要实现这种需求，我们必须自定义 <code>Encodable</code> 协议的 <code>encode(_:)</code> 方法 和 <code>Decodable</code> 协议的 <code>init(from:)</code> 方法，实现自定义的编码解码逻辑，大体分下面几步：</p>
<ul>
<li>定义 CodingKeys 枚举，元素与目标 JSON 数据的键名对应，定义 x 和 y 而不是 position，定义 width 和 height 而不是 size</li>
<li>删除 Rect 定义中的 Codable</li>
<li>扩展 Rect 遵循 Encodable 协议，并实现 <code>encode(_:)</code> 方法</li>
<li>扩展 Rect 遵循 Decodable 协议，并实现 <code>init(from:)</code> 方法</li>
</ul>
<p>最终 Rect 定义如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">struct</span> <span class="nc">Rect</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nv">position</span><span class="p">:</span> <span class="n">Point</span>
    <span class="kd">var</span> <span class="nv">size</span><span class="p">:</span> <span class="n">Size</span>
    
    <span class="kd">enum</span> <span class="nc">CodingKeys</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="n">CodingKey</span> <span class="p">{</span>
        <span class="k">case</span> <span class="n">x</span>
        <span class="k">case</span> <span class="n">y</span>
        <span class="k">case</span> <span class="n">width</span>
        <span class="k">case</span> <span class="n">height</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="nc">Rect</span><span class="p">:</span> <span class="n">Encodable</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">encode</span><span class="p">(</span><span class="n">to</span> <span class="n">encoder</span><span class="p">:</span> <span class="n">Encoder</span><span class="p">)</span> <span class="kr">throws</span> <span class="p">{</span>
        <span class="kd">var</span> <span class="nv">container</span> <span class="p">=</span> <span class="n">encoder</span><span class="p">.</span><span class="n">container</span><span class="p">(</span><span class="n">keyedBy</span><span class="p">:</span> <span class="n">CodingKeys</span><span class="p">.</span><span class="kc">self</span><span class="p">)</span>
        <span class="k">try</span> <span class="n">container</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="n">position</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="p">.</span><span class="n">x</span><span class="p">)</span>
        <span class="k">try</span> <span class="n">container</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="n">position</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="p">.</span><span class="n">y</span><span class="p">)</span>
        <span class="k">try</span> <span class="n">container</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="n">size</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="p">.</span><span class="n">width</span><span class="p">)</span>
        <span class="k">try</span> <span class="n">container</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="p">.</span><span class="n">height</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="nc">Rect</span><span class="p">:</span> <span class="n">Decodable</span> <span class="p">{</span>
    <span class="kd">init</span><span class="p">(</span><span class="n">from</span> <span class="n">decoder</span><span class="p">:</span> <span class="n">Decoder</span><span class="p">)</span> <span class="kr">throws</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nv">container</span> <span class="p">=</span> <span class="k">try</span> <span class="n">decoder</span><span class="p">.</span><span class="n">container</span><span class="p">(</span><span class="n">keyedBy</span><span class="p">:</span> <span class="n">CodingKeys</span><span class="p">.</span><span class="kc">self</span><span class="p">)</span>
        <span class="kd">let</span> <span class="nv">x</span> <span class="p">=</span> <span class="k">try</span> <span class="n">container</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="nb">Double</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="p">.</span><span class="n">x</span><span class="p">)</span>
        <span class="kd">let</span> <span class="nv">y</span> <span class="p">=</span> <span class="k">try</span> <span class="n">container</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="nb">Double</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="p">.</span><span class="n">y</span><span class="p">)</span>
        <span class="n">position</span> <span class="p">=</span> <span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="n">y</span><span class="p">)</span>
        <span class="kd">let</span> <span class="nv">width</span> <span class="p">=</span> <span class="k">try</span> <span class="n">container</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="nb">Double</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="p">.</span><span class="n">width</span><span class="p">)</span>
        <span class="kd">let</span> <span class="nv">height</span> <span class="p">=</span> <span class="k">try</span> <span class="n">container</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="nb">Double</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="p">.</span><span class="n">height</span><span class="p">)</span>
        <span class="n">size</span> <span class="p">=</span> <span class="n">Size</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="n">height</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>测试一下编码实现：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">rect</span> <span class="p">=</span> <span class="n">Rect</span><span class="p">(</span><span class="n">position</span><span class="p">:</span> <span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">),</span> <span class="n">size</span><span class="p">:</span> <span class="n">Size</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">))</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">rectData</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">JSONEncoder</span><span class="p">().</span><span class="n">encode</span><span class="p">(</span><span class="n">rect</span><span class="p">)</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="nb">String</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">rectData</span><span class="p">,</span> <span class="n">encoding</span><span class="p">:</span> <span class="p">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>得到的 JSON 数据的打印结果为：</p>
<div class="highlight"><pre class="chroma"><code class="language-Json" data-lang="Json"><span class="p">{</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nt">&#34;width&#34;</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span><span class="nt">&#34;height&#34;</span><span class="p">:</span><span class="mi">2</span><span class="p">}</span>
</code></pre></div><p>测试一下解码的实现：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">rectString</span> <span class="p">=</span> <span class="s">&#34;&#34;&#34;
</span><span class="s">{
</span><span class="s">    &#34;</span><span class="n">x</span><span class="s">&#34;: 3,
</span><span class="s">    &#34;</span><span class="n">y</span><span class="s">&#34;: 3,
</span><span class="s">    &#34;</span><span class="n">width</span><span class="s">&#34;: 2.5,
</span><span class="s">    &#34;</span><span class="n">height</span><span class="s">&#34;: 2.5
</span><span class="s">}
</span><span class="s">&#34;&#34;&#34;</span>

<span class="k">if</span> <span class="kd">let</span> <span class="nv">json</span> <span class="p">=</span> <span class="n">rectString</span><span class="p">.</span><span class="n">data</span><span class="p">(</span><span class="n">using</span><span class="p">:</span> <span class="p">.</span><span class="n">utf8</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nv">newRect</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">JSONDecoder</span><span class="p">().</span><span class="n">decode</span><span class="p">(</span><span class="n">Rect</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="n">json</span><span class="p">)</span>
    <span class="bp">print</span><span class="p">(</span><span class="n">newRect</span><span class="p">!)</span>
<span class="p">}</span>
</code></pre></div><p>得到的 newRect 实例对象打印结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">Rect</span><span class="p">(</span><span class="n">position</span><span class="p">:</span> <span class="n">__lldb_expr_21</span><span class="p">.</span><span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="mf">3.0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="mf">3.0</span><span class="p">),</span> <span class="n">size</span><span class="p">:</span> <span class="n">__lldb_expr_21</span><span class="p">.</span><span class="n">Size</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="mf">2.5</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="mf">2.5</span><span class="p">))</span>
</code></pre></div><h1 id="codable-带来的福利">Codable 带来的福利</h1>
<p>上面说了这么多的 Codable ，到底我们能用它来干什么呢，最主要的两个应用方向就是自定义类型数据持久化和网络通信。</p>
<h2 id="数据持久化">数据持久化</h2>
<p>其中一种常用的数据持久化方式就是属性列表(Property List)，<code>UserDefaults.standard</code> 适合存储轻量级的本地数据，其提供了与默认数据库相交互的编程接口。其实它存储在应用程序的一个plist文件里，路径为应用沙盒Document目录平级的 <code>/Library/Prefereces</code> 里。另外，其只能存储可以序列化的数据类型，比如我们一开始说的那些基本类型，也就是 Codable 的，所以一旦我们自定义的数据类型遵循了 Codable 协议，即可序列化了，那我们的自定义类型的数据可以自由存取了。</p>
<p><strong>注意：</strong></p>
<ul>
<li>使用这种方式一般用于存储应用程序的配置信息</li>
<li>手动调用 <code>synchronize</code> 方法可以立马将数据持久化存储</li>
</ul>
<p>这里只是以这种数据持久化为例，讲一下如何持久化自定义数据类型的数据：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">rectDemo</span> <span class="p">=</span> <span class="n">Rect</span><span class="p">(</span><span class="n">position</span><span class="p">:</span> <span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">),</span> <span class="n">size</span><span class="p">:</span> <span class="n">Size</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">))</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">demoData</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">JSONEncoder</span><span class="p">().</span><span class="n">encode</span><span class="p">(</span><span class="n">rectDemo</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">UserDefaults</span><span class="p">.</span><span class="n">standard</span><span class="p">.</span><span class="kr">set</span><span class="p">(</span><span class="n">demoData</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="s">&#34;rect-test&#34;</span><span class="p">)</span>
    <span class="n">UserDefaults</span><span class="p">.</span><span class="n">standard</span><span class="p">.</span><span class="n">synchronize</span><span class="p">()</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;saved successfully!&#34;</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">if</span> <span class="kd">let</span> <span class="nv">rectJson</span> <span class="p">=</span> <span class="n">UserDefaults</span><span class="p">.</span><span class="n">standard</span><span class="p">.</span><span class="n">value</span><span class="p">(</span><span class="n">forKey</span><span class="p">:</span> <span class="s">&#34;rect-test&#34;</span><span class="p">)</span> <span class="k">as</span><span class="p">?</span> <span class="n">Data</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nv">newRect</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">JSONDecoder</span><span class="p">().</span><span class="n">decode</span><span class="p">(</span><span class="n">Rect</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="n">rectJson</span><span class="p">)</span>
    <span class="bp">print</span><span class="p">(</span><span class="n">newRect</span><span class="p">!)</span>
<span class="p">}</span>
</code></pre></div><p>可以看到打印结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">saved</span> <span class="n">successfully</span><span class="p">!</span>
<span class="n">Rect</span><span class="p">(</span><span class="n">position</span><span class="p">:</span> <span class="n">__lldb_expr_29</span><span class="p">.</span><span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">),</span> <span class="n">size</span><span class="p">:</span> <span class="n">__lldb_expr_29</span><span class="p">.</span><span class="n">Size</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">))</span>
</code></pre></div><h2 id="网络通信">网络通信</h2>
<p>HTTP/HTTPS 网络通信中 JSON 是常用的交互数据类型，假如我们编写一个网络接口，当外部请求的时候，我们为其返回一个响应，如果我们定义一个 Codable 的 Response 类型，可以方便生成响应数据：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">struct</span> <span class="nc">Response</span><span class="p">:</span> <span class="n">Codable</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nv">ststus</span><span class="p">:</span> <span class="nb">Int</span>
    <span class="kd">var</span> <span class="nv">message</span><span class="p">:</span> <span class="nb">String</span>
<span class="p">}</span>

<span class="kd">let</span> <span class="nv">res</span> <span class="p">=</span> <span class="n">Response</span><span class="p">(</span><span class="n">ststus</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="s">&#34;OK&#34;</span><span class="p">)</span>
<span class="kd">let</span> <span class="nv">resData</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">JSONEncoder</span><span class="p">().</span><span class="n">encode</span><span class="p">(</span><span class="n">res</span><span class="p">)</span>
</code></pre></div><h1 id="总结">总结</h1>
<p>Swift 4 开始支持的 Codable 大大简化了对自定义类型数据序列化的实现，相信您会用得到！</p>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之本地消息通知</title>
			<link>https://blog.5km.studio/2019/03/17/macOS-dev-local-notification/</link>
			<pubDate>Sun, 17 Mar 2019 11:33:15 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/03/17/macOS-dev-local-notification/</guid>
			<description>macOS 中的消息推送分为本地消息通知和远程消息通知，本文十里将介绍一下本地消息通知，展示一些常规的使用方法，方便大家了解本地消息推送的实现过程。 开</description>
			<content type="html"><![CDATA[<p>macOS 中的消息推送分为本地消息通知和远程消息通知，本文十里将介绍一下本地消息通知，展示一些常规的使用方法，方便大家了解本地消息推送的实现过程。</p>
<h1 id="开发平台">开发平台</h1>
<ul>
<li>macOS 10.14.3</li>
<li>swift 4.2.1</li>
<li>xcode 10.1</li>
</ul>
<h1 id="简单介绍">简单介绍</h1>
<p>本地消息通知(Notification)是由 App 请求用户消息中心(User Notifications Center)而推送的，我们的 App 既是消息的提供者又是消息的接受者，一个 App 最多支持 64 个消息通知！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/sq37y.svg" alt=""></p>
<h2 id="消息通知组成">消息通知组成</h2>
<p>一个消息通知一般在显示上看由标题(title)、副标题(subtitle)、消息内容、logo和按钮组成。另外还包括提示音的设置和用户信息(用于传递数据)。</p>
<h2 id="消息通知的类型">消息通知的类型</h2>
<p>消息通知主要有三种类型：无、横幅(barner)和提示(alert)</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/gvq8q.png" alt=""></p>
<p>默认情况下使用 <em>横幅</em> 样式！</p>
<h2 id="通知推送的触发条件">通知推送的触发条件</h2>
<p>消息推送的触发条件有多种方式，消息中心会根据指定的触发条件推送消息：</p>
<ul>
<li>直接推送通知，一般是根据 App 中自身逻辑灵活手动触发</li>
<li>按照指定时间间隔推送通知，可以设置为重复或者不重复</li>
<li>根据指定的日期时间推送通知，可以设置是否重复</li>
<li>根据地理位置推送通知，通常要设置经纬度坐标以及范围(方圆距离)</li>
</ul>
<h2 id="消息通知的处理">消息通知的处理</h2>
<p>本地消息通知通过用户消息通知中心的消息队列进行管理，根据通知的触发条件推送的消息会依次存放到这个消息队列，然后依次通知给 App，而 App 可以通过对消息中心的代理实现代理方法对消息通知行为进行相应处理。</p>
<h2 id="消息通知的管理">消息通知的管理</h2>
<p>消息通知的管理由通知中心完成，包括消息通知的注册、删除、推送以及权限。</p>
<h3 id="消息通知的注册">消息通知的注册</h3>
<p>要想让 App 配置好的通知能够按照预期进行推送，必须要将通知注册到通知中心。</p>
<h3 id="消息通知的推送">消息通知的推送</h3>
<p>通知中心会时刻监听着每个消息通知，一旦满足触发条件就会像消息通知队列中推送消息。</p>
<h3 id="消息通知的删除">消息通知的删除</h3>
<p>有时需要关闭已经注册的消息通知的推送活动，通知中心可以将指定消息通知删除，不再监管。</p>
<h3 id="消息通知的权限">消息通知的权限</h3>
<p>在 macOS 的系统偏好设置中可以设置指定应用的通知权限：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/n1q5z.png" alt=""></p>
<p>这些通知权限在 App 中可以由通知中心进行管理，通常就是消息弹窗和声音播放两点。</p>
<h2 id="关于实现">关于实现</h2>
<p>上面说了本地消息通知的一些基本概念和简单介绍，那么实现本地消息通知的流程就很清楚了：</p>
<ul>
<li>消息通知的配置，包括触发条件、消息通知中内容</li>
<li>注册消息通知到消息通知中心</li>
<li>实现消息通知中心的代理方法，从而完成对消息的处理</li>
</ul>
<p>目前看 Apple 官方的开发接口有两套：</p>
<ul>
<li>传统的消息通知接口，在 <code>Foundation</code> 框架中实现，SDK 支持明确标记 <code>macOS 10.8–10.14</code> <code>Deprecated</code>，也就是说 10.14 之后便废弃了</li>
<li>最新的消息通知接口，与多个软件平台(iOS、watchOS、tvOS)共用，使用 <code>UserNotifacations</code> 框架，SDK 支持
<ul>
<li>iOS 10.0+</li>
<li>macOS 10.14+</li>
<li>tvOS 10.0+</li>
<li>watchOS 3.0+</li>
</ul>
</li>
</ul>
<p>不难看出 Apple 的软件开发生态蓝图的宏大，这不在本文讨论范围内！在下面会以一个简单的例子介绍使用两套方法的实现过程，不过会着重讲新的接口！</p>
<h1 id="示例">示例</h1>
<p>下面我们通过两个简单的 Demo 看一下如何实现本地消息通知。两个 Demo 中我们分别设置一个按钮，并分别绑定 各自的 Action，一个 Action 中按照传统的方式实现消息通知，另一个 Action 中按照新的方式实现。这个消息通知具有以下实现：</p>
<ul>
<li>立马推送的</li>
<li>使用系统声音作为提示音，消息推送时播放</li>
<li>通知携带数据在用户信息中</li>
<li>通知设置两个按钮，一个是关闭一个是确定按钮，点击后打印传递的用户信息</li>
</ul>
<h2 id="传统实现方式">传统实现方式</h2>
<ul>
<li>打开 Xcode 新建一个使用 storyboard 的工程，我们就命名为 <code>OldNotificationDemo</code></li>
<li>打开 <code>Main.storyboard</code> ，在 view controller 中添加一个按钮，按钮标题改为 <code>通知</code></li>
<li>为两个按钮绑定 action，在 <code>Main.storyboard</code> 中按下快捷键 <code>Option</code> + <code>Command</code> + 回车键 打开辅助编辑器，按住 <code>ctrl</code> 键的同时鼠标左键拖动按钮到 ViewController.swift 的 ViewController 类中绑定 action 为 <code>oldNotificationAction</code></li>
</ul>
<h3 id="更改通知样式">更改通知样式</h3>
<p>传统接口下，App 默认使用横幅的通知样式，但是横幅的通知只能显示 reply button 和 other button，但是我们想自己定义一个按钮，只能使用提示的样式，所以我们首先更改一下通知样式，需要在 Info.plist 文件中添加一个新的键——<code>NSUserNotificationAlertStyle</code>，值设置为 <code>alert</code>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/pg2li.png" alt=""></p>
<h3 id="oldnotificationaction-实现">oldNotificationAction 实现</h3>
<p>下面在 oldNotificationAction 中添加使用传统方法消息通知的实现:</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">oldNotificationAction</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nv">userNotification</span> <span class="p">=</span> <span class="n">NSUserNotification</span><span class="p">()</span>
    
    <span class="n">userNotification</span><span class="p">.</span><span class="n">title</span> <span class="p">=</span> <span class="s">&#34;传统方式&#34;</span>
    <span class="n">userNotification</span><span class="p">.</span><span class="n">subtitle</span> <span class="p">=</span> <span class="s">&#34;old&#34;</span>
    <span class="n">userNotification</span><span class="p">.</span><span class="n">informativeText</span> <span class="p">=</span> <span class="s">&#34;我是一个传统的方式&#34;</span>
    
    <span class="n">userNotification</span><span class="p">.</span><span class="n">hasActionButton</span> <span class="p">=</span> <span class="kc">true</span>
    <span class="n">userNotification</span><span class="p">.</span><span class="n">otherButtonTitle</span> <span class="p">=</span> <span class="s">&#34;关闭&#34;</span>
    <span class="n">userNotification</span><span class="p">.</span><span class="n">actionButtonTitle</span> <span class="p">=</span> <span class="s">&#34;显示&#34;</span>
    
    <span class="n">userNotification</span><span class="p">.</span><span class="n">identifier</span> <span class="p">=</span> <span class="s">&#34;OLD_NOTIFICATION_DEMO&#34;</span>
    <span class="n">userNotification</span><span class="p">.</span><span class="n">userInfo</span> <span class="p">=</span> <span class="p">[</span><span class="s">&#34;method&#34;</span><span class="p">:</span> <span class="s">&#34;old&#34;</span><span class="p">]</span>
    
    <span class="n">userNotification</span><span class="p">.</span><span class="n">soundName</span> <span class="p">=</span> <span class="n">NSUserNotificationDefaultSoundName</span>
    
    <span class="n">NSUserNotificationCenter</span><span class="p">.</span><span class="k">default</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
    <span class="n">NSUserNotificationCenter</span><span class="p">.</span><span class="k">default</span><span class="p">.</span><span class="n">deliver</span><span class="p">(</span><span class="n">userNotification</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><ul>
<li>上面使用消息通知中心的 <code>deliver</code> 方法直接推送消息，如果要设置其它触发方式的通知需要使用通知中心的 <code>scheduleNotification</code> 方法</li>
<li>将要传递用户数据设置在 <code>userNotification</code> 的 <code>userInfo</code> 中</li>
<li>设置了通知中心的代理为 self，所以要完成剩下的实现，还需要实现代理方法</li>
<li>要显示 action 按钮必须设置 <code>userNotification</code> 的 <code>hasActionButton</code> 为 <code>true</code></li>
</ul>
<h3 id="实现代理方法">实现代理方法</h3>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">extension</span> <span class="nc">ViewController</span><span class="p">:</span> <span class="n">NSUserNotificationCenterDelegate</span> <span class="p">{</span>
    
    <span class="c1">// 当 App 在前台时是否弹出通知</span>
    <span class="kd">func</span> <span class="nf">userNotificationCenter</span><span class="p">(</span><span class="kc">_</span> <span class="n">center</span><span class="p">:</span> <span class="n">NSUserNotificationCenter</span><span class="p">,</span> <span class="n">shouldPresent</span> <span class="n">notification</span><span class="p">:</span> <span class="n">NSUserNotification</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nb">Bool</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kc">true</span>
    <span class="p">}</span>
    
    <span class="c1">// 推送消息后的回调</span>
    <span class="kd">func</span> <span class="nf">userNotificationCenter</span><span class="p">(</span><span class="kc">_</span> <span class="n">center</span><span class="p">:</span> <span class="n">NSUserNotificationCenter</span><span class="p">,</span> <span class="n">didDeliver</span> <span class="n">notification</span><span class="p">:</span> <span class="n">NSUserNotification</span><span class="p">)</span> <span class="p">{</span>
        <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">(</span><span class="n">timeIntervalSinceNow</span><span class="p">:</span> <span class="mi">0</span><span class="si">))</span><span class="s"> -&gt; 消息已经推送&#34;</span><span class="p">)</span>
    <span class="p">}</span>
    
    <span class="c1">// 用户点击了通知后的回调</span>
    <span class="kd">func</span> <span class="nf">userNotificationCenter</span><span class="p">(</span><span class="kc">_</span> <span class="n">center</span><span class="p">:</span> <span class="n">NSUserNotificationCenter</span><span class="p">,</span> <span class="n">didActivate</span> <span class="n">notification</span><span class="p">:</span> <span class="n">NSUserNotification</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="n">notification</span><span class="p">.</span><span class="n">activationType</span> <span class="p">{</span>
        <span class="k">case</span> <span class="p">.</span><span class="n">actionButtonClicked</span><span class="p">:</span>
            <span class="kd">let</span> <span class="nv">method</span> <span class="p">=</span> <span class="n">notification</span><span class="p">.</span><span class="n">userInfo</span><span class="p">![</span><span class="s">&#34;method&#34;</span><span class="p">]</span> <span class="k">as</span><span class="p">!</span> <span class="nb">String</span>
            <span class="bp">print</span><span class="p">(</span><span class="s">&#34;methods -&gt; </span><span class="si">\(</span><span class="n">method</span><span class="si">)</span><span class="s">&#34;</span><span class="p">)</span>
        <span class="k">case</span> <span class="p">.</span><span class="n">contentsClicked</span><span class="p">:</span>
            <span class="bp">print</span><span class="p">(</span><span class="s">&#34;clicked&#34;</span><span class="p">)</span>
        <span class="k">case</span> <span class="p">.</span><span class="n">replied</span><span class="p">:</span>
            <span class="bp">print</span><span class="p">(</span><span class="s">&#34;replied&#34;</span><span class="p">)</span>
        <span class="k">case</span> <span class="p">.</span><span class="n">additionalActionClicked</span><span class="p">:</span>
            <span class="bp">print</span><span class="p">(</span><span class="s">&#34;additional action&#34;</span><span class="p">)</span>
        <span class="k">default</span><span class="p">:</span>
            <span class="bp">print</span><span class="p">(</span><span class="s">&#34;action&#34;</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
<span class="p">}</span>
</code></pre></div><ul>
<li>代理方法 <code>userNotificationCenter(:shouldPresent:)-&gt;Bool</code> 中如果返回 false，那么 App 的窗口是当前系统桌面显示的窗口，就不会弹出通知也不会播放提示音</li>
<li>通知中心推送消息后会调用 <code>userNotificationCenter(:didDeliver:)</code></li>
<li>当用户操作弹窗时，比如点击弹窗、点击弹窗上的按钮时，<code>userNotificationCenter(:didActivate)</code> 方法就会被调用，在其中要实现对各种操作的处理</li>
</ul>
<h3 id="运行程序">运行程序</h3>
<p>运行程序后点击窗口中按钮，不出意外就会看到通知弹窗，同时控制台会打印推送消息，点击弹窗的按钮或弹窗可以看到控制台打印了相应的信息！</p>
<p>Demo 下载：<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/jfc5c.zip">OldNotificationDemo</a></p>
<h2 id="最新实现方式">最新实现方式</h2>
<ul>
<li>打开 Xcode 新建一个使用 storyboard 的工程，我们就命名为 <code>NotificationDemo</code></li>
<li>打开 <code>Main.storyboard</code> ，在 view controller 中添加一个按钮，按钮标题改为 <code>通知</code></li>
<li>为两个按钮绑定 action，在 <code>Main.storyboard</code> 中按下快捷键 <code>Option</code> + <code>Command</code> + 回车键 打开辅助编辑器，按住 <code>ctrl</code> 键的同时鼠标左键拖动按钮到 ViewController.swift 的 ViewController 类中绑定 action 为 <code>notificationAction</code></li>
</ul>
<h3 id="关于样式">关于样式</h3>
<p>新的实现方式与传统的实现方式不同的是，在样式为**横幅(barner)**时，将鼠标放置在通知弹窗上可以显示自定义的 action 按钮，所以这里没必要更改样式！</p>
<h3 id="notificationaction-实现">notificationAction 实现</h3>
<p>因为新的方式使用的是 <code>UserNotifications</code> 框架，所以需要先导入模块，在 <code>ViewController.swift</code> 中添加代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">import</span> <span class="nc">UserNotifications</span>
</code></pre></div><p>新的方式通知的实现过程与传统的有很大不同，流程大概是：</p>
<ul>
<li>先创建一个 <code>UNMutableNotificationContent</code> 来设置通知的内容，包括标题、内容、图片、标识符、提示声音以及用户数据</li>
<li>(可选) 创建一个触发器，触发器的类型有很多：
<ul>
<li><code>UNCalendarNotificationTrigger</code>: 通过指定日期和时间进行触发</li>
<li><code>UNTimeIntervalNotificationTrigger</code>: 通过设置指定时间间隔和是否重复来触发</li>
<li><code>UNLocationNotificationTrigger</code>: 通过指定地理坐标及地域范围来触发</li>
</ul>
</li>
<li>(可选) 创建操作集合，这个操作集合类型为 <code>UNNotificationCategory</code> 对应通知弹窗的按钮，集合中元素为 <code>UNNotificationAction</code> 实例，需要调用通知中心的 <code>setNotificationCategories</code> 方法添加生效。
<ul>
<li>barner样式下直接显示两个操作项按钮</li>
<li>alert 样式下集合下的操作项会显示为 <code>操作</code> 的子项</li>
<li><code>UNNotificationAction</code> 创建时需要指定唯一标识符、显示名称和选项，标识符用于后期区分 action 进行操作处理</li>
<li>集合的唯一标识符与通知内容实例的唯一标识符统一起来时，才能在 barner 样式下显示按钮</li>
</ul>
</li>
<li>然后通过上面创建好的通知内容实例和触发器创建一个通知请求，它是 <code>UNNotificationRequest</code> 实例，还需要指定一个唯一标识符，另外如果指定的触发器为空，通知中心会立即推送通知</li>
<li>最后指定通知中心的代理实例，一般情况就是类自身即 self，之后调用通知中心实例的 <code>add</code> 方法将通知请求添加到通知中心实例，这个通知中心实例使用系统当前的就可以，调用 <code>UNUserNotificationCenter.current()</code> 即可获得</li>
</ul>
<p>所以根据我们的实现目标，<code>notificationAction</code> 的实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">notificationAction</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nv">content</span> <span class="p">=</span> <span class="n">UNMutableNotificationContent</span><span class="p">()</span>
    <span class="n">content</span><span class="p">.</span><span class="n">title</span> <span class="p">=</span> <span class="s">&#34;新的方式&#34;</span>
    <span class="n">content</span><span class="p">.</span><span class="n">body</span> <span class="p">=</span> <span class="s">&#34;我是一个新的方式&#34;</span>
    
    <span class="n">content</span><span class="p">.</span><span class="n">userInfo</span> <span class="p">=</span> <span class="p">[</span><span class="s">&#34;method&#34;</span><span class="p">:</span> <span class="s">&#34;new&#34;</span><span class="p">]</span>
    
    <span class="n">content</span><span class="p">.</span><span class="n">sound</span> <span class="p">=</span> <span class="n">UNNotificationSound</span><span class="p">.</span><span class="k">default</span>
    <span class="n">content</span><span class="p">.</span><span class="n">categoryIdentifier</span> <span class="p">=</span> <span class="s">&#34;NOTIFICATION_DEMO&#34;</span>
    
    <span class="kd">let</span> <span class="nv">acceptAction</span> <span class="p">=</span> <span class="n">UNNotificationAction</span><span class="p">(</span><span class="n">identifier</span><span class="p">:</span> <span class="s">&#34;SHOW_ACTION&#34;</span><span class="p">,</span> <span class="n">title</span><span class="p">:</span> <span class="s">&#34;显示&#34;</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="p">.</span><span class="kd">init</span><span class="p">(</span><span class="n">rawValue</span><span class="p">:</span> <span class="mi">0</span><span class="p">))</span>
    <span class="kd">let</span> <span class="nv">declineAction</span> <span class="p">=</span> <span class="n">UNNotificationAction</span><span class="p">(</span><span class="n">identifier</span><span class="p">:</span> <span class="s">&#34;CLOSE_ACTION&#34;</span><span class="p">,</span> <span class="n">title</span><span class="p">:</span> <span class="s">&#34;关闭&#34;</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="p">.</span><span class="kd">init</span><span class="p">(</span><span class="n">rawValue</span><span class="p">:</span> <span class="mi">0</span><span class="p">))</span>
    <span class="kd">let</span> <span class="nv">testCategory</span> <span class="p">=</span> <span class="n">UNNotificationCategory</span><span class="p">(</span><span class="n">identifier</span><span class="p">:</span> <span class="s">&#34;NOTIFICATION_DEMO&#34;</span><span class="p">,</span>
                                              <span class="n">actions</span><span class="p">:</span> <span class="p">[</span><span class="n">acceptAction</span><span class="p">,</span> <span class="n">declineAction</span><span class="p">],</span>
                                              <span class="n">intentIdentifiers</span><span class="p">:</span> <span class="p">[],</span>
                                              <span class="n">hiddenPreviewsBodyPlaceholder</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">,</span>
                                              <span class="n">options</span><span class="p">:</span> <span class="p">.</span><span class="n">customDismissAction</span><span class="p">)</span>
    
    <span class="kd">let</span> <span class="nv">request</span> <span class="p">=</span> <span class="n">UNNotificationRequest</span><span class="p">(</span><span class="n">identifier</span><span class="p">:</span> <span class="s">&#34;NOTIFICATION_DEMO_REQUEST&#34;</span><span class="p">,</span>
                                        <span class="n">content</span><span class="p">:</span> <span class="n">content</span><span class="p">,</span>
                                        <span class="n">trigger</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
    
    <span class="c1">// Schedule the request with the system.</span>
    <span class="kd">let</span> <span class="nv">notificationCenter</span> <span class="p">=</span> <span class="n">UNUserNotificationCenter</span><span class="p">.</span><span class="n">current</span><span class="p">()</span>
    <span class="n">notificationCenter</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
    <span class="n">notificationCenter</span><span class="p">.</span><span class="n">setNotificationCategories</span><span class="p">([</span><span class="n">testCategory</span><span class="p">])</span>
    <span class="n">notificationCenter</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">request</span><span class="p">)</span> <span class="p">{</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="k">in</span>
        <span class="k">if</span> <span class="n">error</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
            <span class="c1">// Handle any errors.</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p><strong>注意：</strong></p>
<p><code>testCategory</code> 和 <code>content</code> 一定要使用一致的标识符，否则通知横幅样式下不会显示 action 按钮</p>
<h3 id="代理方法的实现">代理方法的实现</h3>
<p>直接贴出实现：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">extension</span> <span class="nc">ViewController</span><span class="p">:</span> <span class="n">UNUserNotificationCenterDelegate</span> <span class="p">{</span>
    
    <span class="c1">// 用户点击弹窗后的回调</span>
    <span class="kd">func</span> <span class="nf">userNotificationCenter</span><span class="p">(</span><span class="kc">_</span> <span class="n">center</span><span class="p">:</span> <span class="n">UNUserNotificationCenter</span><span class="p">,</span> <span class="n">didReceive</span> <span class="n">response</span><span class="p">:</span> <span class="n">UNNotificationResponse</span><span class="p">,</span> <span class="n">withCompletionHandler</span> <span class="n">completionHandler</span><span class="p">:</span> <span class="p">@</span><span class="n">escaping</span> <span class="p">()</span> <span class="p">-&gt;</span> <span class="nb">Void</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nv">userInfo</span> <span class="p">=</span> <span class="n">response</span><span class="p">.</span><span class="n">notification</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">content</span><span class="p">.</span><span class="n">userInfo</span>
        <span class="k">switch</span> <span class="n">response</span><span class="p">.</span><span class="n">actionIdentifier</span> <span class="p">{</span>
        <span class="k">case</span> <span class="s">&#34;SHOW_ACTION&#34;</span><span class="p">:</span>
            <span class="bp">print</span><span class="p">(</span><span class="n">userInfo</span><span class="p">)</span>
        <span class="k">case</span> <span class="s">&#34;CLOSE_ACTION&#34;</span><span class="p">:</span>
            <span class="bp">print</span><span class="p">(</span><span class="s">&#34;Nothing to do&#34;</span><span class="p">)</span>
        <span class="k">default</span><span class="p">:</span>
            <span class="k">break</span>
        <span class="p">}</span>
        <span class="n">completionHandler</span><span class="p">()</span>
    <span class="p">}</span>
    
    <span class="c1">// 配置通知发起时的行为 alert -&gt; 显示弹窗, sound -&gt; 播放提示音</span>
    <span class="kd">func</span> <span class="nf">userNotificationCenter</span><span class="p">(</span><span class="kc">_</span> <span class="n">center</span><span class="p">:</span> <span class="n">UNUserNotificationCenter</span><span class="p">,</span> <span class="n">willPresent</span> <span class="n">notification</span><span class="p">:</span> <span class="n">UNNotification</span><span class="p">,</span> <span class="n">withCompletionHandler</span> <span class="n">completionHandler</span><span class="p">:</span> <span class="p">@</span><span class="n">escaping</span> <span class="p">(</span><span class="n">UNNotificationPresentationOptions</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nb">Void</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">completionHandler</span><span class="p">([.</span><span class="n">alert</span><span class="p">,</span> <span class="p">.</span><span class="n">sound</span><span class="p">])</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><ul>
<li>两个回调中都有一个逃逸闭包参数，是一个完成处理的回调，一定要执行相应的 <code>completionHandler</code></li>
<li><code>userNotificationCenter(:willPresent:withCompletionHandler)</code> 方法中通过 <code>completionHandler</code> 配置通知行为，这里配置既显示弹窗又播放提示音</li>
</ul>
<h3 id="关于消息通知权限">关于消息通知权限</h3>
<p>其实严格来讲，一个 app 在第一次启动的时候要向系统请求设置通知权限的，等在之后的所有启动的时候就不需要请求设置权限了，只需每次读取系统偏好设置中的权限配置来实现相应的通知行为。貌似在 macOS 中不做这个操作，目前也没什么影响，如果想进一步了解权限可以阅读 <a href="https://developer.apple.com/documentation/usernotifications/asking_permission_to_use_notifications">Asking Permission to Use Notifications</a>!</p>
<h3 id="验证实现">验证实现</h3>
<p>运行程序，点击窗口中的按钮就能看到通知了！将鼠标放在通知上，就能显示操作按钮，点击按钮就能在 xcode 控制器窗口看到相应的打印信息了。</p>
<p>Demo 下载：<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2019-03-17-NotificationDemo.zip">NotificationDemo</a></p>
<h1 id="总结">总结</h1>
<p>到目前为止，我们尝试了两种方式实现消息通知的推送，如果有其它的实现需求，比如按日期时间推送消息，直接参考阅读 Apple 官方的资料吧！Apple 的各大系统平台接口融合是大势所趋，如果有必要，大家赶快根据新的实现方式替换马上要淘汰的方法吧！</p>
<h1 id="参考">参考</h1>
<ul>
<li><a href="https://blog.gaelfoppolo.com/user-notifications-in-macos-66c25ed5c692">User Notifications in macOS</a></li>
<li><a href="https://developer.apple.com/documentation/foundation/nsusernotificationcenter">NSUserNotificationCenter</a></li>
<li><a href="https://developer.apple.com/documentation/usernotifications/unusernotificationcenter">UNUserNotificationCenter</a></li>
</ul>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之 NSTextField 文本的垂直居中</title>
			<link>https://blog.5km.studio/2019/03/10/macOS-dev-vertically-centered-textfield/</link>
			<pubDate>Sun, 10 Mar 2019 15:26:20 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/03/10/macOS-dev-vertically-centered-textfield/</guid>
			<description>在 macOS 开发中不知道您是否为 NSTextField 的垂直居中的问题困扰过呢！从今天这篇文章开始，这个问题将不再是问题，本文将介绍一种可以实现 NSTextField 垂直居中的方法，大概可</description>
			<content type="html"><![CDATA[<p>在 macOS 开发中不知道您是否为 NSTextField 的垂直居中的问题困扰过呢！从今天这篇文章开始，这个问题将不再是问题，本文将介绍一种可以实现 NSTextField 垂直居中的方法，大概可以满足我们的需求！</p>
<h2 id="开发平台">开发平台</h2>
<ul>
<li>macOS 10.14.3</li>
<li>swift 4.2.1</li>
<li>xcode 10.1</li>
</ul>
<h2 id="准备-demo">准备 Demo</h2>
<p>还是老规矩，以实例出发，理解实现！新建一个 macOS app 的工程，使用 storyboard，这里工程就命名为 TextFieldDemo 吧。创建成功后，打开 <code>Main.storyboard</code> ，往 <code>View Controller</code> 中添加一个 <code>Text Field</code> ，调整其大小贴齐窗口四边，添加必要的布局限制，并将以下内容作为 Text Field 的内容：</p>
<blockquote>
<p>漫威电影宇宙（英语：Marvel Cinematic Universe，简称MCU）是由漫威漫画工作室基于漫威漫画出版物中的角色独立制作的一系列电影所构成的架空世界和共同世界（Earth-199999）。</p>
</blockquote>
<p>就像下面这样(十里这里将窗口调小了一点):</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190310154814.png" alt=""></p>
<p>运行程序可以看到显示文本是默认向上对齐的:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190310155150.png" alt=""></p>
<h2 id="实现思路">实现思路</h2>
<p>最终我们要实现 Demo 窗口中的文本垂直居中显示，要想做到这一点，我们得了解一下文本框显示文本的过程。NSTextField 类通过 NSTextFieldCell 类实现用户界面的显示，所以关注点应该放在 NSTextFieldCell 类上，文本是在 text field cell 上的，而 TextField 又是 text field cell 的一个容器，所以只需要调整 text field cell 在 Text Field 中的坐标，如果 y 轴坐标值合适就能实现居中，在 Cocoa 中，控件使用如下坐标系(flipped coordinate)：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190310171441.png" alt=""></p>
<p>NSTextFieldCell 类继承于 NSCell 类，所以可以通过调整绘制过程中 text field cell 的 frame 坐标数据实现其位置调整，对应这个绘制过程的方法就是 <code>drawingRect(forBounds:)</code>，新坐标的计算公式为：</p>
<p>$$
y_{new} = y_{old} + (Height_{frame} - Height_{text}) \times 0.5
$$</p>
<p>其中：</p>
<ul>
<li>$Height_{frame}$ 是指默认生成的 cell 的 frame</li>
<li>$Height_{text}$ 是文本实际占用的 frame</li>
</ul>
<p>为了更彻底一点，我们也可以调整 frame 的高度与文本实际占用的高度一致：</p>
<p>$$
Height_{frame} = Height_{text}
$$</p>
<p>也许您看了下面的图就明白上面公式的含义了：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190310182541.png" alt=""></p>
<h2 id="具体实现">具体实现</h2>
<p>根据上面的思路可以有两种实现方式，一种是定义一个 <code>NSTextFielCell</code> 的子类，在子类中重写方法，另一种是扩展的方式重写 <code>NSTextFieldCell</code> 的方法。第二种会影响 app 中所有的 TextField，所以这里采用第一种方式。</p>
<p>在 <code>AppDelegate.swift</code> 文件中定义子类 <code>VerticallyCenteredTextFieldCell</code> 如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">class</span> <span class="nc">VerticallyCenteredTextFieldCell</span><span class="p">:</span> <span class="n">NSTextFieldCell</span> <span class="p">{</span>
    
    <span class="kr">override</span> <span class="kd">func</span> <span class="nf">drawingRect</span><span class="p">(</span><span class="n">forBounds</span> <span class="n">theRect</span><span class="p">:</span> <span class="n">NSRect</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="n">NSRect</span> <span class="p">{</span>
        <span class="kd">var</span> <span class="nv">newRect</span><span class="p">:</span><span class="n">NSRect</span> <span class="p">=</span> <span class="kc">super</span><span class="p">.</span><span class="n">drawingRect</span><span class="p">(</span><span class="n">forBounds</span><span class="p">:</span> <span class="n">theRect</span><span class="p">)</span>
        <span class="kd">let</span> <span class="nv">textSize</span><span class="p">:</span><span class="n">NSSize</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">cellSize</span><span class="p">(</span><span class="n">forBounds</span><span class="p">:</span> <span class="n">theRect</span><span class="p">)</span>
        <span class="kd">let</span> <span class="nv">heightDelta</span><span class="p">:</span><span class="n">CGFloat</span> <span class="p">=</span> <span class="n">newRect</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span> <span class="o">-</span> <span class="n">textSize</span><span class="p">.</span><span class="n">height</span>
        <span class="k">if</span> <span class="n">heightDelta</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">{</span>
            <span class="n">newRect</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span> <span class="p">=</span> <span class="n">textSize</span><span class="p">.</span><span class="n">height</span>
            <span class="n">newRect</span><span class="p">.</span><span class="n">origin</span><span class="p">.</span><span class="n">y</span> <span class="o">+=</span> <span class="n">heightDelta</span> <span class="o">/</span> <span class="mi">2</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">newRect</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>然后打开 <code>Main.storyboard</code> 文件，选中之前添加的 Text Field 的 cell ，按快捷键 <code>Option</code> + <code>Command</code> + <code>3</code> 打开 Identity 检查器，在 <code>Custom Class</code> 中为 cell 指定刚定义的子类 <code>VerticallyCenteredTextFieldCell</code>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190310183739.png" alt=""></p>
<p>运行程序就能看到文本实现了居中显示：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190310184822.png" alt=""></p>
<p>Demo下载：<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/VerticallyCenteredTextFieldDemo.zip">VerticallyCenteredTextFieldDemo.zip</a></p>
]]></content>
		</item>
		
		<item>
			<title>快快拿走，Xcode 常用快捷键</title>
			<link>https://blog.5km.studio/2019/03/07/mac-dev-keyboard-shortcuts/</link>
			<pubDate>Thu, 07 Mar 2019 14:42:16 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/03/07/mac-dev-keyboard-shortcuts/</guid>
			<description>无论您进行 macOS 开发还是 iOS 开发，大多数情况下都会用到 Xcode，那么本文就分享一下十里整理的常用快捷键，希望对您有所帮助！ 说明 Xocde 的快捷键不会随着</description>
			<content type="html"><![CDATA[<p>无论您进行 macOS 开发还是 iOS 开发，大多数情况下都会用到 Xcode，那么本文就分享一下十里整理的常用快捷键，希望对您有所帮助！</p>
<h1 id="说明">说明</h1>
<ul>
<li>Xocde 的快捷键不会随着版本更迭而变化太多，但还是要说明一下本文的快捷键均能在 Xcode 10.2 中正常使用</li>
<li>正文中均使用快捷键修饰键的符号表示修饰符：
<ul>
<li>Control ⌃</li>
<li>Option ⌥</li>
<li>Shift ⇧</li>
<li>Command ⌘</li>
<li>Return ↩</li>
</ul>
</li>
</ul>
<h1 id="窗口操作">窗口操作</h1>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190307154213.jpg" alt=""></p>
<h1 id="常规">常规</h1>
<table>
<thead>
<tr>
<th>快捷键</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>⌘,</td>
<td>打开偏好设置</td>
</tr>
<tr>
<td>⇧⌘0</td>
<td>打开帮助窗口</td>
</tr>
<tr>
<td>⇧⌘c</td>
<td>打开调试区域的终端栏</td>
</tr>
<tr>
<td>⌘?</td>
<td>打开帮助搜索</td>
</tr>
<tr>
<td>⌘n</td>
<td>新建文件</td>
</tr>
<tr>
<td>⌘o</td>
<td>打开文件</td>
</tr>
<tr>
<td>⇧⌘a</td>
<td>向工程中添加文件</td>
</tr>
</tbody>
</table>
<h1 id="编译及运行">编译及运行</h1>
<table>
<thead>
<tr>
<th>快捷键</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>⌘b</td>
<td>编译工程</td>
</tr>
<tr>
<td>⇧⌘b</td>
<td>分析工程</td>
</tr>
<tr>
<td>⌘r</td>
<td>运行工程</td>
</tr>
<tr>
<td>⌘i</td>
<td>打开 Profile</td>
</tr>
<tr>
<td>⌘u</td>
<td>测试工程</td>
</tr>
<tr>
<td>⇧⌘k</td>
<td>清理工程编译中间文件</td>
</tr>
<tr>
<td>⌘k</td>
<td>清空终端打印的信息</td>
</tr>
<tr>
<td>⌘.</td>
<td>停止运行</td>
</tr>
</tbody>
</table>
<h1 id="代码编辑">代码编辑</h1>
<table>
<thead>
<tr>
<th>快捷键</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>⌃Space</td>
<td>显示补全提示项</td>
</tr>
<tr>
<td>⌃.</td>
<td>下一个补全提示项</td>
</tr>
<tr>
<td>Tab</td>
<td>选中补全提示项</td>
</tr>
<tr>
<td>⌃/</td>
<td>下一个代码 placeholder 项</td>
</tr>
<tr>
<td>⇧⌃/</td>
<td>上一个代码 placeholder 项</td>
</tr>
<tr>
<td>⌘/</td>
<td>注释或取消注释代码</td>
</tr>
<tr>
<td>⌥⌘←</td>
<td>折叠代码（方法或类）</td>
</tr>
<tr>
<td>⌥⌘→</td>
<td>展开折叠代码</td>
</tr>
<tr>
<td>⌃⌘e</td>
<td>编辑修改当前文件中所有出现光标处单词的地方</td>
</tr>
</tbody>
</table>
<h1 id="代码导航">代码导航</h1>
<table>
<thead>
<tr>
<th>快捷键</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>⌘→</td>
<td>跳转到行尾</td>
</tr>
<tr>
<td>⌘←</td>
<td>跳转到行首</td>
</tr>
<tr>
<td>⌘↑</td>
<td>跳转到文件顶部</td>
</tr>
<tr>
<td>⌘↓</td>
<td>跳转到文件底部</td>
</tr>
<tr>
<td>⌥→</td>
<td>向后跳过一个单词</td>
</tr>
<tr>
<td>⌥←</td>
<td>向前跳过一个单词</td>
</tr>
<tr>
<td>⌘l</td>
<td>跳转到指定行</td>
</tr>
<tr>
<td>⌘f</td>
<td>在文件中查找</td>
</tr>
<tr>
<td>⇧⌘f</td>
<td>在工程中查找</td>
</tr>
<tr>
<td>⌘g</td>
<td>查找下一个</td>
</tr>
<tr>
<td>⇧⌘g</td>
<td>查找上一个</td>
</tr>
<tr>
<td>⌃⌘ + 鼠标左键单击</td>
<td>跳转到定义</td>
</tr>
<tr>
<td>⌥ + 鼠标左键单击</td>
<td>快速帮助</td>
</tr>
<tr>
<td>⌃⌘d</td>
<td>显示光标处关键字的快速帮助</td>
</tr>
</tbody>
</table>
<h1 id="文件导航">文件导航</h1>
<table>
<thead>
<tr>
<th>快捷键</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>⌃⌘←</td>
<td>前一个打开的文件</td>
</tr>
<tr>
<td>⌃⌘→</td>
<td>后一个打开的文件</td>
</tr>
<tr>
<td>⇧⌘o</td>
<td>快速打开文件</td>
</tr>
<tr>
<td>⌃1</td>
<td>显示与指定代码相关的文件</td>
</tr>
<tr>
<td>⌘n</td>
<td>新建文件</td>
</tr>
<tr>
<td>⌘o</td>
<td>打开文件</td>
</tr>
<tr>
<td>⇧⌘a</td>
<td>向工程中添加文件</td>
</tr>
</tbody>
</table>
<h1 id="调试">调试</h1>
<table>
<thead>
<tr>
<th>快捷键</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>⌘ \</td>
<td>添加或删除断点</td>
</tr>
<tr>
<td>⌘y</td>
<td>禁用或激活断点</td>
</tr>
<tr>
<td>⌃⌘y</td>
<td>继续执行</td>
</tr>
<tr>
<td>⌃⌘c</td>
<td>继续执行到当前行</td>
</tr>
<tr>
<td>F6</td>
<td>单步跳过</td>
</tr>
<tr>
<td>F7</td>
<td>单步进入</td>
</tr>
<tr>
<td>F8</td>
<td>单步跳出</td>
</tr>
</tbody>
</table>
<h1 id="总结">总结</h1>
<p>为了方便大家收藏，十里为此将上面的快捷键整理为一页 pdf ，方便大家及时查看：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/xcode_shortcuts.pdf" alt=""></p>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之定时器的使用</title>
			<link>https://blog.5km.studio/2019/03/05/macOS-dev-timer/</link>
			<pubDate>Tue, 05 Mar 2019 15:43:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/03/05/macOS-dev-timer/</guid>
			<description>前段时间开发了一款状态栏小工具 时光(Time Go) ，它用到了定时器，近期十里在想大家会不会也有用定时器的需求呢！所以呢，写本文意在分享 macOS 开发中关</description>
			<content type="html"><![CDATA[<p>前段时间开发了一款状态栏小工具 <a href="https://github.com/smslit/timeGO">时光(Time Go)</a> ，它用到了定时器，近期十里在想大家会不会也有用定时器的需求呢！所以呢，写本文意在分享 macOS 开发中关于定时器的的收获及其使用方法，我们一起进步。</p>
<h1 id="实现平台">实现平台</h1>
<ul>
<li>macOS 10.14.3</li>
<li>swift 4.2.1</li>
<li>xcode 10.1</li>
</ul>
<h1 id="准备环境">准备环境</h1>
<p>为了更好地展示定时器的使用，这里我们使用 playground 测试我们的定时器，可以使用快捷键 <code>Option</code> + <code>Shift</code> + <code>Command</code> + n 新建 playgroud 文件。在文件中我们准备一个 TimerDemo 类用于测试定时器：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">import</span> <span class="nc">Foundation</span>


<span class="kd">class</span> <span class="nc">TimerDemo</span> <span class="p">{</span>
    
    <span class="kd">private</span> <span class="kd">var</span> <span class="nv">timeCount</span> <span class="p">=</span> <span class="mi">5</span>
    
    <span class="kd">static</span> <span class="kd">let</span> <span class="nv">share</span> <span class="p">=</span> <span class="n">TimerDemo</span><span class="p">()</span>
    
    <span class="c1">// 添加不同定时器使用方法的示例代码</span>
    <span class="kd">func</span> <span class="nf">run</span><span class="p">()</span> <span class="p">{</span>
        <span class="bp">print</span><span class="p">(</span><span class="s">&#34;TimerDemo&#34;</span><span class="p">)</span>
    <span class="p">}</span>
    
<span class="p">}</span>

<span class="n">TimerDemo</span><span class="p">.</span><span class="n">share</span><span class="p">.</span><span class="n">run</span><span class="p">()</span>
</code></pre></div><p>上面定义了 TimerDemo 类，同时实现了一个 TimerDemo 的单例，方便调用方法展示效果！</p>
<h1 id="定时器的使用">定时器的使用</h1>
<p>网上能搜到有三种实现定时器的方式，因为苹果官方 API 的变化导致部分搜到的信息过时，本文整理的实现方法适用于上述描述的实现平台，近期应该不会过期，哈哈：</p>
<ul>
<li>Timer 类实现</li>
<li>GCD(<a href="https://en.wikipedia.org/wiki/Grand_Central_Dispatch">Grand Central Dispatch</a>) 的方式</li>
<li>利用屏幕刷新实现计数定时</li>
</ul>
<p>本人觉得第三种方式不是什么好的方式，所以本文只讲前两种方式。</p>
<h2 id="使用-timer-类">使用 Timer 类</h2>
<p>Timer 类的使用依赖 RunLoop(线程的事件循环，app 运行一定会有一个名为 main 的 RunLoop，主要负责界面控件的显示和交互) ，只有添加到激活状态的 RunLoop 中定时器才能正常工作。</p>
<p>定时器的运行根据次数分两种：单次和循环：</p>
<ul>
<li>单次的定时器，会在指定的时间过后，自动销毁并从 RunLoop 中弹出，而不再影响 RunLoop 的运行</li>
<li>循环的定时器，会根据指定时间间隔触发执行处理任务，需要开发者手动执行 Timer 对象的 <code>invalidate</code> 方法才能销毁并从 RunLoop 中弹出。一般使用循环定时器，不会让其一直执行，满足某种条件或执行指定次数之后，手动调用 invlaidate 方法停止定时器</li>
</ul>
<p>根据定时器的使用方式，可以分三种：</p>
<ul>
<li>使用 <code>Timer(timeInterval:repeats:block:)</code> 或 <code>Timer(timeInterval:target:selector:userInfo:repeats:)</code> 类方法初始化 Timer 对象，将对象手动添加到指定 RunLoop 中执行</li>
<li>使用 <code>Timer(fire:interval:repeats:block:)</code> 或 <code>Timer(fireAt:interval:target:selector:userInfo:repeats:)</code> 类方法初始化 Timer 对象，将对象手动添加到指定 RunLoop 中执行</li>
<li>使用 <code>Timer.scheduledTimer(withTimeInterval:repeats:block:)</code> 和 <code>Timer.scheduledTimer(timeInterval:target:selector:userInfo:repeats:)</code> 类方法，这两个方法都会创建 Timer 对象，并且以默认的运行模式添加到当前的 RunLoop 中</li>
</ul>
<p>下面我们开始尝试上面的三种方式。先在上面定义的 TimerDemo 类中声明一个 timer 属性：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">private</span> <span class="kd">var</span> <span class="nv">timer</span><span class="p">:</span> <span class="n">Timer</span><span class="p">?</span>
</code></pre></div><h3 id="第一种方式">第一种方式</h3>
<h4 id="timertimeintervalrepeatsblock-类方法"><code>Timer(timeInterval:repeats:block:)</code> 类方法</h4>
<ul>
<li>timeInterval 是定时器的触发时间间隔，单位为秒</li>
<li>repeats 布尔类型，代表是否重复，如果为 true ，定时器是循环定时器，如果为 false ，定时器是单次的</li>
<li>block 为 <code>((timer: Timer?) -&gt; Void)</code> 类型的闭包，其中 timer 是定时器自身</li>
</ul>
<p>我们为 TimerDemo 类定义一个 <code>oneshot1</code> 方法，使用 <code>Timer(timeInterval:repeats:block:)</code> 类方法实现一个单次的定时器，如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">// 使用 Timer(timeInterval:repeats:block:) 初始化对象，并将其添加到 main 线程的 RunLoop 中，单次</span>
<span class="kd">func</span> <span class="nf">oneshot1</span><span class="p">()</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: Timer(timeInterval:repeats:block:) 初始化，单次&#34;</span><span class="p">)</span>
    <span class="n">timer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">(</span><span class="n">timeInterval</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">repeats</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="n">block</span><span class="p">:</span> <span class="p">{</span> <span class="n">timer</span> <span class="k">in</span>
        <span class="kc">self</span><span class="p">.</span><span class="n">oneFireHandler</span><span class="p">(</span><span class="n">timer</span><span class="p">)</span>
    <span class="p">})</span>
    <span class="n">RunLoop</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">timer</span><span class="p">!,</span> <span class="n">forMode</span><span class="p">:</span> <span class="p">.</span><span class="n">common</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>可以看到定时器时间间隔是 1 秒，单次，并且闭包中调用定义的 <code>oneFireHandler</code> 方法，此方法是用来处理我们需要做的定时任务的，比如我们打印一个字符串：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@objc</span> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">oneFireHandler</span><span class="p">(</span><span class="kc">_</span> <span class="n">timer</span><span class="p">:</span> <span class="n">Timer</span><span class="p">?)</span> <span class="p">-&gt;</span> <span class="nb">Void</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: 单次倒计时结束！&#34;</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>还需要注意的是，<code>oneshot1</code> 中初始化得到 Timer 对象后，执行 <code>RunLoop.main.add(timer:forMode:)</code> 方法将定时器加入到 RunLoop 中，这一步是必须的，其中 <code>forMode</code> 参数我们使用默认模式(<code>common</code>)，可以防止其它高优先级 mode 的事件影响定时器的运行。打印信息时同时打印了当前时间，主要作为参考看倒计时对不对。下面我们测试一下我们定义的 <code>oneshot1</code> 方法:</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">TimerDemo</span><span class="p">.</span><span class="n">share</span><span class="p">.</span><span class="n">oneshot1</span><span class="p">()</span>
</code></pre></div><p>可以看到打印结果，确实是 1 秒的时间间隔，且执行了定时器任务。</p>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash">2019-03-05 13:51:07 +0000: Timer<span class="o">(</span>timeInterval:repeats:block:<span class="o">)</span> 初始化，单次
2019-03-05 13:51:08 +0000: 单次倒计时结束！
</code></pre></div><p>上面 <code>oneshot1</code> 和 <code>oneFirehandler(timer:)</code> 的实现可以简化为下面的形式，看您的使用习惯和需求决定使用哪种形式了：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">func</span> <span class="nf">oneshot1</span><span class="p">()</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: Timer(timeInterval:repeats:block:) 初始化，单次&#34;</span><span class="p">)</span>
    <span class="n">timer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">(</span><span class="n">timeInterval</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">repeats</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span> <span class="n">timer</span> <span class="k">in</span>
        <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: 单次倒计时结束！&#34;</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="n">RunLoop</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">timer</span><span class="p">!,</span> <span class="n">forMode</span><span class="p">:</span> <span class="p">.</span><span class="n">common</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>我们继续试一下<strong>循环定时器</strong>，先定义一个循环定时器要处理的任务：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@objc</span> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">loopFireHandler</span><span class="p">(</span><span class="kc">_</span> <span class="n">timer</span><span class="p">:</span> <span class="n">Timer</span><span class="p">?)</span> <span class="p">-&gt;</span> <span class="nb">Void</span> <span class="p">{</span>
    <span class="k">if</span> <span class="kc">self</span><span class="p">.</span><span class="n">timeCount</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="p">{</span>
        <span class="n">timer</span><span class="p">!.</span><span class="n">invalidate</span><span class="p">()</span>
        <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: 循环倒计时结束！&#34;</span><span class="p">)</span>
        <span class="kc">self</span><span class="p">.</span><span class="n">timeCount</span> <span class="p">=</span> <span class="mi">5</span>
        <span class="k">return</span>
    <span class="p">}</span>
    <span class="kc">self</span><span class="p">.</span><span class="n">timeCount</span> <span class="o">-=</span> <span class="mi">1</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: 倒计时 </span><span class="si">\(</span><span class="kc">self</span><span class="p">.</span><span class="n">timeCount</span><span class="si">)</span><span class="s"> 秒&#34;</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p><code>loopFireHandler</code> 方法中进行倒计时，到 0 时，调用定时器对象的 <code>invalidate</code> 方法释放定时器。另外定时器的实现如下 <code>loop1</code> 方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">func</span> <span class="nf">loop1</span><span class="p">()</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: Timer(timeInterval:repeats:block:) 初始化，循环&#34;</span><span class="p">)</span>
    <span class="n">timer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">(</span><span class="n">timeInterval</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">repeats</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">block</span><span class="p">:</span> <span class="p">{</span> <span class="n">timer</span> <span class="k">in</span>
        <span class="kc">self</span><span class="p">.</span><span class="n">loopFireHandler</span><span class="p">(</span><span class="n">timer</span><span class="p">)</span>
    <span class="p">})</span>
    <span class="n">RunLoop</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">timer</span><span class="p">!,</span> <span class="n">forMode</span><span class="p">:</span> <span class="p">.</span><span class="n">common</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>可以看到只是 <code>repeats</code> 参数改为 true，block 中调用 <code>loopFireHandler</code> 方法，下面测试一下 <code>loop1</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">TimerDemo</span><span class="p">.</span><span class="n">share</span><span class="p">.</span><span class="n">loop1</span><span class="p">()</span>
</code></pre></div><p>运行结果如我们所想，实现了 5 秒倒计时：</p>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash">2019-03-05 14:10:22 +0000: Timer<span class="o">(</span>timeInterval:repeats:block:<span class="o">)</span> 初始化，循环
2019-03-05 14:10:23 +0000: 倒计时 <span class="m">4</span> 秒
2019-03-05 14:10:24 +0000: 倒计时 <span class="m">3</span> 秒
2019-03-05 14:10:25 +0000: 倒计时 <span class="m">2</span> 秒
2019-03-05 14:10:26 +0000: 倒计时 <span class="m">1</span> 秒
2019-03-05 14:10:27 +0000: 倒计时 <span class="m">0</span> 秒
2019-03-05 14:10:28 +0000: 循环倒计时结束！
</code></pre></div><h4 id="timertimeintervaltargetselectoruserinforepeats-类方法"><code>Timer(timeInterval:target:selector:userInfo:repeats:)</code> 类方法</h4>
<ul>
<li>timeInterval 定时器时间间隔，单位为秒</li>
<li>target 定时器每次循环结束后发送消息的目标对象</li>
<li>selector 指定定时器每次计时结束要执行的方法，必须为 <code>@objc</code> 的函数对象，所以上面定义的 <code>oneFireHandler</code> 和 <code>loopFireHandler</code> 最前面要加 <code>@objc</code> 关键词</li>
<li>userinfo 指定用户信息，可以指定为 nil</li>
<li>repeats 决定要不要循环执行，true 为循环，false 为单次</li>
</ul>
<p>单次和循环的定时器处理，只有方法中的 <code>repeats</code> 参数和指定的处理任务函数不一样，其它都一致，所以后面只展示循环定时器的实现。</p>
<p>使用方法如下 <code>loop2</code> 方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">// 使用 Timer(timeInterval:target:selector:userInfo:repeats:) 初始化对象，并将其添加到 main 线程的 RunLoop 中，循环</span>
<span class="kd">func</span> <span class="nf">loop2</span><span class="p">()</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: Timer(timeInterval:target:selector:userInfo:repeats:) 初始化，循环&#34;</span><span class="p">)</span>
    <span class="n">timer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">(</span><span class="n">timeInterval</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">target</span><span class="p">:</span> <span class="kc">self</span><span class="p">,</span> <span class="n">selector</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="kc">self</span><span class="p">.</span><span class="n">loopFireHandler</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">userInfo</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="n">repeats</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
    <span class="n">RunLoop</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">timer</span><span class="p">!,</span> <span class="n">forMode</span><span class="p">:</span> <span class="p">.</span><span class="n">common</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>同样需要注意的是，需要将定时器 timer 添加到 RunLoop 中去。调用这个方法 <code>TimerDemo.share.loop2()</code> 即可看到打印结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash">2019-03-05 14:21:07 +0000: Timer<span class="o">(</span>timeInterval:target:selector:userInfo:repeats:<span class="o">)</span> 初始化，循环
2019-03-05 14:21:08 +0000: 倒计时 <span class="m">4</span> 秒
2019-03-05 14:21:09 +0000: 倒计时 <span class="m">3</span> 秒
2019-03-05 14:21:10 +0000: 倒计时 <span class="m">2</span> 秒
2019-03-05 14:21:11 +0000: 倒计时 <span class="m">1</span> 秒
2019-03-05 14:21:12 +0000: 倒计时 <span class="m">0</span> 秒
2019-03-05 14:21:13 +0000: 循环倒计时结束！
</code></pre></div><h3 id="第二种方式">第二种方式</h3>
<h4 id="timerfireintervalrepeatsblock-类方法"><code>Timer(fire:interval:repeats:block:)</code> 类方法</h4>
<p>这个方法相较于 <code>Timer(timeInterval:repeats:block:)</code> 只多了一个 <code>fire</code> 参数，这个参数代表的意思是什么时候触发第一次计时结束，类型是 Date 类，可以使用方法 <code>Date(timeIntervalSinceNow:)</code> 方法指定相对于现在的时刻，这个方法参数单位是秒，最终 <code>Timer(fire:interval:repeats:block:)</code> 类方法实现循环定时器的过程如下 <code>loop3</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">//  使用 Timer(fire:interval:repeats:block:) 初始化对象，并将其添加到 main 线程的 RunLoop 中，循环</span>
<span class="kd">func</span> <span class="nf">loop3</span><span class="p">()</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: Timer(fire:interval:repeats:block:) 初始化，添加到 RunLoop，循环&#34;</span><span class="p">)</span>
    <span class="n">timer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">(</span><span class="n">fire</span><span class="p">:</span> <span class="n">Date</span><span class="p">(</span><span class="n">timeIntervalSinceNow</span><span class="p">:</span> <span class="mi">0</span><span class="p">),</span> <span class="n">interval</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">repeats</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">block</span><span class="p">:</span> <span class="p">{</span> <span class="n">timer</span> <span class="k">in</span>
        <span class="kc">self</span><span class="p">.</span><span class="n">loopFireHandler</span><span class="p">(</span><span class="n">timer</span><span class="p">)</span>
    <span class="p">})</span>
    <span class="n">RunLoop</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">timer</span><span class="p">!,</span> <span class="n">forMode</span><span class="p">:</span> <span class="p">.</span><span class="n">common</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>注意 <code>fire</code> 参数，这里我们设置的是 <code>Date(timeIntervalSinceNow: 0)</code>，也就是现在就触发一次计时结束，此时执行 <code>TimerDemo.share.loop3()</code> 可以看到结果:</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">30</span><span class="p">:</span><span class="mi">35</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="n">Timer</span><span class="p">(</span><span class="n">fire</span><span class="p">:</span><span class="n">interval</span><span class="p">:</span><span class="n">repeats</span><span class="p">:</span><span class="n">block</span><span class="p">:)</span> <span class="err">初始化，添加到</span> <span class="n">RunLoop</span><span class="err">，循环</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">30</span><span class="p">:</span><span class="mi">35</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">倒计时</span> <span class="mi">4</span> <span class="err">秒</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">30</span><span class="p">:</span><span class="mi">36</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">倒计时</span> <span class="mi">3</span> <span class="err">秒</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">30</span><span class="p">:</span><span class="mi">37</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">倒计时</span> <span class="mi">2</span> <span class="err">秒</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">30</span><span class="p">:</span><span class="mi">38</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">倒计时</span> <span class="mi">1</span> <span class="err">秒</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">30</span><span class="p">:</span><span class="mi">39</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">倒计时</span> <span class="mi">0</span> <span class="err">秒</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">30</span><span class="p">:</span><span class="mi">40</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">循环倒计时结束！</span>
</code></pre></div><p>可以看到第一行和第二行的打印时间都是 <code>2019-03-05 14:30:35 +0000</code> 这正匹配我们设置的 fire 参数，我们改一下 fire 参数为 <code>Date(timeIntervalSinceNow: 3)</code>，也就是相对于现在延后 3 秒触发第一次计时结束，执行 <code>TimerDemo.share.loop3()</code> 看一下结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">33</span><span class="p">:</span><span class="mi">00</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="n">Timer</span><span class="p">(</span><span class="n">fire</span><span class="p">:</span><span class="n">interval</span><span class="p">:</span><span class="n">repeats</span><span class="p">:</span><span class="n">block</span><span class="p">:)</span> <span class="err">初始化，添加到</span> <span class="n">RunLoop</span><span class="err">，循环</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">33</span><span class="p">:</span><span class="mi">03</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">倒计时</span> <span class="mi">4</span> <span class="err">秒</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">33</span><span class="p">:</span><span class="mi">04</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">倒计时</span> <span class="mi">3</span> <span class="err">秒</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">33</span><span class="p">:</span><span class="mi">05</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">倒计时</span> <span class="mi">2</span> <span class="err">秒</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">33</span><span class="p">:</span><span class="mi">06</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">倒计时</span> <span class="mi">1</span> <span class="err">秒</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">33</span><span class="p">:</span><span class="mi">07</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">倒计时</span> <span class="mi">0</span> <span class="err">秒</span>
<span class="mi">2019</span><span class="o">-</span><span class="mi">03</span><span class="o">-</span><span class="mi">05</span> <span class="mi">14</span><span class="p">:</span><span class="mi">33</span><span class="p">:</span><span class="mi">08</span> <span class="o">+</span><span class="mi">0000</span><span class="p">:</span> <span class="err">循环倒计时结束！</span>
</code></pre></div><p>第二行与第一行的打印时间果然差了 3 秒，这下您应该明白这个 fire 参数的含义了吧！</p>
<h4 id="timerfireatintervaltargetselectoruserinforepeats-类方法"><code>Timer(fireAt:interval:target:selector:userInfo:repeats:)</code> 类方法</h4>
<p>这个方法相对于 <code>Timer(timeInterval:target:selector:userInfo:repeats:)</code> 方法多了一个 <code>fireAt</code> 参数，这个参数与上一个类方法的 <code>fire</code> 含义一致，使用就很简单了，定义 <code>loop4</code> 方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">//  使用 Timer(fireAt:interval:target:selector:userInfo:repeats:) 初始化对象，并将其添加到 main 线程的 RunLoop 中，循环</span>
<span class="kd">func</span> <span class="nf">loop4</span><span class="p">()</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: Timer(fireAt:interval:target:selector:userInfo:repeats:) 初始化，添加到 RunLoop，循环&#34;</span><span class="p">)</span>
    <span class="n">timer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">(</span><span class="n">fireAt</span><span class="p">:</span> <span class="n">Date</span><span class="p">(</span><span class="n">timeIntervalSinceNow</span><span class="p">:</span> <span class="mi">0</span><span class="p">),</span> <span class="n">interval</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">target</span><span class="p">:</span> <span class="kc">self</span><span class="p">,</span> <span class="n">selector</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="kc">self</span><span class="p">.</span><span class="n">loopFireHandler</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">userInfo</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="n">repeats</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
    <span class="n">RunLoop</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">timer</span><span class="p">!,</span> <span class="n">forMode</span><span class="p">:</span> <span class="p">.</span><span class="n">common</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>这里就不展示执行结果了。</p>
<h3 id="第三种方式">第三种方式</h3>
<h4 id="timerscheduledtimerwithtimeintervalrepeatsblock-类方法"><code>Timer.scheduledTimer(withTimeInterval:repeats:block:)</code> 类方法</h4>
<p>此方法参数与 <code>Timer(timeInterval:repeats:block:)</code> 参数一致，所以实现定时器一定难不倒我们了，但是这里需要注意的是，使用此方法得到的 timer 对象在执行方法的时候已经添加到 RunLoop 中了，所以不需要我们手动添加到 RunLoop 了：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">//  使用 Timer.scheduledTimer(withTimeInterval:repeats:block:) 方法注册运行，循环</span>
<span class="kd">func</span> <span class="nf">loop5</span><span class="p">()</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: Timer.scheduledTimer(withTimeInterval:repeats:block:) 方法，循环&#34;</span><span class="p">)</span>
    <span class="n">timer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">.</span><span class="n">scheduledTimer</span><span class="p">(</span><span class="n">withTimeInterval</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">repeats</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">block</span><span class="p">:</span> <span class="p">{</span> <span class="n">timer</span> <span class="k">in</span>
        <span class="kc">self</span><span class="p">.</span><span class="n">loopFireHandler</span><span class="p">(</span><span class="n">timer</span><span class="p">)</span>
    <span class="p">})</span>
<span class="p">}</span>
</code></pre></div><h4 id="timerscheduledtimertimeintervaltargetselectoruserinforepeats-类方法"><code>Timer.scheduledTimer(timeInterval:target:selector:userInfo:repeats:)</code> 类方法</h4>
<p>此方法如同 <code>Timer.scheduledTimer(withTimeInterval:repeats:block:)</code> 方法，也不需要将方法返回的对象手动添加到 RunLoop 了：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">//  使用 Timer.scheduledTimer(timeInterval:target:selector:userInfo:repeats:) 方法注册运行，循环</span>
<span class="kd">func</span> <span class="nf">loop6</span><span class="p">()</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: Timer.scheduledTimer(withTimeInterval:repeats:block:) 方法，循环&#34;</span><span class="p">)</span>
    <span class="n">timer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">.</span><span class="n">scheduledTimer</span><span class="p">(</span><span class="n">timeInterval</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">target</span><span class="p">:</span> <span class="kc">self</span><span class="p">,</span> <span class="n">selector</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="kc">self</span><span class="p">.</span><span class="n">loopFireHandler</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">userInfo</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="n">repeats</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><h3 id="timer-类定时器总结">Timer 类定时器总结</h3>
<p>综上，我们算是了解了 6 中类方法实现定时器，但是这种方式有个问题：因为是运行在 main 线程的的 RunLoop 中，所以可能出现时间延迟的问题，主线程主要处理 UI 控件的交互，如果在其中再有一些运算量大的操作，必定会影响定时器的执行（阻塞），也就是说 Timer 不论是在哪个 RunLoop 中，终究会是受 RunLoop 的影响。下面我们就再了解一个不受 RunLoop 影响的 GCD 方式的定时器。</p>
<h2 id="gcd-方式的-timer">GCD 方式的 Timer</h2>
<p>通过 GCD 方式创建的定时器不会受 RunLoop 的影响，是一种比较底层的实现，所以很高效而且不受窗口控件频繁操作的影响。GCD 方式的定时器类型为 <code>DispatchSourceTimer</code>，其实例对象可以使用 <code>DispatchSource</code> 的 <code>MakeTimerSource</code> 方法分配资源而得到，这个对象有以下常用方法：</p>
<ul>
<li>resume() 开启运行或恢复运行</li>
<li>cancel() 取消定时器</li>
<li>suspend() 挂起定时器，可以使用 resumne() 恢复运行</li>
<li>setEventHandler(handler: (() -&gt; Void)?) 设置定时器任务的处理</li>
<li>setCancelHandler(handler: (() -&gt; Void)?) 设置定时器取消时的处理</li>
<li>schedule(deadline:repeating:leeway:) 配置定时器的时间参数</li>
</ul>
<h3 id="scheduledeadlinerepeatingleeway-方法">schedule(deadline:repeating:leeway:) 方法</h3>
<p>这个是 <code>DispatchSourceTimer</code> 对象用来配置时间参数的方法，本节主要介绍一下其参数。</p>
<ul>
<li>deadline 类型为 <code>DispatchTime</code> ，这个时间精度可以达到纳秒级，含义与上面 Timer 类的 <code>Timer(fire:interval:repeats:block:)</code> 方法的 <code>fire</code> 一致，不过类型不一样。<code>DispatchTime</code> 有 now() 方法用来获取现在的时间，可以与 <code>DispatchTimeInterval</code> 对象实现 + 操作，得到一个相对于时刻，<code>DispatchTimeInterval</code> 对象可以通过调用 <code>seconds(_:Int)</code> 方法得到秒，<code>milliseconds(_:Int)</code> 方法得到毫秒，<code>microseconds(_:Int)</code> 方法得到微秒，<code>nanoseconds(_:Int)</code> 方法得到纳秒</li>
<li>repeating 与 Timer 类方法中的 repeat 不是一个意思，这里代表的是定时器时间间隔也就是 <strong>timeInterval</strong>，类型为 <code>DispatchTimeInterval</code>，比如指定一秒可以 <code>DispatchTimeInterval.seconds(1)</code>。这个参数具有默认值，当不指定这个参数的时候，这个参数就会设置为 <code>DispatchTimeInterval.never</code> ，那么<strong>定时器只执行一次</strong>。</li>
<li>leeway 时间容差，是一个定时时间的宽容度，具有一个默认值，所以调用此方法的时候可以不用指定这个参数，如果要指定的话，也是 <code>DispatchTimeInterval</code> 类型</li>
</ul>
<h3 id="使用方法">使用方法</h3>
<p>讲了上面这么多，还是用实际的例子说明，首先在 TimerDemo 类中添加如下属性：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">private</span> <span class="kd">var</span> <span class="nv">gcdTimer</span><span class="p">:</span> <span class="n">DispatchSourceTimer</span><span class="p">?</span>
</code></pre></div><p>单次定时器，如方法 <code>oneshot7</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">// GCD 方式的定时器，单次</span>
<span class="kd">func</span> <span class="nf">oneshot7</span><span class="p">()</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: GCD 方式的定时器，单次&#34;</span><span class="p">)</span>
    <span class="n">gcdTimer</span> <span class="p">=</span> <span class="n">DispatchSource</span><span class="p">.</span><span class="n">makeTimerSource</span><span class="p">()</span>
    <span class="n">gcdTimer</span><span class="p">?.</span><span class="n">setEventHandler</span><span class="p">()</span> <span class="p">{</span>
        <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: 单次倒计时结束！&#34;</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="n">gcdTimer</span><span class="p">?.</span><span class="n">schedule</span><span class="p">(</span><span class="n">deadline</span><span class="p">:</span> <span class="p">.</span><span class="n">now</span><span class="p">()</span> <span class="o">+</span> <span class="p">.</span><span class="n">seconds</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
    <span class="n">gcdTimer</span><span class="p">?.</span><span class="n">resume</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div><p>循环定时器，如方法 <code>loop7</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">// GCD 方式的定时器，循环</span>
<span class="kd">func</span> <span class="nf">loop7</span><span class="p">()</span> <span class="p">{</span>
    <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: GCD 方式的定时器，循环&#34;</span><span class="p">)</span>
    <span class="n">gcdTimer</span> <span class="p">=</span> <span class="n">DispatchSource</span><span class="p">.</span><span class="n">makeTimerSource</span><span class="p">()</span>
    <span class="n">gcdTimer</span><span class="p">?.</span><span class="n">setEventHandler</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="kc">self</span><span class="p">.</span><span class="n">timeCount</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="p">{</span>
            <span class="kc">self</span><span class="p">.</span><span class="n">gcdTimer</span><span class="p">?.</span><span class="n">cancel</span><span class="p">()</span>
            <span class="k">return</span>
        <span class="p">}</span>
        <span class="kc">self</span><span class="p">.</span><span class="n">timeCount</span> <span class="o">-=</span> <span class="mi">1</span>
        <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: 倒计时 </span><span class="si">\(</span><span class="kc">self</span><span class="p">.</span><span class="n">timeCount</span><span class="si">)</span><span class="s"> 秒&#34;</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="n">gcdTimer</span><span class="p">?.</span><span class="n">setCancelHandler</span><span class="p">()</span> <span class="p">{</span>
        <span class="bp">print</span><span class="p">(</span><span class="s">&#34;</span><span class="si">\(</span><span class="n">Date</span><span class="si">())</span><span class="s">: 倒计时结束！&#34;</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="n">gcdTimer</span><span class="p">?.</span><span class="n">schedule</span><span class="p">(</span><span class="n">deadline</span><span class="p">:</span> <span class="p">.</span><span class="n">now</span><span class="p">()</span> <span class="o">+</span> <span class="p">.</span><span class="n">seconds</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="n">repeating</span><span class="p">:</span> <span class="p">.</span><span class="n">seconds</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
    <span class="n">gcdTimer</span><span class="p">?.</span><span class="n">resume</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div><p><strong>注意</strong></p>
<p>如果定时器任务中需要对 UI 控件进行操作，要将那部分操作放在主线程进行也就是用下面的代码包裹：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">DispatchQueue</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">async</span> <span class="p">{</span>
    <span class="c1">// UI控件操作</span>
<span class="p">}</span>
</code></pre></div><h2 id="总结">总结</h2>
<p>上述定时器演示的 playgroud 可以通过下面链接查看：</p>
<p><a href="https://gist.github.com/smslit/d62773e68d12816c4efc878395da2365">cocoaTimer.playground</a></p>
<h1 id="两种方式示例对比">两种方式示例对比</h1>
<p>为了凸显 GCD 方式定时器不受 UI 的操作的影响，本小节新建一个名为 TimerDemo 的工程，工程的 Main.storyboard 中添加一个按钮，两个 label 和一个滑块，合理放置位置，比如：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190306002519.png" alt=""></p>
<p>分别将两个 label 的按钮绑定属性到 ViewController 类，同时为按钮添加一个 action 用来启动两种定时器。两个 label 分别显示 Timer 定时器的倒计时时间和 GCD 方式的定时器的倒计时时间，当启动定时器后，我们频繁滑动滑杆，观察两个定时器的情况，最终实现的 ViewController 类如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">class</span> <span class="nc">ViewController</span><span class="p">:</span> <span class="n">NSViewController</span> <span class="p">{</span>

    <span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">timerLabel1</span><span class="p">:</span> <span class="n">NSTextField</span><span class="p">!</span>
    <span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">timerLabel2</span><span class="p">:</span> <span class="n">NSTextField</span><span class="p">!</span>
    <span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">timerButton</span><span class="p">:</span> <span class="n">NSButton</span><span class="p">!</span>
    
    <span class="kd">var</span> <span class="nv">count1</span> <span class="p">=</span> <span class="mi">60</span>
    <span class="kd">var</span> <span class="nv">count2</span> <span class="p">=</span> <span class="mi">60</span>
    <span class="kd">var</span> <span class="nv">timer</span><span class="p">:</span> <span class="n">Timer</span><span class="p">?</span>
    <span class="kd">var</span> <span class="nv">gcdTimer</span><span class="p">:</span> <span class="n">DispatchSourceTimer</span><span class="p">?</span>
    
    <span class="kr">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
        <span class="kc">super</span><span class="p">.</span><span class="n">viewDidLoad</span><span class="p">()</span>

        <span class="c1">// Do any additional setup after loading the view.</span>
    <span class="p">}</span>

    <span class="kr">override</span> <span class="kd">var</span> <span class="nv">representedObject</span><span class="p">:</span> <span class="nb">Any</span><span class="p">?</span> <span class="p">{</span>
        <span class="kr">didSet</span> <span class="p">{</span>
        <span class="c1">// Update the view, if already loaded.</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">startTimer</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">timer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">.</span><span class="n">scheduledTimer</span><span class="p">(</span><span class="n">withTimeInterval</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">repeats</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span> <span class="p">{</span> <span class="n">timer</span> <span class="k">in</span>
            <span class="k">if</span> <span class="kc">self</span><span class="p">.</span><span class="n">count1</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="p">{</span>
                <span class="n">timer</span><span class="p">.</span><span class="n">invalidate</span><span class="p">()</span>
                <span class="k">return</span>
            <span class="p">}</span>
            <span class="kc">self</span><span class="p">.</span><span class="n">count1</span> <span class="o">-=</span> <span class="mi">1</span>
            <span class="kc">self</span><span class="p">.</span><span class="n">timerLabel1</span><span class="p">.</span><span class="n">stringValue</span> <span class="p">=</span> <span class="s">&#34;timer: </span><span class="si">\(</span><span class="kc">self</span><span class="p">.</span><span class="n">count1</span><span class="si">)</span><span class="s"> 秒&#34;</span>
        <span class="p">}</span>
        
        <span class="n">gcdTimer</span> <span class="p">=</span> <span class="n">DispatchSource</span><span class="p">.</span><span class="n">makeTimerSource</span><span class="p">()</span>
        <span class="n">gcdTimer</span><span class="p">?.</span><span class="n">setEventHandler</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">if</span> <span class="kc">self</span><span class="p">.</span><span class="n">count2</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="p">{</span>
                <span class="kc">self</span><span class="p">.</span><span class="n">gcdTimer</span><span class="p">?.</span><span class="n">cancel</span><span class="p">()</span>
                <span class="k">return</span>
            <span class="p">}</span>
            <span class="kc">self</span><span class="p">.</span><span class="n">count2</span> <span class="o">-=</span> <span class="mi">1</span>
            <span class="n">DispatchQueue</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">async</span> <span class="p">{</span>
                <span class="kc">self</span><span class="p">.</span><span class="n">timerLabel2</span><span class="p">.</span><span class="n">stringValue</span> <span class="p">=</span> <span class="s">&#34;gcdTimer: </span><span class="si">\(</span><span class="kc">self</span><span class="p">.</span><span class="n">count2</span><span class="si">)</span><span class="s"> 秒&#34;</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="n">gcdTimer</span><span class="p">?.</span><span class="n">schedule</span><span class="p">(</span><span class="n">deadline</span><span class="p">:</span> <span class="p">.</span><span class="n">now</span><span class="p">()</span> <span class="o">+</span> <span class="p">.</span><span class="n">seconds</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="n">repeating</span><span class="p">:</span> <span class="p">.</span><span class="n">seconds</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="n">leeway</span><span class="p">:</span> <span class="p">.</span><span class="n">milliseconds</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
        <span class="n">gcdTimer</span><span class="p">?.</span><span class="n">resume</span><span class="p">()</span>
        
        <span class="kc">self</span><span class="p">.</span><span class="n">timerButton</span><span class="p">.</span><span class="n">isEnabled</span> <span class="p">=</span> <span class="kc">false</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>最后运行程序，疯狂滑动滑杆可以看到 GCD 方式的定时器丝毫不受影响，而 Timer 类的定时器受到了阻塞：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/timer_compare.mp4" width="960px" controls="controls"></video></p>
<p>示例工程下载：<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/TimerDemo.zip">TimerDemo</a></p>
]]></content>
		</item>
		
		<item>
			<title>已开源 app 实现检查更新的简单方式</title>
			<link>https://blog.5km.studio/2019/03/03/macOS-dev-simple-check-update/</link>
			<pubDate>Sun, 03 Mar 2019 09:38:27 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/03/03/macOS-dev-simple-check-update/</guid>
			<description>如果您开发的 app 没有上架 app store，那么您肯定会考虑如何保证用户及时收到 app 更新的问题，如果您的项目是开源在某个托管平台的，那么本文就提供一种</description>
			<content type="html"><![CDATA[<p>如果您开发的 app 没有上架 app store，那么您肯定会考虑如何保证用户及时收到 app 更新的问题，如果您的项目是开源在某个托管平台的，那么本文就提供一种简单的方式助您实现 app 检查更新的功能。</p>
<h1 id="实现平台">实现平台</h1>
<ul>
<li>macOS 10.14.3</li>
<li>swift 4.2.1</li>
<li>xcode 10.1</li>
</ul>
<h1 id="思路">思路</h1>
<p>本文提供的方法，不需要架设服务器，只要在开源平台上托管就可以。所以，这里假设您的 app 都是在托管平台发布，并且代码也是及时推送到托管平台的。</p>
<p>每个 xcode 工程中都有一个 <code>info.plist</code> 文件，使用其中的 <code>CFBundleShortVersionString</code> 键的值作为版本判断依据，app 运行起来本身能获取自己的版本号，通过网络从托管平台获取最新版本源文件中的 <code>info.plist</code> 文件，然后得到最新版本号，与自身的版本号对比，如果不一样就说明有了最新版本（更严格的判断应该是远端文件中获取的版本号新于 app 自身的版本号），然后通过提示框提醒用户有新版本发现，提示框中给出一个按钮，帮助用户跳转到开源工程最新版本的 release 页面即可。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190303111234.png" alt=""></p>
<p><strong>注</strong>：本文描述的具体方法不适用于 <strong>Xcode 11</strong> 及以上版本，因为 info.plist 的内容有变化，不过可以继续使用此文描述的思路，不同的是需要解析的文件不是 <code>info.plist</code> 而是 <code>xxxxx.xcodeproj/project.pbxproj</code> ，版本就是其中的 <code>MARKETING_VERSION</code>。</p>
<p>这里以工程托管在 github 为例，说明几点注意的地方：</p>
<ul>
<li>
<p>请求的 <code>info.plist</code> 文件链接必须是直接指向原文件的，而不是浏览代码页面对应的链接，网页 github 平台上浏览工程代码，找到 <code>info.plist</code> 文件，然后点击如图位置的 <code>raw</code> 按钮即可得到源文件的直链：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190303111930.png" alt=""></p>
</li>
<li>
<p>跳转最新 release 的页面是根据 tag 名进行跳转的，所以 tag 名称最好与版本号命名规则一致，当然不一致也可以，只要最终能对应上就可以，比如十里的一个开源项目 <a href="https://github.com/smslit/textGO">textGO</a>，对应 tag 的 release 页面链接可以轻易获得：<code>https://github.com/smslit/textGO/releases/tag/v0.3</code> ，规则就是 <code>https://github.com/smslit/textGO/releases/tag/v</code> + 版本号</p>
</li>
<li>
<p>第一步网络请求 <code>info.plist</code> 文件使用 GET 请求即可完成，网络请求相关内容可以通过另一篇文章了解：<a href="/2019/01/26/get_post_cocoa/">macOS 开发之实现 HTTP 的 GET 和 POST 请求</a></p>
</li>
</ul>
<h1 id="示例">示例</h1>
<p>下面以十里的开源项目 <a href="https://github.com/smslit/textGO">textGO</a> 为例说明一下实现。</p>
<h2 id="更新器类">更新器类</h2>
<p>因为十里将项目托管到的是 github 平台，所以封装的更新器类仅适用于 github，如果适配其它平台只需修改两处 url 的处理即可。更新器类为 TextGoUpdater ，在文件 <code>TextGoUpdater.swift</code> 中定义，您可以打开这个<a href="https://github.com/smslit/textGO/blob/master/textGO/TextGoUpdater.swift">文件</a>查看具体实现代码。</p>
<h3 id="属性">属性</h3>
<p>TextGoUpdater 类中定义了两个属性，分别是 <code>user</code>、<code>url</code>。</p>
<ul>
<li><code>user</code> 指的是 github 的账户名</li>
<li><code>url</code> 指的是 <code>info.plist</code> 文件的直链</li>
</ul>
<h3 id="方法">方法</h3>
<ul>
<li><code>init(user:)</code> 初始化方法，初始化过程中根据提供的 github 账户名和工程名推断出 <code>info.plist</code> 文件的直链并赋给 url 属性</li>
<li><code>check(callback:)</code> 触发检查更新的过程，是公共方法。<code>callback</code> 是个逃逸闭包，完成网络请求后在进行数据处理前执行，主要方便调整控件的状态。</li>
<li><code>checkUpdateRequestSuccess(data:response:error:callback:)</code> 封装了网络请求完成后的处理过程，是私有方法。另外使用的两个 <code>tipInfo</code> 方法在 <code>TextGoPublic.swift</code> 文件中定义，用来显示提示框告知用户检测更新的结果。检测到更新以后，会根据类的 user 属性和工程名称以及最新版本号推断出 release 页面链接。</li>
</ul>
<h3 id="使用方法">使用方法</h3>
<p>使用方法很简单，主要分两种：有回调和没有回调。</p>
<h4 id="无回调使用">无回调使用</h4>
<p>可以在文件 <code>AppDelegate.swift</code> 中看到使用方法</p>
<ul>
<li>定义属性 updater</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">updater</span> <span class="p">=</span> <span class="n">TextGoUpdater</span><span class="p">(</span><span class="n">user</span><span class="p">:</span> <span class="s">&#34;smslit&#34;</span><span class="p">)</span> <span class="p">{}</span>
</code></pre></div><ul>
<li>在 <code>checkUpdate()</code> 方法中直接调用 updater 实例的 check 方法：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">checkUpdate</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">updater</span><span class="p">.</span><span class="n">check</span><span class="p">()</span> <span class="p">{}</span>
<span class="p">}</span>
</code></pre></div><h4 id="有回调使用">有回调使用</h4>
<ul>
<li>定义属性 updater</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">updater</span> <span class="p">=</span> <span class="n">TextGoUpdater</span><span class="p">(</span><span class="n">user</span><span class="p">:</span> <span class="s">&#34;smslit&#34;</span><span class="p">)</span> <span class="p">{}</span>
</code></pre></div><ul>
<li>在 <code>checkUpdate()</code> 方法中直接调用 updater 实例的 check 方法：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">checkUpdate</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">updater</span><span class="p">.</span><span class="n">check</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 实现对界面控件的调整</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><h2 id="执行效果">执行效果</h2>
<p>textGO 是一个菜单栏小工具，通过点击<code>检查更新</code>菜单项触发更新的检查，为了更好的展示效果，以视频展示，同时修改版本号来查看有更新和没更新两种情况的效果。</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/check_update_simple.mp4" width="100%" controls="controls"></video></p>
<h1 id="总结">总结</h1>
<p>本文更新器的实现思路比较简单，只是提供了检查更新的功能，不过一般情况下这就足够了，其实这种方法也能适用于托管平台的私有项目，可以在平台中共享出某个可以提供版本号的文件，得到它的直链就可以，思路一样，想办法更改两处链接就能实现。最后，祝您成功！</p>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之本地化工具 bartycrouch 4 使用教程</title>
			<link>https://blog.5km.studio/2019/03/01/macOS-dev-bartycrouch4-tutorial/</link>
			<pubDate>Fri, 01 Mar 2019 08:59:33 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/03/01/macOS-dev-bartycrouch4-tutorial/</guid>
			<description>前些日子写了三篇关于 macOS app 本地化的文章，在文章中用到了一个名为 bartycrouch 的工具，当时十里安装的 bartycrouch 版本是 v3.13.3，昨天升级了 bartycrouch 现在是 v4.0.0 ，发现使用方</description>
			<content type="html"><![CDATA[<p>前些日子写了三篇关于 macOS app 本地化的文章，在文章中用到了一个名为 bartycrouch 的工具，当时十里安装的 bartycrouch 版本是 v3.13.3，昨天升级了 bartycrouch 现在是 v4.0.0 ，发现使用方法变化还挺大，所以赶快在前面三篇文章中标注了一下版本使用问题，本文简单介绍一下 bartycrouch 4 的使用。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190301092140.png" alt=""></p>
<h2 id="开发平台">开发平台</h2>
<ul>
<li>macOS 10.14.3</li>
<li>swift 4.2.1</li>
<li>xcode 10.1</li>
</ul>
<h2 id="bartycrouch-准备">bartycrouch 准备</h2>
<blockquote>
<p>BartyCrouch incrementally updates your Strings files from your Code and from Interface Builder files. &ldquo;Incrementally&rdquo; means that BartyCrouch will by default keep both your already translated values and even your altered comments. Additionally you can also use BartyCrouch for machine translating from one language to 60+ other languages. Using BartyCrouch is as easy as running a few simple commands from the command line what can even be automated using a build script within your project.</p>
</blockquote>
<p><a href="https://github.com/Flinesoft/BartyCrouch">BartyCrouch</a></p>
<p>bartycrouch 可以依据 interfaces 文件( xib 文件) 和代码(swift 、m、h 文件)来增量更新 strings 文件。在这里 <strong>增量</strong> 是指 bartycrouch 会默认保留已经翻译的值及改变了的注释。另外您也可使用 bartycrouch 借助微软的服务从一种语言机器翻译成超过 60+ 种语言。在命令行调用几个简单的命令您就可以轻而易举的使用 bartycrouch，另外您也可以在 xcode 的工程配置中添加运行脚本自动化使用 bartycrouch 完成您期望的任务。</p>
<h3 id="依赖">依赖</h3>
<ul>
<li>Xcode 10.1+</li>
<li>Swift 4.2+</li>
<li>Xcode Command Line Tools (详见：<a href="http://stackoverflow.com/a/9329325/3451975">点我</a>)</li>
</ul>
<h3 id="安装方法">安装方法</h3>
<details>
<summary>使用 [Homebrew](https://brew.sh/) 安装</summary>
<p>通过下面的命令可以很容易安装 bartycrouch:</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">brew install bartycrouch
</code></pre></div><p>另外可以通过下面的命令更新 bartycrouch，保证一直使用最新保本:</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">brew upgrade bartycrouch
</code></pre></div></details>
<details>
<summary>通过 [Mint](https://github.com/yonaskolb/Mint) 安装</summary>
<p>通过以下命令就可以安装 bartycrouch（目前通过这种方式会出现缺少动态库的问题，估计很快就会修复了，所以建议使用 homebrew 安装的方式）:</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">mint install Flinesoft/BartyCrouch
</code></pre></div></details>
<h2 id="bartycrouch-使用">bartycrouch 使用</h2>
<p>这里提醒一下，如果您直接用一个现有的工程尝试这个工具的话，以防万一请先用 git <em>提交</em> 一下您的代码，尽量保证安全，另外这里强烈建议如果研究明白怎么用了后使用为 xcode <a href="#%E5%88%9B%E5%BB%BA%E7%BC%96%E8%AF%91%E8%84%9A%E6%9C%AC">创建编译脚本</a> 的方式使用 bartycrouch。</p>
<p>在命令行下，先执行一下 <code>bartycrouch -h</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash">$ bartycrouch -h

Usage: bartycrouch &lt;command&gt; <span class="o">[</span>options<span class="o">]</span>

Incrementally update <span class="p">&amp;</span> translate your Strings files from code or interface files.

Commands:
  init            Creates the default configuration file <span class="p">&amp;</span> creates a build script <span class="k">if</span> Xcode project found
  update          Update your .strings file contents with the configured tasks <span class="o">(</span>default: interfaces, code, normalize<span class="o">)</span>
  lint            Lints your .strings file contents
  <span class="nb">help</span>            Prints <span class="nb">help</span> information
  version         Prints the current version of this app
</code></pre></div><ul>
<li><code>help</code>: 打印帮助信息，也就是上面，<code>bartycrouch -h</code> 等同于 <code>bartycrouch help</code></li>
<li><code>version</code>: 打印当前 bartycrouch 的版本号</li>
</ul>
<p>下面以 bartycrouch 正常的使用流程分别讲一下 <code>init</code> <code>update</code> 和 <code>lint</code> 三个命令。</p>
<h3 id="init-命令">init 命令</h3>
<p>这个命令用于生成针对 Xcode 工程的 bartycrouch 配置文件。进入到 xcode 的根目录，执行以下命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash">bartycrouch init
</code></pre></div><p>工程根目录下会出现名为 <code>.bartycrouch.toml</code> 的文件。</p>
<details><summary>`.bartycrouch.toml` 默认内容</summary>
<div class="highlight"><pre class="chroma"><code class="language-toml" data-lang="toml"><span class="p">[</span><span class="nx">update</span><span class="p">]</span>
<span class="nx">tasks</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;interfaces&#34;</span><span class="p">,</span> <span class="s2">&#34;code&#34;</span><span class="p">,</span> <span class="s2">&#34;transform&#34;</span><span class="p">,</span> <span class="s2">&#34;normalize&#34;</span><span class="p">]</span>

<span class="p">[</span><span class="nx">update</span><span class="p">.</span><span class="nx">interfaces</span><span class="p">]</span>
<span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;.&#34;</span>
<span class="nx">defaultToBase</span> <span class="p">=</span> <span class="kc">false</span>
<span class="nx">ignoreEmptyStrings</span> <span class="p">=</span> <span class="kc">false</span>
<span class="nx">unstripped</span> <span class="p">=</span> <span class="kc">false</span>

<span class="p">[</span><span class="nx">update</span><span class="p">.</span><span class="nx">code</span><span class="p">]</span>
<span class="nx">codePath</span> <span class="p">=</span> <span class="s2">&#34;.&#34;</span>
<span class="nx">localizablePath</span> <span class="p">=</span> <span class="s2">&#34;.&#34;</span>
<span class="nx">defaultToKeys</span> <span class="p">=</span> <span class="kc">false</span>
<span class="nx">additive</span> <span class="p">=</span> <span class="kc">true</span>
<span class="nx">unstripped</span> <span class="p">=</span> <span class="kc">false</span>

<span class="p">[</span><span class="nx">update</span><span class="p">.</span><span class="nx">transform</span><span class="p">]</span>
<span class="nx">codePath</span> <span class="p">=</span> <span class="s2">&#34;.&#34;</span>
<span class="nx">localizablePath</span> <span class="p">=</span> <span class="s2">&#34;.&#34;</span>
<span class="nx">transformer</span> <span class="p">=</span> <span class="s2">&#34;foundation&#34;</span>
<span class="nx">supportedLanguageEnumPath</span> <span class="p">=</span> <span class="s2">&#34;.&#34;</span>
<span class="nx">typeName</span> <span class="p">=</span> <span class="s2">&#34;BartyCrouch&#34;</span>
<span class="nx">translateMethodName</span> <span class="p">=</span> <span class="s2">&#34;translate&#34;</span>

<span class="p">[</span><span class="nx">update</span><span class="p">.</span><span class="nx">normalize</span><span class="p">]</span>
<span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;.&#34;</span>
<span class="nx">sourceLocale</span> <span class="p">=</span> <span class="s2">&#34;en&#34;</span>
<span class="nx">harmonizeWithSource</span> <span class="p">=</span> <span class="kc">true</span>
<span class="nx">sortByKeys</span> <span class="p">=</span> <span class="kc">true</span>

<span class="p">[</span><span class="nx">lint</span><span class="p">]</span>
<span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;.&#34;</span>
<span class="nx">duplicateKeys</span> <span class="p">=</span> <span class="kc">true</span>
<span class="nx">emptyValues</span> <span class="p">=</span> <span class="kc">true</span>
</code></pre></div></details>
<p>在这个配置文件中包含了另外两个命令( update 和 lint )的相关配置，这是 bartycrouch 的默认配置，应该可以在绝大多数工程中使用，但是这里需要注意以下几点：</p>
<ul>
<li><code>[update]</code> 的值就代表了，执行 <code>bartycrouch update</code> 时要完成的任务，不想执行某个任务可以直接删除，比如不执行 <code>code</code> 任务，可以写成：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-toml" data-lang="toml"><span class="p">[</span><span class="nx">update</span><span class="p">]</span>
<span class="nx">tasks</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;interfaces&#34;</span><span class="p">,</span> <span class="s2">&#34;transform&#34;</span><span class="p">,</span> <span class="s2">&#34;normalize&#34;</span><span class="p">]</span>
</code></pre></div><ul>
<li>
<p>每个任务子项中都包含了对应的配置信息，可依据工程需要进行调整，具体含义可以参考后面任务的介绍</p>
</li>
<li>
<p>尽可能为包含关键字的 <code>path</code> 键提供具体的路径名称 (例如：<code>codePath</code> 设置为包含 swift 代码文件的目录)</p>
</li>
<li>
<p>如果您的工程是纯 swift 实现并且使用了 update 命令的的 <a href="#transform-%E4%BB%BB%E5%8A%A1"><code>tranform</code> 任务</a>，可以删除 <code>code</code> 任务</p>
</li>
<li>
<p>如果您正在使用支持 <code>structured-swift4</code> 模板的 <a href="https://github.com/SwiftGen/SwiftGen#strings">SwiftGen</a> ，您需要将 <code>[update.transform]</code> 的 <code>transformer</code> 从 <code>foundation</code> 改为 <code>swiftgenStructured</code></p>
</li>
<li>
<p>如果您要使用 <code>transform</code> 任务，需要在工程中创建一个新文件，命名为 <code>BartyCrouch.swift</code>，内容如下：</p>
</li>
</ul>
<details><summary>`BartyCrouch.swift` 代码</summary>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">//</span>
<span class="c1">//  This file is required in order for the `transform` task of the translation helper tool BartyCrouch to work.</span>
<span class="c1">//  See here for more details: https://github.com/Flinesoft/BartyCrouch</span>
<span class="c1">//</span>

<span class="kd">import</span> <span class="nc">Foundation</span>

<span class="kd">enum</span> <span class="nc">BartyCrouch</span> <span class="p">{</span>
    <span class="kd">enum</span> <span class="nc">SupportedLanguage</span><span class="p">:</span> <span class="nb">String</span> <span class="p">{</span>
        <span class="c1">// </span><span class="cs">TODO:</span><span class="c1"> remove unsupported languages from the following cases list &amp; add any missing languages</span>
        <span class="k">case</span> <span class="n">arabic</span> <span class="p">=</span> <span class="s">&#34;ar&#34;</span>
        <span class="k">case</span> <span class="n">chineseSimplified</span> <span class="p">=</span> <span class="s">&#34;zh-Hans&#34;</span>
        <span class="k">case</span> <span class="n">chineseTraditional</span> <span class="p">=</span> <span class="s">&#34;zh-Hant&#34;</span>
        <span class="k">case</span> <span class="n">english</span> <span class="p">=</span> <span class="s">&#34;en&#34;</span>
        <span class="k">case</span> <span class="n">french</span> <span class="p">=</span> <span class="s">&#34;fr&#34;</span>
        <span class="k">case</span> <span class="n">german</span> <span class="p">=</span> <span class="s">&#34;de&#34;</span>
        <span class="k">case</span> <span class="n">hindi</span> <span class="p">=</span> <span class="s">&#34;hi&#34;</span>
        <span class="k">case</span> <span class="n">italian</span> <span class="p">=</span> <span class="s">&#34;it&#34;</span>
        <span class="k">case</span> <span class="n">japanese</span> <span class="p">=</span> <span class="s">&#34;ja&#34;</span>
        <span class="k">case</span> <span class="n">korean</span> <span class="p">=</span> <span class="s">&#34;ko&#34;</span>
        <span class="k">case</span> <span class="n">malay</span> <span class="p">=</span> <span class="s">&#34;ms&#34;</span>
        <span class="k">case</span> <span class="n">portuguese</span> <span class="p">=</span> <span class="s">&#34;pt-BR&#34;</span>
        <span class="k">case</span> <span class="n">russian</span> <span class="p">=</span> <span class="s">&#34;ru&#34;</span>
        <span class="k">case</span> <span class="n">spanish</span> <span class="p">=</span> <span class="s">&#34;es&#34;</span>
        <span class="k">case</span> <span class="n">turkish</span> <span class="p">=</span> <span class="s">&#34;tr&#34;</span>
    <span class="p">}</span>

    <span class="kd">static</span> <span class="kd">func</span> <span class="nf">translate</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="n">translations</span><span class="p">:</span> <span class="p">[</span><span class="n">SupportedLanguage</span><span class="p">:</span> <span class="nb">String</span><span class="p">],</span> <span class="n">comment</span><span class="p">:</span> <span class="nb">String</span><span class="p">?</span> <span class="p">=</span> <span class="kc">nil</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nb">String</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nv">typeName</span> <span class="p">=</span> <span class="nb">String</span><span class="p">(</span><span class="n">describing</span><span class="p">:</span> <span class="n">BartyCrouch</span><span class="p">.</span><span class="kc">self</span><span class="p">)</span>
        <span class="kd">let</span> <span class="nv">methodName</span> <span class="p">=</span> <span class="kc">#function</span>

        <span class="bp">print</span><span class="p">(</span>
            <span class="s">&#34;Warning: [BartyCrouch]&#34;</span><span class="p">,</span>
            <span class="s">&#34;Untransformed </span><span class="si">\(</span><span class="n">typeName</span><span class="si">)</span><span class="s">.</span><span class="si">\(</span><span class="n">methodName</span><span class="si">)</span><span class="s"> method call found with key &#39;</span><span class="si">\(</span><span class="n">key</span><span class="si">)</span><span class="s">&#39; and base translations &#39;</span><span class="si">\(</span><span class="n">translations</span><span class="si">)</span><span class="s">&#39;.&#34;</span><span class="p">,</span>
            <span class="s">&#34;Please ensure that BartyCrouch is installed and configured correctly.&#34;</span>
        <span class="p">)</span>

        <span class="c1">// fall back in case something goes wrong with BartyCrouch transformation</span>
        <span class="k">return</span> <span class="s">&#34;BC: TRANSFORMATION FAILED!&#34;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></details>
<ul>
<li>
<p>如果您的第一语言不是英语, 您需要将 <code>normalize</code> 任务的 <code>sourceLocale</code> 改为您的第一语言，值参考上面代码中的枚举 <code>SupportedLanguage</code> 的元素</p>
</li>
<li>
<p>如果您想使用 bartycrouch 的翻译功能，需要将如下的配置添加到 <code>.bartycrouch.toml</code> 中，并且将 <code>translate</code> 添加到任务列表中，其中您需要将 <code>secret</code> 的值替换成您的 <a href="https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-text-how-to-signup#authentication-key">Microsoft Translator Text API Subscription Key</a>:</p>
</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-toml" data-lang="toml"><span class="p">[</span><span class="nx">update</span><span class="p">.</span><span class="nx">translate</span><span class="p">]</span>
<span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;.&#34;</span>
<span class="nx">secret</span> <span class="p">=</span> <span class="s2">&#34;&lt;#Subscription Key#&gt;&#34;</span>
<span class="nx">sourceLocale</span> <span class="p">=</span> <span class="s2">&#34;en&#34;</span>
</code></pre></div><h3 id="update-命令">update 命令</h3>
<p>update 命令可以执行以下几个任务：</p>
<ul>
<li><code>interfaces</code>: 从 Storyboards &amp; XIBs 更新 <code>.strings</code> 文件</li>
<li><code>code</code>: 根据代码中的 <code>NSLocalizedString</code> 调用更新 <code>Localizable.strings</code></li>
<li><code>transform</code>: 仅支持 swift 文件，可以替换特定方法的调用替换为可以提供多种语言翻译的单行方式</li>
<li><code>translate</code>: 从指定语言翻译成多种语言并替换结果到 strings 文件中</li>
<li><code>normalize</code>: 格式化 <code>.strings</code> 文件内容：排序或者清楚多余内容</li>
</ul>
<p>上面已经提到过的，可以通过 <code>.bartycrouch.toml</code> 的 <code>task</code> 配置要执行的任务：</p>
<div class="highlight"><pre class="chroma"><code class="language-toml" data-lang="toml"><span class="p">[</span><span class="nx">update</span><span class="p">]</span>
<span class="nx">tasks</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;interfaces&#34;</span><span class="p">,</span> <span class="s2">&#34;code&#34;</span><span class="p">,</span> <span class="s2">&#34;transform&#34;</span><span class="p">,</span> <span class="s2">&#34;normalize&#34;</span><span class="p">]</span>
</code></pre></div><h4 id="任务配置项介绍">任务配置项介绍</h4>
<details><summary>`interfaces` 配置项含义</summary>
<ul>
<li><code>path</code>: 查找 Storyboards &amp; XIB 文件的路径</li>
<li><code>defaultToBase</code>: 添加 Base translation 作为新键的值</li>
<li><code>ignoreEmptyStrings</code>: 不添加 views 中是空值的项</li>
<li><code>unstripped</code>: 保留 Strings 文件开始和结尾的空白字符串</li>
</ul>
</details>
<details><summary>`code` 配置项含义</summary>
<ul>
<li><code>codePath</code>: 查找 Swift 代码文件的路径</li>
<li><code>localizablePath</code>: 包含 <code>Localizable.strings</code> 文件的目录的父目录</li>
<li><code>defaultsToKeys</code>: 为新键添加与新键名称一致的值</li>
<li><code>additive</code>: 不清除未在代码中发现的键</li>
<li><code>customFunction</code>: 替代 <code>NSLocalizedString</code> 的函数名称</li>
<li><code>customLocalizableName</code>: 替代 <code>Localizable.strings</code> 的文件的名称</li>
<li><code>unstripped</code>: 保留 Strings 文件开始和结尾的空白字符串</li>
</ul>
</details>
<details><summary>`transform` 配置项含义</summary>
<ul>
<li><code>codePath</code>: 查找 Swift 代码文件的路径</li>
<li><code>localizablePath</code>:  包含 <code>Localizable.strings</code> 文件的目录的父目录</li>
<li><code>transformer</code>: 指定转换的模式: 若调用 <code>NSLocalizedString</code> 接口，值就设成 <code>foundation</code>; 若调用 <code>L10n</code> 形式的接口，值就设成 <code>swiftgenStructured</code></li>
<li><code>supportedLanguageEnumPath</code>: 包含 <code>SupportedLanguage</code> 枚举定义的 swift 文件的路径</li>
<li><code>typeName</code>: 包含 <code>SupportedLanguage</code> 枚举和翻译方法的类型名称</li>
<li><code>translateMethodName</code>: 翻译方法的名称</li>
<li><code>customLocalizableName</code>: 替代 <code>Localizable.strings</code> 的文件的名称</li>
</ul>
</details>
<details><summary>`translate`  配置项含义</summary>
<ul>
<li><code>path</code>: 查找 <code>.strings</code> 文件的路径</li>
<li><code>secret</code>: 您的 <a href="https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-text-how-to-signup#authentication-key">Microsoft Translator Text API Subscription Key</a>.</li>
<li><code>sourceLocale</code>: 指定从哪种语言翻译成其他语言</li>
</ul>
</details>
<details><summary>`normalize`  配置项含义</summary>
<ul>
<li><code>path</code>: 查找 <code>.strings</code> 文件的路径</li>
<li><code>sourceLocale</code>: 指定依照哪种语言对其他语言的 string 文件进行格式化</li>
<li><code>harmonizeWithSource</code>: 同步键值对的源语言</li>
<li><code>sortByKeys</code>: 按照字母顺序对键进行排序</li>
</ul>
</details>
<h4 id="transform-任务">transform 任务</h4>
<p>如果配置文件中配置了 <code>transform</code> 任务(详见 <a href="#init-%E5%91%BD%E4%BB%A4">init 命令</a>)并且已经在 xcode 中<a href="#%E5%88%9B%E5%BB%BA%E7%BC%96%E8%AF%91%E8%84%9A%E6%9C%AC">创建编译脚本</a>, 在开发中您可以使用下面简化的流程编写本地化代码:</p>
<ul>
<li>您可以使用 <code>BartyCrouch.translate</code> 替代 <code>NSLocalizedString</code> 指定键、翻译方法和注释，例如:</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kc">self</span><span class="p">.</span><span class="n">title</span> <span class="p">=</span> <span class="n">BartyCrouch</span><span class="p">.</span><span class="n">translate</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="s">&#34;onboarding.first-page.header-title&#34;</span><span class="p">,</span>  <span class="n">translations</span><span class="p">:</span> <span class="p">[.</span><span class="n">english</span><span class="p">:</span> <span class="s">&#34;Welcome!&#34;</span><span class="p">])</span>
</code></pre></div><ul>
<li>
<p>编译应用的时候, BartyCrouch 会自动向所有的 <code>Localizable.strings</code> 文件中添加您指定的键，并且为指定语言的键添加指定语言翻译结果</p>
</li>
<li>
<p>同时 BartyCrouch 会自动依据配置中指定的 <code>transformer</code> 替换上面 <code>BartyCrouch.translate</code> 的调用为对应的方法</p>
<ul>
<li>
<p>当 <code>transformer</code> 设置为 <code>foundation</code>, 上面 <code>BartyCrouch.translate</code> 的调用会替换为如下的结果:</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kc">self</span><span class="p">.</span><span class="n">title</span> <span class="p">=</span> <span class="n">NSLocalizedString</span><span class="p">(</span><span class="s">&#34;onboarding.first-page.header-title&#34;</span><span class="p">,</span> <span class="n">comment</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">)</span>
</code></pre></div></li>
<li>
<p>当 <code>transformer</code> 设置为 <code>swiftgenStructured</code>, 上面 <code>BartyCrouch.translate</code> 的调用会替换为如下的结果:</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kc">self</span><span class="p">.</span><span class="n">title</span> <span class="p">=</span> <span class="n">L10n</span><span class="p">.</span><span class="n">Onboarding</span><span class="p">.</span><span class="n">FirstPage</span><span class="p">.</span><span class="n">headerTitle</span>
</code></pre></div></li>
</ul>
</li>
</ul>
<h5 id="使用-transform-的优点">使用 <code>transform</code> 的优点:</h5>
<ul>
<li>不用为了添加翻译的键值而不断的切换不同语言的 Strings 文件</li>
<li>一旦使用了 SwiftGen, 不需要手动替换 <code>NSLocalizedString</code> 为 <code>L10n</code> 的调用了</li>
<li>与机器翻译配合使得只需在代码中使用一行代码就可以实现所有语言的翻译和值的替换</li>
</ul>
<h5 id="使用-transform-的缺点">使用 <code>transform</code> 的缺点:</h5>
<ul>
<li>只支持 swift 代码而不支持 Objective-C</li>
<li>在下次编译之前，xcode 会有一些错误提示，但是一编译就会没有了错误提示</li>
<li>由于 <a href="https://github.com/apple/swift-syntax">SwiftSyntax</a> 当前<a href="https://www.jpsim.com/evaluating-swiftsyntax-for-use-in-swiftlint/">运行不是特别快</a>所以看上去不如 <code>code</code> 任务快速. (但是这个会随着时间改善！)</li>
</ul>
<h3 id="lint-命令">lint 命令</h3>
<p>lint 命令可以检查 <code>.strings</code> 文件，主要有下面两个检查项:</p>
<ul>
<li><code>duplicateKeys</code>: 查找同一个文件中重复的键值对</li>
<li><code>emptyValues</code>: 查找空值的键值对</li>
</ul>
<h3 id="创建编译脚本">创建编译脚本</h3>
<p>为了让 xcode 可以使用 bartycrouch 来更新和检查您的 <code>.strings</code> 文件，您可以添加一个编译脚本实现。</p>
<p>按照下图所示步骤添加脚本：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190301131243.jpg" alt=""></p>
<details><summary>脚本内容(点我查看)</summary>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash"><span class="k">if</span> which bartycrouch &gt; /dev/null<span class="p">;</span> <span class="k">then</span>
    bartycrouch update -x
    bartycrouch lint -x
<span class="k">else</span>
    <span class="nb">echo</span> <span class="s2">&#34;warning: BartyCrouch not installed, download it from https://github.com/Flinesoft/BartyCrouch&#34;</span>
<span class="k">fi</span>
</code></pre></div></details>
<p>下一步确保这个运行脚本在 <code>Compiling Sources</code> 和 <code>SwiftGen</code>(如果使用了的话) 之前运行，比如在 <code>Target Dependencies</code> 后面，像下图这样：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190301131815.png" alt=""></p>
<p>现在每次运行工程，都会自动的更新和检查 <code>.strings</code> 文件，而不用手动处理了。另外如果工程的成员如果没有安装 bartycrouch 的脚本会产生警告：</p>
<blockquote>
<p>warning: BartyCrouch not installed, download it from <a href="https://github.com/Flinesoft/BartyCrouch">https://github.com/Flinesoft/BartyCrouch</a></p>
</blockquote>
<p>另外补充的是，lint 和 update 有以下三个参数可用：</p>
<ul>
<li><strong><code>-v</code>, <code>--verbose</code></strong>: 打印更多的执行信息</li>
<li><strong><code>-x</code>, <code>--xcode-output</code></strong>: 以 xcode 兼容的形式呈现错误和提醒，在 xcode 中的运行脚本中添加以后，一旦检查出错误或警告会在 xcode 的 issue 导航栏中呈现</li>
<li><strong><code>-w</code>, <code>--fail-on-warnings</code></strong>: 遇到警告的时候返回对应代码</li>
</ul>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之 NSTextField 支持文本快捷键(二): 撤销和重做</title>
			<link>https://blog.5km.studio/2019/02/27/macOS-dev-nstextfield-keys-undo-redo/</link>
			<pubDate>Wed, 27 Feb 2019 10:23:39 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/02/27/macOS-dev-nstextfield-keys-undo-redo/</guid>
			<description>在文章macOS 开发之 NSTextField 支持文本快捷键(一): 基本操作中探讨了 app 开发中键盘事件以及 NSTextfield 支持基本文本快捷键的实现方法，本文跟十里一起实现另外的两</description>
			<content type="html"><![CDATA[<p>在文章<a href="/2019/02/25/macOS-dev-nstextfield-keys/">macOS 开发之 NSTextField 支持文本快捷键(一): 基本操作</a>中探讨了 app 开发中键盘事件以及 NSTextfield 支持基本文本快捷键的实现方法，本文跟十里一起实现另外的两个操作：</p>
<ul>
<li>撤销: <code>Command</code> + <code>z</code></li>
<li>重做: <code>Shift</code> + <code>Command</code> + <code>z</code></li>
</ul>
<h1 id="实现平台">实现平台</h1>
<ul>
<li>macOS 10.14.3</li>
<li>swift 4.2.1</li>
<li>xcode 10.1</li>
</ul>
<h1 id="示例工程">示例工程</h1>
<p>我们继续使用 <a href="/2019/02/25/macOS-dev-nstextfield-keys/">macOS 开发之 NSTextField 支持文本快捷键(一): 基本操作</a> 中的 Demo 工程，点击下面链接可以下载：</p>
<p><a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/TextFieldKeyDemo.zip">TextFieldKeyDemo</a></p>
<h1 id="添加撤销和重做支持">添加撤销和重做支持</h1>
<p>这里先说一下怎么做，然后再解释。</p>
<h2 id="确认勾选-undo">确认勾选 undo</h2>
<p>NSTextField是默认支持撤销和重做的，打开 Main.storyboard 文件，点击 view controller 中的 Text Field，按快捷键 <code>Option</code> + <code>Command</code> + 4 打开 Text Field 的 Attribute Inspector 这是就会看到 Undo 是默认勾选的：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190227124649.png" alt=""></p>
<h2 id="添加快捷键支持">添加快捷键支持</h2>
<p>既然支持了 Undo，那么我们只需按照相应快捷键添加相应的操作就可以了，打开文件 AppDelegate.swift，可以看到上一篇文章中复写的 <code>performKeyEquivalent:</code> 方法，添加撤销和重做的代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">// NSTextField 支持快捷键</span>
<span class="kd">extension</span> <span class="nc">NSTextField</span> <span class="p">{</span>
    <span class="n">open</span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">performKeyEquivalent</span><span class="p">(</span><span class="n">with</span> <span class="n">event</span><span class="p">:</span> <span class="n">NSEvent</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nb">Bool</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">event</span><span class="p">.</span><span class="n">modifierFlags</span><span class="p">.</span><span class="n">isDisjoint</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="p">.</span><span class="n">command</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kc">super</span><span class="p">.</span><span class="n">performKeyEquivalent</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">event</span><span class="p">)</span>
        <span class="p">}</span>
        
        <span class="k">switch</span> <span class="n">event</span><span class="p">.</span><span class="n">charactersIgnoringModifiers</span> <span class="p">{</span>
        <span class="k">case</span> <span class="s">&#34;a&#34;</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">NSApp</span><span class="p">.</span><span class="n">sendAction</span><span class="p">(</span><span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">selectAll</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">to</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">window</span><span class="p">?.</span><span class="n">firstResponder</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span>
        <span class="k">case</span> <span class="s">&#34;c&#34;</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">NSApp</span><span class="p">.</span><span class="n">sendAction</span><span class="p">(</span><span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">copy</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">to</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">window</span><span class="p">?.</span><span class="n">firstResponder</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span>
        <span class="k">case</span> <span class="s">&#34;v&#34;</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">NSApp</span><span class="p">.</span><span class="n">sendAction</span><span class="p">(</span><span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">paste</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">to</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">window</span><span class="p">?.</span><span class="n">firstResponder</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span>
        <span class="k">case</span> <span class="s">&#34;x&#34;</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">NSApp</span><span class="p">.</span><span class="n">sendAction</span><span class="p">(</span><span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">cut</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">to</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">window</span><span class="p">?.</span><span class="n">firstResponder</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span>
        <span class="k">case</span> <span class="s">&#34;z&#34;</span><span class="p">:</span>
            <span class="kc">self</span><span class="p">.</span><span class="n">window</span><span class="p">?.</span><span class="n">firstResponder</span><span class="p">?.</span><span class="n">undoManager</span><span class="p">?.</span><span class="n">undo</span><span class="p">()</span>
            <span class="k">return</span> <span class="kc">true</span>
        <span class="k">case</span> <span class="s">&#34;Z&#34;</span><span class="p">:</span>
            <span class="kc">self</span><span class="p">.</span><span class="n">window</span><span class="p">?.</span><span class="n">firstResponder</span><span class="p">?.</span><span class="n">undoManager</span><span class="p">?.</span><span class="n">redo</span><span class="p">()</span>
            <span class="k">return</span> <span class="kc">true</span>
        <span class="k">default</span><span class="p">:</span>
            <span class="k">return</span> <span class="kc">super</span><span class="p">.</span><span class="n">performKeyEquivalent</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">event</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>添加完上述代码，按下快捷键 <code>Command</code> + <code>r</code> 运行程序，在运行起来的窗口中的文本框中输入一些字符，然后尝试按快捷键 <code>Command</code> + <code>z</code> 和 <code>Command</code> + <code>Z</code> ，恭喜你完成了撤销和重做操作的添加！</p>
<ul>
<li><code>Shift</code> + <code>Command</code> + <code>z</code> 可以理解为 <code>Command</code> + (<code>Shift</code> + <code>z</code>)，而 <code>Shift</code> + <code>z</code> 其实就是 <code>Z</code>，所以最终这个重做快捷键其实就是 <code>Command</code> + <code>Z</code>(大写的 <code>z</code>)</li>
<li>当文本框编辑的时候其就是窗口的第一响应对象，这里使用 <code>self.window?.firstResponder</code> 就是要快速定位到正在编辑的文本对象，而不是通过 NSTextField 的对象层级关系查找键盘事件的响应对象</li>
<li>在 macOS 中，撤销和重做被做了统一封装，由 UndoManager 类进行统一管理这两个操作，而对于继承 NSResponder 类的子类都是默认有一个 undoManager 属性的，类型就是 UndoManager，不需要手动创建。同样，NSTextField 也不例外。macOS 中文本对象的操作都是默认包含 undoManager 的管理的也不需要我们手动添加相关管理实现。</li>
</ul>
<h1 id="撤销和重做的实现原理">撤销和重做的实现原理</h1>
<p>上面已经提到 App 中的撤销和重做操作是由 UndoManager 类进行管理的，虽然我们知道了如何实现 NSTextField 的撤销和重做，但是 UndoManager 实现原理和使用还是得了解一下的，后面有需求要做不是文本操作的撤销和重做功能时也就更容易上手了。</p>
<h2 id="理解撤销和重做操作流程">理解撤销和重做操作流程</h2>
<p>UndoManager 通过管理两个操作栈(撤销栈和重做栈)的压栈和弹栈实现 undo 和 redo，两个操作栈中保存的是最小的操作过程，这个小的操作过程被封装为 NSInvocation 对象，然后保存在两个栈中，示意大概如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190227224058.png" alt=""></p>
<p>NSInvocation 是一种包含执行方法及参数的对象。</p>
<p>操作流程中的操作压栈和弹栈过程分三种情况：</p>
<ul>
<li>正常操作时：将马上要进行的正常操作的逆操作进行封装压入撤销栈，然后执行正常操作</li>
<li>要执行撤销操作时：先从撤销栈中弹出得到要进行的操作，将这一步操作的逆操作压入重做栈，然后执行刚弹出的操作</li>
<li>要执行重做操作时：从重做栈中弹出得到要进行的操作，将其逆向操作的封装压入撤销栈，然后进行刚弹出的操作</li>
</ul>
<h2 id="撤销和重做操作的管理">撤销和重做操作的管理</h2>
<h3 id="undomanager-对象的创建">UndoManager 对象的创建</h3>
<p>如上面提到的，大多数情况下对象中都有一个现成的 UndoManager 对象属性，可以直接使用这个属性 undoManager。继承 NSResponder 的子类由有：NSApplication、NSPopover、NSView、NSViewController、NSWindow 和 NSWindowController。</p>
<h3 id="撤销操作压栈">撤销操作压栈</h3>
<p>要将操作压入撤销栈其实就是一个注册操作的过程，有三个方法：</p>
<ul>
<li><code>registerUndo(withTarget:handler:)</code> 方法，是将对象、处理一起进行注册，使用闭包的形式，例如：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kc">self</span><span class="p">.</span><span class="n">undoManager</span><span class="p">?.</span><span class="n">registerUndo</span><span class="p">(</span><span class="n">withTarget</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span> <span class="p">{</span> <span class="p">(</span><span class="n">textField</span><span class="p">:</span> <span class="n">NSTextField</span><span class="p">)</span> <span class="k">in</span>
    <span class="n">textField</span><span class="p">.</span><span class="n">stringValue</span> <span class="p">=</span> <span class="s">&#34;hello&#34;</span>
<span class="p">}</span>
</code></pre></div><ul>
<li>
<p><code>registerUndo(withTarget:selector:object:)</code> 方法注册</p>
</li>
<li>
<p>使用 <code>prepare(withInvocaionTarget:)</code> 方法注册：</p>
</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="k">if</span> <span class="kd">let</span> <span class="nv">target</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">undoManager</span><span class="p">?.</span><span class="n">prepare</span><span class="p">(</span><span class="n">withInvocationTarget</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span> <span class="k">as</span><span class="p">?</span> <span class="n">NSTextField</span> <span class="p">{</span>
    <span class="n">target</span><span class="p">.</span><span class="n">stringValue</span> <span class="p">=</span> <span class="s">&#34;hello&#34;</span>
<span class="p">}</span>
</code></pre></div><p><strong>注：</strong></p>
<p>UndoManager 对象的 undoRegistionEnabled 是 true 的时候，撤销操作可以注册，但是如果是 false 就会禁用注册功能。</p>
<h3 id="清除所有的撤销操作">清除所有的撤销操作</h3>
<ul>
<li><code>removeAllActions</code>: 删除撤销栈中所有的撤销操作</li>
<li><code>removeAllActions(withTarget:)</code>: 删除撤销栈中指定对象的所有撤销操作</li>
</ul>
<p>需要注意的一点是：UndoManager 会强引用保留对象，所以在一个对象被删除的时候一定要清除这个对象在撤销栈中的所有撤销操作。</p>
<h3 id="为撤销操作命名">为撤销操作命名</h3>
<p>可以使用 <code>setActionName</code> 为撤销操作进行命名，这样在全局菜单中的编辑菜单中的就能看到撤销和重做对应的具体实际的动作。</p>
<h1 id="实例感受撤销和重做的实现">实例感受撤销和重做的实现</h1>
<p>这里继续使用上面的工程，实现一个可以撤销和重做的整数加法器，具体实现过程如下：</p>
<ul>
<li>另外添加两个 Text Field，三个文本框分别是加数、被加数和结果，并将其作为属性绑定到 View Controller 中</li>
<li>添加三个按钮，分别是计算、撤销和重做，同样绑定动作到 View Controller 中</li>
<li>添加三个 Action 的实现，在撤销和重做的实现中，可以参考下面的模板实现：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">private</span> <span class="kd">func</span> <span class="nf">doThing</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// do the thing here</span>
<span class="p">}</span>

<span class="kd">private</span> <span class="kd">func</span> <span class="nf">undoThing</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// undo the thing here</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">undoablyDoThing</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">undoManager</span><span class="p">?.</span><span class="n">registerUndoWithTarget</span><span class="p">(</span><span class="kc">self</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="p">{</span> <span class="n">me</span> <span class="k">in</span>
        <span class="n">me</span><span class="p">.</span><span class="n">redoablyUndoThing</span><span class="p">()</span>
    <span class="p">})</span>
    <span class="c1">// 可以添加有意义的操作名称: undoManager?.setActionName(&#34;Thing&#34;)</span>
    <span class="n">doThing</span><span class="p">()</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">redoablyUndoThing</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">undoManager</span><span class="p">?.</span><span class="n">registerUndoWithTarget</span><span class="p">(</span><span class="kc">self</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="p">{</span> <span class="n">me</span> <span class="k">in</span>
        <span class="n">me</span><span class="p">.</span><span class="n">undoablyDoThing</span><span class="p">()</span>
    <span class="p">})</span>
    <span class="c1">// 可以添加有意义的操作名称: undoManager?.setActionName(&#34;Thing&#34;)</span>
    <span class="n">undoThing</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div><p>另外还要注意的是，最好是也要将撤销按钮和重做按钮与 undoManager 的 canUndo 和 canRedo 同步，形式如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kc">self</span><span class="p">.</span><span class="n">undoButton</span><span class="p">.</span><span class="n">isEnabled</span> <span class="p">=</span> <span class="p">(</span><span class="kc">self</span><span class="p">.</span><span class="n">undoManager</span><span class="p">?.</span><span class="n">canUndo</span><span class="p">)</span><span class="o">!</span>
<span class="kc">self</span><span class="p">.</span><span class="n">redoButton</span><span class="p">.</span><span class="n">isEnabled</span> <span class="p">=</span> <span class="p">(</span><span class="kc">self</span><span class="p">.</span><span class="n">undoManager</span><span class="p">?.</span><span class="n">canRedo</span><span class="p">)</span><span class="o">!</span>
</code></pre></div><p>最终实现效果如下视频，视频中可以看到 ViewController 类的实现：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/undomanager.mp4" controls="controls" width="960"></video></p>
<p>具体实现代码可以参考 Demo 源码：<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/TextFieldKeyDemo_UndoManager.zip">TextFieldKeyDemo_UndoManager</a></p>
<h1 id="参考">参考</h1>
<ul>
<li>《macOS应用开发基础教程》</li>
<li><a href="https://stackoverflow.com/questions/36491789/using-Undomanager-how-to-register-undos-using-swift-closures">Using UndoManager, how to register undos using Swift closures</a></li>
<li><a href="https://developer.apple.com/documentation/foundation/undomanager">UndoManager</a></li>
</ul>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之 NSTextField 支持文本快捷键(一): 基本操作</title>
			<link>https://blog.5km.studio/2019/02/25/macOS-dev-nstextfield-keys/</link>
			<pubDate>Mon, 25 Feb 2019 10:00:05 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/02/25/macOS-dev-nstextfield-keys/</guid>
			<description>在日常 macOS 开发中经常会用到 NSTextField 控件，但是会发现一个问题，如果开发的应用没有顶栏应用菜单，编辑控件中的文本内容的时候，按下文本操作快捷键无效，而得</description>
			<content type="html"><![CDATA[<p>在日常 macOS 开发中经常会用到 NSTextField 控件，但是会发现一个问题，如果开发的应用没有顶栏应用菜单，编辑控件中的文本内容的时候，按下文本操作快捷键无效，而得到的是系统警告音。本文就跟十里一起看一下如何让没有应用菜单的 app 中的 NSTextField 支持常用的文本操作快捷键。</p>
<ul>
<li>复制: <code>Command</code> + <code>c</code></li>
<li>剪切: <code>Command</code> + <code>x</code></li>
<li>粘贴: <code>Command</code> + <code>v</code></li>
<li>全选: <code>Command</code> + <code>a</code></li>
</ul>
<h1 id="实现平台">实现平台</h1>
<ul>
<li>macOS 10.14.3</li>
<li>swift 4.2.1</li>
<li>xcode 10.1</li>
</ul>
<h1 id="键盘事件">键盘事件</h1>
<p>每个 app 都有自己的主循环线程(Main Run Loop)，在这个循环中会遍历系统的事件消息队列，逐一发给对应的响应对象进行处理。具体来说就是 NSApp 的 <code>sendAction:</code> 或 <code>sendEvent: </code>方法发送事件消息给 NSWindow，NSWindow 再分发给具体的视图对象，最后由相应的事件响应方法进行处理。其中一种事件，就是这里要说的<strong>键盘事件</strong>，NSApp 对不同的键盘事件处理方式会不同，主要是以下三种：</p>
<ul>
<li><strong>快捷键</strong>：如果按下快捷键，系统会先向当前的<strong>活动窗口</strong>或<strong>菜单</strong>发送 <code>performKeyEquivalent:</code> 消息，此时窗口会依次遍历所有子视图控件按照响应链传递给 <code>performKeyEquivalent:</code> 的响应者。</li>
<li><strong>控制键</strong>：NSApp 会将消息转发给键盘窗口，完成不同的控件切换控制的功能</li>
<li><strong>其它按键</strong>：NSApp 会将消息转发给键盘窗口，窗口对象会先定位到第一响应对象，根据响应链优先级寻找对 keyDown 键盘事件响应的视图对象，如果可以找到就由相应对象处理，否则就会按照 insertText 方法进行处理(如果是个文本控件，就会插入相应键盘文本)</li>
</ul>
<p>很明显，我们要实现本文的主题，关注快捷键的处理流程就可以了，其实 NSTextField 有上述 <code>performKeyEquivalent:</code> 消息的处理，只不过是没有任何功能实现而已，所以我们只需为 NSTextField 复写一下这个消息的处理即可，这个消息的处理返回值是一个布尔类型，返回 true 则标志着按键事件消息传递的结束。</p>
<h1 id="新建-demo-工程">新建 Demo 工程</h1>
<p>为了更好的说明实现方法，我们还是要通过一个实际的Demo演示！新建的工程包含 storyboard ，新建完成后添加一个文本框控件，如果您对这些操作很熟悉可以跳过本小节。</p>
<ul>
<li>
<p>打开 Xcode，按下快捷键 <code>Shift</code> + <code>Command</code> + <code>N</code> 就会触发新建工程的导航窗口</p>
</li>
<li>
<p>选择 macOS -&gt; Cocoa App，点击 <code>Next</code></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190217102918.png" alt=""></p>
</li>
<li>
<p>工程取名为 TextFieldKeyDemo，勾选 <code>Use Storyboards</code>，点击 Next 选择合适的目录，点击create 创建</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190225210558.png" alt=""></p>
</li>
<li>
<p>进入工程后，单击 Main.storyboard ，按下快捷键 <code>Shift</code> + <code>Command</code> + <code>l</code> 打开控件选择器，搜索 textfield ，将 Text Field 拖入 ViewController ，水平垂直居中放置:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190225214726.png" alt=""></p>
</li>
</ul>
<h1 id="运行工程">运行工程</h1>
<h2 id="运行结果">运行结果</h2>
<p>点击快捷键 <code>Command</code> + <code>r</code> 运行工程，在打开的窗口中可以看到刚刚添加的 Text Field，在其中输入一些文本，然后试一下快件键，比如全选、复制、粘贴、剪切，发现都支持。</p>
<p>此时你会不会有疑问，我们并没有为 NSTextField 类复写 <code>performKeyEquivalent:</code> 方法呀，为什么文本框就支持这些快捷键了，原因是最终键盘信息消息传给了应用菜单，应用菜单中有关于文本编辑的快捷键的处理，那么问题又来了，为什么应用菜单就会对 Text Field 进行操作呢？主要两点：</p>
<ul>
<li>
<p>菜单栏上<strong>编辑</strong>一组中的操作正是对应文章一开头说的几个快捷键，每个操作对应都绑定了一个对象那就是 <strong>First Responder</strong>，比如 Copy 对应的绑定 action：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190225215948.png" alt=""></p>
</li>
<li>
<p>当前活动窗口中的 <strong>First Responder</strong> 就是文本框</p>
</li>
</ul>
<h2 id="试验操作">试验操作</h2>
<p>现在打开工程，打开 Main.storyboard 文件，删除 菜单栏，就是下图标注的东西：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190225220724.png" alt=""></p>
<p>此时再次运行工程，在文本框中尝试使用快捷键，你就会发现如我们所想失效了！</p>
<h1 id="快捷键加持">快捷键加持</h1>
<p>有时我们开发 app 就是不想保留菜单栏，比如状态栏小工具。所以继续研究怎么在没有菜单栏的情况下让 Text Field 支持快捷键是有必要的！很自然的我们就会想到上面说的为 NSTextField 复写 <code>performKeyEquivalent:</code> 方法的方式来解决！</p>
<p>这里直接贴出代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">// NSTextField 自身支持快捷键</span>
<span class="kd">extension</span> <span class="nc">NSTextField</span> <span class="p">{</span>
    <span class="n">open</span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">performKeyEquivalent</span><span class="p">(</span><span class="n">with</span> <span class="n">event</span><span class="p">:</span> <span class="n">NSEvent</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nb">Bool</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="n">event</span><span class="p">.</span><span class="n">charactersIgnoringModifiers</span> <span class="p">{</span>
        <span class="k">case</span> <span class="s">&#34;a&#34;</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">NSApp</span><span class="p">.</span><span class="n">sendAction</span><span class="p">(</span><span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">selectAll</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">to</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">window</span><span class="p">?.</span><span class="n">firstResponder</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span>
        <span class="k">case</span> <span class="s">&#34;c&#34;</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">NSApp</span><span class="p">.</span><span class="n">sendAction</span><span class="p">(</span><span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">copy</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">to</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">window</span><span class="p">?.</span><span class="n">firstResponder</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span>
        <span class="k">case</span> <span class="s">&#34;v&#34;</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">NSApp</span><span class="p">.</span><span class="n">sendAction</span><span class="p">(</span><span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">paste</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">to</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">window</span><span class="p">?.</span><span class="n">firstResponder</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span>
        <span class="k">case</span> <span class="s">&#34;x&#34;</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">NSApp</span><span class="p">.</span><span class="n">sendAction</span><span class="p">(</span><span class="k">#selector</span><span class="p">(</span><span class="n">NSText</span><span class="p">.</span><span class="n">cut</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="n">to</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">window</span><span class="p">?.</span><span class="n">firstResponder</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span>
        <span class="k">default</span><span class="p">:</span>
            <span class="k">return</span> <span class="kc">super</span><span class="p">.</span><span class="n">performKeyEquivalent</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">event</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>上面的代码贴到合适位置即可，比如 AppDelegate.swift 文件 <strong>AppDelegate</strong> 类的后面。</p>
<p>可以看到上面的方法实现了四个快捷键的功能，分别是：全选、复制、粘贴和剪切，都是通过 NSApp 向文本框所在窗口的 <strong>First Responder</strong>(也就是文本框自己) 发起相应行为，而这些行为是 <strong>NStext</strong> 类的基本方法。另外：</p>
<ul>
<li><code>event.charactersIgnoringModifiers</code>: 除去修饰键剩余的按键</li>
</ul>
<blockquote>
<p><strong>charactersIgnoringModifiers</strong>: The characters generated by a key event as if no modifier key (except for Shift) applies</p>
</blockquote>
<ul>
<li>修饰键类型为 <code>NSevent:ModifierFlags</code> 类型，是一个集合，有 capsLock、shift、control、option、command、numericPad、help、function 和 deviceIndependentFlagsMask</li>
</ul>
<p>运行工程，窗口中的 Text Field 支持四个快捷键了！但是此时有一个问题，将 <code>Command</code> 键替换为 <code>Ctrl</code> 按下快捷键也能实现相应文本操作，所以在 <strong>switch</strong> 语句之前加一个判断，如果修饰键不包含 <code>Command</code> 就按照正常响应：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="k">if</span> <span class="n">event</span><span class="p">.</span><span class="n">modifierFlags</span><span class="p">.</span><span class="n">isDisjoint</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="p">.</span><span class="n">command</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="kc">super</span><span class="p">.</span><span class="n">performKeyEquivalent</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">event</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><ul>
<li><code>event.modifierFlags.isDisjoint(with: .command)</code>: 判断快捷键中的修饰键(<strong>modifier</strong>)是不是不包含 Command 键，不包含就返回 true</li>
</ul>
<blockquote>
<p><strong>isDisjoint</strong>: Returns a Boolean value that indicates whether the set has no members in common with the given set.</p>
</blockquote>
<p>此时，还是不完美的，如果按下文本操作快捷键的同时按下了其它有效修饰键(比如 <code>Ctrl</code>) 同样可以完成文本操作，目前还没想到其他更好的处理方式！后面有所发现就会更新这里！</p>
<p>不知道您会不会有疑问：怎么不支持撤销和重做两个快捷键？不是不做，<strong>撤销</strong>和<strong>重做</strong> 操作实现是挺大的一块内容，所以放到这个主题的第二篇文章介绍。</p>
<p>本文工程下载：<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/TextFieldKeyDemo.zip">TextFieldKeyDemo</a></p>
<h1 id="参考">参考</h1>
<ul>
<li><a href="https://www.cnblogs.com/kaymin/articles/7232475.html">NSTextField支持Cmd+C/V快捷键复制、粘贴</a></li>
</ul>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之代码字符串的本地化</title>
			<link>https://blog.5km.studio/2019/02/21/mac_app_international_3/</link>
			<pubDate>Thu, 21 Feb 2019 18:02:47 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/02/21/mac_app_international_3/</guid>
			<description>开发了一款好用的 macOS app 后，为了让更多人的尝到自己 “真香” 的作品，app 的国际化和本地化是有必要的，app 的本地化分三部曲讲解，本文是第三部：代</description>
			<content type="html"><![CDATA[<p>开发了一款好用的 macOS app 后，为了让更多人的尝到自己 “真香” 的作品，app 的国际化和本地化是有必要的，app 的本地化分三部曲讲解，本文是第三部：代码字符串本地化。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190221212506.jpg" alt=""></p>
<p>第一部传送门：<a href="/2019/02/18/mac_app_international_1/">macOS 开发之 APP 名称本地化</a></p>
<p>第二部传送门：<a href="/2019/02/19/mac_app_international_2/">macOS 开发之 storyboard 或 xib 本地化</a></p>
<h1 id="实现平台">实现平台</h1>
<ul>
<li>macOS 10.14.3</li>
<li>swift 4.2.1</li>
<li>xcode 10.1</li>
</ul>
<h1 id="准备工程">准备工程</h1>
<p>本文直接使用上一篇文章<a href="/2019/02/19/mac_app_international_2/">macOS 开发之 storyboard 或 xib 本地化</a>的工程，<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/InternationalDemo2.zip">点我</a>下载！</p>
<p>为了展示本文要讲的内容，这里利用已有的 label，另外添加两个按钮来切换 label 的内容。</p>
<h2 id="添加按钮">添加按钮</h2>
<p>打开上面下载的工程，打开 <code>Main.storyboard</code> 可以看到 View controller 中已经有一个 label ，本小节添加两个按钮，并添加相应的点击 action。</p>
<ol>
<li>
<p>按下快件键 <code>Shift</code> + <code>Command</code> + <code>l</code> 键，打开控件选择器，搜索框输入 <code>button</code> 就会筛选出所有的按钮控件，将其中的 <code>Gradient Button</code> 拖入 View Controller，调整其大小为 50 * 232，靠左放置到合适位置，打开 Attributes Inspector(选中按钮，按组合键 <code>Option</code> + <code>Command</code> + 4)，其中勾选 <code>Transparent</code> ，Image 选择 <code>NSGoForwardTemplate</code>，然后勾选 <code>Refuse First Responder</code></p>
</li>
<li>
<p>同样的方法添加第二个按钮(或者直接复制第一个按钮)，不过是靠右放置，Attributes Inspector 中更改 Image 为 <code>NSGoBackTemplate</code>，其它设置与第一个按钮一致</p>
</li>
<li>
<p>按下快捷键 <code>Option</code> + <code>Command</code> + 回车键会显示 <code>Main.storyboard</code> 中 view controller 对应的 Assitant Editor 并打开对应的 swift 文件</p>
</li>
<li>
<p>选中第一个按钮，按住 <code>Ctrl</code> 键，鼠标左键按住拖动至 Assitant Editor 中松手添加一个 Action ，名称为 <code>previousLabel</code></p>
</li>
<li>
<p>选中第二个按钮，按住 <code>Ctrl</code> 键，鼠标左键按住拖动至 Assitant Editor 中松手添加一个 Action ，名称为 <code>nextLabel</code></p>
</li>
</ol>
<p>上述五步操作，可参考如下视频进行：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/mac_international_add_button.mp4" controls="controls" width="720"></video></p>
<h2 id="添加-label-的-outlet">添加 Label 的 outlet</h2>
<p>打开 Main.storyboard ，选中 view controller 中的 label，按下快捷键 <code>Option</code> + <code>Command</code> + 回车键会显示 <code>Main.storyboard</code> 中 view controller 对应的 Assitant Editor 并打开对应的 swift 文件，按住 <code>Ctrl</code> 键，在 label 上按住鼠标左键拖动到 Assitant Editor 中松手添加一个 outlet ，名称为 <code>movieLabel</code>。</p>
<h2 id="添加标签内容">添加标签内容</h2>
<p>在 ViewController.swift 的 ViewController 类中添加以下定义：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">movies</span> <span class="p">=</span> <span class="p">[</span><span class="s">&#34;天空之城&#34;</span><span class="p">,</span> <span class="s">&#34;幽灵公主&#34;</span><span class="p">,</span> <span class="s">&#34;风之谷&#34;</span><span class="p">]</span>
<span class="kd">var</span> <span class="nv">movieIndex</span> <span class="p">=</span> <span class="mi">0</span>
</code></pre></div><p>在 ViewDidLoad 方法的 <code>super.viewDidLoad()</code> 后面添加：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">movieLable</span><span class="p">.</span><span class="n">stringValue</span> <span class="p">=</span> <span class="n">movies</span><span class="p">[</span><span class="n">movieIndex</span><span class="p">]</span>
</code></pre></div><h2 id="完善按钮的-action">完善按钮的 action</h2>
<p>补充 <code>previousLabel</code> 和 <code>nextLabel</code> 的实现：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">previousLabel</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">movieIndex</span> <span class="o">-=</span> <span class="mi">1</span>
    <span class="k">if</span> <span class="n">movieIndex</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="p">{</span>
        <span class="n">movieIndex</span> <span class="p">=</span> <span class="n">movies</span><span class="p">.</span><span class="bp">count</span> <span class="o">-</span> <span class="mi">1</span>
    <span class="p">}</span>
    <span class="n">movieLable</span><span class="p">.</span><span class="n">stringValue</span> <span class="p">=</span> <span class="n">movies</span><span class="p">[</span><span class="n">movieIndex</span><span class="p">]</span>
<span class="p">}</span>

<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">nextLabel</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">movieIndex</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">if</span> <span class="n">movieIndex</span> <span class="o">&gt;=</span> <span class="n">movies</span><span class="p">.</span><span class="bp">count</span> <span class="p">{</span>
        <span class="n">movieIndex</span> <span class="p">=</span> <span class="mi">0</span>
    <span class="p">}</span>
    <span class="n">movieLable</span><span class="p">.</span><span class="n">stringValue</span> <span class="p">=</span> <span class="n">movies</span><span class="p">[</span><span class="n">movieIndex</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div><h1 id="代码本地化">代码本地化</h1>
<p>代码中字符串的本地化需要新建一个名为 <code>Localizable.strings</code> 的文件，新建过程类似于<a href="/2019/02/18/mac_app_international_1/">macOS 开发之 APP 名称本地化</a> 中 <code>InfoPlist.strings</code>的过程，代码中的字符串如果需要本地化必须在这个文件中定义。</p>
<h2 id="添加本地化文件">添加本地化文件</h2>
<ol>
<li>
<p>按下快捷键 <code>Command</code> + <code>n</code> 打开新建文件的导航窗口，选中 macOS - Resources - Strings file，next， 更改名称为 <code>Localizable.strings</code></p>
</li>
<li>
<p>此时会打开文件，按下快捷键 <code>Option</code> + <code>Command</code> + <code>1</code> 就会打开其 file Inspector，可以看到分组 <code>Localization</code> 中的 <code>Localize...</code> 按钮，点击这个按钮就会跳出弹窗：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218114200.png" alt=""></p>
</li>
<li>
<p>选择 <code>English</code> 选项，点击 <code>Localize</code> 就会发现 <code>InfoPlist.strings</code> Inspector 中的 <code>Localizations</code> 分组变成了三个勾选框，其中 <code>English</code> 是勾选的，这里也把 Chinese 和 Japanese 勾选上，此时你会发现 Xcode 左侧工程导航栏 <code>InfoPlist.strings</code> 左侧出现了一个小三角，点开就会发现其变成了与三种语言对应的三个 Strings 文件：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190221201935.png" alt=""></p>
</li>
</ol>
<h2 id="代码字符串的本地化">代码字符串的本地化</h2>
<p>代码中字符串的本地化需要两步，一是在刚新建的 String 文件中添加字符串定义，二是调用方法 <code>NSLocalizedString(forKey:, comment:)</code> 根据字符串定义的键获取字符串。</p>
<h3 id="添加本地化字符串定义">添加本地化字符串定义</h3>
<p>因为代码中只在 <code>movies</code> 初始化中使用了字符串，所以对应要定义一下它们，分别在三个 Strings 文件中定义。</p>
<ul>
<li>Localizable.strings (English):</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="s">&#34;movie-label-1.title&#34;</span> <span class="p">=</span> <span class="s">&#34;Laputa Castle in the Sky&#34;</span><span class="p">;</span>
<span class="s">&#34;movie-label-2.title&#34;</span> <span class="p">=</span> <span class="s">&#34;Princess Mononoke&#34;</span><span class="p">;</span>
<span class="s">&#34;movie-label-3.title&#34;</span> <span class="p">=</span> <span class="s">&#34;Nausicaa Of The Valley Of The Wind &#34;</span><span class="p">;</span>
</code></pre></div><ul>
<li>Localizable.strings (Chinese(Simplified)):</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="s">&#34;movie-label-1.title&#34;</span> <span class="p">=</span> <span class="s">&#34;天空之城&#34;</span><span class="p">;</span>
<span class="s">&#34;movie-label-2.title&#34;</span> <span class="p">=</span> <span class="s">&#34;幽灵公主&#34;</span><span class="p">;</span>
<span class="s">&#34;movie-label-3.title&#34;</span> <span class="p">=</span> <span class="s">&#34;风之谷&#34;</span><span class="p">;</span>
</code></pre></div><ul>
<li>Localizable.strings (Japanese):</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="s">&#34;movie-label-1.title&#34;</span> <span class="p">=</span> <span class="s">&#34;天空の城ラピュタ&#34;</span><span class="p">;</span>
<span class="s">&#34;movie-label-2.title&#34;</span> <span class="p">=</span> <span class="s">&#34;もののけ姫&#34;</span><span class="p">;</span>
<span class="s">&#34;movie-label-3.title&#34;</span> <span class="p">=</span> <span class="s">&#34;風の谷のナウシカ&#34;</span><span class="p">;</span>
</code></pre></div><h3 id="更新代码">更新代码</h3>
<p>修改添加本地化字符串对应的地方，也就是 <code>movies</code> 初始化的地方，修改为：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">movies</span> <span class="p">=</span> <span class="p">[</span><span class="n">NSLocalizedString</span><span class="p">(</span><span class="s">&#34;movie-label-1.title&#34;</span><span class="p">,</span> <span class="n">comment</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">),</span>
              <span class="n">NSLocalizedString</span><span class="p">(</span><span class="s">&#34;movie-label-2.title&#34;</span><span class="p">,</span> <span class="n">comment</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">),</span>
              <span class="n">NSLocalizedString</span><span class="p">(</span><span class="s">&#34;movie-label-3.title&#34;</span><span class="p">,</span> <span class="n">comment</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">)]</span>
</code></pre></div><h3 id="效果展示">效果展示</h3>
<p>更改系统语言查看各语言的效果。</p>
<ul>
<li>
<p>English</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190221204958.png" alt=""></p>
</li>
<li>
<p>Chinese(Simplified)</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190221204943.png" alt=""></p>
</li>
<li>
<p>Japanese</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190221204925.png" alt=""></p>
</li>
</ul>
<h2 id="添加新的本地化字符串">添加新的本地化字符串</h2>
<p>在 <a href="/2019/02/19/mac_app_international_2/">macOS 开发之 storyboard 或 xib 本地化</a> 中提到了一个工具 <a href="https://github.com/Flinesoft/BartyCrouch">BartyCrouch</a>，它的子命令 <code>code</code> 可以帮助我们自动扫描代码中调用 <code>NSLocalizedString(forKey:, comment:)</code> 的地方，如果 <code>Localizable.strings</code> 中没有的字符串键会自动追加，非常方便！</p>
<p>⚠️：</p>
<p>bartycrouch 已经发布了 <strong>4.0.0</strong>，使用方法不同，这里展示的方法只适用于 bartycrouch <strong>v3.x.x</strong> 版本，后续十里会出一篇关于 bartycrouch v4.0.0 的使用教程，请持续关注<a href="https://www.smslit.top">博客</a> 和 知乎专栏 <a href="https://zhuanlan.zhihu.com/c_1079349488673648640">macOS 开发</a>。</p>
<p>所以我们在之后若要将要使用的字符串本地化，直接使用 <code>NSLocalizedString(forKey:, comment:)</code> 方法，同时定义一个新的 key 先用上，之后去命令行使用 <code>bartycrouch code</code> 就可以添加新的字符串定义，只需要再修改 Strings 文件中新加键的值就可以了。</p>
<h3 id="举例说明">举例说明</h3>
<ul>
<li>比如再在 <code>movies</code> 初始化中新加一个元素，代码可写为：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">movies</span> <span class="p">=</span> <span class="p">[</span><span class="n">NSLocalizedString</span><span class="p">(</span><span class="s">&#34;movie-label-1.title&#34;</span><span class="p">,</span> <span class="n">comment</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">),</span>
              <span class="n">NSLocalizedString</span><span class="p">(</span><span class="s">&#34;movie-label-2.title&#34;</span><span class="p">,</span> <span class="n">comment</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">),</span>
              <span class="n">NSLocalizedString</span><span class="p">(</span><span class="s">&#34;movie-label-3.title&#34;</span><span class="p">,</span> <span class="n">comment</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">),</span>
              <span class="n">NSLocalizedString</span><span class="p">(</span><span class="s">&#34;movie-label-4.title&#34;</span><span class="p">,</span> <span class="n">comment</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">)]</span>
</code></pre></div><ul>
<li>命令行下切换至工程文根目录，执行命令：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash"><span class="c1"># -p 指定包含工程代码文件的目录 ，-l 指定可以搜到 Localizable.strings 文件的代码目录</span>
$ bartycrouch code -p InternationalDemo -l InternationalDemo
ℹ️ BartyCrouch: Successfully updated strings file<span class="o">(</span>s<span class="o">)</span> of Code files.
</code></pre></div><ul>
<li>打开三个 <code>Localizable.strings</code> 文件会发现新添加了一项，并且为每一个添加了预置注释：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="cm">/* No comment provided by engineer. */</span>
<span class="s">&#34;movie-label-4.title&#34;</span> <span class="p">=</span> <span class="s">&#34;&#34;</span><span class="p">;</span>
</code></pre></div><ul>
<li>分别为三个 Strings 文件修改对应新加项的值：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="s">&#34;movie-label-4.title&#34;</span> <span class="p">=</span> <span class="s">&#34;Howl&#39;s Moving Castle&#34;</span><span class="p">;</span>
<span class="s">&#34;movie-label-4.title&#34;</span> <span class="p">=</span> <span class="s">&#34;哈尔的移动城堡&#34;</span><span class="p">;</span>
<span class="s">&#34;movie-label-4.title&#34;</span> <span class="p">=</span> <span class="s">&#34;ハウルの動く城&#34;</span><span class="p">;</span>
</code></pre></div><p>运行程序，可以看到不同语言下，第四个电影就出现了！</p>
<h3 id="编译时更新">编译时更新</h3>
<p><a href="/2019/02/19/mac_app_international_2/">macOS 开发之 storyboard 或 xib 本地化</a> 中在工程中创建了 build phase 运行脚本，脚本中添加代码字符串本地化要执行的命令即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash"><span class="c1"># Type a script or drag a script file from your workspace to insert its path.</span>
<span class="k">if</span> which bartycrouch &gt; /dev/null<span class="p">;</span> <span class="k">then</span>
    bartycrouch interfaces -p .
    bartycrouch code -p . -l .
    bartycrouch lint -e -d -p .
<span class="k">else</span>
    <span class="nb">echo</span> <span class="s2">&#34;warning: BartyCrouch not installed, download it from https://github.com/Flinesoft/BartyCrouch&#34;</span>
<span class="k">fi</span>
</code></pre></div><p>至此，您应该学会如何对代码中字符串本地化了吧！</p>
<p>此部分完成后的工程下载：<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/InternationalDemo3.zip">InternationalDemo3</a></p>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之 storyboard 或 xib 本地化</title>
			<link>https://blog.5km.studio/2019/02/19/mac_app_international_2/</link>
			<pubDate>Tue, 19 Feb 2019 08:53:54 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/02/19/mac_app_international_2/</guid>
			<description>开发了一款好用的 macOS app 后，为了让更多人的尝到自己 “真香” 的作品，app 的国际化和本地化是有必要的，app 的本地化分三部曲讲解，本文是第二部：s</description>
			<content type="html"><![CDATA[<p>开发了一款好用的 macOS app 后，为了让更多人的尝到自己 “真香” 的作品，app 的国际化和本地化是有必要的，app 的本地化分三部曲讲解，本文是第二部：storyboard 或 xib 本地化。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190219090656.jpg" alt=""></p>
<p>第一部传送门：<a href="/2019/02/18/mac_app_international_1/">macOS 开发之 APP 名称本地化</a></p>
<p>第三部传送门：<a href="/2019/02/21/mac_app_international_3/">macOS 开发之代码字符串的本地化</a></p>
<h1 id="实现平台">实现平台</h1>
<ul>
<li>macOS 10.14.3</li>
<li>swift 4.2.1</li>
<li>xcode 10.1</li>
</ul>
<h1 id="准备工程">准备工程</h1>
<p>本文直接使用上一篇文章<a href="/2019/02/18/mac_app_international_1/">macOS 开发之 APP 名称本地化</a>的工程，<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/InternationalDemo.zip">点我</a>下载！</p>
<p>打开工程后，会看到 <code>Main.storyboard</code> 左侧有个小三角，点开可以看到，包含了简体中文和日文的两个 Strings 文件，这两个文件是在<a href="/2019/02/18/mac_app_international_1/#添加语言支持">添加语言支持</a> 的时候添加上的，点击 <code>Main.storyboard</code> 可以在 Inspector 的 <code>Localization</code> 分组看到 English 没有勾选，勾选 English ，会看到新增了一个 English 的 Strings 文件。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190219092044.jpg" alt=""></p>
<p>上面提到的 String 文件包含了 storyboard 中所有可见控件中字符串对应的内容定义，App 运行的时候会根据系统语言读取对应语言的字符串内容，以达到 storyboard 界面元素本地化显示的效果！</p>
<blockquote>
<p>如果要添加自己定义的 xib 文件，也会像 storyboard 文件一样，在 File Inspector 中会有一个 Localization 的分组，如果有 localize&hellip; 按钮，点击它就可以添加 xib 的新的语言支持！</p>
</blockquote>
<h1 id="修改-strings-文件">修改 Strings 文件</h1>
<p>打开上面提到的三个 Strigns 文件：</p>
<ul>
<li>Main.Strings (English)</li>
<li>Main.Strings (Chinese(Simplified))</li>
<li>Main.Strings (Japanese)</li>
</ul>
<p>可以看到文件中都包含了相同的字符串项，例如其中的窗口标题项：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="cm">/* Class = &#34;NSWindow&#34;; title = &#34;Window&#34;; ObjectID = &#34;IQv-IB-iLA&#34;; */</span>
<span class="s">&#34;IQv-IB-iLA.title&#34;</span> <span class="p">=</span> <span class="s">&#34;Window&#34;</span><span class="p">;</span>
</code></pre></div><ol>
<li>
<p>左侧的代表了字符串标识，右侧是相对应的字符串值，我们要对应语言修改的就是每一项的右侧值。</p>
</li>
<li>
<p>每一项后面必须有半角的分号</p>
</li>
<li>
<p>左侧标识使用了控件的 <code>ObjectID</code>，使用点语法表示属性的方式构成字符串标识，其中 <code>ObjectID</code> 可以在控件的 Identity Inspector 中查看，比如选中 Main.storyboard 中的 Window 将 Inspector 切换到 Identity 标签页可以在 Document 分组中看到 Object ID 是 <code>IQv-IB-iLA&quot;</code></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190219112159.png" alt=""></p>
</li>
</ol>
<p>下面的工作就是自行翻译并修改每一项右侧的值了，可以使用<a href="http://translate.google.cn/">谷歌翻译</a>协助翻译。</p>
<p>将 Main.Strings (Chinese(Simplified)) 和 Main.Strings (Japanese) 中的项修改完成后就可以验证效果了。</p>
<h1 id="更新-strings-文件">更新 Strings 文件</h1>
<p>设计的 storyboard 并不是一成不变的，所以 Strings 文件也要随时更新。这里用到一个辅助工具叫做 <strong>bartycrouch</strong>:</p>
<blockquote>
<p>BartyCrouch incrementally updates your Strings files from your Code and from Interface Builder files. &ldquo;Incrementally&rdquo; means that BartyCrouch will by default keep both your already translated values and even your altered comments. Additionally you can also use BartyCrouch for machine translating from one language to 60+ other languages. Using BartyCrouch is as easy as running a few simple commands from the command line what can even be automated using a build script within your project.</p>
</blockquote>
<p><a href="https://github.com/Flinesoft/BartyCrouch">BartyCrouch</a></p>
<p>这个工具也能调用微软的 API 协助翻译。</p>
<h2 id="安装-bartycrouch">安装 bartycrouch</h2>
<p>可以使用 homebrew 安装这个工具：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ brew install bartycrouch
</code></pre></div><p>⚠️：</p>
<p>bartycrouch 已经发布了 <strong>4.0.0</strong>，使用方法不同，这里展示的方法只适用于 bartycrouch <strong>v3.x.x</strong> 版本，后续十里会出一篇关于 bartycrouch v4.0.0 的使用教程，请持续关注<a href="https://www.smslit.top">博客</a> 和 知乎专栏 <a href="https://zhuanlan.zhihu.com/c_1079349488673648640">macOS 开发</a>。</p>
<h2 id="使用">使用</h2>
<p>为了验证使用，这里在 view controller 中添加了一个按钮和一个 label。</p>
<h3 id="更新-interfaces">更新 interfaces</h3>
<p>这里我们主要是用其从 storyboard 或者 xib 文件更新 Strings 文件的功能，只需要调用如下命令就可以工作：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ bartycrouch interfaces -p InternationalDemo/
ℹ️ BartyCrouch: Successfully updated strings file<span class="o">(</span>s<span class="o">)</span> of Storyboard or XIB file.
</code></pre></div><p>其中需要使用 <code>-p</code> 指明包含工程文件的目录，因为此时工作目录是在工程根目录下，所以要指定的目录是存放文件的 <code>InternationalDemo/</code></p>
<p>执行了上面的命令以后，查看三个 Strings 文件中确实新添了按钮和 label 的相关字符串！</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="cm">/* Class = &#34;NSButtonCell&#34;; title = &#34;Button&#34;; ObjectID = &#34;0A1-8b-fid&#34;; */</span>
<span class="s">&#34;0A1-8b-fid.title&#34;</span> <span class="p">=</span> <span class="s">&#34;&#34;</span><span class="p">;</span>

<span class="cm">/* Class = &#34;NSTextFieldCell&#34;; title = &#34;Label&#34;; ObjectID = &#34;by2-mL-GsU&#34;; */</span>
<span class="s">&#34;by2-mL-GsU.title&#34;</span> <span class="p">=</span> <span class="s">&#34;&#34;</span><span class="p">;</span>
</code></pre></div><h3 id="检查-strings-文件">检查 Strings 文件</h3>
<p>bartycrouch 的子命令 lint 可以帮助检查：</p>
<ul>
<li>Strings 文件中重复的键值，需要指定命令参数 <code>-d</code></li>
<li>文件中值为空字符串的键值，需要指定命令参数 <code>-e</code></li>
</ul>
<p>比如我们检查一下有没有空值的键</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ bartycrouch lint -e -p InternationalDemo

ℹ️ Found empty value <span class="k">for</span> key <span class="s1">&#39;0A1-8b-fid.title&#39;</span> in file /Users/5km/Documents/workspace/macOS/InternationalDemo/InternationalDemo/zh-Hans.lproj/Main.strings.
ℹ️ Found empty value <span class="k">for</span> key <span class="s1">&#39;by2-mL-GsU.title&#39;</span> in file /Users/5km/Documents/workspace/macOS/InternationalDemo/InternationalDemo/zh-Hans.lproj/Main.strings.
ℹ️ Found empty value <span class="k">for</span> key <span class="s1">&#39;0A1-8b-fid.title&#39;</span> in file /Users/5km/Documents/workspace/macOS/InternationalDemo/InternationalDemo/ja.lproj/Main.strings.
ℹ️ Found empty value <span class="k">for</span> key <span class="s1">&#39;by2-mL-GsU.title&#39;</span> in file /Users/5km/Documents/workspace/macOS/InternationalDemo/InternationalDemo/ja.lproj/Main.strings.
ℹ️ Found empty value <span class="k">for</span> key <span class="s1">&#39;0A1-8b-fid.title&#39;</span> in file /Users/5km/Documents/workspace/macOS/InternationalDemo/InternationalDemo/en.lproj/Main.strings.
ℹ️ Found empty value <span class="k">for</span> key <span class="s1">&#39;by2-mL-GsU.title&#39;</span> in file /Users/5km/Documents/workspace/macOS/InternationalDemo/InternationalDemo/en.lproj/Main.strings.
❌ Error! <span class="m">6</span> issue<span class="o">(</span>s<span class="o">)</span> found in <span class="m">3</span> file<span class="o">(</span>s<span class="o">)</span>. Executed <span class="m">1</span> checks in <span class="m">6</span> Strings file<span class="o">(</span>s<span class="o">)</span> in total.
</code></pre></div><p>这就检查出刚刚新加的按钮和 label 的相对应的字符串配置了。</p>
<h3 id="自动更新和检查">自动更新和检查</h3>
<p>为了更方便快捷的更新和检查 Strings 文件，这里可以为工程新建一个 Build Phase，按照下图流程添加：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190219142234.jpg" alt=""></p>
<p>添加的 shell 脚本内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell"><span class="c1"># Type a script or drag a script file from your workspace to insert its path.</span>
<span class="k">if</span> which bartycrouch &gt; /dev/null<span class="p">;</span> <span class="k">then</span>
    bartycrouch interfaces -p .
    bartycrouch lint -e -d -p .
<span class="k">else</span>
    <span class="nb">echo</span> <span class="s2">&#34;warning: BartyCrouch not installed, download it from https://github.com/Flinesoft/BartyCrouch&#34;</span>
<span class="k">fi</span>
</code></pre></div><p>运行一下工程，build 失败，原因是 <code>bartycrouch lint</code> 命令检查出了有空值字符串，以后就是每次编译都会更新 Strings 文件。</p>
<p>再把按钮删除运行工程，检查出 String 文件中只有 label 相关的字符串了，给三个文件分别更改 label 标签的内容。</p>
<ul>
<li>Main.strings (English)</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="cm">/* Class = &#34;NSTextFieldCell&#34;; title = &#34;Label&#34;; ObjectID = &#34;by2-mL-GsU&#34;; */</span>
<span class="s">&#34;by2-mL-GsU.title&#34;</span> <span class="p">=</span> <span class="s">&#34;Hello, world!&#34;</span><span class="p">;</span>
</code></pre></div><p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190219143602.png" alt=""></p>
<ul>
<li>Main.strings (Chinese(Simplified))</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="cm">/* Class = &#34;NSTextFieldCell&#34;; title = &#34;Label&#34;; ObjectID = &#34;by2-mL-GsU&#34;; */</span>
<span class="s">&#34;by2-mL-GsU.title&#34;</span> <span class="p">=</span> <span class="s">&#34;世界，你好!&#34;</span><span class="p">;</span>
</code></pre></div><p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190219143627.png" alt=""></p>
<ul>
<li>Main.strings (Japanese)</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="cm">/* Class = &#34;NSTextFieldCell&#34;; title = &#34;Label&#34;; ObjectID = &#34;by2-mL-GsU&#34;; */</span>
<span class="s">&#34;by2-mL-GsU.title&#34;</span> <span class="p">=</span> <span class="s">&#34;こんにちは世界!&#34;</span><span class="p">;</span>
</code></pre></div><p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190219143636.png" alt=""></p>
<p>至此，这一部分就讲完了，希望对您有所帮助！</p>
<p>此部分完成后的工程下载：<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/InternationalDemo2.zip">InternationalDemo2</a></p>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之 APP 名称本地化</title>
			<link>https://blog.5km.studio/2019/02/18/mac_app_international_1/</link>
			<pubDate>Mon, 18 Feb 2019 09:10:02 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/02/18/mac_app_international_1/</guid>
			<description>开发了一款好用的 macOS app 后，为了让更多人的尝到自己 “真香” 的作品，app 的国际化和本地化是有必要的，app 的本地化分三部曲讲解，本文是第一部：a</description>
			<content type="html"><![CDATA[<p>开发了一款好用的 macOS app 后，为了让更多人的尝到自己 “真香” 的作品，app 的国际化和本地化是有必要的，app 的本地化分三部曲讲解，本文是第一部：app 名称的本地化。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218102516.jpg" alt=""></p>
<p>第二部传送门：<a href="/2019/02/19/mac_app_international_2/">macOS 开发之 storyboard 或 xib 本地化</a></p>
<p>第三部传送门：<a href="/2019/02/21/mac_app_international_3/">macOS 开发之代码字符串的本地化</a></p>
<h1 id="实现平台">实现平台</h1>
<ul>
<li>macOS 10.14.3</li>
<li>swift 4.2.1</li>
<li>xcode 10.1</li>
</ul>
<h1 id="新建一个空的工程">新建一个空的工程</h1>
<p>为了更好地讲述本地化 APP 名称，以一个 Cocoa App 新工程为例进行说明。</p>
<ol>
<li>
<p>打开 Xcode，按下快捷键 <code>Shift</code> + <code>Command</code> + <code>N</code> 就会触发新建工程的导航窗口</p>
</li>
<li>
<p>选择 macOS -&gt; Cocoa App，点击 <code>Next</code></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190217102918.png" alt=""></p>
</li>
<li>
<p>工程取名为 InternationalDemo，勾选 <code>Use Storyboards</code></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218103302.jpg" alt=""></p>
</li>
</ol>
<h1 id="添加语言支持">添加语言支持</h1>
<p>工程的国际化和本地化依托于各国语言的支持，所以需要先为工程添加语言支持。</p>
<ol>
<li>
<p>Xcode 左侧工程导航栏，点击工程，会看到右侧工程配置信息，点击 <strong>PROJECT</strong> 下的 <code>InternationalDemo</code></p>
</li>
<li>
<p>在出现的配置项中有 <code>Localizations</code>，可以看到默认是有一个 <code>English</code> 的，点击 <code>+</code> 号会出现选择语言的下拉菜单，如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218110626.png" alt=""></p>
</li>
<li>
<p>选择 <code>Chinese(Simplified)(zh-Hans)</code> 即可出现添加简体中文的窗口，点击 <code>Finish</code> 即可完成添加：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218110922.png" alt=""></p>
</li>
<li>
<p>为了更好的演示，按照上面的操作再添加 <code>Janpanese(ja)</code> 的支持，最终 <code>Localizations</code> 语言如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218111319.png" alt=""></p>
</li>
</ol>
<h1 id="添加相关本地化文件">添加相关本地化文件</h1>
<p>为了实现 macOS 系统下 Dock 栏、全局菜单栏和 Finder 中 app 的名称均可以跟随系统语言的设置显示对应语言的名称，需要在工程中添加一个名为 <code>InfoPlist.strings</code> 的文件，并进行本地化！</p>
<h2 id="添加-infopliststrings">添加 <code>InfoPlist.strings</code></h2>
<ol>
<li>
<p>按下组合键 <code>Command</code> + <code>n</code> 调出新建文件的导航窗口，找到 <code>macOS</code> -&gt; <code>Resources</code> -&gt; <code>Strings File</code>：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218112203.png" alt=""></p>
</li>
<li>
<p>点击 <code>Next</code>，更改名称为 <code>InfoPlist.strings</code>，<code>Group</code> 选择 <code>InternationalDemo</code> 文件夹，其它默认，点击 <code>Create</code> 完成文件的添加。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218112829.png" alt=""></p>
</li>
</ol>
<h2 id="infopliststrings-添加本地化支持"><code>InfoPlist.strings</code> 添加本地化支持</h2>
<ol>
<li>
<p>选中导航栏中的 <code>InfoPlist.strings</code> ，按下组合键 <code>Option</code> + <code>Command</code> + <code>1</code> 就会显示 <code>InfoPlist.strings</code> 的 Inspector</p>
</li>
<li>
<p>会看到有一项是 <code>Localizations</code> 其中有个 <code>Localize...</code> 按钮：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218113743.png" alt=""></p>
</li>
<li>
<p>点击这个按钮，会弹出一个窗口，其中有个下拉菜单控件，其中包含了上面添加的三个语言支持：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218114200.png" alt=""></p>
</li>
<li>
<p>选择 <code>English</code> 选项，点击 <code>Localize</code> 就会发现 <code>InfoPlist.strings</code> Inspector 中的 <code>Localizations</code> 分组变成了三个勾选框，其中 <code>English</code> 是勾选的，这里也把 Chinese 和 Japanese 勾选上，此时你会发现 Xcode 左侧工程导航栏 <code>InfoPlist.strings</code> 左侧出现了一个小三角，点开就会发现其变成了与三种语言对应的三个 Strings 文件：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218114842.png" alt=""></p>
</li>
</ol>
<h1 id="添加-app-名称配置">添加 App 名称配置</h1>
<h2 id="菜单栏-app-名称本地化">菜单栏 App 名称本地化</h2>
<p>要实现菜单栏 App 名称本地化需要在上面添加的三个文件中添加 <code>CFBundleName</code> 字符串。</p>
<ul>
<li><code>InfoPlist.strings (English)</code> 文件中添加以下代码：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">CFBundleName</span> <span class="p">=</span> <span class="s">&#34;International Demo&#34;</span><span class="p">;</span>
</code></pre></div><ul>
<li><code>InfoPlist.strings (Chinese(Simplified))</code> 文件中添加以下代码：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">CFBundleName</span> <span class="p">=</span> <span class="s">&#34;国际化演示&#34;</span><span class="p">;</span>
</code></pre></div><ul>
<li><code>InfoPlist.strings (Japanese)</code> 文件中添加以下代码：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">CFBundleName</span> <span class="p">=</span> <span class="s">&#34;国際デモ&#34;</span><span class="p">;</span>
</code></pre></div><blockquote>
<p>更改系统语言设置，可以查看不同语言设置下的运行效果，在系统语言设置中更改了语言是大部分生效的，不要关闭设置窗口，否则关闭的话会提示重启，各类语言设置检查之后在改回原来的语言即可!</p>
</blockquote>
<ul>
<li>
<p>英文:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218121003.png" alt=""></p>
</li>
<li>
<p>简体中文：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218121058.png" alt=""></p>
</li>
<li>
<p>日文：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218121128.png" alt=""></p>
</li>
</ul>
<h2 id="dock-栏和-finder-中-app-名称本地化">Dock 栏和 Finder 中 App 名称本地化</h2>
<p>如上在三个文件中添加 <code>CFBundleDisplayName</code> 字符串。</p>
<ul>
<li><code>InfoPlist.strings (English)</code> 文件中添加以下代码：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">CFBundleDisplayName</span> <span class="p">=</span> <span class="s">&#34;International Demo&#34;</span><span class="p">;</span>
</code></pre></div><ul>
<li><code>InfoPlist.strings (Chinese(Simplified))</code> 文件中添加以下代码：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">CFBundleDisplayName</span> <span class="p">=</span> <span class="s">&#34;国际化演示&#34;</span><span class="p">;</span>
</code></pre></div><ul>
<li><code>InfoPlist.strings (Japanese)</code> 文件中添加以下代码：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">CFBundleDisplayName</span> <span class="p">=</span> <span class="s">&#34;国際デモ&#34;</span><span class="p">;</span>
</code></pre></div><p>还差一步，需要在工程的 Info 配置中添加 <code>Bundle display name</code> 项：</p>
<ol>
<li>
<p>打开 <code>Info.plist</code> 文件</p>
</li>
<li>
<p>在 <code>Bundle name</code> 下添加 <code>Bundle display name</code> ，值设置为 <code>$(PRODUCT_NAME)</code></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190218124130.png" alt=""></p>
</li>
</ol>
<p>如果要检验效果的话，更改系统语言设置后需要重启才能看到 Dock 栏和 Finder 中 App 名称的本地化适应。</p>
<p>至此 App 名称的本地化就实现了！</p>
<p>本例程可从这里下载：<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/InternationalDemo.zip">InternationalDemo</a></p>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发中实现 APP 的自重启</title>
			<link>https://blog.5km.studio/2019/02/15/mac_app_restart/</link>
			<pubDate>Fri, 15 Feb 2019 13:10:30 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/02/15/mac_app_restart/</guid>
			<description>在日常使用某些 app 时，更改语言设置后 app 会提示“软件语言设置在重启后会生效”，有的软件也会提示是否立即重启，这说明我们能实现 app 的自重启，对吧！那</description>
			<content type="html"><![CDATA[<p>在日常使用某些 app 时，更改语言设置后 app 会提示“软件语言设置在重启后会生效”，有的软件也会提示是否立即重启，这说明我们能实现 app 的自重启，对吧！那么本文将介绍如何通过代码实现 app 的自重启。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190217101720.jpg" alt=""></p>
<h1 id="实现平台">实现平台</h1>
<ul>
<li>macOS 10.14.3</li>
<li>swift 4.2.1</li>
<li>xcode 10.1</li>
</ul>
<h1 id="新建-cocoa-app-工程">新建 Cocoa App 工程</h1>
<p>这里以一个新的空工程为例，所以首先要新建一个 <strong>Cocoa App</strong> 工程。简单说一下这个过程：</p>
<ol>
<li>
<p>打开 Xcode，按下快捷键 <code>Shift</code> + <code>Command</code> + <code>N</code> 就会触发新建工程的导航窗口</p>
</li>
<li>
<p>选择 macOS -&gt; Cocoa App，点击 <code>Next</code></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190217102918.png" alt=""></p>
</li>
<li>
<p>工程取名为 RelaunchDemo，勾选 <code>Use Storyboards</code></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190217110149.png" alt=""></p>
</li>
</ol>
<h1 id="添加重启接口">添加重启接口</h1>
<p>重启的原理很简单，就是在 app 退出前使用方法 <code>NSWorkspace.shared.launchApplication</code> 异步新启动一个 App 实例。</p>
<h2 id="公共变量标记是否重启">公共变量标记是否重启</h2>
<p>为了保证重启操作和退出的区分，需要使用一个公共变量来标记 app 是否需要重启，如果需要重启再进行退出前的特殊操作。将这一变量定义在 <code>AppDelegate.swift</code> 中，这里注意的是要定义在 <code>AppDelegate</code> 类外，放在文件代码 <code>import Cocoa</code> 后即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">var</span> <span class="nv">toRelaunch</span> <span class="p">=</span> <span class="kc">false</span>
</code></pre></div><p>只是标记，所以定义为 <code>Bool</code> 类型，初始化默认值为 <code>false</code>。</p>
<h2 id="app-退出前的特殊处理">App 退出前的特殊处理</h2>
<p>app 退出前的处理可以在文件 <code>AppDelegate.swift</code> 的 <code>AppDelegate</code> 类的 <code>applicationWillTerminate</code> 方法中实现，实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="k">if</span> <span class="p">(</span><span class="n">toRelaunch</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">NSWorkspace</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">launchApplication</span><span class="p">(</span><span class="n">withBundleIdentifier</span><span class="p">:</span> <span class="s">&#34;top.smslit.RelaunchDemo&#34;</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="p">[</span><span class="n">NSWorkspace</span><span class="p">.</span><span class="n">LaunchOptions</span><span class="p">.</span><span class="n">async</span><span class="p">,</span>  <span class="n">NSWorkspace</span><span class="p">.</span><span class="n">LaunchOptions</span><span class="p">.</span><span class="n">newInstance</span><span class="p">],</span> <span class="n">additionalEventParamDescriptor</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="n">launchIdentifier</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>这里要注意的是 <code>withBundleIdentifier</code> 要更改为您的 App 相应的 <code>Bundle Identifier</code> ，这个可以在工程配置信息的 <code>General</code> 中查看。程序启动选项使用的是：</p>
<ul>
<li><code>NSWorkspace.LaunchOptions.async</code>：启动 app 并异步返回结果</li>
<li><code>NSWorkspace.LaunchOptions.newInstance</code>：生成新的实例对象</li>
</ul>
<h2 id="添加-app-重启接口">添加 APP 重启接口</h2>
<p>重启的实现就是将 <code>toRelaunch</code> 设置为 <code>true</code> 后退出 App：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="c1">// 重启 App</span>
<span class="kd">func</span> <span class="nf">relaunchApp</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">toRelaunch</span> <span class="p">=</span> <span class="kc">true</span>
    <span class="n">NSApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">terminate</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>其实，只要是在程序退出前将 <code>toRelaunch</code> 置为 <code>true</code> ，App正常退出的话就会重启 APP。</p>
<h1 id="使用按钮验证效果">使用按钮验证效果</h1>
<p>为了看一下重启实现的效果，可以添加一个按钮，点击调用刚刚实现的重启方法 <code>relaunchApp</code> 就可以看到成果！</p>
<h2 id="添加按钮">添加按钮</h2>
<p>打开 <code>Main.storyboard</code>，按下 <code>Shift</code> + <code>Command</code> + <code>l</code> 组合键就会打开控件库选择器，搜索框搜索 button，找到 <code>Push Button</code>，点击拖入 <code>View Controller</code> 的中央，这样就完成了按钮的添加：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190217111640.png" alt=""></p>
<h2 id="绑定-action">绑定 Action</h2>
<p>下一步绑定 Action 实现点击行为。</p>
<ol>
<li>点击 <code>View Controller</code> ，然后按下组合键 <code>Option</code> + <code>Command</code> + <code>回车</code> 就会调出辅助编辑器</li>
<li>按住 <code>Ctrl</code> 键的同时，鼠标左键点中按钮不放，拖动至打开的辅助编辑器中的 <code>View Controller</code> 类中，出现 <code>Insert Action or Outlet</code> 提示的时候松手，出现弹窗</li>
<li>在弹窗中选择 <code>Action</code> ，name 取为 <code>toRelaunchApp</code>，点击 connect 就会在 <code>View Controller</code> 类中添加方法 <code>toRelaunchApp</code></li>
<li>在方法中调用 <code>relaunchApp</code> 接口即可完成按钮对应的 Action</li>
</ol>
<p>具体操作参考下面视频：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/appRelaunchAction.mp4" controls="controls" width="960"></video></p>
<h2 id="查看效果">查看效果</h2>
<p>点击 Xcode 左上角的运行按钮或按下组合键 <code>Command</code> + <code>r</code> 就会启动 App，在出现的窗口中就会看到我们添加的按钮，点击它就会发现 App 进行了重启，大功告成！</p>
<p>下载此 Demo: <a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/RelaunchDemo.zip">RelaunchDemo</a></p>
<h1 id="参考">参考</h1>
<ul>
<li><a href="https://blog.csdn.net/shagru/article/details/9770047">Cocoa 怎样重新启动app (application relaunching)</a></li>
<li><a href="https://developer.apple.com/documentation/appkit/nsworkspace/1533335-launchapplication">launchApplication(withBundleIdentifier:options:additionalEventParamDescriptor:launchIdentifier:)</a></li>
</ul>
]]></content>
		</item>
		
		<item>
			<title>macOS 开发之实现 HTTP 的 GET 和 POST 请求</title>
			<link>https://blog.5km.studio/2019/01/26/get_post_cocoa/</link>
			<pubDate>Sat, 26 Jan 2019 15:10:40 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/01/26/get_post_cocoa/</guid>
			<description>在 macOS 开发过程中会经常用到外部服务，通常是通过 HTTP 的 GET 和 POST 请求调用它们的 API 获得目标数据，本篇文章就说一下如何在 macOS 开发中实现 GET 和 POST 请求。 平台 因为 swift</description>
			<content type="html"><![CDATA[<p>在 macOS 开发过程中会经常用到外部服务，通常是通过 HTTP 的 GET 和 POST 请求调用它们的 API 获得目标数据，本篇文章就说一下如何在 macOS 开发中实现 GET 和 POST 请求。</p>
<h1 id="平台">平台</h1>
<p>因为 swift 语言在发展期，新版本可能会有写变化，但是使用方法应该不会大变，为了加以注意，这里列出写本文时作者测试代码的平台情况。</p>
<ul>
<li>macOS 10.14.3</li>
<li>xcode 10.1</li>
<li>swift 4.2.1</li>
</ul>
<h1 id="网络请求">网络请求</h1>
<p>本文要讲的是使用 swift 的 <strong>urlSession</strong> 发送 GET 和 POST 请求。先讲一下一般的使用方法，为了更清楚一点，这里先直接粘上代码。</p>
<h2 id="get-请求">GET 请求</h2>
<p>先上代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kd">let</span> <span class="nv">session</span> <span class="p">=</span> <span class="n">URLSession</span><span class="p">(</span><span class="n">configuration</span><span class="p">:</span> <span class="p">.</span><span class="k">default</span><span class="p">)</span>
<span class="kd">let</span> <span class="nv">url</span> <span class="p">=</span> <span class="s">&#34;http://127.0.0.1/api/&#34;</span>
<span class="kd">var</span> <span class="nv">request</span> <span class="p">=</span> <span class="n">URLRequest</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="n">URL</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
<span class="kd">let</span> <span class="nv">task</span> <span class="p">=</span> <span class="n">session</span><span class="p">.</span><span class="n">dataTask</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">request</span><span class="p">)</span> <span class="p">{(</span><span class="n">data</span><span class="p">,</span> <span class="n">response</span><span class="p">,</span> <span class="n">error</span><span class="p">)</span> <span class="k">in</span>
    <span class="k">do</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nv">r</span> <span class="p">=</span> <span class="k">try</span> <span class="n">JSONSerialization</span><span class="p">.</span><span class="n">jsonObject</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">data</span><span class="p">!,</span> <span class="n">options</span><span class="p">:</span> <span class="p">[])</span> <span class="k">as</span><span class="p">!</span> <span class="n">NSDictionary</span>
        <span class="bp">print</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
        <span class="bp">print</span><span class="p">(</span><span class="s">&#34;无法连接到服务器&#34;</span><span class="p">)</span>
        <span class="k">return</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="n">task</span><span class="p">.</span><span class="n">resume</span><span class="p">()</span>
</code></pre></div><ol>
<li>创建一个网络会话</li>
<li>创建网络请求体，默认就是 GET 请求，所以直接使用 url 创建请求即可</li>
<li>利用会话创建一个数据任务，在任务创建中实现相应处理，代码中是将收到的响应数据转换为字典的处理：
<ul>
<li><strong>data</strong> 是请求成功后收到的响应数据</li>
<li><strong>response</strong> 是响应体内容</li>
<li><strong>error</strong> 是网络请求中的错误信息</li>
</ul>
</li>
<li>最后执行创建的数据任务</li>
</ol>
<p>其中 <code>JSONSerialization.jsonObject</code> 是解析 json 字符串为字典数据的方法。</p>
<p>URLSession 具有单例 <code>shared</code> ，所以这里可以使用单例简化操作：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">let</span> <span class="nv">url</span> <span class="p">=</span> <span class="n">URL</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="s">&#34;http://127.0.0.1/api/&#34;</span><span class="p">)</span>
<span class="n">URLSession</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">dataTask</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="p">,</span> <span class="n">response</span><span class="p">,</span> <span class="n">error</span> <span class="k">in</span>
    <span class="k">guard</span> <span class="kd">let</span> <span class="nv">data</span> <span class="p">=</span> <span class="n">data</span> <span class="k">else</span> <span class="p">{</span>
        <span class="bp">print</span><span class="p">(</span><span class="n">error</span><span class="p">?.</span><span class="n">localizedDescription</span> <span class="p">??</span> <span class="s">&#34;Unkown Error!&#34;</span><span class="p">)</span>
        <span class="k">return</span>
    <span class="p">}</span>
    <span class="bp">print</span><span class="p">(</span><span class="nb">String</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">data</span><span class="p">,</span> <span class="n">encoding</span><span class="p">:</span> <span class="p">.</span><span class="n">utf8</span><span class="p">))</span>
<span class="p">}.</span><span class="n">resume</span><span class="p">()</span>
</code></pre></div><p>下面的所有请求均可以使用 <code>URLSession</code> 的单例 <code>shared</code> 实现，下面就不一一赘述！</p>
<h2 id="post-请求">POST 请求</h2>
<p>基本步骤类似于 GET 请求，只是在请求体上的配置有所不同，基本使用代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kd">let</span> <span class="nv">session</span> <span class="p">=</span> <span class="n">URLSession</span><span class="p">(</span><span class="n">configuration</span><span class="p">:</span> <span class="p">.</span><span class="k">default</span><span class="p">)</span>
<span class="kd">let</span> <span class="nv">url</span> <span class="p">=</span> <span class="s">&#34;http://127.0.0.1/api/&#34;</span>
<span class="kd">var</span> <span class="nv">request</span> <span class="p">=</span> <span class="n">URLRequest</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="n">URL</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
<span class="n">request</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="s">&#34;application/x-www-form-urlencoded&#34;</span><span class="p">,</span> <span class="n">forHTTPHeaderField</span><span class="p">:</span> <span class="s">&#34;Content-Type&#34;</span><span class="p">)</span>
<span class="n">request</span><span class="p">.</span><span class="n">httpMethod</span> <span class="p">=</span> <span class="s">&#34;POST&#34;</span>
<span class="kd">let</span> <span class="nv">postData</span> <span class="p">=</span> <span class="p">[</span><span class="s">&#34;username&#34;</span><span class="p">:</span><span class="s">&#34;xxx&#34;</span><span class="p">,</span><span class="s">&#34;password&#34;</span><span class="p">:</span><span class="s">&#34;123456&#34;</span><span class="p">]</span>
<span class="kd">let</span> <span class="nv">postString</span> <span class="p">=</span> <span class="n">postData</span><span class="p">.</span><span class="n">compactMap</span><span class="p">({</span> <span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nb">String</span> <span class="k">in</span>
    <span class="k">return</span> <span class="s">&#34;</span><span class="si">\(</span><span class="n">key</span><span class="si">)</span><span class="s">=</span><span class="si">\(</span><span class="n">value</span><span class="si">)</span><span class="s">&#34;</span>
<span class="p">}).</span><span class="n">joined</span><span class="p">(</span><span class="n">separator</span><span class="p">:</span> <span class="s">&#34;&amp;&#34;</span><span class="p">)</span>
<span class="n">request</span><span class="p">.</span><span class="n">httpBody</span> <span class="p">=</span> <span class="n">postString</span><span class="p">.</span><span class="n">data</span><span class="p">(</span><span class="n">using</span><span class="p">:</span> <span class="p">.</span><span class="n">utf8</span><span class="p">)</span>
<span class="kd">let</span> <span class="nv">task</span> <span class="p">=</span> <span class="n">session</span><span class="p">.</span><span class="n">dataTask</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">request</span><span class="p">)</span> <span class="p">{(</span><span class="n">data</span><span class="p">,</span> <span class="n">response</span><span class="p">,</span> <span class="n">error</span><span class="p">)</span> <span class="k">in</span>
    <span class="k">do</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nv">r</span> <span class="p">=</span> <span class="k">try</span> <span class="n">JSONSerialization</span><span class="p">.</span><span class="n">jsonObject</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">data</span><span class="p">!,</span> <span class="n">options</span><span class="p">:</span> <span class="n">JSONSerialization</span><span class="p">.</span><span class="n">ReadingOptions</span><span class="p">.</span><span class="n">mutableContainers</span><span class="p">)</span> <span class="k">as</span><span class="p">!</span> <span class="n">NSDictionary</span>
        <span class="bp">print</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
        <span class="bp">print</span><span class="p">(</span><span class="s">&#34;无法连接到服务器&#34;</span><span class="p">)</span>
        <span class="k">return</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="n">task</span><span class="p">.</span><span class="n">resume</span><span class="p">()</span>
</code></pre></div><p>注意：</p>
<ol>
<li>网络请求中使用 setValue 进行配置请求头信息</li>
<li>手动配置请求方法为 <strong>POST</strong></li>
<li>上面的 POST 请求数据使用的类型是 <code>application/x-www-form-urlencoded</code>，所以要对请求数据进行特殊字符加 % 进行编码。</li>
</ol>
<h1 id="问题">问题</h1>
<p>在使用上面的操作过程中，因为每个人不同的需求可能会遇到不同的问题，这里列出可能会遇到的问题。</p>
<h2 id="无法连接到网络沙盒阻止">无法连接到网络(沙盒阻止)</h2>
<p>在使用上述方法发送请求时，遇到了错误：</p>
<blockquote>
<p>dns A server with the specified hostname could not be found</p>
</blockquote>
<p>造成此问题的原因是受沙盒限制导致，两种解决方法：</p>
<ol>
<li>点击项目左侧导航栏中的项目名称，右侧顶部标签选择 <strong>Capabilities</strong> 找到第一项 <strong>NetWork</strong> 勾选 <strong>Incoming Connections (Server)</strong> 和 <strong>Outgoing Connections (Client)</strong>，主要是 <strong>Outgoing Connections (Client)</strong></li>
<li>关闭 App SandBox</li>
</ol>
<p>建议使用第一种方法。</p>
<h2 id="http-屏蔽问题">HTTP 屏蔽问题</h2>
<blockquote>
<p>App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app&rsquo;s Info.plist file.</p>
</blockquote>
<p>这个问题是安全机制导致的，如果请求的 API 链接是 HTTP 的而不是 HTTPS 的可能会出现这个问题，解决方法是，在 <code>Info.plist</code> 中添加 <code>App Transport Security Settings</code>，在其下再添加一个 <code>Allow Arbitrary Loads</code> 值设置为 <code>Yes</code> 即可，如下图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190223122624.png" alt=""></p>
<h2 id="url-编码问题">url 编码问题</h2>
<p>进行请求操作中严格按照了上面的步骤操作，可是收到的是错误响应，最后查明原来是 url 编码问题，十里之前在开发一款图片 OCR 的 app 的时候，需要在 POST 数据中包含图片 BASE64 数据。但其中包含了特殊字符，这些字符需要进行
% 转码，要符合 <strong>RFC 3986</strong> 标准，这里给出十里的解决方法，是对 <strong>String</strong> 进行扩展两个属性，字符串这两个属性分别可以完成 url 编码和解码：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="nc">String</span> <span class="p">{</span>
    <span class="c1">//将原始的url编码为合法的url</span>
    <span class="kd">var</span> <span class="nv">urlEncoded</span><span class="p">:</span> <span class="nb">String</span><span class="p">?</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nv">generalDelimitersToEncode</span> <span class="p">=</span> <span class="s">&#34;:#[]@&#34;</span> <span class="c1">// does not include &#34;?&#34; or &#34;/&#34; due to RFC 3986 - Section 3.4</span>
        <span class="kd">let</span> <span class="nv">subDelimitersToEncode</span> <span class="p">=</span> <span class="s">&#34;!$&amp;&#39;()*+,;=&#34;</span>
        <span class="kd">var</span> <span class="nv">allowedCharacterSet</span> <span class="p">=</span> <span class="n">CharacterSet</span><span class="p">.</span><span class="n">urlQueryAllowed</span>
        <span class="n">allowedCharacterSet</span><span class="p">.</span><span class="n">remove</span><span class="p">(</span><span class="n">charactersIn</span><span class="p">:</span> <span class="s">&#34;</span><span class="si">\(</span><span class="n">generalDelimitersToEncode</span><span class="si">)\(</span><span class="n">subDelimitersToEncode</span><span class="si">)</span><span class="s">&#34;</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">addingPercentEncoding</span><span class="p">(</span><span class="n">withAllowedCharacters</span><span class="p">:</span> <span class="n">allowedCharacterSet</span><span class="p">)</span>
    <span class="p">}</span>
    
    <span class="c1">//将编码后的url转换回原始的url</span>
    <span class="kd">var</span> <span class="nv">urlDecoded</span><span class="p">:</span> <span class="nb">String</span><span class="p">?</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">removingPercentEncoding</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>上面的扩展中使用了现有的 <code>addingPercentEncoding</code> 方法，但是现有的字符集不能完全去除特殊字符，所以作者这里使用了自己创建的字符集 <code>allowedCharacterSet</code>。</p>
<h2 id="界面刷新问题">界面刷新问题</h2>
<p>通过请求获取了数据以后，通常需要更新一下界面中的显示结果，此时就会出现问题！上面的接口采用异步的方式，就是为了防止请求时间过长导致界面卡死，请求是在新的线程进行，但是界面是在主线程处理，所以一旦在请求完直接处理界面的话，就会导致 App 崩溃，所以这里建议使用委托以及在委托线程中修改UI，方法如下（这里以简单的 GET 请求为例，POST 请求类似操作）：</p>
<ul>
<li>封装一个请求的方法，其中一个参数是请求完成后处理事务的回调函数：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">func</span> <span class="nf">sendGetRequest</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="n">completionHandler</span><span class="p">:</span> <span class="p">@</span><span class="n">escaping</span> <span class="p">((</span><span class="n">Data</span><span class="p">?,</span><span class="n">URLResponse</span><span class="p">?,</span><span class="n">Error</span><span class="p">?)-&gt;</span><span class="nb">Void</span><span class="p">))</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nv">session</span> <span class="p">=</span> <span class="n">URLSession</span><span class="p">(</span><span class="n">configuration</span><span class="p">:</span> <span class="p">.</span><span class="k">default</span><span class="p">)</span>
    <span class="kd">let</span> <span class="nv">task</span> <span class="p">=</span> <span class="n">session</span><span class="p">.</span><span class="n">dataTask</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">URL</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span><span class="o">!</span><span class="p">,</span> <span class="n">completionHandler</span><span class="p">:</span> <span class="n">completionHandler</span><span class="p">)</span>
    <span class="n">task</span><span class="p">.</span><span class="n">resume</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div><ul>
<li>定义刚才所说的回调函数，包含参数 data, response 和 error，这个回调可以根据不同 API 功能定义多个，以相应功能命名，比如是获取天气：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">func</span> <span class="nf">getWeatherSuccess</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">Data</span><span class="p">?,</span> <span class="n">response</span><span class="p">:</span> <span class="n">URLResponse</span><span class="p">?,</span> <span class="n">error</span><span class="p">:</span> <span class="n">Error</span><span class="p">?)</span> <span class="p">-&gt;</span> <span class="nb">Void</span> <span class="p">{</span>
    <span class="n">DispatchQueue</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">async</span> <span class="p">{</span>
        <span class="c1">// 这里完成 UI 相关操作，调用 UI 对象时要加上 self</span>
        <span class="c1">// 比如： self.weatherLabel = &#34;...&#34;</span>
    <span class="p">}</span>
<span class="p">}</span> 
</code></pre></div><ul>
<li>最后在要请求数据的地方调用第一步定义的通用方法即可，只需指定不同的处理回调就可以完成多 API 的不同请求：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="n">sendGetRequest</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="s">&#34;http://127.0.0.1/api&#34;</span><span class="p">,</span> <span class="n">getWeatherSuccess</span><span class="p">(</span><span class="n">data</span><span class="p">:</span><span class="n">response</span><span class="p">:</span><span class="n">error</span><span class="p">:))</span>
</code></pre></div><h1 id="天气实例">天气实例</h1>
<p>下面以一个获取天气的实例，讲解一下 HTTP 的 GET 请求方式，POST 方式按照上面讲的方式基本差不多少，无非是加了请求数据，这里就先不讲 POST 请求实例了。</p>
<p>我们要做的实例很简单，窗口中有一个 push button 和 一个 label，当点击 button 的时候就会触发请求获取天气数据，并将温度显示在标签上，不作复杂的处理讲明白方法即可。</p>
<h2 id="天气-api">天气 API</h2>
<p>这里使用<a href="https://www.sojson.com/blog/305.html">免费天气API，天气JSON API，不限次数获取十五天的天气预报</a>提供的天气API，免费使用，可免费使用，不过免费版本数据固定 8 个小时更新一次，这倒无所谓了，我们只是用来学习实现 GET 请求而已。</p>
<h3 id="get-请求格式">GET 请求格式</h3>
<p>示例请求链接：http://t.weather.sojson.com/api/weather/city/101030100 。</p>
<p>不用再带任何参数，请求是restfull风格，可以看到链接最后有一串数字，这串数字是城市编码 city_code ，一个 9 位数字，上面的编码代表的是 <strong>天津市</strong> 。地址是 <a href="http://t.weather.sojson.com/api/weather/city/">http://t.weather.sojson.com/api/weather/city/</a> 和 city_code 的拼接。</p>
<h3 id="城市编码">城市编码</h3>
<p>城市编码可以在 <a href="http://cdn.sojson.com/_city.json"><strong>_city.json</strong></a> 中查询，打开文件查找即可，这里我们查询 济南，可以查找到：</p>
<div class="highlight"><pre class="chroma"><code class="language-Json" data-lang="Json"><span class="p">{</span>
  <span class="nt">&#34;_id&#34;</span><span class="p">:</span> <span class="mi">281</span><span class="p">,</span>
  <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="mi">282</span><span class="p">,</span>
  <span class="nt">&#34;pid&#34;</span><span class="p">:</span> <span class="mi">21</span><span class="p">,</span>
  <span class="nt">&#34;city_code&#34;</span><span class="p">:</span> <span class="s2">&#34;101120101&#34;</span><span class="p">,</span>
  <span class="nt">&#34;city_name&#34;</span><span class="p">:</span> <span class="s2">&#34;济南&#34;</span>
<span class="p">}</span>
</code></pre></div><p>所以最终 GET 请求链接是：http://t.weather.sojson.com/api/weather/city/101120101</p>
<h2 id="新建-cocoa-app-工程">新建 Cocoa APP 工程</h2>
<p>为了直接明了这里直接把工程新建和控件添加的过程以视频展示：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/getDemo_newProject.mp4" width="960" controls="controls">新建空工程</video></p>
<h2 id="添加-get-请求方法">添加 GET 请求方法</h2>
<p>打开文件 <code>ViewController.swift</code> ，在 <code>ViewController</code> 中添加如下方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">class</span> <span class="nc">func</span> <span class="n">getRequest</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="n">completionHandler</span><span class="p">:</span> <span class="p">@</span><span class="n">escaping</span> <span class="p">((</span><span class="n">Data</span><span class="p">?,</span> <span class="n">URLResponse</span><span class="p">?,</span> <span class="n">Error</span><span class="p">?)-&gt;</span><span class="nb">Void</span><span class="p">))</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nv">session</span> <span class="p">=</span> <span class="n">URLSession</span><span class="p">.</span><span class="kd">init</span><span class="p">(</span><span class="n">configuration</span><span class="p">:</span> <span class="p">.</span><span class="k">default</span><span class="p">)</span>
    <span class="kd">let</span> <span class="nv">task</span> <span class="p">=</span> <span class="n">session</span><span class="p">.</span><span class="n">dataTask</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">URL</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span><span class="o">!</span><span class="p">,</span> <span class="n">completionHandler</span><span class="p">:</span> <span class="n">completionHandler</span><span class="p">)</span>
    <span class="n">task</span><span class="p">.</span><span class="n">resume</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div><h2 id="请求成功后处理回调">请求成功后处理回调</h2>
<p>定义一个请求成功后的回调函数，同样放到 <code>ViewController</code> 中：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kd">func</span> <span class="nf">parseTemperatureForToday</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">Data</span><span class="p">?,</span> <span class="n">response</span><span class="p">:</span> <span class="n">URLResponse</span><span class="p">?,</span> <span class="n">error</span><span class="p">:</span> <span class="n">Error</span><span class="p">?)</span> <span class="p">-&gt;</span> <span class="nb">Void</span> <span class="p">{</span>
    <span class="n">DispatchQueue</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">async</span> <span class="p">{</span>
        <span class="k">do</span> <span class="p">{</span>
            <span class="kd">let</span> <span class="nv">r</span> <span class="p">=</span> <span class="k">try</span> <span class="n">JSONSerialization</span><span class="p">.</span><span class="n">jsonObject</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">data</span><span class="p">!,</span> <span class="n">options</span><span class="p">:</span> <span class="p">[</span><span class="n">JSONSerialization</span><span class="p">.</span><span class="n">ReadingOptions</span><span class="p">.</span><span class="n">mutableContainers</span><span class="p">])</span> <span class="k">as</span><span class="p">!</span> <span class="n">NSDictionary</span>
            <span class="kd">let</span> <span class="nv">todayData</span> <span class="p">=</span> <span class="n">r</span><span class="p">.</span><span class="n">value</span><span class="p">(</span><span class="n">forKey</span><span class="p">:</span> <span class="s">&#34;data&#34;</span><span class="p">)</span> <span class="k">as</span><span class="p">!</span> <span class="n">NSDictionary</span>
            <span class="kd">let</span> <span class="nv">tempValue</span> <span class="p">=</span> <span class="n">todayData</span><span class="p">.</span><span class="n">value</span><span class="p">(</span><span class="n">forKey</span><span class="p">:</span> <span class="s">&#34;wendu&#34;</span><span class="p">)</span> <span class="k">as</span><span class="p">!</span> <span class="nb">String</span>
            <span class="kc">self</span><span class="p">.</span><span class="n">temperatureLabel</span><span class="p">.</span><span class="n">stringValue</span> <span class="p">=</span> <span class="n">tempValue</span> <span class="o">+</span> <span class="s">&#34; °C&#34;</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
            <span class="bp">print</span><span class="p">(</span><span class="s">&#34;无法连接到服务器&#34;</span><span class="p">)</span>
            <span class="k">return</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><h2 id="按钮点击处理">按钮点击处理</h2>
<p>按钮点击就发送请求，在 <code>getTemperature</code> 方法中添加请求方法的调用即可完成实例：</p>
<div class="highlight"><pre class="chroma"><code class="language-Swift" data-lang="Swift"><span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">getTemperature</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 获取济南天气的 GET 请求链接</span>
    <span class="kd">let</span> <span class="nv">url</span> <span class="p">=</span> <span class="s">&#34;http://t.weather.sojson.com/api/weather/city/101120101&#34;</span>
    <span class="n">ViewController</span><span class="p">.</span><span class="n">getRequest</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="n">completionHandler</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">parseTemperatureForToday</span><span class="p">(</span><span class="n">data</span><span class="p">:</span><span class="n">response</span><span class="p">:</span><span class="n">error</span><span class="p">:))</span>
<span class="p">}</span>
</code></pre></div><h2 id="运行示例">运行示例</h2>
<p>编译运行，可能会遇到本文前面提到的<strong>HTTP 请求屏蔽</strong> 和 <strong>无法连接网络(沙盒阻止)</strong> 的问题，按照上面的解决方法排出问题即可，最终运行效果如下图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190223124656.gif" alt=""></p>
<p><strong>Demo 下载</strong>: <a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/GetRequestDemo.zip">GetRequestDemo</a></p>
<h1 id="参考">参考</h1>
<ul>
<li><a href="https://www.logcg.com/archives/3120.html">swift4 urlSession get和post网络请求</a></li>
<li><a href="https://blog.auv.cool/2018/08/06/Swift/URLSession/">Swift URLSession 网络请求</a></li>
<li><a href="https://blog.csdn.net/agonie201218/article/details/78788685">swift URL 编码</a></li>
<li><a href="https://www.jianshu.com/p/753831ce6adb">Swift4.0 异步Post请求中使用委托以及在委托线程中修改UI</a></li>
<li><a href="https://stackoverflow.com/questions/31254725/transport-security-has-blocked-a-cleartext-http">Transport security has blocked a cleartext HTTP</a></li>
</ul>
]]></content>
		</item>
		
		<item>
			<title>python 利用百度 AI 实现 OCR</title>
			<link>https://blog.5km.studio/2019/01/10/textgo_python/</link>
			<pubDate>Thu, 10 Jan 2019 15:10:32 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/01/10/textgo_python/</guid>
			<description>&lt;p&gt;前两天了解了一下百度 AI，他们提供了免费的图片中文字识别接口，每天可以提供 50000 次免费的通用文字识别，想起了之前帮水木做一个网站时，资料中的问题大部分以图片的形式提供，所以当时就有这个图片 OCR 的需求，本文就说一下如何用 python 借用百度 AI 实现图片的 OCR。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190110210442.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>前两天了解了一下百度 AI，他们提供了免费的图片中文字识别接口，每天可以提供 50000 次免费的通用文字识别，想起了之前帮水木做一个网站时，资料中的问题大部分以图片的形式提供，所以当时就有这个图片 OCR 的需求，本文就说一下如何用 python 借用百度 AI 实现图片的 OCR。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190110210442.png" alt=""></p>
<h1 id="实现分析">实现分析</h1>
<p>阅读了百度提供的文章<a href="http://ai.baidu.com/forum/topic/show/867951"><strong>只要10分钟 快速掌握文字识别</strong></a>，基本就可以整理出实现的步骤了：</p>
<ol>
<li>创建百度应用，获取 api_key 和 secret_key</li>
<li>利用 api_key 和 secret_key 获取 access token</li>
<li>图片转 BASE64</li>
<li>利用图片BASE64码和 access token 构建 POST</li>
<li>响应结果中解析结果</li>
</ol>
<p>其中第一步需要在百度 AI 控制台实现，有了 api_key 和 secret_key 剩下的四步就可以用 python 实现了，最终 python 程序设计思路：</p>
<ol>
<li>指定配置文件和图片路径</li>
<li>读取配置文件中的 api_key 和 secret_key</li>
<li>判断配置文件是否有 access_token
<ul>
<li>如果没有：利用 api_key 和 secret_key 获取 access_key，并将 access_token 写入配置文件</li>
<li>如果有：读取配置中的 access_key</li>
</ul>
</li>
<li>将指定图片转换为 BASE64 码</li>
<li>根据 access_token 和 图片的 BASE64 码 构建 POST，发送请求，解析返回的结果</li>
<li>打印 OCR 结果</li>
</ol>
<h1 id="代码实现">代码实现</h1>
<h2 id="textgo-类">textGO 类</h2>
<p>根据上面的思路，封装了 textGO 类：</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="k">class</span> <span class="nc">TextGO</span><span class="p">:</span>
    <span class="s2">&#34;&#34;&#34; 定义 ocr 类 &#34;&#34;&#34;</span>

    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_file</span><span class="p">):</span>
        <span class="n">config</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">read_config</span><span class="p">(</span><span class="n">config_file</span><span class="p">)</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;api_key&#34;</span><span class="p">)</span> <span class="ow">and</span> <span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;secret_key&#34;</span><span class="p">)):</span>
            <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;请指定一个合理的配置文件，配置文件中要包含 api_key 和 secret_key!&#34;</span><span class="p">)</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">access_token</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
            <span class="k">return</span>
        <span class="n">access_token</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;access_token&#34;</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">access_token</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">or</span> <span class="n">access_token</span> <span class="o">==</span> <span class="s2">&#34;&#34;</span><span class="p">:</span>
            <span class="n">access_token</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_access_token</span><span class="p">(</span>
                <span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;api_key&#34;</span><span class="p">),</span> <span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;secret_key&#34;</span><span class="p">))</span>
            <span class="n">config</span><span class="p">[</span><span class="s2">&#34;access_token&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">access_token</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">write_config</span><span class="p">(</span><span class="n">config_file</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">access_token</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;access_token&#34;</span><span class="p">)</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span> <span class="nf">read_config</span><span class="p">(</span><span class="n">config_file</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
        <span class="s2">&#34;&#34;&#34; 从 json 文件中读取配置 &#34;&#34;&#34;</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">config_file</span><span class="p">):</span>
            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;指定的配置文件 </span><span class="si">{</span><span class="n">config_file</span><span class="si">}</span><span class="s2"> 不存在!&#34;</span><span class="p">)</span>
            <span class="k">return</span> <span class="p">{}</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">config_file</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">load_file</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">load_file</span><span class="p">)</span>
        <span class="k">return</span> <span class="p">{}</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span> <span class="nf">write_config</span><span class="p">(</span><span class="n">config_file</span><span class="p">,</span> <span class="n">config</span><span class="p">):</span>
        <span class="s2">&#34;&#34;&#34; 将配置写入文件 &#34;&#34;&#34;</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">config_file</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f_obj</span><span class="p">:</span>
            <span class="n">json</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="n">f_obj</span><span class="p">)</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span> <span class="nf">img_to_base64</span><span class="p">(</span><span class="n">img</span><span class="p">):</span>
        <span class="s2">&#34;&#34;&#34; convert img to base64 data &#34;&#34;&#34;</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">img</span><span class="p">,</span> <span class="s2">&#34;rb&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">img_f</span><span class="p">:</span>
            <span class="n">file_data</span> <span class="o">=</span> <span class="n">img_f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
            <span class="n">b64_data</span> <span class="o">=</span> <span class="n">b64encode</span><span class="p">(</span><span class="n">file_data</span><span class="p">)</span>
            <span class="n">b64_str</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">b64_data</span><span class="p">,</span> <span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">b64_str</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span> <span class="nf">get_access_token</span><span class="p">(</span><span class="n">api_key</span><span class="p">,</span> <span class="n">secret_key</span><span class="p">):</span>
        <span class="s2">&#34;&#34;&#34; 获取百度 AI 的文字识别 access_token &#34;&#34;&#34;</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;获取百度 AI 文字识别的 Access Token ...&#34;</span><span class="p">)</span>
        <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&amp;client_id=</span><span class="si">%s</span><span class="s2">&amp;client_secret=</span><span class="si">%s</span><span class="s2">&#34;</span>
        <span class="n">response</span><span class="p">:</span> <span class="n">requests</span><span class="o">.</span><span class="n">models</span><span class="o">.</span><span class="n">Response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
            <span class="n">url</span> <span class="o">%</span> <span class="p">(</span><span class="n">api_key</span><span class="p">,</span> <span class="n">secret_key</span><span class="p">))</span>
        <span class="n">body_str</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">content</span><span class="p">,</span> <span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
        <span class="n">body_dict</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">body_str</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">body_dict</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;access_token&#34;</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">ocr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">img</span><span class="p">):</span>
        <span class="s2">&#34;&#34;&#34; 利用百度 AI 识别文字 &#34;&#34;&#34;</span>
        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">access_token</span> <span class="o">==</span> <span class="s2">&#34;&#34;</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;没有 access_token&#34;</span><span class="p">)</span>
            <span class="k">return</span> <span class="s2">&#34;&#34;</span>
        <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;https://aip.baidubce.com/rest/2.0/ocr/v1/accurate?access_token=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">access_token</span><span class="si">}</span><span class="s2">&#34;</span>
        <span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;Content-Type&#34;</span><span class="p">:</span> <span class="s2">&#34;application/x-www-form-urlencoded&#34;</span><span class="p">}</span>
        <span class="n">data</span> <span class="o">=</span> <span class="p">{</span>
            <span class="s2">&#34;image_type&#34;</span><span class="p">:</span> <span class="s2">&#34;BASE64&#34;</span><span class="p">,</span>
            <span class="s2">&#34;image&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">img_to_base64</span><span class="p">(</span><span class="n">img</span><span class="p">),</span>
            <span class="s2">&#34;group_id&#34;</span><span class="p">:</span> <span class="s2">&#34;textGO_test&#34;</span><span class="p">,</span>
            <span class="s2">&#34;user_id&#34;</span><span class="p">:</span> <span class="s2">&#34;5km&#34;</span>
        <span class="p">}</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="o">=</span><span class="n">url</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">data</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
            <span class="n">body_str</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">content</span><span class="p">,</span> <span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
            <span class="n">body_dict</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">body_str</span><span class="p">)</span>
            <span class="n">words</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
            <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">body_dict</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;words_result&#34;</span><span class="p">):</span>
                <span class="n">words</span> <span class="o">+=</span> <span class="n">line</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;words&#34;</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span>
            <span class="k">return</span> <span class="n">words</span>
        <span class="k">return</span> <span class="s2">&#34;&#34;</span>
</code></pre></div><ol>
<li>图片转码使用了内建库 <strong>base64</strong></li>
<li>配置的读取和写入使用了库 <strong>json</strong></li>
<li>发送 GET 和 POST 请求使用库 <strong>requests</strong></li>
</ol>
<h2 id="解析命令参数">解析命令参数</h2>
<p>为了更好的实现命令形式，这里使用了库 <strong>argparse</strong>，封装了函数 <code>get_args</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="k">def</span> <span class="nf">get_args</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">argparse</span><span class="o">.</span><span class="n">Namespace</span><span class="p">:</span>
    <span class="s2">&#34;&#34;&#34; 获取命令参数 &#34;&#34;&#34;</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;OCR 识别图片中的文字&#34;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;img&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&#34;指定要识别的图片&#34;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
        <span class="s2">&#34;-c&#34;</span><span class="p">,</span> <span class="s2">&#34;--config&#34;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">&#34;./config.json&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&#34;指定配置文件&#34;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</code></pre></div><h2 id="功能使用">功能使用</h2>
<p>定义 main 函数，实现整个过程：</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="s2">&#34;&#34;&#34; 主函数 &#34;&#34;&#34;</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">get_args</span><span class="p">()</span>
    <span class="n">textGO</span> <span class="o">=</span> <span class="n">TextGO</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">config</span><span class="p">)</span>
    <span class="n">words</span> <span class="o">=</span> <span class="n">textGO</span><span class="o">.</span><span class="n">ocr</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">img</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">words</span><span class="p">)</span>
</code></pre></div><p>在使用前还需要按照<a href="http://ai.baidu.com/forum/topic/show/867951">只要10分钟 快速掌握文字识别</a>中的操作获取自己的 api_key 和 secret_key ，将他们以下面的形式写入到一个文件，我这里写入文件是 <code>config.json</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-json" data-lang="json"><span class="p">{</span><span class="nt">&#34;api_key&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="nt">&#34;secret_key&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">}</span>
</code></pre></div><blockquote>
<p>将自己应用的 api_key 和 secret_key 对应填到上面 json 内容的对应位置！</p>
</blockquote>
<p>然后调用主函数：</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
    <span class="n">main</span><span class="p">()</span>
</code></pre></div><p>最终 textGO.py 的文件代码就完成了。</p>
<h2 id="测试功能">测试功能</h2>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">python3 textGO.py -h
</code></pre></div><p>通过上述命令查看帮助信息：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">usage: textGO.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-c CONFIG<span class="o">]</span> img

OCR 识别图片中的文字

positional arguments:
  img                   指定要识别的图片

optional arguments:
  -h, --help            show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -c CONFIG, --config CONFIG
                        指定配置文件
</code></pre></div><p>准备一张图片，假如：</p>
<ul>
<li>
<p>图片路径是 <code>~/Desktop/demo.png</code>：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190110231944.png" alt=""></p>
</li>
<li>
<p>配置文件路径为 <code>~/Desktop/config</code></p>
</li>
</ul>
<p>那么可以使用如下命令并得到结果:</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">python3 textGO.py -c ~/Desktop/config.json ~/Desktop/demo.png
获取百度 AI 文字识别的 Access Token ...
 macOS Mojave
版本10.14.2
 MacBook Pro<span class="o">(</span>15-inch, 2016<span class="o">)</span>
处理器2.6 GHz Intel Core i7
内存16GB2133 MHZ LPDDR3
图形卡 Radeon Pro4502048MB
 Intel HD Graphics <span class="m">530</span> <span class="m">1536</span> MB
序列号co2 SXCQWGTFL
</code></pre></div><h2 id="代码托管">代码托管</h2>
<p>代码已托管至 github：<a href="https://github.com/smslit/tools-with-script/tree/master/textGO">tools-with-script/textGO/</a></p>]]></content>
		</item>
		
		<item>
			<title>TimeGO 一款轻量简洁的计时提醒 app</title>
			<link>https://blog.5km.studio/2019/01/08/timeGO/</link>
			<pubDate>Tue, 08 Jan 2019 04:35:37 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/01/08/timeGO/</guid>
			<description>&lt;p&gt;之前了解到番茄工作法，一直寻找一款轻量的番茄钟的 macOS app，结果市面上的 app 一般都很重，带一些自己并不需要的功能，好不容易找到一个叫 &lt;a href=&#34;https://toolinbox.net/iTimer/&#34;&gt;iTimer&lt;/a&gt; 的 app，结果它只包含两个时间设置，添加自定义的竟然收费！算了，功能比较简单，那自己开发一款可以用作番茄钟的计时提醒 app 得了，便有了 &lt;strong&gt;timeGO&lt;/strong&gt;。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>之前了解到番茄工作法，一直寻找一款轻量的番茄钟的 macOS app，结果市面上的 app 一般都很重，带一些自己并不需要的功能，好不容易找到一个叫 <a href="https://toolinbox.net/iTimer/">iTimer</a> 的 app，结果它只包含两个时间设置，添加自定义的竟然收费！算了，功能比较简单，那自己开发一款可以用作番茄钟的计时提醒 app 得了，便有了 <strong>timeGO</strong>。</p>
<h1 id="简介">简介</h1>
<ul>
<li>
<p>可配置时间和通知信息，可使用以下表达式配置计时器序列组合
表达式格式如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">$0+1$
</code></pre></div><ol>
<li>其中 0 和 1 是普通计时器的编号，完成序号 0 的计时器计时以后会自动开启序号 1 的计时。</li>
<li>指定的序号必须是普通计时器的编号</li>
<li>可以重复使用序号，比如 <strong>$0+1+0+1+0+1+0+1$</strong>，会按照表达式顺序执行相应计时器。</li>
</ol>
</li>
<li>
<p>支持语音提醒，跟随软件的语言设置自动选择相关语言的提醒助手。</p>
</li>
<li>
<p>状态栏小工具，轻量，可以快速控制计时器，让您更加专注自己的工作，状态栏显示进度</p>
</li>
<li>
<p>设计清新，适应系统主题，Dark 模式显示更佳</p>
</li>
<li>
<p>开源，已托管于 github，<a href="https://github.com/smslit/timeGO">smslit/timeGO</a></p>
</li>
<li>
<p>支持多种语言：简体中文、繁体中文、英文、日文、韩文</p>
</li>
</ul>
<p>如果您喜欢，可以到<a href="https://apps.apple.com/cn/app/timego/id6448658165?mt=12">商店</a>下载支持，或者去 github 项目仓库的 <a href="https://github.com/smslit/timeGO/releases">release</a> 页面免费下载!</p>
<h1 id="app-截图">App 截图</h1>
<h2 id="图标截图">图标截图</h2>
<p>原图标设计灵感来源于 Smartisan OS 系统自带的时钟 app。</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190108030519.png" width="128px">
<p>为了遵照苹果的图标设计规范，重新设计了一版：</p>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/icon_256x256-20230508-193901.png" width="128px">
<h2 id="应用截图">应用截图</h2>
<ul>
<li>
<p>浅色模式(Aqua Mode)</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/light@2x-20230509-113455.jpg" alt=""></p>
</li>
<li>
<p>深色模式(Dark Aqua Mode)</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/uPic/dark@2x-20230509-113528.jpg" alt=""></p>
</li>
</ul>
<h1 id="支持">支持</h1>
<h2 id="分享">分享</h2>
<p>如果您认为这个 app 还不错的话，那就分享给您周边的小伙伴吧，一起感受分享的快乐！</p>
<h2 id="反馈">反馈</h2>
<p>如果您有什么建议或者使用过程中遇到了问题，就反馈一下吧，您可以通过以下三种方式进行反馈：</p>
<ul>
<li>此页面的评论留言</li>
<li>app 设置窗口的 <strong>反馈信息</strong> 功能</li>
<li>直接向 <a href="mailto:5km@smslit.cn">5km@smslit.cn</a> 发送邮件</li>
</ul>
<h2 id="打赏">打赏</h2>
<p>如果您很欣赏，那就点下面的 <strong>赞赏支持</strong> 扫码请我喝咖啡吧！访问页面<a href="https://bento.me/5km">十里</a>可以找到更多支持我的方式！</p>
<p>或者商店购买支持（只需 1 元 😄），<a href="https://apps.apple.com/cn/app/timego/id6448658165?mt=12">时空隧道</a></p>
<h1 id="更新日志">更新日志</h1>
<h2 id="v10">v1.0</h2>
<p>诈尸版更新，自 2019 年至今四年未更新，毕竟是开发的第一个 macOS ，决定上架商店，生成了几个促销代码，欢迎使用：</p>
<ul>
<li>KWNF46JJX3PP</li>
<li>MXKMNJRK9PL7</li>
<li>MAWNMXXJ3YMK</li>
<li>E6X7XA4W7L9A</li>
<li>J34MWL3KK4TT</li>
<li>49NJNMHEKM3A</li>
<li>3Y73YWJHM76M</li>
<li>3YRAY4EKJNY3</li>
</ul>
<h3 id="特性">特性</h3>
<ul>
<li>适配最新 macOS，支持 10.14 版本以上系统</li>
</ul>
<h3 id="部分调整">部分调整</h3>
<ul>
<li>修改关于页面，指向此页面</li>
<li>去掉设置页面的检查更新</li>
</ul>
<h3 id="问题修复">问题修复</h3>
<ul>
<li>修复按钮显示和操作异常问题</li>
</ul>
<details>
<summary>点我查看更多</summary>
<h2 id="v030">v0.3.0</h2>
<h3 id="功能添加">功能添加</h3>
<ol>
<li>定时器配置支持文本编辑基本快捷键</li>
<li>国际化 APP，并添加界面语言的设置，支持语言：
<ul>
<li>简体中文</li>
<li>繁体中文</li>
<li>英文</li>
<li>日文</li>
<li>韩文</li>
</ul>
</li>
</ol>
<h3 id="部分调整-1">部分调整</h3>
<ol>
<li>语音提醒跟随界面语言设置调整声音。</li>
<li>调整定时器配置读取的处理</li>
<li>调整控件布局</li>
</ol>
<h3 id="问题修复-1">问题修复</h3>
<ol>
<li>修复定时器公式输入一个$符引起崩溃的问题</li>
<li>修复控件布局问题</li>
<li>解决没有定时器设置时点击开始按钮崩溃的问题</li>
</ol>
<h2 id="v021">v0.2.1</h2>
<h3 id="功能添加-1">功能添加</h3>
<p>添加语音提醒支持，并添加相关配置项</p>
<h3 id="部分调整-2">部分调整</h3>
<ul>
<li>删除没必要的 action 连接</li>
<li>优化设置数据操作</li>
<li>支持 macOS 10.13</li>
<li>关于窗口标题栏透明</li>
</ul>
<h2 id="v020">v0.2.0</h2>
<h3 id="功能添加-2">功能添加</h3>
<ol>
<li><strong>状态栏显示计时器进度</strong></li>
<li>关于窗口添加链接</li>
<li>添加计时器的备注功能;</li>
<li><strong>添加彩蛋: 可定义计时器序列，任意组合已有普通计时器</strong></li>
</ol>
<h3 id="部分调整-3">部分调整</h3>
<ol>
<li>调整退出按钮到第一层弹窗中，方便退出</li>
<li>删除提醒信息 label; 调整计时选择器的项目标题格式</li>
<li>调整新添加计时器默认备注为空字符串</li>
</ol>
<h3 id="问题修复-2">问题修复</h3>
<ol>
<li>修复计时器开起时仍是满进度的 bug</li>
<li>修复 app 运行久了无法出现进度显示的问题</li>
<li>修复软件版本号显示不全的问题</li>
</ol>
<h2 id="v010">v0.1.0</h2>
<ol>
<li>添加计时器配置功能</li>
<li>添加额外功能：反馈和关于</li>
<li>优化部分代码实现，修复程序特定情况崩溃的问题</li>
</ol>
<h2 id="v000">v0.0.0</h2>
<ul>
<li>固定的时间选择</li>
<li>倒计时结束通知</li>
<li>状态栏小工具的形式运行</li>
</ul>
</details>]]></content>
		</item>
		
		<item>
			<title>开发的 macOS app 打包为精美的 dmg</title>
			<link>https://blog.5km.studio/2019/01/06/mac_app_to_dmg/</link>
			<pubDate>Sun, 06 Jan 2019 21:06:56 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/01/06/mac_app_to_dmg/</guid>
			<description>&lt;p&gt;最近在开发一个名为 timeGO 的 macOS 应用程序，一款倒计时应用。看到网上别人家 app 的 dmg 安装包，美得很，本文就以这个 app 为例说一下如何将自己的应用发布并打包成带图标和安装提示背景的 dmg 安装包。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106211429.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>最近在开发一个名为 timeGO 的 macOS 应用程序，一款倒计时应用。看到网上别人家 app 的 dmg 安装包，美得很，本文就以这个 app 为例说一下如何将自己的应用发布并打包成带图标和安装提示背景的 dmg 安装包。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106211429.png" alt=""></p>
<h1 id="发布-app">发布 app</h1>
<p>十里使用 xcode 开发 timeGO 这款应用，因为要精美所以本身应用也得精美对吧！所以十里为应用开发专门设计了精美的 appIcon (图标)，这样导出的 app 会有精美的图标。发布 app 的方式有很多种，这里说一个比较常用的发布到本地的方式。</p>
<ol>
<li>
<p>xcode 打开应用工程</p>
</li>
<li>
<p>菜单栏选择 <strong>product</strong> -&gt; <strong>Archive</strong>，此时 xcode 就会打包应用，完成后会跳到类似如下的包管理窗口:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106212650.png" alt=""></p>
</li>
<li>
<p>点击 <code>Distribute App</code> 按钮，选择 <code>Copy App</code>：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106212928.png" alt=""></p>
</li>
<li>
<p>点击 <code>Next</code>，选择导出名称和目录，这里名称使用默认，目录选择 <code>Desktop</code>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106213101.png" alt=""></p>
</li>
<li>
<p>点击 <code>Export</code> 之后，就会在 <code>Desktop</code> 中看到一个步骤4中的名称相同的目录，里面就是导出的 app:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106213609.png" alt=""></p>
</li>
</ol>
<p>以上就完成了 app 的发布。</p>
<h1 id="准备素材">准备素材</h1>
<p>制作 dmg 前需要准备 app 的图标文件 icns 和背景图。</p>
<h2 id="app-的图标文件">app 的图标文件</h2>
<p>这里需要的图标文件为 icns 格式，十里在开发 timeGO 的时候只准备了 png 格式的文件，难道还得进行转换不成，其实不需要，macOS 的应用图标都是 icns 格式，所以可以从刚刚导出的 app 中获取这个图标文件。</p>
<ol>
<li>
<p>在刚才导出的 app 上右键，选择 <code>显示包内容</code></p>
</li>
<li>
<p>此时会打开显示 app 内的文件，依次进入 <code>Contents</code> - <code>Resources</code></p>
</li>
<li>
<p>不出意外就会看到一个格式为 icns 的图片文件，其样子与 app 的图标一样，这就是我们想要的 icns，将其拷贝到桌面：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106215254.png" alt=""></p>
</li>
</ol>
<h2 id="背景图片">背景图片</h2>
<p>相信读者在安装一下 dmg 文件的应用时，都会知道，打开 dmg 文件后，目录中有个背景图片提示将 app 拖放到 applications 文件夹中，像 qq for mac的也是如此：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106215921.png" alt=""></p>
<p>所以我们也需要一个这样的背景图，自己设计就好了，说明意思就 OK，十里使用 sketch 设计了一个简陋的背景图( png 格式)，也放在了桌面，名为 dmg.png:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106221232.png" alt=""></p>
<h1 id="制作-dmg">制作 dmg</h1>
<p>查看 app 、准备的 icns 图标和背景图共占空间的大小，十里这里是：</p>
<table>
<thead>
<tr>
<th>文件</th>
<th>空间大小</th>
</tr>
</thead>
<tbody>
<tr>
<td>timeGO.app</td>
<td>约13.1MB</td>
</tr>
<tr>
<td>AppIcon.icns</td>
<td>65KB</td>
</tr>
<tr>
<td>dmg.png</td>
<td>33KB</td>
</tr>
<tr>
<td>共计</td>
<td>约13.2MB(肯定小于13.3MB)</td>
</tr>
</tbody>
</table>
<p>这里统计三个文件的大小是因为一会儿要使用。</p>
<h2 id="新建空的-dmg-文件">新建空的 dmg 文件</h2>
<ol>
<li>
<p>打开 <code>磁盘工具.app</code></p>
</li>
<li>
<p>菜单栏中：<code>文件</code> - <code>新建映像</code> - <code>空白映像</code></p>
</li>
<li>
<p>在出来的对话框中按您的需求修改橙色框圈起来的地方，其中空间大小设置略大于上面提到的 13.2MB 即可，这里设置为 14.5MB：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106222645.png" alt=""></p>
</li>
<li>
<p>创建完成后就可以在桌面看到空白的映像</p>
</li>
</ol>
<h2 id="放置-dmg-中的文件">放置 dmg 中的文件</h2>
<ol>
<li>
<p>将第一节中导出的 app 文件、桌面上的 icns 文件和背景图全部拖进新建的映像文件中:</p>
 <video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2019-01-06%20_10.42.31.mp4" controls="controls" width="640px">
</li>
<li>
<p>在映像中创建 <code>/Applications</code> 的软链接(在终端下执行命令，cd 的目录改为您对应映像挂载的目录)</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell"><span class="nb">cd</span> /Volumes/timeGO
ln -s /Applications Applications
</code></pre></div></li>
</ol>
<h2 id="配置映像文件的显示选项">配置映像文件的显示选项</h2>
<ol>
<li>
<p>打开映像挂载目录，目录中会看到四个文件：timeGO.app, dmg.png, AppIcon.icns 和 Applications</p>
</li>
<li>
<p>目录中右键点击 <code>查看显示选项</code>，按照下图更改选项：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106230655.png" alt=""></p>
</li>
<li>
<p>把 dmg.png 拖入显示选项下面的框中，为目录设置背景，调整 <code>timeGO.app</code> 和 <code>Applications</code> 到合适位置</p>
</li>
<li>
<p>调整窗口大小刚好覆盖背景图</p>
</li>
<li>
<p>在桌面映像挂载目录上右击选择 <code>显示简介</code>，会跳出目录的简介对话框，拖动目录中的图标文件到简介窗口左上角的 logo 位置，就把挂载目录的图标修改为应用图标了</p>
</li>
<li>
<p>最后隐藏图标文件和背景图：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell"><span class="nb">cd</span> /Volumes/timeGO
chflags hidden AppIcon.icns
chflags hidden dmg.png
</code></pre></div></li>
</ol>
<p>最终打开映像的挂载目录会是类似下面的样子：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106211429.png" alt=""></p>
<h2 id="转换-dmg">转换 dmg</h2>
<p>最后一步转换 dmg 文件，这一步主要起到的作用是压缩文件，减小 dmg 文件的占用空间。</p>
<ol>
<li>
<p>弹出挂载的映像目录</p>
</li>
<li>
<p>打开 <code>磁盘工具.app</code></p>
</li>
<li>
<p>菜单栏 - <code>映像</code> - <code>转换</code>，在弹出的对话框中选择刚刚创建的 dmg 文件：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106232643.png" alt=""></p>
</li>
<li>
<p>点击 <code>选取</code> 后，在弹窗中配置名称和导出目录：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190106232835.png" alt=""></p>
</li>
<li>
<p>点击 <code>转换</code>，完成后就看到了最终的 dmg 文件了</p>
</li>
</ol>
<p>最后可以打开这个 dmg 验证一下，是不是骚气了！</p>]]></content>
		</item>
		
		<item>
			<title>为 mac 连接的 2k 显示器开启 HiDPI</title>
			<link>https://blog.5km.studio/2019/01/02/mac_hidpi/</link>
			<pubDate>Wed, 02 Jan 2019 17:20:38 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2019/01/02/mac_hidpi/</guid>
			<description>&lt;p&gt;最近入手了 thinkvision 的 24寸显示器 &lt;a href=&#34;https://zhuanlan.zhihu.com/p/27516738&#34;&gt;t24h-10&lt;/a&gt;，之所以选择这款显示器，除了很好的工业设计和做工外，最主要的是支持 type-c 连接，用来显示的同时，扩展出了 4 个常规的 USB3.0 接口和 1 个 3.5mm 耳麦接口，并且使用 type-c 的话，显示器可以为我的 macbook pro 反向供电。虽然是 2k(2560*1440) 的分辨率，但因为近距离使用还难免有颗粒感，同时显示内容也会偏小，直观感受就是不细腻，心里还是有个疙瘩！还好最近了解到 macOS 上拥有 HiDPI^[&lt;a href=&#34;https://zhuanlan.zhihu.com/p/36913571&#34;&gt;Macbook外接2K显示器时，如何开启HiDPI?&lt;/a&gt;] 渲染技术，本文就讲解一下如何为自己的 2K 显示器开启 HiDPI 实现 Retina 的效果^[&lt;a href=&#34;https://zhuanlan.zhihu.com/p/20684620?refer=ibuick&#34;&gt;有关retina和HiDPI那点事&lt;/a&gt;]。&lt;/p&gt;
&lt;p&gt;高PPI(硬件) + HiDPI渲染(软件) = 更细腻的显示效果(retina)&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190102211703.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>最近入手了 thinkvision 的 24寸显示器 <a href="https://zhuanlan.zhihu.com/p/27516738">t24h-10</a>，之所以选择这款显示器，除了很好的工业设计和做工外，最主要的是支持 type-c 连接，用来显示的同时，扩展出了 4 个常规的 USB3.0 接口和 1 个 3.5mm 耳麦接口，并且使用 type-c 的话，显示器可以为我的 macbook pro 反向供电。虽然是 2k(2560*1440) 的分辨率，但因为近距离使用还难免有颗粒感，同时显示内容也会偏小，直观感受就是不细腻，心里还是有个疙瘩！还好最近了解到 macOS 上拥有 HiDPI^[<a href="https://zhuanlan.zhihu.com/p/36913571">Macbook外接2K显示器时，如何开启HiDPI?</a>] 渲染技术，本文就讲解一下如何为自己的 2K 显示器开启 HiDPI 实现 Retina 的效果^[<a href="https://zhuanlan.zhihu.com/p/20684620?refer=ibuick">有关retina和HiDPI那点事</a>]。</p>
<p>高PPI(硬件) + HiDPI渲染(软件) = 更细腻的显示效果(retina)</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190102211703.png" alt=""></p>
<p>操作平台：</p>
<ul>
<li>macbook pro 15寸 2016版</li>
<li>thinkvision t24h-10</li>
<li>macOS Mojave (10.14)</li>
</ul>
<h1 id="关闭-sip">关闭 SIP</h1>
<p>因为后面的配置中会需要调整 <code>/System/</code>，<strong>System Integrity Protection</strong>(SIP) 会禁止操作，所以需要禁用 SIP，来获取对 <code>/System/</code> 的操作权限。</p>
<ol>
<li>重启 mac，出现苹果 logo 之前按住 <code>command</code> + <code>r</code> 组合键，启动到 recovery 模式</li>
<li>菜单栏-实用工具-终端，会打开一个终端窗口</li>
<li>输入命令 <code>csrutil disable</code> 即可禁用 SIP^[<a href="http://osxdaily.com/2015/10/05/disable-rootless-system-integrity-protection-mac-os-x/">How to Disable System Integrity Protection (rootless) in Mac OS X</a>]</li>
<li>重启 mac 进入 macOS 正常模式</li>
</ol>
<h1 id="开启-hidpi">开启 HiDPI</h1>
<p>使用命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">sudo defaults write /Library/Preferences/com.apple.windowserver.plist DisplayResolutionEnabled -bool <span class="nb">true</span>
</code></pre></div><h1 id="获取显示器的-id">获取显示器的 ID</h1>
<p>使用命令获取显示器的 Product ID 和 Vendor ID：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">ioreg -lw0 <span class="p">|</span> grep IODisplayPrefsKey
</code></pre></div><p>我的 mac 获取的显示器信息结果是：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">    <span class="p">|</span> <span class="p">|</span>   <span class="p">|</span>   <span class="p">|</span> <span class="p">|</span>   <span class="p">|</span>       <span class="s2">&#34;IODisplayPrefsKey&#34;</span> <span class="o">=</span> <span class="s2">&#34;IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/PEG0@1/IOPP/GFX0@0/ATY,Berbice@3/AMDFramebufferVIB/display0/AppleDisplay-30ae-61b5&#34;</span>
</code></pre></div><ol>
<li>外接显示器标记为 <code>AppleDisplay</code></li>
<li>内建显示器标记为 <code>AppleBacklightDisplay</code></li>
</ol>
<p>因为我只在用外接显示器，所以只得到了外接显示器的信息，上述标记在打印信息的最后出现。</p>
<p><code>AppleDisplay</code> 的后面有两个十六进制数：</p>
<ol>
<li>前一个也就是 <strong>30ae</strong> ，是 <code>DisplayVendorID</code></li>
<li>后一个也就是 <strong>61b5</strong>，是 <code>DisplayProductID</code></li>
</ol>
<h1 id="添加显示配置文件">添加显示配置文件</h1>
<p>需要访问 <a href="https://comsysto.github.io/Display-Override-PropertyList-File-Parser-and-Generator-with-HiDPI-Support-For-Scaled-Resolutions/">SCALED RESOLUTIONS FOR YOUR MACBOOKS EXTERNAL MONITOR</a> 针对自己的显示器按照自己的需求制作显示配置文件。</p>
<p>打开上面的链接会看到如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190102215620.png" alt=""></p>
<p>其中右边区域来更改配置，左边显示配置文件的内容。</p>
<ol>
<li>
<p>修改 <code>DisplayProductName</code> 后的内容用来更改显示器的名称，比如我的是 <code>T24H-10</code></p>
</li>
<li>
<p>修改 <code>DisplayProductID</code> ，这里修改为刚才获取的 <code>61b5</code>，字母需要小写</p>
</li>
<li>
<p>修改 <code>DisplayVendorID</code> ，这里修改为刚才获取的 <code>30ae</code>，字母需要小写</p>
</li>
<li>
<p>修改 <code>Scale Resolutions</code>，这里一般使用默认的配置，如果你要添加自己的分辨率，需要注意也要添加 2 倍于目标分辨率的项，观察默认的分辨率配置项都是成对的，一个目标分辨率，在其上有个二倍于目标分辨率的项</p>
</li>
<li>
<p>点击上图左边栏右下角的 <code>DisplayProductID-61b5</code> 按钮（按钮名称会根据 ProductId 命名），就会下载到一个文件 <code>DisplayProductID-61b5.plist</code>(我的这个文件下载到了 <code>~/Downloads</code> 下)</p>
</li>
<li>
<p>新建一个目录，目录名为 <code>DisplayVendorID-xxxx</code> ，其中 <code>xxxx</code> 为上面查到的 <code>30ae</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">mkdir DisplayVendorID-30ae
</code></pre></div></li>
<li>
<p>重命名下载到的配置文件(去掉 .plist 后缀)，并放置到刚创建的 <code>DisplayVendorID-30ae</code> 目录下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">mv ~/Downloads/DisplayProductID-61b5.plist DisplayVendorID-30ae/DisplayProductID-61b5
</code></pre></div></li>
<li>
<p>将 <code>DisplayVendorID-30ae</code> 目录拷贝到 <code>/System/Library/Displays/Contents/Resources/Overrides/</code> 下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">sudo mv DisplayVendorID-30ae /System/Library/Displays/Contents/Resources/Overrides/
</code></pre></div></li>
</ol>
<p><strong>注意⚠️</strong></p>
<p>上面第 8 小步可能在更新 macOS Catalina 后操作不能成功(提示 <code>read-only file system</code>)，评论区有网友提供了解决方案，需要修改系统根目录的权限，使用 <code>mount</code> 命令将目录从 read-only 变为 read-write：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo mount -uw /
killall Finder
</code></pre></div><p><strong>在 mac 重启后会恢复为原有的权限，亲测可用，感谢网友提供解决方案！</strong></p>
<h1 id="开启-sip">开启 SIP</h1>
<p>为了系统安全还是要把 SIP 开启的，更改 SIP 需要重启，正好上面的显示配置也得重启生效。</p>
<ol>
<li>重启 mac，出现苹果 logo 之前按住 <code>command</code> + <code>r</code> 组合键，启动到 recovery 模式</li>
<li>菜单栏-实用工具-终端，会打开一个终端窗口</li>
<li>输入命令 <code>csrutil enable</code> 即可开启 SIP</li>
<li>重启 mac 进入 macOS 正常模式</li>
</ol>
<h1 id="rdm-调整分辨率">RDM 调整分辨率</h1>
<ol>
<li>
<p>点击 <a href="http://avi.alkalay.net/software/RDM/">http://avi.alkalay.net/software/RDM/ </a> 下载 RDM 最新版，此时是 v2.2</p>
</li>
<li>
<p>安装 RDM</p>
</li>
<li>
<p>运行 RDM 后，菜单栏会出现其图表，点击即可修改分辨率，带小闪电的就是开启 HiDPI 的，比如我的可选项：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20190102223010.png" alt=""></p>
</li>
</ol>
<p>最终选择的是 1920 * 1080，这样字体够大，同时整个显示效果够细腻，效果很明显，如果配置为 1920*1080 的分辨率不使用 HiDPI 就会很模糊，使用了 HiDPI 就会细腻了很多，很 Retina！</p>
<h1 id="参考">参考</h1>
<p>上面的配置方法主要参考和使用了 <a href="https://comsysto.github.io/Display-Override-PropertyList-File-Parser-and-Generator-with-HiDPI-Support-For-Scaled-Resolutions/">SCALED RESOLUTIONS FOR YOUR MACBOOKS EXTERNAL MONITOR</a>。</p>]]></content>
		</item>
		
		<item>
			<title>mac 下使用 Docker 搭建 ubuntu 环境</title>
			<link>https://blog.5km.studio/2018/12/20/docker_ubuntu_learn/</link>
			<pubDate>Thu, 20 Dec 2018 14:49:08 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/12/20/docker_ubuntu_learn/</guid>
			<description>&lt;p&gt;学习网络开发过程中不想“污染”macOS，考虑到之后部署网络应用主要是与linux打交道，所以安装了 ubuntu 虚拟机以满足短期的知识学习需求。十里安装了 ubuntu 虚拟机，一般就是在 mac 中 ssh 连接 ubuntu 虚拟机在终端下进行操作学习，可见安装一个包含完整GUI的 ubuntu 有点多余，还占用很多资源！所以想到了使用 docker 来创建 ubuntu 容器用来开发学习，本文就分享一下这个过程！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20181220150817.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>学习网络开发过程中不想“污染”macOS，考虑到之后部署网络应用主要是与linux打交道，所以安装了 ubuntu 虚拟机以满足短期的知识学习需求。十里安装了 ubuntu 虚拟机，一般就是在 mac 中 ssh 连接 ubuntu 虚拟机在终端下进行操作学习，可见安装一个包含完整GUI的 ubuntu 有点多余，还占用很多资源！所以想到了使用 docker 来创建 ubuntu 容器用来开发学习，本文就分享一下这个过程！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20181220150817.png" alt=""></p>
<p>本文就不介绍 Docker 是什么了，主要描述搭建符合自己需求的 ubuntu 容器的过程。</p>
<h1 id="容器需求">容器需求</h1>
<ul>
<li>可以 ssh 连接</li>
<li>包含 vim、git等基本工具</li>
</ul>
<h1 id="安装和配置-docker">安装和配置 Docker</h1>
<h2 id="下载并安装-docker">下载并安装 Docker</h2>
<ol>
<li>
<p>访问<a href="https://hub.docker.com"> Docker 官网</a> 了解和下载 Docker，这里也可以<a href="https://download.docker.com/mac/stable/Docker.dmg">点我</a>下载最新稳定版的 Docker for mac</p>
</li>
<li>
<p>打开下载的 dmg 文件，将 Docker 拖放到 Application 文件夹中即可完成安装</p>
</li>
<li>
<p>首次运行会有提示输入密码，用来获取完整的操作权限</p>
</li>
<li>
<p>Docker 运行起来会在顶栏出现一个小鲸鱼的logo</p>
</li>
<li>
<p>安装成功后，在终端中查看 Docker 版本会得到下面类似信息：</p>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash">➜  docker --version
Docker version 18.09.0, build 4d60db4
➜  docker-compose --version
docker-compose version 1.23.2, build 1110ad01
➜  docker-machine --version
docker-machine version 0.16.0, build 702c267f
</code></pre></div></li>
</ol>
<h2 id="配置-docker">配置 Docker</h2>
<p>由于国内访问 Docker 官方默认的镜像源很慢，所以需要更换国内的镜像源进行加速，这里使用官方提供的一个镜像仓库地址：https://registry.docker-cn.com。</p>
<ol>
<li>
<p>点击顶栏小鲸鱼的 logo，找到 <code>Preferences</code>点击调出 Doker 配置窗口；</p>
</li>
<li>
<p>点击 <code>Daemon</code> 按钮，就可以看到 <code>Registry Mirrors</code> 的配置页；</p>
</li>
<li>
<p>点击 <code>+</code> 号，添加上面提供的地址即可，添加完成后，点击 <code>Apply &amp; Restart</code> ，等待一会儿 Docker 重启之后，配置即可生效，最终如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20181220153021.png" alt=""></p>
</li>
</ol>
<h1 id="定制-ubuntu-镜像">定制 ubuntu 镜像</h1>
<h2 id="获取-ubuntu-镜像">获取 ubuntu 镜像</h2>
<p>运行命令</p>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash">docker pull ubuntu
</code></pre></div><p>就会拉取官网上的最新 ubuntu 镜像，这是一个极其精简的镜像，作为我们定制 ubuntu 镜像的基础。</p>
<p>使用命令 <code>docker image ls</code> 可以查看当前安装的 Docker 镜像。</p>
<h2 id="ubuntu-容器">ubuntu 容器</h2>
<h3 id="创建-ubuntu-容器">创建 ubuntu 容器</h3>
<p>使用命令 <code>docker run -i -t --name mineos ubuntu bash</code> 可以创建并运行一个可以使用终端交互的 ubuntu 容器，命令参数解释：</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>值</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td>-i</td>
<td>无</td>
<td>可以输入进行交互</td>
</tr>
<tr>
<td>-t</td>
<td>无</td>
<td>终端交互</td>
</tr>
<tr>
<td>&ndash;name</td>
<td>mineos</td>
<td>指定容器名称为 mineos</td>
</tr>
<tr>
<td>ubuntu</td>
<td>无</td>
<td>指定使用镜像</td>
</tr>
<tr>
<td>bash</td>
<td>无</td>
<td>指定容器启动使用的应用</td>
</tr>
</tbody>
</table>
<p>上面的命令执行后，就会登陆 ubuntu 容器的 bash 中，执行命令<code>cat /etc/issue</code> 可以查看系统版本，十里的ubuntu版本是 18.04。此时按快捷键组合 <code>ctrl</code> + <code>d</code> 就会退出 ubuntu 容器，此时就会停止容器运行。</p>
<h3 id="查看已有容器">查看已有容器</h3>
<p>使用命令 <code>docker ps</code> 可以查看当前运行的容器，如果此时执行，会发现没有容器信息，因为我们已经停止了刚才创建的容器。怎么查看已经关闭的容器信息呢？使用命令 <code>docker ps -a</code>，会列出所有容器信息，包括已经关闭的。此时执行，就会看到已经关闭的 mineos 容器。</p>
<h3 id="以交互的形式启动容器">以交互的形式启动容器</h3>
<p>执行命令 <code>docker start mineos</code> 就会启动容器，但是你会发现无法像刚创建时登陆容器的 bash，先使用命令 <code>docker stop mineos</code>，此时加入 <code>-i</code> 参数启动就可以了 <code>docker start -i mineos</code>。</p>
<h2 id="ubuntu-容器的基本配置">ubuntu 容器的基本配置</h2>
<p>登陆进 ubuntu 的 bash 以后就可以当正常的 ubuntu 进行使用了。</p>
<ol>
<li>
<p>更新软件源信息：<code>apt-get update</code></p>
</li>
<li>
<p>因为这个 ubuntu 的依赖镜像太精简了，所以好多工具没有安装，先安装一下 vim: <code>apt-get install vim</code></p>
</li>
<li>
<p>可以看到安装挺慢的，之所以先安装 vim 是为了可以编辑 <code>/etc/apt/sources.list</code> 更换为国内访问更快的软件源，比如将文件中的内容替换为如下阿里云的：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="na">deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse</span>
<span class="na">deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse</span>
<span class="na">deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse</span>
<span class="na">deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse</span>
<span class="na">deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse</span>
<span class="na">deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse</span>
<span class="na">deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse</span>
<span class="na">deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse</span>
<span class="na">deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse</span>
<span class="na">deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse</span>
</code></pre></div></li>
<li>
<p>重新更新软件源信息：<code>apt-get update</code>，会发现快很多</p>
</li>
<li>
<p>飞一般的安装 git 和 python3：<code>apt-get install git python3</code></p>
</li>
</ol>
<h2 id="配置-ssh">配置 SSH</h2>
<p>这一步主要是为了mac 可以 ssh 连接 ubuntu 容器^[<a href="https://www.jianshu.com/p/426f0d8e6cbf">Docker-SSH连接docker容器</a>]。</p>
<h3 id="安装-openssh-server">安装 openssh-server</h3>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">apt-get install openssh-server
</code></pre></div><p>用于开启 ssh 服务供外部连接。</p>
<h3 id="配置-sshd">配置 sshd</h3>
<p>需要更改一下 sshd 的默认配置，编辑文件 <code>/etc/ssh/sshd_config</code> ，大概从 29 行开始主要更改三处，更改后内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">PermitRootLogin yes # 可以登录 root 用户
PubkeyAuthentication yes # 可以使用 ssh 公钥许可
AuthorizedKeysFile	.ssh/authorized_keys # 公钥信息保存到文件 .ssh/authorized_keys 中
</code></pre></div><h3 id="重启-sshd">重启 sshd</h3>
<p>因为 ubuntu 过于精简，不能使用 service 命令方便的重启 sshd，这里使用命令 <code>/etc/init.d/ssh restart</code> 进行重启^[<a href="https://blog.csdn.net/u013015629/article/details/70045809">Ubuntu下&quot;sshd:unrecognized service&quot;</a>]，重启是为了让上面的配置生效。</p>
<h3 id="添加主机的-ssh-公钥">添加主机的 ssh 公钥</h3>
<p>这里的主机指的就是 macOS，保证此时还是在 ubuntu 容器中。</p>
<ol>
<li>在 HOME 目录下创建 <code>.ssh</code> 目录：<code>mkdir ~/.ssh</code></li>
<li>新建文件 <code>~/.ssh/authorized_keys</code> ：<code>touch ~/.ssh/authorized_keys</code></li>
<li>新开一个 macOS 下的终端窗口，执行命令 <code>cat ~/.ssh/id_rsa.pub</code>，复制打印的一行公钥信息</li>
<li>回到 ubuntu 容器中，将第 3 步复制的公钥粘贴到 <code>~/.ssh/authorized_keys</code> 中保存。</li>
</ol>
<blockquote>
<p>如果使用过ssh免密码的登陆操作的话，相信您知道ssh的密钥生成方法，如果没了解过，可以参考：<a href="https://smslit.coding.me/ownwiki/linux/kali/#ssh-keys">ssh-keys</a></p>
</blockquote>
<ol start="5">
<li>此时完成了 SSH 访问支持的添加，<code>ctrl</code> + <code>d</code> 退出容器。</li>
</ol>
<h2 id="提交修改到镜像">提交修改到镜像</h2>
<p>现在已经推出到正常的 mac 终端窗口中了，容器的修改不会影响到源镜像，上面的操作我们已经完成了 Ubuntu 的基本配置，并且添加了 SSH 支持，这一步是产生新的镜像版本。</p>
<ol>
<li>
<p>查看刚刚操作的容器信息，执行命令 <code>docker ps -a</code> ，可以看到 mineos 的状态已经是退出了，主要关注 mineos 的 <code>CONTAINER ID</code> ，复制这个 ID 号，比如为 <code>e5d8c1030724</code></p>
</li>
<li>
<p>执行下面的命令提交产生 ubuntu 新版本的镜像：</p>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash">docker commit -m <span class="s1">&#39;add ssh&#39;</span> -a <span class="s1">&#39;5km&#39;</span> e5d8c1030724 ubuntu-ssh
</code></pre></div><ul>
<li>-m，指定提交信息</li>
<li>-a，指定提交者</li>
<li>你需要把 e5d8c1030724 替换为您的容器的 <code>CONTAINER ID</code></li>
<li>ubuntu-ssh 是新镜像的名称，可以随意指定</li>
</ul>
</li>
<li>
<p>使用命令 <code>docker image ls</code> 可以查看当前安装的镜像，上述操作正常的话就会看到 <code>ubuntu-ssh</code> 的镜像信息</p>
</li>
<li>
<p>此时之前创建的容器就没用了，可以通过命令 <code>docker rm mineos</code> 进行删除</p>
</li>
</ol>
<h1 id="最终的-ubuntu-容器">最终的 ubuntu 容器</h1>
<p>有了具有 SSH 支持的 ubuntu 镜像，我们就可以创建新的 ubuntu 容器，通过以下命令进行创建：</p>
<div class="highlight"><pre class="chroma"><code class="language-Bash" data-lang="Bash">docker run -d -p 26122:22 --name learn ubuntu-ssh /usr/sbin/sshd -D
</code></pre></div><table>
<thead>
<tr>
<th>参数</th>
<th>值</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td>-d</td>
<td>无</td>
<td>后台运行</td>
</tr>
<tr>
<td>-p</td>
<td>26122:22</td>
<td>绑定主机的 26122 端口到ubuntu容器的 22 端口(ssh服务的默认端口为 22)</td>
</tr>
<tr>
<td>&ndash;name</td>
<td>learn</td>
<td>指定容器名称为 learn</td>
</tr>
<tr>
<td>ubuntu-ssh</td>
<td>无</td>
<td>使用镜像 ubuntu-ssh 创建容器</td>
</tr>
<tr>
<td>/usr/sbin/sshd -D</td>
<td>无</td>
<td>指定容器启动使用的应用及参数</td>
</tr>
</tbody>
</table>
<p>在 macOS 的终端中执行命令 <code>ssh -p 26122 root@localhost</code> 即可连接已经启动的 ubuntu 容器 <code>learn</code></p>
<p>为了更方便的连接，可以为容器创建 ssh 连接的主机短名，往 macOS 的 <code>~/.ssh/config</code> 中添加以下内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="na">Host learn</span>
    <span class="na">HostName localhost</span>
    <span class="na">User     root</span>
    <span class="na">Port     26122</span>
</code></pre></div><p>此时就可以通过命令 <code>ssh learn</code> 连接 ubuntu 容器 learn 了。</p>]]></content>
		</item>
		
		<item>
			<title>ubuntu 下 mysql 拒绝访问问题</title>
			<link>https://blog.5km.studio/2018/12/19/ubuntu_mysql_permission/</link>
			<pubDate>Wed, 19 Dec 2018 20:45:52 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/12/19/ubuntu_mysql_permission/</guid>
			<description>&lt;p&gt;今天在 ubuntu 中安装 mysql 后出现 &lt;code&gt;Access denied for user &#39;root&#39;@&#39;localhost&#39;&lt;/code&gt; 问题，整个安装过程中也没有提示要设置用户和密码呀，咋就拒绝访问了呢，本文就解决一下这个问题！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天在 ubuntu 中安装 mysql 后出现 <code>Access denied for user 'root'@'localhost'</code> 问题，整个安装过程中也没有提示要设置用户和密码呀，咋就拒绝访问了呢，本文就解决一下这个问题！</p>
<h1 id="问题分析">问题分析</h1>
<p>mysql 具有用户密码访问机制，其实 mysql 安装后默认有用户 <code>root</code> ，那拒绝访问，那就是没有输入正确的密码，所以我们能想到的一种解决方案就是修改密码。</p>
<h1 id="问题解决">问题解决</h1>
<h2 id="越过访问限制">越过访问限制</h2>
<p>连接上 mysql 后我们才能修改密码，所以有没有一种可以临时越过访问限制的方法呢？当然有，要不接下来我们还写啥！</p>
<ol>
<li>
<p>管理员权限编辑文件 <code>/etc/mysql/mysql.conf.d/mysqld.cnf</code>;</p>
</li>
<li>
<p>找到其中的 <code>[mysqld]</code> 配置段，大概从 27 行开始，在 <code>skip-external-locking</code> (大概在39行) 下面加入一行内容 <code>skip-grant-tables</code>，最终如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="na">...</span>
<span class="k">[mysqld]</span>
<span class="c1">#</span>
<span class="c1"># * Basic Settings</span>
<span class="c1">#</span>
<span class="na">user</span>		<span class="o">=</span> <span class="s">mysql</span>
<span class="na">pid-file</span>	<span class="o">=</span> <span class="s">/var/run/mysqld/mysqld.pid</span>
<span class="na">socket</span>		<span class="o">=</span> <span class="s">/var/run/mysqld/mysqld.sock</span>
<span class="na">port</span>		<span class="o">=</span> <span class="s">3306</span>
<span class="na">basedir</span>		<span class="o">=</span> <span class="s">/usr</span>
<span class="na">datadir</span>		<span class="o">=</span> <span class="s">/var/lib/mysql</span>
<span class="na">tmpdir</span>		<span class="o">=</span> <span class="s">/tmp</span>
<span class="na">lc-messages-dir</span>	<span class="o">=</span> <span class="s">/usr/share/mysql</span>
<span class="na">skip-external-locking</span>
<span class="na">skip-grant-tables</span>
<span class="na">...</span>
</code></pre></div></li>
<li>
<p>重启 mysql 服务，一般可通过以下命名完成</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo service mysql restart
</code></pre></div></li>
<li>
<p>此时可以通过 <code>mysql</code> 命令就可以连接数据库了</p>
</li>
</ol>
<h2 id="修改-root-用户密码">修改 root 用户密码</h2>
<p>在小节<a href="#越过访问限制">越过访问限制</a>中已经可以越过访问限制连接数据库了，下面连接数据库，由于操作命令比较多，所以先准备一个 <code>sql</code> 文件，最后执行文件中 sql 命令。</p>
<ol>
<li>
<p>准备用于修改 root 密码的 <code>setpw.sql</code> 文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-sql" data-lang="sql"><span class="n">use</span><span class="w"> </span><span class="n">mysql</span><span class="p">;</span><span class="w">
</span><span class="w"></span><span class="k">update</span><span class="w"> </span><span class="n">mysql</span><span class="p">.</span><span class="k">user</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="n">authentication_string</span><span class="o">=</span><span class="n">password</span><span class="p">(</span><span class="s1">&#39;123456&#39;</span><span class="p">)</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="k">user</span><span class="o">=</span><span class="s1">&#39;root&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="k">Host</span><span class="w"> </span><span class="o">=</span><span class="s1">&#39;localhost&#39;</span><span class="p">;</span><span class="w">
</span><span class="w"></span><span class="k">update</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="n">plugin</span><span class="o">=</span><span class="s2">&#34;mysql_native_password&#34;</span><span class="p">;</span><span class="w">
</span><span class="w"></span><span class="n">flush</span><span class="w"> </span><span class="k">privileges</span><span class="p">;</span><span class="w">
</span></code></pre></div><blockquote>
<p>上面代码第二行中的 <code>123456</code> 就是要修改的密码，密码修改为您想使用的密码即可</p>
</blockquote>
</li>
<li>
<p>执行命令 <code>mysql</code> 连接数据库，然后执行 mysql 命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-sql" data-lang="sql"><span class="k">source</span><span class="w"> </span><span class="n">setpw</span><span class="p">.</span><span class="k">sql</span><span class="w">
</span></code></pre></div></li>
<li>
<p>（CTRL+D 或者 执行命令 quit;）断开 mysql 连接；</p>
</li>
</ol>
<h2 id="恢复访问限制">恢复访问限制</h2>
<ol>
<li>
<p>编辑文件 <code>/etc/mysql/mysql.conf.d/mysqld.cnf</code>，删除上面小节中加上的 <code>skip-grant-tables</code>;</p>
</li>
<li>
<p>重启 mysql 服务</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo service mysql restart
</code></pre></div><blockquote>
<p>执行命令 <code>mysql -uroot -p</code> 连接数据库，输入自己的密码即可连接数据库！</p>
</blockquote>
</li>
</ol>]]></content>
		</item>
		
		<item>
			<title>最简单的人工智能(皮一下)</title>
			<link>https://blog.5km.studio/2018/12/17/simple_ai_stupid/</link>
			<pubDate>Mon, 17 Dec 2018 16:35:58 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/12/17/simple_ai_stupid/</guid>
			<description>&lt;p&gt;有没有想过实现一个人工智能项目，现在十里就教你用 python3 实现一个最简单的人工智障项目，哦，不对，是人工智能项目，智能(智障)谈话机器人，据说这个项目估值一个亿^[&lt;a href=&#34;https://github.com/ruanyf/weekly/issues/158&#34;&gt;图片：估值一个亿的代码&lt;/a&gt;]！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20181217171933.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>有没有想过实现一个人工智能项目，现在十里就教你用 python3 实现一个最简单的人工智障项目，哦，不对，是人工智能项目，智能(智障)谈话机器人，据说这个项目估值一个亿^[<a href="https://github.com/ruanyf/weekly/issues/158">图片：估值一个亿的代码</a>]！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20181217171933.png" alt=""></p>
<p>新建文件 <code>chatbot.py</code> ，内容如下:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
    <span class="n">chat_str</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
    <span class="n">chat_str</span> <span class="o">=</span> <span class="n">chat_str</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;吗&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
    <span class="n">chat_str</span> <span class="o">=</span> <span class="n">chat_str</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;?&#34;</span><span class="p">,</span> <span class="s2">&#34;!&#34;</span><span class="p">)</span>
    <span class="n">chat_str</span> <span class="o">=</span> <span class="n">chat_str</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;? &#34;</span><span class="p">,</span> <span class="s2">&#34;! &#34;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;小爱:&#34;</span><span class="p">,</span> <span class="n">chat_str</span><span class="p">)</span>
</code></pre></div><p>大功告成！运行文件进行玩耍:</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ python3 chatbot.py
您好
小爱: 您好
在吗
小爱: 在
会说中文吗
小爱: 会说中文
真的吗?
小爱: 真的!
</code></pre></div><p>怎么样，有没有一种被侮辱的感觉？不管怎样，十里就将这个估值一亿的项目交给你了！好好发挥！</p>]]></content>
		</item>
		
		<item>
			<title>简陋的不能再简陋的 HTTP1.1 Server</title>
			<link>https://blog.5km.studio/2018/11/27/simple_server/</link>
			<pubDate>Tue, 27 Nov 2018 10:48:29 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/11/27/simple_server/</guid>
			<description>&lt;p&gt;今天实现了一个超级简陋的 HTTP/1.1 服务，使用 python3 实现，主要为了练习 TCP 通信和理解浏览器与服务器之间的交互过程。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天实现了一个超级简陋的 HTTP/1.1 服务，使用 python3 实现，主要为了练习 TCP 通信和理解浏览器与服务器之间的交互过程。</p>
<h1 id="知识基础">知识基础</h1>
<ul>
<li>python3 的 tcp 网络通信: socket 的使用；</li>
<li>客户端与服务器之间的通信过程；</li>
<li>TCP 通信中的三次握手与四次挥手；</li>
<li>网络的长连接与短连接；</li>
<li>正则表达式；</li>
<li>python 的基本知识应用：文件读、Shebang运行、命令参数解析</li>
</ul>
<h1 id="实现功能">实现功能</h1>
<p>为指定的路径提供简陋的 HTTP/1.1 服务，可以通过浏览器访问，符合 HTTP/1.1 的长连接支持，并且实现的服务是单进程、单线程、非阻塞的。网络服务为了提高响应速度，多以多进程、多线程和协程方式实现服务，一般情况下实现效率从高到低是：协程 &gt; 多线程 &gt; 多进程，主要还是因为资源开销的问题，实际应用过程中大多数情况下主要使用 epoll 技术实现高并发，简单来说是一种高级的单进程、单线程、非阻塞技术，依赖于：内存映射和通知机制。这里实现的非阻塞形式相当简单，与 epoll 相比差很大一截。</p>
<p>这里服务默认开启在 8080 端口，也可以指定端口。</p>
<h1 id="实现代码">实现代码</h1>
<p>因为只是练习，以后应该也不会维护加功能，废话不多说，粗暴地展示代码了：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/bin/env python3</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">import</span> <span class="nn">socket</span>
<span class="kn">import</span> <span class="nn">argparse</span>


<span class="k">def</span> <span class="nf">get_args</span><span class="p">():</span>
    <span class="s2">&#34;&#34;&#34;get_args&#34;&#34;&#34;</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;开启指定网站服务&#34;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;-p&#34;</span><span class="p">,</span> <span class="s2">&#34;--port&#34;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">8080</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&#34;指定网服务端口&#34;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;site&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&#34;指定网站路径&#34;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>


<span class="k">def</span> <span class="nf">response_to</span><span class="p">(</span><span class="n">client_socket</span><span class="p">:</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">site_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
    <span class="c1"># 处理请求</span>
    <span class="n">req_str</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
    <span class="n">req_lines</span> <span class="o">=</span> <span class="n">req_str</span><span class="o">.</span><span class="n">splitlines</span><span class="p">()</span>

    <span class="n">ret</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;[^/]+([^(?| )]*)&#34;</span><span class="p">,</span> <span class="n">req_lines</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
    <span class="n">file_name</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
    <span class="k">if</span> <span class="n">ret</span><span class="p">:</span>
        <span class="n">file_name</span> <span class="o">=</span> <span class="n">ret</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">file_name</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">):</span>
            <span class="n">file_name</span> <span class="o">+=</span> <span class="s2">&#34;index.html&#34;</span>
        <span class="n">file_name</span> <span class="o">=</span> <span class="n">site_path</span> <span class="o">+</span> <span class="n">file_name</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;+&#34;</span> <span class="o">*</span> <span class="mi">50</span> <span class="o">+</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="n">file_name</span> <span class="o">+</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;+&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>

    <span class="k">try</span><span class="p">:</span>
        <span class="n">f_req</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_name</span><span class="p">,</span> <span class="s2">&#34;rb&#34;</span><span class="p">)</span>
    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
        <span class="c1"># 生成响应</span>
        <span class="c1"># 响应内容</span>
        <span class="n">response_body</span> <span class="o">=</span> <span class="s2">&#34;file is not available!&#34;</span>
        <span class="c1"># 响应头</span>
        <span class="n">response_header</span> <span class="o">=</span> <span class="s2">&#34;HTTP/1.1 404 NOT FOUND</span><span class="se">\r\n</span><span class="s2">&#34;</span>
        <span class="n">response_header</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;Content-Length:</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">response_body</span><span class="p">)</span><span class="si">}</span><span class="se">\r\n</span><span class="s2">&#34;</span>
        <span class="n">response_header</span> <span class="o">+=</span> <span class="s2">&#34;</span><span class="se">\r\n</span><span class="s2">&#34;</span>
        <span class="n">response</span> <span class="o">=</span> <span class="p">(</span><span class="n">response_header</span> <span class="o">+</span> <span class="n">response_body</span><span class="p">)</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
        <span class="c1"># 发送响应</span>
        <span class="n">client_socket</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Not found!&#34;</span><span class="p">)</span>

    <span class="k">else</span><span class="p">:</span>
        <span class="n">html_content</span> <span class="o">=</span> <span class="n">f_req</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
        <span class="n">f_req</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
        <span class="c1"># 生成响应</span>
        <span class="c1"># 响应内容</span>
        <span class="n">response_body</span> <span class="o">=</span> <span class="n">html_content</span>
        <span class="c1"># 响应头</span>
        <span class="n">response_header</span> <span class="o">=</span> <span class="s2">&#34;HTTP/1.1 200 OK</span><span class="se">\r\n</span><span class="s2">&#34;</span>
        <span class="n">response_header</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;Content-Length:</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">response_body</span><span class="p">)</span><span class="si">}</span><span class="se">\r\n</span><span class="s2">&#34;</span>
        <span class="n">response_header</span> <span class="o">+=</span> <span class="s2">&#34;</span><span class="se">\r\n</span><span class="s2">&#34;</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">response_header</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="o">+</span> <span class="n">response_body</span>
        <span class="c1"># 发送响应</span>
        <span class="n">client_socket</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>

    <span class="nb">print</span><span class="p">(</span><span class="n">req_str</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="c1"># 获取命令参数</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">get_args</span><span class="p">()</span>
    <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">site</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">site</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">):</span>
            <span class="n">args</span><span class="o">.</span><span class="n">site</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">site</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
    <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">port</span> <span class="o">&gt;=</span> <span class="mi">65535</span><span class="p">:</span>
        <span class="n">args</span><span class="o">.</span><span class="n">port</span> <span class="o">=</span> <span class="mi">8080</span>

    <span class="c1"># 创建套接字</span>
    <span class="n">tcp_socket</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>

    <span class="c1"># 保证服务器先关闭套接字时，重启程序可以立马重复使用上一次的配置端口</span>
    <span class="n">tcp_socket</span><span class="o">.</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SO_REUSEADDR</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">tcp_socket</span><span class="o">.</span><span class="n">setblocking</span><span class="p">(</span><span class="kc">False</span><span class="p">)</span>

    <span class="c1"># 绑定套接字</span>
    <span class="n">tcp_socket</span><span class="o">.</span><span class="n">bind</span><span class="p">((</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">port</span><span class="p">))</span>

    <span class="c1"># 监听</span>
    <span class="n">tcp_socket</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="mi">128</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">site</span><span class="si">}</span><span class="s2"> 的 http 服务已开启&#34;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;访问地址: http://127.0.0.1:</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">port</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>

    <span class="n">client_socket_list</span> <span class="o">=</span> <span class="nb">list</span><span class="p">()</span>

    <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="c1"># 等待</span>
            <span class="n">client_socket</span><span class="p">,</span> <span class="n">client_addr</span> <span class="o">=</span> <span class="n">tcp_socket</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="k">pass</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">client_socket</span><span class="o">.</span><span class="n">setblocking</span><span class="p">(</span><span class="kc">False</span><span class="p">)</span>
            <span class="n">client_socket_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">client_socket</span><span class="p">)</span>

        <span class="k">for</span> <span class="n">client_socket</span> <span class="ow">in</span> <span class="n">client_socket_list</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="n">request</span> <span class="o">=</span> <span class="n">client_socket</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
                <span class="k">pass</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="k">if</span> <span class="n">request</span><span class="p">:</span>
                    <span class="n">response_to</span><span class="p">(</span><span class="n">client_socket</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">site</span><span class="p">)</span>
                <span class="k">else</span><span class="p">:</span>
                    <span class="n">client_socket</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
                    <span class="n">client_socket_list</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">client_socket</span><span class="p">)</span>

                <span class="c1"># 关闭套接字</span>
    <span class="n">tcp_socket</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>


<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
    <span class="n">main</span><span class="p">()</span>
</code></pre></div><p>代码保存到文件 <code>simple_server</code> 中，为源文件添加运行权限：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ chmod +x simple_server
</code></pre></div><h1 id="使用方法">使用方法</h1>
<p>命令行执行命令获取帮助信息</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ ./simple_server -h
usage: simple_server <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-p PORT<span class="o">]</span> site

开启指定网站服务

positional arguments:
  site                  指定网站路径

optional arguments:
  -h, --help            show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -p PORT, --port PORT  指定网服务端口
</code></pre></div><p>开启服务，我这里使用的测试对象是本人静态博客的路径，如此简陋，居然能用!</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ ./simple_server /Users/5km/smslit/public/ -p <span class="m">7788</span>
/Users/5km/smslit/public 的 http 服务已开启
访问地址: http://127.0.0.1:7788
</code></pre></div><p>这里使用了端口 <strong>7788</strong>，访问一切👌，什么？你不信，有图有真相的：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/20181127111056.png" alt=""></p>
<h1 id="分析">分析</h1>
<p><a href="#实现功能">实现功能</a>小节中提到了，本文实现的非阻塞方式相当低级，是对套接字列表轮询的机制，这种方式有两个大问题：</p>
<ol>
<li>套接字列表中元素增多，对于一个操作系统来说，这个列表是在用户层的，每次轮询都需要用户层和系统层之间数据的拷贝，才能让系统正常的操作硬件；当套接字数量到足够大时，可能会因为拷贝而造成时间的浪费；</li>
<li>当列表中套接字数量足够多，如果其中只有极少部分的套接字需要数据处理，那全部轮询就会造成不必要的时间浪费；</li>
</ol>
<p>而上文提到的 epoll 恰恰能解决这两个问题，所以十里用 epoll 的方式也实现了这个服务，只是为了理解原理，所以实现同样很简单，可参考：</p>
<p><a href="https://github.com/smslit/tools-with-script/blob/master/simple_server/simple_server">smslit/tools_of_python/simpe_server</a></p>]]></content>
		</item>
		
		<item>
			<title>图床的选择从七牛云到腾讯云</title>
			<link>https://blog.5km.studio/2018/11/17/qiniu-to-tencent-bucket/</link>
			<pubDate>Sat, 17 Nov 2018 13:56:21 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/11/17/qiniu-to-tencent-bucket/</guid>
			<description>&lt;p&gt;昨天收到七牛云的邮件提醒说要回收我的 CDN 测试域名，博客一直使用七牛云的 bucket 作为图床，也就是说收了域名后，图片以及其它文件无法访问，最糟糕的是七牛云后台管理也无法访问文件，这才有了本文的内容，我要将七牛云对象存储转移到腾讯云的对象存储。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>昨天收到七牛云的邮件提醒说要回收我的 CDN 测试域名，博客一直使用七牛云的 bucket 作为图床，也就是说收了域名后，图片以及其它文件无法访问，最糟糕的是七牛云后台管理也无法访问文件，这才有了本文的内容，我要将七牛云对象存储转移到腾讯云的对象存储。</p>
<h1 id="恢复七牛云文件">恢复七牛云文件</h1>
<p>首先得恢复七牛云中文件的访问。七牛云提供的 <a href="https://developer.qiniu.com/kodo/tools/1302/qshell">qshell</a> 命令行工具，可以管理对象存储空间，这里的思路就是新建一个对象存储空间，将原来用作图床的对象存储空间中的文件拷贝到新的对象存储空间中。</p>
<ol>
<li>
<p>下载 qshell</p>
<ul>
<li>
<p>下载到最新的 qshell 压缩包是 <code>qshell-v2.3.0.zip</code>，解压得到：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">qshell-v2.3.0
├── qshell_darwin_x64
├── qshell_linux_x64
├── qshell_linux_x86
├── qshell_windows_x64.exe
└── qshell_windows_x86.exe

<span class="m">0</span> directories, <span class="m">5</span> files
</code></pre></div></li>
<li>
<p>进入到目录 qshell-v2.3.0 中，为了方便建立软连接 <code>qshell</code>，这里使用的 macOS，所以软连接 <code>qshell_darwin_x64</code></p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ <span class="nb">cd</span> qshell-v2.3.0
$ ln -s qshell_darwin_x64 qshell
</code></pre></div></li>
</ul>
</li>
<li>
<p>登录账户</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">./qshell account &lt;AK&gt; &lt;SK&gt; &lt;username&gt;
</code></pre></div><ul>
<li><code>&lt;AK&gt;</code> 是 app key （个人中心-&gt;密钥管理中查看）</li>
<li><code>&lt;SK&gt;</code> 是 secret key （个人中心-&gt;密钥管理中查看）</li>
<li><code>&lt;username&gt;</code> 是你的七牛账户名</li>
</ul>
</li>
<li>
<p>获取文件详细列表：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">./qshell listbucket &lt;原存储空间&gt; &gt; filelist.txt
</code></pre></div><ul>
<li><code>&lt;原存储空间&gt;</code> 是已经被回收 CDN 域名的对象空间的名称</li>
</ul>
</li>
<li>
<p>切分出文件名称的列表：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">cat filelist.txt <span class="p">|</span> awk -F <span class="s1">&#39;\t&#39;</span> <span class="s1">&#39;{print $1}&#39;</span> &gt; list.txt
</code></pre></div></li>
<li>
<p>去<a href="https://portal.qiniu.com/create">七牛云管理控制台</a>，新建一个新的对象存储空间，这里命名为 <code>backup</code>，新建完成后会看到空间分配了新的 CDN 域名，但是有效期只有一个月，这是要放弃七牛云的主要原因</p>
</li>
<li>
<p>将原空间的文件拷贝到新空间 backup 中：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">./qshell batchcopy &lt;原存储空间&gt; backup -i list.txt
</code></pre></div><p>完成后，访问<a href="https://portal.qiniu.com/create">七牛云管理控制台</a>中 backup 存储空间的内容管理就可以看到原来的文件了，都可以正常访问了。</p>
</li>
</ol>
<h1 id="下载文件到本地">下载文件到本地</h1>
<p>qshell提供了qdownload可以批量下载文件，不过官网给出的api文档特别标注了，这个接口默认是要收费的:配置【该功能默认需要计费，如果希望享受10G的免费流量，请自行设置cdn_domain参数，如不设置，需支付源站流量费用，无法减免！！！】^[<a href="https://sjq597.github.io/2018/10/13/%E4%B8%83%E7%89%9B%E6%B5%8B%E8%AF%95%E5%9F%9F%E5%90%8D%E5%9B%9E%E6%94%B6%E8%BF%81%E7%A7%BB%E5%8D%9A%E5%AE%A2%E5%9B%BE%E5%BA%8A%E5%88%B0%E8%85%BE%E8%AE%AF%E4%BA%91/">七牛测试域名回收迁移博客图床到腾讯云-批量下载到本地</a>]。</p>
<p>呃。。。文件都是有外链的，所以十里用 python 写了一个下载七牛云对象存储空间文件的工具 <a href="https://github.com/smslit/tools-with-script/tree/master/qdownload">qdownload</a>。</p>
<p>打开<a href="https://portal.qiniu.com/create">七牛云管理控制台</a>，找到新的存储空间 backup 查看 CDN 域名，使用 qdownload 下载文件到本地:</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">./qdownload &lt;backup空间的CDN域名&gt; list.txt -o pichome
</code></pre></div><ul>
<li><code>&lt;backup空间的CDN域名&gt;</code>：新的存储空间 backup 查看 CDN 域名</li>
<li><code>list.txt</code>：<a href="#恢复七牛云文件">恢复七牛云文件</a> 中第 4 步得到的文件列表文件</li>
</ul>
<p>执行完命令，就会发现文件都下载到了 <code>~/Downloads/pichome</code> 下了。</p>
<h1 id="准备腾讯云cos">准备腾讯云COS</h1>
<p>腾讯云COS^[在中国大陆地区，使用腾讯云 COS 标准存储的用户，每月可享受一定量的免费存储容量、免费流量、和免费请求。——<a href="https://cloud.tencent.com/document/product/436/6240">免费额度</a>]提供免费空间和每月有限的访问次数，但对于本博客来说足以。</p>
<ol>
<li>
<p>访问<a href="https://console.cloud.tencent.com/cos5">腾讯云 COS 控制台</a>，新建存储桶，只需注意权限选择 <strong>私有写公有读</strong> 即可。</p>
</li>
<li>
<p>访问<a href="https://console.cloud.tencent.com/cam/capi">云API密钥</a>，新建密钥，新建成功后记下 <strong>SecretId</strong> 和 <strong>SecretKey</strong></p>
</li>
<li>
<p>下载 <a href="https://cloud.tencent.com/document/product/436/11366">COSrowser</a> 工具，下载后打开工具，输入第二步的  <strong>SecretId</strong> 和 <strong>SecretKey</strong>，就可以管理存储桶里的文件了：</p>
<p>![](<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/">https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/</a>屏幕快照 2018-11-17 下午3.03.36.png)</p>
</li>
</ol>
<h1 id="上床文件到cos">上床文件到COS</h1>
<p>进入 <code>~/Downloads/pichome</code> 目录，也就是<a href="#下载文件到本地">下载文件到本地</a>中下载文件的目标目录，选中所有文件拖拽到打开的 <strong>COSrowser</strong> 窗口中，软件就会自动上传文件。</p>
<p>等上传完成后，随便找一个文件右键选中复制链接，就会得到文件一个完整的外部访问链接，分析链接记下连接中的域名。</p>
<h1 id="更新博客源文件中资源链接">更新博客源文件中资源链接</h1>
<p>因为原来使用的是七牛云，也是固定使用一个域名的，所有文件从七牛云转存到腾讯COS，文件的相对路径是没变的，所以只需要替换原来的域名为现在腾讯COS分配的新域名即可，有很多方法，比如可以使用 <a href="https://en.wikipedia.org/wiki/Sed">sed</a> 命令，我这里使用 vscode 的高级查找替换域名，在打开的文件夹下搜索所有文件中有原域名的位置，然后全部替换为新域名即可：</p>
<ol>
<li>使用 vscode 打开博客文章的源文件目录，我使用的是hugo，所以打开存 md 文件的 <code>content</code> 目录</li>
<li>按 <code>command</code> + <code>shift</code> + <code>h</code> 打开高级替换的侧栏，上编辑框输入待查找的原域名，就会得到搜索结果</li>
<li>检查搜索结果没什么问题后，下边框输入新域名，点击全部替换按钮</li>
<li>启动博客渲染服务，打开本地的博客，所有的图片和文件访问正常</li>
</ol>
<p>yeah， Done！</p>]]></content>
		</item>
		
		<item>
			<title>使用 matplotlib 的 animation 让数据动起来</title>
			<link>https://blog.5km.studio/2018/11/12/matplotlib-animation/</link>
			<pubDate>Mon, 12 Nov 2018 01:22:15 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/11/12/matplotlib-animation/</guid>
			<description>&lt;p&gt;最近在研究排序算法，为了更好的展示排序过程，决定使用动画演示，专门研究了 python 图表库 matplotlib 的 animation。本文简单讲一下如何使用 matplotlib 让你手中的数据动起来。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181112154195822047625.gif&#34; alt=&#34;20181112154195822047625.gif&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>最近在研究排序算法，为了更好的展示排序过程，决定使用动画演示，专门研究了 python 图表库 matplotlib 的 animation。本文简单讲一下如何使用 matplotlib 让你手中的数据动起来。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181112154195822047625.gif" alt="20181112154195822047625.gif"></p>
<h1 id="animated-line-plot">Animated line plot</h1>
<p>废话不多说，以 matplotlib 官方给的 🌰 （<a href="https://matplotlib.org/gallery/animation/simple_anim.html">Animated line plot</a>）说明基本的使用。</p>
<h2 id="示例代码">示例代码</h2>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="nn">plt</span>
<span class="kn">import</span> <span class="nn">matplotlib.animation</span> <span class="k">as</span> <span class="nn">animation</span>

<span class="n">fig</span><span class="p">,</span> <span class="n">ax</span> <span class="o">=</span> <span class="n">plt</span><span class="o">.</span><span class="n">subplots</span><span class="p">()</span>

<span class="n">x</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">arange</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="o">*</span><span class="n">np</span><span class="o">.</span><span class="n">pi</span><span class="p">,</span> <span class="mf">0.01</span><span class="p">)</span>
<span class="n">line</span><span class="p">,</span> <span class="o">=</span> <span class="n">ax</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">np</span><span class="o">.</span><span class="n">sin</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>


<span class="k">def</span> <span class="nf">init</span><span class="p">():</span>  <span class="c1"># only required for blitting to give a clean slate.</span>
    <span class="n">line</span><span class="o">.</span><span class="n">set_ydata</span><span class="p">([</span><span class="n">np</span><span class="o">.</span><span class="n">nan</span><span class="p">]</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">line</span><span class="p">,</span>


<span class="k">def</span> <span class="nf">animate</span><span class="p">(</span><span class="n">i</span><span class="p">):</span>
    <span class="n">line</span><span class="o">.</span><span class="n">set_ydata</span><span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">sin</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="n">i</span> <span class="o">/</span> <span class="mi">100</span><span class="p">))</span>  <span class="c1"># update the data.</span>
    <span class="k">return</span> <span class="n">line</span><span class="p">,</span>


<span class="n">ani</span> <span class="o">=</span> <span class="n">animation</span><span class="o">.</span><span class="n">FuncAnimation</span><span class="p">(</span>
    <span class="n">fig</span><span class="p">,</span> <span class="n">animate</span><span class="p">,</span> <span class="n">init_func</span><span class="o">=</span><span class="n">init</span><span class="p">,</span> <span class="n">interval</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">blit</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">save_count</span><span class="o">=</span><span class="mi">50</span><span class="p">)</span>

<span class="c1"># To save the animation, use e.g.</span>
<span class="c1">#</span>
<span class="c1"># ani.save(&#34;movie.mp4&#34;)</span>
<span class="c1">#</span>
<span class="c1"># or</span>
<span class="c1">#</span>
<span class="c1"># from matplotlib.animation import FFMpegWriter</span>
<span class="c1"># writer = FFMpegWriter(fps=15, metadata=dict(artist=&#39;Me&#39;), bitrate=1800)</span>
<span class="c1"># ani.save(&#34;movie.mp4&#34;, writer=writer)</span>

<span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div><h2 id="运行结果">运行结果</h2>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/animation/animation.mp4" controls="controls" width="60%">animated line plot</video></p>
<p>上面的视频是由上面的代码生成，不过需要将以下代码（即30～32行）取消注释：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">matplotlib.animation</span> <span class="kn">import</span> <span class="n">FFMpegWriter</span>
<span class="n">writer</span> <span class="o">=</span> <span class="n">FFMpegWriter</span><span class="p">(</span><span class="n">fps</span><span class="o">=</span><span class="mi">15</span><span class="p">,</span> <span class="n">metadata</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span><span class="n">artist</span><span class="o">=</span><span class="s1">&#39;Me&#39;</span><span class="p">),</span> <span class="n">bitrate</span><span class="o">=</span><span class="mi">1800</span><span class="p">)</span>
<span class="n">ani</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="s2">&#34;movie.mp4&#34;</span><span class="p">,</span> <span class="n">writer</span><span class="o">=</span><span class="n">writer</span><span class="p">)</span>
</code></pre></div><p>如果系统没有安装 <a href="https://www.ffmpeg.org">ffmpeg</a> 运行代码可能会提示以下错误：</p>
<blockquote>
<p>&hellip;
FileNotFoundError: [Errno 2] No such file or directory: &lsquo;ffmpeg&rsquo;: &lsquo;ffmpeg&rsquo;</p>
</blockquote>
<p>所以需要安装 ffmpeg，十里使用的是 macOS，所以通过以下命令进行安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ brew install ffmpeg
</code></pre></div><p>其它系统可以下载 <a href="https://www.ffmpeg.org/download.html">ffmpeg 官网</a> 提供的安装包进行安装。</p>
<h2 id="代码分析">代码分析</h2>
<p>理解代码的关键三点：</p>
<ol>
<li>获取图像 <code>fig</code> 和 坐标轴对象 <code>ax</code>：<code>fig, ax = plt.subplots()</code></li>
<li>定义初始化函数 <code>init</code> 和数据更新函数 <code>animate</code>：
<ul>
<li>初始化函数，用于动画的初始化操作，这个可以不用为可选定义</li>
<li>数据更新函数，用于告知 animation 更新操作，这个函数的参数默认传入的是帧序号，但是如果 <code>animation.FuncAnimation</code> 方法指定可选参数 <code>frames</code> 为一个列表的话，更新函数调用时传入的值会是相应索引下的元素，此时动画终点就是取完列表元素，如果不指定 <code>frames</code> 则会一直传入帧序号保持下去。</li>
</ul>
</li>
<li>调用 <code>animation.FuncAnimation</code> 方法：
<ul>
<li>第一个参数传入图像对象，这里是 <code>fig</code></li>
<li>第二个参数默认为更新函数，这里是 <code>animate</code></li>
<li>可选参数 <code>init_func</code>，指定初始化函数</li>
<li>可选参数 <code>interval</code>，指定帧间隔时间单位 <strong>ms</strong></li>
<li>可选参数 <code>frames</code>，指定更新序列，如果是个整数，则指定的是总的帧数</li>
<li>可选参数 <code>save_count</code>，指定保存动画（gif或mp4）的帧数</li>
<li>可选参数 <code>repeat</code>，指定是否循环动画</li>
<li>可选参数 <code>blit</code>，指定是否优化绘图</li>
</ul>
</li>
</ol>
<h2 id="动画保存">动画保存</h2>
<p><a href="#运行结果">运行结果</a> 中说明了保存 mp4 的方法，这里说一下如何保存 gif。</p>
<p><code>ani</code> 的 <code>save</code> 方法可以指定 <code>writer</code> ，这里要保存 <code>gif</code> 默认选择的 <code>writer</code> 为 <code>pillow</code> ，所以需要安装 <code>pillow</code> 库：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ pip3 install pillow
</code></pre></div><p>按照如下调用方法即可保存动画为 <code>gif</code>，这里保存动画的帧数是受 <code>animation.FuncAnimation</code> 的 <code>frames</code> 和 <code>save_count</code> 影响的：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">ani</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="s1">&#39;animation.gif&#39;</span><span class="p">,</span> <span class="n">writer</span><span class="o">=</span><span class="s1">&#39;pillow&#39;</span><span class="p">)</span>
</code></pre></div><h1 id="柱状图动起来">柱状图动起来</h1>
<p>直接上代码，<strong>比着葫芦画瓢</strong> 让你的柱状图舞动起来吧：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
<span class="kn">from</span> <span class="nn">matplotlib</span> <span class="kn">import</span> <span class="n">animation</span>
<span class="kn">from</span> <span class="nn">matplotlib</span> <span class="kn">import</span> <span class="n">pyplot</span> <span class="k">as</span> <span class="n">plt</span>

<span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="o">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">4</span><span class="p">))</span>

<span class="n">x</span> <span class="o">=</span> <span class="p">[</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">)]</span>

<span class="n">data</span> <span class="o">=</span> <span class="p">[[</span><span class="n">np</span><span class="o">.</span><span class="n">sin</span><span class="p">(</span><span class="n">d</span><span class="o">/</span><span class="n">np</span><span class="o">.</span><span class="n">pi</span><span class="o">+</span><span class="n">i</span><span class="o">/</span><span class="mi">50</span><span class="o">*</span><span class="n">np</span><span class="o">.</span><span class="n">pi</span><span class="p">)</span> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">x</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)]</span>
<span class="n">rects</span> <span class="o">=</span> <span class="n">plt</span><span class="o">.</span><span class="n">bar</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">plt</span><span class="o">.</span><span class="n">ylim</span><span class="p">(</span><span class="o">-</span><span class="mf">1.2</span><span class="p">,</span> <span class="mf">1.2</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">animate</span><span class="p">(</span><span class="n">i</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">rect</span><span class="p">,</span> <span class="n">yi</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">rects</span><span class="p">,</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">]):</span>
        <span class="n">rect</span><span class="o">.</span><span class="n">set_height</span><span class="p">(</span><span class="n">yi</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">rects</span>

<span class="n">anim</span> <span class="o">=</span> <span class="n">animation</span><span class="o">.</span><span class="n">FuncAnimation</span><span class="p">(</span><span class="n">fig</span><span class="p">,</span> <span class="n">animate</span><span class="p">,</span> <span class="n">frames</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="n">interval</span><span class="o">=</span><span class="mi">40</span><span class="p">)</span>
<span class="c1"># anim.save(&#39;bar.gif&#39;, writer=&#39;pillow&#39;)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div><p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181112154200404087645.gif" alt="20181112154200404087645.gif"></p>]]></content>
		</item>
		
		<item>
			<title>排序算法知多少</title>
			<link>https://blog.5km.studio/2018/11/07/algorithm-sort/</link>
			<pubDate>Wed, 07 Nov 2018 15:37:05 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/11/07/algorithm-sort/</guid>
			<description>&lt;p&gt;大学里学的第一门编程语言是 c++，c++ 的课程中接触的第一种排序算法是&lt;strong&gt;冒泡排序&lt;/strong&gt;，这已经过去八年，很惭愧的是到目前只了解&lt;strong&gt;冒泡排序&lt;/strong&gt;和&lt;strong&gt;插入排序&lt;/strong&gt;两种，作为一名合格的程序猿也应该了解了解其它的排序算法吧！所以开本文记录整理网上所谓的&lt;strong&gt;十大排序算法&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2018111115418975029904.gif&#34; alt=&#34;2018111115418975029904.gif&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>大学里学的第一门编程语言是 c++，c++ 的课程中接触的第一种排序算法是<strong>冒泡排序</strong>，这已经过去八年，很惭愧的是到目前只了解<strong>冒泡排序</strong>和<strong>插入排序</strong>两种，作为一名合格的程序猿也应该了解了解其它的排序算法吧！所以开本文记录整理网上所谓的<strong>十大排序算法</strong>。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2018111115418975029904.gif" alt="2018111115418975029904.gif"></p>
<h1 id="排序算法概述">排序算法概述</h1>
<p>网上常见的排序算法有十种：<a href="https://zh.wikipedia.org/wiki/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F">冒泡排序</a>、<a href="https://zh.wikipedia.org/wiki/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F">快速排序</a>、<a href="https://zh.wikipedia.org/wiki/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F">插入排序</a>、<a href="https://zh.wikipedia.org/wiki/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F">希尔排序</a>、<a href="https://zh.wikipedia.org/wiki/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F">选择排序</a>、<a href="https://zh.wikipedia.org/wiki/%E5%A0%86%E6%8E%92%E5%BA%8F">堆排序</a>、<a href="https://zh.wikipedia.org/wiki/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F">归并排序</a>、<a href="https://zh.wikipedia.org/wiki/%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F">计数排序</a>、<a href="https://zh.wikipedia.org/wiki/%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F">基数排序</a>、<a href="https://zh.wikipedia.org/wiki/%E6%A1%B6%E6%8E%92%E5%BA%8F">桶排序</a>。</p>
<h2 id="排序算法分类">排序算法分类</h2>
<p>上面十大排序算法按照<strong>非线性时间比较类排序</strong>^[<strong>非线性时间比较类排序</strong>: 通过比较来决定元素间的相对次序，由于其时间复杂度不能突破 $O(n\log n)$ ，因此称为非线性时间比较类排序。]和<strong>线性时间非比较类排序</strong>^[<strong>线性时间非比较类排序</strong>: 不通过比较来决定元素间的相对次序，它可以突破基于比较排序的时间下界，以线性时间运行，因此称为线性时间非比较类排序。]进行分类：</p>
<p>![](<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/Surface">https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/Surface</a> Pro 4 – <a href="mailto:1@2x.png">1@2x.png</a>)</p>
<h2 id="排序算法对比">排序算法对比</h2>
<p>从<strong>时间复杂度</strong>^[<strong>时间复杂度</strong>：对排序数据的总的操作次数。反映当n变化时，操作次数呈现什么规律。]、<strong>空间复杂度</strong>^[<strong>空间复杂度</strong>：是指算法在计算机内执行时所需存储空间的度量，它也是数据规模n的函数。]和 <strong>稳定性</strong>^[<strong>稳定</strong>：如果a原本在b前面，而a=b，排序之后a仍然在b的前面。<strong>不稳定</strong>：如果a原本在b的前面，而a=b，排序之后 a 可能会出现在 b 的后面。]三方面对比十种排序方法如下：</p>
<style>
table th:first-of-type {
    width:75px;
}
table th:last-of-type {
    width:75px;
}
</style>
<table>
<thead>
<tr>
<th>排序算法</th>
<th>时间复杂度(平均)</th>
<th>时间复杂度(最坏)</th>
<th>时间复杂度(最好)</th>
<th>空间复杂度(平均)</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>冒泡排序</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(1)$</td>
<td>稳定</td>
</tr>
<tr>
<td>快速排序</td>
<td>$O(n\log_2 n)$</td>
<td>$O(n^2)$</td>
<td>$O(n\log_2 n)$</td>
<td>$O(n\log_2 n)$</td>
<td>不稳定</td>
</tr>
<tr>
<td>插入排序</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(1)$</td>
<td>稳定</td>
</tr>
<tr>
<td>希尔排序</td>
<td>$O(n^{1.3})$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(1)$</td>
<td>不稳定</td>
</tr>
<tr>
<td>选择排序</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(1)$</td>
<td>不稳定</td>
</tr>
<tr>
<td>堆排序</td>
<td>$O(n\log_2 n)$</td>
<td>$O(n\log_2 n)$</td>
<td>$O(n\log_2 n)$</td>
<td>$O(1)$</td>
<td>不稳定</td>
</tr>
<tr>
<td>归并排序</td>
<td>$O(n\log_2 n)$</td>
<td>$O(n\log_2 n)$</td>
<td>$O(n\log_2 n)$</td>
<td>$O(n)$</td>
<td>稳定</td>
</tr>
<tr>
<td>计数排序</td>
<td>$O(n+k)$</td>
<td>$O(n+k)$</td>
<td>$O(n+k)$</td>
<td>$O(n+k)$</td>
<td>稳定</td>
</tr>
<tr>
<td>基数排序</td>
<td>$O(n+k)$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(n+k)$</td>
<td>稳定</td>
</tr>
<tr>
<td>桶排序</td>
<td>$O(n\cdot k)$</td>
<td>$O(n \cdot k)$</td>
<td>$O(n \cdot k)$</td>
<td>$O(n+k)$</td>
<td>稳定</td>
</tr>
</tbody>
</table>
<h1 id="冒泡排序">冒泡排序</h1>
<p><code>2018年11月08日</code></p>
<p>冒泡排序（英语：Bubble Sort）是一种简单的排序算法。它重复遍历要排序的数列，一次比较两个元素，如果他们的顺序错误就把他们交换过来。遍历数列的工作重复地进行直到没有元素需要交换，即排序完成。这个算法进行时越小的元素会经交换慢慢“浮”到数列的顶端，故得“冒泡之名。</p>
<h2 id="冒泡排序步骤">冒泡排序步骤</h2>
<ol>
<li>比较相邻的元素。如果第一个比第二个大，就交换他们两个。</li>
<li>对每一对相邻元素作同样的工作，从开始第一对到结尾的最后一对。这步做完后，最后的元素会是最大的数。</li>
<li>针对所有的元素重复以上的步骤，除了最后一个。</li>
<li>持续每次对越来越少的元素重复上面的步骤，直到没有任何一对数字需要比较为止。</li>
</ol>
<p>为了方便理解，以数组 <code>[12, 3, 45, 65, 54, 34, 78, 9 ,10, 23]</code> 排序为例，排序过程如以下动图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2018111115418975029904.gif" alt="2018111115418975029904.gif"></p>
<h2 id="冒泡排序示例代码">冒泡排序示例代码</h2>
<p>python3 实现冒泡算法封装成函数如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">bubble_sort</span><span class="p">(</span><span class="n">numlist</span><span class="p">):</span>
    <span class="n">length</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">numlist</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">length</span> <span class="o">-</span><span class="mi">1</span> <span class="o">-</span> <span class="n">i</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">numlist</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">numlist</span><span class="p">[</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]:</span>
                <span class="n">numlist</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">numlist</span><span class="p">[</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">numlist</span><span class="p">[</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">],</span> <span class="n">numlist</span><span class="p">[</span><span class="n">j</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">numlist</span>
</code></pre></div><p>进行测试，简单测试代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">l</span> <span class="o">=</span> <span class="p">[</span><span class="mi">12</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">45</span><span class="p">,</span> <span class="mi">65</span><span class="p">,</span> <span class="mi">54</span><span class="p">,</span> <span class="mi">34</span><span class="p">,</span> <span class="mi">78</span><span class="p">,</span> <span class="mi">9</span> <span class="p">,</span><span class="mi">10</span><span class="p">,</span> <span class="mi">23</span><span class="p">]</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;sorting... </span><span class="si">{</span><span class="n">l</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
    <span class="n">l</span> <span class="o">=</span> <span class="n">bubble_sort</span><span class="p">(</span><span class="n">l</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;sorted  -&gt; </span><span class="si">{</span><span class="n">l</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</code></pre></div><p>运行脚本，一切OK：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ python3 bubble.py
sorting... <span class="o">[</span>12, 3, 45, 65, 54, 34, 78, 9, 10, 23<span class="o">]</span>
sorted  -&gt; <span class="o">[</span>3, 9, 10, 12, 23, 34, 45, 54, 65, 78<span class="o">]</span>
</code></pre></div><h1 id="快速排序">快速排序</h1>
<p><code>2018年11月09日</code></p>
<p>快速排序（英语：Quicksort），又称划分交换排序（partition-exchange sort），简称快排，一种排序算法，最早由东尼·霍尔提出。在平均状况下，排序
$n$ 个项目要 $O(n\log n)$（大O符号）次比较。在最坏状况下则需要 $O(n^2)$ 次比较，但这种状况并不常见。事实上，快速排序 $\Theta (n\log n)$ 通常明显比其他算法更快，因为它的内部循环（inner loop）可以在大部分的架构上很有效率地达成。</p>
<h2 id="快速排序步骤">快速排序步骤</h2>
<p>快排采用和归并排序相同的分而治之的思想，将待排序数组分成左右两个子数组，对两部分子数组独立排序。当子数组均有序时，整个数组也就有序了^[<a href="http://www.litreily.top/2018/07/07/quick-sort/#%E5%BF%AB%E6%8E%92%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86"><strong>Python实现快排及其可视化——快排基本原理</strong></a>]。</p>
<ol>
<li>将原始数组 <code>data</code> 随机打乱，以消除对输入的依赖（本步可选）</li>
<li>选择数组的某个元素作切分元素 <code>v</code>，比如首个元素 <code>data[0]</code></li>
<li>切分数组
<ul>
<li>从左往右找到第一个大于切分元素v的元素 <code>data[i]</code></li>
<li>从右到左找到第一个小于切分元素v的元素 <code>data[j]</code></li>
<li>交换 <code>data[i]</code> 与 <code>data[j]</code></li>
<li>重复以上三步直到 <code>i &gt;= j</code></li>
<li>交换 <code>data[j]</code> 与切分元素 <code>data[0]</code></li>
</ul>
</li>
<li>递归调用，对切分后的左侧子数组进行排序</li>
<li>递归调用，对切分后的右侧子数组进行排序</li>
</ol>
<p>为了方便理解，以数组 <code>[12, 3, 45, 65, 54, 34, 78, 9 ,10, 23]</code> 排序为例，排序过程如以下动图:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181111154194773939997.gif" alt="20181111154194773939997.gif"></p>
<h2 id="快速排序示例代码">快速排序示例代码</h2>
<p>python3 实现快速排序封装成函数如下(代码中选择最后一个元素作切分元素 <code>v</code>)：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python">    <span class="k">if</span> <span class="n">lo</span> <span class="o">&lt;</span> <span class="n">hi</span><span class="p">:</span>
        <span class="n">p</span> <span class="o">=</span> <span class="n">partition</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">hi</span><span class="p">)</span>
        <span class="n">quicksort</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span>
        <span class="n">quicksort</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">p</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">hi</span><span class="p">)</span>
    <span class="k">return</span>

<span class="k">def</span> <span class="nf">partition</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">hi</span><span class="p">):</span>
    <span class="n">pivot</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">hi</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
    <span class="n">i</span> <span class="o">=</span> <span class="n">lo</span> <span class="o">-</span> <span class="mi">1</span>
    <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">lo</span><span class="p">,</span> <span class="n">hi</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">pivot</span><span class="p">:</span>
            <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
    <span class="k">if</span> <span class="n">data</span><span class="p">[</span><span class="n">hi</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]:</span>
        <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">],</span> <span class="n">data</span><span class="p">[</span><span class="n">hi</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">hi</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">i</span><span class="o">+</span><span class="mi">1</span>
</code></pre></div><p>测试实现的快速排序算法：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">l</span> <span class="o">=</span> <span class="p">[</span><span class="mi">12</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">45</span><span class="p">,</span> <span class="mi">65</span><span class="p">,</span> <span class="mi">54</span><span class="p">,</span> <span class="mi">34</span><span class="p">,</span> <span class="mi">78</span><span class="p">,</span> <span class="mi">9</span> <span class="p">,</span><span class="mi">10</span><span class="p">,</span> <span class="mi">23</span><span class="p">]</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;sorting... </span><span class="si">{</span><span class="n">l</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
    <span class="n">quicksort</span><span class="p">(</span><span class="n">l</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">l</span><span class="p">))</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;sorted:    </span><span class="si">{</span><span class="n">l</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</code></pre></div><p>测试一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ python3 quick.py
sorting... <span class="o">[</span>12, 3, 45, 65, 54, 34, 78, 9, 10, 23<span class="o">]</span>
sorted:    <span class="o">[</span>3, 9, 10, 12, 23, 34, 45, 54, 65, 78<span class="o">]</span>
</code></pre></div><h1 id="插入排序">插入排序</h1>
<p><code>2018年11月10日</code></p>
<p>插入排序（英语：Insertion Sort）是一种简单直观的排序算法。它的工作原理是通过构建有序序列，对于未排序数据，在已排序序列中从后向前扫描，找到相应位置并插入。插入排序在实现上，通常采用 <strong>in-place</strong> 排序（即只需用到 $O(1)$ 的额外空间的排序），因而在从后向前扫描过程中，需要反复把已排序元素逐步向后挪位，为最新元素提供插入空间。</p>
<h2 id="插入排序步骤">插入排序步骤</h2>
<p>一般来说，插入排序都采用 <strong>in-place</strong> 在数组上实现。具体算法描述如下：</p>
<ol>
<li>从第一个元素开始，该元素可以认为已经被排序</li>
<li>取出下一个元素，在已经排序的元素序列中从后向前扫描</li>
<li>如果该元素（已排序）大于新元素，将该元素移到下一位置</li>
<li>重复步骤3，直到找到已排序的元素小于或者等于新元素的位置</li>
<li>将新元素插入到该位置后</li>
<li>重复步骤2~5</li>
</ol>
<p>为了方便理解，以数组 <code>[12, 3, 45, 65, 54, 34, 78, 9 ,10, 23]</code> 排序为例，排序过程如以下动图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181111154194998895578.gif" alt="20181111154194998895578.gif"></p>
<h2 id="插入排序示例代码">插入排序示例代码</h2>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">insert_sort</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
    <span class="n">n</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">data</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">]:</span>
                <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="k">break</span>
    <span class="k">return</span> <span class="n">data</span>
</code></pre></div><p>测试一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">l</span> <span class="o">=</span> <span class="p">[</span><span class="mi">12</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">45</span><span class="p">,</span> <span class="mi">65</span><span class="p">,</span> <span class="mi">54</span><span class="p">,</span> <span class="mi">34</span><span class="p">,</span> <span class="mi">78</span><span class="p">,</span> <span class="mi">9</span> <span class="p">,</span><span class="mi">10</span><span class="p">,</span> <span class="mi">23</span><span class="p">]</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;sorting... </span><span class="si">{</span><span class="n">l</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
    <span class="n">l</span> <span class="o">=</span> <span class="n">insert_sort</span><span class="p">(</span><span class="n">l</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;sorted:    </span><span class="si">{</span><span class="n">l</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</code></pre></div><p>结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ python3 insert.py
sorting... <span class="o">[</span>12, 3, 45, 65, 54, 34, 78, 9, 10, 23<span class="o">]</span>
sorted:    <span class="o">[</span>3, 9, 10, 12, 23, 34, 45, 54, 65, 78<span class="o">]</span>
</code></pre></div><h1 id="希尔排序">希尔排序</h1>
<p><code>2018年11月11日</code></p>
<p>希尔排序，也称递减增量排序算法，是插入排序的一种更高效的改进版本。希尔排序按其设计者<a href="https://zh.wikipedia.org/w/index.php?title=%E5%94%90%E7%BA%B3%E5%BE%B7.%E5%B8%8C%E5%B0%94&amp;action=edit&amp;redlink=1">希尔（Donald Shell）</a>的名字命名，该算法由1959年公布。希尔排序是非稳定排序算法。希尔排序是基于插入排序的以下两点性质而提出改进方法的：</p>
<ol>
<li>插入排序在对几乎已经排好序的数据操作时，效率高，即可以达到线性排序的效率</li>
<li>但插入排序一般来说是低效的，因为插入排序每次只能将数据移动一位</li>
</ol>
<p>步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序，最终算法以步长为1进行排序。当步长为1时，算法变为普通插入排序，这就保证了数据一定会被排序。</p>
<p>Donald Shell最初建议步长选择为 $\frac{n}{2}$ 并且对步长取半直到步长达到1。虽然这样取可以比 $\mathcal {O} (n^{2})$ 类的算法（插入排序）更好，但这样仍然有减少平均时间和最差时间的余地。可能希尔排序最重要的地方在于当用较小步长排序后，以前用的较大步长仍然是有序的。比如，如果一个数列以步长5进行了排序然后再以步长3进行排序，那么该数列不仅是以步长3有序，而且是以步长5有序。如果不是这样，那么算法在迭代过程中会打乱以前的顺序，那就不会以如此短的时间完成排序了^[已知的最好步长序列是由 Sedgewick 提出的 <code>(1, 5, 19, 41, 109,...)</code>，该序列的项来自 $9\times 4^{i}-9\times 2^{i}+1 $ 和 $2^{{i+2}}\times (2^{{i+2}}-3)+1$ 这两个算式。这项研究也表明“比较在希尔排序中是最主要的操作，而不是交换。”用这样步长序列的希尔排序比插入排序要快，甚至在小数组中比快速排序和堆排序还快，但是在涉及大量数据时希尔排序还是比快速排序慢。——<a href="https://zh.wikipedia.org/wiki/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F#%E6%AD%A5%E9%95%BF%E5%BA%8F%E5%88%97"><strong>希尔排序</strong></a> @ <a href="https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5"><strong>维基百科</strong></a> ]。</p>
<h2 id="希尔排序步骤">希尔排序步骤</h2>
<p>这里步长选择为 $\frac{n}{2}$ 并且对步长取半直到步长达到1的序列：</p>
<p>序列个数为 $n$，初始化步长 $stepsize = n$。</p>
<ol>
<li>取新步长为 $\frac{stepsize}{2}$，如果 $stepsize &lt; 1$ 算法结束</li>
<li>从第一个元素开始，该元素可以认为已经被排序</li>
<li>取出按照新步长 $stepsize$ 的下一个元素，在已经排序的元素序列中从后向前扫描</li>
<li>如果该元素（已排序）大于新元素，将该元素移到按新步长的下一位置</li>
<li>重复步骤 4，直到找到已排序的元素小于或者等于新元素的位置</li>
<li>将新元素插入到该位置</li>
<li>重复步骤 3~6，直到按照新步长 $stepsize$ 的插入排序结束</li>
<li>重复 1～7步骤</li>
</ol>
<p>为了方便理解，以数组 <code>[12, 3, 45, 65, 54, 34, 78, 9 ,10, 23]</code> 排序为例，排序过程如以下动图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181112154195404360778.gif" alt="20181112154195404360778.gif"></p>
<h2 id="希尔排序示例代码">希尔排序示例代码</h2>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">shell_sort</span><span class="p">(</span><span class="n">numlist</span><span class="p">):</span>
    <span class="n">n</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">numlist</span><span class="p">)</span>
    <span class="c1"># 初始步长</span>
    <span class="n">stepsize</span> <span class="o">=</span> <span class="n">n</span> <span class="o">//</span> <span class="mi">2</span>
    <span class="k">while</span> <span class="n">stepsize</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">stepsize</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span>
            <span class="c1"># 每个步长进行插入排序</span>
            <span class="n">temp</span> <span class="o">=</span> <span class="n">numlist</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
            <span class="n">j</span> <span class="o">=</span> <span class="n">i</span>
            <span class="c1"># 插入排序</span>
            <span class="k">while</span> <span class="n">j</span> <span class="o">&gt;=</span> <span class="n">stepsize</span> <span class="ow">and</span> <span class="n">numlist</span><span class="p">[</span><span class="n">j</span> <span class="o">-</span> <span class="n">stepsize</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">temp</span><span class="p">:</span>
                <span class="n">numlist</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">numlist</span><span class="p">[</span><span class="n">j</span> <span class="o">-</span> <span class="n">stepsize</span><span class="p">]</span>
                <span class="n">j</span> <span class="o">-=</span> <span class="n">stepsize</span>
            <span class="n">numlist</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">temp</span>
        <span class="c1"># 得到新的步长</span>
        <span class="n">stepsize</span> <span class="o">=</span> <span class="n">stepsize</span> <span class="o">//</span> <span class="mi">2</span>
    <span class="k">return</span> <span class="n">numlist</span>
</code></pre></div><p>测试一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">l</span> <span class="o">=</span> <span class="p">[</span><span class="mi">12</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">45</span><span class="p">,</span> <span class="mi">65</span><span class="p">,</span> <span class="mi">54</span><span class="p">,</span> <span class="mi">34</span><span class="p">,</span> <span class="mi">78</span><span class="p">,</span> <span class="mi">9</span> <span class="p">,</span><span class="mi">10</span><span class="p">,</span> <span class="mi">23</span><span class="p">]</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;sorting... </span><span class="si">{</span><span class="n">l</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
    <span class="n">l</span> <span class="o">=</span> <span class="n">shell_sort</span><span class="p">(</span><span class="n">l</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;sorted:    </span><span class="si">{</span><span class="n">l</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</code></pre></div><p>结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ python3 shell.py
sorting... <span class="o">[</span>12, 3, 45, 65, 54, 34, 78, 9, 10, 23<span class="o">]</span>
sorted:    <span class="o">[</span>3, 9, 10, 12, 23, 34, 45, 54, 65, 78<span class="o">]</span>
</code></pre></div><h1 id="选择排序">选择排序</h1>
<p><code>2018年11月12日</code></p>
<p>选择排序（Selection sort）是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小（大）元素，存放到排序序列的起始位置，然后，再从剩余未排序元素中继续寻找最小（大）元素，然后放到已排序序列的末尾。以此类推，直到所有元素均排序完毕。</p>
<p>选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上，则它不会被移动。选择排序每次交换一对元素，它们当中至少有一个将被移到其最终位置上，因此对 $n$ 个元素的表进行排序总共进行至多 $n-1$ 次交换。在所有的完全依靠交换去移动元素的排序方法中，选择排序属于非常好的一种。</p>
<h2 id="选择排序步骤">选择排序步骤</h2>
<ol>
<li>选择未排序的序的首元素作为作为比对元素</li>
<li>往后进行比较，找到最小的元素与首位的元素互换位置</li>
<li>重复 1～2 步一直到只剩一个序列</li>
</ol>
<p>为了方便理解，这里以对序列 <code>[12, 3, 45, 65, 54, 34, 78, 9 ,10, 23]</code> 排序为例展示排序过程动画如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181112154200663696412.gif" alt="20181112154200663696412.gif"></p>
<h2 id="选择排序示例代码">选择排序示例代码</h2>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">select_sort</span><span class="p">(</span><span class="n">numlist</span><span class="p">):</span>
    <span class="n">length</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">numlist</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">length</span><span class="o">-</span><span class="mi">1</span><span class="p">):</span>
        <span class="n">min_index</span> <span class="o">=</span> <span class="n">i</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">length</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">numlist</span><span class="p">[</span><span class="n">min_index</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">numlist</span><span class="p">[</span><span class="n">j</span><span class="p">]:</span>
                <span class="n">min_index</span> <span class="o">=</span> <span class="n">j</span>
        <span class="k">if</span> <span class="n">min_index</span> <span class="o">!=</span> <span class="n">i</span><span class="p">:</span>
            <span class="n">numlist</span><span class="p">[</span><span class="n">min_index</span><span class="p">],</span> <span class="n">numlist</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">numlist</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">numlist</span><span class="p">[</span><span class="n">min_index</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">numlist</span>
</code></pre></div><p>测试一下封装的算法：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">l</span> <span class="o">=</span> <span class="p">[</span><span class="mi">12</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">45</span><span class="p">,</span> <span class="mi">65</span><span class="p">,</span> <span class="mi">54</span><span class="p">,</span> <span class="mi">34</span><span class="p">,</span> <span class="mi">78</span><span class="p">,</span> <span class="mi">9</span> <span class="p">,</span><span class="mi">10</span><span class="p">,</span> <span class="mi">23</span><span class="p">]</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;sorting... </span><span class="si">{</span><span class="n">l</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;sorrted    </span><span class="si">{</span><span class="n">select_sort</span><span class="p">(</span><span class="n">l</span><span class="p">)</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</code></pre></div><p>测试结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ python3 <span class="k">select</span>.py
sorting... <span class="o">[</span>12, 3, 45, 65, 54, 34, 78, 9, 10, 23<span class="o">]</span>
sorrted    <span class="o">[</span>3, 9, 10, 12, 23, 34, 45, 54, 65, 78<span class="o">]</span>
</code></pre></div><h1 id="堆排序">堆排序</h1>
<h1 id="归并排序">归并排序</h1>
<h1 id="计数排序">计数排序</h1>
<h1 id="基数排序">基数排序</h1>
<h1 id="桶排序">桶排序</h1>
<p><strong>未完待续。。。</strong></p>]]></content>
		</item>
		
		<item>
			<title>windows 下 python 开发利器 vscode 的锻造</title>
			<link>https://blog.5km.studio/2018/11/02/python-dev-in-windows/</link>
			<pubDate>Fri, 02 Nov 2018 13:02:49 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/11/02/python-dev-in-windows/</guid>
			<description>&lt;p&gt;之前一直是在 macOS 下进行 python 学习和应用开发，没想到会有一天会用到 windows 进行 python 开发，macOS下使用 vscode 加 python 插件的形式作为开发利器，同样在 windows 下也可以使用 vscode 进行 python 开发，本文就讲一下如何使用 vscode 打造 python 开发利器。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>之前一直是在 macOS 下进行 python 学习和应用开发，没想到会有一天会用到 windows 进行 python 开发，macOS下使用 vscode 加 python 插件的形式作为开发利器，同样在 windows 下也可以使用 vscode 进行 python 开发，本文就讲一下如何使用 vscode 打造 python 开发利器。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/2.png" alt=""></p>
<h1 id="前言">前言</h1>
<p>前几天，十里手贱为了擦屏幕往 macbook 屏幕上洒上了水和洗洁精，导致屏幕进水，导致屏幕上出现了 n 条细小的横线，液晶应该是没有受损，可能是排线进水导致屏幕接收数据异常，从而出现了横线，网上搜了一下说，要立马关机，不要用热风吹，最好是扔到装满干燥剂的箱子里，等待两天屏幕中的水自然蒸发出来，没有干燥剂可以用大米代替，所以我照办了（怪只怪我的macbook不保修了）。</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/IMG_20181102_133559R.jpg" width="360"/>
</figure>

<p>没有电脑了的程序员怎么能叫程序员，所幸水木的 surface pro 闲着，那我就拿来先用着了，也就有了今天文章的主题，在 windows 10 下配置 python 开发环境。</p>
<h1 id="利器打造">利器打造</h1>
<p>现在使用的平台是装有 64位 win10 的 new surface pro ，理论本文的操作不受 windows 版本限制，只要 PC 硬件够用就可以。</p>
<h2 id="安装python">安装python</h2>
<p>在 win10 下默认是不安装 python ，所以还需要自己安装 python。</p>
<h3 id="下载python371">下载python3.7.1</h3>
<p>去<a href="https://www.python.org">官网</a>下载支持 64位 windows 的版本下载，写此文章时最新的 python 版本是 3.7.1 ，建议安装最新的 python。这里下载的是：<a href="https://www.python.org/ftp/python/3.7.1/python-3.7.1-amd64.exe">Windows x86-64 executable installer</a></p>
<h3 id="安装python371">安装python3.7.1</h3>
<ol>
<li>
<p>运行下载到的安装包 <strong>python-3.7.1-amd64.exe</strong></p>
</li>
<li>
<p>勾选 <code>Add Python 3.7 to Path</code>，这一步会将 python 添加到环境变量，这样可以在命令行执行 <code>python</code>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/python-install.png" alt=""></p>
</li>
<li>
<p>点击 <code>Install Now</code>就可以开启自动安装的过程了</p>
</li>
<li>
<p>注销重新登录 windows，是环境变量生效</p>
</li>
<li>
<p><code>win</code> 键 + <code>x</code>，选择 <strong>命令提示符</strong> 打开命令行，输入 <code>python</code>，如果出现 <code>python</code> 交互 CLI 说明安装成功：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/python-cli.png" alt=""></p>
</li>
</ol>
<h2 id="安装vscode">安装vscode</h2>
<ol>
<li>
<p><a href="https://code.visualstudio.com/">官网</a>下载 <a href="https://aka.ms/win32-x64-user-stable">VS Code for windows</a>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/vscode-download.png" alt=""></p>
</li>
<li>
<p>运行下载的安装包，写本文时下载的是 <strong>VSCodeUserSetup-x64-1.28.2.exe</strong></p>
</li>
<li>
<p>一路根据自己的情况下一步，这里要说的是其中一步选择性的勾选一些选项，这里我全部勾选了，比如最后一个添加环境变量Path的，这样安装完成以后就可以在命令提示符窗口输入 <code>code</code> 命令启动 VS code 了：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/vscode-install.png" alt=""></p>
</li>
<li>
<p>继续下一步安装即可，安装完成后打开默认应该是英文界面（配置为中文界面<a href="#配置vscode为中文界面">点我</a>）</p>
</li>
<li>
<p>这里十里将 vscode 主题配置为了 <strong>solarized dark</strong></p>
</li>
</ol>
<h2 id="配置vscode">配置vscode</h2>
<h3 id="安装python插件">安装python插件</h3>
<p>微软官方为 vscode 开发了 python插件，这里安装这个插件就会提供全面的 python IDE 的功能了。</p>
<ol>
<li>打开 vscode，点击窗口左边扩展按钮，搜索 <strong>python</strong> 理论上第一个就是要安装的插件</li>
<li>选中名为 <strong>Python</strong> 的插件点击 <strong>install</strong> 按钮</li>
<li>安装完成后点击 <strong>reload</strong> 就可激活插件了</li>
</ol>
<h3 id="安装pylint库">安装pylint库</h3>
<p>上述 <strong>Python</strong> 插件依赖于 python 的库 <a href="https://pypi.org/project/pylint/">pylint</a>，这里需要安装一下。</p>
<p>有两种方式，一种是 vscode 自行检查并提示安装，另一种是手动安装，推荐使用第一种方法。</p>
<h4 id="vscode安装pylint">vscode安装pylint</h4>
<ol>
<li>
<p>打开一个 py 文件，就会有如下的提示：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/pylint-tip.png" width="451"/>
</figure>

</li>
<li>
<p>点击 <strong>install</strong>，vscode就会自行调用 <code>pip</code> 命令进行插件安装</p>
</li>
</ol>
<h4 id="手动安装pylint">手动安装pylint</h4>
<ol>
<li>
<p>在 vscode 中按下 <code>ctrl</code> + ` 组合键就可以在 vscode 中打开一个命令行窗口：</p>
</li>
<li>
<p>输入以下命令进行安即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">python -m pip install pylint
</code></pre></div></li>
</ol>
<h1 id="测试">测试</h1>
<p>这里新建一个文件 <code>hello.py</code>，输入代码，输入过程中就会看到 vscode 会自动提示补全，同时还能检查语法错误，代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">argparse</span>


<span class="k">def</span> <span class="nf">get_args</span><span class="p">():</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s1">&#39;向某人打招呼&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;person&#39;</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s1">&#39;?&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定打招呼对象&#39;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>


<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">get_args</span><span class="p">()</span>
    <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">person</span> <span class="o">==</span> <span class="kc">None</span><span class="p">:</span>
        <span class="n">args</span><span class="o">.</span><span class="n">person</span> <span class="o">=</span> <span class="s1">&#39;world&#39;</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;hello, </span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">person</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
</code></pre></div><p>这里使用 <strong>argsparse</strong> 库实现了一个简单的工具，可以向指定人打招呼。</p>
<p>在 vscode 编辑器中的代码编辑器的行号前面可以打断点，也就是说此时的 vscode 也可以调试 python 代码，打上断点后，按 <code>F5</code> 键就可以启动调试，运行到我们打断点的地方：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/python-breakpoint.png" alt=""></p>
<h1 id="其它">其它</h1>
<h2 id="配置vscode为中文界面">配置vscode为中文界面</h2>
<h3 id="安装简体中文语言包">安装简体中文语言包</h3>
<ol>
<li>扩展搜索框搜索 <strong>Chinese</strong></li>
<li>安装名为 <strong>Chinese (Simplified) Language Pack for Visual Studio Code</strong> 的扩展</li>
</ol>
<h3 id="更改vscode界面语言配置">更改vscode界面语言配置</h3>
<ol>
<li>
<p><code>ctrl</code> + <code>shift</code> + <code>p</code> 打开 vscode 命令输入框，输入 <strong>language</strong></p>
</li>
<li>
<p>选择 <strong>Configure Display Language</strong>，会打开配置文件 <code>locale.json</code></p>
</li>
<li>
<p>更改配置位 <code>zh-cn</code>，如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-json" data-lang="json"><span class="p">{</span>
    <span class="err">//</span> <span class="err">定义</span> <span class="err">VS</span> <span class="err">Code</span> <span class="err">的显示语言。</span>
    <span class="err">//</span> <span class="err">请参阅</span> <span class="err">https://go.microsoft.com/fwlink/?LinkId=761051，了解支持的语言列表。</span>

    <span class="nt">&#34;locale&#34;</span><span class="p">:</span><span class="s2">&#34;zh-cn&#34;</span> <span class="err">//</span> <span class="err">更改将在重新启动</span> <span class="err">VS</span> <span class="err">Code</span> <span class="err">之后生效。</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>重启 vscode 即可看到界面变成了中文</p>
</li>
</ol>
<h2 id="配置中文显示字体">配置中文显示字体</h2>
<p>作为颜控无法忍受 vscode 默认显示中文为宋体，必须配置为微软雅黑。</p>
<ol>
<li>
<p><code>ctrl</code> + <code>shift</code> + <code>p</code> 打开 vscode 命令输入框，输入 <strong>setting</strong>，选择 <strong>Open Settings(UI)</strong></p>
</li>
<li>
<p>在打开的设置界面中的搜索框，输入 <strong>font</strong>，就会过滤得到一个 <strong>Editor: font family</strong></p>
</li>
<li>
<p>将微软雅黑添加到第一个字体后面，这样不会影响代码的等宽字体显示：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/img/vscode-font.png" alt=""></p>
</li>
</ol>
<p>会看到中文字体的显示会立马生效。</p>
<h1 id="总结">总结</h1>
<p>本文简单介绍了如何在 windows 下 从 0 到 1 的打造 vscode 的 python 开发环境，从此可以在 windows 上使用 python 撒欢了！</p>]]></content>
		</item>
		
		<item>
			<title>python 实现文件校验</title>
			<link>https://blog.5km.studio/2018/10/26/hashcal-python/</link>
			<pubDate>Fri, 26 Oct 2018 13:12:18 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/10/26/hashcal-python/</guid>
			<description>&lt;p&gt;今天又学习了点 &lt;code&gt;argparse&lt;/code&gt; 库的东西——子命令实现的正确姿势(常规操作)，借着实现一个文件校验、比较的小工具来巩固一下刚学到的子命令实现。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天又学习了点 <code>argparse</code> 库的东西——子命令实现的正确姿势(常规操作)，借着实现一个文件校验、比较的小工具来巩固一下刚学到的子命令实现。</p>
<p>本文实现了一个名为 <code>hashpare</code>^[之所以叫 <code>hashpare</code>, 是因为工具使用 <strong>hash</strong> 值对文件进行比较，就是 <strong>hash compare</strong>，合称 <code>hashpare</code>] 的工具，我已经托管到 <a href="https://github.com/smslit">github</a> 上<a href="https://github.com/smslit/tools-with-script/tree/master/hashpare">tools-with-script/hashpare</a>。总感觉直接上代码有点粗暴，下面分步骤讲解实现过程。</p>
<h1 id="脚本准备">脚本准备</h1>
<ol>
<li>
<p>新建文件： <code>touch hashpare</code></p>
</li>
<li>
<p>添加文件运行权限： <code>chmod +x hashpare</code></p>
</li>
<li>
<p>注明脚本解释器，文中首行添加：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/bin/env python3</span>
</code></pre></div></li>
<li>
<p>本文会用到三个内建库，分别是 <code>os</code>、<code>hashlib</code>、<code>argparse</code>，导入它们：</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">hashlib</span>
<span class="kn">import</span> <span class="nn">argparse</span>
</code></pre></div><ul>
<li><code>os</code>: 用来操作文件或目录的路径</li>
<li><code>hashlib</code>: 用来计算文件的 hash 值</li>
<li><code>argparse</code>: 用来解析命令行参数，本文会用到 <code>add_subparsers</code> 添加子解析器集，从而添加子命令^[Many programs split up their functionality into a number of sub-commands, for example, the svn program can invoke sub-commands like svn checkout, svn update, and svn commit&hellip;<code>ArgumentParser</code> supports the creation of such sub-commands with the <code>add_subparsers()</code> method&hellip;, <a href="https://docs.python.org/3/library/argparse.html#sub-commands">Sub-commands</a>]</li>
</ul>
</li>
</ol>
<h1 id="计算校验">计算校验</h1>
<p>这里使用 <code>hashlib</code> 来计算文件的校验码，封装函数如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="k">def</span> <span class="nf">calculate_hashvalue</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="n">algorithm</span><span class="o">=</span><span class="s1">&#39;sha1&#39;</span><span class="p">):</span>
    <span class="s2">&#34;&#34;&#34;计算文件校验值，可选校验算法 sha1 或 md5
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="k">if</span> <span class="n">algorithm</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">[</span><span class="s1">&#39;sha1&#39;</span><span class="p">,</span> <span class="s1">&#39;md5&#39;</span><span class="p">]:</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;[-] 不支持校验算法: </span><span class="si">{</span><span class="n">algorithm</span><span class="si">}</span><span class="s1"> [-]&#39;</span><span class="p">)</span>
        <span class="k">return</span> <span class="p">(</span><span class="n">algorithm</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
    <span class="n">hashobj</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha1</span><span class="p">()</span> <span class="k">if</span> <span class="n">algorithm</span> <span class="o">==</span> <span class="s1">&#39;sha1&#39;</span> <span class="k">else</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">md5</span><span class="p">()</span>
    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="s1">&#39;rb&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">hashobj</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
    <span class="k">return</span> <span class="n">algorithm</span><span class="p">,</span> <span class="n">hashobj</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">()</span>
</code></pre></div><p>方法内部实现很简单，就是根据指定的校验算法计算指定文件的校验码，并返回校验算法名称和校验码。</p>
<h1 id="解析参数">解析参数</h1>
<p>将解析参数相关配置和操作封装到 <code>process_with_args</code> 函数中：</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="k">def</span> <span class="nf">process_with_args</span><span class="p">():</span>
    <span class="s2">&#34;&#34;&#34;解析命令行参数
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s1">&#39;计算文件校验，检查校验值，比较两个文件&#39;</span><span class="p">)</span>
    <span class="n">commands</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_subparsers</span><span class="p">(</span><span class="n">help</span><span class="o">=</span><span class="s1">&#39;子命令&#39;</span><span class="p">)</span>
    <span class="n">check</span> <span class="o">=</span> <span class="n">commands</span><span class="o">.</span><span class="n">add_parser</span><span class="p">(</span><span class="s1">&#39;check&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;检查校验值命令&#39;</span><span class="p">)</span>
    <span class="n">check</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;file&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指明文件路径&#39;</span><span class="p">)</span>
    <span class="n">check</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;hash&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定校验码&#39;</span><span class="p">)</span>
    <span class="n">check</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-a&#39;</span><span class="p">,</span> <span class="s1">&#39;--algorithm&#39;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s1">&#39;sha1&#39;</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;sha1&#39;</span><span class="p">,</span> <span class="s1">&#39;md5&#39;</span><span class="p">],</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定校验算法&#39;</span><span class="p">)</span>
    <span class="n">check</span><span class="o">.</span><span class="n">set_defaults</span><span class="p">(</span><span class="n">func</span><span class="o">=</span><span class="n">check_hash</span><span class="p">)</span>
    <span class="n">compare</span> <span class="o">=</span> <span class="n">commands</span><span class="o">.</span><span class="n">add_parser</span><span class="p">(</span><span class="s1">&#39;compare&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;比较两个文件&#39;</span><span class="p">)</span>
    <span class="n">compare</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;file&#39;</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定要比较的两个文件&#39;</span><span class="p">)</span>
    <span class="n">compare</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-a&#39;</span><span class="p">,</span> <span class="s1">&#39;--algorithm&#39;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s1">&#39;sha1&#39;</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;sha1&#39;</span><span class="p">,</span> <span class="s1">&#39;md5&#39;</span><span class="p">],</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定校验算法&#39;</span><span class="p">)</span>
    <span class="n">compare</span><span class="o">.</span><span class="n">set_defaults</span><span class="p">(</span><span class="n">func</span><span class="o">=</span><span class="n">compare_hash</span><span class="p">)</span>
    <span class="n">calculate</span> <span class="o">=</span> <span class="n">commands</span><span class="o">.</span><span class="n">add_parser</span><span class="p">(</span><span class="s1">&#39;calculate&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;计算校验码&#39;</span><span class="p">)</span>
    <span class="n">calculate</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;file&#39;</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s1">&#39;+&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定要计算校验的文件&#39;</span><span class="p">)</span>
    <span class="n">calculate</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-a&#39;</span><span class="p">,</span> <span class="s1">&#39;--algorithm&#39;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s1">&#39;sha1&#39;</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;sha1&#39;</span><span class="p">,</span> <span class="s1">&#39;md5&#39;</span><span class="p">],</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定校验算法&#39;</span><span class="p">)</span>
    <span class="n">calculate</span><span class="o">.</span><span class="n">set_defaults</span><span class="p">(</span><span class="n">func</span><span class="o">=</span><span class="n">calculate_hash</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</code></pre></div><p>上述代码很想可以看到，有三个子命令：</p>
<ul>
<li>check: 主要是用来检验文件的校验值是不是与给定的校验码一致</li>
<li>compare: 比较两个文件的校验值从而验证内容是否一致</li>
<li>calculate: 计算给定文件的校验码</li>
</ul>
<p>另外，每个子命令的解析器都绑定了一个处理函数，这种方式是处理子命令更加方便。</p>
<h2 id="check子命令处理">check子命令处理</h2>
<p>check 子命令绑定了方法 <code>check_hash</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="k">def</span> <span class="nf">check_hash</span><span class="p">(</span><span class="n">args</span><span class="p">):</span>
    <span class="n">args</span><span class="o">.</span><span class="n">file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">file</span><span class="p">)</span>
    <span class="n">algorithm</span><span class="p">,</span> <span class="n">hashvalue</span> <span class="o">=</span> <span class="n">calculate_hashvalue</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">file</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">algorithm</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">    </span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">file</span><span class="si">}</span><span class="se">\n</span><span class="s1">    </span><span class="si">{</span><span class="n">algorithm</span><span class="si">}</span><span class="s1">: </span><span class="si">{</span><span class="n">hashvalue</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
    <span class="n">result</span> <span class="o">=</span> <span class="s1">&#39;一致&#39;</span> <span class="k">if</span> <span class="n">hashvalue</span> <span class="o">==</span> <span class="n">args</span><span class="o">.</span><span class="n">hash</span> <span class="k">else</span> <span class="s1">&#39;不一致&#39;</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">    文件校验码与 </span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">hash</span><span class="si">}</span><span class="s1"> </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</code></pre></div><p>这个方法会打印文件的名称和校验码，最终打印与给定校验码的比较结果。</p>
<h2 id="compare子命令处理">compare子命令处理</h2>
<p>compare 子命令绑定了方法 <code>compare_hash</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="k">def</span> <span class="nf">compare_hash</span><span class="p">(</span><span class="n">args</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">()</span>
    <span class="n">hashvalue</span> <span class="o">=</span> <span class="p">[</span><span class="kc">None</span><span class="p">,</span> <span class="kc">None</span><span class="p">]</span>
    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">filepath</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">file</span><span class="p">):</span>
        <span class="n">filepath</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
        <span class="n">algorithm</span><span class="p">,</span> <span class="n">hashvalue</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">calculate_hashvalue</span><span class="p">(</span><span class="n">filepath</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">algorithm</span><span class="p">)</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;    name: </span><span class="si">{</span><span class="n">filepath</span><span class="si">}</span><span class="se">\n</span><span class="s1">    </span><span class="si">{</span><span class="n">algorithm</span><span class="si">}</span><span class="s1">: </span><span class="si">{</span><span class="n">hashvalue</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="si">}</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
    <span class="n">result</span> <span class="o">=</span> <span class="s1">&#39;一致&#39;</span> <span class="k">if</span> <span class="n">hashvalue</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="n">hashvalue</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">else</span> <span class="s1">&#39;不一致&#39;</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;    两文件内容</span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</code></pre></div><p>这里最终会打印两个文件的名称、校验码，以及最终的比较结果。</p>
<h2 id="calculate子命令处理">calculate子命令处理</h2>
<p>calculate 子命令绑定了方法<code>calculate_hash</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="k">def</span> <span class="nf">calculate_hash</span><span class="p">(</span><span class="n">args</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">filepath</span> <span class="ow">in</span> <span class="n">args</span><span class="o">.</span><span class="n">file</span><span class="p">:</span>
        <span class="n">filepath</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
        <span class="n">algorithm</span><span class="p">,</span> <span class="n">hashvalue</span> <span class="o">=</span> <span class="n">calculate_hashvalue</span><span class="p">(</span><span class="n">filepath</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">algorithm</span><span class="p">)</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;    name: </span><span class="si">{</span><span class="n">filepath</span><span class="si">}</span><span class="se">\n</span><span class="s1">    </span><span class="si">{</span><span class="n">algorithm</span><span class="si">}</span><span class="s1">: </span><span class="si">{</span><span class="n">hashvalue</span><span class="si">}</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</code></pre></div><p>最终会打印所有指定文件的名称及计算的校验码。</p>
<h1 id="最终实现">最终实现</h1>
<p>最终实现非常简单，只需要如下即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">process_with_args</span><span class="p">()</span>
    <span class="n">args</span><span class="o">.</span><span class="n">func</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
</code></pre></div><h1 id="测试">测试</h1>
<p>工具的帮助信息如下</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ ./hashpare -h
usage: hashpare <span class="o">[</span>-h<span class="o">]</span> <span class="o">{</span>check,compare,calculate<span class="o">}</span> ...

计算文件校验，检查校验值，比较两个文件

positional arguments:
  <span class="o">{</span>check,compare,calculate<span class="o">}</span>
                        子命令
    check               检查校验值命令
    compare             比较两个文件
    calculate           计算校验码

optional arguments:
  -h, --help            show this <span class="nb">help</span> message and <span class="nb">exit</span>
</code></pre></div><h2 id="测试check子命令">测试check子命令</h2>
<p>这里针对 <code>README.md</code> 文件进行测试</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="err">$</span> <span class="o">./</span><span class="n">hashpare</span> <span class="n">check</span> <span class="n">README</span><span class="o">.</span><span class="n">md</span> <span class="mi">1234567890</span>

    <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="mi">5</span><span class="n">km</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">workspace</span><span class="o">/</span><span class="n">python</span><span class="o">/</span><span class="n">tools</span><span class="o">-</span><span class="k">with</span><span class="o">-</span><span class="n">script</span><span class="o">/</span><span class="n">hashpare</span><span class="o">/</span><span class="n">README</span><span class="o">.</span><span class="n">md</span>
    <span class="n">sha1</span><span class="p">:</span> <span class="n">daf0fe7a2fab819444d21b1c082e161f6d613875</span>

    <span class="n">文件校验码与</span> <span class="mi">1234567890</span> <span class="n">不一致</span>

<span class="err">$</span> <span class="o">./</span><span class="n">hashpare</span> <span class="n">check</span> <span class="n">README</span><span class="o">.</span><span class="n">md</span> <span class="n">daf0fe7a2fab819444d21b1c082e161f6d613875</span> <span class="o">-</span><span class="n">a</span> <span class="n">sha1</span>

    <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="mi">5</span><span class="n">km</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">workspace</span><span class="o">/</span><span class="n">python</span><span class="o">/</span><span class="n">tools</span><span class="o">-</span><span class="k">with</span><span class="o">-</span><span class="n">script</span><span class="o">/</span><span class="n">hashpare</span><span class="o">/</span><span class="n">README</span><span class="o">.</span><span class="n">md</span>
    <span class="n">sha1</span><span class="p">:</span> <span class="n">daf0fe7a2fab819444d21b1c082e161f6d613875</span>

    <span class="n">文件校验码与</span> <span class="n">daf0fe7a2fab819444d21b1c082e161f6d613875</span> <span class="n">一致</span>

<span class="err">$</span> <span class="o">./</span><span class="n">hashpare</span> <span class="n">check</span> <span class="n">README</span><span class="o">.</span><span class="n">md</span> <span class="n">daf0fe7a2fab819444d21b1c082e161f6d613875</span> <span class="o">-</span><span class="n">a</span> <span class="n">md5</span>

    <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="mi">5</span><span class="n">km</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">workspace</span><span class="o">/</span><span class="n">python</span><span class="o">/</span><span class="n">tools</span><span class="o">-</span><span class="k">with</span><span class="o">-</span><span class="n">script</span><span class="o">/</span><span class="n">hashpare</span><span class="o">/</span><span class="n">README</span><span class="o">.</span><span class="n">md</span>
    <span class="n">md5</span><span class="p">:</span> <span class="mi">0</span><span class="n">ea8599b24a76f586a6610bf9156b1e7</span>

    <span class="n">文件校验码与</span> <span class="n">daf0fe7a2fab819444d21b1c082e161f6d613875</span> <span class="n">不一致</span>
</code></pre></div><h2 id="测试compare子命令">测试compare子命令</h2>
<p>针对文件 <code>README.md</code> 测试</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="err">$</span> <span class="o">./</span><span class="n">hashpare</span> <span class="n">compare</span> <span class="n">README</span><span class="o">.</span><span class="n">md</span> <span class="n">README</span><span class="o">.</span><span class="n">md</span>

    <span class="n">name</span><span class="p">:</span> <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="mi">5</span><span class="n">km</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">workspace</span><span class="o">/</span><span class="n">python</span><span class="o">/</span><span class="n">tools</span><span class="o">-</span><span class="k">with</span><span class="o">-</span><span class="n">script</span><span class="o">/</span><span class="n">hashpare</span><span class="o">/</span><span class="n">README</span><span class="o">.</span><span class="n">md</span>
    <span class="n">sha1</span><span class="p">:</span> <span class="n">daf0fe7a2fab819444d21b1c082e161f6d613875</span>

    <span class="n">name</span><span class="p">:</span> <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="mi">5</span><span class="n">km</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">workspace</span><span class="o">/</span><span class="n">python</span><span class="o">/</span><span class="n">tools</span><span class="o">-</span><span class="k">with</span><span class="o">-</span><span class="n">script</span><span class="o">/</span><span class="n">hashpare</span><span class="o">/</span><span class="n">README</span><span class="o">.</span><span class="n">md</span>
    <span class="n">sha1</span><span class="p">:</span> <span class="n">daf0fe7a2fab819444d21b1c082e161f6d613875</span>

    <span class="n">两文件内容一致</span>

<span class="err">$</span> <span class="o">./</span><span class="n">hashpare</span> <span class="n">compare</span> <span class="n">README</span><span class="o">.</span><span class="n">md</span> <span class="n">README</span><span class="o">.</span><span class="n">md</span> <span class="o">-</span><span class="n">a</span> <span class="n">md5</span>

    <span class="n">name</span><span class="p">:</span> <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="mi">5</span><span class="n">km</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">workspace</span><span class="o">/</span><span class="n">python</span><span class="o">/</span><span class="n">tools</span><span class="o">-</span><span class="k">with</span><span class="o">-</span><span class="n">script</span><span class="o">/</span><span class="n">hashpare</span><span class="o">/</span><span class="n">README</span><span class="o">.</span><span class="n">md</span>
    <span class="n">md5</span><span class="p">:</span> <span class="mi">0</span><span class="n">ea8599b24a76f586a6610bf9156b1e7</span>

    <span class="n">name</span><span class="p">:</span> <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="mi">5</span><span class="n">km</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">workspace</span><span class="o">/</span><span class="n">python</span><span class="o">/</span><span class="n">tools</span><span class="o">-</span><span class="k">with</span><span class="o">-</span><span class="n">script</span><span class="o">/</span><span class="n">hashpare</span><span class="o">/</span><span class="n">README</span><span class="o">.</span><span class="n">md</span>
    <span class="n">md5</span><span class="p">:</span> <span class="mi">0</span><span class="n">ea8599b24a76f586a6610bf9156b1e7</span>

    <span class="n">两文件内容一致</span>
</code></pre></div><h2 id="测试calculate子命令">测试calculate子命令</h2>
<p>这里计算 <code>README.md</code> 的校验码：</p>
<div class="highlight"><pre class="chroma"><code class="language-Python" data-lang="Python"><span class="err">$</span> <span class="o">./</span><span class="n">hashpare</span> <span class="n">calculate</span> <span class="n">README</span><span class="o">.</span><span class="n">md</span>

    <span class="n">name</span><span class="p">:</span> <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="mi">5</span><span class="n">km</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">workspace</span><span class="o">/</span><span class="n">python</span><span class="o">/</span><span class="n">tools</span><span class="o">-</span><span class="k">with</span><span class="o">-</span><span class="n">script</span><span class="o">/</span><span class="n">hashpare</span><span class="o">/</span><span class="n">README</span><span class="o">.</span><span class="n">md</span>
    <span class="n">sha1</span><span class="p">:</span> <span class="n">daf0fe7a2fab819444d21b1c082e161f6d613875</span>

<span class="err">$</span> <span class="o">./</span><span class="n">hashpare</span> <span class="n">calculate</span> <span class="n">README</span><span class="o">.</span><span class="n">md</span> <span class="o">-</span><span class="n">a</span> <span class="n">md5</span>

    <span class="n">name</span><span class="p">:</span> <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="mi">5</span><span class="n">km</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">workspace</span><span class="o">/</span><span class="n">python</span><span class="o">/</span><span class="n">tools</span><span class="o">-</span><span class="k">with</span><span class="o">-</span><span class="n">script</span><span class="o">/</span><span class="n">hashpare</span><span class="o">/</span><span class="n">README</span><span class="o">.</span><span class="n">md</span>
    <span class="n">md5</span><span class="p">:</span> <span class="mi">0</span><span class="n">ea8599b24a76f586a6610bf9156b1e7</span>
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>python 实现文本文件的编码检测和转换</title>
			<link>https://blog.5km.studio/2018/10/25/encoding-python/</link>
			<pubDate>Thu, 25 Oct 2018 22:05:59 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/10/25/encoding-python/</guid>
			<description>&lt;p&gt;记得在前公司做嵌入式开发时，总是会遇到因代码文件编码不对导致的注释乱码问题。因为部门里很多人喜欢使用 GB2312，而我比较倾向于用 utf-8 编码，所以每次合并他们代码的时候，通常我会使用一个 &lt;code&gt;find&lt;/code&gt; 和 &lt;code&gt;enca&lt;/code&gt; 的组合命令，对工程检索将 GB2312 的 &lt;code&gt;.c&lt;/code&gt; 和 &lt;code&gt;.h&lt;/code&gt; 文件转码为 &lt;code&gt;utf-8&lt;/code&gt;，那么我们是不是可以自己编写一个 python 工具实现文件编码的检测和转换呢？当然可以，借助第三方库 &lt;code&gt;chardet&lt;/code&gt; 就可以做到！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>记得在前公司做嵌入式开发时，总是会遇到因代码文件编码不对导致的注释乱码问题。因为部门里很多人喜欢使用 GB2312，而我比较倾向于用 utf-8 编码，所以每次合并他们代码的时候，通常我会使用一个 <code>find</code> 和 <code>enca</code> 的组合命令，对工程检索将 GB2312 的 <code>.c</code> 和 <code>.h</code> 文件转码为 <code>utf-8</code>，那么我们是不是可以自己编写一个 python 工具实现文件编码的检测和转换呢？当然可以，借助第三方库 <code>chardet</code> 就可以做到！</p>
<h1 id="编码操作">编码操作</h1>
<p>要对文件编码转换前，必须也要知道源文件是什么编码，所以也得进行编码检测。</p>
<h2 id="编码检测">编码检测</h2>
<p>这里是用 <code>chardet</code> 库的 <code>UniversalDetector</code> 类实现文件编码的检测，在使用前需要安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ pip3 install chardet
</code></pre></div><p>比如针对编码为 <code>utf-8</code> 的文件 <code>README.md</code>，可以如下进行代码检测，检测的同时也会得到一个置信度表征检测正确的可信度：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">chardet.universaldetector</span> <span class="kn">import</span> <span class="n">UniversalDetector</span>

<span class="n">detector</span> <span class="o">=</span> <span class="n">UniversalDetector</span><span class="p">()</span>
<span class="n">detector</span><span class="o">.</span><span class="n">reset</span><span class="p">()</span>
<span class="k">for</span> <span class="n">each</span> <span class="ow">in</span> <span class="nb">open</span><span class="p">(</span><span class="n">filepath</span><span class="p">,</span> <span class="s1">&#39;rb&#39;</span><span class="p">):</span>
    <span class="n">detector</span><span class="o">.</span><span class="n">feed</span><span class="p">(</span><span class="n">each</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">detector</span><span class="o">.</span><span class="n">done</span><span class="p">:</span>
        <span class="k">break</span>
<span class="n">detector</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="n">fileencoding</span> <span class="o">=</span> <span class="n">detector</span><span class="o">.</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;encoding&#39;</span><span class="p">]</span>
<span class="n">confidence</span> <span class="o">=</span> <span class="n">detector</span><span class="o">.</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;confidence&#39;</span><span class="p">]</span>
</code></pre></div><p>其中注意的是：</p>
<ul>
<li>要以二进制读取模式打开文件</li>
<li><strong>fileencoding</strong> 是编码，<strong>confidence</strong> 是置信度，取之范围 0～1</li>
</ul>
<h2 id="编码转换">编码转换</h2>
<p>文件编码的转换其实使用文件读写操作就可以完成，打开文件的时候可以指定编码，所以可以使用源文件编码形式打开读取内容，再以新的编码打开要生成的文件，将读取去的内容写入新的文件，就实现编码转换了。</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">sourcefile</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;gb2312&#39;</span><span class="p">,</span> <span class="n">errors</span><span class="o">=</span><span class="s1">&#39;replace&#39;</span><span class="p">)</span>
<span class="n">text</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">f</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">targetfile</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">,</span> <span class="n">errors</span><span class="o">=</span><span class="s1">&#39;replace&#39;</span><span class="p">)</span>
<span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="n">f</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div><p>其中：</p>
<ul>
<li>调用 open 的时候指定 <code>error</code> 参数为 <code>replace</code>，说明会将标记字符插入到错误位置</li>
<li>如果使用 with 语法的话可能会在这里出现一点小问题，如果输出文件路径与原文件路径一致会出现保存编码异常，所以没有使用 with</li>
</ul>
<h1 id="工具实现">工具实现</h1>
<p>这个 python 脚本工具的实现会用到内建库 <code>argparse</code> 库（<a href="/2018/09/13/python-argparse/">点我</a>了解基本用法）、<code>os</code> 库和第三方库 <code>chardet</code> 库。</p>
<h2 id="准备脚本">准备脚本</h2>
<ol>
<li>
<p>新建文件 <code>encoding</code>: <code>touch encoding</code></p>
</li>
<li>
<p>添加运行权限: <code>chmod +x encoding</code></p>
</li>
<li>
<p>指明脚本解释器，文件中添加内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/bin/env python3</span>
</code></pre></div></li>
<li>
<p>导入必要库，文件中添加：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">from</span> <span class="nn">chardet.universaldetector</span> <span class="kn">import</span> <span class="n">UniversalDetector</span>
</code></pre></div></li>
</ol>
<h2 id="添加命令参数解析函数">添加命令参数解析函数</h2>
<p>封装命令行参数解析操作为函数：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">getargs</span><span class="p">():</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s1">&#39;这是一个检测或转换文件编码的工具&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;files&#39;</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s1">&#39;+&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定一个或多个文件的路径&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-e&#39;</span><span class="p">,</span> <span class="s1">&#39;--encoding&#39;</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s1">&#39;?&#39;</span><span class="p">,</span> <span class="n">const</span><span class="o">=</span><span class="s1">&#39;UTF-8&#39;</span><span class="p">,</span>
                        <span class="n">help</span><span class="o">=</span><span class="s1">&#39;&#39;&#39;指定目标编码，可选择的编码：
</span><span class="s1">ASCII, (Default) UTF-8 (with or without a BOM), UTF-16 (with a BOM),
</span><span class="s1">UTF-32 (with a BOM), Big5, GB2312/GB18030, EUC-TW, HZ-GB-2312, 
</span><span class="s1">ISO-2022-CN, EUC-JP, SHIFT_JIS, ISO-2022-JP, ISO-2022-KR, KOI8-R, 
</span><span class="s1">MacCyrillic, IBM855, IBM866, ISO-8859-5, windows-1251, ISO-8859-2, 
</span><span class="s1">windows-1250, EUC-KR, ISO-8859-5, windows-1251, ISO-8859-1, windows-1252, 
</span><span class="s1">ISO-8859-7, windows-1253, ISO-8859-8, windows-1255, TIS-620&#39;&#39;&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-o&#39;</span><span class="p">,</span> <span class="s1">&#39;--outdir&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定转换文件的输出目录&#39;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</code></pre></div><table>
<thead>
<tr>
<th>参数</th>
<th>值</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>files</td>
<td>文件路径</td>
<td>指定一个或多个文件的路径</td>
</tr>
<tr>
<td>-e, &ndash;encoding</td>
<td>编码名称, 默认为 <code>UTF-8</code></td>
<td>指定目标编码</td>
</tr>
<tr>
<td>-o, &ndash;outdir</td>
<td>目录路径</td>
<td>指定转换文件的输出目录</td>
</tr>
</tbody>
</table>
<h2 id="封装编码检测函数">封装编码检测函数</h2>
<p>为了方便处理，根据 <a href="#编码检测">编码检测</a> 小节，封装编码检测函数如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">detcect_encoding</span><span class="p">(</span><span class="n">filepath</span><span class="p">):</span>
    <span class="s2">&#34;&#34;&#34;检测文件编码
</span><span class="s2">    Args:
</span><span class="s2">        detector: UniversalDetector 对象
</span><span class="s2">        filepath: 文件路径
</span><span class="s2">    Return:
</span><span class="s2">        fileencoding: 文件编码
</span><span class="s2">        confidence: 检测结果的置信度，百分比
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="n">detector</span> <span class="o">=</span> <span class="n">UniversalDetector</span><span class="p">()</span>
    <span class="n">detector</span><span class="o">.</span><span class="n">reset</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">each</span> <span class="ow">in</span> <span class="nb">open</span><span class="p">(</span><span class="n">filepath</span><span class="p">,</span> <span class="s1">&#39;rb&#39;</span><span class="p">):</span>
        <span class="n">detector</span><span class="o">.</span><span class="n">feed</span><span class="p">(</span><span class="n">each</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">detector</span><span class="o">.</span><span class="n">done</span><span class="p">:</span>
            <span class="k">break</span>
    <span class="n">detector</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
    <span class="n">fileencoding</span> <span class="o">=</span> <span class="n">detector</span><span class="o">.</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;encoding&#39;</span><span class="p">]</span>
    <span class="n">confidence</span> <span class="o">=</span> <span class="n">detector</span><span class="o">.</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;confidence&#39;</span><span class="p">]</span>
    <span class="k">if</span> <span class="n">fileencoding</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
        <span class="n">fileencoding</span> <span class="o">=</span> <span class="s1">&#39;unknown&#39;</span>
        <span class="n">confidence</span> <span class="o">=</span> <span class="mf">0.99</span>
    <span class="k">return</span> <span class="n">fileencoding</span><span class="p">,</span> <span class="n">confidence</span> <span class="o">*</span> <span class="mi">100</span>
</code></pre></div><p><code>UniversalDetector</code> 实例对象完成检测后，如果不能识别编码，<code>detector.result['encoding']</code> 就会是 <code>None</code> ，所以这里做了一定处理，并且最后返回的置信度这个值取的是百分比的值。</p>
<h2 id="最终实现流程">最终实现流程</h2>
<p>在 <code>__main__</code> 中实现整个功能如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">getargs</span><span class="p">()</span>
    <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">outdir</span><span class="p">:</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">outdir</span><span class="p">):</span>
            <span class="n">answer</span> <span class="o">=</span> <span class="nb">input</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;[-] 无效的导出路径: </span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">outdir</span><span class="si">}</span><span class="s1"> [-]</span><span class="se">\n</span><span class="s1">要用转码后的文件直接替换源文件吗? y or n</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
            <span class="k">if</span> <span class="s1">&#39;y&#39;</span> <span class="ow">in</span> <span class="n">answer</span><span class="p">:</span>
                <span class="n">args</span><span class="o">.</span><span class="n">outdir</span> <span class="o">=</span> <span class="kc">None</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">file</span> <span class="ow">in</span> <span class="n">args</span><span class="o">.</span><span class="n">files</span><span class="p">:</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">file</span><span class="p">):</span>
            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;[-] 无效的文件路径: </span><span class="si">{</span><span class="n">file</span><span class="si">}</span><span class="s1"> [-]&#39;</span><span class="p">)</span>
            <span class="k">continue</span>
        <span class="n">encoding</span><span class="p">,</span> <span class="n">confidence</span> <span class="o">=</span> <span class="n">detcect_encoding</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;[+] </span><span class="si">{</span><span class="n">file</span><span class="si">}</span><span class="s1">: 编码 -&gt; </span><span class="si">{</span><span class="n">encoding</span><span class="si">}</span><span class="s1"> (置信度 </span><span class="si">{</span><span class="n">confidence</span><span class="si">}</span><span class="s1">%) [+]&#39;</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">encoding</span> <span class="ow">and</span> <span class="p">(</span><span class="n">encoding</span> <span class="ow">is</span> <span class="ow">not</span> <span class="s1">&#39;unknown&#39;</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">confidence</span> <span class="o">&gt;</span> <span class="mf">0.75</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">encoding</span> <span class="o">==</span> <span class="n">encoding</span><span class="p">:</span>
                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;[*] </span><span class="si">{</span><span class="n">file</span><span class="si">}</span><span class="s1"> 已经是 </span><span class="si">{</span><span class="n">encoding</span><span class="si">}</span><span class="s1"> 编码了，无需转换！[*]&#39;</span><span class="p">)</span>
                <span class="k">continue</span>
            <span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="n">encoding</span><span class="p">,</span> <span class="n">errors</span><span class="o">=</span><span class="s1">&#39;replace&#39;</span><span class="p">)</span>
            <span class="n">text</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
            <span class="n">f</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
            <span class="n">outpath</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">outdir</span><span class="p">,</span> <span class="n">file</span><span class="p">)</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">outdir</span> <span class="k">else</span> <span class="n">file</span>
            <span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">outpath</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="n">args</span><span class="o">.</span><span class="n">encoding</span><span class="p">,</span> <span class="n">errors</span><span class="o">=</span><span class="s1">&#39;replace&#39;</span><span class="p">)</span>
            <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
            <span class="n">f</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;[+] 转码成功: </span><span class="si">{</span><span class="n">file</span><span class="si">}</span><span class="s1">(</span><span class="si">{</span><span class="n">encoding</span><span class="si">}</span><span class="s1">) -&gt; </span><span class="si">{</span><span class="n">outpath</span><span class="si">}</span><span class="s1">(</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">encoding</span><span class="si">}</span><span class="s1">) [+]&#39;</span><span class="p">)</span>
</code></pre></div><ol>
<li>获取命令行参数</li>
<li>如果命令行中指定了转换文件输出目录，检查目录是否有效</li>
<li>枚举指定的文件列表</li>
<li>针对文件，首先检查路径的有效性，然后通过 <code>detect_encoding</code> 方法获取编码检测结果</li>
<li>如果指定了目标编码并且检测的文件编码不是未知和置信度高于 75% 那就进行编码转换</li>
<li>在编码转换中，检测转换的必要性</li>
<li>如果需要转换那就按照小节<a href="#编码转换">编码转换</a>进行编码的转换</li>
</ol>
<p>最终实现完整的工具代码，可参考：<a href="https://github.com/smslit/tools-with-script/blob/master/encoding/encoding">tools-with-script/encoding/encoding</a></p>
<h1 id="测试">测试</h1>
<p>实现了工具，必须测试一下，这里用到了文件 <a href="https://github.com/smslit/tools-with-script/blob/master/encoding/gb2312.txt">gb2312.txt</a> 和文件 <a href="https://github.com/smslit/tools-with-script/blob/master/encoding/big5.txt">big5.txt</a>：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ ./encoding -h
usage: encoding <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-e <span class="o">[</span>ENCODING<span class="o">]]</span> <span class="o">[</span>-o OUTDIR<span class="o">]</span> files <span class="o">[</span>files ...<span class="o">]</span>

这是一个检测或转换文件编码的工具

positional arguments:
  files                 指定一个或多个文件的路径

optional arguments:
  -h, --help            show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -e <span class="o">[</span>ENCODING<span class="o">]</span>, --encoding <span class="o">[</span>ENCODING<span class="o">]</span>
                        指定目标编码，可选择的编码： ASCII, <span class="o">(</span>Default<span class="o">)</span> UTF-8 <span class="o">(</span>with or without
                        a BOM<span class="o">)</span>, UTF-16 <span class="o">(</span>with a BOM<span class="o">)</span>, UTF-32 <span class="o">(</span>with a BOM<span class="o">)</span>,
                        Big5, GB2312/GB18030, EUC-TW, HZ-GB-2312, ISO-2022-CN,
                        EUC-JP, SHIFT_JIS, ISO-2022-JP, ISO-2022-KR, KOI8-R,
                        MacCyrillic, IBM855, IBM866, ISO-8859-5, windows-1251,
                        ISO-8859-2, windows-1250, EUC-KR, ISO-8859-5,
                        windows-1251, ISO-8859-1, windows-1252, ISO-8859-7,
                        windows-1253, ISO-8859-8, windows-1255, TIS-620
  -o OUTDIR, --outdir OUTDIR
                        指定转换文件的输出目录
$ ./encoding big5.txt gb2312.txt
<span class="o">[</span>+<span class="o">]</span> big5.txt: 编码 -&gt; Big5 <span class="o">(</span>置信度 99.0%<span class="o">)</span> <span class="o">[</span>+<span class="o">]</span>
<span class="o">[</span>+<span class="o">]</span> gb2312.txt: 编码 -&gt; GB2312 <span class="o">(</span>置信度 99.0%<span class="o">)</span> <span class="o">[</span>+<span class="o">]</span>
$ ./encoding big5.txt gb2312.txt -e utf-8
<span class="o">[</span>+<span class="o">]</span> big5.txt: 编码 -&gt; Big5 <span class="o">(</span>置信度 99.0%<span class="o">)</span> <span class="o">[</span>+<span class="o">]</span>
<span class="o">[</span>+<span class="o">]</span> 转码成功: big5.txt<span class="o">(</span>Big5<span class="o">)</span> -&gt; big5.txt<span class="o">(</span>utf-8<span class="o">)</span> <span class="o">[</span>+<span class="o">]</span>
<span class="o">[</span>+<span class="o">]</span> gb2312.txt: 编码 -&gt; GB2312 <span class="o">(</span>置信度 99.0%<span class="o">)</span> <span class="o">[</span>+<span class="o">]</span>
<span class="o">[</span>+<span class="o">]</span> 转码成功: gb2312.txt<span class="o">(</span>GB2312<span class="o">)</span> -&gt; gb2312.txt<span class="o">(</span>utf-8<span class="o">)</span> <span class="o">[</span>+<span class="o">]</span>
$ ✗ ./encoding big5.txt gb2312.txt
<span class="o">[</span>+<span class="o">]</span> big5.txt: 编码 -&gt; utf-8 <span class="o">(</span>置信度 99.0%<span class="o">)</span> <span class="o">[</span>+<span class="o">]</span>
<span class="o">[</span>+<span class="o">]</span> gb2312.txt: 编码 -&gt; utf-8 <span class="o">(</span>置信度 99.0%<span class="o">)</span> <span class="o">[</span>+<span class="o">]</span>
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>python 实现命令行工具 tee</title>
			<link>https://blog.5km.studio/2018/10/24/tee-Python/</link>
			<pubDate>Wed, 24 Oct 2018 17:58:19 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/10/24/tee-Python/</guid>
			<description>&lt;p&gt;最近，朋友 &lt;a href=&#34;http://www.litreily.top&#34;&gt;Litreily&lt;/a&gt; 发表了文章&lt;a href=&#34;http://www.litreily.top/2018/09/27/tee/&#34;&gt;Linux指令 - tee的实现&lt;/a&gt; ，是用 C 语言实现的 tee 命令，那我凑个热闹用 python 实现一下下！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181025154039778364827.png&#34; alt=&#34;20181025154039778364827.png&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>最近，朋友 <a href="http://www.litreily.top">Litreily</a> 发表了文章<a href="http://www.litreily.top/2018/09/27/tee/">Linux指令 - tee的实现</a> ，是用 C 语言实现的 tee 命令，那我凑个热闹用 python 实现一下下！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181025154039778364827.png" alt="20181025154039778364827.png"></p>
<h1 id="tee命令">tee命令</h1>
<p>macOS下也有 tee 命令，好像跟 linux 下的有一点点差别，这个小节讲一下 macOS 下的 tee 命令的使用，总该感受下它的功能才能实现它吧！</p>
<h2 id="tee的功能">tee的功能</h2>
<p>打开终端，输入命令 <code>man tee</code> 可以查看 tee 的手册，介绍非常简单，当然是因为这个命令功能很简单。</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">TEE<span class="o">(</span>1<span class="o">)</span>                    BSD General Commands Manual                   TEE<span class="o">(</span>1<span class="o">)</span>

NAME
     tee -- pipe fitting

SYNOPSIS
     tee <span class="o">[</span>-ai<span class="o">]</span> <span class="o">[</span>file ...<span class="o">]</span>

DESCRIPTION
     The tee utility copies standard input to standard output, making a copy in zero or more files.  The output is unbuffered.

     The following options are available:

     -a      Append the output to the files rather than overwriting them.

     -i      Ignore the SIGINT signal.

     The following operands are available:

     file  A pathname of an output file.

     The tee utility takes the default action <span class="k">for</span> all signals, except in the event of the -i option.

     The tee utility exits <span class="m">0</span> on success, and &gt;0 <span class="k">if</span> an error occurs.

STANDARDS
     The tee <span class="k">function</span> is expected to be POSIX IEEE Std 1003.2 <span class="o">(</span><span class="sb">``</span>POSIX.2<span class="s1">&#39;&#39;</span><span class="o">)</span> compatible.

BSD                              June 6, <span class="m">1993</span>                              BSD
</code></pre></div><p>功能简单来说就是把标准输入传输给标准输出(打印出来)的同时将输入内容写入到指定文件，而在指定文件的时候加上 <code>-a</code> 会在文件内容的基础上追加输入而不是覆盖^[<a href="https://www.ibm.com/support/knowledgecenter/zh/ssw_aix_71/com.ibm.aix.cmds5/tee.htm">tee 命令</a>]。</p>
<h2 id="tee的使用">tee的使用</h2>
<p>还是得实际使用使用才能感受它的功能：</p>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ tee log.txt
<span class="nb">test</span>
<span class="nb">test</span>
原来如此
原来如此
^C
$ cat log.txt
<span class="nb">test</span>
原来如此
$ tee -a log.txt
生活如此多彩
生活如此多彩
^C
$ cat log.txt
<span class="nb">test</span>
原来如此
生活如此多彩
$ tee
hah
hah
^C
</code></pre></div><ol>
<li>tee 命令指定文件 <code>log.txt</code>，输入内容敲回车就会打印输入的内容，比如 <code>test</code> 和 <code>原来如此</code>，CTRL + c 退出命令，用 cat 命令查看 <code>log.txt</code> 的内容与输入的内容一致。</li>
<li>加入 <code>-a</code> 选项，就会在指定文件追加输入的内容，比如 <code>生活如此精彩</code>。</li>
<li>不指定文件的话，只会打印输入的内容。</li>
</ol>
<h1 id="python实现tee">python实现tee</h1>
<p>这里只实现上面 <a href="#tee的使用">tee的使用</a> 小节中测试的功能。</p>
<h2 id="文件准备">文件准备</h2>
<ol>
<li>
<p>新建文件：<code>touch tee</code></p>
</li>
<li>
<p>为文件添加运行权限： <code>chmod +x tee</code></p>
</li>
<li>
<p>文件中指明解释器，添加以下内容到 <code>tee</code> 文件中：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/bin/env python3</span>
</code></pre></div></li>
<li>
<p>文件中导入必要的库：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">argparse</span>
</code></pre></div><ul>
<li>sys 库用于操作标准输入输出；</li>
<li>argparse 库用于解析命令参数，这个库的基本使用可以参考我的另一篇文章 <a href="/2018/09/13/python-argparse/">argparse 库的使用</a></li>
</ul>
</li>
</ol>
<h2 id="解析命令参数">解析命令参数</h2>
<p>这里将使用 argparse 库解析参数的操作封装为函数 <code>getargs</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">getargs</span><span class="p">():</span>
    <span class="s2">&#34;&#34;&#34;获取命令参数
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;The tee utility copies standard </span><span class="se">\
</span><span class="se"></span><span class="s2">                                                input to standard output, making a </span><span class="se">\
</span><span class="se"></span><span class="s2">                                                copy in zero or more files.  The </span><span class="se">\
</span><span class="se"></span><span class="s2">                                                output is unbuffered.&#34;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-v&#39;</span><span class="p">,</span> <span class="s1">&#39;--version&#39;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;version&#39;</span><span class="p">,</span> <span class="n">version</span><span class="o">=</span><span class="s2">&#34;v 1.0.0&#34;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-a&#39;</span><span class="p">,</span> <span class="s1">&#39;--append&#39;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s2">&#34;store_true&#34;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;Append the output to the files rather than overwriting them.&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;file&#39;</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s1">&#39;*&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;A pathname of an output file.&#39;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</code></pre></div><h2 id="tee功能实现">tee功能实现</h2>
<p>直接在 <code>__main__</code> 中实现 tee 的功能如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">getargs</span><span class="p">()</span>
    <span class="n">filemode</span> <span class="o">=</span> <span class="s1">&#39;a&#39;</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">append</span> <span class="k">else</span> <span class="s1">&#39;w&#39;</span>
    <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
        <span class="n">line</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">readline</span><span class="p">()</span>
        <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">file</span> <span class="ow">in</span> <span class="n">args</span><span class="o">.</span><span class="n">file</span><span class="p">:</span>
            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="n">filemode</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
                <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">filemode</span> <span class="ow">is</span> <span class="s1">&#39;w&#39;</span><span class="p">:</span>
            <span class="n">filemode</span> <span class="o">=</span> <span class="s1">&#39;a&#39;</span>
</code></pre></div><p>可以看到代码非常简单就实现了 tee 的完整功能，直接操作 <code>sys.stdin</code> 、<code>sys.stdout</code>^[<a href="https://sparkydogx.github.io/2018/08/08/python-tee/">使用python实现tee的效果</a>] 和 文件^[<a href="http://outofmemory.cn/code-snippet/11733/achieve-tee-command">实现tee命令</a>]即可！最终代码可参考：<a href="https://github.com/smslit/tools-with-script/blob/master/tee/tee">tools-with-script/tee/tee</a>。</p>
<h1 id="测试">测试</h1>
<div class="highlight"><pre class="chroma"><code class="language-Shell" data-lang="Shell">$ ./tee --help
usage: tee <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-v<span class="o">]</span> <span class="o">[</span>-a<span class="o">]</span> <span class="o">[</span>file <span class="o">[</span>file ...<span class="o">]]</span>

The tee utility copies standard input to standard output, making a copy in
zero or more files. The output is unbuffered.

positional arguments:
  file           A pathname of an output file.

optional arguments:
  -h, --help     show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -v, --version  show program<span class="s1">&#39;s version number and exit
</span><span class="s1">  -a, --append   Append the output to the files rather than overwriting them.
</span><span class="s1">$ ./tee
</span><span class="s1">12
</span><span class="s1">12
</span><span class="s1">$ ./tee log.txt
</span><span class="s1">where
</span><span class="s1">where
</span><span class="s1">who i am
</span><span class="s1">who i am
</span><span class="s1">$ cat log.txt
</span><span class="s1">where
</span><span class="s1">who i am
</span><span class="s1">$ ./tee -a log.txt
</span><span class="s1">that&#39;</span>s good
that<span class="s1">&#39;s good
</span><span class="s1">$ cat log.txt
</span><span class="s1">where
</span><span class="s1">who i am
</span><span class="s1">that&#39;</span>s good
$ ./tee -a log.txt log1.txt
Wonderful
Wonderful
$ cat log.txt
where
who i am
that<span class="err">&#39;</span>s good
Wonderful
$ cat log1.txt
Wonderful
</code></pre></div><p>测试步骤解释如下：</p>
<ol>
<li>打印帮助信息；</li>
<li>不指定任何参数，只会将标准输入打印到标准输出；</li>
<li>指定文件 <code>log.txt</code>，标准输入分别输入 <code>where</code> 和 <code>who i am</code>，标准输出也分别打印了；</li>
<li>查看 <code>log.txt</code> 内容与上一步的标准输入一致；</li>
<li>指定文件 <code>log.txt</code>并指定为追加模式，标准输入 <code>that's good</code> ，标准输出正常显示 <code>that's good</code>；</li>
<li>查看 <code>log.txt</code> 内容是在原来的内容上追加了上一步输入的 <code>that's good</code>；</li>
<li>指定两个文件 <code>log.txt</code> 和 <code>log1.txt</code>，并指定为追加模式，标准输入 <code>Wonderful</code>，标准输出正常打印；</li>
<li>查看 <code>log.txt</code> 内容是在原来的内容上追加了上一步输入的 <code>that's good</code>，而新文件 <code>log1.txt</code> 内容为上一步的 <code>Wonderful</code>；</li>
</ol>]]></content>
		</item>
		
		<item>
			<title>zip 文件暴破</title>
			<link>https://blog.5km.studio/2018/10/23/crack-zip-hacker/</link>
			<pubDate>Tue, 23 Oct 2018 17:00:50 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/10/23/crack-zip-hacker/</guid>
			<description>&lt;p&gt;2007年年初，美国德克萨斯州的布朗斯维尔市的程序员 Albert Castillo 协助警方调查 John Craig Zimmerman 涉嫌拥有和制造儿童色情物品的案件，而 Castillo 就是使用&lt;strong&gt;字典式攻击技术&lt;/strong&gt;^[字典式攻击是一种破解密码的方法。是指在破解密码或密钥时，逐一偿试用户自定义词典中的单词或短语的攻击方式。——&lt;a href=&#34;https://baike.baidu.com/item/%E5%AD%97%E5%85%B8%E5%BC%8F%E6%94%BB%E5%87%BB&#34;&gt;&lt;strong&gt;字典式攻击技术&lt;/strong&gt;&lt;/a&gt;]解密了嫌疑人电脑中的zip压缩文件，从而得到儿童色情图片等证据，得以结束案件。本文十里就简单介绍一下使用 python3 进行字典式破解加密zip文件的方法。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181022154020132354092.png&#34; alt=&#34;20181022154020132354092.png&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>2007年年初，美国德克萨斯州的布朗斯维尔市的程序员 Albert Castillo 协助警方调查 John Craig Zimmerman 涉嫌拥有和制造儿童色情物品的案件，而 Castillo 就是使用<strong>字典式攻击技术</strong>^[字典式攻击是一种破解密码的方法。是指在破解密码或密钥时，逐一偿试用户自定义词典中的单词或短语的攻击方式。——<a href="https://baike.baidu.com/item/%E5%AD%97%E5%85%B8%E5%BC%8F%E6%94%BB%E5%87%BB"><strong>字典式攻击技术</strong></a>]解密了嫌疑人电脑中的zip压缩文件，从而得到儿童色情图片等证据，得以结束案件。本文十里就简单介绍一下使用 python3 进行字典式破解加密zip文件的方法。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181022154020132354092.png" alt="20181022154020132354092.png"></p>
<h1 id="准备工作">准备工作</h1>
<h2 id="加密码的zip文件">加密码的zip文件</h2>
<p>首先得准备一个加密码的 zip 文件，以 macOS 为例，可以使用 <code>zip</code> 命令进行压缩并加密码，比如要将 <code>demo.md</code> 压缩为 <code>demo.zip</code>，可以使用命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ zip -e demo.zip demo.md
Enter password:
Verify password:
updating: demo.md <span class="o">(</span>stored 0%<span class="o">)</span>
</code></pre></div><p>输入密码和确认密码，即可完成加密码的压缩，我这里使用密码是 4 位的，为 <strong>6556</strong>，不自己做压缩包的话，可以使用压缩的 zip 压缩包：<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/sharedemo.zip">demo.zip</a></p>
<h2 id="了解-zipfile-库">了解 zipfile 库</h2>
<p>本文使用 zipfile 库操作压缩文件，这是一个内建的 python 库，不需要手动安装，这一小节在 ipython 环境下简单了解一下使用方法。命令行中输入 <code>ipython</code> 回车即可进入 python 交互环境：</p>
<p>导入 zipfile 库：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="kn">import</span> <span class="nn">zipfile</span>
</code></pre></div><h3 id="打开压缩包">打开压缩包</h3>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">In</span> <span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="n">zFile</span> <span class="o">=</span> <span class="n">zipfile</span><span class="o">.</span><span class="n">ZipFile</span><span class="p">(</span><span class="s1">&#39;demo.zip&#39;</span><span class="p">)</span>
</code></pre></div><h3 id="解压压缩包">解压压缩包</h3>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">In</span> <span class="p">[</span><span class="mi">3</span><span class="p">]:</span> <span class="n">zFile</span><span class="o">.</span><span class="n">extractall</span><span class="p">()</span>
<span class="o">---------------------------------------------------------------------------</span>
<span class="ne">RuntimeError</span>                              <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">)</span>
<span class="o">&lt;</span><span class="n">ipython</span><span class="o">-</span><span class="nb">input</span><span class="o">-</span><span class="mi">3</span><span class="o">-</span><span class="mi">24</span><span class="n">da26377a5e</span><span class="o">&gt;</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span><span class="p">()</span>
<span class="o">----&gt;</span> <span class="mi">1</span> <span class="n">zFile</span><span class="o">.</span><span class="n">extractall</span><span class="p">()</span>
<span class="o">...</span>
<span class="ne">RuntimeError</span><span class="p">:</span> <span class="n">File</span> <span class="o">&lt;</span><span class="n">ZipInfo</span> <span class="n">filename</span><span class="o">=</span><span class="s1">&#39;demo.md&#39;</span> <span class="n">filemode</span><span class="o">=</span><span class="s1">&#39;-rw-r--r--&#39;</span> <span class="n">file_size</span><span class="o">=</span><span class="mi">17</span> <span class="n">compress_size</span><span class="o">=</span><span class="mi">29</span><span class="o">&gt;</span> <span class="ow">is</span> <span class="n">encrypted</span><span class="p">,</span> <span class="n">password</span> <span class="n">required</span> <span class="k">for</span> <span class="n">extraction</span>
</code></pre></div><p>可以看到提示，因为我们测试用的 <code>demo.zip</code> 是我们加了密码的，所以需要密码。<code>extractall</code> 方法有一个参数 <code>pwd</code> 指明密码，用正确密码试一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">In</span> <span class="p">[</span><span class="mi">4</span><span class="p">]:</span> <span class="n">zFile</span><span class="o">.</span><span class="n">extractall</span><span class="p">(</span><span class="n">pwd</span><span class="o">=</span><span class="s1">&#39;6556&#39;</span><span class="p">)</span>
<span class="o">---------------------------------------------------------------------------</span>
<span class="ne">TypeError</span>                                 <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">)</span>
<span class="o">&lt;</span><span class="n">ipython</span><span class="o">-</span><span class="nb">input</span><span class="o">-</span><span class="mi">4</span><span class="o">-</span><span class="n">f057151f05c2</span><span class="o">&gt;</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span><span class="p">()</span>
<span class="o">----&gt;</span> <span class="mi">1</span> <span class="n">zFile</span><span class="o">.</span><span class="n">extractall</span><span class="p">(</span><span class="n">pwd</span><span class="o">=</span><span class="s1">&#39;6556&#39;</span><span class="p">)</span>
<span class="o">...</span>
<span class="ne">TypeError</span><span class="p">:</span> <span class="n">pwd</span><span class="p">:</span> <span class="n">expected</span> <span class="nb">bytes</span><span class="p">,</span> <span class="n">got</span> <span class="nb">str</span>
</code></pre></div><p>抛出了一个 <code>TypeError</code> 类型错误的异常，看提示是需要密码是 <code>bytes</code>，那进行转码^[<a href="https://stackoverflow.com/questions/33577116/zipfile-method-doesnt-works">Zipfile Method doesn&rsquo;t works</a>]试一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">In</span> <span class="p">[</span><span class="mi">5</span><span class="p">]:</span> <span class="n">zFile</span><span class="o">.</span><span class="n">extractall</span><span class="p">(</span><span class="n">pwd</span><span class="o">=</span><span class="s1">&#39;6556&#39;</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf8&#39;</span><span class="p">))</span>
</code></pre></div><p>没有了异常或错误提示，说明解压成功了！我们再试一个错误密码：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">In</span> <span class="p">[</span><span class="mi">6</span><span class="p">]:</span> <span class="n">zFile</span><span class="o">.</span><span class="n">extractall</span><span class="p">(</span><span class="n">pwd</span><span class="o">=</span><span class="s1">&#39;5555&#39;</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf8&#39;</span><span class="p">))</span>
<span class="o">---------------------------------------------------------------------------</span>
<span class="ne">RuntimeError</span>                              <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">)</span>
<span class="o">&lt;</span><span class="n">ipython</span><span class="o">-</span><span class="nb">input</span><span class="o">-</span><span class="mi">6</span><span class="o">-</span><span class="mi">260</span><span class="n">b36a163cd</span><span class="o">&gt;</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span><span class="p">()</span>
<span class="o">----&gt;</span> <span class="mi">1</span> <span class="n">zFile</span><span class="o">.</span><span class="n">extractall</span><span class="p">(</span><span class="n">pwd</span><span class="o">=</span><span class="s1">&#39;5555&#39;</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf8&#39;</span><span class="p">))</span>

<span class="ne">RuntimeError</span><span class="p">:</span> <span class="n">Bad</span> <span class="n">password</span> <span class="k">for</span> <span class="n">file</span> <span class="o">&lt;</span><span class="n">ZipInfo</span> <span class="n">filename</span><span class="o">=</span><span class="s1">&#39;demo.md&#39;</span> <span class="n">filemode</span><span class="o">=</span><span class="s1">&#39;-rw-r--r--&#39;</span> <span class="n">file_size</span><span class="o">=</span><span class="mi">17</span> <span class="n">compress_size</span><span class="o">=</span><span class="mi">29</span><span class="o">&gt;</span>
</code></pre></div><p>可以看到如果密码错误会提示密码不对，所以可以使用<code>try-except</code>调用 <code>extractall</code> 方法验证密码是否正确，形如：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">zipfile</span>

<span class="n">zFile</span> <span class="o">=</span> <span class="n">zipfile</span><span class="o">.</span><span class="n">ZipFile</span><span class="p">(</span><span class="s1">&#39;demo.zip&#39;</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
    <span class="n">zFile</span><span class="o">.</span><span class="n">extractall</span><span class="p">(</span><span class="n">pwd</span><span class="o">=</span><span class="n">key</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf8&#39;</span><span class="p">))</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;find password -&gt; </span><span class="si">{</span><span class="n">key</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;bad password&#39;</span><span class="p">)</span>
</code></pre></div><h3 id="检查是否加密">检查是否加密</h3>
<p>检查压缩包是否加密这也是必要的，这里使用 <code>infolist</code> 方法可以查看加密文件信息^[<a href="https://stackoverflow.com/questions/12038446/how-to-check-if-a-zip-file-is-encrypted-using-pythons-standard-library-zipfile">How to check if a zip file is encrypted using python&rsquo;s standard library zipfile?</a>]，从而知道是否加密，如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">In</span> <span class="p">[</span><span class="mi">7</span><span class="p">]:</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">zFile</span><span class="o">.</span><span class="n">infolist</span><span class="p">():</span>
    <span class="o">...</span><span class="p">:</span>     <span class="k">if</span> <span class="n">i</span><span class="o">.</span><span class="n">flag_bits</span> <span class="o">&amp;</span> <span class="mh">0x01</span><span class="p">:</span>
    <span class="o">...</span><span class="p">:</span>         <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;is encrypted&#39;</span><span class="p">)</span>
    <span class="o">...</span><span class="p">:</span>
<span class="ow">is</span> <span class="n">encrypted</span>
</code></pre></div><h2 id="密码字典">密码字典</h2>
<p>这里使用 <a href="/2018/10/21/unzip-dict-hacker/">python3 生成密码字典</a> 一文中实现的 <a href="https://github.com/smslit/tools-with-script/blob/master/keykey/keykey">keykey</a> 工具生成数字组成的4位密码字典，使用keykey工具在当前目录生成密码字典文件 <code>keys.dict</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 keykey <span class="m">4</span> <span class="m">4</span> -c <span class="s1">&#39;0123456789&#39;</span> -o <span class="s1">&#39;keys.dict&#39;</span>
<span class="o">[</span>+<span class="o">]</span> 已生成 <span class="m">4</span> ~ <span class="m">4</span> 位密码字典 <span class="o">[</span>+<span class="o">]</span>
<span class="o">[</span>+<span class="o">]</span> 已保存密码字典到 /Users/5km/Documents/workspace/python/tools-with-script/crackzip/keys.dict <span class="o">[</span>+<span class="o">]</span>
</code></pre></div><p>最终生成密码字典文件 <a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/share/keys.dict"><strong>keys.dict</strong></a> 中，每行一个密码：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2018102315402664999086.png" alt="2018102315402664999086.png"></p>
<h1 id="实现">实现</h1>
<p>新建 <code>crackzip</code> 文件，添加以下内容，指明脚本解释器和导入 <code>zipfile</code> 库：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/bin/env python3</span>
<span class="kn">import</span> <span class="nn">zipfile</span>
<span class="kn">import</span> <span class="nn">argparse</span>
</code></pre></div><p>为文件 `` 添加运行权限：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ chmod +x crackzip
</code></pre></div><p>为了使程序模块化，需要对一些操作进行封装。</p>
<h2 id="破解操作">破解操作</h2>
<p>破解操作封装如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">crackzipfile</span><span class="p">(</span><span class="n">zipFile</span><span class="p">,</span> <span class="n">password</span><span class="p">):</span>
    <span class="s2">&#34;&#34;&#34;尝试破解 zip 文件
</span><span class="s2">    Args:
</span><span class="s2">        zipFile: 待解压 zip 实例
</span><span class="s2">        password: 密码
</span><span class="s2">    Return:
</span><span class="s2">        如果破解解压成功返回 True，如果出现异常返回 False，说明密码错误
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">zipFile</span><span class="o">.</span><span class="n">extractall</span><span class="p">(</span><span class="n">pwd</span><span class="o">=</span><span class="n">password</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf8&#39;</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">password</span>
    <span class="k">except</span><span class="p">:</span>
        <span class="k">return</span>
</code></pre></div><p>如果密码正确就会返回密码，反之返回 <code>None</code>。</p>
<h2 id="判断是否加密">判断是否加密</h2>
<p>使用 <a href="#检查是否加密">检查是否加密</a> 小节中的方法，封装为 <code>is_encrypted</code>方法如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">is_encrypted</span><span class="p">(</span><span class="n">zipFile</span><span class="p">):</span>
    <span class="s2">&#34;&#34;&#34;检查 zip 文件是否加密
</span><span class="s2">    Args:
</span><span class="s2">        zipFile: 待解压 zip 实例
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="k">for</span> <span class="n">info</span> <span class="ow">in</span> <span class="n">zipFile</span><span class="o">.</span><span class="n">infolist</span><span class="p">():</span>
        <span class="k">if</span> <span class="n">info</span><span class="o">.</span><span class="n">flag_bits</span> <span class="o">&amp;</span> <span class="mh">0x01</span><span class="p">:</span>
            <span class="k">return</span> <span class="kc">True</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="kc">False</span>
</code></pre></div><h2 id="获取命令参数">获取命令参数</h2>
<p>使用 <code>argparse</code> 库解析命令参数，这里也进行封装：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">getargs</span><span class="p">():</span>
    <span class="s2">&#34;&#34;&#34; 获取命令行参数
</span><span class="s2">    Return:
</span><span class="s2">        返回命令行参数解析结果
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s1">&#39;暴力破解 zip 文件的密码&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;zfile&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指明要破解的 zip 文件，可以是多个&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;dict&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定使用的密码词典&#39;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</code></pre></div><p>配置了两个占位参数，调用命令时，必须指定 zip 文件和密码字典文件。</p>
<h2 id="主函数">主函数</h2>
<p>整个破解流程封装到 <code>main</code> 方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">getargs</span><span class="p">()</span>
    <span class="k">with</span> <span class="n">zipfile</span><span class="o">.</span><span class="n">ZipFile</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">zfile</span><span class="p">)</span> <span class="k">as</span> <span class="n">zFile</span><span class="p">:</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">is_encrypted</span><span class="p">(</span><span class="n">zFile</span><span class="p">):</span>
            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">zfile</span><span class="si">}</span><span class="s1"> 未加密！&#39;</span><span class="p">)</span>
            <span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">dict</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
            <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">f</span><span class="o">.</span><span class="n">readlines</span><span class="p">():</span>
                <span class="n">key</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
                <span class="n">password</span> <span class="o">=</span> <span class="n">crackzipfile</span><span class="p">(</span><span class="n">zFile</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
                <span class="k">if</span> <span class="n">password</span><span class="p">:</span>
                    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;[+] 密码是 </span><span class="si">{</span><span class="n">password</span><span class="si">}</span><span class="s1"> [+]&#39;</span><span class="p">)</span>
                    <span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</code></pre></div><p>首先获取命令参数，因为要用到 zip 文件和密码字典文件的路径，打开 zip 文件，然后判断是否是加密的，如果不是加密的就打印提示信息，并结束程序；如果是加密的，就读取密码字典中的密码尝试解压，如果找到正确密码，打印信息并结束程序。</p>
<p>直接在执行 <code>main</code> 即可运行程序：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">main</span><span class="p">()</span>
</code></pre></div><p>完整代码参考：<a href="https://github.com/smslit/tools-with-script/blob/master/crackzip/crackzip">tools-with-script/crackzip/crackzip</a></p>
<h1 id="测试">测试</h1>
<p>目录下有一个加密的 zip 文件 <code>demo.zip</code> 和一个没有加密的 zip 文件 <code>demo1.zip</code>，测试如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./crackzip -h
usage: crackzip <span class="o">[</span>-h<span class="o">]</span> zfile dict

暴力破解 zip 文件的密码

positional arguments:
  zfile       指明要破解的 zip 文件，可以是多个
  dict        指定使用的密码词典

optional arguments:
  -h, --help  show this <span class="nb">help</span> message and <span class="nb">exit</span>
$ ./crackzip demo.zip keys.dict
<span class="o">[</span>+<span class="o">]</span> 密码是 <span class="m">6556</span> <span class="o">[</span>+<span class="o">]</span>
$ ./crackzip demo1.zip keys.dict
demo1.zip 未加密！
</code></pre></div><h1 id="总结">总结</h1>
<p>通过使用 zipfile 库和 argparse 库简单实现了破解 zip 文件密码的工具，也简单了解了字典式破解密码的基本原理，这种破解原理适用大部分的密码破解工作，但有时也必须考虑所需时间的合理性。</p>]]></content>
		</item>
		
		<item>
			<title>python3 生成密码字典</title>
			<link>https://blog.5km.studio/2018/10/21/unzip-dict-hacker/</link>
			<pubDate>Sun, 21 Oct 2018 06:58:33 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/10/21/unzip-dict-hacker/</guid>
			<description>&lt;p&gt;黑客破解密码通常会用到密码字典^[密码字典，是配合密码破译软件所使用的。密码字典里包括许多人们习惯性设置的密码，这样可以提高密码破译软件的密码破译成功率和命中率，缩短密码破译的时间。—— &lt;a href=&#34;https://baike.baidu.com/item/%E5%AF%86%E7%A0%81%E5%AD%97%E5%85%B8&#34;&gt;密码字典&lt;/a&gt;]，网上会看到各种各样的密码生成器，那么本文就一起用 python3 实现一个简单的密码生成器吧，就叫它 &lt;strong&gt;keykey&lt;/strong&gt; 吧！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181021154012962176683.png&#34; alt=&#34;20181021154012962176683.png&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>黑客破解密码通常会用到密码字典^[密码字典，是配合密码破译软件所使用的。密码字典里包括许多人们习惯性设置的密码，这样可以提高密码破译软件的密码破译成功率和命中率，缩短密码破译的时间。—— <a href="https://baike.baidu.com/item/%E5%AF%86%E7%A0%81%E5%AD%97%E5%85%B8">密码字典</a>]，网上会看到各种各样的密码生成器，那么本文就一起用 python3 实现一个简单的密码生成器吧，就叫它 <strong>keykey</strong> 吧！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181021154012962176683.png" alt="20181021154012962176683.png"></p>
<p>本文实现的密码生成器，非常简单和暴力，其实就是穷举^[<a href="https://www.jianshu.com/p/3efd3197ab35">Python - 自导自演 密码字典暴力破解</a>]，哈哈哈！</p>
<h1 id="知识点">知识点</h1>
<ul>
<li><a href="https://docs.python.org/3.7/library/itertools.html#module-itertools">itertools</a>，是 python 内建库，提供了很多操作迭代对象的函数接口，本文生成密码主要用到的就是 <code>product</code> 方法。</li>
<li><a href="https://docs.python.org/3.7/library/argparse.html#module-argparse">argparse</a>，这是用于解析命令参数的库，python 内建的，<a href="/2018/09/13/python-argparse/">argparse 库的使用</a> 介绍了基本使用方法，可以了解下。</li>
</ul>
<h1 id="实现">实现</h1>
<p>因为暴力生成密码是很简单的操作，所以只需跟着十里操作就明白怎么实现了。</p>
<h2 id="准备工作">准备工作</h2>
<p>本文实现使用的都是 python3 内建库，所以不需要安装第三方库。</p>
<ol>
<li>
<p>新建脚本文件<code>keykey</code>： <code>touch keykey</code></p>
</li>
<li>
<p>赋予文件运行权限：<code>chmod +x keykey</code></p>
</li>
<li>
<p>为脚本文件注明python解释器，文件中添加内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/bin/env python3</span>
</code></pre></div></li>
<li>
<p>导入需要用到的库，文件中：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">import</span> <span class="nn">itertools</span>
</code></pre></div></li>
</ol>
<h2 id="生成密码列表">生成密码列表</h2>
<p>下一步实现密码列表生成的方法 <code>getkeyslist</code>，文件中添加以下代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">getkeyslist</span><span class="p">(</span><span class="n">minlength</span><span class="p">,</span> <span class="n">maxlength</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="s1">&#39;0123456789&#39;</span><span class="p">):</span>
    <span class="s2">&#34;&#34;&#34;生成 minlength ~ maxlength 位密码列表，并返回列表
</span><span class="s2">    Args:
</span><span class="s2">        minlength: 密码最小位数
</span><span class="s2">        maxlength: 密码最大位数
</span><span class="s2">        choices: 候选字符，密码使用的字符从候选字符中得到
</span><span class="s2">    Returns:
</span><span class="s2">        keyslist: 密码列表
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="k">if</span> <span class="n">choices</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
        <span class="n">choices</span> <span class="o">=</span> <span class="s1">&#39;0123456789&#39;</span>
    <span class="n">keyslist</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">minlength</span><span class="p">,</span> <span class="n">maxlength</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
        <span class="n">keyiter</span> <span class="o">=</span> <span class="n">itertools</span><span class="o">.</span><span class="n">product</span><span class="p">(</span><span class="n">choices</span><span class="p">,</span> <span class="n">repeat</span><span class="o">=</span><span class="n">i</span><span class="p">)</span>
        <span class="n">keys</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">c</span><span class="p">)</span><span class="o">+</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">keyiter</span><span class="p">]</span>
        <span class="n">keyslist</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;[+] 已生成 </span><span class="si">{</span><span class="n">minlength</span><span class="si">}</span><span class="s1"> ~ </span><span class="si">{</span><span class="n">maxlength</span><span class="si">}</span><span class="s1"> 位密码字典 [+]&#39;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">keyslist</span>
</code></pre></div><p>其中:</p>
<ul>
<li>
<p><code>itertools.product(choices, repeat=i)</code> 生成一个迭代器，迭代器每次迭代会从 <code>choices</code> 中提取字符组成包含 <code>i</code> 个字符的 tuple ，每个 tuple 中的元素为单个字符，如下的样子，迭代器会枚举所有可能的 <code>i</code> 个字符组合。</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="p">(</span><span class="s1">&#39;0&#39;</span><span class="p">,</span> <span class="s1">&#39;1&#39;</span><span class="p">,</span> <span class="s1">&#39;2&#39;</span><span class="p">,</span> <span class="s1">&#39;3&#39;</span><span class="p">)</span>
</code></pre></div></li>
<li>
<p><code>[''.join(c)+'\n' for c in keyiter]</code> 会使用字符串的 <code>join</code> 方法将迭代生成的 tuple 中的字符练成字符串生成长度为 上述的 <code>i</code> 长度的密码。</p>
</li>
<li>
<p>for 循环是为了生成位数在 minlength ~ maxlength 之间左右可能的组合的密码。</p>
</li>
</ul>
<h2 id="保存密码列表">保存密码列表</h2>
<p>生成了密码列表还需要保存的，所以封装一个保存密码列表的函数：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">save_keys_dict</span><span class="p">(</span><span class="n">keyslist</span><span class="p">,</span> <span class="n">outfile</span><span class="p">):</span>
    <span class="s2">&#34;&#34;&#34;保存密码字典
</span><span class="s2">    Args:
</span><span class="s2">        keyslist: 密码列表
</span><span class="s2">        outfile: 要保存的密码字典文件
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="k">if</span> <span class="n">outfile</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
        <span class="n">homepath</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">expanduser</span><span class="p">(</span><span class="s1">&#39;~&#39;</span><span class="p">)</span>
        <span class="n">outfile</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">homepath</span><span class="p">,</span> <span class="s1">&#39;Desktop/keys.txt&#39;</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">outfile</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">outfile</span><span class="p">)</span>
    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">outfile</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="k">for</span> <span class="n">keys</span> <span class="ow">in</span> <span class="n">keyslist</span><span class="p">:</span>
            <span class="n">f</span><span class="o">.</span><span class="n">writelines</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;[+] 已保存密码字典到 </span><span class="si">{</span><span class="n">outfile</span><span class="si">}</span><span class="s1"> [+]&#39;</span><span class="p">)</span>
</code></pre></div><p>这个没什么好说的，就是使用 <code>with</code> 语法实现安全的文件写入操作，将密码列表写入到指定文件中，为了防止指定文件为空，这里会使用 <code>~/Desktop/keys.txt</code> 。</p>
<h2 id="获取命令参数">获取命令参数</h2>
<p>使用 <code>argparse</code> 库解析命令参数，这里将相关操作封装到函数 <code>getargs</code> 中，最终返回参数信息：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">getargs</span><span class="p">():</span>
    <span class="s2">&#34;&#34;&#34;获取命令参数
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s1">&#39;用于生成指定长度和字符空间的密码字典&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;minlength&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;用于指定生成密码的最小位数&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;maxlength&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;用于指定生成密码的最大位数&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-o&#39;</span><span class="p">,</span> <span class="s1">&#39;--outfile&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定字典文件的保存路径&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-c&#39;</span><span class="p">,</span> <span class="s1">&#39;--choices&#39;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s1">&#39;0123456789&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定字符串，密码中的字符从字符串中选择，默认为 &#34;0123456789&#34;&#39;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</code></pre></div><p>命令参数设计详情如下表：</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>类型</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>minlength</td>
<td>int</td>
<td>用于指定生成密码的最小位数</td>
</tr>
<tr>
<td>maxlength</td>
<td>int</td>
<td>用于指定生成密码的最大位数</td>
</tr>
<tr>
<td>-o, &ndash;outfile</td>
<td>文件路径</td>
<td>指定字典文件的保存路径</td>
</tr>
<tr>
<td>-c, &ndash;choices</td>
<td>字符串</td>
<td>指定字符串，密码中的字符从字符串中选择，默认为 &ldquo;0123456789&rdquo;</td>
</tr>
</tbody>
</table>
<h2 id="main实现">main实现</h2>
<p>最终整合一下，实现 main：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">getargs</span><span class="p">()</span>
    <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">minlength</span> <span class="o">&lt;</span> <span class="n">args</span><span class="o">.</span><span class="n">maxlength</span><span class="p">:</span>
        <span class="n">keyslist</span> <span class="o">=</span> <span class="n">getkeyslist</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">minlength</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">maxlength</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">choices</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">keyslist</span> <span class="o">=</span> <span class="n">getkeyslist</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">maxlength</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">minlength</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">choices</span><span class="p">)</span>
    <span class="n">save_keys_dict</span><span class="p">(</span><span class="n">keyslist</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">outfile</span><span class="p">)</span>
</code></pre></div><p>其中， <code>if</code> 是为了防止 <code>minlength</code> 比 <code>minlength</code> 大的情况。</p>
<p>完整代码参考：<a href="https://github.com/smslit/tools-with-script/blob/master/keykey/keykey">tools-with-script/keykey/keykey</a></p>
<h1 id="使用">使用</h1>
<h2 id="获取工具帮助信息">获取工具帮助信息</h2>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$  ./keykey -h
usage: keykey <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-o OUTFILE<span class="o">]</span> <span class="o">[</span>-c CHOICES<span class="o">]</span> minlength maxlength

用于生成指定长度和字符空间的密码字典

positional arguments:
  minlength             用于指定生成密码的最小位数
  maxlength             用于指定生成密码的最大位数

optional arguments:
  -h, --help            show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -o OUTFILE, --outfile OUTFILE
                        指定字典文件的保存路径
  -c CHOICES, --choices CHOICES
                        指定字符串，密码中的字符从字符串中选择，默认为 <span class="s2">&#34;0123456789&#34;</span>
</code></pre></div><h2 id="生成-46-位数字密码">生成 4～6 位数字密码</h2>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./keykey <span class="m">4</span> <span class="m">6</span>
<span class="o">[</span>+<span class="o">]</span> 已生成 <span class="m">4</span> ~ <span class="m">6</span> 位密码字典 <span class="o">[</span>+<span class="o">]</span>
<span class="o">[</span>+<span class="o">]</span> 已保存密码字典到 /Users/5km/Desktop/keys.txt <span class="o">[</span>+<span class="o">]</span>
$ head -n <span class="m">5</span> /Users/5km/Desktop/keys.txt
<span class="m">0000</span>
<span class="m">0001</span>
<span class="m">0002</span>
<span class="m">0003</span>
<span class="m">0004</span>
</code></pre></div><h2 id="指定密码字典保存文件">指定密码字典保存文件</h2>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./keykey <span class="m">4</span> <span class="m">6</span> -o keys.txt
<span class="o">[</span>+<span class="o">]</span> 已生成 <span class="m">4</span> ~ <span class="m">6</span> 位密码字典 <span class="o">[</span>+<span class="o">]</span>
<span class="o">[</span>+<span class="o">]</span> 已保存密码字典到 /Users/5km/Documents/workspace/python/tools-with-script/keykey/keys.txt <span class="o">[</span>+<span class="o">]</span>
$ head -n <span class="m">5</span> keys.txt
<span class="m">0000</span>
<span class="m">0001</span>
<span class="m">0002</span>
<span class="m">0003</span>
<span class="m">0004</span>
</code></pre></div><h2 id="指定密码可选择字符">指定密码可选择字符</h2>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./keykey <span class="m">4</span> <span class="m">6</span> -o keys.txt -c <span class="s1">&#39;asdfghjkl&#39;</span>
<span class="o">[</span>+<span class="o">]</span> 已生成 <span class="m">4</span> ~ <span class="m">6</span> 位密码字典 <span class="o">[</span>+<span class="o">]</span>
<span class="o">[</span>+<span class="o">]</span> 已保存密码字典到 /Users/5km/Documents/workspace/python/tools-with-script/keykey/keys.txt <span class="o">[</span>+<span class="o">]</span>
$ head -n <span class="m">5</span> keys.txt
aaaa
aaas
aaad
aaaf
aaag
</code></pre></div><h1 id="总结">总结</h1>
<p>一切顺利的话，到目前就实现了一个简单的密码字典生成器。本文复习了 <code>itertools</code> 和 <code>argparse</code> 的知识和部分使用方法，希望对您有所帮助！感谢您的阅读！</p>]]></content>
		</item>
		
		<item>
			<title>图中藏(cáng)语</title>
			<link>https://blog.5km.studio/2018/10/20/codeimg-python/</link>
			<pubDate>Sat, 20 Oct 2018 10:09:31 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/10/20/codeimg-python/</guid>
			<description>&lt;p&gt;今天分享一个好玩工具，这个工具可以往图片中藏入信息，当然也可以从藏有信息的图片中将信息找出来。专业名词的话，这叫图片隐写技术，但无所谓了，我们知道是咋回事儿就好。本文将介绍如何使用 python3 实现这个工具。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181020154000344643954.png&#34; alt=&#34;20181020154000344643954.png&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天分享一个好玩工具，这个工具可以往图片中藏入信息，当然也可以从藏有信息的图片中将信息找出来。专业名词的话，这叫图片隐写技术，但无所谓了，我们知道是咋回事儿就好。本文将介绍如何使用 python3 实现这个工具。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181020154000344643954.png" alt="20181020154000344643954.png"></p>
<h1 id="原理">原理</h1>
<p>在实现工具之前，先了解一下原理。</p>
<h2 id="隐写术">隐写术</h2>
<blockquote>
<p>隐写术是一门关于信息隐藏的技巧与科学，所谓信息隐藏指的是不让除预期的接收者之外的任何人知晓信息的传递事件或者信息的内容。</p>
</blockquote>
<blockquote>
<p>—— <a href="https://zh.wikipedia.org/wiki/%E9%9A%90%E5%86%99%E6%9C%AF">隐写术</a> <a href="https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5">@维基百科</a></p>
</blockquote>
<p>可以类比一下无线电，无线电传递信息的原理可以简单地理解为：发射端在载波上注入高频信号，而接收端收到信号后解析载波上的高频信号，从而实现信息的无线传输，其中载波的波长远大于载入信号的波长。同理图片隐写术中图片就是信息载体文件，载体文件（cover file）相对隐秘文件的大小（指数据含量，以比特计）越大，隐藏后者就越加容易。</p>
<h2 id="数据存储">数据存储</h2>
<p>这里选择位图图片作为载体，一张8位RGB的 JPG 位图中的一个像素包含 R、G、B 三个通道的数值，每个通道数值为8位数据，也就是 <code>0b00000000</code>～<code>0b11111111</code> 的数值，想象一下其中一个通道，就以 R 通道来讲，<code>0b11101111</code> 和 <code>0b11101110</code> 只是最低位不一样，实际图片的表现上，这种差别肉眼几乎是无法察觉的，所以可以利用每个通道的最低位来存储 1bit 的数据是可行的^[更正式一点地说，使隐写的信息难以探测的，也就是保证“有效载荷”（需要被隐蔽的信号）对“载体”（即原始的信号）的调制对载体的影响看起来（理想状况下甚至在统计上）可以忽略。这就是说，这种改变应该无法与载体中的噪声加以区别。从信息论的观点来看，这就是说信道的容量必须大于传输“表面上”的信号的需求。这就叫做信道的冗余。「来自<a href="https://zh.wikipedia.org/wiki/%E9%9A%90%E5%86%99%E6%9C%AF"><strong>隐写术</strong></a>」]。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181020154001752117286.png" alt="20181020154001752117286.png"></p>
<p>如此的话每个像素三个通道共可以存储 3 bits，那如果像 png 图片一样再多一个 Alpha 通道（透明通道），就可以存储 4 bits，两个像素就可以存储一个字节，方便处理。另外注意的是，我们这里存储和读取字节数据均采用 <a href="https://baike.baidu.com/item/Little-Endian/4118225?fr=aladdin"><strong>Little-Endian</strong></a>方式^[（小字节序、低字节序）,即低位字节排放在内存的低地址端，高位字节排放在内存的高地址端。 与之对应的是：<strong>BIG-ENDIAN</strong>（大字节序、高字节序）]，后面写程序的时候要清楚！</p>
<h1 id="实现">实现</h1>
<p>在 <code>messageimage.py</code> 文件中为图片处理实现一个类 <code>MessageImage</code> ，然后编写一个 python 脚本命令行工具，保存为文件 <code>codeimg</code>。</p>
<h2 id="messageimage-类"><strong>MessageImage</strong> 类</h2>
<p>本文实现的类中，涉及了 <strong>pillow</strong> 库的使用，字符串的编码转换等知识。</p>
<h3 id="问题分析">问题分析</h3>
<p>小节 <a href="#数据存储">原理-数据存储</a> 中介绍了隐写信息在图片中的存储方式，数据写入和读取要参考这个原理，但是这里还需要考虑两个问题，针对这两个问题，分别提出解决方法，剩下的就是代码实现的问题了。</p>
<h4 id="图片没有alpha通道问题">图片没有alpha通道问题</h4>
<p>对于不含透明通道的位图图片（比如jpg）来说，少一个 Alpha 通道，那么 <a href="#数据存储">原理-数据存储</a> 提到的四通道存储方案显然缺少通道，其实解决很简单，只需要使用 <strong>pillow</strong> 库将 jpg 图片数据转换为带 alpha 通道的数据即可进行，可使用如下的方法转换：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>
<span class="n">img</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s1">&#39;demo.jpg&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="s1">&#39;RGBA&#39;</span><span class="p">)</span>
</code></pre></div><p>直接转换为 RGBA 模式即可。</p>
<h4 id="怎么判断图片有没有隐写信息问题">怎么判断图片有没有隐写信息问题</h4>
<p>为了判断图片有没有隐写信息，这里设计一个字节数据包存储协议，协议很简单，如下表：</p>
<table>
<thead>
<tr>
<th>head</th>
<th>length</th>
<th>data</th>
</tr>
</thead>
<tbody>
<tr>
<td>0x5555AAAA</td>
<td>数据长度</td>
<td>数据</td>
</tr>
<tr>
<td>占 4 字节</td>
<td>占 4 字节</td>
<td>占 length 字节</td>
</tr>
</tbody>
</table>
<p>最开始的4个字节为标记，再之后的四字节表示图片中信息的字节数目，之后则是 length 个字节的主要数据。所以判断有没有隐写信息的流程应该是：</p>
<ol>
<li>从图片中前16个像素数据中，解析出8个字节；</li>
<li>判断前4个字节与预设的 head 是不是一致，如果一致则说明有隐写信息；</li>
</ol>
<p>当然，head 是可以任意定义的，通过此方法，判断有没有隐写信息出错的概率为：</p>
<p>$$
p = \frac{1}{2^{32}} = 0.000000000232831
$$</p>
<h3 id="准备工作">准备工作</h3>
<ol>
<li>
<p>这里使用的第三方库是图片处理库 <strong>pillow</strong>，安装很简单：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pip3 install pillow
</code></pre></div></li>
<li>
<p>新建文件 <code>messageimage.py</code></p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ touch messageimage
</code></pre></div></li>
<li>
<p>导入需要的库，向文件 <code>messageimage.py</code> 中添加以下内容</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>
</code></pre></div></li>
<li>
<p>声明类 MessageImage，文件 <code>messageimage.py</code> 中添加:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">MessageImage</span><span class="p">:</span>
    <span class="s2">&#34;&#34;&#34; MessageImage 类
</span><span class="s2">    初始化加载图片或者手动打开图片，将隐写信息写入图片，也可以解析图片中的隐写信息
</span><span class="s2">    Attributes:
</span><span class="s2">        image: 打开图片的 Image 类型对象
</span><span class="s2">        pkghead: 写入图片隐写信息的头标记，可以初始化时赋值
</span><span class="s2">    &#34;&#34;&#34;</span>
</code></pre></div><p>剩下的就是添加类的方法了。</p>
</li>
</ol>
<h3 id="公共方法实现">公共方法实现</h3>
<p>为类实现下面几个公共方法：</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>描述</th>
<th>参数</th>
<th>返回值</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>__init__(self, imgpath, pkghead=None)</code></td>
<td>初始化类，生成 MessageImage 实例</td>
<td><strong>imgpath</strong>：图片路径；<strong>pkghead</strong>：自定义协议头</td>
<td>无</td>
</tr>
<tr>
<td><code>encode(self, info, save=True, show=False)</code></td>
<td>将指定的隐写信息写入图片</td>
<td><strong>info</strong>：隐写信息（字符串）；<strong>save</strong>：控制是否将写入信息的图片进行保存(默认 <strong>True</strong>)；<strong>show</strong>：控制是否在写入信息后显示结果图片(默认 <strong>False</strong>)</td>
<td>写入成功就返回 <strong>True</strong>，失败返回 <strong>False</strong></td>
</tr>
<tr>
<td><code>decode(self)</code></td>
<td>解析图片中的隐写信息</td>
<td>无</td>
<td>如果解析成功，返回隐写信息，其它情况返回 <strong>None</strong></td>
</tr>
<tr>
<td><code>open(self, imgpath)</code></td>
<td>打开指定图片，转换数据为 RGBA 模式数据赋给属性 <strong>image</strong></td>
<td><strong>imgpath</strong>：图片路径</td>
<td>无</td>
</tr>
<tr>
<td><code>freespace(self)</code></td>
<td>计算图片中可用空间，单位 byte</td>
<td>无</td>
<td>返回可用空间大小，单位字节</td>
</tr>
<tr>
<td><code>sethead(self, pkghead)</code></td>
<td>设置协议头</td>
<td><strong>pkghead</strong>：自定义协议头</td>
<td>无</td>
</tr>
</tbody>
</table>
<h4 id="初始化">初始化</h4>
<p>类的初始化，主要是根据指定图片和指定存储协议头初始化基本属性：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">imgpath</span><span class="p">,</span> <span class="n">pkghead</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">imgpath</span><span class="p">:</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">imgpath</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">image</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="k">if</span> <span class="n">pkghead</span><span class="p">:</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">pkghead</span> <span class="o">=</span> <span class="n">pkghead</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">pkghead</span> <span class="o">=</span> <span class="mh">0x5555AAAA</span>
</code></pre></div><p>上述操作中用到了 <a href="#open方法"><strong>open</strong></a> 方法。</p>
<h4 id="encode方法">encode方法</h4>
<p>encode 方法的思路依赖于 <a href="#数据存储">原理-数据存储</a> 和 <a href="#问题分析">问题分析</a>，实现思路为：</p>
<ol>
<li>使用私有方法 <a href="#__pack">__pack</a> 打包指定的隐写信息，得到一个完整的协议报数据；</li>
<li><a href="#__putdata">__putdata</a> 将协议包数据写入图片数据，得到新的图片数据；</li>
</ol>
<p>实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">encode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">info</span><span class="p">,</span> <span class="n">save</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">show</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
    <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">image</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;图片为空，请调用实例的 open 方法打开一张图片！&#39;</span><span class="p">)</span>
        <span class="k">return</span> <span class="kc">False</span>
    <span class="n">pkgbytes</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">__pack</span><span class="p">(</span><span class="n">info</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">__putdata</span><span class="p">(</span><span class="n">pkgbytes</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">save</span><span class="p">:</span>
        <span class="n">pathlist</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">imgpath</span><span class="p">)</span>
        <span class="n">newpath</span> <span class="o">=</span> <span class="n">pathlist</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s1">&#39;_code&#39;</span> <span class="o">+</span> <span class="s1">&#39;.png&#39;</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">newpath</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s1">&#39; &#39;</span><span class="p">)</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">image</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">newpath</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">show</span><span class="p">:</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">image</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
    <span class="k">return</span> <span class="kc">True</span>
</code></pre></div><h5 id="__pack">__pack</h5>
<p>这个方法主要是按照 <a href="#怎么判断图片有没有隐写信息问题">怎么判断图片有没有隐写信息问题</a> 小节中提到的协议进行打包，具体实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">__pack</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">info</span><span class="p">):</span>
    <span class="s2">&#34;&#34;&#34;
</span><span class="s2">    打包数据，转换为待写入图片的字节数据组，并返回
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="n">tagbytes</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">pkghead</span><span class="o">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">&#39;little&#39;</span><span class="p">)</span>
    <span class="n">databytes</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">(</span><span class="n">info</span><span class="p">,</span> <span class="s1">&#39;utf8&#39;</span><span class="p">)</span>
    <span class="n">lengthbytes</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">databytes</span><span class="p">)</span><span class="o">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">&#39;little&#39;</span><span class="p">)</span>
    <span class="n">pkgbytes</span> <span class="o">=</span> <span class="n">tagbytes</span> <span class="o">+</span> <span class="n">lengthbytes</span> <span class="o">+</span> <span class="n">databytes</span>
    <span class="k">return</span> <span class="n">pkgbytes</span>
</code></pre></div><p>这里有两点关键：</p>
<ul>
<li>
<p>使用 <code>int</code> 的 <code>to_bytes</code> 方法，将整数转换成 4 字节数组^[<a href="https://www.delftstack.com/zh/howto/python/how-to-convert-int-to-bytes-in-python-2-and-python-3/">如何将整型int转换为字节bytes</a>]</p>
</li>
<li>
<p>使用 <code>utf8</code> 编码方式将指定隐写信息转换为字节数组^[<a href="https://blog.csdn.net/idealcitier/article/details/80007598">Python实现字符串与字节之间的相互转换</a>]</p>
</li>
</ul>
<h5 id="__putdata">__putdata</h5>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">__putdata</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pkgbytes</span><span class="p">):</span>
    <span class="n">pixels</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">image</span><span class="o">.</span><span class="n">getdata</span><span class="p">())</span>
    <span class="n">freespace</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">freespace</span><span class="p">()</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">pkgbytes</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">freespace</span><span class="p">:</span>  <span class="c1"># 超出全部数据空间， 抛出异常</span>
        <span class="k">raise</span> <span class="ne">Exception</span><span class="p">(</span><span class="s2">&#34;错误: 不能载入超过 &#34;</span> <span class="o">+</span> <span class="n">freespace</span> <span class="o">+</span> <span class="s2">&#34; 字节的数据到图片中。&#34;</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">byte</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">pkgbytes</span><span class="p">):</span>
        <span class="n">_index</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">index</span>
        <span class="n">pixels</span><span class="p">[</span><span class="n">_index</span><span class="p">]</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">([(</span><span class="n">v</span> <span class="o">&amp;</span> <span class="mh">0xFE</span><span class="p">)</span> <span class="o">|</span> <span class="p">(((</span><span class="n">byte</span> <span class="o">&amp;</span> <span class="mh">0x0F</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="n">i</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x01</span><span class="p">)</span>  <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">pixels</span><span class="p">[</span><span class="n">_index</span><span class="p">])])</span>
        <span class="n">_index</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="n">pixels</span><span class="p">[</span><span class="n">_index</span><span class="p">]</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">([(</span><span class="n">v</span> <span class="o">&amp;</span> <span class="mh">0xFE</span><span class="p">)</span> <span class="o">|</span> <span class="p">(((</span><span class="n">byte</span> <span class="o">&gt;&gt;</span> <span class="mi">4</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="n">i</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x01</span><span class="p">)</span>  <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">pixels</span><span class="p">[</span><span class="n">_index</span><span class="p">])])</span>
    <span class="n">newimg</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">image</span><span class="o">.</span><span class="n">mode</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">image</span><span class="o">.</span><span class="n">size</span><span class="p">)</span>
    <span class="n">newimg</span><span class="o">.</span><span class="n">putdata</span><span class="p">(</span><span class="n">pixels</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">image</span> <span class="o">=</span> <span class="n">newimg</span>
</code></pre></div><p>简单说一下上面实现的过程：</p>
<ol>
<li>
<p>得到包含像素数据的列表，这个列表的元素是 <code>tuple</code>，<code>tuple</code> 包含的四个数据就是对应像素的 R、G、B、A 四个通道的数值；</p>
</li>
<li>
<p>为了处理待写入数据超过图片可存空间的情况，这里调用 <a href="#freespace方法">freespace</a> 获取可用空间与待写数据大小比较，如果超出就发起异常！</p>
</li>
<li>
<p>如果空间足够，就将隐写数据写入到像素数据中，这一部分实现在 <code>for</code> 循环中，枚举隐写数据中的字节，每个字节需要写入两个像素数据，低位在前原则，这里解释一下下面的语句：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="nb">tuple</span><span class="p">([(</span><span class="n">v</span> <span class="o">&amp;</span> <span class="mh">0xFE</span><span class="p">)</span> <span class="o">|</span> <span class="p">(((</span><span class="n">byte</span> <span class="o">&amp;</span> <span class="mh">0x0F</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="n">i</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x01</span><span class="p">)</span>  <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">pixels</span><span class="p">[</span><span class="n">_index</span><span class="p">])])</span>
<span class="nb">tuple</span><span class="p">([(</span><span class="n">v</span> <span class="o">&amp;</span> <span class="mh">0xFE</span><span class="p">)</span> <span class="o">|</span> <span class="p">(((</span><span class="n">byte</span> <span class="o">&gt;&gt;</span> <span class="mi">4</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="n">i</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x01</span><span class="p">)</span>  <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">pixels</span><span class="p">[</span><span class="n">_index</span><span class="p">])])</span>
</code></pre></div><ul>
<li><code>i</code> 和 <code>v</code> 分别是像素数据中通道数据的索引和值；</li>
<li><code>v &amp; 0xFE</code> ：将通道数据的最低位置0；</li>
<li><code>((byte &amp; 0x0F) &gt;&gt; i) &amp; 0x01</code> ：获取待写入字节低4位中的每一位数据，最终得到 0 或 1</li>
<li><code>((byte &gt;&gt; 4) &gt;&gt; i) &amp; 0x01</code> ：获取待写入字节高4位中的每一位数据，最终得到 0 或 1</li>
</ul>
</li>
<li>
<p>根据写入隐写信息图片数据创建新的图片对象赋值给属性 <code>image</code></p>
</li>
</ol>
<h4 id="decode方法">decode方法</h4>
<p>这个方法用来解析图片中的隐写信息，实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">decode</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">image</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;图片为空，请调用实例的 open 方法打开一张图片！&#39;</span><span class="p">)</span>
        <span class="k">return</span> <span class="kc">None</span>        
    <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">__unpack</span><span class="p">()</span>
</code></pre></div><p>直接关键是调用了解包函数 <a href="#__unpack">__unpack</a>，返回的是它的结果。</p>
<h5 id="__unpack">__unpack</h5>
<p>用于从图片中检查是否包含隐写数据并解析隐写数据，实现：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">__unpack</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="n">pkgbytes</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">__getdata</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">8</span><span class="p">)</span>
    <span class="n">pkghead</span> <span class="o">=</span> <span class="nb">int</span><span class="o">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">pkgbytes</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">4</span><span class="p">],</span> <span class="s1">&#39;little&#39;</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">pkghead</span> <span class="o">==</span> <span class="bp">self</span><span class="o">.</span><span class="n">pkghead</span><span class="p">:</span>
        <span class="n">length</span> <span class="o">=</span> <span class="nb">int</span><span class="o">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">pkgbytes</span><span class="p">[</span><span class="mi">4</span><span class="p">:</span><span class="mi">8</span><span class="p">],</span> <span class="s1">&#39;little&#39;</span><span class="p">)</span>
        <span class="n">pkgbytes</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">__getdata</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">8</span> <span class="o">+</span> <span class="n">length</span><span class="p">)</span>
        <span class="n">info</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">pkgbytes</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf8&#39;</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">info</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">return</span> <span class="kc">None</span>
</code></pre></div><ol>
<li>首先通过 <a href="#__getdata">__getdata</a> 方法从图片数据中获取存储的前8个字节数据；</li>
<li>使用 <code>int</code> 的 <code>from_bytes</code> 方法^[<a href="https://python3-cookbook.readthedocs.io/zh_CN/latest/c03/p05_pack_unpack_large_int_from_bytes.html">字节到大整数的打包与解包</a>]得到前4个字节代表的协议头；</li>
<li>比较读取的协议头是否与预设的协议头一致，若一致则进行下面的步骤，若不一致则返回结果 <code>None</code></li>
<li>同样使用 <code>int</code> 的 <code>from_bytes</code> 方法从之后的4个字节得到隐写数据的长度；</li>
<li>根据长度调用 <a href="#__getdata">__getdata</a> 方法从图片中获取隐写数据；</li>
<li>将得到的字节数组转换为编码为 <code>utf8</code> 的字符串；</li>
</ol>
<h5 id="__getdata">__getdata</h5>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">__getdata</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">stop</span><span class="p">):</span>
    <span class="n">pixels</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">image</span><span class="o">.</span><span class="n">getdata</span><span class="p">())</span>
    <span class="n">pkgbytes</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">stop</span><span class="p">):</span>
        <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">a</span><span class="p">)</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">([</span><span class="n">v</span> <span class="o">&amp;</span> <span class="mh">0x01</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">pixels</span><span class="p">[</span><span class="mi">2</span> <span class="o">*</span> <span class="n">i</span><span class="p">]])</span>
        <span class="n">v</span> <span class="o">=</span> <span class="n">r</span> <span class="o">|</span> <span class="p">(</span><span class="n">g</span> <span class="o">&lt;&lt;</span> <span class="mi">1</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">b</span> <span class="o">&lt;&lt;</span> <span class="mi">2</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">a</span> <span class="o">&lt;&lt;</span> <span class="mi">3</span><span class="p">)</span>
        <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">a</span><span class="p">)</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">([</span><span class="n">v</span> <span class="o">&amp;</span> <span class="mh">0x01</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">pixels</span><span class="p">[</span><span class="mi">2</span> <span class="o">*</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]])</span>
        <span class="n">v</span> <span class="o">|=</span> <span class="p">(</span><span class="n">r</span> <span class="o">&lt;&lt;</span> <span class="mi">4</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">g</span> <span class="o">&lt;&lt;</span> <span class="mi">5</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">b</span> <span class="o">&lt;&lt;</span> <span class="mi">6</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">a</span> <span class="o">&lt;&lt;</span> <span class="mi">7</span><span class="p">)</span>
        <span class="n">pkgbytes</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">v</span><span class="o">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">&#39;little&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">])</span>
    <span class="k">return</span> <span class="n">pkgbytes</span>  
</code></pre></div><p>这个方法可以从图片数据中获取像素存储的位数据并组成字节数据。其中：</p>
<ul>
<li><code>start</code>：要读取字节的开始位置</li>
<li><code>stop</code>：要读取字节的结束位置</li>
</ul>
<p>所以实现中，<code>for</code> 的每一次循环读取两个像素点数据，组得一个字节数据，上述代码第 5 和第 7 行代码，得到的是每个通道的最低位数值。第 9 行代码将得到的数值转换为单字节数据。</p>
<h4 id="open方法">open方法</h4>
<p>这个方法用来打开新的图片获取 RGBA 模式的图片数据覆盖原来的属性 <code>image</code>，并记录图片路径到属性 <code>imgpath</code>，实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python">    <span class="k">def</span> <span class="nf">open</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">imgpath</span><span class="p">):</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">image</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">imgpath</span><span class="p">)</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="s1">&#39;RGBA&#39;</span><span class="p">)</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">imgpath</span> <span class="o">=</span> <span class="n">imgpath</span>
        <span class="k">except</span><span class="p">:</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">image</span> <span class="o">=</span> <span class="kc">None</span>
</code></pre></div><h4 id="freespace方法">freespace方法</h4>
<p>这个方法用来计算图片可存隐写数据的空间大小，单位是字节，实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">freespace</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="s2">&#34;&#34;&#34;
</span><span class="s2">    查看图片存储隐藏数据的可用空间，单位 byte
</span><span class="s2">    &#34;&#34;&#34;</span>
    <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">image</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;图片为空，请调用实例的 open 方法打开一张图片！&#39;</span><span class="p">)</span>
        <span class="k">return</span> <span class="mi">0</span>
    <span class="n">size</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">image</span><span class="o">.</span><span class="n">size</span>
    <span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">size</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">size</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>
</code></pre></div><p>像素个数的一半就是可存数据的空间大小，因为两个像素存一个字节数据。</p>
<h4 id="sethead方法">sethead方法</h4>
<p>有时可能需要自定义协议头，所以设计这个方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">sethead</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pkghead</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">pkghead</span><span class="p">:</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">pkghead</span> <span class="o">=</span> <span class="n">pkghead</span>
</code></pre></div><h3 id="类测试">类测试</h3>
<p>最终实现了 <code>MessageImage</code> 类。需要测试一下，添加代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">mi</span> <span class="o">=</span> <span class="n">MessageImage</span><span class="p">(</span><span class="s1">&#39;demo.png&#39;</span><span class="p">)</span>
    <span class="n">mi</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;你好，世界！hello, world!&#39;</span><span class="p">)</span>
    <span class="n">mi</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s1">&#39;demo_code.png&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">mi</span><span class="o">.</span><span class="n">decode</span><span class="p">())</span>
</code></pre></div><p>这里以图片 <a href="https://github.com/smslit/tools-with-script/blob/master/codeimg/demo.png">demo.png</a> 为例，如果最终打印了解析结果，就说明 <code>MessageImage</code> 类搞定了，类似于：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python messageimage.py
demo_code.png 你好，世界！hello, world!
</code></pre></div><p>完整代码参考 <a href="https://github.com/smslit/tools-with-script/blob/master/codeimg/messageimage.py">tools-with-script/codeimg/messageimage.py</a></p>
<h2 id="命令行工具">命令行工具</h2>
<p>有了 MessageImage 类，命令行工具就好实现了。</p>
<h3 id="准备工作-1">准备工作</h3>
<ol>
<li>
<p>新建文件：<code>touch codeimg</code>，并为文件添加运行权限 <code>chmod +x codeimg</code></p>
</li>
<li>
<p>文件添加以下内容指定运行解释器：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/bin/env python3</span>
</code></pre></div></li>
<li>
<p>这里使用 MessageImage 类和 argparse，需要导入库，文件添加：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">from</span> <span class="nn">messageimage</span> <span class="kn">import</span> <span class="n">MessageImage</span>
</code></pre></div></li>
</ol>
<h3 id="获取参数方法">获取参数方法</h3>
<p>封装获取命令行参数的方法为 <code>getargs</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">getargs</span><span class="p">():</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;为图片写入隐藏信息，或者从包含隐藏信息的图片中获取信息。&#34;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;command&#39;</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;encode&#39;</span><span class="p">,</span> <span class="s1">&#39;decode&#39;</span><span class="p">,</span> <span class="s1">&#39;check&#39;</span><span class="p">],</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定子命令。encode -&gt; 往图片中写入隐藏信息；decode -&gt; 读取图片中的隐藏信息；check -&gt; 查看图片可存空间(单位 byte)&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-i&#39;</span><span class="p">,</span> <span class="s1">&#39;--info&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定要写入的隐藏信息，子命令为 encode 时有效&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;imgpath&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定要处理的图片&#39;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</code></pre></div><p>设置三个子命令：</p>
<ul>
<li>encode : 往图片中写入隐藏信息；</li>
<li>decode : 读取图片中的隐藏信息；</li>
<li>check : 查看图片可存空间(单位 byte)</li>
</ul>
<p><code>imgpath</code> 图片路径是必须参数。</p>
<h3 id="参数处理">参数处理</h3>
<p>最终脚本的处理如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">getargs</span><span class="p">()</span>
    <span class="n">mi</span> <span class="o">=</span> <span class="n">MessageImage</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">imgpath</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">command</span> <span class="o">==</span> <span class="s1">&#39;encode&#39;</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">info</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
            <span class="n">args</span><span class="o">.</span><span class="n">info</span> <span class="o">=</span> <span class="nb">input</span><span class="p">(</span><span class="s1">&#39;请输入要隐藏的信息：&#39;</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">mi</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">info</span><span class="p">):</span>
                <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;已写入隐藏信息！&#39;</span><span class="p">)</span>
    <span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">command</span> <span class="o">==</span> <span class="s1">&#39;decode&#39;</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;解析到隐藏信息：</span><span class="si">{</span><span class="n">mi</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
    <span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">command</span> <span class="o">==</span> <span class="s1">&#39;check&#39;</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;图片可用空间 </span><span class="si">{</span><span class="n">mi</span><span class="o">.</span><span class="n">freespace</span><span class="p">()</span><span class="si">}</span><span class="s1"> bytes&#39;</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;不支持 </span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">command</span><span class="si">}</span><span class="s1"> 命令&#39;</span><span class="p">)</span>
</code></pre></div><p>大功告成！命令行工具的代码参考：<a href="https://github.com/smslit/tools-with-script/blob/master/codeimg/codeimg">tools-with-script/codeimg/codeimg</a></p>
<h1 id="测试">测试</h1>
<p>同样以<a href="https://github.com/smslit/tools-with-script/blob/master/codeimg/demo.png">demo.png</a> 为例，测试过程如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="err">$</span> <span class="o">./</span><span class="n">codeimg</span> <span class="n">encode</span> <span class="n">demo</span><span class="o">.</span><span class="n">png</span> <span class="o">-</span><span class="n">i</span> <span class="s1">&#39;https://www.smslit.top&#39;</span>
<span class="n">demo_code</span><span class="o">.</span><span class="n">png</span> <span class="n">已写入隐藏信息</span><span class="err">！</span>
<span class="err">$</span> <span class="o">./</span><span class="n">codeimg</span> <span class="n">decode</span> <span class="n">demo_code</span><span class="o">.</span><span class="n">png</span>
<span class="n">解析到隐藏信息</span><span class="err">：</span><span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">www</span><span class="o">.</span><span class="n">smslit</span><span class="o">.</span><span class="n">top</span>
<span class="err">$</span> <span class="o">./</span><span class="n">codeimg</span> <span class="n">check</span> <span class="n">demo</span><span class="o">.</span><span class="n">png</span>
<span class="n">图片可用空间</span> <span class="mi">381024</span> <span class="nb">bytes</span>
<span class="err">$</span> <span class="o">./</span><span class="n">codeimg</span> <span class="o">-</span><span class="n">h</span>
<span class="n">usage</span><span class="p">:</span> <span class="n">codeimg</span> <span class="p">[</span><span class="o">-</span><span class="n">h</span><span class="p">]</span> <span class="p">[</span><span class="o">-</span><span class="n">i</span> <span class="n">INFO</span><span class="p">]</span> <span class="p">{</span><span class="n">encode</span><span class="p">,</span><span class="n">decode</span><span class="p">,</span><span class="n">check</span><span class="p">}</span> <span class="n">imgpath</span>

<span class="n">为图片写入隐藏信息</span><span class="err">，</span><span class="n">或者从包含隐藏信息的图片中获取信息</span><span class="err">。</span>

<span class="n">positional</span> <span class="n">arguments</span><span class="p">:</span>
  <span class="p">{</span><span class="n">encode</span><span class="p">,</span><span class="n">decode</span><span class="p">,</span><span class="n">check</span><span class="p">}</span>
                        <span class="n">指定子命令</span><span class="err">。</span><span class="n">encode</span> <span class="o">-&gt;</span> <span class="n">往图片中写入隐藏信息</span><span class="err">；</span><span class="n">decode</span> <span class="o">-&gt;</span> <span class="n">读取图片中的隐藏信息</span><span class="err">；</span><span class="n">check</span>
                        <span class="o">-&gt;</span> <span class="n">查看图片可存空间</span><span class="p">(</span><span class="n">单位</span> <span class="n">byte</span><span class="p">)</span>
  <span class="n">imgpath</span>               <span class="n">指定要处理的图片</span>

<span class="n">optional</span> <span class="n">arguments</span><span class="p">:</span>
  <span class="o">-</span><span class="n">h</span><span class="p">,</span> <span class="o">--</span><span class="n">help</span>            <span class="n">show</span> <span class="n">this</span> <span class="n">help</span> <span class="n">message</span> <span class="ow">and</span> <span class="n">exit</span>
  <span class="o">-</span><span class="n">i</span> <span class="n">INFO</span><span class="p">,</span> <span class="o">--</span><span class="n">info</span> <span class="n">INFO</span>  <span class="n">指定要写入的隐藏信息</span><span class="err">，</span><span class="n">子命令为</span> <span class="n">encode</span> <span class="n">时有效</span>
</code></pre></div><h1 id="总结">总结</h1>
<p>本文实现了图片隐写术的基本功能，过程中熟悉了pillow 库、 argparse 库的基本使用以及类的编写，同时还了解了字节与字符串、字节与整型数据的相互转换。</p>
<p>如果您觉得本文写的还不错，请持续关注 <a href="https://www.smslit.top">https://www.smslit.top</a>，后面还会有好玩的东西分享！</p>]]></content>
		</item>
		
		<item>
			<title>一起使用 pipenv</title>
			<link>https://blog.5km.studio/2018/10/18/pipenv/</link>
			<pubDate>Thu, 18 Oct 2018 10:53:36 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/10/18/pipenv/</guid>
			<description>&lt;p&gt;今天十里给大家推荐一个 python 工具，它就是 python 官方推荐的包管理工具 &lt;a href=&#34;https://pypi.org/project/pipenv/&#34;&gt;pipenv&lt;/a&gt;。这个工具会十八般武艺， virtualenv、pyenv 和 pip 三者的功能集于一身，可谓是高手中的高手了，所以，掌控了它相信你会轻松很多！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181018153983543930207.png&#34; alt=&#34;20181018153983543930207.png&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天十里给大家推荐一个 python 工具，它就是 python 官方推荐的包管理工具 <a href="https://pypi.org/project/pipenv/">pipenv</a>。这个工具会十八般武艺， virtualenv、pyenv 和 pip 三者的功能集于一身，可谓是高手中的高手了，所以，掌控了它相信你会轻松很多！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181018153983543930207.png" alt="20181018153983543930207.png"></p>
<h2 id="身世与技能">身世与技能</h2>
<p>不知道大家晓不晓得 python 界很有名的励志型男 <a href="https://www.kennethreitz.org">Kenneth Reitz</a>，如果你不知道他的话，那你一定知道 <strong>requests</strong> 库吧，没错，<strong>requests</strong> 库的作者就是 <a href="https://www.kennethreitz.org">Kenneth Reitz</a>，是他开发了用于创建和管理 python 虚拟环境的工具 pipenv。</p>
<p>pipenv 能够自动为项目创建和管理虚拟环境，从 Pipfile 文件中添加或者删除包，同时生成 Pipfile.lock 文件来锁定安装包的版本和依赖信息，避免构建错误。它解决了以下问题^[<a href="https://juejin.im/post/5b31ba8851882574e808e555">Pipenv – 超好用的 Python 包管理工具</a>]：</p>
<ul>
<li>不用再单独使用 virtualenv、pyenv 和 pip 了，现在它们结合到了一起。</li>
<li>不用再维护 requirement.txt 了，使用 Pipfile 和 Pipfile.lock 来代替。</li>
<li>可以在开发环境使用多个 python 版本。</li>
<li>在安装的 pyenv 条件下，可以自动安装需要的 python 版本。</li>
<li>安全，广泛地使用 Hash 校验，能够自动曝露安全漏洞。</li>
<li>随时查看图形化的依赖关系。</li>
</ul>
<h2 id="安装及配置">安装及配置</h2>
<h3 id="安装">安装</h3>
<p>这里推荐使用 pip 安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pip install pipenv
</code></pre></div><h3 id="配置">配置</h3>
<p>我们知道有些 python 工具命令行下默认不支持补全，pipenv 也不例外，如果将下面的内容添加到 <code>.bashrc</code> 或 <code>.zshrc</code> 中就可以实现 pipenv 命令的补全了：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span>pipenv --completion<span class="k">)</span><span class="s2">&#34;</span>
</code></pre></div><h2 id="使用">使用</h2>
<p>本节以一个工程为例展示操作流程，从而说明 pipenv 的使用。</p>
<h3 id="创建虚拟环境">创建虚拟环境</h3>
<ol>
<li>
<p>新建工程</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ mkdir myproject
$ <span class="nb">cd</span> myproject
</code></pre></div></li>
<li>
<p>执行以下命令创建虚拟环境：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv install
</code></pre></div><p>执行完上面的命令，就会发现目录下出现 <code>Pipfile</code> 和 <code>Pipfile.lock</code> 两个文件，它们主要记录包的安装信息，上面的命令会使用系统默认版本的 python 创建虚拟环境，如果要使用其他版本可以指定 <code>--two</code> 、 <code>--three</code> 参数指定使用 2.x 版本还是 3.x 版本，形如：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv install --three
</code></pre></div><p>也可以直接指定详细版本号，比如：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="c1"># pipenv install --python 3</span>
<span class="c1"># pipenv install --python 2.7.15</span>
$ pipenv install --python 3.7
</code></pre></div></li>
</ol>
<h3 id="第三方包管理">第三方包管理</h3>
<h4 id="安装包">安装包</h4>
<p>可以使用 <code>pipenv install 包名</code> 安装第三方包，比如这里安装 <code>requests</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv install requests
</code></pre></div><p>安装过程中，<code>Pipfile</code> 会记录包安装的信息和使用的安装源，名称、版本号等。所以 <code>Pipfile</code> 可以跟踪包的管理信息，可以在与别人分享项目的时候，实现一致的开发环境。而 <code>Pipfile.lock</code> 中包含系统信息，所有已安装包的依赖包及其版本信息，以及所有安装包及其依赖包的 Hash 校验信息。</p>
<h5 id="指定版本安装">指定版本安装</h5>
<p>这里以安装版本为1.22的 <code>urllib3</code> 为例：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pipenv install <span class="nv">urllib3</span><span class="o">==</span>1.22
</code></pre></div><h5 id="安装开发版本包">安装开发版本包</h5>
<p>在安装命令的基础上使用 <code>--dev</code> 参数即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pipenv install httpie --dev
</code></pre></div><p>至此，我们可以查看一下 <code>Pipfile</code> 的内容，看看有没有同步记录包的安装信息：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="k">[[source]]</span>
<span class="na">url</span> <span class="o">=</span> <span class="s">&#34;https://pypi.org/simple&#34;</span>
<span class="na">verify_ssl</span> <span class="o">=</span> <span class="s">true</span>
<span class="na">name</span> <span class="o">=</span> <span class="s">&#34;pypi&#34;</span>

<span class="k">[packages]</span>
<span class="na">requests</span> <span class="o">=</span> <span class="s">&#34;*&#34;</span>
<span class="na">&#34;urllib3&#34;</span> <span class="o">=</span> <span class="s">&#34;==1.22&#34;</span>

<span class="k">[dev-packages]</span>
<span class="na">httpie</span> <span class="o">=</span> <span class="s">&#34;*&#34;</span>

<span class="k">[requires]</span>
<span class="na">python_version</span> <span class="o">=</span> <span class="s">&#34;3.7&#34;</span>
</code></pre></div><p>可以看到开发版本的包信息在 <code>[dev-packages]</code> 节点记录，而正常的包在 <code>[packages]</code> 节点记录，同时也能看到 <code>urllib3</code> 确实是 1.22 版本的。</p>
<h5 id="包同步">包同步</h5>
<p>有了 <code>Pipfile.lock</code> 就可以在一个新的目录下搭建一致的 python 开发环境了，使用 <code>pipenv syns</code> 命令就会读取 <code>Pipfile.lock</code> 包信息进行第三方包的批量安装了。</p>
<h5 id="更改安装源">更改安装源</h5>
<p>有时安装包时比较慢，为了快速一点可以更改为国内的包安装源，更改 <code>pipfile</code> 中的 <code>source</code> 配置即可，主要是 <strong>url</strong> ，比如更改为阿里源，可以为：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="k">[[source]]</span>
<span class="na">url</span> <span class="o">=</span> <span class="s">&#34;https://mirrors.aliyun.com/pypi/simple&#34;</span>
<span class="na">verify_ssl</span> <span class="o">=</span> <span class="s">true</span>
<span class="na">name</span> <span class="o">=</span> <span class="s">&#34;aliyun&#34;</span>
</code></pre></div><h4 id="卸载包">卸载包</h4>
<p>可以使用 <code>pipenv uninstall</code> 命令卸载第三方包，比如要卸载刚刚安装的 <code>httpie</code> ：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv uninstall httpie
</code></pre></div><p>此时看一下 <code>Pipfile</code> 内容，可以看到已经没有了 <code>httpie</code> 的记录信息了：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="k">[[source]]</span>
<span class="na">url</span> <span class="o">=</span> <span class="s">&#34;https://pypi.org/simple&#34;</span>
<span class="na">verify_ssl</span> <span class="o">=</span> <span class="s">true</span>
<span class="na">name</span> <span class="o">=</span> <span class="s">&#34;pypi&#34;</span>

<span class="k">[packages]</span>
<span class="na">requests</span> <span class="o">=</span> <span class="s">&#34;*&#34;</span>
<span class="na">&#34;urllib3&#34;</span> <span class="o">=</span> <span class="s">&#34;==1.22&#34;</span>

<span class="k">[dev-packages]</span>

<span class="k">[requires]</span>
<span class="na">python_version</span> <span class="o">=</span> <span class="s">&#34;3.7&#34;</span>
</code></pre></div><p>如果想把所有的包都卸载，可以使用以下命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pipenv uninstall --all
</code></pre></div><p>另外，可以使用 <code>clean</code> 命令清除 <code>Pipfile.lock</code> 中没有记录的包：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv clean
</code></pre></div><h4 id="更新包">更新包</h4>
<p>更新一个具体的包很简单，比如要更新 <code>urllib3</code> ，可以执行如下命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv update urllib3
</code></pre></div><p>如果想更新所有的包，可以这样：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv update
</code></pre></div><h4 id="查看包安装信息">查看包安装信息</h4>
<p>可以通过以下命令清晰有层次的查看包的安装依赖信息：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv graph
<span class="nv">Pygments</span><span class="o">==</span>2.2.0
<span class="nv">requests</span><span class="o">==</span>2.19.1
  - certifi <span class="o">[</span>required: &gt;<span class="o">=</span>2017.4.17, installed: 2018.10.15<span class="o">]</span>
  - chardet <span class="o">[</span>required: &gt;<span class="o">=</span>3.0.2,&lt;3.1.0, installed: 3.0.4<span class="o">]</span>
  - idna <span class="o">[</span>required: &gt;<span class="o">=</span>2.5,&lt;2.8, installed: 2.7<span class="o">]</span>
  - urllib3 <span class="o">[</span>required: &gt;<span class="o">=</span>1.21.1,&lt;1.24, installed: 1.22<span class="o">]</span>
</code></pre></div><h4 id="检查包完整性">检查包完整性</h4>
<p>有些时候需要检查一下包的完整性，据说可以依据 <code>PEP 508</code> 检查安全漏洞，感觉很高端的样子：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv check
Checking PEP <span class="m">508</span> requirements...
Passed!
Checking installed package safety...
All good!
</code></pre></div><h3 id="其它操作">其它操作</h3>
<h4 id="使用虚拟环境中的-python">使用虚拟环境中的 python</h4>
<p>比如，目录中有个文件 <code>demo.py</code> ，如果想使用虚拟环境中的 python 运行这个文件怎么办？可以使用 <code>pipenv run</code> 比如这样：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv run python demo.py
</code></pre></div><p>也可以这样执行 <code>pip</code> 命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv run pip list
</code></pre></div><h4 id="进入或退出虚拟环境">进入或退出虚拟环境</h4>
<p>执行命令 <code>pipenv shell</code> 便可进入虚拟环境，在环境中使用的 python 就是虚拟环境中的：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv shell
Launching subshell in virtual environment…
. /Users/5km/.local/share/virtualenvs/myproject-NwwHp4QU/bin/activate
$ . /Users/5km/.local/share/virtualenvs/myproject-NwwHp4QU/bin/activate
<span class="o">(</span>myproject-NwwHp4QU<span class="o">)</span> $
</code></pre></div><p>在虚执环境中执行命令 <code>exit</code> 或者使用快捷键 <code>CTRL</code> + <code>d</code> 便可退出虚拟环境。</p>
<h4 id="查看虚拟环境安装目录">查看虚拟环境安装目录</h4>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv --venv
/Users/5km/.local/share/virtualenvs/myproject-NwwHp4QU
</code></pre></div><h4 id="查看工程根目录">查看工程根目录</h4>
<p>有时需要查看当前使用的虚拟环境对应的工程根目录，使用命令 <code>pipenv --where</code> 即可查看：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv --where
/Users/5km/Desktop/myproject
</code></pre></div><h4 id="环境变量">环境变量</h4>
<p>在虚拟开发环境中，操作都是独立的，并不影响系统的环境，这是虚拟环境的最重要作用，有时候也需要独立的虚拟环境变量，而不是存在于系统的环境变量中。只需要在工程根目录新建一个 <code>.env</code> 文件，在文件中声明环境变量即可，比如想添加一个变量 <code>FLASK_KEY</code> 值为 <code>NONE</code>，可以将 <code>FLASK_KEY=NONE</code> 放到 <code>.env</code> 中：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="nb">echo</span> <span class="s2">&#34;FLASK_KEY=NONE&#34;</span> &gt; .env
</code></pre></div><p>重新进入虚拟环境后，就会自动加载 <code>.env</code> 中的环境变量，此时就可以使用环境变量了：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv shell
Launching subshell in virtual environment…
 . /Users/5km/.local/share/virtualenvs/myproject-NwwHp4QU/bin/activate
$ . /Users/5km/.local/share/virtualenvs/myproject-NwwHp4QU/bin/activate
<span class="o">(</span>myproject-NwwHp4QU<span class="o">)</span> $ <span class="nb">echo</span> <span class="nv">$FLASK_KEY</span>
NONE
</code></pre></div><p><strong>注意：</strong></p>
<p>在虚拟环境中，使用 <code>pip</code> 安装第三包是安装在虚拟环境中的，但是不会记录到 <code>Pipfile</code> 中，虚拟环境中也可以使用 <code>pipenv install</code> 安装第三方包，此时仍会记录在 <code>Pipfile</code> 和 <code>Pipfile.lock</code> 中。</p>
<h2 id="问题">问题</h2>
<h3 id="module-object-is-not-callable-问题">&lsquo;module&rsquo; object is not callable 问题</h3>
<p>使用命令 <code>pipenv install</code> 安装软件包的时候，可能会遇到 <strong>TypeError: &lsquo;module&rsquo; object is not callable</strong> 问题，这是因为 pip 版本不兼容的问题导致的，可以在相应的工程内执行以下命令安装 18.0 版本的 pip 临时解决^[<a href="https://github.com/pypa/pipenv/issues/2871">Running pipenv gives TypeError: &lsquo;module&rsquo; object is not callable #2871</a>]：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv run pip install <span class="nv">pip</span><span class="o">==</span>18.0
</code></pre></div><p>这个问题，是在本文章发表的时候刚出现的，可能会随着 pipenv 的升级得到相应解决。</p>
<h3 id="valueerror-unknown-locale-utf-8-问题">ValueError: unknown locale: UTF-8 问题</h3>
<p>这个问题解决方法很简单，只需指定环境变量即可，可以将以下内容添加到 <code>.bashrc</code> 或 <code>.zshrc</code> 中解决^[<a href="https://github.com/pypa/pipenv/issues/187">ValueError: unknown locale: UTF-8 #187</a>]：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="nb">export</span> <span class="nv">LC_ALL</span><span class="o">=</span>en_US.UTF-8
<span class="nb">export</span> <span class="nv">LANG</span><span class="o">=</span>en_US.UTF-8
</code></pre></div><h3 id="lock-updating-很慢的问题">Lock updating 很慢的问题</h3>
<p>在使用 <code>pipenv install</code> 安装包最后，会进行一次 lock 操作，但是 lock 操作奇慢无比， 果不想等待漫长(或者是永久)的时间，可以在每次安装包操作的时候添加参数 <code>--skip-lock</code> 进行跳过^[<a href="https://github.com/pypa/pipenv/issues/1914#issuecomment-378846926">Lock updating is very slow #1914</a>]，形如：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pipenv install requests --skip-lock
</code></pre></div><h2 id="总结">总结</h2>
<p>本文简单介绍了 pipenv 的安装和使用方法，希望您读了本文能感受到这个工具的方便之处！</p>]]></content>
		</item>
		
		<item>
			<title>python3 实现Markdown转html小工具</title>
			<link>https://blog.5km.studio/2018/10/16/md2html_python/</link>
			<pubDate>Tue, 16 Oct 2018 17:44:29 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/10/16/md2html_python/</guid>
			<description>&lt;p&gt;写博客文章一直使用 &lt;a href=&#34;http://www.markdown.cn&#34;&gt;markdown&lt;/a&gt;，&lt;a href=&#34;http://www.markdown.cn&#34;&gt;markdown&lt;/a&gt; 是一个轻量级的标记语言，可以快速创建待格式的简单文档。&lt;a href=&#34;https://typora.io&#34;&gt;typra&lt;/a&gt; 是一款跨平台而且设计极佳的 markdown 写作工具。markdown 本质上算是简化版的 html。那么，如何自己编写一个 &lt;a href=&#34;http://www.markdown.cn&#34;&gt;markdown&lt;/a&gt; 转换工具呢？本文将带你一起用python3 实现一个 markdown 转 html 的命令行工具，用浏览器查看 html 就能看到带格式的文章了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181018153983700396678.png&#34; alt=&#34;20181018153983700396678.png&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>写博客文章一直使用 <a href="http://www.markdown.cn">markdown</a>，<a href="http://www.markdown.cn">markdown</a> 是一个轻量级的标记语言，可以快速创建待格式的简单文档。<a href="https://typora.io">typra</a> 是一款跨平台而且设计极佳的 markdown 写作工具。markdown 本质上算是简化版的 html。那么，如何自己编写一个 <a href="http://www.markdown.cn">markdown</a> 转换工具呢？本文将带你一起用python3 实现一个 markdown 转 html 的命令行工具，用浏览器查看 html 就能看到带格式的文章了。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181018153983700396678.png" alt="20181018153983700396678.png"></p>
<h2 id="准备工作">准备工作</h2>
<p>在开始之前，要安装一些第三方库。</p>
<h2 id="python3-markdown-库">python3-markdown 库</h2>
<p><a href="https://pypi.org/project/Markdown/"><strong>markdown</strong></a> 库是用来编译渲染 markdown 文件的，这是咱编写转换工具的核心。</p>
<h3 id="安装">安装</h3>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pip3 install markdown
</code></pre></div><h3 id="使用">使用</h3>
<p>在这里只使用 markdown 方法，可以将 markdwon 文本转换为 html 格式的文本，如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">markdown</span>

<span class="n">markdownText</span> <span class="o">=</span> <span class="s1">&#39;&#39;&#39;# 测试
</span><span class="s1">- 无序列表1
</span><span class="s1">- 无序列表2
</span><span class="s1">&#39;&#39;&#39;</span>

<span class="nb">print</span><span class="p">(</span><span class="n">markdown</span><span class="o">.</span><span class="n">markdown</span><span class="p">(</span><span class="n">markdownText</span><span class="p">,</span> <span class="n">output_format</span><span class="o">=</span><span class="s1">&#39;html5&#39;</span><span class="p">,</span> <span class="n">extensions</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;extra&#39;</span><span class="p">]))</span>
</code></pre></div><p>上述代码，会打印转换出来的 html 格式的文本。</p>
<h3 id="注意">注意</h3>
<ol>
<li>markdwon 方法返回的结果是 html 文本，但不包含头信息 <code>&lt;head&gt;</code>，所以后面如果使用的话应该给结果加上，保证html文本的兼容性；</li>
<li>markdwon 方法可以指定输出格式，比如按照 <strong>html5</strong> 的格式转换；</li>
<li>markdwon 方法默认不识别代码高亮语法，可以按照上述演示代码中的方式指定 <code>extensions</code> 为 <code>['extra']</code>^[<a href="https://blog.csdn.net/zrools/article/details/51860533">python3-markdown 解析反引号代码块与代码高亮</a>]；</li>
</ol>
<h2 id="beautifulsoup4-库和-html5lib-库">beautifulsoup4 库和 html5lib 库</h2>
<p>这里安装 html5lib 库，主要是使用 <a href="https://pypi.org/project/beautifulsoup4/">beautifulsoup4</a> 库的 <code>prettify</code> 方法的时候依赖 html5lib 库。</p>
<h3 id="安装-1">安装</h3>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pip3 install beautifulsoup4 html5lib
</code></pre></div><h3 id="使用-1">使用</h3>
<p>beautifulsoup4 库可以过滤分析 html 文本的节点信息和其它 html 文本的处理，多用于爬虫应用，这里使用到 beautifulsoup4 库的美化 html 文本的方法，按照 <code>html5lib</code> 的方式，简单参考如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">bs4</span> <span class="kn">import</span> <span class="n">BeautifulSoup</span>

<span class="n">rawHtml</span> <span class="o">=</span> <span class="s1">&#39;&lt;head&gt;&lt;meta charset=&#34;utf-8&#34; /&gt;&lt;/head&gt;&lt;body&gt;&lt;h1&gt;Hello&lt;/h1&gt;&lt;p&gt;hello, world!&lt;/p&gt;&lt;/body&gt;&#39;</span>
<span class="n">prettyHtml</span> <span class="o">=</span> <span class="n">BeautifulSoup</span><span class="p">(</span><span class="n">rawHtml</span><span class="p">,</span> <span class="s1">&#39;html5lib&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">prettify</span><span class="p">()</span>
</code></pre></div><p>主要是用于将 html 文本格式化，处理的更美观易读。</p>
<h2 id="实现">实现</h2>
<p>有了上面安装的 markdown 库，要实现 markdown 转 html 的小工具就变得简单了许多，就不需要自己编写 markdown 语法解析器了。实现思路很简单，编写一个类 Markdown2Html，然后编写一个python脚本形式的命令脚本调用类方法实现文件转换。</p>
<h3 id="markdown2html-类">Markdown2Html 类</h3>
<ol>
<li>
<p>这个类在文件 markdown2html.py 中实现，所以首先要新建这个文件。</p>
</li>
<li>
<p>导入要用到的库</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">markdown</span>
<span class="kn">import</span> <span class="nn">os.path</span> <span class="k">as</span> <span class="nn">op</span>
<span class="kn">from</span> <span class="nn">bs4</span> <span class="kn">import</span> <span class="n">BeautifulSoup</span> 
</code></pre></div></li>
<li>
<p>定义类及初始化函数，因为最终转换要生成 html 文件，那么必然可以使用 css 文件控制样式，所以这里初始化函数可传入一个指定 css 文件配置样式，实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">Markdown2Html</span><span class="p">:</span>

    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cssfile</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
        <span class="s1">&#39;&#39;&#39;
</span><span class="s1">        初始化 Markdown2Html 类，可传入特定 css 文件作为样式
</span><span class="s1">        &#39;&#39;&#39;</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">headTag</span> <span class="o">=</span> <span class="s1">&#39;&lt;head&gt;&lt;meta charset=&#34;utf-8&#34; /&gt;&lt;/head&gt;&#39;</span>
        <span class="k">if</span> <span class="n">cssfile</span><span class="p">:</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">setStyle</span><span class="p">(</span><span class="n">cssfile</span><span class="p">)</span>
</code></pre></div><p>初始化中定义的 <code>headTag</code> 主要是用于完善 markdwon 库得到的 html 文本，<code>&lt;head&gt;</code> 中预留了 css 样式文本的位置。<code>setStyle</code> 方法主要处理传入的 css 文件，具体见下一步。</p>
</li>
<li>
<p>实现 Markdown2Html 类的 <code>setStyle</code> 方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">setStyle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cssfile</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;
</span><span class="s1">    设置样式表文件
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="k">if</span> <span class="n">cssfile</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">headTag</span> <span class="o">=</span> <span class="s1">&#39;&lt;head&gt;&lt;meta charset=&#34;utf-8&#34; /&gt;&lt;/head&gt;&#39;</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">cssfile</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
            <span class="n">css</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">headTag</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">headTag</span><span class="p">[:</span><span class="o">-</span><span class="mi">7</span><span class="p">]</span> <span class="o">+</span> <span class="sa">f</span><span class="s1">&#39;&lt;style  type=&#34;text/css&#34;&gt;</span><span class="si">{</span><span class="n">css</span><span class="si">}</span><span class="s1">&lt;/style&gt;&#39;</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">headTag</span><span class="p">[</span><span class="o">-</span><span class="mi">7</span><span class="p">:]</span>

</code></pre></div><p><code>setStyle</code> 方法传入 cssfile 参数，如果为空，说明不指定样式，将 html 文本预留头信息设置为初始化状态，如果设置了 css 文件，那么用 css 文件内容生成新的 <code>head</code> 信息。</p>
</li>
<li>
<p>实现 markdown 转换为 html 的方法 <code>convert</code>，这里设计方法，可以传入三个参数，先看实现，后解释：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">convert</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">infile</span><span class="p">,</span> <span class="n">outfile</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">prettify</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;
</span><span class="s1">    转换文件
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">op</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">infile</span><span class="p">):</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;请输入正确的 markdown 文件路径！&#39;</span><span class="p">)</span>
        <span class="k">return</span>

    <span class="k">if</span> <span class="n">outfile</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
        <span class="n">outfile</span> <span class="o">=</span> <span class="n">op</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">infile</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s1">&#39;.html&#39;</span>

    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">infile</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">markdownText</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>

    <span class="n">rawhtml</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">headTag</span> <span class="o">+</span> <span class="n">markdown</span><span class="o">.</span><span class="n">markdown</span><span class="p">(</span><span class="n">markdownText</span><span class="p">,</span> <span class="n">output_format</span><span class="o">=</span><span class="s1">&#39;html5&#39;</span><span class="p">,</span> <span class="n">extensions</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;extra&#39;</span><span class="p">])</span>

    <span class="k">if</span> <span class="n">prettify</span><span class="p">:</span>
        <span class="n">prettyHtml</span> <span class="o">=</span> <span class="n">BeautifulSoup</span><span class="p">(</span><span class="n">rawhtml</span><span class="p">,</span> <span class="s1">&#39;html5lib&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">prettify</span><span class="p">()</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">outfile</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
            <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">prettyHtml</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">outfile</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
            <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">rawhtml</span><span class="p">)</span>
</code></pre></div><p>这里首先判断指定的输入文件路径对应是不是文件，然后检查输出文件有没有指定，之后读取 markdown 文件获取 markdown 文本，调用 markdown 库的 markdown 方法将 markdown 文本转换为 html5 格式的 html 文本，并添加准备好的头信息，最后判断是否需要美化生成的 html 文本，如果是，就先美化，再写入到指定文件，如果不是，则直接将结果写入到指定输出文件。需要注意的是，上述文件操作中都指明了文件编码为 <code>utf8</code> ，是为了保证跨平台编码统一，提高兼容性。</p>
</li>
<li>
<p>编写以下代码进行测试，同级目录下存储的 <code>github.css</code> 文件，所以这里指定 css文件 转换 <code>README.md</code> 文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">m2h</span> <span class="o">=</span> <span class="n">Markdown2Html</span><span class="p">(</span><span class="s1">&#39;github.css&#39;</span><span class="p">)</span>
    <span class="n">m2h</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="s1">&#39;./README.md&#39;</span><span class="p">,</span> <span class="s1">&#39;./README.html&#39;</span><span class="p">)</span>
</code></pre></div><p>运行：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">python3 markdown2html.py
</code></pre></div><p>结果：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181016153970548190814.png" alt="20181016153970548190814.png"></p>
<p>Markdown2Html 类完成！</p>
<p>完整代码参考：<a href="https://github.com/smslit/tools-with-script/blob/master/m2h/markdown2html.py">tools-with-script/m2h/markdown2html.py</a></p>
</li>
</ol>
<h3 id="脚本">脚本</h3>
<p>有了上面的 Markdown2Html 类，现在可以编写脚本命令了。</p>
<h4 id="准备文件">准备文件</h4>
<ol>
<li>
<p>新建名为 <code>m2h</code> 的文件，添加以下内容，指明脚本解释器为 <code>python3</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/bin/env python3</span>
</code></pre></div></li>
<li>
<p>执行以下命令，为文件添加运行权限：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">chmod +x ./m2h
</code></pre></div><p>这样就可以像执行命令一样调用工具了。</p>
</li>
</ol>
<h4 id="脚本实现">脚本实现</h4>
<h5 id="导入必要库">导入必要库</h5>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">from</span> <span class="nn">markdown2html</span> <span class="kn">import</span> <span class="n">Markdown2Html</span>
</code></pre></div><h5 id="命令参数">命令参数</h5>
<p>作为一个合格的文件转换命令工具，最起码要有一个占位参数，用来指定待转换的 markdown 文件。另外，还要设计一个可选参数用来指定要使用的 css 样式文件，用来指定转换后的主题，有时候需要将所有的转换文件统一放在一个目录中，所以还要有一个参数用来指定这个路径。最终设计的参数如下：</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>infiles</td>
<td>指定 markdown 文件，可以是多个 md 文件</td>
</tr>
<tr>
<td>&ndash;style, -s</td>
<td>指定要使用的 css 文件</td>
</tr>
<tr>
<td>&ndash;outdir, -o</td>
<td>指定文件输出目录</td>
</tr>
</tbody>
</table>
<p>这里使用 python3 的官方库 <a href="https://docs.python.org/3/library/argparse.html">argparse</a> 来实现命令参数的过滤，可以阅读 <a href="/2018/09/13/python-argparse/">argparse 库的使用</a> 了解这个库的基本使用，将参数过滤封装为函数 <code>getArgs</code> ：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">get_args</span><span class="p">():</span>
    <span class="s1">&#39;&#39;&#39;
</span><span class="s1">    获取命令参数
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-s&#39;</span><span class="p">,</span> <span class="s1">&#39;--style&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定要使用的 css 样式，css文件路径&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-o&#39;</span><span class="p">,</span> <span class="s1">&#39;--outdir&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定 html 文件输出目录&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;infiles&#39;</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s1">&#39;+&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定要转换的 markdown 文件，可以指定多个&#39;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</code></pre></div><p>其中 <code>infiles</code> 参数指定<code>nargs='+'</code>，可以得到的是一个包含大于或等于1个文件路径的列表^[<a href="https://codeday.me/bug/20170705/32423.html">python – argparse选项用于传递列表作为选项</a>]。函数返回的就是过滤后得到的参数信息。</p>
<h5 id="最终实现过程">最终实现过程</h5>
<p>先贴出代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">get_args</span><span class="p">()</span>
    <span class="n">m2h</span> <span class="o">=</span> <span class="n">Markdown2Html</span><span class="p">()</span>
    <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">style</span><span class="p">:</span>
        <span class="n">m2h</span><span class="o">.</span><span class="n">setStyle</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">style</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">mdfile</span> <span class="ow">in</span> <span class="n">args</span><span class="o">.</span><span class="n">infiles</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">outdir</span><span class="p">:</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">outdir</span><span class="p">):</span>
                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;新建目录 </span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">outdir</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
                <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">outdir</span><span class="p">)</span>
            <span class="n">filename</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">mdfile</span><span class="p">))[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s1">&#39;.html&#39;</span>
            <span class="n">outfile</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">outdir</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span>
            <span class="n">count</span> <span class="o">=</span> <span class="mi">1</span>
            <span class="k">while</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">outfile</span><span class="p">):</span>
                <span class="n">filename</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">mdfile</span><span class="p">))[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="sa">f</span><span class="s1">&#39;_</span><span class="si">{</span><span class="n">count</span><span class="si">}</span><span class="s1">.html&#39;</span>
                <span class="n">outfile</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">outdir</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span>
                <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">outfile</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">mdfile</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s1">&#39;.html&#39;</span>
        <span class="n">m2h</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="n">mdfile</span><span class="p">,</span> <span class="n">outfile</span><span class="o">=</span><span class="n">outfile</span><span class="p">,</span> <span class="n">prettify</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">mdfile</span><span class="si">}</span><span class="s1"> 已转换完成，保存为</span><span class="si">{</span><span class="n">outfile</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</code></pre></div><p><code>m2h</code>文件完整代码，参考<a href="https://github.com/smslit/tools-with-script/blob/master/m2h/m2h">tools-with-script/m2h/m2h</a>。</p>
<p>首先获取过滤参数，之后声明 <code>Markdown2Html</code> 类的一个实例 <code>m2h</code>，如果执行命令中指定了样式文件，就为实例设置这个 css 文件。枚举待转换文件列表中的文件，处理文件分以下几步：</p>
<ol>
<li>
<p>判断是否指定了统一的输出目录</p>
<ul>
<li>
<p>如果指定统一输出目录：检查目录是否存在，若不存在就新建目录；生成 html 文件的路径名称，为了防止输出到统一路径是出现重名而覆盖文件，这里只要发现文件存在就增加后缀序号；</p>
</li>
<li>
<p>如果没有指定统一输出目录：直接生成相应的 html 输出文件的路径即可；</p>
</li>
</ul>
</li>
<li>
<p>调用实例 <code>m2h</code> 的 <code>convert</code> 方法，指定 markdown文件和输出文件，进行转换。</p>
</li>
</ol>
<h5 id="测试">测试</h5>
<p>命令使用帮助信息：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$  ./m2h -h
usage: m2h <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-s STYLE<span class="o">]</span> <span class="o">[</span>-o OUTDIR<span class="o">]</span> infiles <span class="o">[</span>infiles ...<span class="o">]</span>

positional arguments:
  infiles               指定要转换的 markdown 文件，可以指定多个

optional arguments:
  -h, --help            show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -s STYLE, --style STYLE
                        指定要使用的 css 样式，css文件路径
  -o OUTDIR, --outdir OUTDIR
                        指定 html 文件输出目录
</code></pre></div><p>我这里同级目录和上一层目录各有一个 <code>README.md</code> 文件，这里尝试转换并输出到同级目录下的 <code>output</code> 目录下，注意现在 <code>output</code> 目录并不存在，并指定 <code>github.css</code> 作为样式文件，执行命令如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./m2h README.md ../README.md -s github.css -o ./output
新建目录 ./output
README.md 已转换完成，保存为./output/README.html
../README.md 已转换完成，保存为./output/README_1.html
</code></pre></div><h2 id="总结">总结</h2>
<p>本文学习了 markdown 库的使用，同时练习了类的编写和使用。工具的完整文件见：<a href="https://github.com/smslit/tools-with-script/tree/master/m2h">m2h</a></p>]]></content>
		</item>
		
		<item>
			<title>python3 简单实现验证码识别器</title>
			<link>https://blog.5km.studio/2018/10/12/verification_code_chars/</link>
			<pubDate>Fri, 12 Oct 2018 14:59:37 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/10/12/verification_code_chars/</guid>
			<description>&lt;p&gt;编写爬虫总该要过验证码识别这个坎，今天用python3简单实现一个验证码识别器。其实主要是实践，最后效果也不是特别好，对于复杂的验证码，还是力不从心的！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181012153933272275283.png&#34; alt=&#34;20181012153933272275283.png&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>编写爬虫总该要过验证码识别这个坎，今天用python3简单实现一个验证码识别器。其实主要是实践，最后效果也不是特别好，对于复杂的验证码，还是力不从心的！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181012153933272275283.png" alt="20181012153933272275283.png"></p>
<p>开始之前，提醒⏰，应该是中间有些处理不对，造成结果不理想，不过本文的思路是可行的，因为参考的<a href="https://www.shiyanlou.com/courses/364">实验楼 —— python 破解验证码</a>！</p>
<h2 id="前言">前言</h2>
<p>其实已经有很多现成的python库可以用于识别验证码了，比如：<strong>pytesseract</strong> 和 <strong>tesseract_ocr</strong>，本文也要自己实现一种方法，最终做一个可以使用三种方法的验证码识别器。</p>
<h2 id="准备工作">准备工作</h2>
<p>在开始之前需要做一些准备工作，安装必要的库和准备独立的开发环境。</p>
<ul>
<li>pipenv</li>
<li>pytesseract</li>
<li>tesseract_ocr</li>
<li>pillow</li>
</ul>
<p>这里使用独立的python环境进行开发，用到了<strong>pipenv</strong></p>
<ol>
<li>
<p>首先安装 <strong>pipenv</strong></p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pip3 install pipenv
</code></pre></div></li>
<li>
<p>为当前工程激活独立的python环境：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pipenv install
</code></pre></div></li>
<li>
<p>进入pipenv的独立环境：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pipenv shell
</code></pre></div><p>进入后会看到命令行每条命令输入前多了一部分信息，形如<code>(pin2chars-E2eD5P-4)</code></p>
</li>
<li>
<p>在安装和使用库 <strong>pytesseract</strong> 和 <strong>tesseract_ocr</strong> 之前需要安装 <strong>tesseract</strong>：</p>
<ul>
<li>macOS下：<code>brew install tesseract</code></li>
<li>其它系统，根据自己系统的包安装方法安装；</li>
</ul>
</li>
<li>
<p>然后使用 <code>pip3</code> 安装库（库会安装在当前的激活环境中）：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pip3 install pipenv pytesseract tesseract_ocr pillow
</code></pre></div></li>
</ol>
<h2 id="pytesseract-和-tesseract_ocr-库python3-破解验证码httpswwwjianshucompff2ce797cf70">pytesseract 和 tesseract_ocr 库^[<a href="https://www.jianshu.com/p/ff2ce797cf70">python3 破解验证码</a>]</h2>
<p><strong>pytesseract</strong> 和 <strong>tesseract_ocr</strong> 库都基于工具 <strong>tesseract</strong>，这两个库只需各自调用一个方法就能实现验证码的识别，非常简单。下面使用 ipython 看一下使用方法，这里准备一个验证码图片 <code>code.gif</code> 作为识别对象：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181013153938827799704.gif" alt="20181013153938827799704.gif"></p>
<h3 id="pytesseract-库识别验证码">pytesseract 库识别验证码</h3>
<p>使用库中的 <code>image_to_string</code> 方法传入图片的路径，比如上面的图片 <code>code.gif</code> 为例：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="o">(</span>pin2chars-E2eD5P-4<span class="o">)</span> ➜  pin2chars git:<span class="o">(</span>master<span class="o">)</span> ✗ ipython
Python 3.7.0 <span class="o">(</span>default, Sep <span class="m">18</span> 2018, 18:47:08<span class="o">)</span>

In <span class="o">[</span>1<span class="o">]</span>: import pytesseract

In <span class="o">[</span>2<span class="o">]</span>: pytesseract.image_to_string<span class="o">(</span><span class="s1">&#39;code.gif&#39;</span><span class="o">)</span>
Out<span class="o">[</span>2<span class="o">]</span>: <span class="s1">&#39;7S9T9J’&#39;</span>
</code></pre></div><p>图像经过二值化处理的话可能效果更好一些。</p>
<h3 id="tesseract_ocr-库识别验证码">tesseract_ocr 库识别验证码</h3>
<p>使用库中的 <code>text_for_filename</code> 方法传入图片的路径，比如上面的图片 <code>code.gif</code> 为例：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="o">(</span>pin2chars-E2eD5P-4<span class="o">)</span> ➜  pin2chars git:<span class="o">(</span>master<span class="o">)</span> ✗ ipython
Python 3.7.0 <span class="o">(</span>default, Sep <span class="m">18</span> 2018, 18:47:08<span class="o">)</span>

In <span class="o">[</span>1<span class="o">]</span>: import tesseract_ocr

In <span class="o">[</span>2<span class="o">]</span>: tesseract_ocr.text_for_filename<span class="o">(</span><span class="s1">&#39;code.gif&#39;</span><span class="o">)</span>
Warning. Invalid resolution <span class="m">0</span> dpi. Using <span class="m">70</span> instead.
Out<span class="o">[</span>2<span class="o">]</span>: <span class="s1">&#39;7S9T9J’&#39;</span>
</code></pre></div><p>图像经过二值化处理的话可能效果更好一些。</p>
<h2 id="自实现库-pin_cracker">自实现库 pin_cracker</h2>
<p>自实现方法的思路：先二值化图片，然后分割图片的单个字符图片数据，最后利用向量空间识别方法得到字符。</p>
<p>导入必要库：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">math</span>
<span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>
</code></pre></div><h3 id="二值化图片">二值化图片</h3>
<p>新建文件 <code>pin_cracker.py</code>，自实现库的代码均存在此文件中。观察到验证码图片中，字符位置对应像素颜色是很明显的，可以先找到像素颜色最多的颜色，列出来，选择器其中正确的像素值作为阈值。</p>
<p>先将图片灰度化，这样每个像素点的取值范围就是 <strong>[0, 255]</strong> 了，取得像素分布直方图数据:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">get_binary_image</span><span class="p">(</span><span class="n">filename</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;得到二值化图片数据&#39;&#39;&#39;</span>
    <span class="n">img</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="s2">&#34;P&#34;</span><span class="p">)</span>
    <span class="n">his</span> <span class="o">=</span> <span class="n">img</span><span class="o">.</span><span class="n">histogram</span><span class="p">()</span>
    <span class="n">his_10</span> <span class="o">=</span> <span class="p">[(</span><span class="n">j</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span> <span class="k">for</span> <span class="n">j</span><span class="p">,</span><span class="n">k</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="nb">enumerate</span><span class="p">(</span><span class="n">his</span><span class="p">),</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span><span class="n">x</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">reverse</span> <span class="o">=</span> <span class="kc">True</span><span class="p">)[:</span><span class="mi">10</span><span class="p">]]</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">his_10</span><span class="p">)</span>
</code></pre></div><p>得到结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="o">[</span>
    <span class="o">(</span>255, 625<span class="o">)</span>, 
    <span class="o">(</span>212, 365<span class="o">)</span>, 
    <span class="o">(</span>220, 186<span class="o">)</span>, 
    <span class="o">(</span>219, 135<span class="o">)</span>, 
    <span class="o">(</span>169, 132<span class="o">)</span>, 
    <span class="o">(</span>227, 116<span class="o">)</span>, 
    <span class="o">(</span>213, 115<span class="o">)</span>, 
    <span class="o">(</span>234, 21<span class="o">)</span>, 
    <span class="o">(</span>205, 18<span class="o">)</span>, 
    <span class="o">(</span>184, 15<span class="o">)</span>
<span class="o">]</span>
</code></pre></div><p>选择其中 <strong>220</strong> 和 <strong>227</strong> 值作为阈值。最终实现封装为函数如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">get_binary_image</span><span class="p">(</span><span class="n">filename</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;得到二值化图片数据&#39;&#39;&#39;</span>
    <span class="n">img</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="s2">&#34;P&#34;</span><span class="p">)</span>
    <span class="c1"># his = img.histogram()</span>
    <span class="c1"># his_10 = [(j, k) for j,k in sorted(enumerate(his), key=lambda x:x[1], reverse = True)[:10]]</span>
    <span class="c1"># print(his_10)</span>
    
    <span class="n">threshold</span> <span class="o">=</span> <span class="p">[</span><span class="mi">213</span><span class="p">,</span> <span class="mi">219</span><span class="p">,</span> <span class="mi">220</span><span class="p">,</span> <span class="mi">227</span><span class="p">]</span>
    <span class="n">binary_img</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;P&#34;</span><span class="p">,</span> <span class="n">img</span><span class="o">.</span><span class="n">size</span><span class="p">,</span> <span class="mi">255</span><span class="p">)</span>

    <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">img</span><span class="o">.</span><span class="n">size</span><span class="p">[</span><span class="mi">1</span><span class="p">]):</span>
        <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">img</span><span class="o">.</span><span class="n">size</span><span class="p">[</span><span class="mi">0</span><span class="p">]):</span>
            <span class="n">pix</span> <span class="o">=</span> <span class="n">img</span><span class="o">.</span><span class="n">getpixel</span><span class="p">((</span><span class="n">y</span><span class="p">,</span><span class="n">x</span><span class="p">))</span>
            <span class="k">if</span> <span class="n">pix</span> <span class="ow">in</span> <span class="n">threshold</span><span class="p">:</span>
                <span class="c1"># these are the numbers to get</span>
                <span class="n">binary_img</span><span class="o">.</span><span class="n">putpixel</span><span class="p">((</span><span class="n">y</span><span class="p">,</span><span class="n">x</span><span class="p">),</span> <span class="mi">0</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">binary_img</span>
</code></pre></div><p>试一下效果：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">bimg</span> <span class="o">=</span> <span class="n">get_binary_image</span><span class="p">(</span><span class="s1">&#39;code.gif&#39;</span><span class="p">)</span>
<span class="n">bimg</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div><p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/201810131539388080128.png" alt="201810131539388080128.png"></p>
<h3 id="分割图片">分割图片</h3>
<p>图片比较简单，所以可以利用简单的纵向切割得到每个字符的像素范围，一列一列地判断像素值，全是1那就不是字符区，最终封装实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">slice_image</span><span class="p">(</span><span class="n">bimg</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;通过传入的二值化图片数据进行纵向切图，得到每个字符的分割图，并返回图片数量&#39;&#39;&#39;</span>
    <span class="n">letters</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">foundletter</span> <span class="o">=</span> <span class="kc">False</span>
    <span class="n">letter_start</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">letter_end</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bimg</span><span class="o">.</span><span class="n">size</span><span class="p">[</span><span class="mi">0</span><span class="p">]):</span>
        <span class="n">pixelist</span> <span class="o">=</span> <span class="p">[</span><span class="n">bimg</span><span class="o">.</span><span class="n">getpixel</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span> <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bimg</span><span class="o">.</span><span class="n">size</span><span class="p">[</span><span class="mi">1</span><span class="p">])]</span>
        <span class="n">has_p</span> <span class="o">=</span> <span class="mi">0</span> <span class="ow">in</span> <span class="n">pixelist</span>
        <span class="k">if</span> <span class="n">foundletter</span> <span class="o">==</span> <span class="kc">False</span> <span class="ow">and</span> <span class="n">has_p</span> <span class="o">==</span> <span class="kc">True</span><span class="p">:</span>
            <span class="n">foundletter</span> <span class="o">=</span> <span class="kc">True</span>
            <span class="n">letter_start</span> <span class="o">=</span> <span class="n">x</span>
        <span class="k">if</span> <span class="n">foundletter</span> <span class="o">==</span> <span class="kc">True</span> <span class="ow">and</span> <span class="n">has_p</span> <span class="o">==</span> <span class="kc">False</span><span class="p">:</span>
            <span class="n">foundletter</span> <span class="o">=</span> <span class="kc">False</span>
            <span class="n">letter_end</span> <span class="o">=</span> <span class="n">x</span>
            <span class="n">letters</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">letter_start</span><span class="p">,</span> <span class="n">letter_end</span><span class="p">))</span>
    
    <span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">letter</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">letters</span><span class="p">):</span>
        <span class="n">img</span> <span class="o">=</span> <span class="n">bimg</span><span class="o">.</span><span class="n">crop</span><span class="p">((</span><span class="n">letter</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="mi">0</span><span class="p">,</span> <span class="n">letter</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">bimg</span><span class="o">.</span><span class="n">size</span><span class="p">[</span><span class="mi">1</span><span class="p">]))</span>
        <span class="n">img</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">index</span><span class="si">}</span><span class="s1">.gif&#39;</span><span class="p">)</span>

    <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">letters</span><span class="p">)</span>
</code></pre></div><h3 id="向量空间">向量空间</h3>
<p>这里使用向量空间搜索引擎来做字符识别，它具也有优点也有缺点^[<a href="https://www.shiyanlou.com/courses/364">实验楼 —— python 破解验证码</a>]:</p>
<p>优点：</p>
<ul>
<li>不需要大量的训练迭代</li>
<li>不会训练过度</li>
<li>可以随时加入/移除错误的数据查看效果</li>
<li>很容易理解和代码实现</li>
<li>提供分级结果，可以查看最接近的多个匹配</li>
<li>对于无法识别的东西只要加入到搜索引擎中，马上就能识别</li>
</ul>
<p>缺点：</p>
<ul>
<li>分类速度慢于神经网络分类</li>
<li>找不到自己的方法解决问题</li>
</ul>
<p>阅读<a href="http://ondoc.logand.com/d/2697/pdf">Basic Vector Space Search Engine Theory</a> 可以了解向量空间搜索引擎原理。拿文章里的例子通俗讲：要量化文档的相似度，可以比较2篇文档所使用的相同单词数量，越多的话两篇文章就越相似！单词太多了也不要紧，可以选择几个关键单词，选择的单词又被称作特征，每一个特征就好比空间中的一个维度（x,y,z等），一组特征就是一个矢量，每一个文档我们就能得到这么一个矢量，只要计算矢量之间的夹角就能得到文章的相似度了。</p>
<p>先实现一个向量空间类：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">VectorCompare</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">magnitude</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">concordance</span><span class="p">):</span>
        <span class="s2">&#34;&#34;&#34;矢量大小&#34;&#34;&#34;</span>
        <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">for</span> <span class="n">count</span> <span class="ow">in</span> <span class="n">concordance</span><span class="o">.</span><span class="n">values</span><span class="p">():</span>
            <span class="n">total</span> <span class="o">+=</span> <span class="n">count</span> <span class="o">**</span> <span class="mi">2</span>
        <span class="k">return</span> <span class="n">math</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">total</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">relation</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">concordance1</span><span class="p">,</span> <span class="n">concordance2</span><span class="p">):</span>
        <span class="s2">&#34;&#34;&#34;矢量之间的夹角的cos值&#34;&#34;&#34;</span>
        <span class="n">topvalue</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">for</span> <span class="n">word</span><span class="p">,</span> <span class="n">count</span> <span class="ow">in</span> <span class="n">concordance1</span><span class="o">.</span><span class="n">iteritems</span><span class="p">():</span>
            <span class="k">if</span> <span class="n">concordance2</span><span class="o">.</span><span class="n">has_key</span><span class="p">(</span><span class="n">word</span><span class="p">):</span>
                <span class="n">topvalue</span> <span class="o">+=</span> <span class="n">count</span> <span class="o">*</span> <span class="n">concordance2</span><span class="p">[</span><span class="n">word</span><span class="p">]</span>
        <span class="k">return</span> <span class="n">topvalue</span> <span class="o">/</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">magnitude</span><span class="p">(</span><span class="n">concordance1</span><span class="p">)</span> <span class="o">*</span> <span class="bp">self</span><span class="o">.</span><span class="n">magnitude</span><span class="p">(</span><span class="n">concordance2</span><span class="p">))</span>
</code></pre></div><p>定义一个将图片转换为向量的方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">build_vector</span><span class="p">(</span><span class="n">im</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;将图片转换为矢量&#39;&#39;&#39;</span>
    <span class="n">vector</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">im</span><span class="o">.</span><span class="n">getdata</span><span class="p">():</span>
        <span class="n">vector</span><span class="p">[</span><span class="n">count</span><span class="p">]</span> <span class="o">=</span> <span class="n">i</span>
        <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">return</span> <span class="n">vector</span>
</code></pre></div><h3 id="建立样本库">建立样本库</h3>
<p>需要取大量验证码图片提取单个字符图片作为训练集合，这一部分工作可以利用 <a href="#二值化图片">二值化图片</a> 和 <a href="#分割图片">分割图片</a> 描述的方法，对搜集到的验证码进行处理的得到单个字符的图片，最终得到以下字符集对应的训练集合：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">charset</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;0&#39;</span><span class="p">,</span> <span class="s1">&#39;1&#39;</span><span class="p">,</span> <span class="s1">&#39;2&#39;</span><span class="p">,</span> <span class="s1">&#39;3&#39;</span><span class="p">,</span><span class="s1">&#39;4&#39;</span><span class="p">,</span> <span class="s1">&#39;5&#39;</span><span class="p">,</span> <span class="s1">&#39;6&#39;</span><span class="p">,</span> <span class="s1">&#39;7&#39;</span><span class="p">,</span> <span class="s1">&#39;8&#39;</span><span class="p">,</span> <span class="s1">&#39;9&#39;</span><span class="p">,</span> <span class="s1">&#39;0&#39;</span><span class="p">,</span> <span class="s1">&#39;a&#39;</span><span class="p">,</span> <span class="s1">&#39;b&#39;</span><span class="p">,</span> <span class="s1">&#39;c&#39;</span><span class="p">,</span> <span class="s1">&#39;d&#39;</span><span class="p">,</span> <span class="s1">&#39;e&#39;</span><span class="p">,</span> <span class="s1">&#39;f&#39;</span><span class="p">,</span> <span class="s1">&#39;g&#39;</span><span class="p">,</span> <span class="s1">&#39;h&#39;</span><span class="p">,</span> <span class="s1">&#39;i&#39;</span><span class="p">,</span> <span class="s1">&#39;j&#39;</span><span class="p">,</span> <span class="s1">&#39;k&#39;</span><span class="p">,</span> <span class="s1">&#39;l&#39;</span><span class="p">,</span> <span class="s1">&#39;m&#39;</span><span class="p">,</span> <span class="s1">&#39;n&#39;</span><span class="p">,</span> <span class="s1">&#39;o&#39;</span><span class="p">,</span> <span class="s1">&#39;p&#39;</span><span class="p">,</span> <span class="s1">&#39;q&#39;</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="s1">&#39;s&#39;</span><span class="p">,</span> <span class="s1">&#39;t&#39;</span><span class="p">,</span> <span class="s1">&#39;u&#39;</span><span class="p">,</span> <span class="s1">&#39;v&#39;</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">,</span> <span class="s1">&#39;x&#39;</span><span class="p">,</span> <span class="s1">&#39;y&#39;</span><span class="p">,</span> <span class="s1">&#39;z&#39;</span><span class="p">]</span>
</code></pre></div><p>这里就不演示怎么见库了，可以使用<a href="https://www.shiyanlou.com/teacher/8834">实验楼老师ekCit </a>建立好的样本库：<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/blog/pin_cracker/iconset.zip">iconset</a></p>
<p>从样本库中建立训练数据集：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">load_iconset</span><span class="p">(</span><span class="n">iconset</span><span class="p">,</span> <span class="n">dir_path</span><span class="o">=</span><span class="s1">&#39;./iconset&#39;</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;加载训练集数据&#39;&#39;&#39;</span>
    <span class="n">imageset</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">letter</span> <span class="ow">in</span> <span class="n">iconset</span><span class="p">:</span>
        <span class="n">letter_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dir_path</span><span class="p">,</span> <span class="n">letter</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">img</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">letter_dir</span><span class="p">):</span>
            <span class="n">temp</span> <span class="o">=</span> <span class="p">[]</span>
            <span class="k">if</span> <span class="s1">&#39;.gif&#39;</span> <span class="ow">in</span> <span class="n">img</span><span class="p">:</span>
                <span class="n">img_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">letter_dir</span><span class="p">,</span> <span class="n">img</span><span class="p">)</span>
                <span class="n">letter_image</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">img_path</span><span class="p">)</span>
                <span class="n">temp</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">build_vector</span><span class="p">(</span><span class="n">letter_image</span><span class="p">))</span>
                <span class="n">imageset</span><span class="o">.</span><span class="n">append</span><span class="p">({</span><span class="n">letter</span><span class="p">:</span> <span class="n">temp</span><span class="p">})</span>
    
    <span class="k">return</span> <span class="n">imageset</span>
</code></pre></div><h3 id="训练识别">训练识别</h3>
<p>有了样本库就可以进行训练识别了，下面是实现代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">guess_image_by</span><span class="p">(</span><span class="n">imageset</span><span class="p">,</span> <span class="n">bimg</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;根据样本数据识别&#39;&#39;&#39;</span>
    <span class="n">guess</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">v</span> <span class="o">=</span> <span class="n">VectorCompare</span><span class="p">()</span>

    <span class="k">for</span> <span class="n">img</span> <span class="ow">in</span> <span class="n">slice_image</span><span class="p">(</span><span class="n">bimg</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">image</span> <span class="ow">in</span> <span class="n">imageset</span><span class="p">:</span>
            <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="ow">in</span> <span class="n">image</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
                <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
                    <span class="n">guess</span><span class="o">.</span><span class="n">append</span><span class="p">((</span> <span class="n">v</span><span class="o">.</span><span class="n">relation</span><span class="p">(</span><span class="n">y</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="n">build_vector</span><span class="p">(</span><span class="n">img</span><span class="p">)),</span> <span class="n">x</span><span class="p">))</span>
        <span class="n">guess</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
        <span class="k">yield</span> <span class="n">guess</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</code></pre></div><h3 id="识别函数">识别函数</h3>
<p>综合上述，编写识别方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">image_to_string</span><span class="p">(</span><span class="n">filename</span><span class="p">):</span>
    <span class="n">bimg</span> <span class="o">=</span> <span class="n">get_binary_image</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
    <span class="n">iconset</span> <span class="o">=</span> <span class="p">[</span>
        <span class="s1">&#39;0&#39;</span><span class="p">,</span> <span class="s1">&#39;1&#39;</span><span class="p">,</span> <span class="s1">&#39;2&#39;</span><span class="p">,</span> <span class="s1">&#39;3&#39;</span><span class="p">,</span><span class="s1">&#39;4&#39;</span><span class="p">,</span> <span class="s1">&#39;5&#39;</span><span class="p">,</span> <span class="s1">&#39;6&#39;</span><span class="p">,</span> <span class="s1">&#39;7&#39;</span><span class="p">,</span> <span class="s1">&#39;8&#39;</span><span class="p">,</span> <span class="s1">&#39;9&#39;</span><span class="p">,</span>
        <span class="s1">&#39;0&#39;</span><span class="p">,</span> <span class="s1">&#39;a&#39;</span><span class="p">,</span> <span class="s1">&#39;b&#39;</span><span class="p">,</span> <span class="s1">&#39;c&#39;</span><span class="p">,</span> <span class="s1">&#39;d&#39;</span><span class="p">,</span> <span class="s1">&#39;e&#39;</span><span class="p">,</span> <span class="s1">&#39;f&#39;</span><span class="p">,</span> <span class="s1">&#39;g&#39;</span><span class="p">,</span> <span class="s1">&#39;h&#39;</span><span class="p">,</span> <span class="s1">&#39;i&#39;</span><span class="p">,</span> 
        <span class="s1">&#39;j&#39;</span><span class="p">,</span> <span class="s1">&#39;k&#39;</span><span class="p">,</span> <span class="s1">&#39;l&#39;</span><span class="p">,</span> <span class="s1">&#39;m&#39;</span><span class="p">,</span> <span class="s1">&#39;n&#39;</span><span class="p">,</span> <span class="s1">&#39;o&#39;</span><span class="p">,</span> <span class="s1">&#39;p&#39;</span><span class="p">,</span> <span class="s1">&#39;q&#39;</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="s1">&#39;s&#39;</span><span class="p">,</span> 
        <span class="s1">&#39;t&#39;</span><span class="p">,</span> <span class="s1">&#39;u&#39;</span><span class="p">,</span> <span class="s1">&#39;v&#39;</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">,</span> <span class="s1">&#39;x&#39;</span><span class="p">,</span> <span class="s1">&#39;y&#39;</span><span class="p">,</span> <span class="s1">&#39;z&#39;</span>
    <span class="p">]</span>
    <span class="n">imageset</span> <span class="o">=</span> <span class="n">load_iconset</span><span class="p">(</span><span class="n">iconset</span><span class="p">)</span>
    <span class="n">s</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>
    <span class="k">for</span> <span class="n">guess_tuple</span> <span class="ow">in</span> <span class="n">guess_image_by</span><span class="p">(</span><span class="n">imageset</span><span class="p">,</span> <span class="n">bimg</span><span class="p">):</span>
        <span class="n">s</span> <span class="o">+=</span> <span class="n">guess_tuple</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">s</span>
</code></pre></div><h3 id="测试">测试</h3>
<p>编写主函数内容:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">image_to_string</span><span class="p">(</span><span class="s1">&#39;code.gif&#39;</span><span class="p">))</span>
</code></pre></div><p>最终脚本内容参考：<a href="https://github.com/smslit/tools-with-script/blob/master/pin2chars/pin_cracker.py">tools-with-script/pin2chars/pin_cracker.py</a></p>
<p>执行结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pin_cracker.py
777ttt
</code></pre></div><p>恭喜我，以失败告终，崩溃。不过思路是对的，不知道哪里出了问题，先不研究了，主要目的还是练习python编程，有时间再说吧！</p>
<h2 id="编写验证码识别器">编写验证码识别器</h2>
<h3 id="实现">实现</h3>
<p>综合上面第三方库和自己实现的库，python3 实现一个命令行小工具，新建名为 <code>pin2chars</code> 的文件，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/bin/env python3</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">import</span> <span class="nn">pin_cracker</span>
<span class="kn">import</span> <span class="nn">pytesseract</span>
<span class="kn">import</span> <span class="nn">tesseract_ocr</span>
<span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>


<span class="k">def</span> <span class="nf">get_args</span><span class="p">():</span>
    <span class="s1">&#39;&#39;&#39;得到命令参数
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-m&#39;</span><span class="p">,</span> <span class="s2">&#34;--method&#34;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;选择方法进行验证码的识别:  0. 使用 pin_cracker 库方法识别验证码; 1. 使用 pytesseract 库方法识别验证码; 2. 使用 tesseract_ocr 库方法识别验证码;&#39;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;imgfile&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定要识别的验证码图片&#39;</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>


<span class="k">def</span> <span class="nf">init_methods</span><span class="p">():</span>
    <span class="s1">&#39;&#39;&#39;初始化方法列表
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">methods</span> <span class="o">=</span> <span class="p">[</span>
        <span class="p">{</span>
            <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">pin_cracker</span><span class="o">.</span><span class="n">image_to_string</span><span class="p">,</span>
            <span class="s1">&#39;tip&#39;</span><span class="p">:</span> <span class="s1">&#39;pin_cracker 库的识别结果：&#39;</span>
        <span class="p">},</span>
        <span class="p">{</span>
            <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">pytesseract</span><span class="o">.</span><span class="n">image_to_string</span><span class="p">,</span>
            <span class="s1">&#39;tip&#39;</span><span class="p">:</span> <span class="s1">&#39;pytesseract 库的识别结果：&#39;</span>
        <span class="p">},</span>
        <span class="p">{</span>
            <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">tesseract_ocr</span><span class="o">.</span><span class="n">text_for_filename</span><span class="p">,</span>
            <span class="s1">&#39;tip&#39;</span><span class="p">:</span> <span class="s1">&#39;tesseract_ocr 库的识别结果：&#39;</span>
        <span class="p">}</span>
    <span class="p">]</span>

    <span class="k">return</span> <span class="n">methods</span>


<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">get_args</span><span class="p">()</span>
    <span class="n">methods</span> <span class="o">=</span> <span class="n">init_methods</span><span class="p">()</span>
    <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">imgfile</span><span class="p">):</span>
        <span class="k">if</span> <span class="s1">&#39;.gif&#39;</span> <span class="ow">in</span> <span class="n">args</span><span class="o">.</span><span class="n">imgfile</span><span class="p">:</span> <span class="c1"># 避免 tesseract 读取 gif 图片出问题</span>
            <span class="n">filename</span> <span class="o">=</span> <span class="s1">&#39;b_code.png&#39;</span>
            <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">imgfile</span><span class="p">)</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="s1">&#39;P&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
            <span class="n">args</span><span class="o">.</span><span class="n">imgfile</span> <span class="o">=</span> <span class="n">filename</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">methods</span><span class="p">[</span><span class="n">args</span><span class="o">.</span><span class="n">method</span><span class="p">][</span><span class="s1">&#39;tip&#39;</span><span class="p">],</span> <span class="n">methods</span><span class="p">[</span><span class="n">args</span><span class="o">.</span><span class="n">method</span><span class="p">][</span><span class="s1">&#39;method&#39;</span><span class="p">](</span><span class="n">args</span><span class="o">.</span><span class="n">imgfile</span><span class="p">))</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;请给定有效图片路径！&#39;</span><span class="p">)</span>

</code></pre></div><h3 id="测试-1">测试</h3>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="o">(</span>pin2chars-E2eD5P-4<span class="o">)</span> ➜  pin2chars git:<span class="o">(</span>master<span class="o">)</span> ✗ ./pin2chars -m<span class="o">=</span><span class="m">0</span> code.gif
pin_cracker 库的识别结果： 777ttt
<span class="o">(</span>pin2chars-E2eD5P-4<span class="o">)</span> ➜  pin2chars git:<span class="o">(</span>master<span class="o">)</span> ✗ ./pin2chars -m<span class="o">=</span><span class="m">1</span> code.gif
pytesseract 库的识别结果： 7S9T9J’
<span class="o">(</span>pin2chars-E2eD5P-4<span class="o">)</span> ➜  pin2chars git:<span class="o">(</span>master<span class="o">)</span> ✗ ./pin2chars -m<span class="o">=</span><span class="m">2</span> code.gif
Info in pixReadStreamPng: converting <span class="o">(</span>cmap + alpha<span class="o">)</span> <span class="o">==</span>&gt; RGBA
Info in pixReadStreamPng: converting <span class="m">8</span> bpp cmap with <span class="nv">alpha</span> <span class="o">==</span>&gt; RGBA
Warning. Invalid resolution <span class="m">0</span> dpi. Using <span class="m">70</span> instead.
tesseract_ocr 库的识别结果： 7S9T9J’
</code></pre></div><h2 id="总结">总结</h2>
<p>本文实现了一个简单的验证码识别器，使用了三个方法，前两种使用现有库，后一种使用了分割图片 + 向量识别的方式简单实现了验证码的识别，通过本文熟悉了 <strong>argparse</strong>、<strong>pillow</strong> 等库的使用，通过实践练习了python的基本使用，Peace！这里的方法明显处理不了更加复杂的验证码，要想提升方法的适应性，还得增加新的判别方法，后续有时间的话再研究吧。</p>]]></content>
		</item>
		
		<item>
			<title>PUBG &amp; ROE, See you later</title>
			<link>https://blog.5km.studio/2018/10/02/pubg_roe_bye/</link>
			<pubDate>Tue, 02 Oct 2018 23:24:41 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/10/02/pubg_roe_bye/</guid>
			<description>&lt;p&gt;今年八月初，第一次真正的玩PC大型游戏，以前也就玩个 &lt;strong&gt;愤怒的小鸟&lt;/strong&gt; 这种小游戏，吃鸡（绝地求生：大逃杀&lt;PUBG&gt;）正🔥，咱也掺和掺和，所以既是PC端游戏菜鸟，又是PUBG专业快递员。9月底腾讯无限法则（ROE）在steam上上市了，作为一个合格的好奇宝宝，我当然要去里面滑滑❄️啦，所以也算玩过ROE了，技术依旧烂的一笔。嗯&amp;hellip;&amp;hellip;可是当初势必要练得“走位骚、刚强猛、爆头准”的决心随着心境的变化已经慢慢淡去，所以呀，开此文祭奠这即将逝去的 &lt;strong&gt;Game Pleasure&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181002153849597149140.jpg&#34; alt=&#34;20181002153849597149140.jpg&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今年八月初，第一次真正的玩PC大型游戏，以前也就玩个 <strong>愤怒的小鸟</strong> 这种小游戏，吃鸡（绝地求生：大逃杀<PUBG>）正🔥，咱也掺和掺和，所以既是PC端游戏菜鸟，又是PUBG专业快递员。9月底腾讯无限法则（ROE）在steam上上市了，作为一个合格的好奇宝宝，我当然要去里面滑滑❄️啦，所以也算玩过ROE了，技术依旧烂的一笔。嗯&hellip;&hellip;可是当初势必要练得“走位骚、刚强猛、爆头准”的决心随着心境的变化已经慢慢淡去，所以呀，开此文祭奠这即将逝去的 <strong>Game Pleasure</strong>！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181002153849597149140.jpg" alt="20181002153849597149140.jpg"></p>
<h2 id="缘起">缘起</h2>
<p>说起来，我玩PUBG的缘起还是比较奇怪的，本人过去比较痴迷于红白机🎮NES🎮游戏，macbook上安装了个开源的模拟器 <a href="https://github.com/OpenEmu/OpenEmu"><strong>OpenEMU</strong></a>，专门玩这些像素游戏。当然这不是本文的重点，重点是我没料到被弟弟带着玩起了手机版的PUBG——<strong>绝地求生：刺激战场</strong>。随后就闲着的时候就会玩个几局，非常喜欢这个游戏，入门简单，操作也不复杂，更不需要记一些人物属性什么的（曾被朋友带着玩《魔兽世界》，感觉需要知道的东西太多，玩的乐趣都木有了），百人抢物资进圈求生的模式非常吸引我，首先对这款游戏我就有了好感！</p>
<p>之前在鼎信工作的时候，就听一些同事朋友说吃鸡吃鸡什么的，当时也没起多大的兴趣，没想到玩了手机版的还挺好玩！那应该也会有PC版的喽，网上了解过后，原来是steam平台的游戏，只有PC版，木有mac版，好纠结，怎么办，我真想体验一下，哈哈，幸好我之前做了一个我的macbook的移动硬盘版的 <a href="https://en.wikipedia.org/wiki/Windows_To_Go"><strong>WTG</strong></a>，启动进入 WTG 的系统，安装steam，毫不犹豫的花98大洋买了这款游戏，并安装了。我买的macbook是16版的15寸带touchbar版本的基础款，所以拥有独立显卡，勉强是可以玩这款游戏的！</p>
<p>第一次玩，有了手机版游戏的基础，对于基本流程、地区样貌和枪械知识都有一定基础，还是挺顺利的苟进了前20，<strong>but</strong>，见了人必死，被人打了都不知道敌方在哪儿，一开始玩这个游戏就晋升为合格的 <strong>快递员</strong>，偶尔落地成个盒子。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181003153849926392053.jpg" alt="20181003153849926392053.jpg"></p>
<h2 id="发展">发展</h2>
<p>随着对游戏的关注的增加，以及与发小和同学朋友的交流，了解到很多PUBG的事情，慢慢的自己也练就了听声辨位，枪法依旧不准但是比以前稳了，最起码不是刚开始的一扫冲天了，😅！</p>
<p>慢慢的我知道了国内的4AM、OMG等战队，也知道了lion_kk、韦神、17shou和shroud等大神，偶尔会看他们的游戏录播视频，感觉与他们相比，我简直就是菜鸟中的矮矬穷。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181003153849866765466.jpg" alt="20181003153849866765466.jpg"></p>
<p>时不时的还让发小带带我，结果最后的结论就是，我发小觉得更我一起玩PUBG的双人模式，就像是玩1.5人模式一样，有一次尴尬到想放烟救他，结果放出🔥，把他烧死了。笑归笑，我还是想尽快提高自己的技术呀，真的不想被嘲笑！所以我有空就会看shroud的视频，学会了一些骚走位，慢慢的也有了一定观察意识，枪法进步龟速。得益于发小的指点，开镜操作变顺畅了很多，慢慢自己心里立下了一个目标，能在比赛中刚强杀死人。</p>
<p>关注游戏到什么程度呢！微博、twitter我都关注了PUBG，没有更新维护，我都很兴奋，真的很喜欢这款游戏！为了更好的发挥macbook的性能，我还是安装了bootcamp的windows，windows中安装了游戏。</p>
<p>慢慢从发小口中知道，腾讯做了一款仿吃鸡的游戏，叫做无限法则（ROE），我也慢慢关注，一开始的时候，ROE只有泰服，但上个月，也就是9月19日，雪地版的ROE上线steam了，我也第一时间体验了一把，还跟发小一起玩了，非常有意思的是可以滑雪、滑翔和攀岩，一些细节做的挺好，比如毒圈成了暴风雪和雪崩造成的低温伤害、人物可以捏脸、低温也会影响枪械和载具的性能等。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20181003153849944180371.jpg" alt="20181003153849944180371.jpg"></p>
<h2 id="淡去">淡去</h2>
<p>玩PUBG快两个月了，有一定成果：敲键盘的手速提高了，学会了一些走位，枪法提高了一丢丢等，但慢慢游戏玩的不多了，原来那种强烈的要练好自己技术的决心越来越小了，归结有以下原因吧：</p>
<ul>
<li>自己老成盒子，信心丧失，失去了原有的斗志</li>
<li>水木其实不喜欢我玩游戏，我也慢慢克制，玩的变少了</li>
<li>自己一个人玩有点无趣，同学朋友经常凑不起来一起玩，慢慢变得没意思</li>
<li>前面确实玩游戏分精力了，还是得把心思放到正事儿上，玩游戏只作放松，不会痴迷了</li>
<li>如果今天就是我的最后一天，我会选择陪家人，而不是自己打游戏</li>
</ul>
<p>所以今晚毅然决然的把中途安装的bootcamp的windows分区删掉了，以后条件允许了买个游戏主机再玩吧！再会，<strong>PUBG</strong> 和 <strong>ROE</strong>！</p>]]></content>
		</item>
		
		<item>
			<title>macOS下lldb遇到_remove_dead_weakref的Import_Error</title>
			<link>https://blog.5km.studio/2018/09/30/lldb_weakref_error/</link>
			<pubDate>Sun, 30 Sep 2018 12:01:47 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/09/30/lldb_weakref_error/</guid>
			<description>&lt;p&gt;今天使用lldb遇到了_weakref的Import_Error问题，找了很久才找到合适的解决方法，开此文分享一下。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;$ lldb
&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;lldb&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; script
Traceback &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;most recent call last&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;:
  File &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;lt;input&amp;gt;&amp;#34;&lt;/span&gt;, line 1, in &amp;lt;module&amp;gt;
  File &lt;span class=&#34;s2&#34;&gt;&amp;#34;/usr/local/Cellar/python/2.7.15/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy.py&amp;#34;&lt;/span&gt;, line 52, in &amp;lt;module&amp;gt;
    import weakref
  File &lt;span class=&#34;s2&#34;&gt;&amp;#34;/usr/local/Cellar/python/2.7.15/Frameworks/Python.framework/Versions/2.7/lib/python2.7/weakref.py&amp;#34;&lt;/span&gt;, line 14, in &amp;lt;module&amp;gt;
    from _weakref import &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;
ImportError: cannot import name _remove_dead_weakref
Python Interactive Interpreter. To exit, &lt;span class=&#34;nb&#34;&gt;type&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;quit()&amp;#39;&lt;/span&gt;, &lt;span class=&#34;s1&#34;&gt;&amp;#39;exit()&amp;#39;&lt;/span&gt; or Ctrl-D.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
			<content type="html"><![CDATA[<p>今天使用lldb遇到了_weakref的Import_Error问题，找了很久才找到合适的解决方法，开此文分享一下。</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ lldb
<span class="o">(</span>lldb<span class="o">)</span> script
Traceback <span class="o">(</span>most recent call last<span class="o">)</span>:
  File <span class="s2">&#34;&lt;input&gt;&#34;</span>, line 1, in &lt;module&gt;
  File <span class="s2">&#34;/usr/local/Cellar/python/2.7.15/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy.py&#34;</span>, line 52, in &lt;module&gt;
    import weakref
  File <span class="s2">&#34;/usr/local/Cellar/python/2.7.15/Frameworks/Python.framework/Versions/2.7/lib/python2.7/weakref.py&#34;</span>, line 14, in &lt;module&gt;
    from _weakref import <span class="o">(</span>
ImportError: cannot import name _remove_dead_weakref
Python Interactive Interpreter. To exit, <span class="nb">type</span> <span class="s1">&#39;quit()&#39;</span>, <span class="s1">&#39;exit()&#39;</span> or Ctrl-D.
</code></pre></div><p>看到错误提示中有个关键字段 <code>/usr/local/Cellar/python/2.7.15</code> ，我们知道macOS下 <strong>homebrew</strong> 安装软件到 <code>/usr/local/Cellar</code> 目录下，所以猜测是不是自己安装的 <strong>python2.7.15</strong> 的问题。</p>
<p>那尝试卸载已安装的 <strong>python2.7.15</strong>：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">brew uninstall python@2
</code></pre></div><p>结果得到提醒：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">Error: Refusing to uninstall /usr/local/Cellar/python@2/2.7.15_1
because it is required by mongodb, which is currently installed.
You can override this and force removal with:
  brew uninstall --ignore-dependencies python@2
</code></pre></div><p>哎呀，得到提示有依赖问题，没事儿！先按照 <code>brew uninstall --ignore-dependencies python@2</code> 强制卸载看看是不是这个版本python与lldb不兼容，卸载后，运行lldb，果然没有错误了！</p>
<p>看来真的是版本不兼容的问题，但是我还必须安装 <strong>python2.7.15</strong>，因为 <strong>mongodb</strong> 依赖最新的这个版本的python，还必须安装这个版本python，怎么办？</p>
<p>求助google，找到方法，强制 <strong>lldb</strong> 按照系统的 bin 环境运行——<code>PATH=/usr/bin /usr/bin/lldb</code>^[<a href="http://qaru.site/questions/1690278/strange-mixing-of-system-homebrew-python-with-lldb">Странное смешивание системы + homebrew Python с LLDB</a>]，就不会出现问题了。</p>
<p>每次这样运行 <strong>lldb</strong> 太麻烦了，何不 <strong>alias</strong> 一下，在终端下执行：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="c1"># 如果您使用bash，执行：</span>
<span class="c1"># echo &#39;alias lldb=&#34;PATH=/usr/bin /usr/bin/lldb&#34;&#39; &gt;&gt; ~/.bashrc</span>
<span class="c1"># 如果您使用zsh，执行：</span>
<span class="nb">echo</span> <span class="s1">&#39;alias lldb=&#34;PATH=/usr/bin /usr/bin/lldb&#34;&#39;</span> &gt;&gt; ~/.zshrc
</code></pre></div><p>开启一个新的终端，运行 <code>lldb</code> 没有异常，Done！</p>]]></content>
		</item>
		
		<item>
			<title>python3实现图片转换为字符画</title>
			<link>https://blog.5km.studio/2018/09/14/image2charspic/</link>
			<pubDate>Fri, 14 Sep 2018 17:04:01 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/09/14/image2charspic/</guid>
			<description>&lt;p&gt;记得之前看到过有人做了一个小工具，可以将图片用字符展示出来，就是所谓的 &lt;strong&gt;字符画&lt;/strong&gt; 了，今天咱也用python3实现这么一个有趣的小工具。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180914153691718315759.png&#34; alt=&#34;20180914153691718315759.png&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>记得之前看到过有人做了一个小工具，可以将图片用字符展示出来，就是所谓的 <strong>字符画</strong> 了，今天咱也用python3实现这么一个有趣的小工具。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180914153691718315759.png" alt="20180914153691718315759.png"></p>
<h2 id="原理">原理</h2>
<p>首先理一下思路：</p>
<ol>
<li>用字符画图，其实就是用字符替代像素点，所以得有一个字符表；</li>
<li>字符是偏大的为了更好的呈现字符画的状态，还应该调整图片大小为更低像素，比如80*80</li>
<li>一幅图片一般情况下就是RGB位图，但是纯文本字符没有色彩可言，所以需要将图片转换为灰度图；</li>
<li>灰度图一般时8位，取值范围会是0～255，255的值是白色，所以字符表是空格效果会好一些；</li>
<li>这里使用半角字符作为基本元素，为了是字符画更规整，使用两个一样的半角字符代替一个像素，字符表里的元素位两个一样的字符；</li>
<li>需要建立图片灰度值与字符表的映射关系，这里使用灰度值映射表索引取字符，可用以下公式计算索引：
$$
index = gray / 256 * length
$$
<ul>
<li><strong>index</strong>: 元素索引</li>
<li><strong>gray</strong>: 像素点的灰度值</li>
<li><strong>length</strong>: 字符表的长度</li>
</ul>
</li>
</ol>
<h2 id="实现思路">实现思路</h2>
<p>本文目标是设计一个命令行小工具，使用命令的时候，需要制定图片的路径，可以指定宽高和保存目录，指定按照上述原理，实现思路也就非常明了：</p>
<ol>
<li>建立字符表；</li>
<li>获取指定的命令参数：
<ul>
<li>图片路径；</li>
<li>图片宽高；</li>
<li>字符画保存路径；</li>
</ul>
</li>
<li>读取图片，按照指定尺寸转换图片大小；</li>
<li>将图片转换为灰度图；</li>
<li>按行读取像素灰度值获取字符追加到结果字符串中，每读一行追加一个换行；</li>
<li>保存结果字符串为文本文件；</li>
</ol>
<p>根据上述思路，会使用以下库：</p>
<ul>
<li><strong>pillow</strong>：处理图片并获取图片数据；</li>
<li><strong>argparse</strong>：放便过滤命令参数，比如指定图片、保存目录，宽高等</li>
</ul>
<h2 id="实现过程">实现过程</h2>
<h3 id="安装库">安装库</h3>
<p><strong>argparse</strong> 库是官方库，不用安装，这里只需安装 <strong>pillow</strong> 库：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pip3 install pillow
</code></pre></div><p><strong>注：</strong>
可以阅读 <a href="/2018/09/13/python-argparse/"><strong>argparse 库的使用</strong></a> 了解 <strong>argparse</strong> 库的使用！</p>
<h3 id="实现">实现</h3>
<h4 id="准备文件">准备文件</h4>
<p>创建文件 <strong>img2ascii</strong>，然后添加以下内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/local/bin/python3</span>
<span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>
<span class="kn">import</span> <span class="nn">argparse</span>
</code></pre></div><p>第一行指明脚本解释器的路径，后两行导入所需库，然后为文件指定执行权限：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ chmod +x img2ascii
</code></pre></div><p>这样就可以通过以下方式执行脚本了：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./img2ascii
</code></pre></div><h4 id="添加参数解析实现">添加参数解析实现</h4>
<p>添加四个参数：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s1">&#39;此工具可以将图片转换为字符画。&#39;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;file&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;待转换的图片路径&#39;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-o&#39;</span><span class="p">,</span> <span class="s1">&#39;--output&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;指定生成字符画的保存路径&#39;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-W&#39;</span><span class="p">,</span> <span class="s1">&#39;--width&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">80</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;可以通过此参数指定图片转换大小到一定宽度，单位：像素，默认值：80&#39;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-H&#39;</span><span class="p">,</span> <span class="s1">&#39;--height&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">80</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;可以通过此参数指定图片转换大小到一定高度，单位：像素，默认值：80&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>


<span class="n">IMG</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">file</span>
<span class="n">WIDTH</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">width</span>
<span class="n">HEIGHT</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">height</span>
<span class="n">OUTPUT</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">output</span>
</code></pre></div><h4 id="生成字符表">生成字符表</h4>
<p>构建需要的字符表，其实这个表里的字符可以任意添加：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">ascii_chars</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="s2">&#34;aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ!@#$%^&amp;*()_+|?&gt;&lt;.,:;~`1234567890 &#34;</span><span class="p">)</span>
<span class="n">ascii_table</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="o">*</span><span class="mi">2</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">ascii_chars</span><span class="p">]</span>
<span class="n">TABLE_LENGTH</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">ascii_table</span><span class="p">)</span>
</code></pre></div><h4 id="创建映射函数">创建映射函数</h4>
<p>映射函数用于获取对应像素点上应该放置的字符串：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">getChars</span><span class="p">(</span><span class="n">gray</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">gray</span> <span class="o">==</span> <span class="mi">255</span><span class="p">:</span>
        <span class="k">return</span> <span class="s1">&#39;  &#39;</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">ascii_table</span><span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">gray</span> <span class="o">/</span> <span class="mi">256</span>  <span class="o">*</span> <span class="n">TABLE_LENGTH</span><span class="p">)]</span>
</code></pre></div><h4 id="打开图像并灰度化">打开图像并灰度化</h4>
<p>使用 <strong>pillow</strong> 库打开图片，将图片转换为灰度图：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">im</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">IMG</span><span class="p">)</span>
<span class="n">im</span> <span class="o">=</span> <span class="n">im</span><span class="o">.</span><span class="n">resize</span><span class="p">((</span><span class="n">WIDTH</span><span class="p">,</span> <span class="n">HEIGHT</span><span class="p">),</span> <span class="n">Image</span><span class="o">.</span><span class="n">NEAREST</span><span class="p">)</span>
<span class="n">im</span> <span class="o">=</span> <span class="n">im</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="s1">&#39;L&#39;</span><span class="p">)</span>
<span class="n">txt</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>
</code></pre></div><h4 id="生成字符画">生成字符画</h4>
<p>通过读取每个像素点的灰度值，获取替代字符串，生成字符画：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">HEIGHT</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">WIDTH</span><span class="p">):</span>
        <span class="n">gray</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">im</span><span class="o">.</span><span class="n">getpixel</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)))</span>
        <span class="n">txt</span> <span class="o">+=</span> <span class="n">getChars</span><span class="p">(</span><span class="n">gray</span><span class="p">)</span>
    <span class="n">txt</span> <span class="o">+=</span> <span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span>
</code></pre></div><h4 id="导出字符画文件">导出字符画文件</h4>
<p>将生成的字符画字符串以文件的形式保存到指定路径：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="n">OUTPUT</span><span class="p">:</span>
    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">OUTPUT</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">txt</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">&#39;output.txt&#39;</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">txt</span><span class="p">)</span>
</code></pre></div><p><strong>注：</strong></p>
<p><a href="https://github.com/smslit/tools-with-script/blob/master/img2ascii/img2ascii"><strong>点我</strong></a> 查看完整代码！</p>
<h3 id="测试效果">测试效果</h3>
<p>这里以图片 <a href="https://www.smslit.top/android-chrome-512x512.png"><strong>android-chrome-512x512.png</strong></a> 为例：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./img2ascii android-chrome-512x512.png
</code></pre></div><p>这时会看到图片同级目录下出现 <strong>output.txt</strong> ，打开后就会看到字符画了，尝试加入宽高参数的指定：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./img2ascii android-chrome-512x512.png -W <span class="m">20</span> -H <span class="m">20</span>
</code></pre></div><p>得到如下字符画：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180914153693707997013.png" alt="20180914153693707997013.png"></p>
<h2 id="总结">总结</h2>
<p>终于完成了一个简单的工具可以帮助我们得到图片对应的字符画，应该有更好的实现方式，如果以后发现会继续在博客中分享，欢迎持续关注！</p>]]></content>
		</item>
		
		<item>
			<title>argparse 库的使用</title>
			<link>https://blog.5km.studio/2018/09/13/python-argparse/</link>
			<pubDate>Thu, 13 Sep 2018 15:16:37 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/09/13/python-argparse/</guid>
			<description>&lt;p&gt;如果您接触过命令行的话，一定感受过命令工具的使用，会有个参数的概念，比如以列表的形式列出当前目录下的所有可见文件：&lt;code&gt;ls -l&lt;/code&gt;。进行python开发的时候，也许最终会以命令行工具的形式工作，可能需要对命令参数进行处理，那么可以使用 &lt;code&gt;sys.argv&lt;/code&gt; 获取命令输入的参数列表，进行分析判断做相应处理，但随着参数项增多，这种方式会越来越复杂，而本文将要介绍的 &lt;strong&gt;argparse&lt;/strong&gt; 库，会使参数的管理变得优雅，下面一起看一下怎么使用吧！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>如果您接触过命令行的话，一定感受过命令工具的使用，会有个参数的概念，比如以列表的形式列出当前目录下的所有可见文件：<code>ls -l</code>。进行python开发的时候，也许最终会以命令行工具的形式工作，可能需要对命令参数进行处理，那么可以使用 <code>sys.argv</code> 获取命令输入的参数列表，进行分析判断做相应处理，但随着参数项增多，这种方式会越来越复杂，而本文将要介绍的 <strong>argparse</strong> 库，会使参数的管理变得优雅，下面一起看一下怎么使用吧！</p>
<p>本文就不用解释这个参数的概念了吧，相信您对参数这个概念有了自己的感受，开发一个属于自己的命令工具一定很酷，Let&rsquo;s do it!</p>
<h2 id="初步感受">初步感受</h2>
<p>本文以python3开发为例，以一个简单的示例开始，创建文件 <strong>pug.py</strong> ，代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</code></pre></div><p>我们运行初步感受一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py
$ python3 pug.py --help
usage: pug.py <span class="o">[</span>-h<span class="o">]</span>

optional arguments:
  -h, --help  show this <span class="nb">help</span> message and <span class="nb">exit</span>
$ python3 pug.py --verbose
usage: pug.py <span class="o">[</span>-h<span class="o">]</span>
pug.py: error: unrecognized arguments: --verbose
$ python3 pug.py foo
usage: pug.py <span class="o">[</span>-h<span class="o">]</span>
pug.py: error: unrecognized arguments: foo
</code></pre></div><p>看看发生了什么：</p>
<ul>
<li>直接运行文件，不加任何参数，没有任何处理；</li>
<li>第二次执行加了参数 <strong>&ndash;help</strong> 会打印一定的帮助信息，这个帮助信息中可以看到 <strong>&ndash;help</strong>可以简写为 <strong>-h</strong>；</li>
<li>第三次和第四次执行使用了不支持的参数，打印了错误提醒；</li>
</ul>
<p><strong>parser.parse_args()</strong> 就是参数解析的过程。</p>
<h2 id="positional-arguments-的使用">Positional arguments 的使用</h2>
<p><strong>Positional arguments</strong> 怎么翻译比较好呢？看到网上有人叫它 <strong>定位参数</strong>，我咋觉得叫它 <strong>占位参数</strong> 会比较贴切呢，无所谓了，还是用它本名 <strong>Positional arguments</strong> 吧！</p>
<p>更改pug.py代码为：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;echo&#34;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">echo</span><span class="p">)</span>
</code></pre></div><p>执行看看：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="nb">echo</span>
pug.py: error: the following arguments are required: <span class="nb">echo</span>
$ python3 pug.py -h
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="nb">echo</span>

positional arguments:
  <span class="nb">echo</span>

optional arguments:
  -h, --help  show this <span class="nb">help</span> message and <span class="nb">exit</span>
$ python3 pug.py yeah
yeah
</code></pre></div><p>可以看到在方法 <strong>add_argument</strong> 的帮助下 <strong>echo</strong> 成功上位，使用命令时必须为echo位置上输上点什么，它才罢休，这里可以简单地认为echo是一个必须赋值的变量。似乎少了点这个参数的帮助信息，不要紧！有方法 <strong>add_argument</strong> 的加持啥都不怕，此方法有个参数 <strong>help</strong>，为其指定内容就可以填充帮助信息了，比如这样：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;echo&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;学您说话！&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">echo</span><span class="p">)</span>
</code></pre></div><p>运行看看：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py -h
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="nb">echo</span>

positional arguments:
  <span class="nb">echo</span>        学您说话！

optional arguments:
  -h, --help  show this <span class="nb">help</span> message and <span class="nb">exit</span>
</code></pre></div><p>下面，我们不能让pug太调皮，得干点实用的事，比如让它算数，那就让它算一个指定整数的平方吧，修改代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;square&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;您说个数，我就能告诉您这个数的平方！&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">square</span><span class="o">**</span><span class="mi">2</span><span class="p">)</span>
</code></pre></div><p>执行一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py <span class="m">2</span>
Traceback <span class="o">(</span>most recent call last<span class="o">)</span>:
  File <span class="s2">&#34;pug.py&#34;</span>, line 5, in &lt;module&gt;
    print<span class="o">(</span>args.square**2<span class="o">)</span>
TypeError: unsupported operand type<span class="o">(</span>s<span class="o">)</span> <span class="k">for</span> ** or pow<span class="o">()</span>: <span class="s1">&#39;str&#39;</span> and <span class="s1">&#39;int&#39;</span>
</code></pre></div><p>出错啦！为什么？因为参数默认都是字符串，小狗子pug肯定不会计算字符串的平方吧！<strong>add_argument</strong> 方法此时笑了，不用担心，它可以为您按类型解析这个参数的值，只需指定方法的参数 <strong>type</strong> 就可以了：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;square&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;您说个数，我就能告诉您这个数的平方！&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">square</span><span class="o">**</span><span class="mi">2</span><span class="p">)</span>
</code></pre></div><p>试一下效果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py <span class="m">2</span>
<span class="m">4</span>
$ python3 pug.py -h
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> square

positional arguments:
  square      您说个数，我就能告诉您这个数的平方！

optional arguments:
  -h, --help  show this <span class="nb">help</span> message and <span class="nb">exit</span>
</code></pre></div><p>小狗子pug可以的！</p>
<h2 id="optional-arguments-的使用">Optional arguments 的使用</h2>
<p>下面一起看一下可选参数（<strong>Optional arguments</strong>）的使用。</p>
<p>代码更改为：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;--sleep&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;让我睡会儿，困死了！&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;我睡着了(-_-)zzz，别打搅我！&#39;</span><span class="p">)</span>
</code></pre></div><p>看一下小狗子有多懒：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py -h
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>--sleep SLEEP<span class="o">]</span>

optional arguments:
  -h, --help     show this <span class="nb">help</span> message and <span class="nb">exit</span>
  --sleep SLEEP  让我睡会儿，困死了！
$ python3 pug.py --sleep <span class="m">1</span>
我睡着了<span class="o">(</span>-_-<span class="o">)</span>zzz，别打搅我！
</code></pre></div><p>简单来说，对于睡觉，要么睡着要么没睡，应该是二值，那可以限制这个参数 <strong>&ndash;sleep</strong> 要么 <strong>True</strong> 要么 <strong>False</strong>，方法 <strong>add_argument</strong> 提供了设置参数 <strong>action</strong> ，设置其为 <strong>store_true</strong> 即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;--sleep&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;让我睡会儿，困死了！&#39;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;store_true&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;我睡着了(-_-)zzz，别打搅我！&#39;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;我很清醒，不想睡觉！&#39;</span><span class="p">)</span>
</code></pre></div><p>逗一下小狗子pug：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py -h
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>--sleep<span class="o">]</span>

optional arguments:
  -h, --help  show this <span class="nb">help</span> message and <span class="nb">exit</span>
  --sleep     让我睡会儿，困死了！
$ python3 pug.py --sleep <span class="m">1</span>
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>--sleep<span class="o">]</span>
pug.py: error: unrecognized arguments: <span class="m">1</span>
$ python3 pug.py --sleep
我睡着了<span class="o">(</span>-_-<span class="o">)</span>zzz，别打搅我！
$ python3 pug.py
我很清醒，不想睡觉！
</code></pre></div><p>可以看到，运行命令时，如果指定参数 <strong>&ndash;sleep</strong>，那么这个参数值就为 <strong>True</strong>， 如果没有，那么参数值为 <strong>False</strong>。如果您了解命令行工具的使用一定会问：短参数的实现会是怎样的？其实很简单：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-s&#39;</span><span class="p">,</span> <span class="s2">&#34;--sleep&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;让我睡会儿，困死了！&#39;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;store_true&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;我睡着了(-_-)zzz，别打搅我！&#39;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;我很清醒，不想睡觉！&#39;</span><span class="p">)</span>
</code></pre></div><p>叫小狗子pug试一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py -h
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-s<span class="o">]</span>

optional arguments:
  -h, --help   show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -s, --sleep  让我睡会儿，困死了！
$ python3 pug.py -s
我睡着了<span class="o">(</span>-_-<span class="o">)</span>zzz，别打搅我！
</code></pre></div><h2 id="positional-arguments-和-optional-arguments-结合使用">Positional arguments 和 Optional arguments 结合使用</h2>
<p>让小狗子睡着觉算数会是咋样的，试一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;square&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;您说个数，我就能告诉您这个数的平方！&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-s&#39;</span><span class="p">,</span> <span class="s2">&#34;--sleep&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;让我睡会儿，困死了！&#39;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;store_true&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="n">answer</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">square</span> <span class="o">**</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;我睡着了(-_-)zzz都能算数：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">！&#39;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;我很清醒，结果是：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
</code></pre></div><p>让小狗子嚣张嚣张：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py -s <span class="m">3</span>
我睡着了<span class="o">(</span>-_-<span class="o">)</span>zzz都能算数：9！
$ python3 pug.py <span class="m">3</span>
我很清醒，结果是：9!
$ python3 pug.py -h
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-s<span class="o">]</span> square

positional arguments:
  square       您说个数，我就能告诉您这个数的平方！

optional arguments:
  -h, --help   show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -s, --sleep  让我睡会儿，困死了！
</code></pre></div><p>小狗子很厉害呀，睡着觉都给算出结果来了，那小狗子睡觉也分状态的吧，熟睡和半睡半醒状态应该会不一样，看一下会怎么样：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;square&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;您说个数，我就能告诉您这个数的平方！&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-s&#39;</span><span class="p">,</span> <span class="s2">&#34;--sleep&#34;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;让我睡会儿，困死了！&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="n">answer</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">square</span> <span class="o">**</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;虽然我睡的很死，但我的耳朵好像还挺好使的的，用耳朵都能算的：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">！&#39;</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;半睡半醒的我当然听得清你说什么，很简单的题嘛：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;我很清醒，结果是：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
</code></pre></div><p>那我们考一下小狗子：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py <span class="m">3</span> -s
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-s SLEEP<span class="o">]</span> square
pug.py: error: argument -s/--sleep: expected one argument
$ python3 pug.py <span class="m">3</span> -s <span class="m">1</span>
半睡半醒的我当然听得清你说什么，很简单的题嘛：9!
$ python3 pug.py <span class="m">3</span> -s <span class="m">2</span>
虽然我睡的很死，但我的耳朵好像还挺好使的的，用耳朵都能算的：9！
$ python3 pug.py <span class="m">3</span> -s <span class="m">0</span>
我很清醒，结果是：9!
$ python3 pug.py -h
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-s SLEEP<span class="o">]</span> square

positional arguments:
  square                您说个数，我就能告诉您这个数的平方！

optional arguments:
  -h, --help            show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -s SLEEP, --sleep SLEEP
                        让我睡会儿，困死了！
$ python3 pug.py <span class="m">3</span>
我很清醒，结果是：9!
</code></pre></div><p>简直了，但是这里有个问题，pug睡觉状态并不是很多，如果我们指定其它可能没有必要嘛，能不能对值进行限制呢？当然可以啦，添加参数时给定 <strong>choices</strong> 就可以啦：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;square&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;您说个数，我就能告诉您这个数的平方！&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-s&#39;</span><span class="p">,</span> <span class="s2">&#34;--sleep&#34;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;让我睡会儿，困死了！&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="n">answer</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">square</span> <span class="o">**</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;虽然我睡的很死，但我的耳朵好像还挺好使的的，用耳朵都能算的：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">！&#39;</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;半睡半醒的我当然听得清你说什么，很简单的题嘛：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;我很清醒，结果是：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
</code></pre></div><p>看一下效果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py <span class="m">3</span> -s <span class="m">0</span>
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-s <span class="o">{</span>1,2<span class="o">}]</span> square
pug.py: error: argument -s/--sleep: invalid choice: <span class="m">0</span> <span class="o">(</span>choose from 1, 2<span class="o">)</span>
$ python3 pug.py -h
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-s <span class="o">{</span>1,2<span class="o">}]</span> square

positional arguments:
  square                您说个数，我就能告诉您这个数的平方！

optional arguments:
  -h, --help            show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -s <span class="o">{</span>1,2<span class="o">}</span>, --sleep <span class="o">{</span>1,2<span class="o">}</span>
                        让我睡会儿，困死了！
</code></pre></div><p>效果还可以，现在尝试另一种玩法，先看代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;square&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;您说个数，我就能告诉您这个数的平方！&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-s&#39;</span><span class="p">,</span> <span class="s2">&#34;--sleep&#34;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;count&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;让我睡会儿，困死了！&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="n">answer</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">square</span> <span class="o">**</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;虽然我睡的很死，但我的耳朵好像还挺好使的的，用耳朵都能算的：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">！&#39;</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;半睡半醒的我当然听得清你说什么，很简单的题嘛：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;我很清醒，结果是：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
</code></pre></div><p>再看一下结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py <span class="m">3</span> -s
半睡半醒的我当然听得清你说什么，很简单的题嘛：9!
$ python3 pug.py <span class="m">3</span> -ss
虽然我睡的很死，但我的耳朵好像还挺好使的的，用耳朵都能算的：9！
$ python3 pug.py <span class="m">3</span> -sss
我很清醒，结果是：9!
$ python3 pug.py <span class="m">3</span>
我很清醒，结果是：9!
</code></pre></div><p>好像第三条有点问题哈，修复一下这个bug：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;square&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;您说个数，我就能告诉您这个数的平方！&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-s&#39;</span><span class="p">,</span> <span class="s2">&#34;--sleep&#34;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;count&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;让我睡会儿，困死了！&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="n">answer</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">square</span> <span class="o">**</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span> <span class="o">&gt;=</span> <span class="mi">2</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;虽然我睡的很死，但我的耳朵好像还挺好使的的，用耳朵都能算的：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">！&#39;</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;半睡半醒的我当然听得清你说什么，很简单的题嘛：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;我很清醒，结果是：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
</code></pre></div><p>再看一下这次是不是更好了：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py <span class="m">3</span> -s
半睡半醒的我当然听得清你说什么，很简单的题嘛：9!
$ python3 pug.py <span class="m">3</span> -ss
虽然我睡的很死，但我的耳朵好像还挺好使的的，用耳朵都能算的：9！
$ python3 pug.py <span class="m">3</span> -sss
虽然我睡的很死，但我的耳朵好像还挺好使的的，用耳朵都能算的：9！
$ python3 pug.py <span class="m">3</span>
Traceback <span class="o">(</span>most recent call last<span class="o">)</span>:
  File <span class="s2">&#34;pug.py&#34;</span>, line 7, in &lt;module&gt;
    <span class="k">if</span> args.sleep &gt;<span class="o">=</span> 2:
TypeError: <span class="s1">&#39;&gt;=&#39;</span> not supported between instances of <span class="s1">&#39;NoneType&#39;</span> and <span class="s1">&#39;int&#39;</span>
</code></pre></div><p>哎呀，好像最后一个让小狗子炸锅了，稍微修理一下它（给参数指定默认值）：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;square&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;您说个数，我就能告诉您这个数的平方！&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-s&#39;</span><span class="p">,</span> <span class="s2">&#34;--sleep&#34;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;count&#39;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;让我睡会儿，困死了！&#39;</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="n">answer</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">square</span> <span class="o">**</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span> <span class="o">&gt;=</span> <span class="mi">2</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;虽然我睡的很死，但我的耳朵好像还挺好使的的，用耳朵都能算的：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">！&#39;</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;半睡半醒的我当然听得清你说什么，很简单的题嘛：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;我很清醒，结果是：</span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
</code></pre></div><p>看一下是否正常了：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py <span class="m">3</span>
我很清醒，结果是：9!
</code></pre></div><p>OKay! 效果不错，小狗子修炼得不错了！</p>
<h2 id="互斥参数">互斥参数</h2>
<p>有的时候小狗子的行为是互斥的，比如睡着觉的时候不能跳舞，跳舞的时候没有在睡觉，所以呀，有的时候还需要互斥参数的使用。</p>
<p>使用起来并不复杂，丰富一下我们的小狗子：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">group</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_mutually_exclusive_group</span><span class="p">()</span>
<span class="n">group</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-s&#39;</span><span class="p">,</span> <span class="s2">&#34;--sleep&#34;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;store_true&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;让我睡会儿，困死了！&#39;</span><span class="p">)</span>
<span class="n">group</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-d&#39;</span><span class="p">,</span> <span class="s1">&#39;--dance&#39;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;store_true&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;好开心，让我跳会儿舞！&#39;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;square&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;您说个数，我就能告诉您这个数的平方！&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="n">answer</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">square</span> <span class="o">**</span> <span class="mi">2</span>

<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;睡觉的我依然给力，</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">square</span><span class="si">}</span><span class="s1"> 的平方是 </span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">dance</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;跳舞的时候，我的大脑非常活跃，所以，</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">square</span><span class="si">}</span><span class="s1"> 的平方是 </span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">square</span><span class="si">}</span><span class="s1"> 的平方是 </span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
</code></pre></div><p>现在小狗子点了新技能，展示一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py -h
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-s <span class="p">|</span> -d<span class="o">]</span> square

positional arguments:
  square       您说个数，我就能告诉您这个数的平方！

optional arguments:
  -h, --help   show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -s, --sleep  让我睡会儿，困死了！
  -d, --dance  好开心，让我跳会儿舞！
$ python3 pug.py <span class="m">5</span>
<span class="m">5</span> 的平方是 25!
$ python3 pug.py -s <span class="m">5</span>
睡觉的我依然给力，5 的平方是 25!
$ python3 pug.py -d <span class="m">5</span>
跳舞的时候，我的大脑非常活跃，所以，5 的平方是 25!
$ python3 pug.py -d -s <span class="m">5</span>
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-s <span class="p">|</span> -d<span class="o">]</span> square
pug.py: error: argument -s/--sleep: not allowed with argument -d/--dance
</code></pre></div><p>对了，我们应该让小狗子可以自我介绍，在初始化参数分析器的时候添加描述就好了：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">argparse</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s1">&#39;我是一只快乐的哈巴狗，我会算整数的平方哦！而且跳着舞或者睡着觉的时候我依然可以呢！&#39;</span><span class="p">)</span>
<span class="n">group</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_mutually_exclusive_group</span><span class="p">()</span>
<span class="n">group</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-s&#39;</span><span class="p">,</span> <span class="s2">&#34;--sleep&#34;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;store_true&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;让我睡会儿，困死了！&#39;</span><span class="p">)</span>
<span class="n">group</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-d&#39;</span><span class="p">,</span> <span class="s1">&#39;--dance&#39;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;store_true&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;好开心，让我跳会儿舞！&#39;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&#34;square&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;您说个数，我就能告诉您这个数的平方！&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="n">answer</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">square</span> <span class="o">**</span> <span class="mi">2</span>

<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;睡觉的我依然给力，</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">square</span><span class="si">}</span><span class="s1"> 的平方是 </span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">dance</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;跳舞的时候，我的大脑非常活跃，所以，</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">square</span><span class="si">}</span><span class="s1"> 的平方是 </span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">square</span><span class="si">}</span><span class="s1"> 的平方是 </span><span class="si">{</span><span class="n">answer</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
</code></pre></div><p>让它好好的自我介绍一下自己吧：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 pug.py -h
usage: pug.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-s <span class="p">|</span> -d<span class="o">]</span> square

我是一只快乐的哈巴狗，我会算整数的平方哦！而且跳着舞或者睡着觉的时候我依然可以呢！

positional arguments:
  square       您说个数，我就能告诉您这个数的平方！

optional arguments:
  -h, --help   show this <span class="nb">help</span> message and <span class="nb">exit</span>
  -s, --sleep  让我睡会儿，困死了！
  -d, --dance  好开心，让我跳会儿舞！
</code></pre></div><h2 id="总结">总结</h2>
<p>作者希望您通过与小哈巴狗的玩耍，很快学会 <strong>argparse</strong> 库的使用，我们也要像它一样快乐才好！(◐‿◑)</p>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://docs.python.org/3.7/howto/argparse.html">Argparse Tutorial</a></li>
<li><a href="http://blog.xiayf.cn/2013/03/30/argparse/">argparse - 命令行选项与参数解析（译）</a></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>AZW3 格式电子书转换为 MOBI 格式保持原有排版格式</title>
			<link>https://blog.5km.studio/2018/09/12/convert-ebook/</link>
			<pubDate>Wed, 12 Sep 2018 00:20:28 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/09/12/convert-ebook/</guid>
			<description>&lt;p&gt;前些天写的文章 &lt;a href=&#34;https://blog.5km.studio/2018/09/06/kindle-deDRM-to-mobi/&#34;&gt;《让kindle电子书不受DRM禁锢》&lt;/a&gt; 中将kindle中的azw3格式电子书去除了DRM，并转换格式为mobi格式，这样就能导入我的 &lt;strong&gt;ireader plus&lt;/strong&gt; 阅读器了。但是这些天发现，用calibre转换的书籍把原有排版格式改变了挺多，所以网上搜索了解决方法，找到文章《将 AZW3 格式转换为 MOBI 格式并保持原有排版格式》^[&lt;a href=&#34;https://bookfere.com/post/102.html&#34;&gt;将 AZW3 格式转换为 MOBI 格式并保持原有排版格式&lt;/a&gt;]，按照方法尝试，果然最大限度的保留了排版格式，现分享详细操作过程！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>前些天写的文章 <a href="/2018/09/06/kindle-deDRM-to-mobi/">《让kindle电子书不受DRM禁锢》</a> 中将kindle中的azw3格式电子书去除了DRM，并转换格式为mobi格式，这样就能导入我的 <strong>ireader plus</strong> 阅读器了。但是这些天发现，用calibre转换的书籍把原有排版格式改变了挺多，所以网上搜索了解决方法，找到文章《将 AZW3 格式转换为 MOBI 格式并保持原有排版格式》^[<a href="https://bookfere.com/post/102.html">将 AZW3 格式转换为 MOBI 格式并保持原有排版格式</a>]，按照方法尝试，果然最大限度的保留了排版格式，现分享详细操作过程！</p>
<p>本文的方法过程很简单：</p>
<ol>
<li>使用专门的工具 <strong>KindleUnpack</strong> 对电子书文件解包；</li>
<li>使用 <strong>KindleGen</strong> 对解包文件进行打包生成mobi电子书文件；</li>
</ol>
<h2 id="准备工作">准备工作</h2>
<p>操作之前，需要做一些准备工作。操作平台是 macOS 系统，所以下面的操作是针对 macOS的。</p>
<h3 id="azw3格式电子书文件">azw3格式电子书文件</h3>
<p>电子书文件获取方式有多种，可以像 <a href="/2018/09/06/kindle-deDRM-to-mobi/#获取电子书">《让kindle电子书不受DRM禁锢》- 获取电子书</a> 中从kindle中获取，也可以是自己撰写的书籍，当然也可以是别人赠予的。</p>
<h3 id="解包工具kindleunpack">解包工具KindleUnpack</h3>
<p>KindleUnpack （原 mobiunpack）是一款用 Python 写成的小程序，它可以用来提取 Kindle 电子书如 mobi、azw3 等格式文件中的 HTML 内容、图像以及元数据文件，并能把这些文件按照 KindleGen 生成电子书的标准和形式放置^[<a href="https://bookfere.com/tools#ku">KindleUnpack – 拆解 Kindle 电子书文件的利器</a>]。支持跨平台，有四种版本：</p>
<ul>
<li>带界面的 pyw 格式 Python 脚本： <a href="http://www.mobileread.com/forums/attachment.php?attachmentid=139470&amp;d=1434875606">KindleUnpack-080.zip</a></li>
<li>支持拖放操作的 AppleScript 版本：<a href="http://www.mobileread.com/forums/attachment.php?attachmentid=139469&amp;d=1434875606">KindleUnpack v0.80.app.zip</a></li>
<li>仅支持 mobi 文件的单文件脚本：<a href="http://www.mobileread.com/forums/attachment.php?attachmentid=89514&amp;d=1342902594">mobiunpack 32.py.zip</a></li>
<li>依附于 Calibre 运行的插件版本：<a href="http://www.mobileread.com/forums/attachment.php?attachmentid=141815&amp;d=1441654208">kindle_unpack_v0812_plugin.zip</a></li>
</ul>
<p>这里使用 <a href="http://www.mobileread.com/forums/attachment.php?attachmentid=139469&amp;d=1434875606"><strong>支持拖放操作的 AppleScript 版本</strong></a>，下载完成后解压会得到如下的app：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180912153668507496280.png" alt="20180912153668507496280.png"></p>
<h3 id="亚马逊官方电子书转换工具kindlegen">亚马逊官方电子书转换工具KindleGen</h3>
<p>KindleGen 是一款免费的电子书格式转换工具，需要使用命令行操作，也是亚马逊唯一官方支持的文件转换工具，它可通过它把 HTML、XHTML 或 IDPF 2.0 格式（带有 XML.opf 描述文件的 HTML 内容文件）的源文件创建为适合 Kindle 阅读的电子书格式^[<a href="https://bookfere.com/tools#KindleGen">亚马逊官方电子书转换工具KindleGen</a>]。</p>
<p>支持三大平台，但有人制作了macOS下的图形界面的版本：<a href="http://pan.baidu.com/s/1qYu0V28">Mac OS X 版（v2.9 带界面）</a>，下载后解压得到：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180912153668453011199.png" alt="20180912153668453011199.png"></p>
<h2 id="电子书解包">电子书解包</h2>
<p>解包非常简单，只需要将azw3电子书文件拖拽到上面下载的KindleUnpack上，就会自动解包，最终在电子书文件同级目录下生成以电子书名称命名的文件夹，在里面会得到如下目录层级的内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">.
├── HDImages
├── mobi7
│   └── Images
│       ...
└── mobi8
    ├── Flask Web 开发实战.epub
    ├── META-INF
    │   └── container.xml
    ├── OEBPS
    │   ├── Fonts
    │   ├── Images
    │   │   └── ...
    │   ├── Styles
    │   │   └── style0001.css
    │   ├── Text
    │   │   └── ...
    │   ├── content.opf
    │   └── toc.ncx
    └── mimetype
</code></pre></div><h2 id="转化电子书">转化电子书</h2>
<p>打开解压的到的KindleGen的app，会看到如下窗口：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180912153668574549309.png" alt="20180912153668574549309.png"></p>
<p>窗口提示拖拽文件到窗口中进行处理，待拖拽的文件有两种选择：</p>
<ul>
<li><strong>mobi8</strong> 目录下的 epub 格式文件，如上面操作的到的 <strong>Flask Web 开发实战.epub</strong>；</li>
<li><strong>mobi8/OEBPS</strong> 目录下的 <strong>content.opf</strong> 文件；</li>
</ul>
<p>拖拽上面任意一个文件到窗口中等待一段时间后均能得到转换后的mobi电子书文件：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180912153668598068955.png" alt="20180912153668598068955.png"></p>
<p>生成的文件在所拖拽文件的同级目录中，比如拖拽的epub文件，那么mobi文件就在目录 <strong>mobi8</strong> 中；如果拖拽的 <strong>content.opf</strong>，那么 mobi 文件就在 <strong>mobi8/OEBPS</strong> 中。</p>
<p>至此已经完成电子书的转换，导入我的 <strong>ireader plus</strong> ，果然奏效， ^ ^ ！</p>
<h2 id="总结">总结</h2>
<p>如果您和小编我一样是强迫症，对格式十分看重，那么本文对您应该是有用的。最后感谢您的阅读，有什么想法要交流的话可以在下面留言！</p>]]></content>
		</item>
		
		<item>
			<title>python常用正则表达式总结</title>
			<link>https://blog.5km.studio/2018/09/11/regex-python/</link>
			<pubDate>Tue, 11 Sep 2018 16:53:07 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/09/11/regex-python/</guid>
			<description>&lt;p&gt;python开发，经常需要从字符串中提取关键信息，这个提取过程多半使用正则表达式实现，开本文来记录常用的正则表达式，方便日后续用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180911153665750929978.png&#34; alt=&#34;20180911153665750929978.png&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>python开发，经常需要从字符串中提取关键信息，这个提取过程多半使用正则表达式实现，开本文来记录常用的正则表达式，方便日后续用。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180911153665750929978.png" alt="20180911153665750929978.png"></p>
<table>
<thead>
<tr>
<th>信息</th>
<th style="text-align:center">表达式</th>
</tr>
</thead>
<tbody>
<tr>
<td>非负整数</td>
<td style="text-align:center">^\d+$</td>
</tr>
<tr>
<td>正整数</td>
<td style="text-align:center">^[0-9]*[1-9][0-9]*$</td>
</tr>
<tr>
<td>非正整数</td>
<td style="text-align:center">^((-\d+)|(0+))$</td>
</tr>
<tr>
<td>负整数</td>
<td style="text-align:center">^-[0-9]*[1-9][0-9]*$</td>
</tr>
<tr>
<td>整数</td>
<td style="text-align:center">^-?\d+$</td>
</tr>
<tr>
<td>非负浮点数</td>
<td style="text-align:center">^\d+\.?\d+$</td>
</tr>
<tr>
<td>正浮点数</td>
<td style="text-align:center">^((0-9)+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)$</td>
</tr>
<tr>
<td>非正浮点数</td>
<td style="text-align:center">^((-\d+\.\d+)?)|(0+(\.0+)?))$</td>
</tr>
<tr>
<td>负浮点数</td>
<td style="text-align:center">^(-((正浮点数正则式)))$</td>
</tr>
<tr>
<td>英文字符串</td>
<td style="text-align:center">^[A-Za-z]+$</td>
</tr>
<tr>
<td>英文大写串</td>
<td style="text-align:center">^[A-Z]+$</td>
</tr>
<tr>
<td>英文小写串</td>
<td style="text-align:center">^[a-z]+$</td>
</tr>
<tr>
<td>英文字符数字串</td>
<td style="text-align:center">^[A-Za-z0-9]+$</td>
</tr>
<tr>
<td>英数字加下划线串</td>
<td style="text-align:center">^\w+$</td>
</tr>
<tr>
<td>E-mail地址</td>
<td style="text-align:center">^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$</td>
</tr>
<tr>
<td>URL</td>
<td style="text-align:center">^[a-zA-Z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\s*)?$</td>
</tr>
<tr>
<td>URL</td>
<td style="text-align:center">^http:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&amp;_~`@[\]\':+!]*([^&lt;&gt;\&quot;\&quot;])* $</td>
</tr>
<tr>
<td>邮政编码</td>
<td style="text-align:center">^[1-9]\d{5}$</td>
</tr>
<tr>
<td>中文</td>
<td style="text-align:center">^[\u0391-\uFFE5]+$</td>
</tr>
<tr>
<td>电话号码</td>
<td style="text-align:center">^((\(\d{2,3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$</td>
</tr>
<tr>
<td>手机号码</td>
<td style="text-align:center">^((\(\d{2,3}\))|(\d{3}\-))?13\d{9}$</td>
</tr>
<tr>
<td>双字节字符(包括汉字在内)</td>
<td style="text-align:center">^\x00-\xff</td>
</tr>
<tr>
<td>匹配首尾空格</td>
<td style="text-align:center">(^\s*)|(\s*$)（像vbscript那样的trim函数）</td>
</tr>
<tr>
<td>匹配HTML标记</td>
<td style="text-align:center">&lt;(.*)&gt;.*&lt;\/\1&gt;|&lt;(.*)\/&gt;</td>
</tr>
<tr>
<td>匹配空行</td>
<td style="text-align:center">\n[\s| ]*\r</td>
</tr>
<tr>
<td>提取信息中的网络链接</td>
<td style="text-align:center">(h|H)(r|R)(e|E)(f|F)  *=  *('|&quot;)?(\w|\\|\/|\.)+('|&quot;|  *|&gt;)?</td>
</tr>
<tr>
<td>提取信息中的邮件地址</td>
<td style="text-align:center">\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*</td>
</tr>
<tr>
<td>提取信息中的图片链接</td>
<td style="text-align:center">(s|S)(r|R)(c|C)  *=  *('|&quot;)?(\w|\\|\/|\.)+('|&quot;|  *|&gt;)?</td>
</tr>
<tr>
<td>提取信息中的IP地址</td>
<td style="text-align:center">(\d+)\.(\d+)\.(\d+)\.(\d+)</td>
</tr>
<tr>
<td>提取信息中的中国手机号码</td>
<td style="text-align:center">(86)\*0\*13\d{9}</td>
</tr>
<tr>
<td>提取信息中的中国固定电话号码</td>
<td style="text-align:center">(\(\d{3,4}\)|\d{3,4}-|\s)?\d{8}</td>
</tr>
<tr>
<td>提取信息中的中国电话号码（包括移动和固定电话）</td>
<td style="text-align:center">(\(\d{3,4}\)|\d{3,4}-|\s)?\d{7,14}</td>
</tr>
<tr>
<td>提取信息中的中国邮政编码</td>
<td style="text-align:center">[1-9]{1}(\d+){5}</td>
</tr>
<tr>
<td>提取信息中的浮点数（即小数）</td>
<td style="text-align:center">(-?\d*)\.?\d+</td>
</tr>
<tr>
<td>提取信息中的任何数字</td>
<td style="text-align:center">(-?\d*)(\.\d+)?</td>
</tr>
<tr>
<td>IP</td>
<td style="text-align:center">(\d+)\.(\d+)\.(\d+)\.(\d+)</td>
</tr>
<tr>
<td>电话区号</td>
<td style="text-align:center">/^0\d{2,3}$/</td>
</tr>
<tr>
<td>腾讯QQ号</td>
<td style="text-align:center">^[1-9]*[1-9][0-9]*$</td>
</tr>
<tr>
<td>帐号(字母开头，允许5-16字节，允许字母数字下划线)</td>
<td style="text-align:center">^[a-zA-Z][a-zA-Z0-9_]{4,15}$</td>
</tr>
<tr>
<td>中文、英文、数字及下划线</td>
<td style="text-align:center">^[\u4e00-\u9fa5_a-zA-Z0-9]+$</td>
</tr>
<tr>
<td>匹配中文字符的正则表达式</td>
<td style="text-align:center">[\u4e00-\u9fa5]</td>
</tr>
<tr>
<td>匹配双字节字符(包括汉字在内)</td>
<td style="text-align:center">[^\x00-\xff]</td>
</tr>
<tr>
<td>匹配空行的正则表达式</td>
<td style="text-align:center">\n[\s| ]*\r</td>
</tr>
<tr>
<td>匹配HTML标记的正则表达式</td>
<td style="text-align:center">/&lt;(.*)&gt;.*&lt;\/\1&gt;|&lt;(.*) \/&gt;/</td>
</tr>
<tr>
<td>sql语句</td>
<td style="text-align:center">^(select|drop|delete|create|update|insert).*$</td>
</tr>
<tr>
<td>匹配首尾空格的正则表达式</td>
<td style="text-align:center">(^\s*)|(\s*$)</td>
</tr>
<tr>
<td>匹配Email地址的正则表达式</td>
<td style="text-align:center">\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*</td>
</tr>
</tbody>
</table>
<h2 id="参考">参考</h2>
<p><a href="http://thman.lofter.com/post/2311ca_100dff6">python：常用的正则表达式总结</a></p>]]></content>
		</item>
		
		<item>
			<title>Phantomjs失控 - Element is not currently visible</title>
			<link>https://blog.5km.studio/2018/09/07/phantomjs-element-invisible/</link>
			<pubDate>Fri, 07 Sep 2018 16:50:29 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/09/07/phantomjs-element-invisible/</guid>
			<description>&lt;p&gt;今天写一个爬虫，使用 &lt;a href=&#34;https://www.seleniumhq.org&#34;&gt;&lt;strong&gt;selenium&lt;/strong&gt;&lt;/a&gt;(可以操纵浏览器的python库)操控phantomjs出了问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Element is not currently visible and may not be manipulated exception&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;获取的元素不可见是什么鬼，操控其它浏览器却没有这个问题，研究了一下，现分享解决方法。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天写一个爬虫，使用 <a href="https://www.seleniumhq.org"><strong>selenium</strong></a>(可以操纵浏览器的python库)操控phantomjs出了问题：</p>
<blockquote>
<p>Element is not currently visible and may not be manipulated exception</p>
</blockquote>
<p>获取的元素不可见是什么鬼，操控其它浏览器却没有这个问题，研究了一下，现分享解决方法。</p>
<h2 id="问题现象">问题现象</h2>
<p>获取一个弹窗上的节点，操作节点时出了问题：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">In</span> <span class="p">[</span><span class="mi">9</span><span class="p">]:</span> <span class="n">checkallbtn</span> <span class="o">=</span> <span class="n">browser</span><span class="o">.</span><span class="n">find_element_by_css_selector</span><span class="p">(</span><span class="s1">&#39;input[title=&#34;全部&#34;]&#39;</span><span class="p">)</span>

<span class="n">In</span> <span class="p">[</span><span class="mi">10</span><span class="p">]:</span> <span class="n">checkallbtn</span><span class="o">.</span><span class="n">click</span><span class="p">()</span>
<span class="o">---------------------------------------------------------------------------</span>
<span class="n">ElementNotVisibleException</span>                <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">)</span>
<span class="o">&lt;</span><span class="n">ipython</span><span class="o">-</span><span class="nb">input</span><span class="o">-</span><span class="mi">10</span><span class="o">-</span><span class="mi">8</span><span class="n">ff787fdd7d7</span><span class="o">&gt;</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span><span class="p">()</span>
<span class="o">----&gt;</span> <span class="mi">1</span> <span class="n">checkallbtn</span><span class="o">.</span><span class="n">click</span><span class="p">()</span>

<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">python3</span><span class="mf">.7</span><span class="o">/</span><span class="n">site</span><span class="o">-</span><span class="n">packages</span><span class="o">/</span><span class="n">selenium</span><span class="o">/</span><span class="n">webdriver</span><span class="o">/</span><span class="n">remote</span><span class="o">/</span><span class="n">webelement</span><span class="o">.</span><span class="n">py</span> <span class="ow">in</span> <span class="n">click</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
     <span class="mi">78</span>     <span class="k">def</span> <span class="nf">click</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
     <span class="mi">79</span>         <span class="s2">&#34;&#34;&#34;Clicks the element.&#34;&#34;&#34;</span>
<span class="o">---&gt;</span> <span class="mi">80</span>         <span class="bp">self</span><span class="o">.</span><span class="n">_execute</span><span class="p">(</span><span class="n">Command</span><span class="o">.</span><span class="n">CLICK_ELEMENT</span><span class="p">)</span>
     <span class="mi">81</span>
     <span class="mi">82</span>     <span class="k">def</span> <span class="nf">submit</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>

<span class="o">...</span>

<span class="n">ElementNotVisibleException</span><span class="p">:</span> <span class="n">Message</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;errorMessage&#34;</span><span class="p">:</span><span class="s2">&#34;Element is not currently visible and may not be manipulated&#34;</span><span class="p">,</span><span class="s2">&#34;request&#34;</span><span class="p">:{</span><span class="s2">&#34;headers&#34;</span><span class="p">:{</span><span class="s2">&#34;Accept&#34;</span><span class="p">:</span><span class="s2">&#34;application/json&#34;</span><span class="p">,</span><span class="s2">&#34;Accept-Encoding&#34;</span><span class="p">:</span><span class="s2">&#34;identity&#34;</span><span class="p">,</span><span class="s2">&#34;Content-Length&#34;</span><span class="p">:</span><span class="s2">&#34;81&#34;</span><span class="p">,</span><span class="s2">&#34;Content-Type&#34;</span><span class="p">:</span><span class="s2">&#34;application/json;charset=UTF-8&#34;</span><span class="p">,</span><span class="s2">&#34;Host&#34;</span><span class="p">:</span><span class="s2">&#34;127.0.0.1:51891&#34;</span><span class="p">,</span><span class="s2">&#34;User-Agent&#34;</span><span class="p">:</span><span class="s2">&#34;selenium/3.14.0 (python mac)&#34;</span><span class="p">},</span><span class="s2">&#34;httpVersion&#34;</span><span class="p">:</span><span class="s2">&#34;1.1&#34;</span><span class="p">,</span><span class="s2">&#34;method&#34;</span><span class="p">:</span><span class="s2">&#34;POST&#34;</span><span class="p">,</span><span class="s2">&#34;post&#34;</span><span class="p">:</span><span class="s2">&#34;{</span><span class="se">\&#34;</span><span class="s2">id</span><span class="se">\&#34;</span><span class="s2">: </span><span class="se">\&#34;</span><span class="s2">:wdc:1536311901172</span><span class="se">\&#34;</span><span class="s2">, </span><span class="se">\&#34;</span><span class="s2">sessionId</span><span class="se">\&#34;</span><span class="s2">: </span><span class="se">\&#34;</span><span class="s2">ddbffb50-b27e-11e8-b0df-c57be7bc1c52</span><span class="se">\&#34;</span><span class="s2">}&#34;</span><span class="p">,</span><span class="s2">&#34;url&#34;</span><span class="p">:</span><span class="s2">&#34;/click&#34;</span><span class="p">,</span><span class="s2">&#34;urlParsed&#34;</span><span class="p">:{</span><span class="s2">&#34;anchor&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="s2">&#34;query&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="s2">&#34;file&#34;</span><span class="p">:</span><span class="s2">&#34;click&#34;</span><span class="p">,</span><span class="s2">&#34;directory&#34;</span><span class="p">:</span><span class="s2">&#34;/&#34;</span><span class="p">,</span><span class="s2">&#34;path&#34;</span><span class="p">:</span><span class="s2">&#34;/click&#34;</span><span class="p">,</span><span class="s2">&#34;relative&#34;</span><span class="p">:</span><span class="s2">&#34;/click&#34;</span><span class="p">,</span><span class="s2">&#34;port&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="s2">&#34;host&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="s2">&#34;password&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="s2">&#34;user&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="s2">&#34;userInfo&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="s2">&#34;authority&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="s2">&#34;protocol&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="s2">&#34;source&#34;</span><span class="p">:</span><span class="s2">&#34;/click&#34;</span><span class="p">,</span><span class="s2">&#34;queryKey&#34;</span><span class="p">:{},</span><span class="s2">&#34;chunks&#34;</span><span class="p">:[</span><span class="s2">&#34;click&#34;</span><span class="p">]},</span><span class="s2">&#34;urlOriginal&#34;</span><span class="p">:</span><span class="s2">&#34;/session/ddbffb50-b27e-11e8-b0df-c57be7bc1c52/element/:wdc:1536311901172/click&#34;</span><span class="p">}}</span>
<span class="n">Screenshot</span><span class="p">:</span> <span class="n">available</span> <span class="n">via</span> <span class="n">screen</span>
</code></pre></div><h2 id="排查问题">排查问题</h2>
<p>这部分主要描述问题排查过程，不感兴趣可以<a href="#解决方法">点我</a>跳过。</p>
<ol>
<li>
<p>首先确认了一下，获取元素使用的 <strong>CSS选择器</strong> 没有问题：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">checkallbtn</span> <span class="o">=</span> <span class="n">browser</span><span class="o">.</span><span class="n">find_element_by_css_selector</span><span class="p">(</span><span class="s1">&#39;#cityDiv #winCanton li label input[title=&#34;全部&#34;]&#39;</span><span class="p">)</span>
</code></pre></div></li>
<li>
<p>再用phantomjs截个图看看此时渲染状态：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180907153631208080986.jpg" alt="20180907153631208080986.jpg"></p>
<p>获取的元素就是图片中央弹窗上的元素，但是可以看到图中最上面有个阴影区，<strong>Element is not currently visible</strong> 意思会不会是弹窗在可见范围以外，如果是这样的话，那个阴影区应该代表窗口，为什么在chrome中访问的时候，弹窗就能正确显示到chrome窗口中央的，好奇怪！</p>
</li>
<li>
<p>搜索这个问题，找到了链接<a href="https://github.com/ariya/phantomjs/issues/11637">Element is not currently visible and may not be manipulated exception</a>，看来很多人遇到了这个问题，看到有个人说：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180907153631255285279.png" alt="20180907153631255285279.png"></p>
<p>看来是selenium操控的浏览器窗口大小的问题。</p>
</li>
<li>
<p>查看一下此时浏览器的窗口大小：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">In</span> <span class="p">[</span><span class="mi">8</span><span class="p">]:</span> <span class="n">browser</span><span class="o">.</span><span class="n">get_window_size</span><span class="p">()</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">8</span><span class="p">]:</span> <span class="p">{</span><span class="s1">&#39;width&#39;</span><span class="p">:</span> <span class="mi">400</span><span class="p">,</span> <span class="s1">&#39;height&#39;</span><span class="p">:</span> <span class="mi">300</span><span class="p">}</span>
</code></pre></div><p>窗口好小，看来是这个问题。</p>
</li>
</ol>
<h2 id="解决方法">解决方法</h2>
<p>根据上面问题排查基本找到原因，是被操控浏览器窗口大小不合适导致弹窗显示在可视区外，解决办法显而易见，更改一个合适的窗口大小：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">In</span> <span class="p">[</span><span class="mi">11</span><span class="p">]:</span> <span class="n">browser</span><span class="o">.</span><span class="n">set_window_size</span><span class="p">(</span><span class="mi">1280</span><span class="p">,</span> <span class="mi">800</span><span class="p">)</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">12</span><span class="p">]:</span> <span class="n">checkallbtn</span><span class="o">.</span><span class="n">click</span><span class="p">()</span>
</code></pre></div><p>合家欢乐，没有出现问题 (^_^)v。</p>
<h2 id="参考">参考</h2>
<p><a href="https://github.com/ariya/phantomjs/issues/11637">Element is not currently visible and may not be manipulated exception</a></p>]]></content>
		</item>
		
		<item>
			<title>让kindle电子书不受DRM禁锢</title>
			<link>https://blog.5km.studio/2018/09/06/kindle-deDRM-to-mobi/</link>
			<pubDate>Thu, 06 Sep 2018 12:04:02 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/09/06/kindle-deDRM-to-mobi/</guid>
			<description>&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153620800099678.png&#34; alt=&#34;20180906153620800099678.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;昨天在亚马逊购买了关注很久的新书，电子版的，临时使用水木的 &lt;strong&gt;kindle voyage&lt;/strong&gt; 进行阅读，屏幕略小呀，我有一个 &lt;strong&gt;ireader&lt;/strong&gt; 电纸书阅读器，屏幕是6.8寸的，比kindle大一整圈，为什么我不用它来阅读呢（ireader平台没有上这本新书）！问题来了，kindle中的电子书受 &lt;a href=&#34;https://en.wikipedia.org/wiki/Digital_rights_management&#34;&gt;&lt;strong&gt;DRM&lt;/strong&gt;&lt;/a&gt; 保护，导出的电子书文件不能用其它阅读器中打开，经过探索，找到了去除 &lt;strong&gt;DRM&lt;/strong&gt; 并转换导出&lt;a href=&#34;https://en.wikipedia.org/wiki/EPUB&#34;&gt;epub&lt;/a&gt; 或 &lt;a href=&#34;https://en.wikipedia.org/wiki/Mobi&#34;&gt;mobi&lt;/a&gt; 电子书的方法，本文分享一下这一成果！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153620800099678.png" alt="20180906153620800099678.png"></p>
<p>昨天在亚马逊购买了关注很久的新书，电子版的，临时使用水木的 <strong>kindle voyage</strong> 进行阅读，屏幕略小呀，我有一个 <strong>ireader</strong> 电纸书阅读器，屏幕是6.8寸的，比kindle大一整圈，为什么我不用它来阅读呢（ireader平台没有上这本新书）！问题来了，kindle中的电子书受 <a href="https://en.wikipedia.org/wiki/Digital_rights_management"><strong>DRM</strong></a> 保护，导出的电子书文件不能用其它阅读器中打开，经过探索，找到了去除 <strong>DRM</strong> 并转换导出<a href="https://en.wikipedia.org/wiki/EPUB">epub</a> 或 <a href="https://en.wikipedia.org/wiki/Mobi">mobi</a> 电子书的方法，本文分享一下这一成果！</p>
<p>本文操作是在mac下完成的，windows下的操作可以类比，只有一些文件路径不太一样，操作步骤还是基本一致的！</p>
<h2 id="获取电子书">获取电子书</h2>
<p>这里讲解两种方法获取电子书文件，这个文件一般为 <strong>awz</strong> 、<strong>awz3</strong> 格式，其实可以直接USB连接kindle电纸书获取，但是最新的电纸书改成了 <strong>kfz</strong> 格式，可能这个文件并不是完整的电子书文件，所以这里不推荐通过kindle设备导出。推荐使用的两种方法：</p>
<ul>
<li>通过kindle电脑客户端获取电子书文件；</li>
<li>去亚马逊网站下载电子书文件；</li>
</ul>
<p>当然上述实现是建立在您已经购买此电子书的基础上的。</p>
<h3 id="电脑客户端获取">电脑客户端获取</h3>
<h4 id="安装客户端">安装客户端</h4>
<ol>
<li>
<p>访问 <a href="https://www.amazon.com/kindle-dbs/fd/kcp">https://www.amazon.com/kindle-dbs/fd/kcp</a> 下载客户端：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622073189036.jpg" alt="20180906153622073189036.jpg"></p>
</li>
<li>
<p>mac的话会得到一个dmg文件，打开后将 <strong>kindle.app</strong> 拖入 <strong>applications</strong> 文件夹即可完成安装</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622083965152.png" alt="20180906153622083965152.png"></p>
</li>
<li>
<p>启动 <strong>Kindle.app</strong> ，登录自己的亚马逊账号，就可以看到自己已购书单：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622106031221.png" alt="20180906153622106031221.png"></p>
</li>
<li>
<p>在待导出电子书的封面上右键单击，可以看到 <code>下载</code>，单击即可下载到本地：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2018090615362212568184.png" alt="2018090615362212568184.png"></p>
</li>
<li>
<p>启动终端（mac下的命令行工具），输入命令</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">open ~/Library/Application<span class="se">\ </span>Support/Kindle/My<span class="se">\ </span>Kindle<span class="se">\ </span>Content/ 
</code></pre></div><p>此时会打开一个finder窗口，你会找到刚下载电子书对应的 <strong>awz</strong> 文件，将其拷贝到桌面备用。</p>
</li>
</ol>
<h3 id="亚马逊网站获取">亚马逊网站获取</h3>
<ol>
<li>
<p>访问网址 <a href="https://www.amazon.cn/hz/mycd/myx#/home/content/booksAll/dateDsc/">https://www.amazon.cn/hz/mycd/myx#/home/content/booksAll/dateDsc/</a>，登录自己的亚马逊账户，就能看到自己购买的书单，类似：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153621988176856.png" alt="20180906153621988176856.png"></p>
</li>
<li>
<p>可以看到每一项的 <code>操作</code> 列中包含一个三个小点的按钮，点击待导出电子书的这个按钮，能看到一些操作项：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622005546113.png" alt="20180906153622005546113.png"></p>
</li>
<li>
<p>点击 <code>通过电脑下载USB传输</code>，可以看到一个跳窗，选择对应的kindle设备，点击 <code>下载</code> 就可以下载到电子书文件了</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622023195517.png" alt="20180906153622023195517.png"></p>
<p>理论上下载的文件应该是一个 <strong>awz3</strong> 文件，将其拷贝到桌面备用。</p>
</li>
</ol>
<h2 id="安装calibre和dedrm插件">安装Calibre和DeDRM插件</h2>
<h3 id="安装-calibre">安装 <strong>Calibre</strong></h3>
<ol>
<li>
<p>访问 <a href="https://calibre-ebook.com/download">https://calibre-ebook.com/download</a> 下载 Calibre：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622219216546.png" alt="20180906153622219216546.png"></p>
</li>
<li>
<p>mac下载得到一个dmg文件，如 <strong>calibre-3.30.0.dmg</strong>，打开后，将 <strong>Calibre.app</strong> 拖入 <strong>Applications</strong> 即可完成安装</p>
</li>
<li>
<p>初次启动 Calibre 会出现配置窗口，相信您能自己搞定，完成后就会看到主窗口：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622239713512.png" alt="20180906153622239713512.png"></p>
</li>
</ol>
<h3 id="安装-dedrm-插件">安装 <strong>DeDRM</strong> 插件</h3>
<ol>
<li>
<p>访问<a href="https://github.com/apprenticeharper/DeDRM_tools/releases">apprenticeharper/DeDRM_tools</a>下载最新插件，我这里下载的是 <strong>DeDRM_tools_6.6.1.zip</strong></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622271157232.png" alt="20180906153622271157232.png"></p>
</li>
<li>
<p>解压下载的插件到桌面备用；</p>
</li>
<li>
<p>启动 Calibre，依次找到 <strong>菜单栏</strong> - <strong>偏好选项</strong> - <strong>调整calibre操作方式</strong>，得到窗口：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622255682731.png" alt="20180906153622255682731.png"></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622303928379.png" alt="20180906153622303928379.png"></p>
</li>
<li>
<p>点击窗口左下角的 <strong>插件</strong> ，会启动插件管理窗口：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622314086532.png" alt="20180906153622314086532.png"></p>
</li>
<li>
<p>点击右下角的 <strong>从文件加载插件</strong>，按照下图从上面解压到桌面的插件找到插件选择：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622340325039.png" alt="20180906153622340325039.png"></p>
</li>
<li>
<p>此时会提示有风险问是否继续安装，选择 <strong>是</strong> 即可</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622381263863.png" alt="20180906153622381263863.png"></p>
<p>然后会提醒需要重启才能生效，点击 <strong>确定</strong></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622381873868.png" alt="20180906153622381873868.png"></p>
<p>可以看到插件已经安装成功：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622383489537.png" alt="20180906153622383489537.png"></p>
<p>点击左下角的 <strong>应用</strong>。</p>
</li>
<li>
<p>双击上图中刚安装的插件，会跳出配置窗口：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622469077974.png" alt="20180906153622469077974.png"></p>
</li>
<li>
<p>单击第一个 <strong>eInk kindle books</strong>，会跳出kindle设备串号的管理窗口，点击 <code>+</code> 按钮，添加您kindle设备的串号，一路确定即可配置完成：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622497256903.png" alt="20180906153622497256903.png"></p>
<p><strong>注：</strong> <a href="https://bookfere.com/post/200.html">点我</a> 了解如何得到kindle设备串号。</p>
</li>
</ol>
<h2 id="转换电子书">转换电子书</h2>
<p>有了上面的准备工作，我们就可以处理电子书文件了。</p>
<h3 id="dedrm">DeDRM</h3>
<p>启动 <strong>Calibre</strong>，拖动桌面上的电子书文件到 <strong>Calibre</strong> 主窗口中，主窗口中就会出现这本书，双击它，如果能阅读，说明已完成DeDRM，下面就可以转换格式了：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2018090615362252841670.png" alt="2018090615362252841670.png"></p>
<h3 id="转换电子书-1">转换电子书</h3>
<p>我的ireader阅读器可识别mobi或者epub，这里以将电子书导出为mobi为例。</p>
<ol>
<li>
<p>选中主窗口的电子书，单击工具栏中的第二个按钮 <strong>转换书籍</strong>，会得到配置窗口：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622552212554.png" alt="20180906153622552212554.png"></p>
</li>
<li>
<p>使用默认配置就ok，当然您也可以根据自己的需求进行修改，然后点击右下角的 <strong>确定</strong> 按钮即可启动转换任务，完成转换后 Calibre 会提醒完成；</p>
</li>
<li>
<p>完成后，右击主窗口的电子书条目，可以看到操作菜单：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180906153622564761740.png" alt="20180906153622564761740.png"></p>
</li>
<li>
<p>选择 <strong>打开所在目录</strong> 就会打开一个finder窗口，转换完成的mobi的就在里面了，这样我就可以导入到我的ireader中阅读啦，·皆大欢喜！</p>
</li>
</ol>
<h2 id="参考">参考</h2>
<p><a href="https://www.epubor.com/3-ways-to-remove-drm-from-kindle-books.html">3 Ways to Remove DRM from Kindle Books</a></p>
<h2 id="提醒">提醒</h2>
<p>本文操作只是为了自己方便，电子书毕竟自己购买了，想拥有更多掌控权没什么错，但是为了尊重作者的劳动成果还是提醒大家转换了自己购买了的电子书后，不要随便传阅。</p>]]></content>
		</item>
		
		<item>
			<title>chrome网页长截图神技</title>
			<link>https://blog.5km.studio/2018/09/05/chrome-screenshot/</link>
			<pubDate>Wed, 05 Sep 2018 16:48:03 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/09/05/chrome-screenshot/</guid>
			<description>&lt;p&gt;相信大家都有过要对网页进行长截图的时候，不知道大家是怎么实现的，想必大部分的可能还是安装浏览器插件吧，当然还可能是比较奇葩的可能——用python实现，如 &lt;a href=&#34;https://blog.5km.studio/2018/06/27/spider-practice-page-screenshot/&#34;&gt;爬虫实践之网页长截图&lt;/a&gt;。本文介绍一种不用安装插件、不用写代码的方式，只需要一个chrome就能搞定，一起来感受chrome的强大！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>相信大家都有过要对网页进行长截图的时候，不知道大家是怎么实现的，想必大部分的可能还是安装浏览器插件吧，当然还可能是比较奇葩的可能——用python实现，如 <a href="/2018/06/27/spider-practice-page-screenshot/">爬虫实践之网页长截图</a>。本文介绍一种不用安装插件、不用写代码的方式，只需要一个chrome就能搞定，一起来感受chrome的强大！</p>
<h2 id="初探">初探</h2>
<p>先简单通过一个例子介绍这个功能的使用，比如截取本博客首页<a href="https://www.smslit.top">https://www.smslit.top</a>。用chrome打开这个网页，下面只需简单的三部就可以得到长截图：</p>
<ol>
<li>
<p>调出 <strong>检查</strong> 窗口，如下橙色覆盖区：
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180905153613901782059.jpg" alt="20180905153613901782059.jpg"></p>
<p>调出方式有两种：</p>
<ul>
<li>右击网页空白处，点击 <strong>检查</strong></li>
<li>快捷键调出
<ul>
<li>如果您使用 <strong>windows</strong>，按下 <code>ctrl</code> + <code>alt</code> + <code>i</code>;</li>
<li>如果您使用 <strong>macOS</strong>，按下  <code>command</code> + <code>option</code> + <code>i</code>;</li>
</ul>
</li>
</ul>
</li>
<li>
<p>调出命令入口：</p>
<ul>
<li>如果您使用 <strong>windows</strong>，按下 <code>ctrl</code> + <code>shift</code> + <code>p</code>;</li>
<li>如果您使用 <strong>mac</strong>，按下 <code>command</code> + <code>shift</code> + <code>p</code>;</li>
</ul>
<p>如下：
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180905153613951944214.png" alt="20180905153613951944214.png"></p>
</li>
<li>
<p>输入命令 <code>screenshot</code>，你会发现有三个截图方式，长截图就是 <code>Capture full size screenshot</code>：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180905153613971810921.png"
         alt="20180905153613971810921.png" width="480px"/>
</figure>

<p>单击 <code>capture fullsize screenshot</code> 就可以得到长截图了。</p>
</li>
</ol>
<h2 id="定制尺寸截图">定制尺寸截图</h2>
<p>上一节中有没有感受到chrome截图的便捷性呢？如果您想更进一步得到不同的显示尺寸的页面，请您继续阅读下面的内容，如果上面的操作已经满足您的需要，本节可略过。</p>
<p>还是以<a href="https://www.smslit.top">https://www.smslit.top</a>为例，先使用chrome打开页面。</p>
<ol>
<li>
<p>调出 <strong>检查</strong> 窗口，如上。</p>
</li>
<li>
<p>点击下图橙色标注的按钮，</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180905153614043330795.png" alt="20180905153614043330795.png"></p>
<p>可以看到页面尺寸变化，同时页面上方多出一个工具栏：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180905153614054638208.png" alt="20180905153614054638208.png"></p>
</li>
<li>
<p>单击新出现工具栏最左侧的下拉列表，选择满意的设备尺寸，或者自定义，比如我选择 <strong>iphone X</strong>，页面会有所变化：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180905153614074799871.png" alt="20180905153614074799871.png"></p>
</li>
<li>
<p>此时单击新出现工具栏最右侧的菜单按钮，就会出现一个功能菜单：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180905153614104986640.png" alt="20180905153614104986640.png"></p>
</li>
<li>
<p>选择其中的 <code>Capture full size screenshot</code> 就可以得到想要的长截图了。</p>
</li>
</ol>
<h2 id="总结">总结</h2>
<p>经过本文想必您已经掌握长截图这一神技，如果您一直习惯使用chrome，这应该是您需要的。</p>]]></content>
		</item>
		
		<item>
			<title>糖豆广场舞视频下载</title>
			<link>https://blog.5km.studio/2018/07/15/tangdou-video/</link>
			<pubDate>Sun, 15 Jul 2018 22:24:33 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/07/15/tangdou-video/</guid>
			<description>&lt;p&gt;回到老家总会面临各种奇葩的需求，装系统、下歌、修电脑、修电器，我内心是崩溃的，家人和乡亲们也真是“热情”！最近几年，乡镇文化中又多了流行的广场舞，所以我又担负起给人下载广场舞视频的重任。今天就简单说一下如何下载糖豆广场舞中的视频。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>回到老家总会面临各种奇葩的需求，装系统、下歌、修电脑、修电器，我内心是崩溃的，家人和乡亲们也真是“热情”！最近几年，乡镇文化中又多了流行的广场舞，所以我又担负起给人下载广场舞视频的重任。今天就简单说一下如何下载糖豆广场舞中的视频。</p>
<p>这不，这次回老家，老妈让给下载一下别人微信分享给她的广场舞视频，虽然内心是拒绝的，但我还是接下了这个任务。我把别人分享给老妈的信息转发给我自己，研究一下怎么下载，也就有了现在这篇文章！对于我的探索过程无感想直接看方法的同学可以点<a href="#方法">我</a>。</p>
<h2 id="探索">探索</h2>
<p>老妈一共八条广场舞共享链接，这么多，随便打开一个看一下！原来是糖豆广场舞的小程序，打开就开始播放对应的视频，找了一会儿也没发现有什么下载的地方，那就google一下吧，简直了，<a href="http://www.tangdou.com">糖豆广场舞</a>还有自己的网站，厉害了，广场舞！在糖豆广场舞中随便打开一个视频，原来是一个FLASH视频网站，去死吧（我已经卸载掉了Adobe Flash Player），不能播放！但是赫然看到几个字：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180715153166773944403.png" width="50%"/>
</figure>

<p>嘿嘿，以我的尿性，我是坚决不会为了这么小的事情下这个app的，所以继续探索，发现了一个专门解析视频的工具网站<a href="https://www.parsevideo.com/">视频解析网</a>，这里可解析的平台好多，也支持糖豆的：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180715153166810969671.png" alt="20180715153166810969671.png"></p>
<p>当然要尝试一下啦，我试了一个糖豆视频，发现输入链接点击解析按钮以后，过了一会儿下面出现了两个链接，分了两种清晰度的链接，哇塞，好像可用，<strong>BUT</strong> ，试了第二个、第三个和第n个，好像都不行了，出现了信息：</p>
<blockquote>
<p>数据获取失败，欢迎加入网站底部QQ群提交错误信息！</p>
</blockquote>
<p>算了，没必要了，这时想起了：手机既然能看，尤其是iOS平台的，那么说明不支持flash的平台也可以正常观看，那么一定在观看的时候一定可以得到视频链接。</p>
<p>现在浏览器均可以模拟不同浏览器进行渲染网页，所以也可以按照iphone的safari模式渲染，打开了一个视频，果然可以得到链接并进行视频下载，具体过程见<a href="#方法">方法</a>。</p>
<h2 id="方法">方法</h2>
<p>这里的测试平台：</p>
<ul>
<li>macOS 10.13.5</li>
<li>浏览器：safari</li>
</ul>
<p><strong>Tip：</strong></p>
<p>不同浏览器虽然操作有所差异，但是应该都会支持模拟移动设备渲染的，所以依据下面方法进行相应类比操作也能达到相同效果。</p>
<h3 id="锁定视频">锁定视频</h3>
<p><a href="http://www.tangdou.com">糖豆网</a>支持搜索功能，一般家里人都会下载知道歌名等具体信息的广场舞视频，比如小程序分享的糖豆广场舞视频都会显示视频的具体标题，所以我们就用这些关键信息搜索视频，比如微信共享信息中的这个：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180715153166927119248.png" width="80%"/>
</figure>

<p>所以可以搜索「<strong>丽丽自由广场舞《拥抱你离去》</strong>」：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180715153166945860086.png" alt="20180715153166945860086.png"></p>
<p>得到搜索结果，很容易就能找到我们的目标视频：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180715153166972665100.png" alt="20180715153166972665100.png"></p>
<h3 id="获取链接">获取链接</h3>
<p>打开上面找到的视频后，在 <strong>菜单栏</strong> - <strong>开发</strong> - <strong>用户代理</strong> 中找到相应代理，我用的是 <strong>Safari浏览器-iOS11.0-iPhone</strong>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180715153167010664947.png" alt="20180715153167010664947.png"></p>
<p>此时就可以以iphone上safari的方式打开链接，等几秒钟过去广告，就可以看到html5视频，右键单击视频区就能看到拷贝视频地址，得到地址可以使用自己喜欢的方式下载。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180715153167032317400.png" alt="20180715153167032317400.png"></p>
<h3 id="视频下载">视频下载</h3>
<p>得到链接以后，可以利用下载工具下载，比如迅雷、Aria2，也可以使用浏览器自带下载器，这里以浏览器下载为例讲一下。</p>
<p>新标签页打开链接后，会出现html视频播放内容，在视频区同样右键单击就能看到 <strong>下载视频为</strong> ，点击后更改文件名保存即可。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180716153167074790691.jpg" alt="20180716153167074790691.jpg"></p>
<h2 id="最后">最后</h2>
<p>方式略 <strong>low</strong>，但还算有效，以此文记录方法，方便以后回老家继续做义工！</p>]]></content>
		</item>
		
		<item>
			<title>platformio串口监视器功能</title>
			<link>https://blog.5km.studio/2018/07/11/platformio-command-device/</link>
			<pubDate>Wed, 11 Jul 2018 23:28:08 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/07/11/platformio-command-device/</guid>
			<description>&lt;p&gt;今天收到了前些天买的&lt;a href=&#34;https://store.arduino.cc/arduino-leonardo-with-headers&#34;&gt;Arduino Leonardo&lt;/a&gt;，写了一个简单的程序测试了一下基本的LED blinky和串口通信，本文简单介绍一下如何使用 &lt;strong&gt;platformio&lt;/strong&gt; 监视 Arduino的串口数据。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180711153132350560223.jpg&#34; alt=&#34;20180711153132350560223.jpg&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天收到了前些天买的<a href="https://store.arduino.cc/arduino-leonardo-with-headers">Arduino Leonardo</a>，写了一个简单的程序测试了一下基本的LED blinky和串口通信，本文简单介绍一下如何使用 <strong>platformio</strong> 监视 Arduino的串口数据。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180711153132350560223.jpg" alt="20180711153132350560223.jpg"></p>
<p>预警：如果您对操作命令行不感冒，那么本篇文章不适合您的口味，不过看看了解一下也不错！</p>
<p>本文测试条件：</p>
<ul>
<li>电脑操作系统 -&gt; <strong>macOS</strong></li>
<li>使用工具 -&gt; <strong>platformio</strong></li>
<li>开发板 -&gt; <strong>Arduino Leonardo</strong></li>
</ul>
<h2 id="测试程序">测试程序</h2>
<p>本人喜欢使用vim，所以使用文章 <a href="/2017/08/15/platformio-vim/">vim也是platformio的IDE</a> 中的方法新建工程：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">mkdir leonardo_play <span class="o">&amp;&amp;</span> <span class="nb">cd</span> leonardo_play
pio init --ide vim --board leonardo
</code></pre></div><p>完成工程的新建后，为了方便构建程序和上传程序到开发板 <strong>Arduino Leonardo</strong>，这里还新建文件 <code>Makefile</code>，内容为：</p>
<div class="highlight"><pre class="chroma"><code class="language-makefile" data-lang="makefile"><span class="nf">all</span><span class="o">:</span>
	platformio -f -c vim run

<span class="nf">upload</span><span class="o">:</span>
	platformio -f -c vim run --target upload

<span class="nf">clean</span><span class="o">:</span>
	platformio -f -c vim run --target clean

<span class="nf">program</span><span class="o">:</span>
	platformio -f -c vim run --target program

<span class="nf">uploadfs</span><span class="o">:</span>
	platformio -f -c vim run --target uploadfs

<span class="nf">update</span><span class="o">:</span>
	platformio -f -c vim update
</code></pre></div><p>添加测试源码 <code>src/main.cpp</code>，代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-c++" data-lang="c++"><span class="cp">#include</span> <span class="cpf">&lt;Arduino.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">115200</span><span class="p">);</span>
    <span class="n">pinMode</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">digitalWrite</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">&#34;Led on!</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
    <span class="n">delay</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
    <span class="n">digitalWrite</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">&#34;Led off!</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
    <span class="n">delay</span><span class="p">(</span><span class="mi">900</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div><p>上述代码主要实现LED管脚和串口的初始化，在主循环中，每一秒LED闪动一次，期间串口会发送LED状态。以此代码来测试新收到的开发板，还有介绍本文的主题，串口数据监视器功能。</p>
<p>连接 <strong>Arduino Leonardo</strong> 和 电脑，编译工程并上传程序：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">make all <span class="o">&amp;&amp;</span> make upload
</code></pre></div><p>程序上传成功后看到，板载led开始闪动，说明程序基本运行了：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/platform/command/device/blinky.mp4" width="100%" controls="controls" loop="loop"></video></p>
<p>下面我们只需要看一下，串口是否打印了LED状态。</p>
<h2 id="device命令"><code>device</code>命令</h2>
<p>这个命令用来控制设备，常用的功能就是监视设配通过串口发送的数据，或向设备发送串口数据。串口用的很多，所以这里只拿串口来分享经验。</p>
<p>执行命令，查看一下帮助信息：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">➜  leonardo pio device -h
Usage: pio device <span class="o">[</span>OPTIONS<span class="o">]</span> COMMAND <span class="o">[</span>ARGS<span class="o">]</span>...

Options:
  -h, --help  Show this message and exit.

Commands:
  list     List devices
  monitor  Monitor device <span class="o">(</span>Serial<span class="o">)</span>
</code></pre></div><p>可以看到有两个子命令：</p>
<ul>
<li><strong>list</strong> : 列出所有可用设备；</li>
<li><strong>monitor</strong> : 监视串口设备；</li>
</ul>
<h3 id="列出可用设备">列出可用设备</h3>
<p>执行命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pio device list
</code></pre></div><p>此时就会列出所有可用大的串口接口，我的是这样：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  leonardo pio device list
/dev/cu.SOC
-----------
Hardware ID: n/a
Description: n/a

/dev/cu.MALS
------------
Hardware ID: n/a
Description: n/a

/dev/cu.Bluetooth-Incoming-Port
-------------------------------
Hardware ID: n/a
Description: n/a

/dev/cu.yeAirPods-WirelessiAP-1
-------------------------------
Hardware ID: n/a
Description: n/a

/dev/cu.usbmodem1441
--------------------
Hardware ID: USB VID:PID<span class="o">=</span>2341:8036 <span class="nv">LOCATION</span><span class="o">=</span>20-6
Description: Arduino Leonardo
</code></pre></div><p>能够看到， <strong>Arduino Leonardo</strong> 对应的串口应该是 <code>/dev/cu.usbmodem1441</code>。</p>
<h3 id="开启监视器">开启监视器</h3>
<p><code>pio device monitor [ARGS]</code> 就能开启监视器<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>，<code>ARGS</code> 如果没有，就会使用默认值，串口监视相关的<code>ARGS</code>相关常用项使用信息如下表：</p>
<table>
<thead>
<tr>
<th style="text-align:center">参数</th>
<th style="text-align:center">默认值</th>
<th style="text-align:center">参数标识</th>
<th style="text-align:left">可选值</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">波特率</td>
<td style="text-align:center">9600</td>
<td style="text-align:center">-b, &ndash;baud</td>
<td style="text-align:left">2400、4800、9600、19200、115200等等</td>
</tr>
<tr>
<td style="text-align:center">端口</td>
<td style="text-align:center">自动识别</td>
<td style="text-align:center">-p, &ndash;port</td>
<td style="text-align:left"><code>pio devcie list</code>列出的设备地址</td>
</tr>
<tr>
<td style="text-align:center">校验位</td>
<td style="text-align:center">N</td>
<td style="text-align:center">&ndash;parity</td>
<td style="text-align:left"><code>N</code>, <code>E</code>, <code>O</code>, <code>S</code>, <code>M</code></td>
</tr>
<tr>
<td style="text-align:center">编码</td>
<td style="text-align:center">UTF-8</td>
<td style="text-align:center">&ndash;encoding</td>
<td style="text-align:left"><code>hexlify</code>, <code>UTF-8</code></td>
</tr>
<tr>
<td style="text-align:center">回显</td>
<td style="text-align:center">off</td>
<td style="text-align:center">&ndash;echo</td>
<td style="text-align:left">无设置值</td>
</tr>
</tbody>
</table>
<p>还有很多其它参数项可以设置，这里列出的只是常用的。不加 <code>ARGS</code> 只执行 <code>pio device monitor</code> 可以自动识别和匹配参数，其实在这里执行了无参数的命令就可以正常看到 <strong>Arduino Leonardo</strong> 的打印信息了。</p>
<h4 id="指定端口">指定端口</h4>
<p>有时，命令不能自己识别端口，需要我们自己指定一个特定端口，这项参数其实也可以配置到 <code>platformio.ini</code> 文件中作为参数的默认值<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。在这里我们使用前面提到的端口 <code>/dev/cu.usbmodem1441</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">pio device monitor -p /dev/cu.usbmodem1441
</code></pre></div><h4 id="通信速率">通信速率</h4>
<p>决定通信速率的参数是 <strong>波特率</strong> ，程序中我们设置了波特率为 <code>115200</code>，同样这个参数也支持配置到文件 <code>platformio.ini</code> 中以作默认值<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。如果在命令中的话，可以这样指定：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">pio device monitor -p /dev/consoleu.usbmodem1441 -b <span class="m">115200</span>
</code></pre></div><h4 id="校验位">校验位</h4>
<p>在串口通信中，为了提高串口数据剧的准确性，会使用校验位，常用的是奇偶校验，奇校验是 <strong>odd</strong>，所以对应值为 <code>O</code>，偶校验是 <strong>even</strong>，对应 <code>E</code>，默认无校验 <strong>None</strong>，对应 <code>N</code>，如果要指定偶校验位，这样做：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">pio device monitor --patity E
</code></pre></div><h4 id="编码">编码</h4>
<p>这个编码的功能类似于windows下好多串口助手中的文本/二进制选择的功能，默认串口助手开启以后会按照Ascii字符显示打印数据：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">➜  leonardo pio device monitor --echo
--- Miniterm on /dev/cu.usbmodem1441  9600,8,N,1 ---
--- Quit: Ctrl+C <span class="p">|</span> Menu: Ctrl+T <span class="p">|</span> Help: Ctrl+T followed by Ctrl+H ---
Led on!
Led off!
Led on!
Led off!
</code></pre></div><p>调试程序的时候，有可能会需要串口通信收发二进制数据，尤其是使用一定通信协议的情况下，此时想看二进制形式的数据，使用命令的时候需要指定 <code>--encoding</code> 为 <code>hexlify</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">➜  leonardo pio device monitor --encoding hexlify
--- Miniterm on /dev/cu.usbmodem1441  9600,8,N,1 ---
--- Quit: Ctrl+C <span class="p">|</span> Menu: Ctrl+T <span class="p">|</span> Help: Ctrl+T followed by Ctrl+H ---
4C <span class="m">65</span> <span class="m">64</span> <span class="m">20</span> 6F 6E <span class="m">21</span> 0A 4C <span class="m">65</span> <span class="m">64</span> <span class="m">20</span> 6F <span class="m">66</span> <span class="m">66</span> <span class="m">21</span> 0A
</code></pre></div><p>可以看到打印结果就是原来字符串对应的Ascii码了！</p>
<h4 id="数据回显">数据回显</h4>
<p>主要用于发送数据的时候，默认情况是不会显示发送的字符或数据的，当开启回显时就能显示发送的数据：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">➜  leonardo pio device monitor --echo
--- Miniterm on /dev/cu.usbmodem1441  9600,8,N,1 ---
--- Quit: Ctrl+C <span class="p">|</span> Menu: Ctrl+T <span class="p">|</span> Help: Ctrl+T followed by Ctrl+H ---
Led off!
Led on!
Led off!
Led on!
yLed off!
yyyyyLed on!
</code></pre></div><p>可以看到我发了好多 <strong>y</strong>，在开启串口监视后，在键盘上按按键就会发送按键对应的字符，在二进制模式(hexlify)下时，只识别十六进制码然后发送。</p>
<h4 id="临时配置">临时配置</h4>
<p><code>device</code>命令使用的是 <a href="https://gist.github.com/bewest/4632563">miniterm</a>，所以在使用中可以临时配置一些参数，开启监视器后可以按快捷键 <code>CTRL</code>+<code>T</code> <code>CTRL</code> + <code>H</code>就会打印帮助信息：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">--- pySerial <span class="o">(</span>3.4<span class="o">)</span> - miniterm - <span class="nb">help</span>
---
--- Ctrl+C   Exit program
--- Ctrl+T   Menu escape key, followed by:
--- Menu keys:
---    Ctrl+T  Send the menu character itself to remote
---    Ctrl+C  Send the <span class="nb">exit</span> character itself to remote
---    Ctrl+I  Show info
---    Ctrl+U  Upload file <span class="o">(</span>prompt will be shown<span class="o">)</span>
---    Ctrl+A  encoding
---    Ctrl+F  edit filters
--- Toggles:
---    Ctrl+R  RTS   Ctrl+D  DTR   Ctrl+B  BREAK
---    Ctrl+E  <span class="nb">echo</span>  Ctrl+L  EOL
---
--- Port settings <span class="o">(</span>Ctrl+T followed by the following<span class="o">)</span>:
---    p          change port
---    <span class="m">7</span> <span class="m">8</span>        <span class="nb">set</span> data bits
---    N E O S M  change parity <span class="o">(</span>None, Even, Odd, Space, Mark<span class="o">)</span>
---    <span class="m">1</span> <span class="m">2</span> <span class="m">3</span>      <span class="nb">set</span> stop bits <span class="o">(</span>1, 2, 1.5<span class="o">)</span>
---    b          change baud rate
---    x X        disable/enable software flow control
---    r R        disable/enable hardware flow control
</code></pre></div><p>根据帮助信息进行操作即可修改通信参数。</p>
<h2 id="总结">总结</h2>
<p>本文主要分享了 <code>platformio</code> 的串口监视功能，程序设计中少不了串口的使用，所以这个功能一定是非常需要的，尤其是对于喜欢使用 CLI 的童鞋，希望本文对您有所帮助！</p>
<section class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1" role="doc-endnote">
<p><a href="http://docs.platformio.org/en/latest/userguide/cmd_device.html">platformio device</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p><a href="http://docs.platformio.org/en/latest/projectconf/section_env_monitor.html"> Project Configuration File platformio.ini - Monitor options</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</section>]]></content>
		</item>
		
		<item>
			<title>hugo博客添加评论系统Valine</title>
			<link>https://blog.5km.studio/2018/07/08/hugo-valine/</link>
			<pubDate>Sun, 08 Jul 2018 17:01:05 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/07/08/hugo-valine/</guid>
			<description>&lt;p&gt;博客已经基本从 &lt;a href=&#34;https://hexo.io&#34;&gt;hexo&lt;/a&gt; 迁移到 &lt;a href=&#34;https://gohugo.io/&#34;&gt;hugo&lt;/a&gt; ，对使用的 &lt;a href=&#34;https://github.com/olOwOlo/hugo-theme-even&#34;&gt;Even&lt;/a&gt; 主题非常满意，虽自带了评论系统 &lt;a href=&#34;https://github.com/imsun/gitment&#34;&gt;gitment&lt;/a&gt; 的支持，但更喜欢迁移之前在hexo博客中使用的一个非常清新的评论系统，那就是 &lt;a href=&#34;https://valine.js.org/&#34;&gt;valine&lt;/a&gt;，本文就讲一下如何在 &lt;a href=&#34;https://github.com/olOwOlo/hugo-theme-even&#34;&gt;Even&lt;/a&gt; 中添加 &lt;a href=&#34;https://valine.js.org/&#34;&gt;Valine&lt;/a&gt; 的支持。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>博客已经基本从 <a href="https://hexo.io">hexo</a> 迁移到 <a href="https://gohugo.io/">hugo</a> ，对使用的 <a href="https://github.com/olOwOlo/hugo-theme-even">Even</a> 主题非常满意，虽自带了评论系统 <a href="https://github.com/imsun/gitment">gitment</a> 的支持，但更喜欢迁移之前在hexo博客中使用的一个非常清新的评论系统，那就是 <a href="https://valine.js.org/">valine</a>，本文就讲一下如何在 <a href="https://github.com/olOwOlo/hugo-theme-even">Even</a> 中添加 <a href="https://valine.js.org/">Valine</a> 的支持。</p>
<blockquote>
<p>Valine - 一款快速、简洁且高效的无后端评论系统。</p>
</blockquote>
<blockquote>
<p>Valine 诞生于2017年8月7日，是一款基于Leancloud的快速、简洁且高效的无后端评论系统。</p>
</blockquote>
<blockquote>
<p>理论上支持但不限于静态博客，目前已有Hexo、Jekyll、Typecho等博客程序在使用Valine。</p>
</blockquote>
<blockquote>
<p><a href="https://valine.js.org">@Valine</a></p>
</blockquote>
<p>所以，理论上它也是支持 <strong>Hugo</strong> 的， 实践证明，确实如此。其特性如下：</p>
<ul>
<li>快速</li>
<li>安全</li>
<li>Emoji 😉</li>
<li>无后端实现</li>
<li>MarkDown 全语法支持</li>
<li>轻量易用(~15kb gzipped)</li>
<li>文章阅读量统计 v1.2.0-beta1+</li>
</ul>
<p>这么好的评论系统，值得宣传，希望看到此文章的童鞋，也支持一下 <a href="https://valine.js.org/#%E6%8D%90%E8%B5%A0">Valine</a>。下面就讲一下我是如何一步步添加 <a href="https://valine.js.org">Valine</a> 支持的。</p>
<p><strong>Tips:</strong></p>
<ul>
<li>整个过程，是以<strong>Even</strong>主题为例的，其它主题操作大同小异。</li>
<li>配置之前应该先阅读<a href="https://valine.js.org/quickstart.html">Valine快速开始</a></li>
</ul>
<h2 id="leancloud相关配置">Leancloud相关配置</h2>
<p>评论系统依赖于leancloud，所以需要先在leancloud中进行相关的准备工作。</p>
<ul>
<li><a href="https://leancloud.cn/dashboard/login.html#/signin">登录</a> 或 <a href="https://leancloud.cn/dashboard/login.html#/signup">注册</a> LeanCloud</li>
<li>登录成功后，进入后台点击左上角的创建应用：
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180708153104380829479.png" width="80%"/>
</figure>
</li>
<li>创建好应用，进入应用，左边栏找到 <strong>设置</strong> ，然后点击 <strong>应用Key</strong>，此时记录出现的 <strong>App ID</strong> 和 <strong>App Key</strong>，后面配置文件中会用到：
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180708153104457148134.png"/>
</figure>
</li>
<li>因为评论和文章阅读数统计依赖于存储，所以还需要建立两个新的存储 <code>Class</code>，左边栏找到并点击 <strong>存储</strong>，点击 <strong>创建Class</strong>:
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180708153104475972323.png" width="80%"/>
</figure>
</li>
<li>创建两个存储Class，分别命名为: <code>Counter</code> 和 <code>Comment</code>;</li>
<li>还需要为应用添加安全域名，左边栏点击 <strong>设置</strong>，找到 <strong>安全中心</strong>，点击后会看到 <strong>安全域名</strong> 设置框，输入博客使用的域名，点击保存即可：
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180708153104592457270.png"/>
</figure>
</li>
</ul>
<h2 id="configtoml添加参数">config.toml添加参数</h2>
<p>为了使配置更灵活，将 <strong>Valine</strong> 中大部分初始化参数项均设置为配置文件中的参数项，在 <strong>config.toml</strong> 的适当位置，比如我的文件中 <strong>[params.gitment]</strong> 的下面：</p>
<div class="highlight"><pre class="chroma"><code class="language-toml" data-lang="toml">  <span class="p">[</span><span class="nx">params</span><span class="p">.</span><span class="nx">gitment</span><span class="p">]</span>          <span class="c"># Gitment is a comment system based on GitHub issues. see https://github.com/imsun/gitment</span>
    <span class="nx">owner</span> <span class="p">=</span> <span class="s2">&#34;&#34;</span>              <span class="c"># Your GitHub ID</span>
    <span class="nx">repo</span> <span class="p">=</span> <span class="s2">&#34;&#34;</span>               <span class="c"># The repo to store comments</span>
    <span class="nx">clientId</span> <span class="p">=</span> <span class="s2">&#34;&#34;</span>           <span class="c"># Your client ID</span>
    <span class="nx">clientSecret</span> <span class="p">=</span> <span class="s2">&#34;&#34;</span>       <span class="c"># Your client secret</span>

  <span class="c"># 这里添加Valine的相关参数</span>

</code></pre></div><p>添加 <strong>Valine</strong> 参数项：</p>
<div class="highlight"><pre class="chroma"><code class="language-toml" data-lang="toml">  <span class="c"># Valine.</span>
  <span class="c"># You can get your appid and appkey from https://leancloud.cn</span>
  <span class="c"># more info please open https://valine.js.org</span>
  <span class="p">[</span><span class="nx">params</span><span class="p">.</span><span class="nx">valine</span><span class="p">]</span>
    <span class="nx">enable</span> <span class="p">=</span> <span class="kc">true</span>
    <span class="nx">appId</span> <span class="p">=</span> <span class="s1">&#39;你的appId&#39;</span>
    <span class="nx">appKey</span> <span class="p">=</span> <span class="s1">&#39;你的appKey&#39;</span>
    <span class="nx">notify</span> <span class="p">=</span> <span class="kc">false</span>  <span class="c"># mail notifier , https://github.com/xCss/Valine/wiki</span>
    <span class="nx">verify</span> <span class="p">=</span> <span class="kc">false</span> <span class="c"># Verification code</span>
    <span class="nx">avatar</span> <span class="p">=</span> <span class="s1">&#39;mm&#39;</span> 
    <span class="nx">placeholder</span> <span class="p">=</span> <span class="s1">&#39;说点什么吧...&#39;</span>
    <span class="nx">visitor</span> <span class="p">=</span> <span class="kc">true</span>
</code></pre></div><p>上面几项内容的含义，这里简单一说，具体还是要看 <a href="https://valine.js.org/configuration.html">Valine官网中配置相关的内容</a>：</p>
<table>
<thead>
<tr>
<th style="text-align:center">参数</th>
<th style="text-align:left">用途</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">enable</td>
<td style="text-align:left">这是用于主题中配置的，不是官方Valine的参数，<strong>true</strong>时控制开启此评论系统</td>
</tr>
<tr>
<td style="text-align:center">appId</td>
<td style="text-align:left">这是在 <a href="https://leancloud.cn/">leancloud</a> 后台应用中获取的，也就是上面提到的 <strong>App ID</strong></td>
</tr>
<tr>
<td style="text-align:center">appKey</td>
<td style="text-align:left">这是在 <a href="https://leancloud.cn/">leancloud</a> 后台应用中获取的，也就是上面提到的 <strong>App Key</strong></td>
</tr>
<tr>
<td style="text-align:center">notify</td>
<td style="text-align:left">用于控制是否开启邮件通知功能，具体参考<a href="https://github.com/xCss/Valine/wiki/Valine-%E8%AF%84%E8%AE%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E7%9A%84%E9%82%AE%E4%BB%B6%E6%8F%90%E9%86%92%E8%AE%BE%E7%BD%AE">邮件提醒配置</a></td>
</tr>
<tr>
<td style="text-align:center">verify</td>
<td style="text-align:left">用于控制是否开启评论验证码功能</td>
</tr>
<tr>
<td style="text-align:center">avatar</td>
<td style="text-align:left">用于配置评论项中用户头像样式，有多种选择：mm, identicon, monsterid, wavatar, retro, hide。详细参考：<a href="https://valine.js.org/avatar.html">头像配置</a></td>
</tr>
<tr>
<td style="text-align:center">placehoder</td>
<td style="text-align:left">评论框的提示符</td>
</tr>
<tr>
<td style="text-align:center">visitor</td>
<td style="text-align:left">控制是否开启文章阅读数的统计功能i, 详情阅读<a href="https://valine.js.org/visitor.html">文章阅读数统计</a></td>
</tr>
</tbody>
</table>
<h2 id="修改主题文件">修改主题文件</h2>
<p>主要是修改主题中评论相关的布局文件 <code>themes/even/layouts/partials/comments.html</code>，按照 <a href="https://valine.js.org/quickstart.html">Valine快速开始</a> 添加 <strong>Valine</strong> 相关代码，找到以下位置，大概55～81行的位置：</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html">  <span class="c">&lt;!-- gitment --&gt;</span>
  {{- if .Site.Params.gitment.enable -}}
  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;comments-gitment&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
  <span class="c">&lt;!--这里省略了部分代码--&gt;</span>
  <span class="p">&lt;</span><span class="nt">noscript</span><span class="p">&gt;</span>Please enable JavaScript to view the <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://github.com/imsun/gitment&#34;</span><span class="p">&gt;</span>comments powered by gitment.<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">noscript</span><span class="p">&gt;</span>
  {{- end }}

  <span class="c">&lt;!--这个位置添加Valine相关代码--&gt;</span>
</code></pre></div><p>添加的 <strong>Valine</strong> 评论的代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html">  <span class="c">&lt;!-- valine --&gt;</span>
  {{- if .Site.Params.valine.enable -}}
  <span class="c">&lt;!-- id 将作为查询条件 --&gt;</span>
  <span class="p">&lt;</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;{{ .URL | relURL }}&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;leancloud_visitors&#34;</span> <span class="na">data-flag-title</span><span class="o">=</span><span class="s">&#34;{{ .Title }}&#34;</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;post-meta-item-text&#34;</span><span class="p">&gt;</span>文章阅读量 <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;leancloud-visitors-count&#34;</span><span class="p">&gt;</span>1000000<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;vcomments&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;//cdn1.lncld.net/static/js/3.0.4/av-min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#39;//unpkg.com/valine/dist/Valine.min.js&#39;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/javascript&#34;</span><span class="p">&gt;</span>
    <span class="k">new</span> <span class="nx">Valine</span><span class="p">({</span>
        <span class="nx">el</span><span class="o">:</span> <span class="s1">&#39;#vcomments&#39;</span> <span class="p">,</span>
        <span class="nx">appId</span><span class="o">:</span> <span class="s1">&#39;{{ .Site.Params.valine.appId }}&#39;</span><span class="p">,</span>
        <span class="nx">appKey</span><span class="o">:</span> <span class="s1">&#39;{{ .Site.Params.valine.appKey }}&#39;</span><span class="p">,</span>
        <span class="nx">notify</span><span class="o">:</span> <span class="p">{{</span> <span class="p">.</span><span class="nx">Site</span><span class="p">.</span><span class="nx">Params</span><span class="p">.</span><span class="nx">valine</span><span class="p">.</span><span class="nx">notify</span> <span class="p">}},</span> 
        <span class="nx">verify</span><span class="o">:</span> <span class="p">{{</span> <span class="p">.</span><span class="nx">Site</span><span class="p">.</span><span class="nx">Params</span><span class="p">.</span><span class="nx">valine</span><span class="p">.</span><span class="nx">verify</span> <span class="p">}},</span> 
        <span class="nx">avatar</span><span class="o">:</span><span class="s1">&#39;{{ .Site.Params.valine.avatar }}&#39;</span><span class="p">,</span> 
        <span class="nx">placeholder</span><span class="o">:</span> <span class="s1">&#39;{{ .Site.Params.valine.placeholder }}&#39;</span><span class="p">,</span>
        <span class="nx">visitor</span><span class="o">:</span> <span class="p">{{</span> <span class="p">.</span><span class="nx">Site</span><span class="p">.</span><span class="nx">Params</span><span class="p">.</span><span class="nx">valine</span><span class="p">.</span><span class="nx">visitor</span> <span class="p">}}</span>
    <span class="p">});</span>
  <span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
  {{- end }}
</code></pre></div><p>可以看到上述代码中引用了配置文件中的相关参数，这样以后修改配置就不用修改代码了，只需要改配置文件 <code>config.toml</code>，另外注意到的是，我也添加了文章阅读数统计的显示内容。将配置文件中 <strong>valine</strong> 配置的 <code>eanble</code> 设置为 <code>true</code> ，本地测试一下，正常的话，打开一篇文章会看到：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180708153104555886981.png"/>
</figure>

<p>此时，生成静态博客文件，部署到自己的托管平台，正常的话打开博客中的一篇文章，就可以看到正常的文章计数和评论框了，此时随便评论一条，验证一下，评论如果成功，可以去leancloud后台看一下 <code>Comment</code> 和 <code>Counter</code>存储中新加了相应网址的条目。</p>
<h2 id="完善评论通知">完善评论通知</h2>
<p><strong>Valine</strong> 评论邮件提醒功能不太健全，通知邮件中没有文章直达链接，<strong>Valine</strong> 官网中提供了评论系统第三方功能扩展<a href="https://github.com/zhaojun1998/Valine-Admin">Valine</a>链接，按照链接中的说明，非常详细的步骤，一步步很容易实现完备的评论系统后台管理以及邮件提醒功能，部分高级配置<a href="https://github.com/zhaojun1998/Valine-Admin/blob/master/%E9%AB%98%E7%BA%A7%E9%85%8D%E7%BD%AE.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%82%AE%E4%BB%B6%E6%9C%8D%E5%8A%A1%E5%99%A8">点我</a>了解，这里简单列举步骤如下：</p>
<ul>
<li>进入leancloud后台相关应用，<strong>云引擎</strong> 中部署链接中对应的代码；</li>
<li>添加环境变量，此时就可以使用；</li>
<li><strong>云引擎</strong> —— <strong>设置</strong> —— <strong>Web主机域名</strong>，然后进入 <strong>存储</strong> —— <strong>_User</strong> 添加一个用户，只需 User，password，email 三个信息即可，为了安全email必须是第二步中设置的发送邮箱 <strong>SMTP_USER</strong> 或站长邮箱 <strong>TO_EMAIL</strong>;此时可以使用定义的主机域名登录后台管理系统了，用户名为刚设置的邮箱；</li>
<li>LeanCloud 休眠策略的话，我自己有一个VPS，所以在VPS中设置了定时任务去唤醒我的leancloud应用，远程登录VPS系统，执行命令<code>crontab -e</code> 即可使用vim编辑任务，填入内容<code>*/20 7-23 * * * curl https://你配置的域名前缀.leanapp.cn</code>，任务中替换自己的主机域名，保存即可生效；</li>
</ul>
<p>登录上面主机域名进入后台瞅一瞅：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2018070815310476526255.png" width="480"/>
</figure>

<p>我自己沙发了一条评论，进入后台后可以看到：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180708153104781462098.png"/>
</figure>

<p>同时，我也收到了通知邮件：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180708153104796086245.png"/>
</figure>

<p>至此完成了 <strong>Valine</strong> 评论系统的添加和完善，喝杯咖啡☕️庆祝一下！</p>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://valine.js.org">Valine</a></li>
<li><a href="https://github.com/zhaojun1998/Valine-Admin">Valine-Admin</a></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>hexo博客迁移到hugo</title>
			<link>https://blog.5km.studio/2018/07/07/hexo2hugo/</link>
			<pubDate>Sat, 07 Jul 2018 21:31:38 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/07/07/hexo2hugo/</guid>
			<description>&lt;p&gt;前段时间了解了一下 &lt;a href=&#34;https://gohugo.io&#34;&gt;hugo&lt;/a&gt;，发现了其一个非常突出的优点，那就是快，据说&lt;strong&gt;5000篇文章渲染只需要6s的时间&lt;/strong&gt;。阅读官方文档进一步了解了一下，使用上与hexo模式类似，加上也有非常多的主题，再考虑到后期文章会不断增多，决定放弃hexo使用hugo，毕竟现在90+篇文章，hexo渲染一遍竟然需要8s+，叔能忍，哥不能忍！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>前段时间了解了一下 <a href="https://gohugo.io">hugo</a>，发现了其一个非常突出的优点，那就是快，据说<strong>5000篇文章渲染只需要6s的时间</strong>。阅读官方文档进一步了解了一下，使用上与hexo模式类似，加上也有非常多的主题，再考虑到后期文章会不断增多，决定放弃hexo使用hugo，毕竟现在90+篇文章，hexo渲染一遍竟然需要8s+，叔能忍，哥不能忍！</p>
<p>本文不是教程，只是记录过程！是的，这就是一水文，xx的流水账！</p>
<h2 id="hugo">Hugo</h2>
<blockquote>
<p>Hugo is a fast and modern static site generator written in Go, and designed to make website creation fun again.</p>
</blockquote>
<blockquote>
<p>@Hugo <a href="https://gohugo.io/about/what-is-hugo/">What is Hugo</a></p>
</blockquote>
<p>Hugo是Go语言实现的，这可能就是其做到神速的原因。</p>
<h3 id="安装">安装</h3>
<p>Hugo不依赖任何运行库和程序，是一个完整独立的运行程序。在macOS下安装很简单，使用homebrew就可以轻松安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">brew install hugo
</code></pre></div><p>安装完成可以查看一下版本号：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">hugo version
</code></pre></div><h3 id="创建站点">创建站点</h3>
<p>下面可以创建一个新的博客站点，我继续叫smslit：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">hugo new site smslit
<span class="nb">cd</span> smslit
ls -l
</code></pre></div><p>此时会看到出现新的目录 <code>smslit</code> ，进入此目录会看到如下文件结构：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180707153097244537665.png" alt="20180707153097244537665.png"></p>
<p>上面文件及目录与hexo博客的对应关系是：</p>
<table>
<thead>
<tr>
<th style="text-align:center">hugo目录</th>
<th style="text-align:center">hexo目录</th>
<th style="text-align:left">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">content</td>
<td style="text-align:center">source</td>
<td style="text-align:left">存储博文的md文件，hexo中的_post,_draft,各自定义的页面目录其实对应到hugo中的话，都是在content下的</td>
</tr>
<tr>
<td style="text-align:center">config.toml</td>
<td style="text-align:center"><strong>_config.yaml</strong></td>
<td style="text-align:left">用于配置站点中各项参数</td>
</tr>
<tr>
<td style="text-align:center">themes</td>
<td style="text-align:center">themes</td>
<td style="text-align:left">存放主题</td>
</tr>
<tr>
<td style="text-align:center">static</td>
<td style="text-align:center">source</td>
<td style="text-align:left">hugo中的static目录中存放一些常规静态文件，可以是博客中引用的图片、文件也可以是自添加的任意文件，最终渲染都会放到站点的根目录</td>
</tr>
<tr>
<td style="text-align:center">public</td>
<td style="text-align:center">public</td>
<td style="text-align:left">存放渲染后的站点静态文件</td>
</tr>
<tr>
<td style="text-align:center">layout</td>
<td style="text-align:center">无</td>
<td style="text-align:left">存放自定义的样式布局文件，优先级高于主题中的同名文件，方便修改主题样式</td>
</tr>
</tbody>
</table>
<h4 id="主题">主题</h4>
<p>我选择了 <a href="https://github.com/olOwOlo/hugo-theme-even">Even</a>主题，安装很简单：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">git clone https://github.com/olOwOlo/hugo-theme-even themes/even
cp themes/even/exampleSite/config.toml .
</code></pre></div><p>修改配置文件，这里主要是根据自己需要进行定制和修改：</p>
<ul>
<li>
<p>修改菜单栏项与原hexo博客中的一致；</p>
</li>
<li>
<p>修改相应的社交网络链接；</p>
</li>
<li>
<p>使能gitment</p>
<p>&hellip;</p>
</li>
</ul>
<p>执行命令，渲染站点并在本地部署：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">hugo server
</code></pre></div><p>打开链接 <a href="127.0.0.1:1313">127.0.0.1:1313</a>，看到了静态博客的样子，点点各项链接，不正确的相应调整。</p>
<h4 id="新建文章">新建文章</h4>
<p>使用主题一定要看一下主题中的样例站点，文章内容管理方式要与样例一致，所以查看 <code>themes/even/exampleSite/content</code>中的目录，我是直接cp过来的：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">cp -r themes/even/exampleSite/content/* content
</code></pre></div><p>删掉post中的文章，然后新建了与配置文件中菜单栏一致的目录，并添加相应的index.md文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  smslit tree content
content
├── about
│   └── index.md
├── drip
│   └── index.md
└── post
</code></pre></div><p>将 <code>about/index.md</code>(对应菜单项——关于) 和 <code>drip/index.md</code>(对应菜单项——札记) 内容更改为hexo博客中相应文件的内容，调整内容中的不兼容标签项。</p>
<p>使用命令新建一篇博文：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  smslit hugo new post/hello.md
</code></pre></div><p>此时，本地部署查看博客，发现菜单项<strong>关于</strong>和<strong>札记</strong>正常显示内容，但是没有发现新加的文章。</p>
<p>打开文件，会发现头信息中有一项 <code>draft</code> 值为 <code>true</code>，说明新建的文件归为草稿，原来是hugo部署和生成静态站点文件的时候会根据md文件中的 <code>draft</code> 值来判断是否渲染，没有此项内容认为是 <code>false</code> ,也就是添加渲染，所以这里可以将 <code>draft</code>项的值改为 <code>false</code> 或者直接删掉，再次尝试部署，可以在 <a href="127.0.0.1:1313">127.0.0.1:1313</a>中看到新加的文章。</p>
<h2 id="迁移博文">迁移博文</h2>
<p>迁移博文主要面临两个问题：</p>
<ul>
<li>md文件头信息(Front Matter)格式有区别；</li>
<li>hexo和hugo的部分标签语言不兼容，比如hexo的<code>{% image %}</code>；</li>
</ul>
<h3 id="文章头信息">文章头信息</h3>
<p>在hugo中文件的头信息有三种格式可选：yaml、toml和json。这个可以在 <code>config.toml</code>中配置，为了迁移方便，所以选择了hexo中博文使用的yaml格式。总结博文不兼容点如下：</p>
<ul>
<li>文件名，在hugo中不用包含日期；</li>
<li>不包含<strong>layout</strong>项；</li>
<li>hugo中<strong>date</strong>项的格式是：<strong>2018-07-05T00:00:00+08:00</strong></li>
<li>hugo中<strong>tags</strong>和<strong>categories</strong>的格式有两种，不兼容hexo中多标签时只使用<code>,</code>分隔的方式；</li>
</ul>
<p>根据上述四项的不同，编写了python脚本批量处理文章：<a href="https://github.com/smslit/tools-with-script/blob/master/hexo2hugo/hexo2hugo.py">hexo2hugo.py</a></p>
<h3 id="标签语言不兼容">标签语言不兼容</h3>
<p>这个问题，用python脚本批处理的话比较不值得了，比较使用特殊的hexo标签的地方并不是很多，所以，可以使用grep命令帮助寻找包含 <code>{%</code>的md文件，然后打开相应文件修改为hugo下对应格式就可以了。</p>
<h3 id="其它问题">其它问题</h3>
<p>上面两个问题解决后，将文章放到 <code>content/post</code>下，本地部署查看，所有的文章均正常出现了，但是会发现一个问题是，文章列表中的预览内容并没有按照 <code>&lt;!--more--&gt;</code> 标记分界显示，后来发现原来是标记的格式不对，文章中的标记为 <code>&lt;!-- more --&gt;</code>，<strong>more</strong> 两边多了两个空格，hugo只识别不带空格的 <strong>more</strong> 标记，为此编写了python脚本<a href="https://github.com/smslit/tools-with-script/blob/master/hexo2hugo/format_more_tag.py">format_more_tag.py</a>进行批处理，解决！</p>
<h2 id="发布">发布</h2>
<p>我的博客是部署到<a href="https://coding.net">coding</a>的，国内访问比较快，与部署到github无异，不过比较好的是，coding中的pages服务可以强制使用<code>https</code>，😁！hugo没有像hexo一样专门的部署命令，所以我编写了一个部署脚本，在使用脚本前需要做以下工作：</p>
<ul>
<li>将静态文件的仓库clone到本地，比如到<code>~/Downloads</code>目录，仓库目录最好是设置成隐藏目录，目录名加个.即可；</li>
<li>进入<code>~/Downloads/.smslit</code>中，删掉所有除了.git的文件，将hugo博客中public目录下所有文件cp到<code>~/Downloads/.smslit</code>下，提交一下修改，并推到coding中，等到coding的pages服务完成渲染，访问<a href="https://www.smslit.top">https://www.smslit.top </a>，已经成为本地hugo渲染的样子，说明已经打通通道；</li>
<li>在目录<code>/usr/local/bin</code>下创建脚本文件 <code>deploy</code>，内容如下：
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="cp">#!/bin/bash
</span><span class="cp"></span><span class="c1"># ~/smslit目录为hugo站点目录</span>
<span class="nb">cd</span> ~/smslit

<span class="c1"># ~/Downloads/.hogo目录为本地静态文件的git仓库</span>
cp -r ~/Downloads/.hugo/.git public

<span class="nb">echo</span> <span class="s1">&#39;Generating Web Pages...&#39;</span>
hugo

<span class="nb">cd</span> public
git add .
git commit -m <span class="s1">&#39;update pages&#39;</span>

<span class="nb">echo</span> <span class="s1">&#39;Deploy the pages&#39;</span>
git push

rm -rf ~/Downloads/.hugo/
mkdir ~/Downloads/.hugo
mv ~/smslit/public/.git ~/Downloads/.hugo/
<span class="nb">echo</span> <span class="s1">&#39;Done, have a good time!&#39;</span>
</code></pre></div></li>
<li>为脚本<code>/usr/local/bin/deploy</code>添加执行权限：<code>chmod +x /usr/local/bin/deploy</code></li>
</ul>
<p>完成上述工作，重新开启一个终端，执行命令就可以自动渲染并提交静态文件修改了：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">deploy
</code></pre></div><p>可以看到，静态站点文件生成速度很快，原来hexo需要8s+，现在只需要300+ms:</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">                   <span class="p">|</span> ZH-CN
+------------------+-------+
  Pages            <span class="p">|</span>   <span class="m">254</span>
  Paginator pages  <span class="p">|</span>    <span class="m">13</span>
  Non-page files   <span class="p">|</span>     <span class="m">0</span>
  Static files     <span class="p">|</span>   <span class="m">264</span>
  Processed images <span class="p">|</span>     <span class="m">0</span>
  Aliases          <span class="p">|</span>    <span class="m">73</span>
  Sitemaps         <span class="p">|</span>     <span class="m">1</span>
  Cleaned          <span class="p">|</span>     <span class="m">0</span>

Total in <span class="m">376</span> ms
</code></pre></div><h2 id="参考">参考</h2>
<p><a href="https://gohugo.io/documentation/">Hugo Documentation</a></p>]]></content>
		</item>
		
		<item>
			<title>python实现图片的抖音效果</title>
			<link>https://blog.5km.studio/2018/07/04/python-practice-douyin/</link>
			<pubDate>Wed, 04 Jul 2018 21:01:38 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/07/04/python-practice-douyin/</guid>
			<description>&lt;p&gt;前两篇文章 &lt;a href=&#34;https://blog.5km.studio/2018/07/03/affinity-photo-douyin/&#34;&gt;Affinity Photo绘制抖音音符&lt;/a&gt;  和 &lt;a href=&#34;https://blog.5km.studio/2018/07/03/sketch-practice-douyin/&#34;&gt;Sketch练习稿之绘制抖音音符&lt;/a&gt; 研究了如何用设计软件绘制抖音音符，本篇文章将说一下如何使用python将普通图片处理成抖音效果，甚至制作gif动图。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>前两篇文章 <a href="/2018/07/03/affinity-photo-douyin/">Affinity Photo绘制抖音音符</a>  和 <a href="/2018/07/03/sketch-practice-douyin/">Sketch练习稿之绘制抖音音符</a> 研究了如何用设计软件绘制抖音音符，本篇文章将说一下如何使用python将普通图片处理成抖音效果，甚至制作gif动图。</p>
<h2 id="基本原理">基本原理</h2>
<p><a href="/2018/07/03/sketch-practice-douyin/">Sketch练习稿之绘制抖音音符</a> 中讲述了抖音效果的原理，领域内可能有人称这种效果为红蓝溢出效果。按照前面文章中的描述，这里以一张图片为例简单再讲一下原理。</p>
<p>一张正常的位图，一般 <code>jpg</code> 图片颜色方案为 <code>RGB</code>，而 <code>png</code>图片除了 <code>RGB</code>色彩通道还有 <code>A</code>通道控制透明，不过色彩配置还是一致的，以手上的图片 <code>glasses.jpg</code> 为例。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180704153070171769240.jpg" alt=""></p>
<p>去掉 <code>GB</code> 两通道的图片如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180704153070201122889.jpg" alt=""></p>
<p>去掉 <code>R</code> 通道的图片如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180704153070208255951.jpg" alt=""></p>
<p>如果之间将两张图以相加的方式进行混合，就会得到原图，即第一张正常的图片。如果去掉 <code>R</code> 通道的图片在去掉 <code>GB</code> 通道的图片上方，并且向上和向左错开10px，就会得到抖音效果的图片：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180704153070293737860.jpg" alt=""></p>
<h2 id="实现图片抖音效果">实现图片抖音效果</h2>
<p>使用python3为图片添加抖音效果</p>
<h3 id="库安装">库安装</h3>
<p>需要用到两个库，一个是 <strong>pillow</strong> 用来读写图片数据，另一个是 <strong>numpy</strong> 用来处理图片数据转换成的的矩阵数据，使用 <strong>pip3</strong> 安装即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pip3 install pillow
pip3 install numpy
</code></pre></div><p>安装完成后，执行 <code>pip3 list</code> 会列出安装的包，检查是否完成安装。</p>
<h3 id="库的操作">库的操作</h3>
<h4 id="pillow">pillow</h4>
<p>本文主要用到库中的 <strong>Image</strong> 模块。</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>
</code></pre></div><p>使用 <strong>Image</strong> 模块的 <code>open</code> 方法可以打开图片得到图片原生数据，类型为 <code>PIL.JpegImagePlugin.JpegImageFile</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">image_data</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s1">&#39;picname.jpg&#39;</span><span class="p">)</span>
</code></pre></div><p>上述对象有两个常用的方法：</p>
<ul>
<li>show() : 显示图片，比如<code>image_data.show()</code></li>
<li>save(path): 按照指定路径保存图片，比如<code>image_data.save(path)</code></li>
</ul>
<h4 id="numpy">numpy</h4>
<p>这里主要使用<strong>numpy</strong> 库的矩阵操作，可以很方便的对矩阵进行切片操作，与 <strong>matlab</strong> 中的操作极其类似，首先导入库：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</code></pre></div><p><code>np.array()</code> 方法可以将上面 <code>PIL.JpegImagePlugin.JpegImageFile</code> 对象转换为原始矩阵数据，生成的矩阵为三维矩阵，前两维度为高宽像素点坐标，第三维度为 <code>RGB</code> 三通道，需要注意的是转换得到的矩阵数据类型为 <code>numpy.uint8</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">img_array</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">(</span><span class="n">image_data</span><span class="p">)</span>
</code></pre></div><p>上面的操作可以逆向进行，也就是将 <strong>img_array</strong> 转换为 <code>PIL.JpegImagePlugin.JpegImageFile</code> 对象，需要依赖于 <strong>Image</strong> 的另一个方法 <code>fromarray(array)</code></p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">image</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">fromarray</span><span class="p">(</span><span class="n">img_array</span><span class="p">)</span>
</code></pre></div><p>可以很方便地使用切片操作处理图片矩阵数据中三个通道的数据，比如将 <code>R</code> 通道的数据设置为0:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">img_array</span><span class="p">[:,</span> <span class="p">:,</span> <span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
</code></pre></div><p>另外还需要用到矩阵复制的方法 <code>numpy.copy(array)</code>，会得到 <strong>array</strong> 的复制对象：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">img_other</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">copy</span><span class="p">(</span><span class="n">img_array</span><span class="p">)</span>
</code></pre></div><h3 id="具体实现">具体实现</h3>
<ul>
<li>导入两个库
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
<span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>
</code></pre></div></li>
<li>打开指定图片，这里以 <code>glasses.jpg</code> 为例，转换得到图片矩阵数据：
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">img_data</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s1">&#39;glasses.jpg&#39;</span><span class="p">)</span>
<span class="n">img_array</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">(</span><span class="n">img_data</span><span class="p">)</span>
</code></pre></div></li>
<li>复制得到两个新的矩阵：
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">img_r</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">copy</span><span class="p">(</span><span class="n">img_array</span><span class="p">)</span>
<span class="n">img_bg</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">copy</span><span class="p">(</span><span class="n">img_array</span><span class="p">)</span>
</code></pre></div></li>
<li>这里需要注意的是，为了保证得到数据保存后的图片还是原图片的尺寸，这里只对有错位相加部分的像素点进行去通道操作，这里计划将 <strong>img_gb</strong> 向上向左错位10px，那么只需处理 <strong>img_gb</strong> 宽和高纬度上第 11px 到最后的像素点的 <code>R</code> 通道数据为0，同时 <strong>img_r</strong> 宽和高纬度上从第0到倒数第11个像素点的 <code>GB</code> 通道数据为0:
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="c1"># 去GB两通道数据</span>
<span class="n">img_r</span><span class="p">[:</span><span class="o">-</span><span class="mi">10</span><span class="p">,</span> <span class="p">:</span><span class="o">-</span><span class="mi">10</span><span class="p">,</span> <span class="mi">1</span><span class="p">:</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># 去除R通道数据</span>
<span class="n">img_gb</span><span class="p">[</span><span class="mi">10</span><span class="p">:,</span> <span class="mi">10</span><span class="p">:,</span> <span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
</code></pre></div></li>
<li>将两个矩阵数据错位相加保存到 <strong>img_array</strong> 中，即 <strong>img_r</strong> 的 <code>[:-10,:-10,:]</code> 数据与 <strong>img_gb</strong> 的 <code>[10:, 10:, :]</code>数据相加，赋值给 <strong>img_array</strong> 的 <code>[:-10, :10, :]</code>：
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">img_array</span><span class="p">[:</span><span class="o">-</span><span class="mi">10</span><span class="p">,</span> <span class="p">:</span><span class="o">-</span><span class="mi">10</span><span class="p">,</span> <span class="p">:]</span> <span class="o">=</span> <span class="n">img_r</span><span class="p">[:</span><span class="o">-</span><span class="mi">10</span><span class="p">,</span> <span class="p">:</span><span class="o">-</span><span class="mi">10</span><span class="p">,</span> <span class="p">:]</span> <span class="o">+</span> <span class="n">img_gb</span><span class="p">[</span><span class="mi">10</span><span class="p">:,</span> <span class="mi">10</span><span class="p">:,</span> <span class="p">:]</span>
</code></pre></div></li>
<li>从矩阵数据创建 <code>PIL.JpegImagePlugin.JpegImageFile</code> 对象，并保存和显示图片：
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">image</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">fromarray</span><span class="p">(</span><span class="n">img_array</span><span class="p">)</span>
<span class="n">image</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="s1">&#39;glasses_douyin.jpg&#39;</span><span class="p">)</span>
<span class="n">image</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></li>
</ul>
<p>最终得到图片如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180704153070669239617.jpg" alt="20180704153070669239617.jpg"></p>
<h2 id="创建gif动图">创建gif动图</h2>
<p>上面我们实现了普通图片添加抖音效果，那么想不想看一下错位不同大小的抖音效果的变化呢？可以创建不同错位下的静态抖音效果图，然后用静态图创建gif动图，废话不多说，行动！</p>
<h3 id="库安装-1">库安装</h3>
<p>这里创建gif动图，使用到 <strong>imageio</strong> 库，同样可以使用 <strong>pip3</strong> 安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pip3 install imageio
</code></pre></div><p>使用 <strong>imageio</strong> 模块的 <code>mimsave()</code> 方法生成 gif 动图，比较简单，利用方法封装得到 <code>create_gif</code> 函数：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">create_gif</span><span class="p">(</span><span class="n">image_list</span><span class="p">,</span> <span class="n">gif_name</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;创建gif图片
</span><span class="s1">    :param image_list: 图片名称列表
</span><span class="s1">    :type image_list: list
</span><span class="s1">    :param gif_name: gif图片路径
</span><span class="s1">    :type gif_name: unicode字符串
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">Creating 「</span><span class="si">{</span><span class="n">gif_name</span><span class="si">}</span><span class="s1">」from </span><span class="si">{</span><span class="n">image_list</span><span class="si">}</span><span class="s1"> ...&#39;</span><span class="p">)</span>
    <span class="n">frames</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">image_name</span> <span class="ow">in</span> <span class="n">image_list</span><span class="p">:</span>
        <span class="n">frames</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">imageio</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="n">image_name</span><span class="p">))</span>
    <span class="n">imageio</span><span class="o">.</span><span class="n">mimsave</span><span class="p">(</span><span class="n">gif_name</span><span class="p">,</span> <span class="n">frames</span><span class="p">,</span> <span class="s1">&#39;GIF&#39;</span><span class="p">,</span> <span class="n">duration</span> <span class="o">=</span> <span class="mf">0.05</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Saved </span><span class="si">{</span><span class="n">gif_name</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
</code></pre></div><h3 id="封装抖音效果添加函数">封装抖音效果添加函数</h3>
<p>为了更方便得到不同错位尺寸下的静态抖音效果图，这里封装上面的实现，得到以下函数接口：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">convert_douyin_image</span><span class="p">(</span><span class="n">pic_path</span><span class="p">,</span> <span class="n">offset</span><span class="o">=</span><span class="mi">10</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;为普通图片添加红蓝溢出位移效果，抖音app效果
</span><span class="s1">    :param pic_path: 图片路径
</span><span class="s1">    :type pic_path: unicode字符串
</span><span class="s1">    :param offset: 红蓝位移大小，单位像素，默认值是10
</span><span class="s1">    :return r_pic_path: 返回转换图片的路径
</span><span class="s1">    :rtype: unicode字符串
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">pic_path</span><span class="p">):</span>
        <span class="n">pic_name</span><span class="p">,</span> <span class="n">extension</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">pic_path</span><span class="p">)</span>
        <span class="n">pic_path_new</span> <span class="o">=</span> <span class="n">pic_name</span> <span class="o">+</span> <span class="s1">&#39;_&#39;</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">offset</span><span class="p">)</span> <span class="o">+</span> <span class="n">extension</span>
        <span class="n">img_data</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">pic_path</span><span class="p">)</span>
        <span class="n">img_array</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">(</span><span class="n">img_data</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">offset</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
            <span class="n">img_r</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">copy</span><span class="p">(</span><span class="n">img_array</span><span class="p">)</span>
            <span class="n">img_gb</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">copy</span><span class="p">(</span><span class="n">img_array</span><span class="p">)</span>
            <span class="n">img_r</span><span class="p">[:</span><span class="o">-</span><span class="n">offset</span><span class="p">,</span> <span class="p">:</span><span class="o">-</span><span class="n">offset</span><span class="p">,</span> <span class="mi">1</span><span class="p">:</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
            <span class="n">img_gb</span><span class="p">[</span><span class="n">offset</span><span class="p">:,</span> <span class="n">offset</span><span class="p">:,</span> <span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
            <span class="n">img_array</span><span class="p">[:</span><span class="o">-</span><span class="n">offset</span><span class="p">,</span> <span class="p">:</span><span class="o">-</span><span class="n">offset</span><span class="p">,</span> <span class="p">:]</span> <span class="o">=</span> <span class="n">img_r</span><span class="p">[:</span><span class="o">-</span><span class="n">offset</span><span class="p">,</span> <span class="p">:</span><span class="o">-</span><span class="n">offset</span><span class="p">,</span> <span class="p">:]</span> <span class="o">+</span> <span class="n">img_gb</span><span class="p">[</span><span class="n">offset</span><span class="p">:,</span> <span class="n">offset</span><span class="p">:,</span> <span class="p">:]</span>
        <span class="n">image</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">fromarray</span><span class="p">(</span><span class="n">img_array</span><span class="p">)</span>
        <span class="n">image</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">pic_path_new</span><span class="p">)</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Saved </span><span class="si">{</span><span class="n">pic_path_new</span><span class="si">}</span><span class="s1">!&#39;</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">pic_path_new</span>
</code></pre></div><p>接口中，对 offset 为 0 时做了处理，直接保存原图，最终函数返回保存的图片路径，方便生成图片路径列表用于生成gif。</p>
<h3 id="整体实现">整体实现</h3>
<p>导入必要库：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">imageio</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
<span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>
</code></pre></div><p>这里定义一个图片路径，方便指定待处理图片：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">PIC_PATH</span> <span class="o">=</span> <span class="s1">&#39;glasses.jpg&#39;</span>
</code></pre></div><p>最后，先得到错位[0, 2, 4, 6, 8, 10]px 的静态抖音效果图和图片路径列表，为了是动图连贯，处理列表中元素按照错位尺寸为 [0, 2, 4, 6, 8, 10, 8, 6, 4, 2, 0]px，然后根据 <strong>PIC_PATH</strong> 得到 gif图片保存路径，调用 <strong>create_gif</strong> 函数生成gif：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">pic_list</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span>
        <span class="n">pic_path</span> <span class="o">=</span> <span class="n">convert_douyin_image</span><span class="p">(</span><span class="n">PIC_PATH</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">pic_path</span><span class="p">:</span>
            <span class="n">pic_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">pic_path</span><span class="p">)</span>
    <span class="n">temp_l</span> <span class="o">=</span> <span class="nb">list</span><span class="o">.</span><span class="n">copy</span><span class="p">(</span><span class="n">pic_list</span><span class="p">)</span>
    <span class="n">temp_l</span><span class="o">.</span><span class="n">reverse</span><span class="p">()</span>
    <span class="n">pic_list</span> <span class="o">=</span> <span class="n">pic_list</span> <span class="o">+</span> <span class="n">temp_l</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
    <span class="n">p_name</span><span class="p">,</span> <span class="n">p_ext</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">PIC_PATH</span><span class="p">)</span>
    <span class="n">gif_name</span> <span class="o">=</span> <span class="n">PIC_PATH</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">p_ext</span><span class="p">,</span> <span class="s1">&#39;.gif&#39;</span><span class="p">)</span>
    <span class="n">create_gif</span><span class="p">(</span><span class="n">pic_list</span><span class="p">,</span> <span class="n">gif_name</span><span class="p">)</span>
</code></pre></div><p>最终得到的gif图片效果如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180704153070846317421.gif" alt="20180704153070846317421.gif"></p>
<p>完整源码参考:</p>
<p><a href="https://github.com/smslit/tools-with-script/blob/master/douyin/douyin.py">https://github.com/smslit/tools-with-script/blob/master/douyin/douyin.py </a></p>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://blog.csdn.net/guduruyu/article/details/77540445">gif动态图的解析与合成</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/37923938">用Python给图片加上抖音效果</a></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>Affinity Photo 绘制抖音音符</title>
			<link>https://blog.5km.studio/2018/07/03/affinity-photo-douyin/</link>
			<pubDate>Tue, 03 Jul 2018 17:59:56 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/07/03/affinity-photo-douyin/</guid>
			<description>&lt;p&gt;上一篇文章 &lt;a href=&#34;https://blog.5km.studio/2018/07/03/sketch-practice-douyin/&#34;&gt;sketch练习稿之抖音音符&lt;/a&gt; 中介绍了绘制抖音效果的原理，根据原理其实也可以用其它工具绘制抖音音符，比如PS、Affinity Photo。本文展示如何使用&lt;a href=&#34;https://affinity.serif.com/zh-cn/photo/&#34;&gt;Affinity Photo&lt;/a&gt;绘制上一篇博文中大的抖音音符。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>上一篇文章 <a href="/2018/07/03/sketch-practice-douyin/">sketch练习稿之抖音音符</a> 中介绍了绘制抖音效果的原理，根据原理其实也可以用其它工具绘制抖音音符，比如PS、Affinity Photo。本文展示如何使用<a href="https://affinity.serif.com/zh-cn/photo/">Affinity Photo</a>绘制上一篇博文中大的抖音音符。</p>
<p><a href="https://affinity.serif.com/zh-cn/photo/">Affinity Photo</a>是一款处理图片的软件，已经开始在撼动PS的地位，本人对PS只是菜鸟级的认识，不过可以很明显看出，Affinity Photo比PS更现代一些，更轻量一些，该有的功能也都有，甚至算法和操作更优，比如扣毛发、光照特效等，还有一个很明显的优势就是软件授权，购买一次可以授权5台设备终生支持免费升级，而PS生态环境的问题，国内无法订阅正版，盗版PS还是居多，这看用户自己的认识了，每个人都有自己的选择！另外作为一个颜控，十里也是觉得Affinity Photo更佳。</p>
<p>扯远了，回来一起绘制抖音音符。</p>
<h2 id="原理回想">原理回想</h2>
<p>再简单说一下原理，利用了对原图色彩通道的处理和混合实现抖音晕眩的效果，一张图只用<code>R</code>通道，一张图只用<code>G</code>和<code>B</code>通道，然后将两图以<code>相加</code>的方式混合，两图重合构成原图，一旦有错位就会出现抖音的效果。</p>
<h2 id="绘制">绘制</h2>
<p>为了更直接展示绘制过程，这里直接录制了绘制过程：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/affinity/douyin/douyin.mp4" controls="controls" width="100%">Affinity Photo 绘制抖音音符</video></p>]]></content>
		</item>
		
		<item>
			<title>sketch练习稿之抖音音符</title>
			<link>https://blog.5km.studio/2018/07/03/sketch-practice-douyin/</link>
			<pubDate>Tue, 03 Jul 2018 15:36:43 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/07/03/sketch-practice-douyin/</guid>
			<description>&lt;p&gt;前些天看了文章&lt;a href=&#34;https://zhuanlan.zhihu.com/p/37923938&#34;&gt;用Python给图片加上抖音效果&lt;/a&gt;明白了抖音效果的原理，所以有了本文的练习，用sketch绘制抖音音符：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180703153058590031266.png&#34; alt=&#34;20180703153058590031266.png&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>前些天看了文章<a href="https://zhuanlan.zhihu.com/p/37923938">用Python给图片加上抖音效果</a>明白了抖音效果的原理，所以有了本文的练习，用sketch绘制抖音音符：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180703153058590031266.png" alt="20180703153058590031266.png"></p>
<p><a href="https://www.sketchapp.com">sketch</a>这个强大的设计工具就不在这里介绍了，本文重点是讲一下抖音效果的原理和如何绘制抖音效果的🎵。</p>
<h2 id="抖音效果的原理">抖音效果的原理</h2>
<p>抖音效果利用了图片的颜色混合通道的处理以及显示的原理<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>，目前图片多选用RGB，像png图片还包含了alpha通道主要控制透明，这里只关心RGB三个通道：<strong>red</strong> 、<strong>green</strong> 和 <strong>blue</strong>。观看上面抖音音符的图片可以看到是两个音符叠放相加得到的。</p>
<p>其实上面的两个音符本来都是白色，只是青色的音符关闭了<code>R</code>通道，及<code>R</code>通道设置为了0:</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/sketch/douyin/qing.mp4" controls="controls" width="100%">青色</video></p>
<p>红色的音符同样的道理是关闭了<code>G</code>和<code>B</code>通道：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/sketch/douyin/red.mp4" controls="controls" width="100%">红色</video></p>
<p>然后接着需要修改青色圆图层到红色圆图层的上方，然后修改青圆的填充的混合方式为：<code>screen</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>，屏幕的混合方式的效果与前面提到的文章中所说的相加一致，然后将青色圆形拖动与红色圆形重叠，会发现重叠部分是白色的，与抖音音符效果一致：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/sketch/douyin/screen.mp4" controls="controls" width="100%">重叠</video></p>
<p>这样我们就知道了抖音效果的原理，这个原理针对于复杂的图片一样适用。</p>
<h2 id="分析音符">分析音符</h2>
<p>抖音音符的基本元素是两个一样的音符，sketch具有几何布尔运算的功能，所以下面分解一下音符的结构，就可以理清音符绘制步骤。</p>
<p>可以简单的将音符看成三个部分：</p>
<ul>
<li>上面音符尾巴的 1/4 圆环</li>
<li>中间的矩形</li>
<li>下面的 3/4 圆环</li>
</ul>
<p>就像这样：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180703153058885065459.png" alt="20180703153058885065459.png"></p>
<p>下面只需分析三个部分怎么画即可。</p>
<h3 id="14-圆环">1/4 圆环</h3>
<p>这个可以不用直接画圆环，可以绘制矩形然后取一个点做圆角处理就能得到一个扇形，然后复制扇形缩小尺寸为原来的 1/2 ，然后对两个扇形做 <strong>几何相减</strong> 处理就可以了。</p>
<h3 id="34-圆环">3/4 圆环</h3>
<ul>
<li>绘制整圆环：绘制两个圆，这里可以设计小圆的直径为大圆的 1/3 ，然后对两个圆做 <strong>几何布尔相减</strong> 操作即可；</li>
<li>裁切圆环：绘制边长为圆环半径大小的矩形，放置到圆环右上角，然后进行 <strong>几何布尔相减</strong> 操作；</li>
</ul>
<h2 id="矩形">矩形</h2>
<p>绘制矩形，尺寸注意一下就可以，宽度应该要与 3/4 圆环的宽度一致，长度的话，为了更自然一点，这里可以选择与 3/4 圆环外直径一致。</p>
<h2 id="sketch绘制">sketch绘制</h2>
<p>sketch版本是50.2</p>
<h3 id="绘制画板">绘制画板</h3>
<p>打开sketch，按快捷键 <code>a</code>，可以调出画板选择界面，这里选择<code>Responsive Web</code>分组的<code>Desktop</code>画板，1024*1024，配置画板背景色为黑色：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180703153058970475514.png" alt="20180703153058970475514.png"></p>
<h3 id="绘制-34-圆环">绘制 3/4 圆环</h3>
<ul>
<li>按 <code>o</code> 键，然后按住 <code>Shift</code> 拖动添加圆形，直径为<strong>360px</strong>；</li>
<li>复制粘贴得到另一个圆形，调整直径为 <strong>120px</strong> ；</li>
<li>调整小圆位置使小圆和大圆同心；</li>
<li>选中两个圆，点击工具栏的 <code>subtract</code> 布尔相减处理，得到一个外经 <strong>360px</strong> 内径 <strong>120</strong> 的圆环；</li>
<li>按 <code>r</code> 键，然后按住 <code>Shift</code> 拖动创建边长 <strong>180px</strong> 的正方形；</li>
<li>拖动正方形到圆环的右上角使正方形正好占 1/4 ；</li>
<li>选中圆环和正方形，点击工具栏 <code>subtract</code> 布尔相减处理，最终得到 3/4 圆环；</li>
</ul>
<p>整个操作过程参考下方视频：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/sketch/douyin/bigRing.mp4" controls="controls" width="100%">3/4圆环</video></p>
<h3 id="绘制-14-圆环">绘制 1/4 圆环</h3>
<ul>
<li>按下<code>r</code>键，按住 <code>Shift</code>键拖动添加边长 <strong>240px</strong> 的正方形；</li>
<li>双击正方形进入编辑状态，单击选中左下角的点，添加圆角 <strong>240px</strong>，这是得到一个1/4扇形；</li>
<li>复制粘贴出一个新的扇形，调整尺寸为 <strong>120px</strong> x <strong>120px</strong>;</li>
<li>拖动小扇形到大扇形的右上角对齐边缘；</li>
<li>选中两个扇形，点击工具栏的 <code>subtract</code>工具进行几何布尔相减，得到 1/4 圆环；</li>
</ul>
<p>具体操作参考：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/sketch/douyin/smalRing.mp4" controls="controls" width="100%"> 1/4 圆环</video></p>
<h3 id="绘制矩形">绘制矩形</h3>
<p>按下 <code>r</code> 键，拖动绘制宽 <strong>120px</strong>, 高 <strong>360px</strong> 的矩形，这个就不需要演示视频了。</p>
<h3 id="组合音符">组合音符</h3>
<ul>
<li>选中拖动矩形与 3/4 圆环朝上的开口对齐；</li>
<li>选中 1/4 圆环上边线与矩形的上边线对齐；</li>
<li>选中三个图形，点击工具栏的 <code>Union</code> 布尔组合按钮，进行组合得到音符；</li>
<li>选中音符，属性栏中勾掉<code>board</code>，调整<code>fill</code>颜色为白色；</li>
<li>将音符调整到画布正中间；</li>
</ul>
<p>具体过程参考：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/sketch/douyin/yinfu.mp4" controls="controls" width="100%">音符</video></p>
<h3 id="抖音吧">抖音吧</h3>
<p>来吧，一起抖音！</p>
<ul>
<li>复制音符，粘贴得到新的音符；</li>
<li>调整下层音符的填充颜色为 <code>#FF0000</code>，即纯红色；</li>
<li>调整上层音符的填充颜色为 <code>#00FFFF</code>，即青色；</li>
<li>选中青色音符，按照最开始的原理，选择混合方式为: <code>screen</code>；</li>
<li>调整青色音符的位置向左向右各 <strong>10px</strong>，最终就看到了抖音音符了；</li>
</ul>
<p>就像这样：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/sketch/douyin/douyin.mp4" controls="controls" width="100%">抖音音符</video></p>
<section class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1" role="doc-endnote">
<p><a href="https://zhuanlan.zhihu.com/p/37923938">用Python给图片加上抖音效果</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p><a href="https://cdn2.jianshu.io/p/ec2040bdfbfe">Sketch图层混合模式（Blending）详解</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</section>]]></content>
		</item>
		
		<item>
			<title>python用法之字符串拼接和列表生成</title>
			<link>https://blog.5km.studio/2018/07/01/python-basic-joinstr-gerneratelist/</link>
			<pubDate>Sun, 01 Jul 2018 00:25:27 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/07/01/python-basic-joinstr-gerneratelist/</guid>
			<description>&lt;p&gt;今天聊一下python用法的技巧和思考：字符串的拼接方法和列表的生成技巧。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天聊一下python用法的技巧和思考：字符串的拼接方法和列表的生成技巧。</p>
<h2 id="列表生成">列表生成</h2>
<p>如果让你生成从0～1000的平方组成的列表，你会有几种实现方法，现在我知道的可以归结为两种：</p>
<ul>
<li>列表生成式；</li>
<li>循环追加；</li>
</ul>
<h3 id="循环追加">循环追加</h3>
<p>先说一下<strong>循环追加</strong>，这种其实就是比较直白的从c语言中过渡来的实现方式，代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">L</span> <span class="o">=</span> <span class="p">[]</span>

<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">):</span>
    <span class="n">L</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">i</span><span class="o">**</span><span class="mi">2</span><span class="p">)</span>
</code></pre></div><h3 id="列表生成式">列表生成式</h3>
<p>而列表生成式只需要一行代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">L</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="o">**</span> <span class="mi">2</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">)]</span>
</code></pre></div><h3 id="对比">对比</h3>
<p>下面咱编写一组测试脚本对比一下这两种方式耗时，分别测试用两种方式生成同一列表，这个列表以0开始以指定数字结束，测试所用结束点为以下数字：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">[200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000]
</code></pre></div><p>测算耗时使用<code>timeit</code>模块，最终得到两种方法下所用耗时，绘制相应对比曲线，编写脚本文件，内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/local/bin/python3</span>

<span class="kn">import</span> <span class="nn">timeit</span>
<span class="kn">from</span> <span class="nn">matplotlib</span> <span class="kn">import</span> <span class="n">pyplot</span> <span class="k">as</span> <span class="n">plt</span>

<span class="k">def</span> <span class="nf">loop_generate_list</span><span class="p">(</span><span class="n">end</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;循环生成
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">L</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">end</span><span class="p">):</span>
        <span class="n">L</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">x</span><span class="o">**</span><span class="mi">2</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">generate_list</span><span class="p">(</span><span class="n">end</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;列表生成式
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">L</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span><span class="o">**</span><span class="mi">2</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">end</span><span class="p">)]</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">end_num</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span><span class="o">*</span><span class="mi">200</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">11</span><span class="p">)]</span>
    <span class="n">t1</span> <span class="o">=</span> <span class="p">[</span><span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s1">&#39;loop_generate_list(</span><span class="si">%d</span><span class="s1">)&#39;</span> <span class="o">%</span> <span class="n">x</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="s1">&#39;from __main__ import loop_generate_list&#39;</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">500</span><span class="p">)</span> <span class="o">/</span> <span class="mi">500</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">end_num</span><span class="p">]</span>
    <span class="n">t2</span> <span class="o">=</span> <span class="p">[</span><span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s1">&#39;generate_list(</span><span class="si">%d</span><span class="s1">)&#39;</span> <span class="o">%</span> <span class="n">x</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="s1">&#39;from __main__ import generate_list&#39;</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">500</span><span class="p">)</span> <span class="o">/</span> <span class="mi">500</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">end_num</span><span class="p">]</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">close</span><span class="p">(</span><span class="s2">&#34;all&#34;</span><span class="p">)</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">4</span><span class="p">))</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">end_num</span><span class="p">,</span> <span class="n">t1</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s2">&#34;For-loop&#34;</span><span class="p">)</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">end_num</span><span class="p">,</span> <span class="n">t2</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s2">&#34;list-generate&#34;</span><span class="p">)</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s1">&#39;the end number of list&#39;</span><span class="p">)</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s1">&#39;Escaped Time(s)&#39;</span><span class="p">)</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">legend</span><span class="p">()</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div><p>执行脚本以后会得到以下曲线：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180630153037143746732.png" alt="20180630153037143746732.png"></p>
<p>耗时数据如下：</p>
<p>列表   | 循环方式耗时(s)        | 列表生成式耗时(s)
&mdash;&mdash;-|&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;|
0~199  | 7.943870799499565e-05  | 5.8151310004177506e-05
0~399  | 0.00015830999000172598 | 0.00011730378400534392
0~599  | 0.00023279044800437988 | 0.00018087501599802635
0~799  | 0.0003016821240016725  | 0.00025029307800286915
0~999  | 0.00037272097000095526 | 0.00030559123599960004
0~1199 | 0.000465159749990562   | 0.00036801772599574177
0~1399 | 0.0005052312600018923  | 0.0004436784459976479
0~1599 | 0.0005855820479919203  | 0.0004932414859940763
0~1799 | 0.0006699951940099709  | 0.0005555873200064525
0~1999 | 0.0007183871679881122  | 0.0006266429160023108</p>
<p>综上结果，列表生成式非但写法简单，而且执行速度也是比手动循环添加快，可以说是一种语法好糖了，建议都使用<strong>列表生成式</strong>生成自己想要的列表。</p>
<h2 id="拼接字符串">拼接字符串</h2>
<p>字符串的拼接在<code>python</code>中很灵活，既可以用<code>+</code>运算符，又可以用格式化字符串，还能用<code>join</code>函数，那我们用哪一个比较好呢，这里咱先关心一下执行速度，所以就像第一节中一样比较一下哪种速度更快！</p>
<h3 id="号拼接"><code>+</code>号拼接</h3>
<p>比如：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">hello</span> <span class="o">=</span> <span class="s1">&#39;hello, &#39;</span> <span class="o">+</span> <span class="s1">&#39;Monkey!&#39;</span> <span class="o">+</span> <span class="s1">&#39;Let</span><span class="se">\&#39;</span><span class="s1">t&#39;</span> <span class="o">+</span> <span class="s1">&#39;Do&#39;</span> <span class="o">+</span> <span class="s1">&#39;it&#39;</span>
</code></pre></div><h3 id="号拼接-1"><code>%</code>号拼接</h3>
<p>类似于c库中的<code>sprintf</code></p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">hello</span> <span class="o">=</span> <span class="s1">&#39;</span><span class="si">%s%s%s%s%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="p">(</span><span class="s1">&#39;hello, &#39;</span><span class="p">,</span> <span class="s1">&#39;Monkey! &#39;</span><span class="p">,</span> <span class="s1">&#39;Let</span><span class="se">\&#39;</span><span class="s1">t &#39;</span><span class="p">,</span> <span class="s1">&#39;Do &#39;</span><span class="p">,</span> <span class="s1">&#39;it!&#39;</span><span class="p">)</span>
</code></pre></div><h3 id="format方法拼接">format方法拼接</h3>
<p>是python2.6中出现的用来替代<code>%</code>拼接字符串的方式。</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">hello</span> <span class="o">=</span> <span class="s1">&#39;</span><span class="si">{}{}{}{}{}</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="s1">&#39;hello, &#39;</span><span class="p">,</span> <span class="s1">&#39;Monkey! &#39;</span><span class="p">,</span> <span class="s1">&#39;Let</span><span class="se">\&#39;</span><span class="s1">t &#39;</span><span class="p">,</span> <span class="s1">&#39;Do &#39;</span><span class="p">,</span> <span class="s1">&#39;it!&#39;</span><span class="p">)</span>
</code></pre></div><h3 id="f-string方式拼接"><code>f-string</code>方式拼接</h3>
<p><code>f-string</code>全称Formatted String Literals，也就是字面量格式化字符串，这是python3.6引入的，用法类似于前面<code>%</code>号拼接和<code>format</code>方法</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">str_l</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;hello, &#39;</span><span class="p">,</span> <span class="s1">&#39;Monkey! &#39;</span><span class="p">,</span> <span class="s1">&#39;Let</span><span class="se">\&#39;</span><span class="s1">t &#39;</span><span class="p">,</span> <span class="s1">&#39;Do &#39;</span><span class="p">,</span> <span class="s1">&#39;it!&#39;</span><span class="p">]</span>
<span class="n">hello</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">str_l</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}{</span><span class="n">str_l</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="si">}{</span><span class="n">str_l</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="si">}{</span><span class="n">str_l</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="si">}{</span><span class="n">str_l</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="si">}</span><span class="s1">&#39;</span>
</code></pre></div><p>可以说这种方式非常强大，会执行<code>{}</code>中的内容，将结果作为替代项，放置到相应位置。</p>
<h3 id="join函数拼接"><code>join</code>函数拼接</h3>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">hello</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s1">&#39;hello, &#39;</span><span class="p">,</span> <span class="s1">&#39;Monkey! &#39;</span><span class="p">,</span> <span class="s1">&#39;Let</span><span class="se">\&#39;</span><span class="s1">t &#39;</span><span class="p">,</span> <span class="s1">&#39;Do &#39;</span><span class="p">,</span> <span class="s1">&#39;it!&#39;</span><span class="p">])</span>
</code></pre></div><h3 id="对比-1">对比</h3>
<p>同样使用<code>timeit</code>模块评估运算时间, 这次看一下每种方法执行100000次用的时间：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/local/bin/python3</span>
<span class="kn">import</span> <span class="nn">timeit</span>

<span class="n">t1</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s2">&#34;hello = &#39;hello, &#39; + &#39;Monkey!&#39; + &#39;Just&#39; + &#39;Do&#39; + &#39;it&#39;&#34;</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">100000</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;+运算符拼接：&#39;</span><span class="p">,</span> <span class="n">t1</span><span class="p">)</span>
<span class="n">t2</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s2">&#34;hello = &#39;</span><span class="si">%s%s%s%s%s</span><span class="s2">&#39; % (&#39;hello, &#39;, &#39;Monkey! &#39;, &#39;Just &#39;, &#39;Do &#39;, &#39;it!&#39;)&#34;</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">100000</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;%操作符拼接：&#39;</span><span class="p">,</span> <span class="n">t2</span><span class="p">)</span>
<span class="n">t3</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s2">&#34;hello = &#39;</span><span class="si">{}{}{}{}{}</span><span class="s2">&#39;.format(&#39;hello, &#39;, &#39;Monkey! &#39;, &#39;Just &#39;, &#39;Do &#39;, &#39;it!&#39;)&#34;</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">100000</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Format方法拼接：&#39;</span><span class="p">,</span> <span class="n">t3</span><span class="p">)</span>
<span class="n">t4</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s2">&#34;str_l = [&#39;hello, &#39;, &#39;Monkey! &#39;, &#39;Just&#39; , &#39;Do &#39;, &#39;it!&#39;];hello = f&#39;</span><span class="si">{str_l[0]}{str_l[1]}{str_l[2]}{str_l[3]}{str_l[4]}</span><span class="s2">&#39;&#34;</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">100000</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;f-string方法拼接：&#39;</span><span class="p">,</span> <span class="n">t4</span><span class="p">)</span>
<span class="n">t5</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s2">&#34;hello = &#39;&#39;.join([&#39;hello, &#39;, &#39;Monkey! &#39;, &#39;Just &#39;, &#39;Do &#39;, &#39;it!&#39;])&#34;</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">100000</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;join方法：&#39;</span><span class="p">,</span> <span class="n">t5</span><span class="p">)</span>
</code></pre></div><p>结果是：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">+运算符拼接： 0.0016716789978090674
%操作符拼接： 0.02950063199386932
Format方法拼接： 0.054942579998169094
f-string方法拼接： 0.03282728197518736
join方法： 0.022729617019649595
</code></pre></div><p>可以看出小规模拼接短字符串时，使用<code>+</code>拼接更快速直接。</p>
<p>但是当需要大规模拼接很多长字符串时，会是什么情况呢？此时前面说的中间三个类似于格式化字符串的方式不太适合连续合并大量字符串，要实现要么代码长而粗暴，要么实现代码短但不高效。</p>
<p>那么就比较一下其余两种方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/local/bin/python3</span>
<span class="kn">import</span> <span class="nn">timeit</span>

<span class="k">def</span> <span class="nf">test_add_str</span><span class="p">():</span>
    <span class="n">hello</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>
    <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10000</span><span class="p">):</span>
        <span class="n">hello</span> <span class="o">+=</span> <span class="s1">&#39;hello, every body! Welcom here for our pretty moment! Thanks for that!</span><span class="se">\n</span><span class="s1">&#39;</span>

<span class="k">def</span> <span class="nf">test_join_str</span><span class="p">():</span>
    <span class="n">str_test</span> <span class="o">=</span> <span class="s1">&#39;hello, every body! Welcom here for our pretty moment! Thanks for that!</span><span class="se">\n</span><span class="s1">&#39;</span>
    <span class="n">str_list</span> <span class="o">=</span> <span class="p">[</span><span class="n">str_test</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10000</span><span class="p">)]</span>
    <span class="n">hello</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">str_list</span><span class="p">)</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">t1</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s1">&#39;test_add_str()&#39;</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="s1">&#39;from __main__ import test_add_str&#39;</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span> <span class="o">/</span> <span class="mi">100</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;+运算符拼接：&#39;</span><span class="p">,</span> <span class="n">t1</span><span class="p">)</span>
    <span class="n">t2</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s1">&#39;test_join_str()&#39;</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="s1">&#39;from __main__ import test_join_str&#39;</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span> <span class="o">/</span> <span class="mi">100</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;join方法：&#39;</span><span class="p">,</span> <span class="n">t2</span><span class="p">)</span>
</code></pre></div><p>对比结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">+运算符拼接： 0.0019161234499915736
join方法： 0.00045332342000619974
</code></pre></div><p>可以看到<code>join</code>方法的优势更明显。为什么会出现这样的结果呢？其实是两个方法的机制不一样，<code>+</code>运算符拼接的方法是每次拼接都会生成新的副本，拼接一次就有一次复制操作；而<code>join</code>方法是根据列表先预估空间，后面只需复制每次要拼接的字符串就好了，所以像这种规模大且数量多的字符串拼接还是选<code>join</code>方法比较合适。</p>
<h3 id="总结">总结</h3>
<p>当拼接字符串短且少的时候使用<code>+</code>号拼接会是更快的实现，另外<code>f-string</code>的方式可能会比较灵活，而当拼接字符串变长或者数量增多的话，还是使用<code>join</code>效率更高。</p>
<h2 id="参考">参考：</h2>
<ul>
<li><a href="https://docs.python.org/3/library/timeit.html#module-timeit">timeit</a></li>
<li><a href="https://docs.python.org/3/library/stdtypes.html#list">list</a></li>
<li><a href="https://docs.python.org/3/library/stdtypes.html#str">str</a></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>macOS应用开发基础之Popover</title>
			<link>https://blog.5km.studio/2018/06/29/macOS-dev-basic-NSPopover/</link>
			<pubDate>Fri, 29 Jun 2018 15:20:04 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/06/29/macOS-dev-basic-NSPopover/</guid>
			<description>&lt;p&gt;mac中安装了一个叫&lt;a href=&#34;http://paper.meiyuan.in&#34;&gt;pap.er&lt;/a&gt;的app，用来下载和管理桌面壁纸，壁纸资源质量很高幺，app不但很轻量(仅有5MB)而且设计精美，还免费的(我这算在帮他们做推广吗^_^，好的东西就应该推广一下)。这款app采用了状态栏小工具的形式，界面在 &lt;code&gt;Popover&lt;/code&gt; 中实现。看视频感受一下，叫&lt;code&gt;Popover&lt;/code&gt;的弹窗，这是本文要讲的东西。&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/pap.er.mp4&#34; controls=&#34;controls&#34; width=&#34;100%&#34; poster=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153023532585285.jpg&#34;&gt;pap.er&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;昨天研究了&lt;code&gt;Popover&lt;/code&gt;的使用，所以今天以一个实例与大家分享一下！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>mac中安装了一个叫<a href="http://paper.meiyuan.in">pap.er</a>的app，用来下载和管理桌面壁纸，壁纸资源质量很高幺，app不但很轻量(仅有5MB)而且设计精美，还免费的(我这算在帮他们做推广吗^_^，好的东西就应该推广一下)。这款app采用了状态栏小工具的形式，界面在 <code>Popover</code> 中实现。看视频感受一下，叫<code>Popover</code>的弹窗，这是本文要讲的东西。</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/pap.er.mp4" controls="controls" width="100%" poster="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153023532585285.jpg">pap.er</video></p>
<p>昨天研究了<code>Popover</code>的使用，所以今天以一个实例与大家分享一下！</p>
<h2 id="平台">平台</h2>
<ul>
<li>macOS 10.13.5</li>
<li>Xcode 9.4.1</li>
<li>swift 4.1.2</li>
</ul>
<p>本文基于上述平台实现，下面的代码中可能随着<code>Swift</code>语言的版本的不同会需要调整（不过应该不多），<code>xcode</code>会比较智能的提出修改建议，视情况调整即可，不过实现思路是一致的。</p>
<h2 id="新建及配置工程">新建及配置工程</h2>
<ul>
<li>
<p>打开xcode新建工程， <strong>macOS</strong> -&gt; <strong>Cocoa App</strong> -&gt; <strong>Next</strong>:
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024028025921.png" alt="20180629153024028025921.png"></p>
</li>
<li>
<p>输入工程名称：<code>PopoverDemo</code>，语言选择<code>Swift</code>，勾掉<code>Storyboard</code>:
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024043769527.png" alt="20180629153024043769527.png"></p>
</li>
<li>
<p><strong>Next</strong>，点击 <strong>create</strong> 即可打开创建的新工程；</p>
</li>
<li>
<p>点击运行按钮，可以看到程序运行，出现一个空的窗口，同时dock上出现了应用图标，这不是我们想要的，设置一下不显示它们：</p>
<ul>
<li>工程导航栏选中工程<code>PopoverDemo</code>，打开<code>Info</code>标签页;</li>
<li>可以看到<code>Custom macOS application Target Properties</code>组，添加新的配置<code>Application is agent(UI Element)</code>，布尔属性，值为 <strong>YES</strong>:
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024095465288.png" alt="20180629153024095465288.png"></li>
</ul>
</li>
<li>
<p>重新运行程序，可以看到已经不显示Dock图标；</p>
</li>
<li>
<p>打开文件<code>MainMenu.xib</code>，可以看到界面设计中有<strong>Window</strong> 和 <strong>MainMenu</strong>，两个选中删掉；</p>
</li>
<li>
<p>打开文件<code>AppDelegate.swift</code>，删掉以下代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">window</span><span class="p">:</span> <span class="n">NSWindow</span><span class="p">!</span>
</code></pre></div></li>
<li>
<p>再次运行程序，主窗口也不显示了，连菜单栏也木有了，不要着急，咱继续。</p>
</li>
</ul>
<h2 id="添加状态栏按钮">添加状态栏按钮</h2>
<p>打开文件<code>AppDelegate.swift</code>，在类中添加属性，这一步是创建一个状态栏按钮，设置宽度属性<code>NSStatusItem.squareLength</code>，代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kd">let</span> <span class="nv">statusItem</span> <span class="p">=</span> <span class="n">NSStatusBar</span><span class="p">.</span><span class="n">system</span><span class="p">.</span><span class="n">statusItem</span><span class="p">(</span><span class="n">withLength</span><span class="p">:</span> <span class="n">NSStatusItem</span><span class="p">.</span><span class="n">squareLength</span><span class="p">)</span>
</code></pre></div><p>状态栏按钮总该需要一个图标吧！打开<code>Assets.xcassets</code>，右击显示<code>AppIcon</code>下方的空白区，选择<code>New Image Set</code>，重命名为<code>statusIcon</code>，当然这个名字随便定，选中这个图集，会看到右侧有配置区，配置图集按照<code>Template Image</code>渲染：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024293742991.png" alt="20180629153024293742991.png"></p>
<p>看到有三个虚线框空白区，这就是图片区，状态栏按钮的图片基本大小为 $18px\times18px$ ，还需2倍和3倍的适用于视网膜屏幕的mac，像素分别是 $36px\times36px$ 和 $54px\times54px$ ，可以使用以下我提供的图标：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024309821129.png" width="18px"><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2018062915302434352614.png" width="36px"><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024344990828.png" width="54px"></p>
<p>分别将图拖到对应位置：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024408859417.png" alt="20180629153024408859417.png"></p>
<p>切换到文件<code>AppDelegate.swift</code>，定义一个测试状态栏按钮点击行为的函数，这里以关闭应用程序为例吧，实现的函数：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">quitApp</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">AnyObject</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">NSApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">terminate</span><span class="p">(</span><span class="kc">self</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>然后找到<code>applicationDidFinishLaunching </code>在其中添加以下代码，为状态栏按钮配置图标和行为：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="k">if</span> <span class="kd">let</span> <span class="nv">button</span> <span class="p">=</span> <span class="n">statusItem</span><span class="p">.</span><span class="n">button</span> <span class="p">{</span>
	<span class="n">button</span><span class="p">.</span><span class="n">image</span> <span class="p">=</span> <span class="n">NSImage</span><span class="p">(</span><span class="n">named</span><span class="p">:</span> <span class="n">NSImage</span><span class="p">.</span><span class="n">Name</span><span class="p">(</span><span class="s">&#34;statusIcon&#34;</span><span class="p">))</span>
	<span class="n">button</span><span class="p">.</span><span class="n">action</span> <span class="p">=</span> <span class="k">#selector</span><span class="p">(</span><span class="n">quitApp</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div><p>此时运行程序会看到状态栏中出现了我们定义的按钮，点击一下，应用程序就退出了。</p>
<blockquote>
<p>前面设置图片集渲染方式为<code>Template Image</code>，是为了适配不同的状态栏主题，因为macOS还有个暗黑主题不是？</p>
</blockquote>
<p>两种主题下的效果如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2018062915302450436095.png" alt="2018062915302450436095.png"></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024505287040.png" alt="20180629153024505287040.png"></p>
<h2 id="添加popover">添加Popover</h2>
<h3 id="添加popover控件">添加Popover控件</h3>
<p>打开文件<code>MainMenu.xib</code>，右下脚搜索控件<code>Popover</code>就会看到：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024689568360.png" alt="20180629153024689568360.png"></p>
<p>点击控件将其拖入界面，添加后其并没有可视化的元素，可以在<code>Objects</code>管理器中看到已经添加成功：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024707619315.png" alt="20180629153024707619315.png"></p>
<p>启动<code>Assitant Editor</code>，按住<code>Contorl</code>键点击<code>Popover</code>拖入<code>AppDelegate.swift</code>文件，创建<code>popover</code>属性：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/link.mp4" controls="controls" width="100%">popover_link</video></p>
<h3 id="添加popover-view-controller">添加Popover View Controller</h3>
<p>此时<code>popover</code>是没有界面的，因为此时还没有为其指定<code>view controller</code>。</p>
<ul>
<li>
<p><code>Command</code>+<code>n</code>或者菜单栏依次选择<strong>File</strong>-&gt;<strong>New</strong>-&gt;**File…，就会调出新建文件窗口，**选择 <strong>macOS</strong> -&gt; <strong>Cocoa Class</strong> -&gt; <strong>Next</strong>;</p>
</li>
<li>
<p>名称最好是跟目的统一，这里我设置成<code>PopoverDemoViewController</code>，继承自<code>NSViewController</code>，勾选☑️<code>also create XIB file for user interface</code>，语言依旧是<code>Swift</code>，然后 <strong>Next</strong> -&gt; <strong>Create</strong>会创建两个文件。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024886162075.png" alt="20180629153024886162075.png"></p>
<ul>
<li>PopoverDemoViewController.swift</li>
<li>PopoverDemoViewController.xib</li>
</ul>
</li>
<li>
<p>打开文件<code>MainMenu.xib</code>，选择界面设计中的<code>Popover View Controller</code>，然后设置其对应的类为刚才创建的<code>PopoverDemoViewController</code>，此时<code>popover</code>的界面就可以在<code>PopoverDemoViewController.xib</code>中设计了:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153024927617269.png" alt="20180629153024927617269.png"></p>
</li>
<li>
<p>此时虽指定了具体的 <strong>view controller</strong>，但还没有触发<code>popover</code>显示的地方，一开始我们添加了 <code>statusItem</code>，就是为了利用状态栏按钮点击来显示的，只需要定义开关<code>popover</code>的接口指定给<code>statusItem</code>的<code>action</code>即可，定义以下三个函数：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">showPopover</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">AnyObject</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="kd">let</span> <span class="nv">button</span> <span class="p">=</span> <span class="n">statusItem</span><span class="p">.</span><span class="n">button</span> <span class="p">{</span>
        <span class="n">popover</span><span class="p">.</span><span class="n">show</span><span class="p">(</span><span class="n">relativeTo</span><span class="p">:</span> <span class="n">button</span><span class="p">.</span><span class="n">bounds</span><span class="p">,</span> <span class="n">of</span><span class="p">:</span> <span class="n">button</span><span class="p">,</span> <span class="n">preferredEdge</span><span class="p">:</span> <span class="n">NSRectEdge</span><span class="p">.</span><span class="n">minY</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">closePopover</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">AnyObject</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">popover</span><span class="p">.</span><span class="n">performClose</span><span class="p">(</span><span class="n">sender</span><span class="p">)</span>
<span class="p">}</span>

<span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">togglePopover</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">AnyObject</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">popover</span><span class="p">.</span><span class="n">isShown</span> <span class="p">{</span>
        <span class="n">closePopover</span><span class="p">(</span><span class="n">sender</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">showPopover</span><span class="p">(</span><span class="n">sender</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>重新更改一下<code>applicationDidFinishLaunching </code>中实现的<code>statusItem</code>的<code>action</code>为上面的<code>togglePopover</code>，删掉之前定义的<code>quitApp</code>就可以了：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="k">if</span> <span class="kd">let</span> <span class="nv">button</span> <span class="p">=</span> <span class="n">statusItem</span><span class="p">.</span><span class="n">button</span> <span class="p">{</span>
    <span class="n">button</span><span class="p">.</span><span class="n">image</span> <span class="p">=</span> <span class="n">NSImage</span><span class="p">(</span><span class="n">named</span><span class="p">:</span> <span class="n">NSImage</span><span class="p">.</span><span class="n">Name</span><span class="p">(</span><span class="s">&#34;statusIcon&#34;</span><span class="p">))</span>
    <span class="n">button</span><span class="p">.</span><span class="n">action</span> <span class="p">=</span> <span class="k">#selector</span><span class="p">(</span><span class="n">togglePopover</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>此时运行程序，就会看到正常出现的状态栏按钮，点击按钮就会弹出<code>Popover</code>，再次点击就会关闭。</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/popover-click.mp4" width="100%" controls="controls">popover-click</video></p>
</li>
</ul>
<h3 id="设计popover界面">设计Popover界面</h3>
<p>上面提到，我们可以在<code>PopoverDemoViewController.xib</code>中设计<code>popover</code>的界面。</p>
<h4 id="添加应用退出按钮">添加应用退出按钮</h4>
<ul>
<li>
<p>打开<code>PopoverDemoViewController.xib</code>文件，会看到一个<code>view</code>控件，我们拖动一个<code>Push Button</code>控件到<code>view</code>中，放置到右上角。</p>
</li>
<li>
<p>选中添加的按钮，在控件属性窗口中， 取消勾选 <strong>Boarded</strong>，选择<strong>Image</strong> 为 <code>NSStopProcessFrestandingTemplate</code>：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153025204615191.png" alt="20180629153025204615191.png"></p>
</li>
<li>
<p>调出<code>Assitant Editor</code>，按住<code>Control</code>键，点击按钮拖入<code>PopoverDemoViewContoller.swift</code>，创建action为点击事件<code>quitApp</code>，实现代码与之前的测试函数<code>quiApp</code>一样：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">quitApp</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">NSApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">terminate</span><span class="p">(</span><span class="kc">self</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>运行程序，点击弹出popover，此时可以看到刚才添加的按钮，点击一下按钮，程序就会退出。</p>
</li>
</ul>
<h4 id="添加无用的标签">添加无用的标签</h4>
<p>总要在程序中显示点东西吧，就像添加按钮一样拖动一个<code>Label</code>控件到<code>view</code>中，内容改为经典的<code>Hello, World!</code>，啊，不行太俗了，还是改为<code>Hello, Popover!</code>吧，然后调整标签大小，并调整位置在水平和垂直居中的位置，调整内容居中：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180629153025294914532.png" alt="20180629153025294914532.png"></p>
<p>运行程序，看到想要的效果！</p>
<h2 id="优化popover">优化Popover</h2>
<p>此时运行，你会发现有一个问题：点击弹窗外面，弹窗不会自动收起。这并不是我们想要的，查看apple官方的<code>NSPopover</code>文档，我们知道他有一个<code>behavior</code>属性，其值为<code>NSPopover.Behavior.transient</code>的时候好像可以实现，尝试一下。</p>
<p>打开<code>MainMenu.xib</code>，选中<code>Popover</code>，在其属性设置区就会看到<code>Behavior</code>，我们选择<code>Transient</code>，运行程序会发现：确实可以实现，点击弹窗外面，弹窗会自动收起，但是前提是必须在弹窗内有一次点击事件后才能做到这个效果。</p>
<blockquote>
<p>后来发现若弹窗内有 <strong>firs responder</strong> 就可以实现理想结果，不用操作弹窗中的内容，在弹窗外点击就会收起弹窗!</p>
</blockquote>
<p>显然上面的配置也不是我们想要的，网上看到一种神奇的方式：添加系统事件监视器来实现对交互事件的监测，从而做到弹窗显示后，无论什么时候点击弹窗外面都能收起弹窗的效果。</p>
<ul>
<li>
<p>新建名为<code>EventMonitor</code>的<code>swift</code>文件：<code>Command</code>+<code>n</code>组合拳，选择<strong>macOS</strong>-&gt; <strong>Swift File</strong> -&gt; <strong>Next</strong>，输入文件名<code>EventMonitor</code>创建；</p>
</li>
<li>
<p>文件代码为：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kd">import</span> <span class="nc">Cocoa</span>

<span class="kd">class</span> <span class="nc">EventMonitor</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nv">mask</span><span class="p">:</span> <span class="n">NSEvent</span><span class="p">.</span><span class="n">EventTypeMask</span>
    <span class="kd">var</span> <span class="nv">handler</span> <span class="p">:</span> <span class="p">(</span><span class="n">NSEvent</span><span class="p">?)</span> <span class="p">-&gt;</span> <span class="p">()</span>
    <span class="kd">var</span> <span class="nv">monitor</span><span class="p">:</span> <span class="nb">Any</span><span class="p">?</span>

    <span class="kd">init</span><span class="p">(</span><span class="n">mask</span><span class="p">:</span> <span class="n">NSEvent</span><span class="p">.</span><span class="n">EventTypeMask</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="p">@</span><span class="n">escaping</span> <span class="p">(</span><span class="n">NSEvent</span><span class="p">?)</span> <span class="p">-&gt;</span> <span class="p">()){</span>
        <span class="kc">self</span><span class="p">.</span><span class="n">mask</span> <span class="p">=</span> <span class="n">mask</span>
        <span class="kc">self</span><span class="p">.</span><span class="n">handler</span> <span class="p">=</span> <span class="n">handler</span>
    <span class="p">}</span>

    <span class="kd">deinit</span> <span class="p">{</span>
        <span class="n">stop</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">start</span><span class="p">(){</span>
        <span class="n">monitor</span> <span class="p">=</span> <span class="n">NSEvent</span><span class="p">.</span><span class="n">addGlobalMonitorForEvents</span><span class="p">(</span><span class="n">matching</span><span class="p">:</span> <span class="n">mask</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="n">handler</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">stop</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">monitor</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
            <span class="n">NSEvent</span><span class="p">.</span><span class="n">removeMonitor</span><span class="p">(</span><span class="n">monitor</span><span class="p">!)</span>
            <span class="n">monitor</span> <span class="p">=</span> <span class="kc">nil</span>
        <span class="p">}</span>
    <span class="p">}</span>

<span class="p">}</span>
</code></pre></div><p>文件中定义了<code>EventMonitor</code>类，添加了构造函数和两个接口用于，创建用户操作事件监视器、启动和关闭监视器。</p>
</li>
<li>
<p>打开文件<code>AppDelegate.swift</code>，添加监视器属性：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kd">var</span> <span class="nv">eventMonitor</span><span class="p">:</span> <span class="n">EventMonitor</span><span class="p">?</span>
</code></pre></div></li>
<li>
<p>在<code>applicationDidFinishLaunching</code>中添加监视器的初始化操作：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="n">eventMonitor</span> <span class="p">=</span> <span class="n">EventMonitor</span><span class="p">(</span><span class="n">mask</span><span class="p">:</span> <span class="p">[.</span><span class="n">leftMouseDown</span><span class="p">,</span> <span class="p">.</span><span class="n">rightMouseDown</span><span class="p">])</span> <span class="p">{</span> <span class="p">[</span><span class="kr">weak</span> <span class="kc">self</span><span class="p">]</span> <span class="n">event</span> <span class="k">in</span>
    <span class="k">if</span> <span class="kd">let</span> <span class="nv">strongSelf</span> <span class="p">=</span> <span class="kc">self</span><span class="p">,</span> <span class="n">strongSelf</span><span class="p">.</span><span class="n">popover</span><span class="p">.</span><span class="n">isShown</span> <span class="p">{</span>
        <span class="n">strongSelf</span><span class="p">.</span><span class="n">closePopover</span><span class="p">(</span><span class="n">event</span><span class="p">!)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>还需要完善<code>popover</code>显示和关闭的接口：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">closePopover</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">AnyObject</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">popover</span><span class="p">.</span><span class="n">performClose</span><span class="p">(</span><span class="n">sender</span><span class="p">)</span>
    <span class="n">eventMonitor</span><span class="p">?.</span><span class="n">stop</span><span class="p">()</span>
<span class="p">}</span>

<span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">showPopover</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">AnyObject</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="kd">let</span> <span class="nv">button</span> <span class="p">=</span> <span class="n">statusItem</span><span class="p">.</span><span class="n">button</span> <span class="p">{</span>
        <span class="n">popover</span><span class="p">.</span><span class="n">show</span><span class="p">(</span><span class="n">relativeTo</span><span class="p">:</span> <span class="n">button</span><span class="p">.</span><span class="n">bounds</span><span class="p">,</span> <span class="n">of</span><span class="p">:</span> <span class="n">button</span><span class="p">,</span> <span class="n">preferredEdge</span><span class="p">:</span> <span class="n">NSRectEdge</span><span class="p">.</span><span class="n">minY</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="n">eventMonitor</span><span class="p">?.</span><span class="n">start</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>最后，最好是打开<code>MainMenu.xib</code>文件将<code>Popover</code>的<code>Behavior</code>属性设置为<code>Applicationed Defined</code>。运行程序，当啷啷，符合预期！</p>
</li>
</ul>
<h2 id="运行效果">运行效果：</h2>
<p>最终的这么简陋的程序的运行效果：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/popover/popover_final.mp4" width="100%" controls="controls">popover_final</video></p>
<p>完整的工程可以<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/popover/PopoverDemo.zip">点我</a>下载。</p>
<h2 id="参考">参考：</h2>
<ul>
<li>
<p><a href="https://www.raywenderlich.com/165853/menus-popovers-menu-bar-apps-macos">Menus and Popovers in Menu Bar Apps for macOS</a></p>
</li>
<li>
<p><a href="https://developer.apple.com/documentation/appkit/nspopover">NSPopover</a></p>
</li>
<li>
<p><a href="https://developer.apple.com/documentation/appkit/nsstatusbar">NSStatusBar</a></p>
</li>
</ul>]]></content>
		</item>
		
		<item>
			<title>爬虫实践之网页长截图</title>
			<link>https://blog.5km.studio/2018/06/27/spider-practice-page-screenshot/</link>
			<pubDate>Wed, 27 Jun 2018 23:58:50 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/06/27/spider-practice-page-screenshot/</guid>
			<description>&lt;p&gt;今天在想一个问题，会不会有一天我的静态博客就废了，应该不至于哈，本地也能渲染嘛！但是假设真的没有地方可以访问我们的文章了，我们提前截个图，保存个pdf，备个份啥的也是有必要的嘛！本文就来说一下怎么为python网页内容截图，炒🐔煎🥚！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天在想一个问题，会不会有一天我的静态博客就废了，应该不至于哈，本地也能渲染嘛！但是假设真的没有地方可以访问我们的文章了，我们提前截个图，保存个pdf，备个份啥的也是有必要的嘛！本文就来说一下怎么为python网页内容截图，炒🐔煎🥚！</p>
<h2 id="环境与依赖">环境与依赖</h2>
<ul>
<li>python3</li>
<li>selenium库</li>
<li>PhantomJS</li>
</ul>
<h3 id="selenium库"><code>selenium</code>库</h3>
<p>使用<code>pip3</code>安装即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pip3 install selenium
</code></pre></div><blockquote>
<p>Selenium automates browsers. That&rsquo;s it! What you do with that power is entirely up to you. Primarily, it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should!) be automated as well.</p>
</blockquote>
<blockquote>
<p>Selenium has the support of some of the largest browser vendors who have taken (or are taking) steps to make Selenium a native part of their browser. It is also the core technology in countless other browser automation tools, APIs and frameworks.</p>
</blockquote>
<p>简单来说，<code>selenium</code>是一个可以操作浏览器或web渲染引擎的东西，方便自动化测试，比较明显的特征就是可以模拟浏览器中用户做的操作，比如点击、滚动、拖动等等，当然也能解析网页内容，所以这就使它经常被用来爬动态页面。</p>
<p>可以用<code>selenium</code>创建<code>chrome</code>、<code>Firefox</code>、<code>Safari</code>、<code>PhantomJS</code>浏览器实例，具体使用方法以后有时间整理一下。</p>
<h3 id="phantomjs"><code>PhantomJS</code></h3>
<p>这个东西不太熟悉，不过知道它是一个无界面、可脚本编程的网页浏览器，应该与<code>chrome</code>以<code>--headless</code>(使chrome无界面运行)参数运行的状态类似，为什么这里用<code>PhantomJS</code>？因为这个与<code>Seleniumium</code>库配合更容易截长图，装个<code>phantomJS</code>先。</p>
<p>访问网址：<a href="http://phantomjs.org/download.html">http://phantomjs.org/download.html </a>进行下载对应平台的应用包。</p>
<p>解压后会看到<code>bin</code>目录，其下就是<code>phantomjs</code>命令行运行程序，将它扔到<code>$PATH</code>路径中，比如这样：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">mv phantomjs /usr/local/bin/
</code></pre></div><p>重启终端，验证一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  phantomjs -v
2.1.1
</code></pre></div><p>版本是2.1.1，说明安装成功！</p>
<h2 id="长截图">长截图</h2>
<p>这里十里使用<code>selenium</code>库和<code>phantomjs</code>引擎实现长截图，<code>selenium</code>库其实就是封装了调用<code>phantomjs</code>的接口，非常方便！要实现某个网页的长截图，代码非常少：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/local/bin/python3</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">from</span> <span class="nn">selenium</span> <span class="kn">import</span> <span class="n">webdriver</span>

<span class="n">URL</span> <span class="o">=</span> <span class="s1">&#39;https://www.smslit.top&#39;</span>
<span class="n">PIC_PATH</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">expanduser</span><span class="p">(</span><span class="s1">&#39;~&#39;</span><span class="p">),</span> <span class="s1">&#39;Pictures/python/screenshot&#39;</span><span class="p">)</span> 
<span class="n">PIC_NAME</span> <span class="o">=</span> <span class="s1">&#39;screenshot.png&#39;</span>

<span class="k">def</span> <span class="nf">screenshot_web</span><span class="p">(</span><span class="n">url</span><span class="o">=</span><span class="n">URL</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="n">PIC_PATH</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">PIC_NAME</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;capture the whole web page
</span><span class="s1">    :param url: the website url
</span><span class="s1">    :type url: str
</span><span class="s1">    :param path: the path for saving picture
</span><span class="s1">    :type str
</span><span class="s1">    :param name: the name of picture
</span><span class="s1">    :type name: str
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
        <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
    <span class="n">browser</span> <span class="o">=</span> <span class="n">webdriver</span><span class="o">.</span><span class="n">PhantomJS</span><span class="p">()</span>
    <span class="n">browser</span><span class="o">.</span><span class="n">set_window_size</span><span class="p">(</span><span class="mi">1200</span><span class="p">,</span> <span class="mi">800</span><span class="p">)</span>
    <span class="n">browser</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
    <span class="n">pic_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">name</span><span class="p">))</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">pic_path</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">browser</span><span class="o">.</span><span class="n">save_screenshot</span><span class="p">(</span><span class="n">pic_path</span><span class="p">):</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Done!&#39;</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Failed!&#39;</span><span class="p">)</span>
    <span class="n">browser</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
        
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">screenshot_web</span><span class="p">()</span>
</code></pre></div><p>首先构造了一个以<code>PhantomJS</code>引擎为核心的实例，然后<code>get</code>相应网址，等待3秒后，执行保存截图函数。 <code>selenium</code>中提供了<code>WebDriverWait</code>模块用来等待网页加载，这里就不展示了，使用比较暴力的死等。</p>
<h2 id="执行效果">执行效果</h2>
<p>执行脚本：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">python3 screenshot.py
</code></pre></div><p>⚠️</p>
<p>执行脚本后可能会看到以下内容：</p>
<blockquote>
<p>/usr/local/lib/python3.6/site-packages/selenium/webdriver/phantomjs/webdriver.py:49: UserWarning: Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead
warnings.warn(&lsquo;Selenium support for PhantomJS has been deprecated, please use headless '</p>
</blockquote>
<p><code>selenium</code>建议使用<code>Chrome</code>或者<code>Firefox</code>的<code>--headless</code>模式替代<code>PhantomJS</code>，但是并不影响使用。</p>
<p><code>Finder</code>中查看生成相应目录，并有相应截图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180627153011510569656.png" alt="20180627153011510569656.png"></p>]]></content>
		</item>
		
		<item>
			<title>mac下修复文件和目录为正常权限</title>
			<link>https://blog.5km.studio/2018/06/26/mac-fix-permission/</link>
			<pubDate>Tue, 26 Jun 2018 15:03:47 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/06/26/mac-fix-permission/</guid>
			<description>&lt;p&gt;昨天误删了macbook上&lt;code&gt;smslit&lt;/code&gt;用户下好多东西，无奈新建了一个用户&lt;code&gt;5km&lt;/code&gt;，在删除原用户目录前，将重要的文档拷贝到了移动硬盘，然后进入新用户中，把文件从移动硬盘拷贝到用户目录下，结果今天&lt;code&gt;ls&lt;/code&gt;查看文件的时候发现权限不对呀，所有的文件和目录权限都变成了&lt;code&gt;777&lt;/code&gt;。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>昨天误删了macbook上<code>smslit</code>用户下好多东西，无奈新建了一个用户<code>5km</code>，在删除原用户目录前，将重要的文档拷贝到了移动硬盘，然后进入新用户中，把文件从移动硬盘拷贝到用户目录下，结果今天<code>ls</code>查看文件的时候发现权限不对呀，所有的文件和目录权限都变成了<code>777</code>。</p>
<p>就像下图中的样子，糊了一片骚黄，好扎眼，权限肯定不对呀，而且这只是目录，<code>ll</code>查看其中目录下的文件，咋还都有了运行权限，红呼呼一条条的，现在想想，好像之前和朋友<a href="vimux.org">vimux</a>就注意到过这个问题。必须解决一下了。</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180626152998802950626.png" width="480px"/>
</figure>

<h2 id="文件的正常权限">文件的正常权限</h2>
<p>在这之前，十里只知道怎么查看文件权限以及它们代表的意思，正常文件和目录的权限过去没关注，好惭愧-_-!!!。</p>
<p>其实也不难，新建一个文件和目录，看一下就知道了：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  Documents touch newfile <span class="o">&amp;&amp;</span> mkdir newdir <span class="o">&amp;&amp;</span> ll
</code></pre></div><figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180626152998790147157.png" width="480px"/>
</figure>

<p>那么正常权限：</p>
<ul>
<li>文件：<code>-rw-r--r--</code>也就是<code>644</code></li>
<li>目录：<code>drwxr-xr-x</code>也就是<code>755</code></li>
</ul>
<h2 id="修复权限">修复权限</h2>
<p>思路很简单，查找目录下所有文件修改权限为<code>644</code>，查找目录下所有目录修改权限为<code>755</code>，这里使用<code>find</code>命令按类型查找，然后根据类型使用<code>chmod</code>命令对应修改权限。</p>
<ul>
<li>
<p>修改所有文件权限：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  Documents find . -type f -exec chmod <span class="m">644</span> <span class="o">{}</span> <span class="se">\;</span>
<span class="c1"># 也可以使用 find . -type f -print0 | xargs -0 chmod 644</span>
</code></pre></div></li>
<li>
<p>修改目录权限</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  Documents find . -type d -exec chmod <span class="m">755</span> <span class="o">{}</span> <span class="se">\;</span>
<span class="c1"># 也可以使用 find . -type d -print0 | xargs -0 chmod 755</span>
</code></pre></div><figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180626152998915286786.png" width="480px"/>
</figure>

<p>上述命令中利用<code>find</code>命令的<code>-type</code>指定类型，<code>-exec</code>以find的一条输出作为<code>chmod</code>的参数。</p>
<blockquote>
<ul>
<li>➜后面的关键字，代表的是当前工作目录，再后面才是命令</li>
<li>在上面的每行命令下面的注释中是另一种实现方式，其中<code>-print0</code>和<code>-0</code>表示可以处理名称带空格的文件和目录。</li>
</ul>
</blockquote>
</li>
</ul>
<h2 id="封装">封装</h2>
<p>为了更方便一些，可以写一个shell脚本，传入一个路径作为参数，就可以修复指定路径下文件和目录的权限了。</p>
<h3 id="脚本内容">脚本内容</h3>
<p>十里编写了脚本<code>fixpermissioin</code>如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="cp">#!/bin/bash
</span><span class="cp"></span>
helpinfo<span class="o">()</span> <span class="o">{</span>
    <span class="nb">echo</span> -e <span class="s2">&#34;Usage:\n  </span><span class="nv">$0</span><span class="s2"> dirname\n  - dirname is a path&#34;</span>
<span class="o">}</span>

<span class="c1"># 判断参数</span>
<span class="k">if</span> <span class="o">[</span> <span class="nv">$#</span> !<span class="o">=</span> <span class="m">1</span> <span class="o">]</span><span class="p">;</span><span class="k">then</span>
    helpinfo
    <span class="nb">exit</span> <span class="m">1</span>
<span class="k">fi</span>

<span class="c1"># 判断目录是否存在</span>
<span class="k">if</span> <span class="o">[</span> ! -d <span class="nv">$1</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
    <span class="nb">echo</span> -e <span class="s2">&#34;</span><span class="nv">$1</span><span class="s2"> does not exist!\nPlease enter a valid path name!&#34;</span>
    helpinfo
    <span class="nb">exit</span> <span class="m">1</span>
<span class="k">fi</span>

<span class="c1"># 修复目录下文件和目录的权限</span>
<span class="nb">echo</span> <span class="s2">&#34;Fix permission of files ...&#34;</span>
<span class="c1"># find $1  -type f -print0 | xargs -0 chmod 644</span>
find <span class="nv">$1</span> -type f -exec chmod <span class="m">644</span> <span class="o">{}</span> <span class="se">\;</span>
<span class="nb">echo</span> <span class="s2">&#34;Done!&#34;</span>
<span class="nb">echo</span> <span class="s2">&#34;Fix permission of dirs ...&#34;</span>
<span class="c1"># find $1  -type d -print0 | xargs -0 chmod 755</span>
find <span class="nv">$1</span> -type d -exec chmod <span class="m">755</span> <span class="o">{}</span> <span class="se">\;</span>
<span class="nb">echo</span> <span class="s2">&#34;Done!&#34;</span>
</code></pre></div><h3 id="脚本权限">脚本权限</h3>
<p>为脚本添加运行权限：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">chmod +x fixpermissioin
</code></pre></div><h3 id="测试脚本">测试脚本</h3>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  /tmp ./fixpermission
Usage:
  ./fixpermission dirname
  - dirname is a path
➜  /tmp ./fixpermission ~/Downloads/applications/

Fix permission of files ...
/Users/5km/Downloads/applications//pap.er_v3.2.dmg
/Users/5km/Downloads/applications//.DS_Store
/Users/5km/Downloads/applications//PicU-1.54.dmg
/Users/5km/Downloads/applications//Dash_4.2.0_<span class="o">[</span>TNT<span class="o">]</span>.dmg

Done!

Fix permission of <span class="nb">dirs</span> ...
/Users/5km/Downloads/applications/

Done!

</code></pre></div><h2 id="总结">总结</h2>
<p>Peace! 搞定。</p>]]></content>
		</item>
		
		<item>
			<title>vim插件管理工具pack</title>
			<link>https://blog.5km.studio/2018/06/25/vim-pack/</link>
			<pubDate>Mon, 25 Jun 2018 17:23:57 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/06/25/vim-pack/</guid>
			<description>&lt;p&gt;今天手残输错了一个删除命令，结果把&lt;code&gt;.vimrc&lt;/code&gt;和&lt;code&gt;.vim&lt;/code&gt;删光了，其实更惨的是我的用户目录下的所有软件配置文件都没了，可‘歌’可泣，现在听着外面的大雨哗啦啦的，老天都在为我的&amp;rsquo;壮举&amp;rsquo;哭泣呀！幸好我用的vim插件不多，前段时间听朋友说过&lt;code&gt;vim 8.0&lt;/code&gt;有个新的插件管理工具横空出世，叫&lt;a href=&#34;https://github.com/maralla/pack&#34;&gt;&lt;code&gt;pack&lt;/code&gt;&lt;/a&gt;，那就玩耍一番喽！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天手残输错了一个删除命令，结果把<code>.vimrc</code>和<code>.vim</code>删光了，其实更惨的是我的用户目录下的所有软件配置文件都没了，可‘歌’可泣，现在听着外面的大雨哗啦啦的，老天都在为我的&rsquo;壮举&rsquo;哭泣呀！幸好我用的vim插件不多，前段时间听朋友说过<code>vim 8.0</code>有个新的插件管理工具横空出世，叫<a href="https://github.com/maralla/pack"><code>pack</code></a>，那就玩耍一番喽！</p>
<h2 id="现状">现状</h2>
<ul>
<li>vim8.1</li>
<li>macOS 10.13.5</li>
<li>vim用户目录下零配置零插件</li>
</ul>
<h2 id="遗失的vimrc">遗失的vimrc</h2>
<p>潇洒地敲完命令，猛击回车，眼前屏幕上哗啦啦的一直闪过<code>permission denied</code>字样，扎心了，反应过来的时候已经过去3秒，故作淡定的十里一套组合拳<code>ctrl</code>+<code>c</code>，让这烦人的打印消息消停了，随后听到十里的一阵儿鬼哭狼嚎：</p>
<blockquote>
<p>我去～我去～我去～来，啊～</p>
</blockquote>
<p>用户目录已垮，胆战心惊的查看了用户目录下的<code>Documents</code>、<code>Downloads</code>、<code>Pictures</code>几个关键目录下的文件，还好还好，都还健在，咋还有种不祥的预感，啊～用户目录下的Library目录下的应用程序的配置和数据已经没了大半，还有隐藏的那些xxxrc文件都木有了，会呼吸的痛！</p>
<p>好吧，索性直接把文件拷出来新建一个<code>5km</code>用户吧，从新开始！</p>
<p>之前<code>.vimrc</code>里的内容还不少，也要从新开始吗？还是算了吧，因为我之前有用Timemachine备份过整个系统，你问我为什么不直接恢复备份呢，还新键用户干嘛？因为：</p>
<ul>
<li>备份是n个多月前的；</li>
<li>我早就想清理门户了；</li>
</ul>
<p>连接备份盘，找了最新备份下的文件，果然被我找到了<code>.vimrc</code>，呀！还有<code>.vim</code>，进去一看是只是目录结构然而没有文件，无所谓了，要从新开始嘛，只把遗失的<code>.vimrc</code> cp到用户目录下，我找到了遗失的你，这就是命！</p>
<h2 id="我需要装啥插件">我需要装啥插件</h2>
<p>之前用<code>bundle</code>管理vim插件，所以去备份盘里的<code>.vim</code>下看看<code>bundle</code>目录下有啥来着，呵呵记不太清了，看一下就知道了：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">completor.vim
completor-neosnippet
vim-colors-solarized
vim-airline
vim-airline-themes
vim-markdown
</code></pre></div><h2 id="pack一下">pack一下</h2>
<p>去github上找到了<a href="https://github.com/maralla/pack"><code>pack</code></a>，教程写的很清楚耶！教程里的示例竟然有我装的四个插件的安装演示，可以的……^ _ ^</p>
<h3 id="安装pack">安装pack</h3>
<ul>
<li>
<p>去工程的<a href="https://github.com/maralla/pack/releases">release</a>页面，找到最新的发布包<code>pack-v0.2.2-x86_64-apple-darwin.tar.gz</code>下载；</p>
</li>
<li>
<p>解压安装包，进入目录可以看到<code>pack</code>命令行工具；</p>
</li>
<li>
<p>将<code>pack</code> cp到<code>/usr/local/bin</code>下；</p>
</li>
<li>
<p>重新打开一个<a href="https://www.iterm2.com">iterm2</a>窗口，输入命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  ~ pack -h
pack 0.2.3
maralla &lt;maralla.ai@gmail.com&gt;
Package manager <span class="k">for</span> vim

USAGE:
    pack <span class="o">[</span>SUBCOMMAND<span class="o">]</span>

FLAGS:
    -h, --help       Prints <span class="nb">help</span> information
    -V, --version    Prints version information

SUBCOMMANDS:
    config       Configure/edit the package specific configuration
    generate     Generate the pack package file
    <span class="nb">help</span>         Prints this message or the <span class="nb">help</span> of the given subcommand<span class="o">(</span>s<span class="o">)</span>
    install      Install new packages/plugins
    list         List installed packages
    move         Move a package to a different category or make it optional.
    uninstall    Uninstall packages/plugins
    update       Update packages
</code></pre></div></li>
</ul>
<h3 id="安装插件">安装插件</h3>
<p>用pack安装很方便，现在大部分vim插件都托管到了github上，貌似基本的安装方式就是：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pack install maralla/completor.vim maralla/completor-neosnippet plasticboy/vim-markdown vim-airline/vim-airline vim-airline/vim-airline-themes

   ✓ <span class="o">[</span>maralla/completor.vim<span class="o">]</span> <span class="k">done</span>
   ✓ <span class="o">[</span>maralla/completor-neosnippet<span class="o">]</span> <span class="k">done</span>
   ✓ <span class="o">[</span>plasticboy/vim-markdown<span class="o">]</span> <span class="k">done</span>
   ✓ <span class="o">[</span>vim-airline/vim-airline<span class="o">]</span> <span class="k">done</span>
   ✓ <span class="o">[</span>vim-airline/vim-airline-themes<span class="o">]</span> <span class="k">done</span>

</code></pre></div><p>像主题这种插件要以<code>opt</code>方式安装，可以这样：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pack install altercation/vim-colors-solarized -o

   ✓ <span class="o">[</span>altercation/vim-colors-solarized<span class="o">]</span> <span class="k">done</span>

</code></pre></div><p>其它安装的帮助信息可以这样查看：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pack install -h
</code></pre></div><h3 id="查看安装的插件">查看安装的插件</h3>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pack list
altercation/vim-colors-solarized <span class="o">=</span>&gt; pack/default/opt
maralla/completor-neosnippet <span class="o">=</span>&gt; pack/default/start
maralla/completor.vim <span class="o">=</span>&gt; pack/default/start
plasticboy/vim-markdown <span class="o">=</span>&gt; pack/default/start
vim-airline/vim-airline <span class="o">=</span>&gt; pack/default/start
vim-airline/vim-airline-themes <span class="o">=</span>&gt; pack/default/start
</code></pre></div><h3 id="其它用法">其它用法</h3>
<h4 id="更新插件">更新插件</h4>
<p>更新全部插件：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pack update

   ✓ <span class="o">[</span>altercation/vim-colors-solarized<span class="o">]</span> <span class="k">done</span>
   ✓ <span class="o">[</span>maralla/completor-neosnippet<span class="o">]</span> <span class="k">done</span>
   ✓ <span class="o">[</span>maralla/completor.vim<span class="o">]</span> <span class="k">done</span>
   ✓ <span class="o">[</span>plasticboy/vim-markdown<span class="o">]</span> <span class="k">done</span>
   ✓ <span class="o">[</span>vim-airline/vim-airline<span class="o">]</span> <span class="k">done</span>
   ✓ <span class="o">[</span>vim-airline/vim-airline-themes<span class="o">]</span> <span class="k">done</span>

</code></pre></div><p>更新指定插件：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pack update altercation/vim-colors-solarized

   ✓ <span class="o">[</span>altercation/vim-colors-solarized<span class="o">]</span> <span class="k">done</span>

</code></pre></div><h4 id="配置插件">配置插件</h4>
<p>形如：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pack config authorname/packname
</code></pre></div><p>此时会打开vim，输入相应插件的配置内容，保存再次打开vim即可生效。</p>
<h4 id="卸载插件">卸载插件</h4>
<p>只需将安装插件时的<code>install</code>改成<code>uninstall</code>即可，另外最后可以加参数<code>-d</code>或<code>-a</code>，表示将配置文件也删除。</p>
<h2 id="总结">总结</h2>
<p>致此，稍微将<code>.vimrc</code>部分无用的内容注释掉后，十里潇洒的<code>vim</code>又强势归来。</p>
<ul>
<li><code>rm</code>命令加餐<code>-rf</code>须谨慎！</li>
<li>利用好<code>Timemachine</code>，常备份！</li>
<li><code>pack</code>很生猛，强烈推荐使用！</li>
</ul>
<p>第二条臣妾做不到，不为什么！我不是人造革，我是真的皮！</p>]]></content>
		</item>
		
		<item>
			<title>爬虫实践之hexo博客分析</title>
			<link>https://blog.5km.studio/2018/06/25/spider-practice-hexo-blogo/</link>
			<pubDate>Mon, 25 Jun 2018 15:17:27 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/06/25/spider-practice-hexo-blogo/</guid>
			<description>&lt;p&gt;爬虫最好处理的就是静态页面，而静态博客就是静态页面，像&lt;a href=&#34;https://jekyllrb.com&#34;&gt;jekyll&lt;/a&gt;和&lt;a href=&#34;https://hexo.io&#34;&gt;hexo&lt;/a&gt;生成的博客就属于这一类型。本文将记录针对hexo博客中博文的爬取和统计以及博文中图片的批量爬取。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>爬虫最好处理的就是静态页面，而静态博客就是静态页面，像<a href="https://jekyllrb.com">jekyll</a>和<a href="https://hexo.io">hexo</a>生成的博客就属于这一类型。本文将记录针对hexo博客中博文的爬取和统计以及博文中图片的批量爬取。</p>
<p>我们知道<code>hexo</code>博客系统生成的博客，大量依赖主题模板，所以对于页面内容的解析一定更有规律可循，虽然不同主题之间存在差异，但是一定存在通用的方法可以针对所有hexo博客。</p>
<h2 id="平台说明">平台说明</h2>
<ul>
<li>操作系统：macOS 10.13.5</li>
<li>浏览器：safari</li>
<li>python版本：python3.6.5</li>
<li>python库管理工具：pip3</li>
</ul>
<p>其它平台思路一致，只是工具使用有略微差别，大部分用户可能使用chrome分析页面，其实safari和chrome分析页面的过程一样，检查窗口布局稍有不同而已，但是macOS下都可以通过<code>command</code>+<code>option</code>+<code>i</code>调出这个开发用的窗口。</p>
<blockquote>
<p>这里需要注意的是，默认safari是打不开页面元素检查窗口的，需要设置一下：</p>
</blockquote>
<blockquote>
<ul>
<li>打开<code>偏好设置</code>，调到<code>高级</code>标签页；</li>
<li>勾选<code>在菜单栏显示&quot;开发&quot;菜单</code>，勾选成功后，菜单栏会多出<code>开发</code>项</li>
</ul>
</blockquote>
<h2 id="目标">目标</h2>
<p>本文中爬虫应用的目标是分析hexo博客中博主每月发布文章的数量，并绘制相关柱状图，以及爬取每篇文章里的图片。</p>
<h2 id="分析">分析</h2>
<p>可以发现hexo博客系统中存在<code>page</code>这种属性的页面，其中会有个<code>archive</code>（也就是归档）的页面，在这个页面中是整个博客中按照年份整理的博文列表，比如十里的好友<a href="http://www.litreily.top/">LITREILY</a>的归档页面http://www.litreily.top/archives/：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180625152990237065363.png" alt="20180625152990237065363.png"></p>
<p>可以看到页面中博文列表中的元素，包含博文标题、链接，而链接中包含了日期信息。所以分析这个页面中数据就能得到博文的具体信息：</p>
<ul>
<li>博文标题</li>
<li>博文链接</li>
<li>博文日期</li>
</ul>
<p>有了上面的信息就可以按时间统计博文了，同时可以访问博文链接，因为模板的原因，每篇博文DOM树一定是一样的，所以就可以先找到博文内容部分的节点，然后在节点中找<code>img</code>节点就可以了，这样就能获取图片的链接了<code>img</code>节点的<code>src</code>属性内容。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180625152990261978481.png" alt="20180625152990261978481.png"></p>
<h2 id="实现思路设计">实现思路设计</h2>
<h3 id="过程">过程</h3>
<ol>
<li><code>requests</code>库获取指定归档地址页面内容，利用<code>pyquery</code>库根据指定CSS选择器得到的博文列表信息，返回结果是字典，包含博客标题和博文列表，博文列表的元素为每个博文的信息，包含：
<ul>
<li>标题</li>
<li>链接(根据<code>pyquery</code>解析的不完整链接与<code>urllib.parse</code>工具配合合成完整博文链接)</li>
<li>日期</li>
</ul>
</li>
<li>根据第1步返回的数据统计自博客创建来每个月博文发表数量，返回相应统计的字典数据；</li>
<li>根据第2步中的返回结果，利用<code>matplotlib</code>的<code>pyplot</code>绘制统计图；</li>
<li>根据第1步中获取的博文列表数据，<code>requests</code>库访问博文链接，<code>pyquery</code>解析<code>img</code>节点，获取图片链接信息，最终形成图片信息列表，作为结果返回；</li>
<li>根据第4步结果，访问图片链接获取图片二进制内容，利用<code>hashlib</code>的<code>md5</code>生成图片名称，利用<code>os</code>库检查并创建以博文标题命名的目录，将图片按照生成图片名称保存到相应的目录中。</li>
</ol>
<h3 id="形式">形式</h3>
<p>按照文章 <a href="/2018/06/21/spider-practice-pic-dog/">爬虫实践之头条狗狗图片</a> 通用化脚本小节描述的，将脚本做成一命令行工具形式使用的方式，工具分为两个子命令：统计博客活跃度子命令和爬取博客内图片的命令。</p>
<p>同时命令还需传入博客归档页面地址和页面中博文列表元素的CSS选择器，大概的调用形式：</p>
<ul>
<li><strong>爬虫脚本名</strong>   <u>子命令</u>   <em>归档页面地址</em>  <em>博文链接CSS选择器</em></li>
</ul>
<p>而命令形式下，命令参数的获取需要使用<code>sys</code>库。</p>
<h3 id="python库">python库</h3>
<p>根据上述内容描述，程序会依赖下面这些库：</p>
<ul>
<li><code>requests</code></li>
<li><code>pyquery</code></li>
<li><code>urllib</code></li>
<li><code>matplotlib</code></li>
<li><code>hashlib</code></li>
<li><code>os</code></li>
<li><code>sys</code></li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pip3 list
</code></pre></div><p>上述命令查看安装的包，没有安装的python库通过<code>pip3</code>安装即可。</p>
<h2 id="代码实现">代码实现</h2>
<h3 id="导入所需库">导入所需库</h3>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">from</span> <span class="nn">hashlib</span> <span class="kn">import</span> <span class="n">md5</span>
<span class="kn">import</span> <span class="nn">urllib.parse</span> <span class="k">as</span> <span class="nn">up</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="nn">plt</span>
<span class="kn">from</span> <span class="nn">pyquery</span> <span class="kn">import</span> <span class="n">PyQuery</span> <span class="k">as</span> <span class="n">pq</span>
</code></pre></div><h3 id="获取博文列表">获取博文列表</h3>
<p>根据小节<a href="#%E8%BF%87%E7%A8%8B">过程</a>中第一步的描述，获取博客文章列表，封装为一个方法<code>get_blog_posts</code>，需要传入两个参数：</p>
<ul>
<li>博客归档网址</li>
<li>选择页面中博文条目对应的<code>&lt;a&gt;</code>标签</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">get_blog_posts</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">css_selector</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;获取博客文章列表
</span><span class="s1">    :param url: 博客的归档页面地址
</span><span class="s1">    :type url: 字符串
</span><span class="s1">    :param css_selector: 用于选择文章条目对应&lt;a&gt;标签的CSS选择器
</span><span class="s1">    :type css_selector: 字符串
</span><span class="s1">    :return archive: 一个包含博客名称和博文列表的字典，博文列表包含标题、链接和日期信息
</span><span class="s1">    :rtype: dict
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">HEADERS</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
            <span class="n">url_p</span> <span class="o">=</span> <span class="n">up</span><span class="o">.</span><span class="n">urlparse</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
            <span class="n">host_url</span> <span class="o">=</span> <span class="s1">&#39;</span><span class="si">{0}</span><span class="s1">://</span><span class="si">{1}</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">url_p</span><span class="o">.</span><span class="n">scheme</span><span class="p">,</span> <span class="n">url_p</span><span class="o">.</span><span class="n">netloc</span><span class="p">)</span>
            <span class="n">doc</span> <span class="o">=</span> <span class="n">pq</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
            <span class="n">archive</span> <span class="o">=</span> <span class="p">{</span>
                <span class="s1">&#39;name&#39;</span><span class="p">:</span> <span class="n">doc</span><span class="p">(</span><span class="s1">&#39;head title&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">text</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;|&#39;</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">(),</span>
                <span class="s1">&#39;posts&#39;</span><span class="p">:</span> <span class="p">[]</span>
            <span class="p">}</span>
            <span class="k">for</span> <span class="n">post</span> <span class="ow">in</span> <span class="n">doc</span><span class="p">(</span><span class="n">css_selector</span><span class="p">)</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
                <span class="n">article</span> <span class="o">=</span> <span class="p">{</span>
                    <span class="s1">&#39;title&#39;</span><span class="p">:</span> <span class="n">post</span><span class="o">.</span><span class="n">text</span><span class="p">(),</span>
                    <span class="s1">&#39;url&#39;</span><span class="p">:</span> <span class="s1">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">host_url</span><span class="p">,</span> <span class="n">post</span><span class="o">.</span><span class="n">attr</span><span class="o">.</span><span class="n">href</span><span class="p">]),</span>
                    <span class="s1">&#39;date&#39;</span><span class="p">:</span> <span class="n">post</span><span class="o">.</span><span class="n">attr</span><span class="o">.</span><span class="n">href</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="mi">11</span><span class="p">]</span>
                <span class="p">}</span>
                <span class="n">archive</span><span class="p">[</span><span class="s1">&#39;posts&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">article</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">archive</span>
    <span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">ConnectionError</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;网络链接异常！请重试！&#39;</span><span class="p">)</span>
        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div><p>比如对于上面提到的<a href="http://www.litreily.top/archives/">http://www.litreily.top/archives/ </a>可以用浏览器查看一下元素：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180625152990601332320.png" alt="20180625152990601332320.png"></p>
<p>那么可以使用以下CSS选择器进行节点的查询:</p>
<blockquote>
<p>&lsquo;.post-archive .listing li a&rsquo;</p>
</blockquote>
<p>可以这样调用函数获取博文列表：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">get_blog_posts</span><span class="p">(</span><span class="s1">&#39;http://www.litreily.top/archives&#39;</span><span class="p">,</span> <span class="s1">&#39;.post-archive .listing li a&#39;</span><span class="p">)</span>
</code></pre></div><h3 id="统计博文">统计博文</h3>
<p>根据上一步得到的博文列表统计每年的每个月份发表博文数量，结果为字典数据，最外层键为年份，指为一个字典，这个字典以月份作为键，值为对应月份的博客数量：</p>
<ol>
<li>生成空列表；</li>
<li>查询一个博文信息中日期信息，根据年份判断字典中有无对应键，若没有添加新键，初始化值为字典，该字典以12个月份对应数字字符串为键，值初始化为0；</li>
<li>根据博文日期的月份为，对应键的值+1；</li>
<li>一直重复2、3步，知道列表中所有博客查询一遍；</li>
<li>返回统计结果；</li>
</ol>
<p>具体实现：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">statistic_posts_by_date</span><span class="p">(</span><span class="n">posts</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;按照日期对博文分类统计文章个数
</span><span class="s1">    :param posts: 博文列表，每个元素包含标题、链接和日期信息
</span><span class="s1">    :type posts: list
</span><span class="s1">    :return statistics: 按照年份月份统一文章个数
</span><span class="s1">    :rtype statistics: dict
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">statistics</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="n">months</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;</span><span class="si">%02d</span><span class="s1">&#39;</span><span class="o">%</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">13</span><span class="p">)]</span>
    <span class="k">for</span> <span class="n">post</span> <span class="ow">in</span> <span class="n">posts</span><span class="p">:</span>
        <span class="n">year</span><span class="p">,</span> <span class="n">month</span><span class="p">,</span> <span class="n">day</span> <span class="o">=</span> <span class="n">post</span><span class="p">[</span><span class="s1">&#39;date&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">statistics</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">year</span><span class="p">)</span> <span class="o">==</span> <span class="kc">None</span><span class="p">:</span>
            <span class="n">statistics</span><span class="p">[</span><span class="n">year</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
            <span class="k">for</span> <span class="n">month_key</span> <span class="ow">in</span> <span class="n">months</span><span class="p">:</span>
                <span class="n">statistics</span><span class="p">[</span><span class="n">year</span><span class="p">][</span><span class="n">month_key</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="n">statistics</span><span class="p">[</span><span class="n">year</span><span class="p">][</span><span class="n">month</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">return</span> <span class="n">statistics</span>
</code></pre></div><h3 id="绘制统计图">绘制统计图</h3>
<p>这一步利用<code>matplotlib</code>库绘制，使用 <code>subplot</code>方法，默认绘制两列，行数根据博客中年份的数量决定，每年绘制一个子图，子图横坐标设置为月份，以柱状图的形式呈现，为了更好呈现对比，每个子图纵坐标设置大小限制，图中绘制相应月份的数量数值：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">plot_statistics</span><span class="p">(</span><span class="n">statistics</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s1">&#39;posts yearly&#39;</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;根据统计数据绘制曲线图
</span><span class="s1">    :param statistics: 博文按照日期的统计数据
</span><span class="s1">    :type statistics: dict
</span><span class="s1">    :param title: 曲线图标题
</span><span class="s1">    :type title: 字符串
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">count</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">statistics</span><span class="p">)</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">close</span><span class="p">(</span><span class="s1">&#39;all&#39;</span><span class="p">)</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="n">count</span> <span class="o">*</span> <span class="mi">2</span><span class="p">))</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">suptitle</span><span class="p">(</span><span class="n">title</span><span class="p">)</span>
    <span class="n">row</span> <span class="o">=</span> <span class="n">count</span> <span class="o">//</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">1</span>
    <span class="n">sub_n</span> <span class="o">=</span> <span class="mi">1</span>
    <span class="k">for</span> <span class="n">year</span><span class="p">,</span> <span class="n">data</span> <span class="ow">in</span> <span class="n">statistics</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
        <span class="n">plt</span><span class="o">.</span><span class="n">subplot</span><span class="p">(</span><span class="n">row</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">sub_n</span><span class="p">)</span>
        <span class="n">sub_n</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="n">x</span> <span class="o">=</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
        <span class="n">plt</span><span class="o">.</span><span class="n">bar</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">data</span><span class="o">.</span><span class="n">values</span><span class="p">(),</span> <span class="n">label</span><span class="o">=</span><span class="n">year</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">a</span><span class="p">,</span><span class="n">b</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">data</span><span class="o">.</span><span class="n">values</span><span class="p">()):</span>
            <span class="n">plt</span><span class="o">.</span><span class="n">text</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="o">+</span><span class="mf">0.05</span><span class="p">,</span> <span class="s1">&#39;</span><span class="si">%d</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">b</span><span class="p">,</span> <span class="n">ha</span><span class="o">=</span><span class="s1">&#39;center&#39;</span><span class="p">,</span> <span class="n">va</span><span class="o">=</span> <span class="s1">&#39;bottom&#39;</span><span class="p">,</span><span class="n">fontsize</span><span class="o">=</span><span class="mi">7</span><span class="p">)</span>
        <span class="n">plt</span><span class="o">.</span><span class="n">xticks</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">data</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
        <span class="n">plt</span><span class="o">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s1">&#39;Month&#39;</span><span class="p">)</span>
        <span class="n">plt</span><span class="o">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s1">&#39;Count of posts&#39;</span><span class="p">)</span>
        <span class="n">plt</span><span class="o">.</span><span class="n">ylim</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">15</span><span class="p">)</span>
        <span class="n">plt</span><span class="o">.</span><span class="n">legend</span><span class="p">()</span>
    <span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div><h3 id="获取博文中图片信息">获取博文中图片信息</h3>
<p>主要获取图片的链接，首先遍历博文列表，访问每个博文链接，获取页面内容，解析得到文章主体内容部分，再从内容中提取<code>img</code>节点，节点的<code>src</code>属性就是链接，如果是博客内的图片，链接会是相对路径，并不是完整的，需要填充博客主页网址组成完整地址：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">get_post_content</span><span class="p">(</span><span class="n">post</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;获取指定博文的内容
</span><span class="s1">    :param post: 博文
</span><span class="s1">    :type post: dict
</span><span class="s1">    :return post_content: 访问链接中博文内容部分的PyQuery对象
</span><span class="s1">    :rtype: PyQuery
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">url</span> <span class="o">=</span> <span class="n">post</span><span class="p">[</span><span class="s1">&#39;url&#39;</span><span class="p">]</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">HEADERS</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
            <span class="n">doc</span> <span class="o">=</span> <span class="n">pq</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">doc</span><span class="p">(</span><span class="s1">&#39;.post&#39;</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;异常，状态码：&#39;</span><span class="p">,</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="p">)</span>
    <span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">ConnectionError</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">异常...&#39;</span><span class="p">,</span> <span class="n">post</span><span class="p">[</span><span class="s1">&#39;title&#39;</span><span class="p">],</span> <span class="n">url</span><span class="p">,</span> <span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">get_post_images</span><span class="p">(</span><span class="n">posts</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;获取博文列表中博文中所有图片的链接
</span><span class="s1">    :param posts: 博文列表，每个元素包含博文标题、链接和日期
</span><span class="s1">    :type posts: list
</span><span class="s1">    :return images: image信息，包含所属博文标题、图片链接
</span><span class="s1">    :rtype: dict
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="k">for</span> <span class="n">post</span> <span class="ow">in</span> <span class="n">posts</span><span class="p">:</span>
        <span class="n">post_content</span> <span class="o">=</span> <span class="n">get_post_content</span><span class="p">(</span><span class="n">post</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">post_content</span><span class="p">:</span>
            <span class="n">images</span> <span class="o">=</span> <span class="n">post_content</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s1">&#39;img&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
            <span class="n">url_p</span> <span class="o">=</span> <span class="n">up</span><span class="o">.</span><span class="n">urlparse</span><span class="p">(</span><span class="n">post</span><span class="p">[</span><span class="s1">&#39;url&#39;</span><span class="p">])</span>
            <span class="n">host_url</span> <span class="o">=</span> <span class="s1">&#39;</span><span class="si">{0}</span><span class="s1">://</span><span class="si">{1}</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">url_p</span><span class="o">.</span><span class="n">scheme</span><span class="p">,</span> <span class="n">url_p</span><span class="o">.</span><span class="n">netloc</span><span class="p">)</span>
            <span class="k">for</span> <span class="n">image</span> <span class="ow">in</span> <span class="n">images</span><span class="p">:</span>
                <span class="n">image_url</span> <span class="o">=</span> <span class="n">image</span><span class="o">.</span><span class="n">attr</span><span class="o">.</span><span class="n">src</span>
                <span class="k">if</span> <span class="n">image_url</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;/&#39;</span><span class="p">:</span>
                    <span class="n">image_url</span> <span class="o">=</span> <span class="n">host_url</span> <span class="o">+</span> <span class="n">image_url</span>
                <span class="k">yield</span> <span class="p">{</span>
                    <span class="s1">&#39;title&#39;</span><span class="p">:</span> <span class="n">post</span><span class="p">[</span><span class="s1">&#39;title&#39;</span><span class="p">],</span>
                    <span class="s1">&#39;image&#39;</span><span class="p">:</span> <span class="n">image_url</span>
                <span class="p">}</span>
</code></pre></div><h3 id="保存图片">保存图片</h3>
<p>根据传入的图片信息中的链接获取图片内容，再根据内容生成图片名称，将图片保存在指定目录下对应的博文名称命名的文件夹中，实现过程与文章 <a href="/2018/06/21/spider-practice-pic-dog/">爬虫实践之头条狗狗图片</a> 中保存图片的思路一致：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">save_image_from</span><span class="p">(</span><span class="n">image_info</span><span class="p">,</span> <span class="n">to_dir</span><span class="o">=</span><span class="s1">&#39;&#39;</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;根据链接获取图片，保存到标题命名的目录中，图片以md5码命名
</span><span class="s1">    :param image_info: 包含标题和链接信息的字典数据
</span><span class="s1">    :type image_info: dict
</span><span class="s1">    :param to_dir: 要保存到的目录，默认值是空字符串，可指定目录，格式如: &#39;狗狗&#39;
</span><span class="s1">    :type to_dir: unicode 字符串
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="k">if</span> <span class="n">image_info</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">image_info</span><span class="p">)</span>
        <span class="n">image_dir</span> <span class="o">=</span> <span class="n">to_dir</span> <span class="o">+</span> <span class="s1">&#39;/&#39;</span> <span class="o">+</span> <span class="n">image_info</span><span class="p">[</span><span class="s1">&#39;title&#39;</span><span class="p">]</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">image_dir</span><span class="p">):</span>
            <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">image_dir</span><span class="p">)</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">image_info</span><span class="p">[</span><span class="s1">&#39;image&#39;</span><span class="p">])</span>
            <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
                <span class="n">image_path</span> <span class="o">=</span> <span class="s1">&#39;</span><span class="si">{0}</span><span class="s1">/</span><span class="si">{1}</span><span class="s1">.</span><span class="si">{2}</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">image_dir</span><span class="p">,</span> <span class="n">md5</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">content</span><span class="p">)</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">(),</span> <span class="s1">&#39;jpg&#39;</span><span class="p">)</span>
                <span class="nb">print</span><span class="p">(</span><span class="n">image_path</span><span class="p">)</span>
                <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">image_path</span><span class="p">):</span>
                    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">image_path</span><span class="p">,</span> <span class="s1">&#39;wb&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
                        <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
                    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;图片下载完成！&#39;</span><span class="p">)</span>
                <span class="k">else</span><span class="p">:</span>
                    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;图片已下载 -&gt; &#39;</span><span class="p">,</span> <span class="n">image_path</span><span class="p">)</span>
        <span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">ConnectionError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;图片下载失败！&#39;</span><span class="p">)</span>
        <span class="k">except</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;出现异常！&#39;</span><span class="p">)</span>
</code></pre></div><h3 id="命令参数解析">命令参数解析</h3>
<p>按照命令形式的方式构造脚本还需要对参数输入进行解析：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">parse_sys_args</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;解析命令参数
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">help_info</span> <span class="o">=</span> <span class="s1">&#39;&#39;&#39;</span><span class="se">\n</span><span class="s1">    使用方法：
</span><span class="s1">      </span><span class="si">%s</span><span class="s1"> 子命令 hexo博客归档地址 CSS选择器
</span><span class="s1">      - 子命令：count 和 pic，count用于得到指定博客中每个月份博文数，pic用于爬取博文中的图片
</span><span class="s1">      - hexo博客归档地址：比如http://www.litreily.top/archives/
</span><span class="s1">      - CSS选择器：用于选择文章列表中文章标题对应的&lt;a&gt;标签
</span><span class="s1">    举例：
</span><span class="s1">      </span><span class="si">%s</span><span class="s1"> count http://www.litreily.top/archives/ &#39;.post-archive .listing li a&#39;
</span><span class="s1">      </span><span class="si">%s</span><span class="s1"> pic http://www.smslit.top/archives/ &#39;#posts article .post-title-link&#39;
</span><span class="s1">
</span><span class="s1">    &#39;&#39;&#39;</span> <span class="o">%</span> <span class="p">(</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">4</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">help_info</span><span class="p">)</span>
        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">!=</span> <span class="s1">&#39;pic&#39;</span> <span class="ow">and</span> <span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">!=</span> <span class="s1">&#39;count&#39;</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;请输入正确子命令！&#39;</span><span class="p">)</span>
            <span class="nb">print</span><span class="p">(</span><span class="n">help_info</span><span class="p">)</span>
            <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
        <span class="k">return</span> <span class="p">(</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">args</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">args</span><span class="p">[</span><span class="mi">3</span><span class="p">])</span>
</code></pre></div><h3 id="主体执行过程">主体执行过程</h3>
<p>最后组织脚本，实现预期的功能，具体实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">command</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">css_selector</span> <span class="o">=</span> <span class="n">parse_sys_args</span><span class="p">()</span>
    <span class="n">archive</span> <span class="o">=</span> <span class="n">get_blog_posts</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">css_selector</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">archive</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">],</span> <span class="s1">&#39;一共有&#39;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">archive</span><span class="p">[</span><span class="s1">&#39;posts&#39;</span><span class="p">]),</span> <span class="s1">&#39;篇博文！&#39;</span><span class="p">)</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">archive</span><span class="p">[</span><span class="s1">&#39;posts&#39;</span><span class="p">])</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">command</span> <span class="o">==</span> <span class="s1">&#39;count&#39;</span><span class="p">:</span>
            <span class="n">plot_statistics</span><span class="p">(</span><span class="n">statistic_posts_by_date</span><span class="p">(</span><span class="n">archive</span><span class="p">[</span><span class="s1">&#39;posts&#39;</span><span class="p">]),</span> <span class="n">archive</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span><span class="o">+</span><span class="s1">&#39;</span><span class="se">\&#39;</span><span class="s1">s posts yearly&#39;</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">for</span> <span class="n">image</span> <span class="ow">in</span> <span class="n">get_post_images</span><span class="p">(</span><span class="n">archive</span><span class="p">[</span><span class="s1">&#39;posts&#39;</span><span class="p">]):</span>
                <span class="n">save_image_from</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="n">archive</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">])</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;没有发现博文，请确认您写的css选择器是否合理！&#39;</span><span class="p">)</span>
</code></pre></div><p>至此完成脚本文件，在这里十里给文件的命名是：<code>blogo</code>。</p>
<h2 id="测试功能">测试功能</h2>
<p>代码已经完成，分别测试两个子命令。</p>
<h4 id="博文统计">博文统计</h4>
<p>执行命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./blogo count http://www.litreily.top/archives/ <span class="s1">&#39;.post-archive .listing li a&#39;</span>
LITREILY 一共有 <span class="m">51</span> 篇博文！
</code></pre></div><p>得到统计结果：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180625152991020613192.png" alt="20180625152991020613192.png"></p>
<h4 id="爬取博文图片">爬取博文图片</h4>
<p>执行命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./blogo pic http://www.litreily.top/archives/ <span class="s1">&#39;.post-archive .listing li a&#39;</span>
LITREILY 一共有 <span class="m">51</span> 篇博文！
<span class="o">{</span><span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;Python网络爬虫4 - scrapy入门&#39;</span>, <span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://www.litreily.top/assets/spider/scrapy/scrapy.jpg&#39;</span><span class="o">}</span>
LITREILY/Python网络爬虫4 - scrapy入门/a9f6acf5b1893055a235b846caa3998e.jpg
图片下载完成！
<span class="o">{</span><span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;Python网络爬虫3 - 生产者消费者模型爬取某金融网站数据&#39;</span>, <span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://www.litreily.top/assets/spider/cfachina/home_page.png&#39;</span><span class="o">}</span>
LITREILY/Python网络爬虫3 - 生产者消费者模型爬取某金融网站数据/b911b25d568bef51de4e902c8e0fa725.jpg
图片下载完成！
<span class="o">{</span><span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;Python网络爬虫3 - 生产者消费者模型爬取某金融网站数据&#39;</span>, <span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://www.litreily.top/assets/spider/cfachina/personinfo.png&#39;</span><span class="o">}</span>
LITREILY/Python网络爬虫3 - 生产者消费者模型爬取某金融网站数据/9d598568290765c2fb19cc890d7e47d4.jpg
图片下载完成！
...
</code></pre></div><p>上面执行结果省略了大部分打印信息，执行完成后可以看到同级目录下出现了以博客名称命名的文件夹，里面的文件夹以博文标题命名，文件夹内存的就是相应博文的图片：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180625152991073857165.png" alt="20180625152991073857165.png"></p>
<p>完整代码参考：<a href="https://github.com/smslit/spider-collection/blob/master/blogo/blogo">blogo</a></p>]]></content>
		</item>
		
		<item>
			<title>爬虫基础之pyquery</title>
			<link>https://blog.5km.studio/2018/06/23/spider-basic-pyquery/</link>
			<pubDate>Sat, 23 Jun 2018 17:32:16 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/06/23/spider-basic-pyquery/</guid>
			<description>&lt;p&gt;爬虫程序中少不了解析库的使用，使用库来解析网页内容的话，效率会很高，常用的解析库有:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lxml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Beautiful Soup&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pyquery&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;十里对三个库都稍微了解了一下，个人比较喜欢&lt;code&gt;pyquery库&lt;/code&gt;，当然这只是个人喜好问题，无关优劣。所以本文中十里将与您一起学习&lt;code&gt;pyquery&lt;/code&gt;库的简单使用。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>爬虫程序中少不了解析库的使用，使用库来解析网页内容的话，效率会很高，常用的解析库有:</p>
<ul>
<li><code>lxml</code></li>
<li><code>Beautiful Soup</code></li>
<li><code>pyquery</code></li>
</ul>
<p>十里对三个库都稍微了解了一下，个人比较喜欢<code>pyquery库</code>，当然这只是个人喜好问题，无关优劣。所以本文中十里将与您一起学习<code>pyquery</code>库的简单使用。</p>
<h2 id="安装">安装</h2>
<p>安装很简单，这里使用<code>pip</code>包管理工具安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pip3 install pyquery
</code></pre></div><p><strong>注：</strong> pip有时会很慢，可以使用国内源加速，这个需要的话自行google。</p>
<h2 id="使用">使用</h2>
<h3 id="初始化内容">初始化内容</h3>
<p><code>pyquery</code>库可以打开本地html文件，可以初始化字符串，同样可以直接打开指定网址。</p>
<h4 id="初始化字符串">初始化字符串</h4>
<p>可以编写一个html字符串内容传给<code>PyQuery</code>，直接看代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">pyquery</span> <span class="kn">import</span> <span class="n">PyQuery</span> <span class="k">as</span> <span class="n">pq</span>

<span class="n">html</span> <span class="o">=</span> <span class="s1">&#39;&#39;&#39;&lt;html&gt;
</span><span class="s1">&lt;div&gt;
</span><span class="s1">&lt;p class=&#39;ppp&#39;&gt;hello, 5km!&lt;/p&gt;
</span><span class="s1">&lt;/div&gt;
</span><span class="s1">&lt;/html&gt;
</span><span class="s1">&#39;&#39;&#39;</span>

<span class="n">d</span> <span class="o">=</span> <span class="n">pq</span><span class="p">(</span><span class="n">html</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">d</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">d</span><span class="p">))</span>
</code></pre></div><p>运行结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">&lt;html&gt;
&lt;div&gt;
&lt;p <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;ppp&#34;</span>&gt;hello, 5km!&lt;/p&gt;
&lt;/div&gt;
&lt;/html&gt;
&lt;class <span class="s1">&#39;pyquery.pyquery.PyQuery&#39;</span>&gt;
</code></pre></div><h4 id="初始化文件">初始化文件</h4>
<p>将上述代码中的html内容写入文件<code>5km.html</code>，然后按照以下方式可以初始化html文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">pyquery</span> <span class="kn">import</span> <span class="n">PyQuery</span> <span class="k">as</span> <span class="n">pq</span>

<span class="n">d</span> <span class="o">=</span> <span class="n">pq</span><span class="p">(</span><span class="n">filename</span><span class="o">=</span><span class="s1">&#39;5km.html&#39;</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">d</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">d</span><span class="p">))</span>
</code></pre></div><p>执行代码后会发现结果与上述一样，说明成功初始化。</p>
<h4 id="初始化链接">初始化链接</h4>
<p>这里以<code>http://www.httpbin.org</code>为例：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">pyquery</span> <span class="kn">import</span> <span class="n">PyQuery</span> <span class="k">as</span> <span class="n">pq</span>

<span class="n">d</span> <span class="o">=</span> <span class="n">pq</span><span class="p">(</span><span class="n">url</span><span class="o">=</span><span class="s1">&#39;http://www.httpbin.org&#39;</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">d</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">d</span><span class="p">))</span>
</code></pre></div><p>结果就会返回上述网址对应的html文件内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">&lt;html <span class="nv">lang</span><span class="o">=</span><span class="s2">&#34;en&#34;</span>&gt;
&lt;head&gt;
  &lt;meta <span class="nv">charset</span><span class="o">=</span><span class="s2">&#34;UTF-8&#34;</span>/&gt;
  &lt;title&gt;httpbin.org&lt;/title&gt;
  &lt;link <span class="nv">href</span><span class="o">=</span><span class="s2">&#34;https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700&#34;</span> <span class="nv">rel</span><span class="o">=</span><span class="s2">&#34;stylesheet&#34;</span>/&gt;
...这里省略部分内容
...
&lt;/html&gt;
&lt;class <span class="s1">&#39;pyquery.pyquery.PyQuery&#39;</span>&gt;
</code></pre></div><h3 id="css选择器">CSS选择器</h3>
<p><code>pyquery</code>支持基本的<a href="http://www.w3school.com.cn/cssref/css_selectors.asp">css选择器</a>，可以按照css选择器的规则找到相应节点，为了方便展示后面的使用，从这儿开始使用文本方式初始化<code>PyQuery</code>，使用网上常规html范例文本初始化：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">pyquery</span> <span class="kn">import</span> <span class="n">PyQuery</span> <span class="k">as</span> <span class="n">pq</span>

<span class="n">html</span> <span class="o">=</span> <span class="s1">&#39;&#39;&#39;&lt;div&gt;
</span><span class="s1">&lt;ul id = &#39;5km&#39;&gt;
</span><span class="s1">&lt;li class=&#34;item-0&#34;&gt;first item&lt;/li&gt;
</span><span class="s1">&lt;li class=&#34;item-1&#34;&gt;&lt;a href=&#34;link2.html&#34;&gt;second item&lt;/a&gt;&lt;/li&gt;
</span><span class="s1">&lt;li class=&#34;item-0 active&#34;&gt;&lt;a href=&#34;link3.html&#34;&gt;&lt;span class=&#34;bold&#34;&gt;third item&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
</span><span class="s1">&lt;li class=&#34;item-1 active&#34;&gt;&lt;a href=&#34;link4.html&#34;&gt;fourth item&lt;/a&gt;&lt;/li&gt;
</span><span class="s1">&lt;li class=&#34;item-0&#34;&gt;&lt;a href=&#34;link5.html&#34;&gt;fifth item&lt;/a&gt;&lt;/li&gt;
</span><span class="s1">&lt;/ul&gt;
</span><span class="s1">&lt;/div&gt;
</span><span class="s1">&#39;&#39;&#39;</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">pq</span><span class="p">(</span><span class="n">html</span><span class="p">)</span>
</code></pre></div><p>如果想获得<strong>class</strong>为 <strong>item-0</strong> 的<strong>li</strong>，可以：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">result</span> <span class="o">=</span> <span class="n">d</span><span class="p">(</span><span class="s1">&#39;#5km .item-0&#39;</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">result</span><span class="p">))</span>
</code></pre></div><p>运行结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0&#34;</span>&gt;first item&lt;/li&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0 active&#34;</span>&gt;&lt;a <span class="nv">href</span><span class="o">=</span><span class="s2">&#34;link3.html&#34;</span>&gt;&lt;span <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;bold&#34;</span>&gt;third item&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0&#34;</span>&gt;&lt;a <span class="nv">href</span><span class="o">=</span><span class="s2">&#34;link5.html&#34;</span>&gt;fifth item&lt;/a&gt;&lt;/li&gt;

&lt;class <span class="s1">&#39;pyquery.pyquery.PyQuery&#39;</span>&gt;
</code></pre></div><p>尝试其它的选择器使用，非常方便！</p>
<h3 id="遍历节点">遍历节点</h3>
<p>如上面CSS选择器的例子，会选择出三个符合条件的，那我们能不能遍历它们逐一做一些处理呢？当然是没问题了。返回结果会有一个<strong>items</strong>方法，此方法会得到一个结果对应的生成器，就可以使用<code>for-in</code>进行遍历结果中的每一条<code>li</code>节点了，使用方法如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">item</span><span class="p">))</span>
</code></pre></div><p>结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0&#34;</span>&gt;first item&lt;/li&gt;
&lt;class <span class="s1">&#39;pyquery.pyquery.PyQuery&#39;</span>&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0 active&#34;</span>&gt;&lt;a <span class="nv">href</span><span class="o">=</span><span class="s2">&#34;link3.html&#34;</span>&gt;&lt;span <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;bold&#34;</span>&gt;third item&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;class <span class="s1">&#39;pyquery.pyquery.PyQuery&#39;</span>&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0&#34;</span>&gt;&lt;a <span class="nv">href</span><span class="o">=</span><span class="s2">&#34;link5.html&#34;</span>&gt;fifth item&lt;/a&gt;&lt;/li&gt;
&lt;class <span class="s1">&#39;pyquery.pyquery.PyQuery&#39;</span>&gt;
</code></pre></div><h3 id="查询节点">查询节点</h3>
<p>在上面例子中均打印了结果的类型，它们都是<code>PyQuery</code>对象，包含了节点信息。它们具有查询节点的方法，可供获取子孙节点、父节点和兄弟节点。</p>
<h4 id="子孙节点">子孙节点</h4>
<p>子孙节点的查询可以使用<code>find</code>和<code>children</code>两种方法，不同点是，前者可以得到所有子孙节点，而后者只关心子节点信息。为了区分这两种的效果，这里稍微改动一下<code>html</code>内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">pyquery</span> <span class="kn">import</span> <span class="n">PyQuery</span> <span class="k">as</span> <span class="n">pq</span>

<span class="n">html</span> <span class="o">=</span> <span class="s1">&#39;&#39;&#39;&lt;div&gt;
</span><span class="s1">&lt;div id=&#34;container&#34;&gt;
</span><span class="s1">&lt;ul id=&#39;5km&#39;&gt;
</span><span class="s1">&lt;li class=&#34;item-0&#34;&gt;first item&lt;/li&gt;
</span><span class="s1">&lt;li class=&#34;item-ol&#34;&gt;
</span><span class="s1">&lt;ol class=&#39;test&#39;&gt;
</span><span class="s1">&lt;li&gt;ol1&lt;/li&gt;
</span><span class="s1">&lt;li&gt;ol2&lt;/li&gt;
</span><span class="s1">&lt;/ol&gt;
</span><span class="s1">&lt;/li&gt;
</span><span class="s1">&lt;/ul&gt;
</span><span class="s1">&lt;/div&gt;
</span><span class="s1">&lt;/ddiv&gt;
</span><span class="s1">&#39;&#39;&#39;</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">pq</span><span class="p">(</span><span class="n">html</span><span class="p">)</span>
</code></pre></div><h5 id="所有子孙节点">所有子孙节点</h5>
<p>使用<code>find</code>方法查询<code>ul</code> 下的 <code>li</code>节点，实现代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">ul</span> <span class="o">=</span> <span class="n">d</span><span class="p">(</span><span class="s1">&#39;#5km&#39;</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">ul</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s1">&#39;li&#39;</span><span class="p">))</span>
</code></pre></div><p>运行结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0&#34;</span>&gt;first item&lt;/li&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-ol&#34;</span>&gt;
&lt;ol <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;test&#34;</span>&gt;
&lt;li&gt;ol1&lt;/li&gt;
&lt;li&gt;ol2&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;ol1&lt;/li&gt;
&lt;li&gt;ol2&lt;/li&gt;
</code></pre></div><p>看到结果就明白了，此方法把<code>ul</code>第二个直接子节点下的两个<code>li</code>节点也查询并入查询结果了。下面我们看一下<code>children</code>方法的效果。</p>
<h5 id="直接子节点">直接子节点</h5>
<p>利用<code>children</code>方法查询<code>ul</code>下的<code>li</code>节点，当然也可以使用CSS选择器：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="nb">print</span><span class="p">(</span><span class="n">ul</span><span class="o">.</span><span class="n">children</span><span class="p">(</span><span class="s1">&#39;li&#39;</span><span class="p">))</span>
</code></pre></div><p>运行结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0&#34;</span>&gt;first item&lt;/li&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-ol&#34;</span>&gt;
&lt;ol <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;test&#34;</span>&gt;
&lt;li&gt;ol1&lt;/li&gt;
&lt;li&gt;ol2&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
</code></pre></div><p>不出所料，结果中只有直接子节点，其实<code>children</code>方法可以不加参数，就会返回所有的直接子节点。</p>
<h4 id="祖先节点">祖先节点</h4>
<p>按照继承的思路，查询祖先节点就是找父节点、父节点的父节点等等一直按照继承关系向上查询节点。有两个方法：<code>parent</code>和 <code>parents</code>，前者用来查找直接父节点，后者用来查询所有祖先节点。继续使用前面的<code>d。</code></p>
<h5 id="直接父节点">直接父节点</h5>
<p>直接父节点可以通过<code>parent</code>方法进行查询：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="nb">print</span><span class="p">(</span><span class="n">ul</span><span class="o">.</span><span class="n">parent</span><span class="p">())</span>
</code></pre></div><p>运行结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">&lt;div <span class="nv">id</span><span class="o">=</span><span class="s2">&#34;container&#34;</span>&gt;
&lt;ul <span class="nv">id</span><span class="o">=</span><span class="s2">&#34;5km&#34;</span>&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0&#34;</span>&gt;first item&lt;/li&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-ol&#34;</span>&gt;
&lt;ol <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;test&#34;</span>&gt;
&lt;li&gt;ol1&lt;/li&gt;
&lt;li&gt;ol2&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</code></pre></div><p>此方法直接返回了<code>ul</code>节点的直接父节点<code>&lt;div id='container'</code>的内容。</p>
<h5 id="所有祖先节点">所有祖先节点</h5>
<p><code>parents</code>方法可以查询所有祖先节点，可以使用CSS选择器，也可以不加参数使用：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="nb">print</span><span class="p">(</span><span class="n">ul</span><span class="o">.</span><span class="n">parents</span><span class="p">(</span><span class="s1">&#39;div&#39;</span><span class="p">))</span>
</code></pre></div><p>查询祖先节点中所有的<code>div</code>节点，返回结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">&lt;div&gt;
&lt;div <span class="nv">id</span><span class="o">=</span><span class="s2">&#34;container&#34;</span>&gt;
&lt;ul <span class="nv">id</span><span class="o">=</span><span class="s2">&#34;5km&#34;</span>&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0&#34;</span>&gt;first item&lt;/li&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-ol&#34;</span>&gt;
&lt;ol <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;test&#34;</span>&gt;
&lt;li&gt;ol1&lt;/li&gt;
&lt;li&gt;ol2&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;/div&gt;&lt;div <span class="nv">id</span><span class="o">=</span><span class="s2">&#34;container&#34;</span>&gt;
&lt;ul <span class="nv">id</span><span class="o">=</span><span class="s2">&#34;5km&#34;</span>&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0&#34;</span>&gt;first item&lt;/li&gt;
&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-ol&#34;</span>&gt;
&lt;ol <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;test&#34;</span>&gt;
&lt;li&gt;ol1&lt;/li&gt;
&lt;li&gt;ol2&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</code></pre></div><p>可以看到得到了两层<code>div</code>节点的内容。</p>
<h4 id="兄弟节点">兄弟节点</h4>
<p>兄弟节点的获取需要使用方法<code>siblings</code>，首先我们获取<code>class</code>为<code>item-ol</code>的<code>li</code>节点，然后调用<code>siblings</code>方法找到所有兄弟节点：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">li</span> <span class="o">=</span> <span class="n">d</span><span class="p">(</span><span class="s1">&#39;#5km .item-ol&#39;</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">li</span><span class="o">.</span><span class="n">siblings</span><span class="p">())</span>
</code></pre></div><p>运行结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">&lt;li <span class="nv">class</span><span class="o">=</span><span class="s2">&#34;item-0&#34;</span>&gt;first item&lt;/li&gt;
</code></pre></div><p><code>class</code>为<code>item-0</code>的节点即为结果。</p>
<h3 id="获取节点信息">获取节点信息</h3>
<p>节点信息一般就是节点文本和节点的属性了，分别进行探索。</p>
<h4 id="节点文本">节点文本</h4>
<p>节点文本的获取有两种方法：</p>
<ul>
<li><code>text</code>：获取节点内所有文本及子孙节点的文本内容；</li>
<li><code>html</code>：获取节点的html文本；</li>
</ul>
<p>这里以<code>class</code>为<code>test</code>的<code>ol</code>节点为例说明两个方法的使用：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">ol</span> <span class="o">=</span> <span class="n">d</span><span class="p">(</span><span class="s1">&#39;#5km .item-ol .test&#39;</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">ol</span><span class="o">.</span><span class="n">text</span><span class="p">())</span>
<span class="nb">print</span><span class="p">(</span><span class="n">ol</span><span class="o">.</span><span class="n">html</span><span class="p">())</span>
</code></pre></div><p>运行结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">ol1
ol2

&lt;li&gt;ol1&lt;/li&gt;
&lt;li&gt;ol2&lt;/li&gt;
</code></pre></div><p>可以看到按照预期结果分别得到了所有文本信息和html文本。</p>
<h4 id="节点属性">节点属性</h4>
<p>有两种方法可以获取节点属性：</p>
<ul>
<li>使用节点的<code>attr</code>方法，参数传入待获取属性的名称，比如<code>class</code>、<code>id</code>、<code>href</code>等，形如<code>attr('class')</code>；</li>
<li>使用节点<code>attr</code>属性的属性，点语法获取，形如<code>attr.href</code>，这里要查询<code>class</code>属性的值比较特殊，因为与python的关键词冲突，所以以此方法查询节点的<code>class</code>属性的话，应该以<code>attr.class_</code>的形式访问查询；</li>
<li>作为字典的形式访问，以属性名作为键值访问，形如<code>attr['class']</code></li>
</ul>
<p>举例说明：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="nb">print</span><span class="p">(</span><span class="n">ol</span><span class="o">.</span><span class="n">attr</span><span class="p">(</span><span class="s1">&#39;class&#39;</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="n">ol</span><span class="o">.</span><span class="n">attr</span><span class="o">.</span><span class="n">class_</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">ol</span><span class="o">.</span><span class="n">attr</span><span class="p">[</span><span class="s1">&#39;class&#39;</span><span class="p">])</span>
</code></pre></div><p>运行结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="nb">test</span>
<span class="nb">test</span>
<span class="nb">test</span>
</code></pre></div><p>上面用三种形式查询了<code>ol</code>节点的<code>class</code>属性。</p>
<h2 id="总结">总结</h2>
<p>本文主要一起探索了一下<code>pyquery</code>使用的冰山一角，主要集中的是初始化和读取的操作，其实还有修改节点的功能，因为爬虫程序中大部分还是只用查询解析功能，所以本文只简单列举了节点解析读取功能的具体操作，后面如果有新的总结会持续更新。</p>]]></content>
		</item>
		
		<item>
			<title>爬虫实践之头条狗狗图片</title>
			<link>https://blog.5km.studio/2018/06/21/spider-practice-pic-dog/</link>
			<pubDate>Thu, 21 Jun 2018 14:41:32 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/06/21/spider-practice-pic-dog/</guid>
			<description>&lt;p&gt;最近在学习爬虫，学了就应该多实践实践，本文将记录使用&lt;code&gt;requests&lt;/code&gt; 库下载头条上搜到的狗狗图片的实现过程，使用&lt;strong&gt;python3&lt;/strong&gt;实现。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>最近在学习爬虫，学了就应该多实践实践，本文将记录使用<code>requests</code> 库下载头条上搜到的狗狗图片的实现过程，使用<strong>python3</strong>实现。</p>
<h2 id="前言">前言</h2>
<p>我们知道现在有部分网页访问的时候返回的并不是完整的html，其利用javascript获取局部数据最终实现完整页面的呈现，而这依赖的是<code>AJAX</code>技术：</p>
<blockquote>
<p>AJAX，即“Asynchronous Javascript And XML”（异步 JavaScript 和 XML），是一种创建交互式网页应用的网页开发技术。</p>
</blockquote>
<blockquote>
<p>通过在后台与服务器进行少量数据交换，AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下，对网页的某部分进行更新。</p>
</blockquote>
<blockquote>
<p>@W3Cschool <a href="https://www.w3cschool.cn/ajax/nr583fns.html">AJAX简介</a></p>
</blockquote>
<p>而头条的搜图页面也是采用 <code>AJAX</code> 技术，下面分析一下网页。</p>
<h2 id="网页分析">网页分析</h2>
<p>首先，浏览器（本文使用的是<a href="https://www.google.cn/chrome/">google chrome</a>）打开头条图片频道的网址：<a href="https://www.toutiao.com/ch/news_image/">https://www.toutiao.com/ch/news_image </a>。打开页面会在右上角看到搜索框，在搜索框中输入<code>狗狗</code>，进行搜索，就会得到下面的页面：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/2018062115295708613054.png" alt="search0"></p>
<p>右击页面选择<code>检查</code>，就会出现一个网页检查窗口，有很多标签页，依次找到<code>Network</code>-<code>XHR</code>，此时点击上图页面中的<code>图集</code>，就会发现网页检查窗口中左栏出现一个<code>XHR</code>，如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180622152964929637874.png" alt="20180622152964929637874.png"></p>
<p>点击图中圈出的XHR，就会出现此条AJAX请求的详细内容，选择<code>Headers</code> 标签页：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180621152957098380206.png" alt="XHR-detail"></p>
<p>可以看到有请求头和响应头的信息，而 <code>Preview</code> 标签页内显示的则是请求结果，很明显的响应结果是个json格式的内容，chrome已经清晰的排版了json内容，对照网页呈现的内容查看一下json内容。</p>
<h3 id="响应结果分析">响应结果分析</h3>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180621152957267413410.png" alt="json"></p>
<p>按照层级查看json内容，展开 <code>data</code> 就会发现有20个内容，正好与网页中显示的20个条目对应，展开 <code>0</code> 查看一下，可以看到两个关键信息：</p>
<ul>
<li><code>title</code>：对应网页条目的标题</li>
<li><code>image_list</code>: 对应网页中图片的链接</li>
</ul>
<p>查看 <code>data</code> 中的其它项，会发现都是与网页显示内容的条目一一对应，这样就一目了然了，可以通过AJAX请求获取json内容，然后解析json内容中的<code>data</code>里每个条目里的 <code>title</code>和<code>image_list</code>就可以得到图片链接了，这样就可以根据链接获取图片了，那么还得分析一下AJAX请求有什么规律。</p>
<h3 id="请求头分析">请求头分析</h3>
<p>切换到<code>Headers</code> 标签页，可以看到<code>Requests URL</code>为：</p>
<ul>
<li><a href="https://www.toutiao.com/search_content/?offset=0&amp;format=json&amp;keyword=%E7%8B%97%E7%8B%97&amp;autoload=true&amp;count=20&amp;cur_tab=3&amp;from=gallery">https://www.toutiao.com/search_content/?offset=0&amp;format=json&amp;keyword=%E7%8B%97%E7%8B%97&amp;autoload=true&amp;count=20&amp;cur_tab=3&amp;from=gallery</a></li>
</ul>
<p>滚动网页到最底以后，网页会增加20个新的条目，这时候会发现出现了一个新的 <code>XHR</code>，点击这个新的请求，查看头信息中的 <code>Requests URL</code>为：</p>
<ul>
<li><a href="https://www.toutiao.com/search_content/?offset=20&amp;format=json&amp;keyword=%E7%8B%97%E7%8B%97&amp;autoload=true&amp;count=20&amp;cur_tab=3&amp;from=gallery">https://www.toutiao.com/search_content/?offset=20&amp;format=json&amp;keyword=%E7%8B%97%E7%8B%97&amp;autoload=true&amp;count=20&amp;cur_tab=3&amp;from=gallery</a></li>
</ul>
<p>同时查看 <code>Preview</code> 标签页里的json数据，会发现与网页中新增加的20个条目是对应的，同理在继续滚动加载，又会多一个XHR，响应的json与新增加的条目对应。</p>
<p>对比每次增加的<code>XHR</code>中的请求链接，会发现链接只有一个 <code>offset</code> 参数在变，一个比一个多20，不难想20其实就是对应了 <code>count</code> 参数，其中还有一个 <code>keyword</code> 参数，对应的汉字其实就是 <code>狗狗</code>，那么看到这儿也就明白了，请求链接可以通过改变 <code>offset</code> 参数来生成，请求回来的json数据中就包含了对应offset下的20条数据，然后解析数据得到相应标题下的所有图片链接就可以了。</p>
<h2 id="爬虫实现">爬虫实现</h2>
<p>根据对网页的分析，就可以针对性的设计爬虫程序了。</p>
<h3 id="爬虫思路">爬虫思路</h3>
<p>根据上一节的分析，可以分三步实现图片的爬取：</p>
<ol>
<li>按照上一节分析的结果按照索引生成请求链接，请求网页获取响应结果；</li>
<li>从响应结果中提取关键信息：</li>
</ol>
<ul>
<li>标题</li>
<li>图片链接</li>
</ul>
<ol start="3">
<li>根据图片链接获取图片，并保存到以标题进行命名的目录中；</li>
</ol>
<h3 id="库的选择">库的选择</h3>
<p>首先要生成请求链接，观察链接格式，可以选择库 <code>urllib</code>parse模块的方法 <code>urlencode</code>方法生成，然后选择 <code>requests</code> 库进行网页请求，返回的结果是json，其实对应的是字典数据，提取信息不需要额外的库，同样使用 <code>requests</code> 库请求图片链接获取图片，为了好管理图片还得生成目录，需要使用 <code>os</code>库。本地保存的话，为了防止命名冲突，这里根据图片数据的md5码命名，那么会用到 <code>hashlib</code> 库，总结会用到以下库和方法：</p>
<ul>
<li><code>requests</code></li>
<li><code>urllib.parse</code>的<code>urlencode</code></li>
<li><code>hashlib</code>的<code>md5</code></li>
<li><code>os</code></li>
</ul>
<p>上述库中，只需要安装<code>requests</code>和<code>urllib</code>这两个库，一般通过pip3安装。</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ pip3 install requests urllib
</code></pre></div><h3 id="爬虫实现-1">爬虫实现</h3>
<p>首先将上述库和方法导入。</p>
<div class="highlight"><pre class="chroma"><code class="language-py" data-lang="py"><span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">from</span> <span class="nn">urllib.parse</span> <span class="kn">import</span> <span class="n">urlencode</span>
<span class="kn">from</span> <span class="nn">hashlib</span> <span class="kn">import</span> <span class="n">md5</span>
<span class="kn">import</span> <span class="nn">os</span>
</code></pre></div><h4 id="请求页面内容">请求页面内容</h4>
<p>根据前面的分析，只需要更改网页链接中的 <code>offset</code> 参数就可以生成新的请求链接，为了提高封装性，这里将网页内容的获取过程定义为一个函数，参数为 <code>offset</code>，这里需要注意的是，为了防止被头条屏蔽IP或拒绝访问，这里还需要给出一定头信息，参考chrome检查窗口中看到的请求头的信息即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">HEADERS</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s1">&#39;Host&#39;</span><span class="p">:</span> <span class="s1">&#39;www.toutiao.com&#39;</span><span class="p">,</span>
    <span class="s1">&#39;User-Agent&#39;</span><span class="p">:</span> <span class="s1">&#39;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15&#39;</span><span class="p">,</span>
    <span class="s1">&#39;X-Requested-With&#39;</span><span class="p">:</span> <span class="s1">&#39;XMLHttpRequest&#39;</span>
<span class="p">}</span>
</code></pre></div><p>请求链接中的参数使用<code>urlencode</code>方法进行封装处理，根据链接特征需要指定参数如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">params</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s1">&#39;offset&#39;</span><span class="p">:</span> <span class="n">offset</span><span class="p">,</span>
    <span class="s1">&#39;format&#39;</span><span class="p">:</span> <span class="s1">&#39;json&#39;</span><span class="p">,</span>
    <span class="s1">&#39;keyword&#39;</span><span class="p">:</span> <span class="s1">&#39;狗狗&#39;</span><span class="p">,</span>
    <span class="s1">&#39;autoload&#39;</span><span class="p">:</span> <span class="s1">&#39;true&#39;</span><span class="p">,</span>
    <span class="s1">&#39;count&#39;</span><span class="p">:</span> <span class="s1">&#39;20&#39;</span><span class="p">,</span>
    <span class="s1">&#39;cur_tab&#39;</span><span class="p">:</span> <span class="s1">&#39;3&#39;</span><span class="p">,</span>
    <span class="s1">&#39;from&#39;</span><span class="p">:</span> <span class="s1">&#39;gallery&#39;</span>
<span class="p">}</span>
</code></pre></div><p>为了提高程序的鲁棒性，使用try-except处理异常，最终实现的函数如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">get_page_json</span><span class="p">(</span><span class="n">offset</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;生成ajax请求，发出get请求，并获取响应结果，以字典的形式返回json数据
</span><span class="s1">    :params offset: 页面请求偏移值
</span><span class="s1">    :type offset: 能被20整除的整数
</span><span class="s1">    :return: 响应数据
</span><span class="s1">    :rtype: dict
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">params</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s1">&#39;offset&#39;</span><span class="p">:</span> <span class="n">offset</span><span class="p">,</span>
        <span class="s1">&#39;format&#39;</span><span class="p">:</span> <span class="s1">&#39;json&#39;</span><span class="p">,</span>
        <span class="s1">&#39;keyword&#39;</span><span class="p">:</span> <span class="s1">&#39;狗狗&#39;</span><span class="p">,</span>
        <span class="s1">&#39;autoload&#39;</span><span class="p">:</span> <span class="s1">&#39;true&#39;</span><span class="p">,</span>
        <span class="s1">&#39;count&#39;</span><span class="p">:</span> <span class="s1">&#39;20&#39;</span><span class="p">,</span>
        <span class="s1">&#39;cur_tab&#39;</span><span class="p">:</span> <span class="s1">&#39;3&#39;</span><span class="p">,</span>
        <span class="s1">&#39;from&#39;</span><span class="p">:</span> <span class="s1">&#39;gallery&#39;</span>
    <span class="p">}</span>

    <span class="n">url</span> <span class="o">=</span> <span class="s1">&#39;https://www.toutiao.com/search_content/?&#39;</span> <span class="o">+</span> <span class="n">urlencode</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">HEADERS</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
    <span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">ConnectionError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Error&#39;</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
</code></pre></div><p>使用get方法进行请求，如果正常的话，就会得到响应的json数据，最终函数返回json数据转换成的字典数据，如果出现异常就会返回<code>None</code>。</p>
<h4 id="解析响应结果">解析响应结果</h4>
<p>上一步获得了一次请求的json数据，下面就该实现响应结果的解析了，因为得到的是字典，所以首先得到<code>data</code>键对应的值，是一个列表，对应包含20个条目的数据，遍历列表中的元素，每个元素都是字典，找到<code>title</code>键对应的值就是标题，<code>image_list</code>键对应的值是一个列表，列表中的元素就是链接关键信息，为链接信息加上协议组成正常的url字符串即可，将解析出来的每个链接和上层的标题共同组成一个字典返回，这里用<code>yield</code>方法生成迭代器，最终实现的函数如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">parse_images_url</span><span class="p">(</span><span class="n">json</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;解析json字典，获得对应条目标题和图片链接
</span><span class="s1">    :param json: 页面请求响应结果对应的字典数据
</span><span class="s1">    :type json: dict
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="p">[</span><span class="s1">&#39;data&#39;</span><span class="p">]</span>
    <span class="k">if</span> <span class="n">data</span><span class="p">:</span>
        <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
            <span class="n">title</span> <span class="o">=</span> <span class="n">item</span><span class="p">[</span><span class="s1">&#39;title&#39;</span><span class="p">]</span>
            <span class="n">images</span> <span class="o">=</span> <span class="n">item</span><span class="p">[</span><span class="s1">&#39;image_list&#39;</span><span class="p">]</span>
            <span class="k">if</span> <span class="n">images</span><span class="p">:</span>
                <span class="k">for</span> <span class="n">image</span> <span class="ow">in</span> <span class="n">images</span><span class="p">:</span>
                    <span class="k">yield</span> <span class="p">{</span>
                        <span class="s1">&#39;image&#39;</span><span class="p">:</span> <span class="s1">&#39;http:&#39;</span> <span class="o">+</span> <span class="n">image</span><span class="p">[</span><span class="s1">&#39;url&#39;</span><span class="p">],</span>
                        <span class="s1">&#39;title&#39;</span><span class="p">:</span> <span class="n">title</span>
                    <span class="p">}</span>
</code></pre></div><h4 id="下载图片">下载图片</h4>
<p>得到了图片链接，那么我们就可以保存图片了，同样将保存图片的操作封装为一个函数。</p>
<p>思路很简单，为了更好的管理图片，生成以标题命名的目录用来保存图片，get请求图片链接，因为获取的是二进制数据，所以将响应结果按照二进制保存为文件，文件名则根据二进制数据计算md5生成文件名，实现中加入了多个信息判断以及try-except方式的处理，最终结果如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">save_image_from</span><span class="p">(</span><span class="n">image_info</span><span class="p">,</span> <span class="n">to_dir</span><span class="o">=</span><span class="s1">&#39;&#39;</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;根据链接获取图片，保存到标题命名的目录中，图片以md5码命名
</span><span class="s1">    :param image_info: 包含标题和链接信息的字典数据
</span><span class="s1">    :type image_info: dict
</span><span class="s1">    :param to_dir: 要保存到的目录，默认值是空字符串，可指定目录，格式如: &#39;狗狗&#39;
</span><span class="s1">    :type to_dir: unicode 字符串
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="k">if</span> <span class="n">image_info</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">image_info</span><span class="p">)</span>
        <span class="n">image_dir</span> <span class="o">=</span> <span class="n">to_dir</span> <span class="o">+</span> <span class="s1">&#39;/&#39;</span> <span class="o">+</span> <span class="n">image_info</span><span class="p">[</span><span class="s1">&#39;title&#39;</span><span class="p">]</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">image_dir</span><span class="p">):</span>
            <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">image_dir</span><span class="p">)</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">image_info</span><span class="p">[</span><span class="s1">&#39;image&#39;</span><span class="p">])</span>
            <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
                <span class="n">image_path</span> <span class="o">=</span> <span class="s1">&#39;</span><span class="si">{0}</span><span class="s1">/</span><span class="si">{1}</span><span class="s1">.</span><span class="si">{2}</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">image_dir</span><span class="p">,</span> <span class="n">md5</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">content</span><span class="p">)</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">(),</span> <span class="s1">&#39;jpg&#39;</span><span class="p">)</span>
                <span class="nb">print</span><span class="p">(</span><span class="n">image_path</span><span class="p">)</span>
                <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">image_path</span><span class="p">):</span>
                    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">image_path</span><span class="p">,</span> <span class="s1">&#39;wb&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
                        <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
                    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;图片下载完成！&#39;</span><span class="p">)</span>
                <span class="k">else</span><span class="p">:</span>
                    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;图片已下载 -&gt; &#39;</span><span class="p">,</span> <span class="n">image_path</span><span class="p">)</span>
        <span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">ConnectionError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;图片下载失败！&#39;</span><span class="p">)</span>
        <span class="k">except</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;出现异常！&#39;</span><span class="p">)</span>
</code></pre></div><p>可以看到函数还多了一个参数<code>to_dir</code>，这个参数用来指定目录，用来保存以标题命名的目录以及其目录下的图片。</p>
<h4 id="编写主函数实现">编写主函数实现</h4>
<p>每一次请求会收到20个条目的数据，这里我们以请求10次为例，也就是总共获取200个条目下的图片，实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="c1"># 请求数</span>
<span class="n">PAGE_NUM</span> <span class="o">=</span> <span class="mi">10</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="k">for</span> <span class="n">offset</span> <span class="ow">in</span> <span class="p">[</span><span class="n">x</span> <span class="o">*</span> <span class="mi">20</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">PAGE_NUM</span><span class="p">)]:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;获取第&#39;</span><span class="p">,</span> <span class="n">offset</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">&#39;～&#39;</span><span class="p">,</span> <span class="n">offset</span> <span class="o">+</span> <span class="mi">20</span><span class="p">,</span> <span class="s1">&#39;个条目...&#39;</span><span class="p">)</span>
        <span class="n">json</span> <span class="o">=</span> <span class="n">get_page_json</span><span class="p">(</span><span class="n">offset</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">image</span> <span class="ow">in</span> <span class="n">parse_images_url</span><span class="p">(</span><span class="n">json</span><span class="p">):</span>
            <span class="n">save_image_from</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="n">to_dir</span><span class="o">=</span><span class="s1">&#39;狗狗&#39;</span><span class="p">)</span>
</code></pre></div><h4 id="整合实现">整合实现</h4>
<p>将上述几部分的说明和实现进行整合：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/local/bin/python3</span>
<span class="c1"># 确认上面的python3路径是否正确</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">from</span> <span class="nn">urllib.parse</span> <span class="kn">import</span> <span class="n">urlencode</span>
<span class="kn">from</span> <span class="nn">hashlib</span> <span class="kn">import</span> <span class="n">md5</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="n">PAGE_NUM</span> <span class="o">=</span> <span class="mi">10</span>

<span class="n">HEADERS</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s1">&#39;User-Agent&#39;</span><span class="p">:</span> <span class="s1">&#39;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15&#39;</span><span class="p">,</span>
    <span class="s1">&#39;X-Requested-With&#39;</span><span class="p">:</span> <span class="s1">&#39;XMLHttpRequest&#39;</span>
<span class="p">}</span>

<span class="k">def</span> <span class="nf">get_page_json</span><span class="p">(</span><span class="n">offset</span><span class="p">):</span>
    <span class="c1"># 此处省略，参考前面的实现</span>

<span class="k">def</span> <span class="nf">parse_images_url</span><span class="p">(</span><span class="n">json</span><span class="p">):</span>
    <span class="c1"># 此处省略，参考前面的实现</span>

<span class="k">def</span> <span class="nf">save_image_from</span><span class="p">(</span><span class="n">image_info</span><span class="p">,</span> <span class="n">to_dir</span><span class="o">=</span><span class="s1">&#39;&#39;</span><span class="p">):</span>
    <span class="c1"># 此处省略，参考前面的实现</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="c1"># 此处省略，参考前面的实现</span>
</code></pre></div><h2 id="测试及优化">测试及优化</h2>
<p>上述代码在脚本文件<code>toutiao_pic.py</code>中实现，执行脚本测试一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ python3 toutiao_pic.py
获取第 <span class="m">1</span> ～ <span class="m">20</span> 个条目...
<span class="o">{</span><span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://p3.pstatp.com/list/pgc-image/15296042422187b4c0bfb38&#39;</span>, <span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;史上最美的11对狗狗的结婚照，一张你都没见过！&#39;</span><span class="o">}</span>
狗狗/史上最美的11对狗狗的结婚照，一张你都没见过！/f56a49c55933be46f87f554d961841a9.jpg
图片下载完成！
<span class="o">{</span><span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://p3.pstatp.com/list/pgc-image/152960424260924673d65ea&#39;</span>, <span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;史上最美的11对狗狗的结婚照，一张你都没见过！&#39;</span><span class="o">}</span>
狗狗/史上最美的11对狗狗的结婚照，一张你都没见过！/ca6973e7d891a48ade41477ef3944627.jpg
图片下载完成！
<span class="o">{</span><span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://p3.pstatp.com/list/pgc-image/15296042427877f2cd0e93a&#39;</span>, <span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;史上最美的11对狗狗的结婚照，一张你都没见过！&#39;</span><span class="o">}</span>
狗狗/史上最美的11对狗狗的结婚照，一张你都没见过！/5317c8b27494c21f8e05936fd092c580.jpg
图片下载完成！
<span class="o">{</span><span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://p3.pstatp.com/list/pgc-image/1529604243143d15de20946&#39;</span>, <span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;史上最美的11对狗狗的结婚照，一张你都没见过！&#39;</span><span class="o">}</span>
狗狗/史上最美的11对狗狗的结婚照，一张你都没见过！/d83f8de993ccba427d62138812cbc9bf.jpg
图片下载完成！
<span class="o">{</span><span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://p1.pstatp.com/list/pgc-image/1529588142869caebf0b30d&#39;</span>, <span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;儿子打了狗狗一下，接下来让一家人感动&#39;</span><span class="o">}</span>
狗狗/儿子打了狗狗一下，接下来让一家人感动/c1f1880a9dff12cadeb5893715b7b5de.jpg
图片下载完成！
.
.
.
<span class="o">{</span><span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://p1.pstatp.com/list/pgc-image/15268265312399549e4d5fe&#39;</span>, <span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;可爱的狗狗&#39;</span><span class="o">}</span>
狗狗/可爱的狗狗/808e74f76dea32fd56614b82c2d55b79.jpg
图片下载完成！
<span class="o">{</span><span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://p3.pstatp.com/list/pgc-image/1526826535155011a163e03&#39;</span>, <span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;可爱的狗狗&#39;</span><span class="o">}</span>
狗狗/可爱的狗狗/df7a97475a5b62ad4f0545fbf64f91ed.jpg
图片下载完成！
<span class="o">{</span><span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://p1.pstatp.com/list/pgc-image/15268265365385bfb3779ae&#39;</span>, <span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;可爱的狗狗&#39;</span><span class="o">}</span>
狗狗/可爱的狗狗/7e9ae7183132420a1a49b8f14032f7f9.jpg
图片下载完成！
<span class="o">{</span><span class="s1">&#39;image&#39;</span>: <span class="s1">&#39;http://p3.pstatp.com/list/pgc-image/15268265382025a24d029a8&#39;</span>, <span class="s1">&#39;title&#39;</span>: <span class="s1">&#39;可爱的狗狗&#39;</span><span class="o">}</span>
狗狗/可爱的狗狗/8fa9fe7c4096e6b5026767f7cca9400e.jpg
图片下载完成！
获取第 <span class="m">181</span> ～ <span class="m">200</span> 个条目...
</code></pre></div><p>顺利的话，查看脚本同级目录中出现了<code>狗狗</code> 的目录，检查目录中确实按照预期爬取了图片。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/20180622152964907254058.png" alt="20180622152964907254058.png"></p>
<h3 id="结果分析">结果分析</h3>
<p>针对成果，总结分析有三点需要改进的地方：</p>
<ol>
<li>图片分辨率低：查看图片，你会发现分辨率很低，有的几乎看不清；</li>
<li>没有使用多进程爬取；</li>
<li>脚本不够通用，能不能像命令一样执行，实现可以搜索任意想搜索的图片，而不仅仅局限于🐶；</li>
</ol>
<h3 id="优化">优化</h3>
<h4 id="图片分辨率问题">图片分辨率问题</h4>
<p>还是得分析网页，找到狗狗图集的页面，以第一个条目的第一个图片为例，看看有什么规律可循。</p>
<p>记录json数据中第一个图片的链接为：</p>
<p><a href="http://p1.pstatp.com/list/pgc-image/15296042422187b4c0bfb38">http://p1.pstatp.com/list/pgc-image/15296042422187b4c0bfb38</a></p>
<p>点击第一个条目，进入相应图集，还是看第一个图，这是发现图片分辨率明显提高，那么此时这个图片的链接是什么，查看一下为：</p>
<p><a href="http://p3.pstatp.com/origin/pgc-image/15296042422187b4c0bfb38">http://p3.pstatp.com/origin/pgc-image/15296042422187b4c0bfb38</a></p>
<p>比较两个链接，发现有两个不同的地方：</p>
<table>
<thead>
<tr>
<th style="text-align:center">图片链接</th>
<th style="text-align:center">主机名</th>
<th style="text-align:center">目录名</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">低分辨率</td>
<td style="text-align:center">p1</td>
<td style="text-align:center">list</td>
</tr>
<tr>
<td style="text-align:center">高分辨率</td>
<td style="text-align:center">p3</td>
<td style="text-align:center">origin</td>
</tr>
</tbody>
</table>
<p>更改低分辨率图片链接中的<code>p1</code>为<code>p3</code>，用浏览器去访问，居然也能出图片，但还是低分辨率，看来这个不关键，那只更改<code>list</code>为<code>origin</code>试一下，居然可以访问，而且是高分辨率的图片。</p>
<p>以此方式比较几张其它图片的链接，发现均可以只改链接中的<code>list</code>为<code>origin</code>就能得到高分辨率的图片，那么优化函数<code>parse_images_url</code>中的image链接生成的那一行代码，调用字符串替换函数<code>replace</code>就可以，如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="c1"># 原代码</span>
<span class="s1">&#39;image&#39;</span><span class="p">:</span> <span class="s1">&#39;http:&#39;</span> <span class="o">+</span> <span class="n">image</span><span class="p">[</span><span class="s1">&#39;url&#39;</span><span class="p">],</span>
<span class="c1"># 改进后代码</span>
<span class="s1">&#39;image&#39;</span><span class="p">:</span> <span class="s1">&#39;http:&#39;</span> <span class="o">+</span> <span class="n">image</span><span class="p">[</span><span class="s1">&#39;url&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;list&#39;</span><span class="p">,</span> <span class="s1">&#39;origin&#39;</span><span class="p">),</span>
</code></pre></div><p>重新运行脚本，检查图片，结果可行！</p>
<h4 id="多进程爬取">多进程爬取</h4>
<p>这里采用多进程库<code>multiprocessing</code>的进程池实现多进程调度，为了方便映射，将主函数中的实现，封装为main函数，参数用来进程分配，所以为<code>offset</code>，<code>main</code>函数实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">offset</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;主函数，用于多进程调度
</span><span class="s1">    :param offset: 页面链接offset参数的值
</span><span class="s1">    :type offset: 能被20整除的整数
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;获取第&#39;</span><span class="p">,</span> <span class="n">offset</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">&#39;～&#39;</span><span class="p">,</span> <span class="n">offset</span> <span class="o">+</span> <span class="mi">20</span><span class="p">,</span> <span class="s1">&#39;个条目...&#39;</span><span class="p">)</span>
    <span class="n">json</span> <span class="o">=</span> <span class="n">get_page_json</span><span class="p">(</span><span class="n">offset</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">image</span> <span class="ow">in</span> <span class="n">parse_images_url</span><span class="p">(</span><span class="n">json</span><span class="p">):</span>
        <span class="n">save_image_from</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="n">to_dir</span><span class="o">=</span><span class="s1">&#39;狗狗&#39;</span><span class="p">)</span>
</code></pre></div><p>引入多进程库：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">multiprocessing.pool</span> <span class="kn">import</span> <span class="n">Pool</span>
</code></pre></div><p>生成进程池，映射主函数并执行，最终实现如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">offset_list</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="o">*</span> <span class="mi">20</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">PAGE_NUM</span><span class="p">)]</span>
    <span class="n">pool</span> <span class="o">=</span> <span class="n">Pool</span><span class="p">()</span>
    <span class="n">pool</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">main</span><span class="p">,</span> <span class="n">offset_list</span><span class="p">)</span>
    <span class="n">pool</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
    <span class="n">pool</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</code></pre></div><p>执行后，看打印信息，会发现会有多个请求同时进行，速度明显加快。</p>
<h4 id="通用化脚本">通用化脚本</h4>
<p>这里说通用化，也仅局限于获取头条中的图集，目前根据前面分析，链接中可以更改的除了<code>offset</code>，其实还有个<code>keyword</code>，其它的目前来看没必要更改了，说起来其中<code>count</code>应该也可以修改，这里有个假设：</p>
<ul>
<li><code>offset</code>与<code>count</code>有关，<code>offset</code>值要能被<code>count</code>整除；</li>
<li>响应结果中条目数与<code>count</code>对应</li>
</ul>
<blockquote>
<p>上面的假设就不验证了，留给感兴趣的人吧，这里只做<code>offset</code>和<code>keyword</code>的定制实现。</p>
</blockquote>
<p>稍微修改<code>get_page_json</code>函数和<code>main</code>函数，加入keyword参数用来指定搜索关键词：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">get_page_json</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="n">keyword</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;生成ajax请求，发出get请求，并获取响应结果，以字典的形式返回json数据
</span><span class="s1">    :param offset: 页面请求偏移值
</span><span class="s1">    :type offset: 能被20整除的整数
</span><span class="s1">    :param keyword: 图片搜索的关键词
</span><span class="s1">    :type keyword: unicode字符串
</span><span class="s1">    :return: 响应数据
</span><span class="s1">    :rtype: dict
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">params</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s1">&#39;offset&#39;</span><span class="p">:</span> <span class="n">offset</span><span class="p">,</span>
        <span class="s1">&#39;format&#39;</span><span class="p">:</span> <span class="s1">&#39;json&#39;</span><span class="p">,</span>
        <span class="s1">&#39;keyword&#39;</span><span class="p">:</span> <span class="n">keyword</span><span class="p">,</span>
        <span class="s1">&#39;autoload&#39;</span><span class="p">:</span> <span class="s1">&#39;true&#39;</span><span class="p">,</span>
        <span class="s1">&#39;count&#39;</span><span class="p">:</span> <span class="s1">&#39;20&#39;</span><span class="p">,</span>
        <span class="s1">&#39;cur_tab&#39;</span><span class="p">:</span> <span class="s1">&#39;3&#39;</span><span class="p">,</span>
        <span class="s1">&#39;from&#39;</span><span class="p">:</span> <span class="s1">&#39;gallery&#39;</span>
    <span class="p">}</span>

    <span class="n">url</span> <span class="o">=</span> <span class="s1">&#39;https://www.toutiao.com/search_content/?&#39;</span> <span class="o">+</span> <span class="n">urlencode</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">HEADERS</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
    <span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">ConnectionError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Error&#39;</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="n">keyword</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;主函数，用于多进程调度
</span><span class="s1">    :param offset: 页面链接offset参数的值
</span><span class="s1">    :type offset: 能被20整除的整数
</span><span class="s1">    :param keyword: 图片搜索关键词
</span><span class="s1">    :type keyword: unicode字符串
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;获取第&#39;</span><span class="p">,</span> <span class="n">offset</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">&#39;～&#39;</span><span class="p">,</span> <span class="n">offset</span> <span class="o">+</span> <span class="mi">20</span><span class="p">,</span> <span class="s1">&#39;个条目...&#39;</span><span class="p">)</span>
    <span class="n">json</span> <span class="o">=</span> <span class="n">get_page_json</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="n">keyword</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">image</span> <span class="ow">in</span> <span class="n">parse_images_url</span><span class="p">(</span><span class="n">json</span><span class="p">):</span>
        <span class="n">save_image_from</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="n">to_dir</span><span class="o">=</span><span class="n">keyword</span><span class="p">)</span>
</code></pre></div><p>能不能做成类似于命令行的方式传入搜索的关键词呢？当然可以了，首先在脚本文档最前面加上如下的代码，用来指明所使用的python路径，例如<a href="https://www.smslit.top/about">十里</a>的是：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="ch">#!/usr/local/bin/python3</span>
</code></pre></div><p>然后为脚本文件加上执行权限：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ chmod +x toutiao_pic.py
</code></pre></div><p>这样你就可以像命令一样执行脚本了：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./toutiao_pic.py
</code></pre></div><p>这里实现一个简单的参数设置思路，规定必须输入两个参数，一个是关键词，一个是请求个数，所以固定命令的格式为：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">./toutiao_pic.py 狗狗 <span class="m">5</span>
</code></pre></div><p>即命令后跟着第一个参数就是图片搜索的关键词，然后就是请求个数的设置，如果要提取这两个参数，需要用到<code>sys</code>库的<code>argv</code>，在脚本中<code>sys.argv</code>是一个字符串列表，第一个元素就是命令，在这里的话，如果输入命令格式正确，那么这个列表会包含三个元素，后两个按顺序分别是：图片搜索关键词和请求个数（每个请求会得到20个图集信息），为了更好的交互，还得加入一定的帮助信息的打印，所以最终添加了以下代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">get_help_info</span><span class="p">(</span><span class="n">command</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;生成帮助信息
</span><span class="s1">    :param command: 命令的名称
</span><span class="s1">    :type command: 字符串
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">help_info</span> <span class="o">=</span> <span class="s1">&#39;</span><span class="se">\n</span><span class="s1">使用:</span><span class="se">\n</span><span class="s1">  &#39;</span> <span class="o">+</span> <span class="n">command</span> <span class="o">+</span> <span class="s1">&#39; 关键字 请求个数</span><span class="se">\n</span><span class="s1">&#39;</span>
    <span class="n">help_info</span> <span class="o">+=</span> <span class="s1">&#39;  - 关键字: 图片搜索关键词</span><span class="se">\n</span><span class="s1">  - 请求个数: 一次请求会获取20个图集的图片</span><span class="se">\n</span><span class="s1">&#39;</span>
    <span class="n">help_info</span> <span class="o">+=</span> <span class="s1">&#39;例如:</span><span class="se">\n</span><span class="s1">  &#39;</span> <span class="o">+</span> <span class="n">command</span> <span class="o">+</span> <span class="s1">&#39; 狗狗 5</span><span class="se">\n</span><span class="s1">  -&gt; 会获取100个图集中狗狗的图片</span><span class="se">\n</span><span class="s1">&#39;</span>
    <span class="k">return</span> <span class="n">help_info</span>

<span class="k">def</span> <span class="nf">parse_argv</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">):</span>
    <span class="s1">&#39;&#39;&#39;解析命令参数
</span><span class="s1">    &#39;&#39;&#39;</span>
    <span class="n">args_dict</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s1">&#39;-h&#39;</span><span class="p">:</span> <span class="n">get_help_info</span><span class="p">(</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span>
        <span class="s1">&#39;-k&#39;</span><span class="p">:</span> <span class="s1">&#39;狗狗&#39;</span><span class="p">,</span>
        <span class="s1">&#39;-n&#39;</span><span class="p">:</span> <span class="n">PAGE_NUM</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">3</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">args_dict</span><span class="p">[</span><span class="s1">&#39;-h&#39;</span><span class="p">])</span>
        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">page_num</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">args</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span>
            <span class="n">args_dict</span><span class="p">[</span><span class="s1">&#39;-n&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">page_num</span> <span class="k">if</span> <span class="n">page_num</span> <span class="o">&lt;=</span> <span class="mi">100</span> <span class="k">else</span> <span class="mi">5</span>
            <span class="n">args_dict</span><span class="p">[</span><span class="s1">&#39;-k&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
        <span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;请求个数有误，请输入整数&#39;</span><span class="p">)</span>
            <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">args_dict</span>
</code></pre></div><p>此时会有一个问题，那就是main函成为了有两个参数函数，进程池map的处理需要调整。先看一下现在程序的执行思路：先解析命令参数获得关键词和请求个数，然后传入main函数进行处理。进程池的map只能管理单个参数的函数，那么现在的问题就是解决进程池的map的多参数处理，应该有多个方法，可以发现main函数执行的话每次传入的图片搜索关键词是一定的，那么可以利用<code>functools</code>库的<code>partial</code>类构造一个只有<code>offset</code>这个参数的partial函数，实现过程：</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">args_dict</span> <span class="o">=</span> <span class="n">parse_argv</span><span class="p">()</span>
    <span class="n">offset_list</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="o">*</span> <span class="mi">20</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">args_dict</span><span class="p">[</span><span class="s1">&#39;-n&#39;</span><span class="p">])]</span>
    <span class="n">pool</span> <span class="o">=</span> <span class="n">Pool</span><span class="p">()</span>
    <span class="n">partial_main</span> <span class="o">=</span> <span class="n">partial</span><span class="p">(</span><span class="n">main</span><span class="p">,</span> <span class="n">keyword</span><span class="o">=</span><span class="n">args_dict</span><span class="p">[</span><span class="s1">&#39;-k&#39;</span><span class="p">])</span>
    <span class="n">pool</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">partial_main</span><span class="p">,</span> <span class="n">offset_list</span><span class="p">)</span>
    <span class="n">pool</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
    <span class="n">pool</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</code></pre></div><h2 id="总结">总结</h2>
<p>至此，所有代码已经完成，为了让脚本用起来更像命令行工具，可以把后缀名<code>py</code>去掉，然后想一个好名字重命名一下，比如这样：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ mv toutiao_pic.py ttpic
$ ./ttpic

使用:
  ./ttpic 关键字 请求个数
  - 关键字: 图片搜索关键词
  - 请求个数: 一次请求会获取20个图集的图片
例如:
  ./ttpic 狗狗 <span class="m">5</span>
  -&gt; 会获取100个图集中狗狗的图片
</code></pre></div><p>如果我们想要爬取<code>猫咪</code>的图片，另外想要获取100个图集的，请求个数设置为 $100\div20=5$ ，最终命令为：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ ./ttpic 猫咪 <span class="m">5</span>
</code></pre></div><p>完整代码可参考: <a href="https://github.com/smslit/spider-collection/blob/master/ttpic/ttpic">ttpic</a></p>]]></content>
		</item>
		
		<item>
			<title>sketch练习稿第三波——我们</title>
			<link>https://blog.5km.studio/2018/03/06/sketch-practice3/</link>
			<pubDate>Tue, 06 Mar 2018 23:03:58 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/03/06/sketch-practice3/</guid>
			<description>&lt;p&gt;十里非常喜欢iReader Plus电纸书阅读器的其中一款屏保，名曰：阅赏自然。明天就是女神节，索性，十里以这一套五个图作为sketch临摹对象，做一封kindle情书得了，附上十里的憧憬，水木一定非常喜欢。这也就得到了本文所要展示的第三波练习稿，作品：《我们》。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>十里非常喜欢iReader Plus电纸书阅读器的其中一款屏保，名曰：阅赏自然。明天就是女神节，索性，十里以这一套五个图作为sketch临摹对象，做一封kindle情书得了，附上十里的憧憬，水木一定非常喜欢。这也就得到了本文所要展示的第三波练习稿，作品：《我们》。</p>
<h2 id="临摹五图">临摹五图</h2>
<p>五张自然图，附上三两挚语，使用娃娃字体，显得更加轻松自然而不失真情。</p>
<h3 id="sunrise">sunrise</h3>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/l2dqj.png" width="379px"/>
</figure>

<h3 id="mountain">mountain</h3>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/4zakw.png" width="379px"/>
</figure>

<h3 id="ocean">ocean</h3>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/jefo2.png" width="379px"/>
</figure>

<h3 id="tree">tree</h3>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/jzlmb.png" width="379px"/>
</figure>

<h3 id="countryside">countryside</h3>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/si0jm.png" width="379px"/>
</figure>

<h2 id="kindle情书">Kindle情书</h2>
<p>利用上面的五张图，外加绘制的封面图，制作了送给水木的情书《我们》。毕竟本文主角是sketch，此次练习使用了最新版本sketch49新加的特色功能——交互，利用sketch预览功能可以展示在Kindle中同样的页面切换效果，如下：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/siuwr.gif" width="379px"/>
</figure>

<p>情书已送给十里的女神——水木，虽只是简单的几页，但她很喜欢，十里很开心！</p>
<h2 id="资料下载">资料下载</h2>
<p><a href="https://pan.baidu.com/s/1XH9oB_3t2UsVzersOuMGkw">sketch练习稿3by十里</a> 密码: af1m</p>
<p>包含：</p>
<ul>
<li><strong>我们.mobi</strong>: Kindle情书；</li>
<li><strong>我们.mov</strong>: 效果展示；</li>
<li><strong>we.sketch</strong>: sketch设计稿；</li>
</ul>]]></content>
		</item>
		
		<item>
			<title>Sketch练习稿第二波</title>
			<link>https://blog.5km.studio/2018/02/25/sketch-practice2/</link>
			<pubDate>Sun, 25 Feb 2018 20:12:15 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/02/25/sketch-practice2/</guid>
			<description>&lt;p&gt;继上次的练习稿第一波，sketch练习稿第二波该整理一下了，放假在老家果然什么也干不成呀，哈哈，至今也没练习多少。整理这些东西图自己开心而已，反正也没人看，尴尬！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>继上次的练习稿第一波，sketch练习稿第二波该整理一下了，放假在老家果然什么也干不成呀，哈哈，至今也没练习多少。整理这些东西图自己开心而已，反正也没人看，尴尬！</p>
<h2 id="镜头logo">镜头logo</h2>
<p>效果图：</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/8elsj.png"
         alt="镜头" width="50%"/>
</figure>

<p>参考：</p>
<ul>
<li><a href="http://www.sketchs.cn/tutorials/detail/245.html">用Sketch制作一枚精美摄影图标</a></li>
</ul>
<h2 id="小绵羊">小绵羊</h2>
<p>效果图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/dvyfn.png" alt="小绵羊"></p>
<p>这个图标是参照网上搜到的一个小羊logo图片自己画的，然后头上给她加了小花，还有眼睫毛，更像是女羊，哈哈哈！！！</p>
<p>参考：</p>
<p>这个找不到原来的出处了，找到后再补充（侵删）</p>
<h2 id="小猴子">小猴子</h2>
<p>小猴子是参照网上看到的一个图片中小猴子自己画的，和原图可能有些出入，自己画着玩嘛，练练手就好了！</p>
<p>效果图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/aad42.png" alt="monkey"></p>
<p>参考：</p>
<p>这个找不到原来的出处了，找到后再补充（侵删）</p>
<h2 id="下载">下载</h2>
<p><a href="https://pan.baidu.com/s/1dGUnKAX">sketch练习稿2by十里.zip</a> 密码: xfs7</p>]]></content>
		</item>
		
		<item>
			<title>逼疯我的AFIO，STM32的JTAG调试接口作为GPIO用</title>
			<link>https://blog.5km.studio/2018/02/10/stm32-AFIO/</link>
			<pubDate>Sat, 10 Feb 2018 16:34:01 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/02/10/stm32-AFIO/</guid>
			<description>&lt;p&gt;不知是心境不佳还是时运不佳，不好的情绪造成了消极待事还是不好的境遇导致了消极情绪？连STM32的GPIO都要耍十里，还好十里最终破解了AFIO的把戏，没有过不去的坎，对不对！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>不知是心境不佳还是时运不佳，不好的情绪造成了消极待事还是不好的境遇导致了消极情绪？连STM32的GPIO都要耍十里，还好十里最终破解了AFIO的把戏，没有过不去的坎，对不对！</p>
<h2 id="现象">现象</h2>
<p>最近在玩带按钮的<a href="https://baike.baidu.com/item/%E7%BC%96%E7%A0%81%E5%99%A8/6029803?fr=aladdin">旋转编码器</a>，旋转编码器用到了 <strong>stm32f103c8t6</strong> 的3个GPIO作为输入，分别是：</p>
<ul>
<li>PB4 -&gt; A相</li>
<li>PB3 -&gt; B相</li>
<li>PA15 -&gt; 按钮</li>
</ul>
<p>这里就不介绍旋转编码器了，因为这里只需要知道是作为输入管脚使用就可以了。</p>
<p>出现了很奇怪的现象，PB3的管脚不能正常采集外部输入，造成了驱动异常，十里一度怀疑管脚坏了，但是十里使用STM32CubeMX生成了新的工程，测试管脚正常采集高低电平，简直了！此时此刻脑袋中有一万头羊驼奔过。。。</p>
<h2 id="分析">分析</h2>
<h3 id="管脚配置问题">管脚配置问题</h3>
<p>十里曾经怀疑是不是STM32CubeMX生成的的初始化代码有问题，但是十里在调试过程中查看了管脚配置，与手册对照配置是正确的。</p>
<h3 id="管脚坏了">管脚坏了</h3>
<p>现象中已经提到，新的工程中PB3作为输入是正常的，所以原因排除。</p>
<h3 id="外部电路干扰">外部电路干扰</h3>
<p>直连编码器和单片机，管脚仍然配置为上拉输入，依旧存在问题，说明肯定还有其他原因才对！开始怀疑人生。。。</p>
<h3 id="难道管脚有什么特殊之处">难道管脚有什么特殊之处</h3>
<p>查看芯片手册，最终发现原来真的是有猫腻，PB3管脚复位后的主功能原来不是GPIO，而是JTDO（JTAG接口的数据输出），原来是管脚复用（AFIO）的问题，网上查找资料，果然被十里找到一篇文章，问题出错原理一模一样：</p>
<blockquote>
<p>查看STM32F1参考手册可以发现PB4及PA13\PA14\PA15\PB3等5个脚在芯片复位后默认的就是专用的调试口，非通用GPIO。现在客户工程师虽然用SWD接口，只用到PA13\PA14两根线，但PB4及PA15、PB3三根线的属性没变，还是专用调试口。如果要把不用的PB4等三根线作为GPIO，还得额外做些相关寄存器配置，即操作AFIO_MAPR寄存器中的SWJ_CFG【2:0】三个位。</p>
</blockquote>
<blockquote>
<p>&hellip;</p>
</blockquote>
<blockquote>
<p>如果使用ST公司的STM32CubeMx图形化配置工具来做管脚安排及时钟初始化等就可以避免很多类似上面谈到的繁琐或麻烦。利用STM32CubeMx配置工具，很多初始化的东西都可以依据你的管脚和时钟安排、外设功能的使能等而生成出相应的配置代码，不必手动二次添加配置,让你去专注你的用户应用代码设计与调试。</p>
</blockquote>
<blockquote>
<p>取自：<a href="http://www.51hei.com/bbs/dpj-41000-1.html">一个关于STM32 GPIO管脚复用冲突的话题</a></p>
</blockquote>
<h2 id="解决">解决</h2>
<p>依照找到的原因查看程序，调试过程中发现<code>AFIO-&gt;MAPR</code>并不是期望值2，也就是说复位保持了PB3的复用功能JTDO，所以导致PB3作为输入无法正常工作。那么为什么我也采用了STM32CubeMX生成的代码，却没有屏蔽调试接口的处理呢？查看代码找到文件<code>stm32f1xx_hal_msp.c</code>中的 <strong>HAL_Msp_init</strong> 函数，发现里面有屏蔽调试接口的操作：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">__HAL_AFIO_REMAP_SWJ_NOJTAG</span><span class="p">();</span>
</code></pre></div><p>但是为什么没有生效呢？gdb一步步调试程序，发现执行HAL_Msp_Init函数时跳到了其他文件，这个文件中这个函数为弱函数定义，其中没有任何操作，说明文件stm32f1xx_hal_msp.c没有被编译进程序，最后查找Makefile发现，果真没有加入这个文件的编译，加入后，编译运行，一切OK！！！</p>
<p>这也就解释了为什么其他用STM32CubeMX配置工具生成的工程可以正常操作PB3。</p>
<h2 id="总结">总结</h2>
<p>没有总结，只有安利，gdb真的很好用，对于stm32芯片可以使用<strong>arm-none-eabi-gdb</strong>进行调试，nice！比IDE中的调试功能强大且灵活。。。</p>]]></content>
		</item>
		
		<item>
			<title>Sketch练习稿第一波</title>
			<link>https://blog.5km.studio/2018/02/01/sketch-practice1/</link>
			<pubDate>Thu, 01 Feb 2018 20:48:10 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/02/01/sketch-practice1/</guid>
			<description>&lt;p&gt;Sketch真的是一枚神器，是一款运行在Mac平台的能力炸天的UI辅助设计App。有了它的加持，十里这样半路出家的程序猿也可以做一些专业设计师才可以做的设计。很久之前就开始用了，不过之前基本都是绘制一些矢量示意图，最近花了点时间稍微学习玩耍了一番，了解了一些高级的功能，很开心！看到萧条死寂的博客，十里就决定写一篇没用的博文记录一下已经做了的四个练习稿。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>Sketch真的是一枚神器，是一款运行在Mac平台的能力炸天的UI辅助设计App。有了它的加持，十里这样半路出家的程序猿也可以做一些专业设计师才可以做的设计。很久之前就开始用了，不过之前基本都是绘制一些矢量示意图，最近花了点时间稍微学习玩耍了一番，了解了一些高级的功能，很开心！看到萧条死寂的博客，十里就决定写一篇没用的博文记录一下已经做了的四个练习稿。</p>
<p>不是十里自夸，十里还是挺有悟性的，没看软件操作说明，就能把操作摸个差不多，直接上手绘制一些简单的示意图和小logo。尤其是去年上半年用latex写论文，需要一些矢量示意图，就用这个神器和另一个神器Omni Graffle搞定的。但是呢，十里也是个有志小程序猿，何不好好了解一下工具和UI设计呢，毕竟十里对设计一直很感兴趣的嘛！</p>
<h2 id="黄钻app">黄钻App</h2>
<p>Sketch的logo还是比较有辨识度的，一枚绽放光芒的黄钻：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/65fzk.png" alt="sketch"></p>
<h2 id="学习资源">学习资源</h2>
<p>发现了三个不错的网站可以学习Sketch以及好的UI设计：</p>
<ul>
<li><a href="http://sketch.im/">sketch.im</a></li>
<li><a href="http://www.sketchs.cn/index.html">Sketch中国</a></li>
<li><a href="http://www.ui.cn/">UI中国</a></li>
</ul>
<h2 id="练习稿">练习稿</h2>
<p>说了这么多，我还是没忘正事儿，记录我的练习稿。这里得说一下，每个练习稿设计都是依照稿下链接学习的，设计版权由对应链接文章的作者所有。其中彩色开关完全按照教程来的，但其它三个都是看了一遍最终展示图，十里里自己独立画的，有一定自己的发挥，这一点蛮欣慰的！</p>
<h3 id="彩色开关">彩色开关</h3>
<p>效果图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ghccz.png" alt="彩色开关"></p>
<p>参考：</p>
<ul>
<li><a href="http://www.sketchs.cn/tutorials/detail/199.html">教你利用sketch创建彩色开关（上）</a></li>
<li><a href="http://www.sketchs.cn/tutorials/detail/200.html">教你利用sketch创建彩色开关（下）</a></li>
</ul>
<h3 id="蓝鹦">蓝鹦</h3>
<p>效果图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/o8bvh.png" alt="蓝鹦"></p>
<p>参考：</p>
<ul>
<li><a href="http://www.sketchs.cn/tutorials/detail/204.html">sketch创建小鸟图标</a></li>
</ul>
<h3 id="google-sheet">Google Sheet</h3>
<p>效果图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/lmp1j.png" alt="Google Sheet"></p>
<p>参考：</p>
<ul>
<li><a href="http://www.sketchs.cn/tutorials/detail/233.html">创建 Google Sheets 图标</a></li>
</ul>
<h3 id="大白">大白</h3>
<p>效果图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/klxv1.png" alt="大白"></p>
<p>参考：</p>
<ul>
<li><a href="http://www.sketchs.cn/tutorials/detail/234.html">用sketch画一个萌萌哒的大白</a></li>
</ul>
<h2 id="下载">下载</h2>
<p><a href="https://pan.baidu.com/s/1nwExkKx">sketch练习稿1by十里.zip</a> 密码: hwus</p>]]></content>
		</item>
		
		<item>
			<title>《黑客与画家》阅读</title>
			<link>https://blog.5km.studio/2018/01/31/note-hackerAndPainter/</link>
			<pubDate>Wed, 31 Jan 2018 12:13:51 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2018/01/31/note-hackerAndPainter/</guid>
			<description>&lt;p&gt;尝试三次终于读完了《黑客与画家》，前两次刚读完没几篇就没时间读给耽搁了，这次不管怎样，还是抽时间从0读完了整本书，有很多收获，或许好久没写东西的原因，明明有所收获却难以抒发，索性就把阅读中添加的笔记贴出来，之前不咋习惯用电纸书阅读时添标注（主要原因还是我平时读书少，惭愧呀！），好像阅读到中间才习惯添加标注，想来不全，就待再次阅读的时候补充了。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>尝试三次终于读完了《黑客与画家》，前两次刚读完没几篇就没时间读给耽搁了，这次不管怎样，还是抽时间从0读完了整本书，有很多收获，或许好久没写东西的原因，明明有所收获却难以抒发，索性就把阅读中添加的笔记贴出来，之前不咋习惯用电纸书阅读时添标注（主要原因还是我平时读书少，惭愧呀！），好像阅读到中间才习惯添加标注，想来不全，就待再次阅读的时候补充了。</p>
<h2 id="摘记">摘记</h2>
<ol>
<li>
<p>黑客如何才能做自己喜欢的事情？</p>
<blockquote>
<p>黑客如何才能做自己喜欢的事情？我认为这个问题的解决方法是一个几乎所有创作者都知道的方法：找一份养家糊口的“白天工作”（day job）。这个词是从音乐家身上来的，他们晚上表演音乐，所以白天可以找一份其他工作。更一般地说，“白天工作”的意思是，你有一份为了赚钱的工作，还有一份为了爱好的工作。</p>
</blockquote>
</li>
<li>
<p>不停修改</p>
<blockquote>
<p>注明散文家E.B怀特说过，“最好的文字来自不停地修改”。</p>
</blockquote>
</li>
<li>
<p>年轻的自己和老成的自己</p>
<p>在这里作者好像是在说，写优秀软件需要同时具备两种互相冲突的信念：</p>
<ul>
<li>像初生牛犊一样对自己能力信心万丈；</li>
<li>又像历经沧桑的老人对自己的能力抱着怀疑态度；</li>
</ul>
<blockquote>
<p>在你的大脑中，有一个声音说“千难万险只等闲”，还有一个声音却说“早岁哪知世事难”。</p>
</blockquote>
</li>
<li>
<p>弱即是强</p>
<blockquote>
<p>软件领域，贴近用户的设计思想被归纳为“弱即是强”模式&hellip;如果你正在设计某种新东西，就应该尽快拿出原型，听取用户的意见。</p>
</blockquote>
</li>
<li>
<p>没有完工</p>
<blockquote>
<p>画作永远没有完工的一天，你只是不再画下去而已。</p>
</blockquote>
</li>
</ol>
<h2 id="术语整理">术语整理</h2>
<ol>
<li><strong>胶水程序</strong>
在应用程序之间整理或者转移数据的程序。</li>
<li><strong>格林斯潘第十定律</strong>
任何C或Fortran程序复杂到一定程度之后，都会包含一个临时开发的、只有一半功能的、不完全符合规格的、到处都是bug的、运行速度很慢的Common Lisp实现。</li>
<li><strong>奥卡姆剃刀原则</strong>
简单的解释就是较好的解释。</li>
<li><strong>帕金森定律</strong>
完成一项任务所需的资源会不断扩展，直至这种资源消耗为止。</li>
<li><strong>图灵完备</strong>
如果一种编程语言写出的所有程序都能转换成图灵程序，并且反之成立，那么这种编程语言就是图灵完备的。所有当代编程语言都是图灵完备的，这意味着（在理论上）它们的功能都是一样强大的。图灵完备又称图灵等价(Turing equivalent)。</li>
</ol>]]></content>
		</item>
		
		<item>
			<title>stm32开发新方式-platformio</title>
			<link>https://blog.5km.studio/2017/11/08/stm32InPIO-start/</link>
			<pubDate>Wed, 08 Nov 2017 09:10:51 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/11/08/stm32InPIO-start/</guid>
			<description>&lt;p&gt;&lt;a href=&#34;http://platformio.org/&#34;&gt;platformio&lt;/a&gt;，这个强大的嵌入式开发平台当然也提供了stm32的支持，本文尝试在platformio开发平台上开发stm32。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p><a href="http://platformio.org/">platformio</a>，这个强大的嵌入式开发平台当然也提供了stm32的支持，本文尝试在platformio开发平台上开发stm32。</p>
<p>如果您不喜欢使用CLI(命令行)的stm32开发方式，可以阅读文章<a href="/vscode-pio-ide/"><strong>stm32开发新方式——platformio的IDE</strong></a>，也许你会得到惊喜！</p>
<p>测试环境：</p>
<ol>
<li>macOS</li>
<li>stm32f103c8t6最小系统板</li>
<li>st-link v2</li>
</ol>
<h2 id="新建pio工程">新建pio工程</h2>
<ul>
<li>
<p>创建工程文件夹并进入：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">mkdir <span class="nb">test</span> <span class="o">&amp;&amp;</span> <span class="nb">cd</span> <span class="nb">test</span>
</code></pre></div></li>
<li>
<p>查找自己适合的板子</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pio boards
</code></pre></div><p>会看到以下结果，十里这里选择<code>genericSTM32F103C8</code>，这里执行这个命令，主要是查找对应自己最小系统板的<code>board ID</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">...
disco_l152rb          STM32L152RBT6  32Mhz     128kB   16kB   ST STM32LDISCOVERY
disco_f100rb          STM32F100RBT6  24Mhz     128kB   8kB    ST STM32VLDISCOVERY
genericSTM32F103C8    STM32F103C8    72Mhz     64kB    20kB   STM32F103C8 <span class="o">(</span>20k RAM. 64k Flash<span class="o">)</span>
genericSTM32F103CB    STM32F103CB    72Mhz     128kB   20kB   STM32F103CB <span class="o">(</span>20k RAM. 128k Flash<span class="o">)</span>
genericSTM32F103R8    STM32F103R8    72Mhz     64kB    20kB   STM32F103R8 <span class="o">(</span>20k RAM. 64k Flash<span class="o">)</span>
genericSTM32F103RB    STM32F103RB    72Mhz     128kB   20kB   STM32F103RB <span class="o">(</span>20k RAM. 128k Flash<span class="o">)</span>
genericSTM32F103RC    STM32F103RC    72Mhz     256kB   48kB   STM32F103RC <span class="o">(</span>48k RAM. 256k Flash<span class="o">)</span>
genericSTM32F103RE    STM32F103RE    72Mhz     512kB   64kB   STM32F103RE <span class="o">(</span>64k RAM. 512k Flash<span class="o">)</span>
...
</code></pre></div></li>
<li>
<p>初始化工程，以vim作为IDE：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">pio init --ide vim -b genericSTM32F103C8
</code></pre></div></li>
<li>
<p>新建Makefile，添加以下内容（这里注意缩进问题）：</p>
<div class="highlight"><pre class="chroma"><code class="language-mk" data-lang="mk"><span class="nf">all</span><span class="o">:</span>
  platformio -f -c vim run
<span class="nf">upload</span><span class="o">:</span>
  platformio -f -c vim run --target upload
<span class="nf">clean</span><span class="o">:</span>
  platformio -f -c vim run --target clean
<span class="nf">program</span><span class="o">:</span>
  platformio -f -c vim run --target program
<span class="nf">uploadfs</span><span class="o">:</span>
  platformio -f -c vim run --target uploadfs
<span class="nf">update</span><span class="o">:</span>
  platformio -f -c vim update
</code></pre></div></li>
<li>
<p>配置<code>platformio.ini</code>文件
因为我们这里使用stlink v2，所以需要将程序上传方式定为<code>stlink</code>，默认生成的工程使用<code>arduino</code>框架，这是玩过Arduino的网友的福音，不是吗？只需添加一行指定程序上传方式，最终内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="k">[env:genericSTM32F103C8]</span>
<span class="na">platform</span> <span class="o">=</span> <span class="s">ststm32</span>
<span class="na">board</span> <span class="o">=</span> <span class="s">genericSTM32F103C8</span>
<span class="na">framework</span> <span class="o">=</span> <span class="s">arduino</span>
<span class="na">upload_protocol</span> <span class="o">=</span> <span class="s">stlink</span>
</code></pre></div></li>
</ul>
<p><code>upload_protocol = stlink</code> 可以省略。</p>
<h2 id="添加代码">添加代码</h2>
<p>添加点灯代码，十里的最小系统板上LED对应于管脚<code>PB0</code>，所以在<code>src</code>目录下添加<code>main.cpp</code>的内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c++" data-lang="c++"><span class="cp">#include</span> <span class="cpf">&lt;Arduino.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">pinMode</span><span class="p">(</span><span class="n">PB0</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">digitalWrite</span><span class="p">(</span><span class="n">PB0</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
    <span class="n">delay</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
    <span class="n">digitalWrite</span><span class="p">(</span><span class="n">PB0</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
    <span class="n">delay</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div><p>编译一下，编译的时候会下载stm32平台开发需要的编译工具和相关框架，你能做的就是waiting&hellip;：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ <span class="nb">test</span> make all
platformio -f -c vim run
<span class="o">[</span>Wed Nov  <span class="m">8</span> 09:42:57 2017<span class="o">]</span> Processing genericSTM32F103C8 
<span class="o">(</span>upload_protocol: stlink<span class="p">;</span> platform: ststm32<span class="p">;</span> board: genericSTM32F103C8<span class="p">;</span> framework: arduino<span class="o">)</span>
--------------------------------------------------------------------------------------------------
Verbose mode can be enabled via <span class="sb">`</span>-v, --verbose<span class="sb">`</span> option
Collected <span class="m">27</span> compatible libraries
Looking <span class="k">for</span> dependencies...
No dependencies
Linking .pioenvs/genericSTM32F103C8/firmware.elf
Calculating size .pioenvs/genericSTM32F103C8/firmware.elf
text	   data	    bss	    dec	    hex	filename
6484	   1936	    312	   8732	   221c	.pioenvs/genericSTM32F103C8/firmware.elf
<span class="o">================================</span> <span class="o">[</span>SUCCESS<span class="o">]</span> Took 1.55 <span class="nv">seconds</span> <span class="o">=====================================</span>
</code></pre></div><h2 id="上传程序">上传程序</h2>
<p>因为前面已经配置了程序上传方式为<code>stlink</code>，所以将stlink连接最小系统板和mac就可以执行上传了，这里也会下载相应的下载工具，waiting &hellip; ：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ make upload
</code></pre></div><p>正常的话就会看到命令行窗口打印上传过程的信息。但也可能会遇到下面类似的问题：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ <span class="nb">test</span> make upload
platformio -f -c vim run --target upload
<span class="o">[</span>Wed Nov  <span class="m">8</span> 09:50:10 2017<span class="o">]</span> Processing genericSTM32F103C8 
<span class="o">(</span>upload_protocol: stlink<span class="p">;</span> platform: ststm32<span class="p">;</span> board: genericSTM32F103C8<span class="p">;</span> framework: arduino<span class="o">)</span>
------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via <span class="sb">`</span>-v, --verbose<span class="sb">`</span> option
Collected <span class="m">27</span> compatible libraries
Looking <span class="k">for</span> dependencies...
No dependencies
Linking .pioenvs/genericSTM32F103C8/firmware.elf
Checking program size
text	   data	    bss	    dec	    hex	filename
6484	   1936	    312	   8732	   221c	.pioenvs/genericSTM32F103C8/firmware.elf
Uploading .pioenvs/genericSTM32F103C8/firmware.bin
2017-11-08T09:50:11 INFO src/common.c: Loading device parameters....
2017-11-08T09:50:11 WARN src/common.c: unknown chip id! 0xe0042000
st-flash 1.3.1
*** <span class="o">[</span>upload<span class="o">]</span> Error <span class="nv">255</span>
<span class="o">======================================</span> <span class="o">[</span>ERROR<span class="o">]</span> Took 1.53 <span class="nv">seconds</span> <span class="o">======================================</span>
make: *** <span class="o">[</span>upload<span class="o">]</span> Error <span class="m">1</span>
</code></pre></div><blockquote>
<p>有一个临时的解决方法，将板上的<code>BOOT0</code>与<code>VDD</code>短接，复位一下最小系统板，会进入boot模式，此时执行<code>make upload</code>，程序会同时烧写在flash和内存上，一旦复位内存上的程序会消失，如果<code>BOOT0</code>还是接<code>VDD</code>的话还会进入boot模式，此时程序已经没了，但是flash 中仍然存在，所以只需将<code>BOOT0</code>短接<code>GND</code>再复位一下，板子就从flash启动了，所以最终程序调试方法为：</p>
</blockquote>
<blockquote>
<p>调试程序：<code>BOOT0</code>短接<code>VDD</code>，每次上传程序先复位板子，然后执行<code>make upload</code>;不进行调试的话，先以调试程序的方式上传程序，然后短接<code>BOOT0</code>和<code>GND</code>，复位一下板子，板子会从flash启动运行固化的程序。</p>
</blockquote>
<p>上述问题本来不该出现，后来发现原来是我的usb hub有问题，我直接将stlink v2连接到macbook上是没有上述问题的。</p>
<h2 id="调试程序">调试程序</h2>
<p>MCU开发过程中有可能会遇到比较棘手的问题，所以硬件调试是少不了的。如果是第一次建立这个stm的PIO工程，在上面编译过程中就会下载stm32单片机开发需要的编译工具和调试工具。</p>
<h3 id="调试配置">调试配置</h3>
<p>免费版的PIO有所限制，不支持stm32硬件调试，但我们可以自己进行简单配置，搭建硬件调试的快捷方式：</p>
<ol>
<li>
<p>添加必要的编译选项</p>
<p>向 <code>platformio.ini</code> 文件中添加以下内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="na">build_flags</span> <span class="o">=</span> <span class="s">
</span><span class="s">    -g</span>
</code></pre></div><p>添加这个参数项，是为了编译生成调试需要的符号项，比如变量名、函数名等。</p>
</li>
<li>
<p>添加调试相关的Make快捷命令</p>
<p>向 <code>Makefile</code> 中添加以下代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-makefile" data-lang="makefile"><span class="nf">debug</span><span class="o">:</span>
    arm-none-eabi-gdb .pioenvs/genericSTM32F103C8/firmware.elf

<span class="nf">server</span><span class="o">:</span>
    openocd -f interface/stlink.cfg -f target/stm32f1x.cfg &gt; .dblog
</code></pre></div><ul>
<li><strong>server</strong> 命令：用于开启 <strong>openocd</strong> 调试服务，指明了使用 <strong>stlink</strong> 调试 stm32f1x 系列芯片^[系统环境变量中已添加openocd的路径(<code>export PATH=&quot;/Users/5km/.platformio/packages/tool-openocd/bin:$PATH&quot;</code>)，调用 <strong>openocd</strong> 的时候会先从其相对路径下查找指定的配置文件，所以这里可以使用配置文件的相对路径，比如<code>interface/stlink.cfg</code>。关于openocd的使用参考<a href="http://openocd.org/doc-release/html/index.html">OpenOCD User’s Guide</a>。]；</li>
<li><strong>debug</strong> 命令：用于开启gdb进行调试，指明了 <code>.pioenvs/genericSTM32F103C8/firmware.elf</code> 为可执行调试文件^[这里调用了 <code>arm-none-eabi-gdb</code> 这个是 arm 芯片的交叉编译链工具集中的调试命令，这里直接调用，是因为工具集路径已经添加到环境变量中了]；</li>
</ul>
</li>
<li>
<p>添加gdb初始化文件</p>
<p>工程根目录下添加文件 <code>.gdbinit</code>，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-ini" data-lang="ini"><span class="na">target extended-remote localhost:3333</span>
<span class="na">monitor reset halt</span>
<span class="na">load</span>
<span class="na">break main</span>
<span class="na">continue</span>
</code></pre></div><p>上述内容，会在开启调试命令后执行，首先连接本地已经开始的 <strong>openocd</strong> 调试服务，复位待调试硬件，加载可执行文件（这里是elf文件），然后在程序入口 <strong>main函数</strong> 处打断点，最后执行程序到 <strong>main</strong> 函数。</p>
</li>
</ol>
<h3 id="调试操作">调试操作</h3>
<p>完成上述配置后就可以进行调试了。</p>
<ol>
<li>
<p>开启调试服务</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$  make server
~/.platformio/packages/tool-openocd/bin/openocd -f ~/.platformio/packages/tool-openocd/scripts/interface/stlink.cfg -f ~/.platformio/packages/tool-openocd/scripts/target/stm32f1x.cfg &gt; .dblog
GNU MCU Eclipse 64-bits Open On-Chip Debugger 0.10.0+dev-00392-gbe9ef0b0 <span class="o">(</span>2018-01-12-16:51<span class="o">)</span>
Licensed under GNU GPL v2
For bug reports, <span class="nb">read</span>
        http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport <span class="s2">&#34;hla_swd&#34;</span>. To override use <span class="s1">&#39;transport select &lt;transport&gt;&#39;</span>.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: <span class="m">1000</span> kHz
adapter_nsrst_delay: <span class="m">100</span>
none separate
Info : Listening on port <span class="m">6666</span> <span class="k">for</span> tcl connections
Info : Listening on port <span class="m">4444</span> <span class="k">for</span> telnet connections
Info : Unable to match requested speed <span class="m">1000</span> kHz, using <span class="m">950</span> kHz
Info : Unable to match requested speed <span class="m">1000</span> kHz, using <span class="m">950</span> kHz
Info : clock speed <span class="m">950</span> kHz
Info : STLINK v2 JTAG v18 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.241188
Info : stm32f1x.cpu: hardware has <span class="m">6</span> breakpoints, <span class="m">4</span> watchpoints
Info : Listening on port <span class="m">3333</span> <span class="k">for</span> gdb connections
...
</code></pre></div></li>
<li>
<p>启动调试</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$  make debug
arm-none-eabi-gdb .pioenvs/genericSTM32F103C8/firmware.elf
GNU gdb <span class="o">(</span>GNU Tools <span class="k">for</span> Arm Embedded Processors 7-2017-q4-major<span class="o">)</span> 8.0.50.20171128-git
Copyright <span class="o">(</span>C<span class="o">)</span> <span class="m">2017</span> Free Software Foundation, Inc.
...
...
...
Reading symbols from .pioenvs/genericSTM32F103C8/firmware.elf...done.
0x00000000 in ?? <span class="o">()</span>
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000fb8 msp: 0x20005000
Loading section .text, size 0x39e4 lma 0x8000000
Loading section .ARM.exidx, size 0x8 lma 0x80039e8
Loading section .data, size 0x938 lma 0x80039f0
Loading section .rodata, size 0x4fc lma 0x8004328
Start address 0x8000fb8, load size <span class="m">18464</span>
Transfer rate: <span class="m">16</span> KB/sec, <span class="m">4616</span> bytes/write.
Breakpoint <span class="m">1</span> at 0x8002c22: file /Users/5km/.platformio/packages/framework-arduinoststm32/STM32F1/cores/maple/main.cpp, line 38.
---Type &lt;<span class="k">return</span>&gt; to <span class="k">continue</span>, or q &lt;<span class="k">return</span>&gt; to quit---
<span class="o">(</span>gdb<span class="o">)</span> 
</code></pre></div></li>
<li>
<p>进行gdb调试</p>
<p>下面就可以使用 <strong>gdb</strong> 命令对程序进行调试了：打断点，单步，执行等等，相关gdb跳是命令这里就不讲解了，可以阅读 <a href="https://liuenyan.github.io/sphinx-wiki/gdb.html">GDB 命令速查</a> 了解。</p>
</li>
</ol>]]></content>
		</item>
		
		<item>
			<title>时间都去哪儿了</title>
			<link>https://blog.5km.studio/2017/10/31/timetogo/</link>
			<pubDate>Tue, 31 Oct 2017 06:35:29 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/10/31/timetogo/</guid>
			<description>&lt;p&gt;看标题有点唬人，其实我写的还是技术文章。算法中会评估时间复杂度，而实际编写c程序时，也会用到一些方法去评估时间，看一看程序运行“时间都去哪儿了”。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>看标题有点唬人，其实我写的还是技术文章。算法中会评估时间复杂度，而实际编写c程序时，也会用到一些方法去评估时间，看一看程序运行“时间都去哪儿了”。</p>
<p>文章编写的c程序均在<code>macOS</code>中实现验证，应该同样适用于<code>Linux/Unix</code>。这里将会用到下面三种方法评估程序的运行时间：</p>
<ul>
<li>clock()函数</li>
<li>time()函数</li>
<li>gettimeofday()函数</li>
</ul>
<h2 id="clock函数"><code>clock()</code>函数</h2>
<p><code>clock()</code>函数是 ANSI C 的标准库函数，其声明在<code>time.h</code>文件中，其返回从开启这个程序进程到调用clock()函数之间的CPU 时钟计时单元（clock tick）数，在 MSDN 中称之为挂钟时间（wal-clock），这有点像嵌入式系统中的系统滴答。</p>
<p>函数返回一个<code>clock_t</code> 的类型数据，其实就是一个无符号的长整型，那么要获得时间的话，还得除以 <code>CLOCKS_PER_SEC</code> 的宏定义。顾名思义，<code>CLOCKS_PER_SEC</code> 就是每秒所经过的滴答数。</p>
<h3 id="测试程序">测试程序</h3>
<p>那么编写一下程序 <code>example1.c</code> 尝尝鲜：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&lt;time.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">long</span> <span class="n">loopCount</span> <span class="o">=</span> <span class="mi">10000000L</span><span class="p">;</span>
	<span class="n">clock_t</span> <span class="n">begin</span><span class="p">,</span> <span class="n">end</span><span class="p">;</span>
	<span class="kt">double</span> <span class="n">duration</span><span class="p">;</span>
	<span class="n">printf</span><span class="p">(</span> <span class="s">&#34;Time to do %ld empty loops: &#34;</span><span class="p">,</span> <span class="n">loopCount</span><span class="p">)</span> <span class="p">;</span>
	<span class="n">begin</span> <span class="o">=</span> <span class="n">clock</span><span class="p">();</span>
	<span class="k">while</span><span class="p">(</span> <span class="n">loopCount</span><span class="o">--</span> <span class="p">);</span>
	<span class="n">end</span> <span class="o">=</span> <span class="n">clock</span><span class="p">();</span>
	<span class="n">duration</span> <span class="o">=</span> <span class="p">(</span><span class="kt">double</span><span class="p">)(</span><span class="n">end</span> <span class="o">-</span> <span class="n">begin</span><span class="p">)</span> <span class="o">/</span> <span class="n">CLOCKS_PER_SEC</span><span class="p">;</span>
    <span class="c1">// 精确到ms
</span><span class="c1"></span>	<span class="n">printf</span><span class="p">(</span> <span class="s">&#34;%.3f ms</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">duration</span> <span class="o">*</span> <span class="mf">1000.0</span> <span class="p">);</span>
	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>程序验证了10000000个空循环的耗时，执行结果如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  duration ./example1
Time to <span class="k">do</span> <span class="m">10000000</span> empty loops: 18.601 ms
</code></pre></div><h3 id="注意">注意</h3>
<blockquote>
<ul>
<li>函数得到的是程序所占进程在CPU上的消耗的滴答数，所以sleep这类函数的时间不会计入；</li>
<li>在 POSIX 兼容系统中，CLOCKS_PER_SEC的值为 1,000,000 的，也就是 1MHz，所以理论上精度应该是us；</li>
</ul>
</blockquote>
<blockquote>
<p>网上好多资料说是精度是ms级别的，仔细想一下其实他们说的不对，就像上面我说的<code>CLOCKS_PER_SEC</code>是1000000，那么这个<code>clock()</code>函数其实返回的是该进程占用CPU的us时间，那么他们说打印函数的时间占用为0ms，问题出在哪儿了呢？很简单，打印一下开始和结束的滴答数就知道了，占用滴答数不可能为0的。<code>CLOCKS_PER_SEC</code>是个整型，计算的时候前后获取的clock()的值也都是整型，所以计算的时候当然会是0，只需要按浮点类型计算就可以了，比如下面<code>example.c</code>的实现。这里要注意的是，有些系统中<code>CLOCKS_PER_SEC</code>可能不是1000000，所以程序中计算还应让<code>CLOCKS_PER_SEC</code>参与。</p>
</blockquote>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&lt;time.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">double</span> <span class="n">begin</span><span class="p">,</span> <span class="n">end</span><span class="p">;</span>
    <span class="kt">double</span> <span class="n">duration</span><span class="p">;</span>
    <span class="n">begin</span> <span class="o">=</span> <span class="n">clock</span><span class="p">();</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">&#34;Hello, world!</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
    <span class="n">end</span> <span class="o">=</span> <span class="n">clock</span><span class="p">();</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">&#34;begin clock: %0.f</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">begin</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">&#34;end clock: %0.f</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">end</span><span class="p">);</span>
    <span class="n">duration</span> <span class="o">=</span> <span class="p">(</span><span class="n">end</span> <span class="o">-</span> <span class="n">begin</span><span class="p">)</span> <span class="o">/</span> <span class="n">CLOCKS_PER_SEC</span> <span class="o">*</span> <span class="mf">1000.0</span><span class="p">;</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">&#34;print time: %.3f ms&#34;</span><span class="p">,</span> <span class="n">duration</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="err">➜</span>  <span class="n">duration</span> <span class="p">.</span><span class="o">/</span><span class="n">example</span>
<span class="n">Hello</span><span class="p">,</span> <span class="n">world</span><span class="o">!</span>
<span class="n">begin</span> <span class="nl">clock</span><span class="p">:</span> <span class="mi">2056</span>
<span class="n">end</span> <span class="nl">clock</span><span class="p">:</span> <span class="mi">2084</span>
<span class="n">print</span> <span class="nl">time</span><span class="p">:</span> <span class="mf">0.028</span> <span class="n">ms</span>
</code></pre></div><h2 id="time函数"><code>time()</code>函数</h2>
<p><code>time()</code>函数来获得当前日历时间，是从一个标准时间点（一般是1970年1月1日0时0分0秒）到现在的时间，单位为秒。函数声明:</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">time_t</span> <span class="nf">time</span><span class="p">(</span><span class="n">time_t</span> <span class="o">*</span> <span class="n">timer</span><span class="p">);</span> 
</code></pre></div><p><code>time_t</code>也是一个长整型类型，函数中如果<code>timer</code>为<code>NULL</code>或遇到错误返回结果-1，如果不为<code>NULL</code>，则会把值保存在<code>timer</code>中。</p>
<h3 id="试一下">试一下</h3>
<p>编写程序<code>example2.c</code>使用延时函数<code>sleep()</code>进行测试：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="c1">// time()方法精度只有1s
</span><span class="c1"></span><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&lt;time.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span>
    <span class="n">time_t</span> <span class="n">t1</span><span class="p">,</span> <span class="n">t2</span><span class="p">;</span>
    <span class="n">time</span><span class="p">(</span><span class="o">&amp;</span><span class="n">t1</span><span class="p">);</span>
    <span class="n">sleep</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
    <span class="n">time</span><span class="p">(</span><span class="o">&amp;</span><span class="n">t2</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">&#34;t1: %ld, t2: %ld, sleep: %ld s&#34;</span><span class="p">,</span> <span class="n">t1</span><span class="p">,</span> <span class="n">t2</span><span class="p">,</span> <span class="n">t2</span> <span class="o">-</span> <span class="n">t1</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>输出结果如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  duration ./example2
t1: 1509401991, t2: 1509401994, sleep: <span class="m">3</span> s
</code></pre></div><p>这里要注意的是编译的时候<code>gcc</code>默认使用标准<code>c99</code>，而这个标准下<code>sleep()</code>无效，这里采用<code>c89</code>标准进行编译即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">gcc -std<span class="o">=</span>c89 -o example2 example2.c
</code></pre></div><h3 id="注意-1">注意</h3>
<blockquote>
<p>此种方法的精度只有1s！</p>
</blockquote>
<h2 id="gettimeofday函数"><code>gettimeofday()</code>函数</h2>
<p><code>gettimeofday()</code>函数在<code>sys/time.h</code>中，声明如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">int</span> <span class="nf">gettimeofday</span><span class="p">(</span><span class="k">struct</span> <span class="n">timeval</span><span class="o">*</span><span class="n">tv</span><span class="p">,</span> <span class="k">struct</span> <span class="n">timezone</span> <span class="o">*</span><span class="n">tz</span> <span class="p">);</span>
</code></pre></div><p>其中形参对应结构体如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="k">struct</span>  <span class="n">timeval</span> <span class="p">{</span>
    <span class="kt">long</span>  <span class="n">tv_sec</span><span class="p">;</span>
    <span class="kt">long</span>  <span class="n">tv_usec</span><span class="p">;</span>
<span class="p">}</span><span class="err">；</span>

<span class="k">struct</span>  <span class="n">timezone</span><span class="p">{</span>
    <span class="kt">int</span> <span class="n">tz_minuteswest</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">tz_dsttime</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div><p>我们这里只关心时间差，所以只需要传入定义好的<code>timeval</code>结构体就好，<code>timezone</code>可以为<code>NULL</code>，每次调用函数，会把时间戳记录在传入的<code>timeval</code>结构体变量指针中，其中:</p>
<ul>
<li><code>tv_sec</code>存储的是s；</li>
<li><code>tv_usec</code>存储是us；</li>
</ul>
<h3 id="实例测试">实例测试</h3>
<p>那么可以编写程序<code>example3.c</code>进行测试了：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&lt;sys/time.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">timeval</span> <span class="n">start</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">timeval</span> <span class="n">end</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">duration</span><span class="p">;</span>
    <span class="n">gettimeofday</span><span class="p">(</span><span class="o">&amp;</span><span class="n">start</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">&#34;Hello,World!</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
    <span class="n">gettimeofday</span><span class="p">(</span><span class="o">&amp;</span><span class="n">end</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
    <span class="n">duration</span> <span class="o">=</span> <span class="mi">1000000</span> <span class="o">*</span> <span class="p">(</span><span class="n">end</span><span class="p">.</span><span class="n">tv_sec</span> <span class="o">-</span> <span class="n">start</span><span class="p">.</span><span class="n">tv_sec</span><span class="p">)</span><span class="o">+</span> <span class="n">end</span><span class="p">.</span><span class="n">tv_usec</span><span class="o">-</span><span class="n">start</span><span class="p">.</span><span class="n">tv_usec</span><span class="p">;</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">&#34;%ld us:&#34;</span><span class="p">,</span> <span class="n">duration</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>最终结果为：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">➜  duration ./example3
Hello,World!
<span class="m">35</span> us:%
</code></pre></div><h3 id="注意-2">注意</h3>
<blockquote>
<p>精度为us；</p>
</blockquote>
<h2 id="总结">总结</h2>
<p>综上所述，<code>clock()</code> 和 <code>gettimeofday()</code> 方法均可以达到us的精度，但是 <code>clock()</code> 方法依赖于宏定义 <code>CLOCKS_PER_SEC</code>，不过最终一般是us级别；而 <code>time()</code> 方法精度只有秒级，所以适合较大的计算过程。</p>
<h2 id="参考">参考</h2>
<p>感谢<a href="http://baige5117.github.io/"><strong>向日葵花</strong></a>: <a href="http://baige5117.github.io/blog/calculate_the_running_time_of_algorithm.html">Linux/Unix 环境下实现精确计算程序运行的时间</a></p>]]></content>
		</item>
		
		<item>
			<title>数学知识复习</title>
			<link>https://blog.5km.studio/2017/10/27/mathknowlege/</link>
			<pubDate>Fri, 27 Oct 2017 08:51:23 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/10/27/mathknowlege/</guid>
			<description>&lt;p&gt;记得高中的时候数学能力还是很突出的，但到了大学，高数好像离十里越来越远。过去这么长时间，已经有了定论：十里的数学知识已还给老师！这还真是让人崩溃，现在无处不数学，当然为了研究算法也得把高数给拾回来，所以有了本篇数学知识的复习。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>记得高中的时候数学能力还是很突出的，但到了大学，高数好像离十里越来越远。过去这么长时间，已经有了定论：十里的数学知识已还给老师！这还真是让人崩溃，现在无处不数学，当然为了研究算法也得把高数给拾回来，所以有了本篇数学知识的复习。</p>
<p>此篇应该会一直补充，十里会边学习边丰富文章内容，一方面根据需求复习数学的基础知识，另一方面锻炼整理能力。</p>
<p>虽然部分知识很简单，也在这里一并整理上，主要参考：<strong>《数据结构与算法分析——C语言描述》</strong></p>
<h2 id="指数">指数</h2>
<p>$$
\begin{aligned}
&amp; X^AX^B=X^{A+B}\newline
&amp; \frac{X^A}{X^B}=X^{A-B}\newline
&amp; (X^A)^B=X^{AB}\newline
&amp; X^N+X^N=2X^N \ne X^{2N}\newline
&amp; 2^N+2^N=2^{N+1}
\end{aligned}
$$</p>
<h2 id="对数">对数</h2>
<p>除了特殊说明，所有的对数均以2为底数。</p>
<p>$$
\begin{aligned}
&amp; \log_AB=\frac{\log_CB}{\log_CA}\newline
&amp; \log AB=\log A + \log B\newline
&amp; \log\frac{A}{B}=\log A - \log B\newline
&amp; \log (A^B)=B\log A\newline
&amp; \log X &lt; X(对所有的X&gt;0成立。)
\end{aligned}
$$</p>
<h2 id="级数">级数</h2>
<h3 id="几何级数">几何级数</h3>
<p>公式</p>
<p>$$\begin{equation}
\sum_{i=0}^N A^i=\frac{A^{N+1}-1}{A-1}
\end{equation}\label{eq1}$$</p>
<p>分两种情况讨论：</p>
<ol>
<li>
<p>$A = 1$</p>
<p>此时，有：</p>
<p>$$\begin{equation}
\sum_{i=0}^N A^i\xrightarrow[]{A=1}\sum_{i=0}^N 1=N
\end{equation}\label{eq4}$$</p>
</li>
<li>
<p>$A \ne 1$</p>
<p>公式 $\ref{eq1}$ 证明：</p>
<p>$$
S=1+A+A^2+A^3+A^4+\dots+A^N
$$</p>
<p>$AS=A\times S+1-1$, 有:</p>
<p>$$
\begin{aligned}
&amp; AS=1+A+A^2+A^3+A^4+\dots+A^N+A^{N+1}-1\newline
&amp; \therefore (A-1)S=AS-S=A^{N+1}-1\newline
&amp; \therefore S=\frac{A^{N+1}-1}{A-1}
\end{aligned}
$$</p>
<p>那么可得：</p>
<p>$$
\sum_{i=0}^N A^i=\frac{A^{N+1}-1}{A-1}
$$</p>
<p>特别的，当 $A=2$ 时，有:</p>
<p>$$
\sum_{i=0}^N 2^i=2^{N+1}-1
$$</p>
<p>另外当 $0&lt;A&lt;1$ 时，由 $\ref{eq1}$ 公式还可以得到公式 $\ref{eq2}$:</p>
<p>$$\begin{equation}
\sum_{i=0}^N A^i \le \frac{1}{1-A}
\end{equation}\label{eq2}$$</p>
<p>公式 $\ref{eq2}$ 表示了 $N\rightarrow\infty$ 值趋近于 $\frac{1}{1-A}$  简单证明过程如下：</p>
<p>$$
\begin{aligned}
&amp; S = 1 + A + A^2 + A^3 + A^4 + \dots \newline
&amp; AS = A + A^2 + A^3 + A^4 + \dots \newline
&amp; \because 0&lt;A&lt;1 \newline
&amp; \therefore S-AS \le 1 \newline
&amp; \therefore \sum_{i=0}^N A^i = S \le \frac{1}{1-A}
\end{aligned}
$$</p>
</li>
</ol>
<h3 id="算术级数">算术级数</h3>
<p>另一种常用类型的级数是算术级数，任何如下公式 $\ref{eq3}$ 的级数都可以通过基本公式计算值。</p>
<p>$$\begin{equation}
\sum_{i=1}^N i=\frac{N(N+1)}{2} \approx \frac{N^2}{2}
\end{equation}\label{eq3}$$</p>
<p>比如，$1+5+9+13+\dots+(4N-3)$ 有：</p>
<p>$$\begin{equation}
\sum_{i=1}{^N} 4N-3=N(2N-1)
\end{equation}\label{eq5}$$</p>
<p>公式 $\ref{eq5}$ 计算过程依赖于公式 $\ref{eq4}$ 和 $\ref{eq3}$:</p>
<p>$$
\begin{aligned}
\sum_{i=1}{^N} 4N-3&amp;=1+5+9+13+\dots+(4N-3) \newline
&amp;=4(1+2+3+\dots+N)-3(1+1+1+\dots+1) \newline
&amp;=\frac{4N(N+1)}{2}-3N \newline
&amp;=N(2N-1)
\end{aligned}
$$</p>
<h4 id="平方和公式">平方和公式</h4>
<p>不太常见的还有平方和公式，又叫四角锥数或金字塔数，如公式 $\ref{eq6}$：</p>
<p>$$\begin{equation}
\sum_{i=1}^N i^2=\frac{N(N+1)(2N+1)}{6}
\end{equation}\label{eq6}$$</p>
<p>这里简单说一下恒等式方法证明公式 $\ref{eq6}$ 的过程：</p>
<p>已知 $(N+1)^3=N^3+3N^2+3N+1$，那么可得：</p>
<p>$$
\begin{aligned}
(N+1)^3-N^3&amp;=3N^2+3N+1 \newline
N^3-(N-1)^3&amp;=3(N-1)^2+3(N-1)+1 \newline
(N-1)^3-(N-2)^3&amp;=3(N-2)^2+3(N-2)+1 \newline
&amp;\ \vdots \newline
2^3-1^3&amp;=3\times 1^2+3\times 1 + 1
\end{aligned}
$$</p>
<p>恒等式左右相加可得:</p>
<p>$$
\begin{aligned}
(n+1)^3-1&amp;=3(1^2+2^2+3^2+\dots+N^2)+3(1+2+3+\dots+N)+N \newline
&amp;\downarrow \newline
1^2+2^2+3^2+\dots+N^2i&amp;=\frac{(N+1)^3-\frac{3N(N+1)}{2}-N-1}{3}
\end{aligned}
$$</p>
<p>最终整理可得:</p>
<p>$$
\sum_{i+1}^N i^2=1+2^2+3^2+\dots+N^2=\frac{N(N+1)(2N+1)}{6}
$$</p>
<p>其它证明方法可参考:<a href="https://baike.baidu.com/item/%E5%B9%B3%E6%96%B9%E5%92%8C%E5%85%AC%E5%BC%8F/3264126?fr=aladdin">平方和公式</a></p>
<h4 id="冯哈伯公式faulhabers-formula">冯哈伯公式(Faulhaber&rsquo;s formula)</h4>
<p>平方和公式是冯哈伯公式(Faulhaber&rsquo;s formula<a href="https://en.wikipedia.org/wiki/Faulhaber%27s_formula">2</a>)的一个特例，可知有如下有近似估计公式:</p>
<p>$$
\sum_{i=1}^N i^k \approx \frac{N^{k+1}}{\vert k+1 \vert},\ k\ne-1
$$</p>
<p>可以看到上述公式的条件为 $k\ne-1$，$k=-1$ 时公式 $\ref{eq6}$ 是不成立的，这时需要用到一个据说在计算机领域经常用到的公式：</p>
<p>$$
H_N=\sum_{i=1}^N \frac{1}{i} \approx \log_eN
$$</p>
<p>$H_N$ 叫做调和数，上述近似式的误差趋近于 $\gamma\approx 0.57721566$，这个数值称为欧拉常数(Euler&rsquo;s constant)</p>
<h3 id="代数运算">代数运算</h3>
<p>$$
\begin{aligned}
&amp; \sum_{i=1}^N f(N)=Nf(N)\newline
&amp; \sum_{i=n_{0}}^N f(i)=\sum_{i=1}^N f(i) - \sum_{i=1}^{n_{0}-1} f(i)
\end{aligned}
$$</p>
<h2 id="证明方法">证明方法</h2>
<p>数据结构及算法分析中针对结论常用的的证明方法有归纳法和反证法。</p>
<h3 id="归纳法">归纳法</h3>
<p>归纳法有两个标准的部分：</p>
<ul>
<li><strong>证明基准情形</strong>：确定定理对于某些值的正确性；</li>
<li><strong>归纳假设</strong></li>
</ul>
<p>以公式 $\ref{eq6}$ 为例进行说明，归纳法证明如下。</p>
<p>对于基准情形，定理当 $N=1$ 时代入数可知成立；对于归纳假设，假设公式 $\ref{eq6}$ 对于 $1 \le k \le N$ 成立，在此情况下，只需证明 $N+1$ 时公式同样成立即可。</p>
<p>如果 $N\ge1$, 易得：</p>
<p>$$
\sum_{i=1}^{N+1} i^2=\sum_{i=1}^{N} i^2 + (N+1)^2
$$</p>
<p>代入公式 $\ref{eq6}$:</p>
<p>$$
\begin{aligned}
\sum_{i=1}^{N+1} i^2&amp;=\frac{N(N+1)(2N+1)}{6}+(N+1)^2 \newline
&amp;=\frac{(N+1)(N+2)(2N+3)}{6} \newline
&amp;=\frac{(N+1)[(N+1)+1][2(N+1)+1]}{6}
\end{aligned}
$$</p>
<p>可以看到, $N+1$ 时符合，定理得证。</p>
<h3 id="反证法">反证法</h3>
<p>通过证明定理不成立，然后证明该假设导致某个已知的性质不成立，从而说明愿假设是错误的。</p>]]></content>
		</item>
		
		<item>
			<title>comtrade文件解析</title>
			<link>https://blog.5km.studio/2017/10/24/comtrade/</link>
			<pubDate>Tue, 24 Oct 2017 06:43:36 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/10/24/comtrade/</guid>
			<description>&lt;p&gt;工作中接触了comtrade这个东西，尤其最近研究故障判断算法，直接接触的就是comtrade波形文件，目前十里研究算法使用python的科学计算包&lt;code&gt;Anaconda&lt;/code&gt;，为了方便获取波形数据和快速查看波形，用python实现了comtrade模块和comtrade波形查看命令行工具。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>工作中接触了comtrade这个东西，尤其最近研究故障判断算法，直接接触的就是comtrade波形文件，目前十里研究算法使用python的科学计算包<code>Anaconda</code>，为了方便获取波形数据和快速查看波形，用python实现了comtrade模块和comtrade波形查看命令行工具。</p>
<h2 id="comtrade介绍">comtrade介绍</h2>
<p>COMTRADE是英文Common format for transient data exchange (COMTRADE) for power systems 的简写，中文一般称为电力系统瞬态数据交换的通用格式。IEEE为了解决数字故障录波装置、数字保护、微机测试装置之间的数据交换问题，与1991年提出了这个标准，并于1999年进行了修订和完善。该标准是一种公用的数据传输格式标准，为不同厂家生产的设备所遵循。每个COMTRADE记录最多包含四个相关的文件。这些文件的文件名相同，但是扩展名不同。四个文件分别是:</p>
<ol>
<li>标题文件(.hdr)</li>
<li>配置文件(.cfg)</li>
<li>数据文件(.dat)</li>
<li>信息文件(.inf)</li>
</ol>
<p>目前国内产品专检等一些检定机构默认只使用cfg和dat文件，所以这里只讨论cfg和dat文件。</p>
<h3 id="cfg文件">cfg文件</h3>
<p>配置文件为一种ASCII文本文件，用于正确地说明数据（.DAT）文件的格式，因此必须以一种具体的格式保存。该文件诠释了数据（.DAT）文件所包含信息，其中包括诸如采样速率、通道数量、频率、通道信息等项。 配置文件第一行中的一个字段识别文件所依照的COMTRADE标准版本的年份（例如1991、1999等）。如果该字段不存在或是空的，则假设文件则遵照标准的最初发行日期（1991）。配置文件还包含识别伴随的数据文件是以ASCII格式还是以二进制格式存储的字段。</p>
<h3 id="dat文件">dat文件</h3>
<p>正如cfg文件中介绍的，dat文件可以是ascii或binary格式。数据文件包含记录中每个采样所有输入通道的值。数据文件包含一个顺序号和每次采样的时间标志。这些采样值除记录模拟输入的数据之外，也记录状态，即表示开/关信号的输入。</p>
<h2 id="解析实现">解析实现</h2>
<p>因为这是一个标准，所以一定是有格式的，首先按照格式解析cfg文件中的每一行文本，对应解析出信息，这些信息用于解析dat文件。刚说的这种格式，可以网上查找相应标准文件，其中很详细的介绍了cfg和dat文件的格式，比如十里手上的<a href="https://pan.baidu.com/s/1kVwy7OF">IEEE Standard Common Format for Transient Data Exchange (COMTRADE) for Power Systems IEEE Std C37.111-19</a>（密码: hu4x），本文实现的comtrade解析模块根据这个标准只做了cfg解析和二进制的dat文件解析。</p>
<p>实现项目已托管到github平台：<a href="https://github.com/smslit/comtrade">comtrade</a>。</p>
<p>另外，为了方便同事进行matlab的算法仿真，十里又用c#编写了comtrade文件转csv文件的小工具，毕竟matlab更容易导入csv文件，该工程也托管到了github平台：<a href="https://github.com/smslit/ComtradeGo">ComtradeGo</a>。</p>]]></content>
		</item>
		
		<item>
			<title>欧几里得算法</title>
			<link>https://blog.5km.studio/2017/10/22/gcd/</link>
			<pubDate>Sun, 22 Oct 2017 20:47:49 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/10/22/gcd/</guid>
			<description>&lt;p&gt;天杀的，原来我现在都蠢到连最大公约数的求解算法理解都这么费劲，欧几里得算法又称辗转相除法，用于计算两个数的最大公约数。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>天杀的，原来我现在都蠢到连最大公约数的求解算法理解都这么费劲，欧几里得算法又称辗转相除法，用于计算两个数的最大公约数。</p>
<h2 id="简介">简介</h2>
<p>多应用领域在计算机和数学两个领域，计算公式为：</p>
<p>$$
gcd(a, b) = gcd(b, mod(a, b)), a &gt; b且a和b为正整数
$$</p>
<p>古希腊数学家欧几里德在其著作《The Elements》中最早描述了这种算法, 所以被命名为欧几里德算法。算法依赖于下面的定理：</p>
<blockquote>
<p>两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数。最大公约数（Greatest Common Divisor）缩写为GCD。</p>
</blockquote>
<h2 id="证明">证明</h2>
<p>两种证明定理的思路。</p>
<p>若整数 $d$  整除整数 $a$ ，记作 $d\vert a$。</p>
<h3 id="方法1">方法1</h3>
<p>设:</p>
<p>$$
\begin{aligned}
&amp; r = \ a\ \boldsymbol{\mathrm{mod}}\ b, 有r = a - kb\\<br>
&amp; d 是 a和b 的公约数, 记: d \vert a, d \vert b
\end{aligned}
$$</p>
<p>那么，</p>
<p>$$
\begin{aligned}
&amp; r = a - kb, 等式两边同时除以d \Longrightarrow \frac{r}{d}=\frac{a}{d}-\frac{kb}{d}\\<br>
&amp; \because d \vert a且 d \vert b\\<br>
&amp; \therefore d \vert r
\end{aligned}
$$</p>
<p>所以，$d$ 也是 $b$ 和 $a\ \boldsymbol{\mathrm{mod}}\ b$ 的公约数。</p>
<p>设:</p>
<p>$$
\begin{aligned}
&amp; r = \ a\ \boldsymbol{\mathrm{mod}}\ b, 有r = a - kb\\<br>
&amp; d 是 a和a\ \boldsymbol{\mathrm{mod}}\ b的公约数, 记: d \vert a, d \vert (a-kb)
\end{aligned}
$$</p>
<p>那么，</p>
<p>$$
d \vert a即，d也是a的约数
$$</p>
<p>综上，$a$ 和 $b$ 的公约数与b和 $a\ \boldsymbol{\mathrm{mod}}\ b$ 的公约数是一致的，那么最大公约数也是一样。</p>
<h3 id="方法2">方法2</h3>
<p>令:</p>
<p>$$
c = gcd(a, b) \xrightarrow[]{m,n为整数} \begin{cases}a=mc\\b=nc\end{cases}
$$</p>
<p>有,</p>
<p>$$
\begin{aligned}
r&amp;=a-kb\\<br>
&amp;=mc-knc\\<br>
&amp;=(m-kn)c
\end{aligned}
$$</p>
<p>$(m-kn)$ 为整数，那么有c整除余数r，即:</p>
<p>$$
c \vert r
$$</p>
<p>下面证明 $c = gcd(b, r)$:</p>
<p>设$m-kn$ 和 $n$ 有大于1的公约数$d$，即</p>
<p>$$
\begin{cases}m-kn&amp;=xd\\n&amp;=yd\end{cases} \Longrightarrow \begin{aligned}m&amp;=kn+xd\\&amp;=kyd+xd\\&amp;=(ky+x)d\end{aligned}
$$</p>
<p>$$
\therefore
\begin{cases}
a&amp;=mc=(ky+x)cd\\<br>
b&amp;=nc=ycd
\end{cases}
$$</p>
<p>那么有$cd \vert a 且 cd \vert b$，这与$c$ 是 $a$ 和 $b$ 的最大公约数是矛盾的。也就是说不存在 $d&gt;1$ 满足上述过程，也就是说有：</p>
<p>$$
c=gcd(b, r)
$$</p>
<p>综上可得：</p>
<p>$$
c=gcd(a, b)=gcd(b, r)
$$</p>
<h2 id="实现思路">实现思路</h2>
<p>实现思路就是，不断的取模求最大公约数，直到整除为止就能得到最终要求解最大公约数。</p>
<ol>
<li>
<p>令 $r = a\ \boldsymbol{\mathrm{mod}}\ b, (0 \le r &lt; b )$
若 $r=0$ 算法结束, $b$ 就是最终结果</p>
</li>
<li>
<p>算法过程: $a \leftarrow b, b \leftarrow r$，返回步骤1</p>
</li>
</ol>
<h2 id="具体实现">具体实现</h2>
<h3 id="c版本">c版本</h3>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">int</span> <span class="nf">gcd</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">int</span> <span class="n">r</span><span class="p">;</span>
    <span class="k">while</span><span class="p">(</span><span class="n">b</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">r</span> <span class="o">=</span> <span class="n">a</span> <span class="o">%</span> <span class="n">b</span><span class="p">;</span>
        <span class="n">a</span> <span class="o">=</span> <span class="n">b</span><span class="p">;</span>
        <span class="n">b</span> <span class="o">=</span> <span class="n">r</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">a</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><h3 id="c版本-1">c++版本</h3>
<div class="highlight"><pre class="chroma"><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="nf">gcd</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">a</span> <span class="o">&lt;</span> <span class="n">b</span><span class="p">)</span> <span class="n">std</span><span class="o">::</span><span class="n">swap</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">b</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="nl">a</span> <span class="p">:</span> <span class="n">gcd</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="n">a</span> <span class="o">%</span> <span class="n">b</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div><h3 id="python版本">python版本</h3>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">gcd</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
    <span class="k">while</span> <span class="n">a</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
        <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">b</span> <span class="o">%</span> <span class="n">a</span><span class="p">,</span> <span class="n">a</span>
    <span class="k">return</span> <span class="n">b</span>
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>编程工具的选择引发的思考--至停滞不前的自己</title>
			<link>https://blog.5km.studio/2017/09/23/coding-editor/</link>
			<pubDate>Sat, 23 Sep 2017 15:13:34 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/09/23/coding-editor/</guid>
			<description>&lt;p&gt;最近有了一些感悟，十里惊恐的看到自己在很多不必要的事情上浪费了太多时间!比如编辑工具和开发平台的选择！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>最近有了一些感悟，十里惊恐的看到自己在很多不必要的事情上浪费了太多时间!比如编辑工具和开发平台的选择！</p>
<p>看到别人都在进步，而我却固步自封，别人在自己从事的领域越走越高，而我走走停停，自己喜欢编程，回头一想，自己在编程工具的配置和选择上下的功夫相较于程序开发本身多太多，十里逐渐认为自己是多么的肤浅！</p>
<h3 id="自己正在减速">自己正在减速</h3>
<p>最近几天，听说了不少令人高兴同时也令人嫉妒的事情，几个认识的人，是自己周围的人，&ldquo;帮&quot;我实现了出国梦。十里自小有着出国深造的梦想，奈何自己的学习觉悟不高，最终只能拿其他理由安慰自己，曾经自诩比别人能力强的十里，慢慢的被别人甩在身后，不是别人跑得快了，而是自己正在减速，甚至停滞不前！</p>
<p>在学校的时候，因为自己了解的东西比较多，同学看到给出美好的赞许，我却信以为真，自己做的并没有深度，钻研一些并没有太多价值的东西，不过好的一点是自学能力得到完美的体现。工作了一段时间，发现那些原本在学校并没有目标的同学也渐渐因为有了工作，虽然以挣钱为目标，但他们也是保持着兴趣一点点成长，比我进步的速度不值快多少倍！</p>
<p>有一位同事，向我展示了自己在学校做的软件，一个压缩包里几十甚至上百个小工具！而他是用的开发工具便是我嗤之以鼻的windows下用C#开发的，我当时出了赞赏，更多的是自我羞愧：十里在学校的时候，有多少作品呢，软件开发上什么都去了解了，了解了多种开发语言，然后做了多少成果呢，nothing！太可悲了！太弱了！</p>
<h3 id="错误的观念">错误的观念</h3>
<p>拿编辑器的选择来说，编辑器是用来干什么的呢？是用来搬砖的，最终还是要创造自己的软件作品的，然而为何我依旧执着于编辑器的选择和系统的选择上呢？其中作祟的是自己内心肮脏的鄙视链，回头看看我的博文，绝大部分还是讲的开发环境的配置呀、工具的配置，然而真正对程序员有营养的东西太少了，不禁又觉得自己肤浅之极了。</p>
<p>我有什么资格去鄙视别人使用工具和系统平台呢？我并没有很多拿得出手的程序作品，自己在编程工具的选择、配置上纠结且饮酒自醉了！一口一个瘟到死（windows），一口一个都是渣渣，十里自惭形秽，哪里有洞可以钻，快告诉我！</p>
<p>十里并没有选择最适合自己的工具，反而执念于对自己来说效率并不高的vim，纠结于插件的配置，改过来改过去，虽然要有钻研精神，但十里应该是做过了。<code>vim就是唯一</code>的观念显然是错的，其实它也只能适用于部分场合，并不是万能的！</p>
<p>有自己的观点是对的，但一直唠叨，将精力投注于并不重要的事情上是悲剧的！所以，十里对自己使命本末倒置的太久了，该觉醒了！</p>
<p>十里自诩是全能的，对这种无聊的假象迷惑的太久，麻木的我已经感受到现实的痛！什么都去了解，什么都不精通，这就是眼前的可怜人！</p>
<h3 id="该做的不是忏悔而是觉醒">该做的不是忏悔而是觉醒</h3>
<p>沉睡的雄狮，一旦觉醒，必定是震撼的！现在该做的不是忏悔，而是觉醒！也许现在就是该做出改变的时候了：</p>
<ol>
<li>适合自己的才是最好的：找到横扫每一份工作的配套利剑，而不是执着一个情怀假衣中的“圣剑！</li>
<li>知道的不如了解的，了解的比不上精通的，精通的才像是万能的！</li>
<li>抓住重点，聚焦追求！生活中太多干扰，克制没必要的强迫症，所谓无聊的情怀莫过于平庸的倔强，思进取，更应该执着于有价值、有深度、有高度的事情上！</li>
</ol>]]></content>
		</item>
		
		<item>
			<title>解决mac终端ssh登录linux中文乱码问题</title>
			<link>https://blog.5km.studio/2017/09/07/mac-ssh-linux-encoding/</link>
			<pubDate>Thu, 07 Sep 2017 16:34:12 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/09/07/mac-ssh-linux-encoding/</guid>
			<description>&lt;p&gt;mac下ssh远程登录VPS的centos发现中文竟然乱码，想必是系统编码的问题。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>mac下ssh远程登录VPS的centos发现中文竟然乱码，想必是系统编码的问题。</p>
<h4 id="在linux下查看编码">在linux下查看编码：</h4>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback"># root @ vps in ~ [4:29:47]
$ locale
LANG=C
LC_CTYPE=C
LC_NUMERIC=&#34;C&#34;
LC_TIME=&#34;C&#34;
LC_COLLATE=&#34;C&#34;
LC_MONETARY=&#34;C&#34;
LC_MESSAGES=&#34;C&#34;
LC_PAPER=&#34;C&#34;
LC_NAME=&#34;C&#34;
LC_ADDRESS=&#34;C&#34;
LC_TELEPHONE=&#34;C&#34;
LC_MEASUREMENT=&#34;C&#34;
LC_IDENTIFICATION=&#34;C&#34;
LC_ALL=
</code></pre></div><h4 id="在macos下查看编码">在macOS下查看编码</h4>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">➜  smslit locale
LANG=&#34;zh_CN.UTF-8&#34;
LC_COLLATE=&#34;zh_CN.UTF-8&#34;
LC_CTYPE=&#34;zh_CN.UTF-8&#34;
LC_MESSAGES=&#34;zh_CN.UTF-8&#34;
LC_MONETARY=&#34;zh_CN.UTF-8&#34;
LC_NUMERIC=&#34;zh_CN.UTF-8&#34;
LC_TIME=&#34;zh_CN.UTF-8&#34;
LC_ALL=
</code></pre></div><h4 id="解决">解决</h4>
<ol>
<li>远程登录VPS；</li>
<li>编辑.zshrc，添加如下内容:
export LC_ALL=zh_CN.UTF-8
export LANG=zh_CN.UTF-8</li>
<li>关闭远程登录，重新登录VPS，发现中文不会乱码。</li>
</ol>]]></content>
		</item>
		
		<item>
			<title>WSL(windows subsytem linux)下添加网络浏览器支持</title>
			<link>https://blog.5km.studio/2017/09/02/wsl-webbrowser/</link>
			<pubDate>Sat, 02 Sep 2017 13:41:03 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/09/02/wsl-webbrowser/</guid>
			<description>&lt;p&gt;windows下在subsystem linux中安装了Anaconda，在使用jupyter notebook时，发现命令行并不能自动打开浏览器，原来是子系统linux中没有可用网页浏览器，本文就讲述一下如何在WSL中添加网络浏览器支持。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>windows下在subsystem linux中安装了Anaconda，在使用jupyter notebook时，发现命令行并不能自动打开浏览器，原来是子系统linux中没有可用网页浏览器，本文就讲述一下如何在WSL中添加网络浏览器支持。</p>
<h2 id="问题现象">问题现象</h2>
<p>WSL命令行中，在相应目录下使用jupyter notebook，会出现如下提示：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">➜  Algorithm_Research git:<span class="o">(</span>master<span class="o">)</span> jupyter notebook
<span class="o">[</span>I 10:44:16.783 NotebookApp<span class="o">]</span> Serving notebooks from <span class="nb">local</span> directory: /mnt/c/Users/smslit/Desktop/topscomm/Algorithm_Research
<span class="o">[</span>I 10:44:16.784 NotebookApp<span class="o">]</span> <span class="m">0</span> active kernels 
<span class="o">[</span>I 10:44:16.784 NotebookApp<span class="o">]</span> The Jupyter Notebook is running at: http://localhost:8888/?token<span class="o">=</span>000982665d732a5d7052fe3bbadb4346fe0b3fdbc8b87bb4
<span class="o">[</span>I 10:44:16.784 NotebookApp<span class="o">]</span> Use Control-C to stop this server and shut down all kernels <span class="o">(</span>twice to skip confirmation<span class="o">)</span>.
<span class="o">[</span>W 10:44:16.789 NotebookApp<span class="o">]</span> No web browser found: could not locate runnable browser.
<span class="o">[</span>C 10:44:16.789 NotebookApp<span class="o">]</span> 
    
    Copy/paste this URL into your browser when you connect <span class="k">for</span> the first time,
    to login with a token:
        http://localhost:8888/?token<span class="o">=</span>000982665d732a5d7052fe3bbadb4346fe0b3fdbc8b87bb4
</code></pre></div><p>其中可以看到<code>No web browser found: could not locate runnable browser</code>，这是因为win下启用了ubuntu bash后只是命令行的接口，但没有相应网络浏览器安装。</p>
<h2 id="意外发现">意外发现</h2>
<h3 id="wsl可以执行exe">WSL可以执行exe</h3>
<p>惊奇的发现wsl的命令行中可以调用exe，比如执行以下命令可以打开chrome浏览器，也就是说可以将windows中安装的浏览器注册给wsl。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">/mnt/c/Program<span class="se">\ </span>Files<span class="se">\ \(</span>x86<span class="se">\)</span>/Google/Chrome/Application/chrome.exe
</code></pre></div><h3 id="wsl中可以用命令行配置默认浏览器">WSL中可以用命令行配置默认浏览器</h3>
<p>如果我们直接用如下命令行配置网页浏览器，会提示没有安装浏览器，没有选项。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo update-alternatives --config x-www-browser
</code></pre></div><p>怎么办？这个时候想到了一种饶路子的方式，ubuntu下有配置浏览器选项优先级的命令，这个命令需要指明浏览器，此时指定win 下chrome的路径就可以了，试一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">➜  Downloads <span class="nb">cd</span> /mnt/c/Program<span class="se">\ </span>Files<span class="se">\ \(</span>x86<span class="se">\)</span>/Google/Chrome/Application
➜  Application ll
总用量 1.4M
dr-xr-xr-x <span class="m">0</span> root root  <span class="m">512</span> 8月  <span class="m">18</span> 08:31 60.0.3112.101
-r-xr-xr-x <span class="m">1</span> root root 1.3M 8月  <span class="m">11</span> 15:40 chrome.exe
-r-xr-xr-x <span class="m">1</span> root root  <span class="m">410</span> 8月  <span class="m">18</span> 08:31 chrome.VisualElementsManifest.xml
-r-xr-xr-x <span class="m">1</span> root root 121K 8月  <span class="m">14</span> 08:41 master_preferences
drwxrwxrwx <span class="m">0</span> root root  <span class="m">512</span> 8月  <span class="m">25</span> 18:14 SetupMetrics
➜  Application sudo update-alternatives --install /usr/bin/x-www-browser x-www-browser /mnt/c/Program<span class="se">\ </span>Files<span class="se">\ \(</span>x86<span class="se">\)</span>/Google/Chrome/Application/chrome.exe <span class="m">100</span>
update-alternatives: 使用 /mnt/c/Program Files <span class="o">(</span>x86<span class="o">)</span>/Google/Chrome/Application/chrome.exe 来在自动模式中提供 /usr/bin/x-www-browser <span class="o">(</span>x-www-browser<span class="o">)</span>
➜  Application sudo update-alternatives --config x-www-browser 
链接组 x-www-browser <span class="o">(</span>提供 /usr/bin/x-www-browser<span class="o">)</span>中只有一个候选项：/mnt/c/Program Files <span class="o">(</span>x86<span class="o">)</span>/Google/Chrome/Application/chrome.exe无需配置。
</code></pre></div><h2 id="jupyter-notebook">jupyter notebook</h2>
<p>此时再次执行jupyter notebook，惊喜，确实可以自动打开chrome呈现仿真目录：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">➜  Algorithm_Research git:(master) jupyter notebook                               
[I 10:59:17.800 NotebookApp] Serving notebooks from local directory: /mnt/c/Users/smslit/Desktop/topscomm/Algorithm_Research
[I 10:59:17.800 NotebookApp] 0 active kernels 
[I 10:59:17.800 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/?token=2d7f588a4341b73bc65fcc6c85aac0289677123ef1b8e24c
[I 10:59:17.800 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 10:59:17.807 NotebookApp] 
    
    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:
        http://localhost:8888/?token=2d7f588a4341b73bc65fcc6c85aac0289677123ef1b8e24c
[I 10:59:18.359 NotebookApp] Accepting one-time-token-authenticated connection from 127.0.0.1
[W 11:02:15.429 NotebookApp] 404 GET /static/components/preact/preact.min.js.map (127.0.0.1) 32.15ms referer=None
[W 11:02:15.433 NotebookApp] 404 GET /static/components/proptypes/index.js.map (127.0.0.1) 2.30ms referer=None
[W 11:02:15.436 NotebookApp] 404 GET /static/components/preact-compat/preact-compat.min.js.map (127.0.0.1) 2.09ms referer=None
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>vim也是platformio的IDE</title>
			<link>https://blog.5km.studio/2017/08/15/platformio-vim/</link>
			<pubDate>Tue, 15 Aug 2017 10:00:29 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/08/15/platformio-vim/</guid>
			<description>&lt;p&gt;知道platformio很久了，但是一直没时间玩耍，最近有时间了，又研究了一番，platformio也为vim用户提供了一些可用的姿势，本篇文章就分享一下vim与pio的配合好戏！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>知道platformio很久了，但是一直没时间玩耍，最近有时间了，又研究了一番，platformio也为vim用户提供了一些可用的姿势，本篇文章就分享一下vim与pio的配合好戏！</p>
<h2 id="初始化工程">初始化工程</h2>
<p>这里以<code>Arduino uno</code>为例，可以使用以下命令初始化工程，哦，对了，之前先切换到工程目录：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">pio init --ide vim -b &lt;ID&gt;
</code></pre></div><p>其中，<code>&lt;ID&gt;</code>代表板子的ID，可以通过命令<code>pio boards</code>列出所有的板子ID，选择自己使用的，所以可以执行以下命令初始化：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">pio init --ide vim -b uno
</code></pre></div><p>此时会发现生成了一下文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">.
├── .clang_complete
├── .gcc-flags.json
├── .gitignore
├── .pioenvs
│   ├── <span class="k">do</span>-not-modify-files-here.url
│   └── structure.hash
├── .travis.yml
├── lib
│   └── readme.txt
├── platformio.ini
└── src

<span class="m">3</span> directories, <span class="m">8</span> files
</code></pre></div><p>其中<code>.clang_complete</code>用于代码补全，后面会告诉你如何利用这个文件。</p>
<p>为了方便编译和上传等，可以创建文件<code>Makefile</code>，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-makefile" data-lang="makefile"><span class="nf">all</span><span class="o">:</span>
 	platformio -f -c vim run

<span class="nf">upload</span><span class="o">:</span>
	platformio -f -c vim run --target upload

<span class="nf">clean</span><span class="o">:</span>
	platformio -f -c vim run --target clean

<span class="nf">program</span><span class="o">:</span>
	platformio -f -c vim run --target program

<span class="nf">uploadfs</span><span class="o">:</span>
	platformio -f -c vim run --target uploadfs

<span class="nf">update</span><span class="o">:</span>
	platformio -f -c vim update
</code></pre></div><p>那么以后在工程目录下，执行<code>make all</code>就可以编译了，如此简单！</p>
<h2 id="添加源代码">添加源代码</h2>
<p>在目录<code>src</code>下新建文件<code>main.cpp</code>，内容可以添加如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c++" data-lang="c++"><span class="cp">#include</span> <span class="cpf">&lt;Arduino.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">&#34;What?&#34;</span><span class="p">);</span>
    <span class="n">delay</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">&#34;Hello, world!&#34;</span><span class="p">);</span>
    <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div><h2 id="编译工程">编译工程</h2>
<p>为了方便在vim下编辑代码时可以编译工程，这里可以添加快捷键，在<code>~/.vimrc</code> 中添加如下内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">nnoremap &lt;C-b&gt; :make&lt;CR&gt;
</code></pre></div><p>这样可以按快捷键<code>ctrl+b</code>编译工程了。</p>
<h2 id="代码补全">代码补全</h2>
<p>有两种方法，一个是使用<a href="https://github.com/Valloric/YouCompleteMe">YouCompleteMe</a>，一个是使用插件<a href="https://github.com/maralla/completor.vim">completor</a>，这两种方式任选其一即可，这里强烈建议使用后者 <strong>completor.vim</strong>，因为配置方便，安装轻量。</p>
<h3 id="completorvim">completor.vim</h3>
<p>如果您使用的vim版本是8.0+，那么可以看一下 <a href="/2018/06/25/vim-pack/">vim插件管理工具pack</a> 了解一款新的vim插件管理工具，当然也可以使用其他的管理工具，这里说一下使用 <strong>pack</strong> 安装，非常简单：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ pack install maralla/completor.vim
</code></pre></div><p>确保已经安装 <strong>clang</strong>，自行搜索安装方法。</p>
<p>查看<code>clang</code>安装目录：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">➜  which clang
/usr/bin/clang
</code></pre></div><p>最后一步，配置插件所需要的 <code>clang</code> 路径即可，可以在 <code>.vimrc</code>中添加配置：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">let</span> g:completor_clang_binary <span class="o">=</span> <span class="s1">&#39;/usr/bin/clang&#39;</span>
map  &lt;leader&gt;jj &lt;Plug&gt;CompletorCppJumpToPlaceholder
imap &lt;leader&gt;jj &lt;Plug&gt;CompletorCppJumpToPlaceholder
</code></pre></div><p>⚠️： <strong>clang</strong> 的目录配置是您电脑中clang的安装目录，可能与这里不同。</p>
<p>或者使用 <a href="https://github.com/maralla/pack">pack</a> 的配置方法：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">➜  pack config maralla/completor
</code></pre></div><p>此时会跳出vim来编辑配置，添加上面三行的配置内容保存退出即可。三行配置中后两行用来配置跳转占位符快捷键。</p>
<p>打开源码 <code>src/main.cpp</code> ，输入函数前几个字母就会有提示，选择相应函数， <strong>completor</strong> 在形参位置以占位符填充，按下配置中的快捷键就会在占位符间跳转，方便填写实参。</p>
<h3 id="youcompleteme">YouCOmpleteMe</h3>
<p>为VIM安装YCM插件，自行研究安装即可，<a href="https://github.com/oblitum/YouCompleteMe/tree/clang_complete-params">传送门在这里</a>。假设已经成功安装<code>YCM</code>，其中相应<code>.vimrc</code>中的配置如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">&#34; set the runtime path to include Vundle and initialize
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()
&#34; alternatively, pass a path where Vundle should install plugins
&#34;call vundle#begin(&#39;~/some/path/here&#39;)

&#34; let Vundle manage Vundle, required
Plugin &#39;VundleVim/Vundle.vim&#39;
Bundle &#39;Valloric/YouCompleteMe&#39;
Bundle &#39;rdnetto/YCM-Generator&#39;
&#34; The following are examples of different formats supported.
&#34; Keep Plugin commands between vundle#begin/end.

&#34; All of your Plugins must be added before the following line
call vundle#end()            &#34; required

&#34; for ycm
let g:ycm_error_symbol = &#39;&gt;&gt;&#39;
let g:ycm_warning_symbol = &#39;&gt;*&#39;
nnoremap &lt;leader&gt;gl :YcmCompleter GoToDeclaration&lt;CR&gt;
nnoremap &lt;leader&gt;gf :YcmCompleter GoToDefinition&lt;CR&gt;
nnoremap &lt;leader&gt;gg :YcmCompleter GoToDefinitionElseDeclaration&lt;CR&gt;
&#34;让YouCompleteMe同时利用原来的ctags
let g:ycm_collect_identifiers_from_tags_files = 1
nmap &lt;F4&gt; :YcmDiags&lt;CR&gt;
&#34; let g:loaded_youcompleteme = 1
</code></pre></div><p>插件需要文件<code>.ycm_extra_conf.py</code>，可以根据<strong>Anthony Ford</strong>提供的配置文件<a href="https://gist.github.com/ajford/f551b2b6fd4d6b6e1ef2">PlatformIO/YouCompleteMe Integration</a>修改我们工程需要的这个配置文件。先将这个文件下载到工程目录下，然后进行修改即可。</p>
<p>需要修改的地方有几处：</p>
<ul>
<li>libDirs中对应的几个库目录，也就是31、36、40这三行，对应自己使用的framwork目录就可以，不同MCU平台可能有不同，自行添加即可；</li>
<li>修改flags，<code>.ycm_extra_conf.py</code>中61～65这五行内容替换成正确的，这里需要参考上面提到的<code>.clang_complete</code>文件，文件中最后几行给出了flags；</li>
</ul>
<p>再次启动vim编辑<code>/src/main.cpp</code>，提示使用我们创建的<code>.ycm_extra_conf.py</code>，OK即可，然后就可以享受语义分析的代码补全了。</p>
<h2 id="写在最后">写在最后</h2>
<p>如果你也玩 <code>Arduino</code>，使用 <a href="http://platformio.org">platformio</a>进行开发，同时喜欢使用 <strong>vim</strong> 这款神级代码编辑器，那么本文应该会对您有所帮助，感谢您的阅读！</p>]]></content>
		</item>
		
		<item>
			<title>为嵌入式MCU工程建立完整的cscope和ctags索引</title>
			<link>https://blog.5km.studio/2017/06/18/embeddedVim/</link>
			<pubDate>Sun, 18 Jun 2017 13:54:31 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/06/18/embeddedVim/</guid>
			<description>&lt;p&gt;这几天深入了解了一下vim的神级插件&lt;a href=&#34;https://github.com/Valloric/YouCompleteMe&#34;&gt;YouCompleteMe&lt;/a&gt;，果然厉害，但是在MCU工程中函数跳转和语义补全就歇菜了，研究了几天也没找到很好的使用方法，最终还是无法在MCU嵌入式工程中跳转函数定义，这是无法忍受的，在PC或mac上的c/c++/clang工程可以完美使用。所以我还是老实的使用cscope和ctags吧，本篇文章就简单说一下，如何为MCU工程建立完整的索引，完成完美跳转！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>这几天深入了解了一下vim的神级插件<a href="https://github.com/Valloric/YouCompleteMe">YouCompleteMe</a>，果然厉害，但是在MCU工程中函数跳转和语义补全就歇菜了，研究了几天也没找到很好的使用方法，最终还是无法在MCU嵌入式工程中跳转函数定义，这是无法忍受的，在PC或mac上的c/c++/clang工程可以完美使用。所以我还是老实的使用cscope和ctags吧，本篇文章就简单说一下，如何为MCU工程建立完整的索引，完成完美跳转！</p>
<h2 id="mcu工程">MCU工程</h2>
<h3 id="现状">现状</h3>
<ul>
<li>macOS平台</li>
<li>mcu为siliconlab的EFM32单片机，EFM32JG1B200</li>
<li>编辑器vim</li>
</ul>
<p>安装了Silicon Labs官方的开发环境simplicity studio为EFM32的开发带来很大的方便，是很棒的开发工具，同时可以很方便的管理SDK和toolchain，十里却希望可以用vim完成代码编写和阅读，为了实现向开发环境中的各种跳转，十里决定使用cscope和ctags。建立工程依旧使用Simplicity Studio，建立好的工程会生成相应的makefile，这就免去了自己写makefile的麻烦！</p>
<h3 id="sdk和toolchains目录">SDK和Toolchains目录</h3>
<ul>
<li>SDK
<ul>
<li>&ldquo;/Applications/Simplicity Studio.app/Contents/Eclipse/developer/sdks/gecko_sdk_suite/v1.1/&rdquo;</li>
</ul>
</li>
<li>toolchain
<ul>
<li>&ldquo;/Applications/Simplicity Studio.app/Contents/Eclipse/developer/toolchains/gnu_arm/4.9_2015q3&rdquo;</li>
</ul>
</li>
</ul>
<h2 id="思路">思路</h2>
<p>先用find命令查找源文件和头文件，并建立索引文件cscope.files，然后通过索引文件创建数据库，这里要注意的是生成的索引文件中，每行代表一个文件的路径，如果路径中包含空格cscope会无法识别正确路径，所以需要用sed命令添加双引号进行处理，而ctags读取索引文件时会自动添加双引号所以应该在sed处理索引文件前先建立ctags数据库。</p>
<p>上述过程可以shell脚本的方式实现。</p>
<h2 id="shell脚本">shell脚本</h2>
<p>最终脚本如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="cp">#!/bin/bash
</span><span class="cp"></span>
list_sources<span class="o">()</span> <span class="o">{</span>
    <span class="nb">echo</span> <span class="s2">&#34;---&gt; Listing sources...&#34;</span>
	
    find	<span class="s2">&#34;/Applications/Simplicity Studio.app/Contents/Eclipse/developer/toolchains/gnu_arm/4.9_2015q3/arm-none-eabi/include&#34;</span> 	<span class="se">\
</span><span class="se"></span>			-path <span class="s2">&#34;/Applications/Simplicity Studio.app/Contents/Eclipse/developer/toolchains/gnu_arm/4.9_2015q3/arm-none-eabi/include/c++&#34;</span> -prune -o	<span class="se">\
</span><span class="se"></span>        	-type f -name <span class="s2">&#34;*.[chsS]&#34;</span> -print &gt; cscope.tmp

	find	<span class="s2">&#34;/Applications/Simplicity Studio.app/Contents/Eclipse/developer/sdks/gecko_sdk_suite/v1.1/platform/emlib/inc&#34;</span>			<span class="se">\
</span><span class="se"></span>			<span class="s2">&#34;/Applications/Simplicity Studio.app/Contents/Eclipse/developer/sdks/gecko_sdk_suite/v1.1/platform/CMSIS/Include&#34;</span>		<span class="se">\
</span><span class="se"></span>			<span class="s2">&#34;/Applications/Simplicity Studio.app/Contents/Eclipse/developer/sdks/gecko_sdk_suite/v1.1/platform/Device/SiliconLabs/EFM32JG1B/Include&#34;</span>	<span class="se">\
</span><span class="se"></span>			<span class="s2">&#34;/Applications/Simplicity Studio.app/Contents/Eclipse/developer/sdks/gecko_sdk_suite/v1.1/hardware/kit/common/bsp&#34;</span>		<span class="se">\
</span><span class="se"></span>			<span class="s2">&#34;/Applications/Simplicity Studio.app/Contents/Eclipse/developer/sdks/gecko_sdk_suite/v1.1/hardware/kit/common/drivers&#34;</span>	<span class="se">\
</span><span class="se"></span>			<span class="s2">&#34;.&#34;</span>	<span class="se">\
</span><span class="se"></span>        	-type f -name <span class="s2">&#34;*.[chsS]&#34;</span> -print &gt;&gt; cscope.tmp
<span class="o">}</span>

create_cscope_db<span class="o">()</span> <span class="o">{</span>
	<span class="c1">#因为目录中包含空格，所以需要将生成的文件索引中每行文件路径放到双引号中</span>
	sed <span class="s1">&#39;s/^/&#34;/;s/$/&#34;/&#39;</span> cscope.tmp &gt; cscope.files

    <span class="nb">echo</span> <span class="s2">&#34;---&gt; Creating cscope DB...&#34;</span>
    cscope -k -b -q -R -i cscope.files
<span class="o">}</span>

create_ctags_db<span class="o">()</span> <span class="o">{</span>
    <span class="nb">echo</span> <span class="s2">&#34;---&gt; Creating CTags DB...&#34;</span>
    ctags -L cscope.tmp
<span class="o">}</span>

cleanup<span class="o">()</span> <span class="o">{</span>
    <span class="nb">echo</span> <span class="s2">&#34;---&gt; Removing garbage...&#34;</span>
    rm -f cscope.files cscope.tmp
<span class="o">}</span>

list_sources
<span class="c1"># 因为ctags读取文件索引的时候会对每一行加上引号解决路径包含空格的问题，所以建立ctags库应该直接用加引号前的文件cscope.tmp</span>
create_ctags_db
create_cscope_db
cleanup
</code></pre></div><h2 id="结语">结语</h2>
<p>在相应的工程下运行这个脚本，就会建立完整文件的cscope和ctags索引，就可以happy地各种跳转了，包括SDK中的源码。</p>
<p>因为不可能就只用到EFM32这一个MCU平台，所以十里又写了一个脚本<a href="https://github.com/smslit/mktags">mktags</a>，其还可以建立Renesas平台的工程索引，这里只加了这一个，其实可以参考十里写的脚本很容易添加其他平台的，十里将其置于<code>/usr/local/bin</code>目录下，重启终端后就可以为所欲为的构建MCU工程的cscope和ctags索引了。</p>
<p>mktags帮助信息如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">Usage: /usr/local/bin/mktags SUB_COMMAND [MCU_NAME]
1. SUB_COMMAND:
    build: build the cscope database for MCU_NAME
    clean: no need MCU_NAME, clean the old cscope database
    help: no need MCU_NAME, show the help tips
    info: show the infomation of MCU_NAME
2. MCU_NAME:
    rx21a -&gt; renesas RX mcu
    efm32jg -&gt; Silicon labs efm32 mcu
Example:
    /usr/local/bin/mktags build efm32jg
    This can build cscope database at current dir for efm32jg
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>ubuntu 16.04下编译全功能vim8.0</title>
			<link>https://blog.5km.studio/2017/06/15/vim-full-version/</link>
			<pubDate>Thu, 15 Jun 2017 08:24:08 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/06/15/vim-full-version/</guid>
			<description>&lt;p&gt;十里又又又在公司的笔记本上安装了Ubuntu，已经无法忍受windows下蹩脚的vim体验，在ubuntu下尝试使用与系统剪切板的交互命令结果让人失落呀，找了一大圈还是没找到支持&lt;code&gt;clipboard&lt;/code&gt;的vim，毅然决然的决定自己编译一个全功能的vim！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>十里又又又在公司的笔记本上安装了Ubuntu，已经无法忍受windows下蹩脚的vim体验，在ubuntu下尝试使用与系统剪切板的交互命令结果让人失落呀，找了一大圈还是没找到支持<code>clipboard</code>的vim，毅然决然的决定自己编译一个全功能的vim！</p>
<h2 id="编译vim">编译vim</h2>
<h3 id="卸载原有的所有vim">卸载原有的所有vim</h3>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo apt-get remove --purge vim vim-runtime vim-gnome vim-tiny vim-gui-common
</code></pre></div><div class="tip">
有可能之前使用checkinstall安装了vim，可以通过`dpkg -r vim`和`sudo rm -rf /usr/local/share/vim /usr/bin/vim`卸载一下试试！
</div>
<h3 id="安装编译vim需要的依赖包">安装编译vim需要的依赖包</h3>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo apt-get install liblua5.1-dev luajit libluajit-5.1 python-dev ruby-dev libperl-dev libncurses5-dev libatk1.0-dev libx11-dev libxpm-dev libxt-dev
</code></pre></div><h3 id="下载源码">下载源码</h3>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">cd</span> ~/Downloads
git clone https://github.com/vim/vim
<span class="nb">cd</span> vim
git pull <span class="o">&amp;&amp;</span> git fetch
</code></pre></div><h3 id="配置编译">配置编译</h3>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">./configure <span class="se">\
</span><span class="se"></span>--enable-multibyte <span class="se">\
</span><span class="se"></span>--enable-perlinterp<span class="o">=</span>dynamic <span class="se">\
</span><span class="se"></span>--enable-rubyinterp<span class="o">=</span>dynamic <span class="se">\
</span><span class="se"></span>--with-ruby-command<span class="o">=</span>/usr/local/bin/ruby <span class="se">\
</span><span class="se"></span>--enable-pythoninterp<span class="o">=</span>dynamic <span class="se">\
</span><span class="se"></span>--with-python-config-dir<span class="o">=</span>/usr/lib/python2.7/config-x86_64-linux-gnu <span class="se">\
</span><span class="se"></span>--enable-python3interp <span class="se">\
</span><span class="se"></span>--with-python3-config-dir<span class="o">=</span>/usr/lib/python3.5/config-3.5m-x86_64-linux-gnu <span class="se">\
</span><span class="se"></span>--enable-luainterp <span class="se">\
</span><span class="se"></span>--with-luajit <span class="se">\
</span><span class="se"></span>--enable-cscope <span class="se">\
</span><span class="se"></span>--enable-gui<span class="o">=</span>auto <span class="se">\
</span><span class="se"></span>--with-features<span class="o">=</span>huge <span class="se">\
</span><span class="se"></span>--with-x <span class="se">\
</span><span class="se"></span>--enable-fontset <span class="se">\
</span><span class="se"></span>--enable-largefile <span class="se">\
</span><span class="se"></span>--disable-netbeans <span class="se">\
</span><span class="se"></span>--with-compiledby<span class="o">=</span><span class="s2">&#34;yourname&#34;</span> <span class="se">\
</span><span class="se"></span>--enable-fail-if-missing
</code></pre></div><h3 id="编译并安装">编译并安装</h3>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">make <span class="o">&amp;&amp;</span> sudo make install
</code></pre></div><h3 id="测试一下">测试一下</h3>
<ul>
<li>
<p>先看一下版本信息</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">VIM - Vi IMproved 8.0 <span class="o">(</span><span class="m">2016</span> Sep 12, compiled Jun <span class="m">14</span> <span class="m">2017</span> 19:00:28<span class="o">)</span>
包含补丁: 1-642
编译者 yourname
巨型版本 带 GTK2 图形界面。
  可使用<span class="o">(</span>+<span class="o">)</span>与不可使用<span class="o">(</span>-<span class="o">)</span>的功能:
+acl             +file_in_path    +mouse_sgr       +tag_old_static
+arabic          +find_in_path    -mouse_sysmouse  -tag_any_white
+autocmd         +float           +mouse_urxvt     -tcl
+balloon_eval    +folding         +mouse_xterm     +termguicolors
+browse          -footer          +multi_byte      +terminfo
++builtin_terms  +fork<span class="o">()</span>          +multi_lang      +termresponse
+byte_offset     +gettext         -mzscheme        +textobjects
+channel         -hangul_input    -netbeans_intg   +timers
+cindent         +iconv           +num64           +title
+clientserver    +insert_expand   +packages        +toolbar
+clipboard       +job             +path_extra      +user_commands
+cmdline_compl   +jumplist        +perl/dyn        +vertsplit
+cmdline_hist    +keymap          +persistent_undo +virtualedit
+cmdline_info    +lambda          +postscript      +visual
+comments        +langmap         +printer         +visualextra
+conceal         +libcall         +profile         +viminfo
+cryptv          +linebreak       +python/dyn      +vreplace
+cscope          +lispindent      +python3/dyn     +wildignore
+cursorbind      +listcmds        +quickfix        +wildmenu
+cursorshape     +localmap        +reltime         +windows
+dialog_con_gui  +lua             +rightleft       +writebackup
+diff            +menu            +ruby/dyn        +X11
+digraphs        +mksession       +scrollbind      -xfontset
+dnd             +modify_fname    +signs           +xim
-ebcdic          +mouse           +smartindent     +xpm
+emacs_tags      +mouseshape      +startuptime     +xsmp_interact
+eval            +mouse_dec       +statusline      +xterm_clipboard
+ex_extra        -mouse_gpm       -sun_workshop    -xterm_save
+extra_search    -mouse_jsbterm   +syntax          
+farsi           +mouse_netterm   +tag_binary      
     系统 vimrc 文件: <span class="s2">&#34;</span><span class="nv">$VIM</span><span class="s2">/vimrc&#34;</span>
     用户 vimrc 文件: <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.vimrc&#34;</span>
 第二用户 vimrc 文件: <span class="s2">&#34;~/.vim/vimrc&#34;</span>
      用户 exrc 文件: <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.exrc&#34;</span>
    系统 gvimrc 文件: <span class="s2">&#34;</span><span class="nv">$VIM</span><span class="s2">/gvimrc&#34;</span>
    用户 gvimrc 文件: <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.gvimrc&#34;</span>
第二用户 gvimrc 文件: <span class="s2">&#34;~/.vim/gvimrc&#34;</span>
       defaults file: <span class="s2">&#34;</span><span class="nv">$VIMRUNTIME</span><span class="s2">/defaults.vim&#34;</span>
        系统菜单文件: <span class="s2">&#34;</span><span class="nv">$VIMRUNTIME</span><span class="s2">/menu.vim&#34;</span>
         <span class="nv">$VIM</span> 预设值: <span class="s2">&#34;/usr/local/share/vim&#34;</span>
编译方式: gcc -c -I. -Iproto -DHAVE_CONFIG_H -DFEAT_GUI_GTK  -pthread -I/usr/include/gtk-2.0 -I/usr/lib/x86_64-linux-gnu/gtk-2.0/include -I/usr/include/gio-unix-2.0/ -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/libpng12 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/libpng12 -I/usr/include/pango-1.0 -I/usr/include/harfbuzz -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/freetype2   -g -O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE<span class="o">=</span><span class="m">1</span>       
链接方式: gcc   -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector -rdynamic -Wl,-export-dynamic -Wl,-E   -L/usr/local/lib -Wl,--as-needed -o vim   -lgtk-x11-2.0 -lgdk-x11-2.0 -lpangocairo-1.0 -latk-1.0 -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lpangoft2-1.0 -lpango-1.0 -lgobject-2.0 -lglib-2.0 -lfontconfig -lfreetype -lSM -lICE -lXpm -lXt -lX11 -lXdmcp -lSM -lICE  -lm -ltinfo -lnsl  -lselinux   -ldl  -L/usr/lib/x86_64-linux-gnu -lluajit-5.1 -Wl,-E  -fstack-protector-strong -L/usr/local/lib  -L/usr/lib/x86_64-linux-gnu/perl/5.22/CORE -lperl -ldl -lm -lpthread -lcrypt
</code></pre></div><p>试了一下可以用<code>+</code>和<code>*</code>寄存器了。</p>
</li>
</ul>
<h2 id="打包vim的deb包">打包vim的deb包</h2>
<p>可以使用checkinstall工具对编译的vim进行打包生成deb安装包，方便以后直接安装。</p>
<h3 id="安装checkinstall">安装checkinstall</h3>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">sudo apt-get install
</code></pre></div><h3 id="生成deb包">生成deb包</h3>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">cd vim
sudo checkinstall
</code></pre></div><p><a href="https://pan.baidu.com/s/1slDFMHJ">点我</a>下载十里编译的巨型版本vim安装包，嘎嘎！密码: <code>vhdd</code></p>]]></content>
		</item>
		
		<item>
			<title>macOS升级到12.5后jupyter不能自动打开浏览器</title>
			<link>https://blog.5km.studio/2017/06/05/jupyter-notebook-not-auto-open-webpage/</link>
			<pubDate>Mon, 05 Jun 2017 14:00:13 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/06/05/jupyter-notebook-not-auto-open-webpage/</guid>
			<description>&lt;p&gt;升级了macOS后一直也没有使用&lt;strong&gt;jupyter notebook&lt;/strong&gt;，昨天使用了发现竟然不能自动打开浏览器了。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>升级了macOS后一直也没有使用<strong>jupyter notebook</strong>，昨天使用了发现竟然不能自动打开浏览器了。</p>
<p>终端下调用<strong>jupyter notebook</strong>：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">➜  LearnPythonNotebook git:(master) ✗ jupyter notebook
zsh: /usr/local/bin/jupyter: bad interpreter: /usr/local/opt/python/bin/python2.7: no such file or directory
[I 13:58:51.602 NotebookApp] Serving notebooks from local directory: /Users/smslit/Desktop/LearnPythonNotebook
[I 13:58:51.602 NotebookApp] 0 active kernels
[I 13:58:51.602 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/?token=f6ca93231d4791de0dd64749e7f2fd62a50e55fc01456716
[I 13:58:51.602 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 13:58:51.606 NotebookApp]

    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:
        http://localhost:8888/?token=f6ca93231d4791de0dd64749e7f2fd62a50e55fc01456716
0:97: execution error: “&#34;http://localhost:8888/tree?token=50b06d86ad89e16f44b101656e1b3aec168231dd00b143a4&#34;”不理解“open location”信息。 (-1708)
</code></pre></div><p>问了一下我们的哥(谷歌)，找到了解决方案，在<code>~/.jupyter</code>目录下创建文件<code>jupyter_notebook_config.py</code>，文件内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">c.NotebookApp.browser = u&#39;Safari&#39;
c.NotebookApp.token = &#39;&#39;
c.NotebookApp.password = &#39;&#39;
</code></pre></div><p>重新启动一下<strong>jupyter notebook</strong>:</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">➜  LearnPythonNotebook git:(master) ✗ jupyter notebook
zsh: /usr/local/bin/jupyter: bad interpreter: /usr/local/opt/python/bin/python2.7: no such file or directory
[W 14:19:57.878 NotebookApp] All authentication is disabled.  Anyone who can connect to this server will be able to run code.
[I 14:19:57.888 NotebookApp] Serving notebooks from local directory: /Users/smslit/Desktop/LearnPythonNotebook
[I 14:19:57.888 NotebookApp] 0 active kernels
[I 14:19:57.888 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/
[I 14:19:57.888 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).

</code></pre></div><p>可以自动打开safari了，<em>peace！</em></p>]]></content>
		</item>
		
		<item>
			<title>python之父的coding禅念</title>
			<link>https://blog.5km.studio/2017/06/02/python-coding-style/</link>
			<pubDate>Fri, 02 Jun 2017 22:39:10 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/06/02/python-coding-style/</guid>
			<description>&lt;p&gt;情怀，是一个摸不到见不着的东西！码代码砖是不是也应该有自己的情怀，python之父表达了python之禅，这应该也是一种情怀吧！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>情怀，是一个摸不到见不着的东西！码代码砖是不是也应该有自己的情怀，python之父表达了python之禅，这应该也是一种情怀吧！</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">➜  smslit python
Python 2.7.10 (default, Feb  7 2017, 00:08:15)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin
Type &#34;help&#34;, &#34;copyright&#34;, &#34;credits&#34; or &#34;license&#34; for more information.
&gt;&gt;&gt; import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren&#39;t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you&#39;re Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it&#39;s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let&#39;s do more of those!
</code></pre></div><p>有高人翻译了个三字经，厉害了：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">The Zen of Python, 
蛇宗三字经 
 
作者：Tim Peters 
翻译：元创 
 
 
Beautiful is better than ugly. 
美胜丑 
Explicit is better than implicit. 
明胜暗 
Simple is better than complex. 
简胜复 
Complex is better than complicated. 
复胜杂 
Flat is better than nested. 
浅胜深 
Sparse is better than dense. 
疏胜密 
Readability counts. 
辞达意 
Special cases aren&#39;t special enough to break the rules. 
不逾矩 
Although practicality beats purity. 
弃至清 
Errors should never pass silently. 
无阴差 
Unless explicitly silenced. 
有阳错 
In the face of ambiguity, refuse the temptation to guess. 
拒疑数 
There should be one-- and preferably only one --obvious way to do it. 
求完一 
Although that way may not be obvious at first unless you&#39;re Dutch. 
虽不至，向往之 
Now is better than never. 
敏于行 
Although never is often better than *right* now. 
戒莽撞 
If the implementation is hard to explain, it&#39;s a bad idea. 
差难言 
If the implementation is easy to explain, it may be a good idea. 
好易说 
Namespaces are one honking great idea -- let&#39;s do more of those! 
每师出，多有名
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>开启platformio之旅</title>
			<link>https://blog.5km.studio/2017/05/20/platformio-start/</link>
			<pubDate>Sat, 20 May 2017 19:59:43 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/05/20/platformio-start/</guid>
			<description>&lt;p&gt;十里还是比较喜欢用终端解决问题的，在某个&lt;a href=&#34;https://www.hexcrow.org&#34;&gt;小伙伴&lt;/a&gt;身上学到了些&lt;a href=&#34;http://www.vim.org&#34;&gt;vim&lt;/a&gt;神技，虽然十里的技术现在很菜，但十里还是有追求的！比如十里希望能在终端下进行嵌入式开发工作，哈哈！平常会使用&lt;code&gt;arduino&lt;/code&gt;，最近这些天又在找vim插件，但基本上年久不更，跟不上Arduino版本的更新！转换思路，用bing搜索了支持&lt;code&gt;Arduino&lt;/code&gt;开发的CLI，没想到就找到了文章主角&lt;a href=&#34;http://platformio.org&#34;&gt;platformio&lt;/a&gt;，它不要太强大，竟然支持很多平台和板子的嵌入式开发，wonderful，更重要的是跨平台特性，windows\macOS\linux(+arm)，简直了！特此分享！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>十里还是比较喜欢用终端解决问题的，在某个<a href="https://www.hexcrow.org">小伙伴</a>身上学到了些<a href="http://www.vim.org">vim</a>神技，虽然十里的技术现在很菜，但十里还是有追求的！比如十里希望能在终端下进行嵌入式开发工作，哈哈！平常会使用<code>arduino</code>，最近这些天又在找vim插件，但基本上年久不更，跟不上Arduino版本的更新！转换思路，用bing搜索了支持<code>Arduino</code>开发的CLI，没想到就找到了文章主角<a href="http://platformio.org">platformio</a>，它不要太强大，竟然支持很多平台和板子的嵌入式开发，wonderful，更重要的是跨平台特性，windows\macOS\linux(+arm)，简直了！特此分享！</p>
<p>看图感受一下支持的MCU平台：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/5xjjq.png" alt="支持的MCU厂商"></p>
<blockquote>
<p>PlatformIO is an open source ecosystem for IoT development. Cross-platform build system. Continuous and IDE integration. Arduino and ARM mbed compatible</p>
</blockquote>
<p><code>platformio</code>提供的是一个嵌入式平台开发的构建系统和开发环境，它会管理你使用到的编译连工具、调试器和下载工具等，不需要费心自己去找然后艰难搭建各种MCU的开发环境了，真的非常方便！</p>
<h3 id="安装">安装</h3>
<p><code>platformio</code>提供可视化的IDE，也提供CLI，CLI就是十里需要的<code>Platformio Core</code>，首先需要<a href="http://docs.platformio.org/en/latest/installation.html">安装</a>，macOS下非常简单，使用homebrew就可以安装。</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">brew install platformio
</code></pre></div><p>安装完，终端重启一下就可以调出<code>platformio</code>命令，还有一个alias命令是<code>pio</code>，执行一下help感受一下：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">➜  play pio --help
Usage: pio [OPTIONS] COMMAND [ARGS]...

Options:
  --version          Show the version and exit.
  -f, --force        Force to accept any confirmation prompts.
  -c, --caller TEXT  Caller ID (service).
  -h, --help         Show this message and exit.

Commands:
  account   Manage PIO Account
  boards    Embedded Board Explorer
  ci        Continuous Integration
  device    Monitor device or list existing
  init      Initialize PlatformIO project or update existing
  lib       Library Manager
  platform  Platform Manager
  remote    PIO Remote
  run       Process project environments
  settings  Manage PlatformIO settings
  test      Local Unit Testing
  update    Update installed platforms, packages and libraries
  upgrade   Upgrade PlatformIO to the latest version
➜  play pio --version
PlatformIO, version 3.3.0
</code></pre></div><h3 id="从一个arduino点灯开启使用旅程">从一个Arduino点灯开启使用旅程</h3>
<p>其实此时此刻可以把Arduino IDE卸载掉了，哈哈！下面可以跟我简单走一个 <code>Arduino UNO</code> 例程的开发过程。</p>
<h4 id="查看platformio支持的开发板">查看<code>platformio</code>支持的开发板</h4>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">pio boards
</code></pre></div><p>这样就会列出当前所支持的开发板，目前应该是200多款，它们按照硬件平台或MCU进行分类，可以看到其中Arduino官方开发板。其中<code>ID</code>一列在使用命令的时候，用<code>ID</code>用来表示不同开发板。其中我们要实验的 <code>Arduino UNO</code> 对应的是 <code>uno</code>。</p>
<h4 id="初始化blinky工程">初始化blinky工程</h4>
<p>在自己想要的目录下创建blinky目录，并进入此目录</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">mkdir blinky &amp;&amp; cd blinky
</code></pre></div><p>初始化<code>uno</code>工程</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">pio init --board uno
</code></pre></div><p>正常的话就生成了工程目录和文件如下</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">.
├── lib
│   └── readme.txt
├── platformio.ini
└── src
	└── main.cpp
</code></pre></div><p>其中<code>platformio.ini</code>就是配置文件，可以点<a href="http://docs.platformio.org/en/latest/projectconf.html">我</a>了解更改配置相关的内容，这里我们不需要更改，保持默认就好。<code>lib</code>以后用来保存使用的库文件，默认没有文件，而<code>src</code>下包含了工程源码，自动生成了<code>main.cpp</code>，内容如下</p>
<div class="highlight"><pre class="chroma"><code class="language-c++" data-lang="c++"><span class="cm">/**
</span><span class="cm"> * Blink
</span><span class="cm"> *
</span><span class="cm"> * Turns on an LED on for one second,
</span><span class="cm"> * then off for one second, repeatedly.
</span><span class="cm"> */</span>
<span class="cp">#include</span> <span class="cpf">&#34;Arduino.h&#34;</span><span class="cp">
</span><span class="cp"></span>
<span class="cp">#ifndef LED_BUILTIN
</span><span class="cp">#define LED_BUILTIN 13
</span><span class="cp">#endif
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span>
<span class="p">{</span>
	<span class="c1">// initialize LED digital pin as an output.
</span><span class="c1"></span>	<span class="n">pinMode</span><span class="p">(</span><span class="n">LED_BUILTIN</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span>
<span class="p">{</span>
	<span class="c1">// turn the LED on (HIGH is the voltage level)
</span><span class="c1"></span>	<span class="n">digitalWrite</span><span class="p">(</span><span class="n">LED_BUILTIN</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>

	<span class="c1">// wait for a second
</span><span class="c1"></span>	<span class="n">delay</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>

	<span class="c1">// turn the LED off by making the voltage LOW
</span><span class="c1"></span>	<span class="n">digitalWrite</span><span class="p">(</span><span class="n">LED_BUILTIN</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>

	<span class="c1">// wait for a second
</span><span class="c1"></span>	<span class="n">delay</span><span class="p">(</span><span class="mi">900</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div><h4 id="编译工程">编译工程</h4>
<p>可以使用<code>run</code>命令进行编译和上传程序，执行</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">pio run
</code></pre></div><p><code>platformio</code>会自动下载对应开发板的交叉编译链工具、工程框架等，<code>Arduino UNO</code>对应的就是avr的相关CLI开发工具，下载完成后就会自动编译了。</p>
<h4 id="上传程序">上传程序</h4>
<p>程序编译完成后，需要将程序上传到开发板，执行</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">pio run -t upload
</code></pre></div><p>点<a href="http://docs.platformio.org/en/latest/userguide/cmd_run.html">我</a>了解更多关于<code>run</code>命令的使用，<code>-t</code> 用来指定功能，比如这里的<code>upload</code>，因为缺少工具，所以<code>platformio</code>会自动下载<code>Arduino UNO</code>程序下载工具<code>avrdude</code>，下载完成后就会将程序上传到连接电脑的开发板，这期间会自动选择合适的串口进行上传。</p>
<h3 id="总结">总结</h3>
<p>走过以上步骤，会发现<code>platformio</code>是一个智能的工具，首先它会根据你指定的开发板在<code>platformio.ini</code>生成默认的开发板工程配置，执行编译命令后就会自动选择相应的交叉编译工具和依赖工具，如果资源不好还会自动更换资源地址，尽量保证工具能够下载安装，然后完成代码编译。将程序上传到开发板时，它同样智能的下载对应平台的上传工具，并完成智能选择串口上传。其实一个工程还可以指定多种开发板，详情可以点<a href="http://docs.platformio.org/en/latest/userguide/cmd_init.html">我</a>了解。总之，十里十分喜欢这个工具，建议看到本文的小伙伴安装体验一下，无论IDE还是CLI都一样令人心动！</p>
<p>后面如果有时间，十里还会分享使用感受！</p>]]></content>
		</item>
		
		<item>
			<title>巧合特别多</title>
			<link>https://blog.5km.studio/2017/05/12/coincidental/</link>
			<pubDate>Fri, 12 May 2017 17:30:34 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/05/12/coincidental/</guid>
			<description>&lt;p&gt;2017年注定是一个巧合非常多的一年。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>2017年注定是一个巧合非常多的一年。</p>
<h3 id="一直夫妇的日常">一直夫妇的日常</h3>
<p>有一天知乎上看了一篇文章，无意间关注了 <code>一直夫妇的日常</code> 公众号，这个公众号是专属于一对情侣（秦驿，栗之），他们分享自己的故事与感悟，被他们的小幸福感动，蛮喜欢这个公众号的!</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/7mz1t.jpeg"
         alt="一直夫妇的日常" width="480px" height="120px"/>
</figure>

<blockquote>
<p>家乡一南一北，学校一南一北，栗之和秦驿，隔了千里，还是在北京相遇了。我们每天都会发布一篇我们之间的日常故事，互黑为主，狗粮只占很小的比例，单身狗们可以放心观看~</p>
</blockquote>
<p>关注公众号的第二天看到这篇文章——<a href="https://mp.weixin.qq.com/s?__biz=MzA4NTE2MzE2NQ==&amp;mid=2648851737&amp;idx=1&amp;sn=f642934237ec796b01756d535198dcc8&amp;chksm=87cac8ebb0bd41fdb98bcae6ccf3d692afa5e3c479a72ae76c5862da5c5438e222fc47c93dde&amp;scene=42#wechat_redirect">礼物真的要买贵的吗？</a>，我惊呆了，公众号的男主角秦驿的生日竟然与我同一天，我就兴奋的在文章中留言了，单独也在公众号中留言了，了解到秦驿竟然与我 <code>同年同月同日生</code> ，无意间的公众号让我找到了同年同月同日生的人，好神奇！</p>
<h3 id="温州的相遇">温州的相遇</h3>
<p>前段时间出差温州，到温州就是中午了，然后接应我们的谢总，先带我们去了他们公司附近，在温州乐清市柳市镇，然后带我和同事先简单去吃点，我惊呆了，去的地方竟然是 <code>田野中餐厅</code> ，要知道我就叫田野！</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/4rr3d.png"
         alt="田野中餐厅" width="480px" height="300px"/>
</figure>

<p>下午干完活，在未与我和同事商量的情况下，谢总带我们去了一个地方去吃完饭，我再一次惊呆了，那个地方叫 <code>江南人家</code> ，要知道我的水木就叫江楠！</p>
<figure><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/r1mmq.png"
         alt="江南人家" width="480px" height="300px"/>
</figure>

<p>就这样，田野（十里）和江楠（水木）在柳市镇相遇了！</p>]]></content>
		</item>
		
		<item>
			<title>从简开始</title>
			<link>https://blog.5km.studio/2017/03/19/simpleStart/</link>
			<pubDate>Sun, 19 Mar 2017 13:07:48 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/03/19/simpleStart/</guid>
			<description>&lt;p&gt;从今天开始博客更换面貌，使用了hexo的apollo主题，真的像主题作者说的，博客应该以内容为主，所以SMSLIT要从简开始了！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>从今天开始博客更换面貌，使用了hexo的apollo主题，真的像主题作者说的，博客应该以内容为主，所以SMSLIT要从简开始了！</p>
<p>从现在开始，SMSLIT应该一直会是简约的形态。我过去虽然一直崇尚简约，但貌似SMSLIT呈现的形式过于复杂，所以，我想以此文作为见证，以后博客内容呈现尽量简单直接，<code>5km</code>[^1](<code>十里</code> 是我，我就是 <code>5km</code>)精力可能更多放在博客内容上了，再说了 <code>5km</code> 也还有更多自己想做的事呢！</p>
<p>新的博客移除了评论，如果真的想与<code>5km</code>交流，你会在博客中找到联系方式的！</p>]]></content>
		</item>
		
		<item>
			<title>Anaconda闪退</title>
			<link>https://blog.5km.studio/2017/03/02/anacondaQuit/</link>
			<pubDate>Thu, 02 Mar 2017 12:13:06 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/03/02/anacondaQuit/</guid>
			<description>&lt;p&gt;春节前终于下定了决心，分期买了2016款的 macbook pro，也就是水木口中的“十里的大情人”，确实很激动，很开心！之前有台 mac mini ，有做备份，当时将备份恢复到“大情人”了，一直没有用 Anaconda ，今天一用发现启动竟然闪退。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>春节前终于下定了决心，分期买了2016款的 macbook pro，也就是水木口中的“十里的大情人”，确实很激动，很开心！之前有台 mac mini ，有做备份，当时将备份恢复到“大情人”了，一直没有用 Anaconda ，今天一用发现启动竟然闪退。</p>
<p>目前还不怎么了解Anaconda ，想以后用它替代matlab进行科学计算的工作，咋就罢工了呢？点击Anaconda navigator 出现启动界面不一会儿就闪退了：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/3pgs8.png" alt="anaconda"></p>
<p>刚才已经说了，我是恢复的备份，应该是这个原因，造成现用户与从 mac mini 恢复过来的用户目录下anaconda的配置文件<code>.continuum</code>冲突不兼容，这是一个文件夹，很简单的解决方案，就是删掉这个文件夹，让 Anaconda navigator 启动的时候重建配置。</p>
<p>删掉配置文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ sudo rm -rf .continuum
</code></pre></div><p>然后再重新启动 Anaconda navigator 就可以了。</p>]]></content>
		</item>
		
		<item>
			<title>vim中恢复ctrl&#43;z</title>
			<link>https://blog.5km.studio/2017/01/13/vim-Ctrl-z/</link>
			<pubDate>Fri, 13 Jan 2017 09:37:56 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/01/13/vim-Ctrl-z/</guid>
			<description>&lt;p&gt;就在刚才vim下编辑一个文本本来想按下&lt;code&gt;shift+zz&lt;/code&gt;来保存文本并退出vim，没想到又按成了&lt;code&gt;ctrl+z&lt;/code&gt;，之前不了解状况的我，以为保存了，但是再次用vim打开这个文本的时候，发现vim保存了一个对应的swp文件，总会出现下面的提示，我这次忍无可忍了，就搜索了解了一下。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>就在刚才vim下编辑一个文本本来想按下<code>shift+zz</code>来保存文本并退出vim，没想到又按成了<code>ctrl+z</code>，之前不了解状况的我，以为保存了，但是再次用vim打开这个文本的时候，发现vim保存了一个对应的swp文件，总会出现下面的提示，我这次忍无可忍了，就搜索了解了一下。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">E325: ATTENTION
Found a swap file by the name <span class="s2">&#34;.vimrc.swp&#34;</span>
          owned by: smslit   dated: Fri Jan <span class="m">13</span> 10:27:11 <span class="m">2017</span>
         file name: ~smslit/.vimrc
          modified: no
         user name: smslit   host name: tianyes-Mac-mini.local
        process ID: <span class="m">1110</span> <span class="o">(</span>still running<span class="o">)</span>
While opening file <span class="s2">&#34;.vimrc&#34;</span>
             dated: Sun Nov <span class="m">27</span> 16:58:29 <span class="m">2016</span>

<span class="o">(</span>1<span class="o">)</span> Another program may be editing the same file.  If this is the <span class="k">case</span>,
    be careful not to end up with two different instances of the same
    file when making changes.  Quit, or <span class="k">continue</span> with caution.
<span class="o">(</span>2<span class="o">)</span> An edit session <span class="k">for</span> this file crashed.
    If this is the <span class="k">case</span>, use <span class="s2">&#34;:recover&#34;</span> or <span class="s2">&#34;vim -r .vimrc&#34;</span>
    to recover the changes <span class="o">(</span>see <span class="s2">&#34;:help recovery&#34;</span><span class="o">)</span>.
    If you did this already, delete the swap file <span class="s2">&#34;.vimrc.swp&#34;</span>
    to avoid this message.

Swap file <span class="s2">&#34;.vimrc.swp&#34;</span> already exists!
<span class="o">[</span>O<span class="o">]</span>pen Read-Only, <span class="o">(</span>E<span class="o">)</span>dit anyway, <span class="o">(</span>R<span class="o">)</span>ecover, <span class="o">(</span>Q<span class="o">)</span>uit, <span class="o">(</span>A<span class="o">)</span>bort:
</code></pre></div><p>原来是因为把编辑这个文件的vim推到了后台，其实按下<code>ctrl+z</code>后会有提示如下，但之前不知道咋回事儿，其实仔细想想，好像有个很好的朋友告诉过我这个，汗，记性变差好严重！</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">➜  ~ vim .vimrc

<span class="o">[</span>1<span class="o">]</span>  + <span class="m">1110</span> suspended  vim .vimrc
</code></pre></div><p>解决办法很简单，可以先查看一下后台有啥：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">➜  ~ <span class="nb">jobs</span>
<span class="o">[</span>1<span class="o">]</span>  + suspended  vim .vimrc
</code></pre></div><p>然后执行fg，就能跳回编辑文件的vim了：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">➜  ~ <span class="nb">fg</span>
<span class="o">[</span>1<span class="o">]</span>  + <span class="m">1110</span> continued  vim .vimrc
</code></pre></div><p>为此做了动态图片可以看清操作流程：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/rgp9o.gif" alt="vimCtrlZ"></p>]]></content>
		</item>
		
		<item>
			<title>诗-闽行路遇</title>
			<link>https://blog.5km.studio/2017/01/11/peomForFuzhou/</link>
			<pubDate>Wed, 11 Jan 2017 20:19:12 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2017/01/11/peomForFuzhou/</guid>
			<description>&lt;p&gt;这周有机会去福建出差，真心累呀，比较老实的我，没有逮着机会出去玩，在出差期间，联调的测试需要在福州和厦门之间走动，都是走高速，期间因所见所闻与所吃，诗性大发了，哈哈，在从厦门到福州的路上题诗一首（胡写乱题），觉得还是手写比较好，献丑：&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>这周有机会去福建出差，真心累呀，比较老实的我，没有逮着机会出去玩，在出差期间，联调的测试需要在福州和厦门之间走动，都是走高速，期间因所见所闻与所吃，诗性大发了，哈哈，在从厦门到福州的路上题诗一首（胡写乱题），觉得还是手写比较好，献丑：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/xzsf7.jpg" alt="fuzhou"></p>]]></content>
		</item>
		
		<item>
			<title>笔尖痕迹-其实不复杂</title>
			<link>https://blog.5km.studio/2016/12/23/simpleWorld/</link>
			<pubDate>Fri, 23 Dec 2016 22:52:51 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/12/23/simpleWorld/</guid>
			<description>&lt;p&gt;有的时候，会很烦恼，烦恼这世界如此复杂，渐渐发现复杂的事情也能简单化，希望自己眼中的世界慢慢变得简单！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>有的时候，会很烦恼，烦恼这世界如此复杂，渐渐发现复杂的事情也能简单化，希望自己眼中的世界慢慢变得简单！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ow0rb.jpg" alt="simpleWorld"></p>]]></content>
		</item>
		
		<item>
			<title>Qt开发学习问题解决笔记</title>
			<link>https://blog.5km.studio/2016/12/23/Qt-Program-Notes-Develop/</link>
			<pubDate>Fri, 23 Dec 2016 21:23:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/12/23/Qt-Program-Notes-Develop/</guid>
			<description>&lt;p&gt;本文主要用来整理学习Qt开发过程中遇到的问题及解决办法，会持续更新。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>本文主要用来整理学习Qt开发过程中遇到的问题及解决办法，会持续更新。</p>
<p>更新表：</p>
<table>
<thead>
<tr>
<th>日期</th>
<th>更新内容</th>
</tr>
</thead>
<tbody>
<tr>
<td>2016-02-27</td>
<td>QtSerialport库使用时编译错误</td>
</tr>
<tr>
<td>&hellip;</td>
<td>macOS下程序图标设置问题</td>
</tr>
<tr>
<td></td>
<td>Linux下Qt的<code>can't find lGL问题</code></td>
</tr>
<tr>
<td></td>
<td>Linux下无法启动Qt帮助问题</td>
</tr>
<tr>
<td>2016-12-27</td>
<td>Linux下Qt添加中文Fctix输入法支持</td>
</tr>
</tbody>
</table>
<h3 id="qtserialport库编译错误">QtSerialport库编译错误</h3>
<h4 id="现象">现象</h4>
<p>QtSerialport使用时，虽然包含了头文件，但仍然会爆出错误，linux和macOS下都会这样。</p>
<h5 id="linux下">linux下</h5>
<p>QtSerialport库编译错误undefined reference</p>
<p>编译错误提示类似如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">C:<span class="se">\U</span>sers<span class="se">\s</span>mslit<span class="se">\D</span>ocuments<span class="se">\w</span>orkspace<span class="se">\Q</span>t<span class="se">\b</span>uild-teatSerial-Desktop_Qt_5_5_0_MinGW_32bit-Debug/../teatSerial/mainwindow.cpp:
24: undefined reference to <span class="sb">`</span> _imp___ZN11QSerialPort11setPortNameERK7QString<span class="s1">&#39;
</span><span class="s1">C:\Users\smslit\Documents\workspace\Qt\build-teatSerial-Desktop_Qt_5_5_0_MinGW_32bit-Debug/../teatSerial/mainwindow.cpp:
</span><span class="s1">25: undefined reference to ` _imp___ZN11QSerialPort9setParityENS_6ParityE&#39;</span>
C:<span class="se">\U</span>sers<span class="se">\s</span>mslit<span class="se">\D</span>ocuments<span class="se">\w</span>orkspace<span class="se">\Q</span>t<span class="se">\b</span>uild-teatSerial-Desktop_Qt_5_5_0_MinGW_32bit-Debug/../teatSerial/mainwindow.cpp:
26: undefined reference to <span class="sb">`</span> _imp___ZN11QSerialPort11setDataBitsENS_8DataBitsE<span class="s1">&#39;
</span><span class="s1">C:\Users\smslit\Documents\workspace\Qt\build-teatSerial-Desktop_Qt_5_5_0_MinGW_32bit-Debug/../teatSerial/mainwindow.cpp:
</span><span class="s1">27: undefined reference to ` _imp___ZN11QSerialPort11setStopBitsENS_8StopBitsE&#39;</span>
C:<span class="se">\U</span>sers<span class="se">\s</span>mslit<span class="se">\D</span>ocuments<span class="se">\w</span>orkspace<span class="se">\Q</span>t<span class="se">\b</span>uild-teatSerial-Desktop_Qt_5_5_0_MinGW_32bit-Debug/../teatSerial/mainwindow.cpp:
28: undefined reference to <span class="sb">`</span> _imp___ZN11QSerialPort17setReadBufferSizeEx<span class="s1">&#39;
</span><span class="s1">C:\Users\smslit\Documents\workspace\Qt\build-teatSerial-Desktop_Qt_5_5_0_MinGW_32bit-Debug/../teatSerial/mainwindow.cpp:
</span><span class="s1">29: undefined reference to ` _imp___ZN11QSerialPort14setFlowControlENS_11FlowControlE&#39;</span>
</code></pre></div><h5 id="macos下">macOS下</h5>
<p>编译会有类似提示如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">Undefined symbols <span class="k">for</span> architecture x86_64:
  <span class="s2">&#34;QSerialPort::setBaudRate(int, QFlags&lt;QSerialPort::Direction&gt;)&#34;</span>, referenced from:
      MainWindow::on_openPortButton_clicked<span class="o">()</span> in mainwindow.o
  <span class="s2">&#34;QSerialPort::setDataBits(QSerialPort::DataBits)&#34;</span>, referenced from:
      MainWindow::on_openPortButton_clicked<span class="o">()</span> in mainwindow.o
  .
  .
  .
  <span class="s2">&#34;QSerialPortInfo::portName() const&#34;</span>, referenced from:
      MainWindow::fillPortsInfo<span class="o">()</span> in mainwindow.o
ld: symbol<span class="o">(</span>s<span class="o">)</span> not found <span class="k">for</span> architecture x86_64
clang: error: linker <span class="nb">command</span> failed with <span class="nb">exit</span> code <span class="m">1</span> <span class="o">(</span>use -v to see invocation<span class="o">)</span>
</code></pre></div><h4 id="解决办法"><strong>解决办法</strong></h4>
<p>在工程的pro文件中的第一行加入以下内容，再次编译即可通过：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">QT</span>  <span class="o">+=</span> <span class="n">serialport</span>
</code></pre></div><h3 id="mac-osx下qt设置程序的图标">Mac OSX下QT设置程序的图标</h3>
<ul>
<li>
<p>第一步，设计好自己想要的icns图标，比如图标命名时app.icns，放到工程根目录，如下图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/xnnv2.png" alt="1"></p>
</li>
<li>
<p>第二步，在pro文件中，加入新的一行，内容为  <strong>ICON = app.icns</strong>，其中app.icns为你的图标文件名。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/95srw.png" alt="2"></p>
</li>
<li>
<p>第三步，选择release，编译即可生成APP，app效果如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/gwrag.png" alt="3"></p>
</li>
</ul>
<h3 id="cannot-find--lgl">cannot find -lGL</h3>
<p>Ubuntu下Qt编译错误：cannot find -lGL</p>
<p>在编译之前常见的一个小工程（在mac下创建的）的时候，出现lGL错误，我以为是不兼容，但又新建了一个项目，编译出错：</p>
<blockquote>
<p>cannot find -lGL。</p>
</blockquote>
<p>经过了解是因为缺少文件导致，需要安装部分文件，网上找到的解决方案是：安装<code>libqt4-dev</code>或者<code>libgl1-mesa-dev</code>或者<code>libgl1-mesa-dev</code>或者<code>libglu1-mesa-dev</code></p>
<p>我只尝试了安装<code>libqt4-dev</code>，在Teminal中执行一下命令安装相关库文件，然后编译就成功了。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo apt-get isntall libqt4-dev
</code></pre></div><h3 id="无法启动help插件">无法启动help插件</h3>
<p>Ubuntu下qtceator后无法启动help插件</p>
<p>安装好qt5.5后启动qtceator时提示：</p>
<blockquote>
<p>/opt/Qt5.5.1/Tools/QtCreator/lib/qtcreator/plugins/libHelp.so: 无法加载库/opt/Qt5.5.1/Tools/QtCreator/lib/qtcreator/plugins/libHelp.so：(libgstapp-0.10.so.0: 无法打开共享对象文件: 没有那个文件或目录)</p>
</blockquote>
<p>一定时缺少库造成的，可能是因为Qt帮助是采用流媒体的方式加载，尝试安装流媒体库文件，来解决，如下来安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo apt-get  install libgstreamer0.10-dev
sudo apt-get  install libgstreamer-plugins-base0.10-dev
</code></pre></div><h3 id="linux下添加qt的中文输入支持">Linux下添加Qt的中文输入支持</h3>
<h4 id="现象-1">现象</h4>
<p>在linux下用Qt在写一个小工具，想在使用过程中输入中文，发现怎么也不行，后来发现Qt编译后的程序也不支持中文输入，最终定位原因在未安装fctix输入法的支持，我的输入法是搜狗，其基于fctix，所以不支持，只需安装相应库并放倒两个目录下解决。</p>
<h4 id="解决方法">解决方法</h4>
<ul>
<li>
<p>首先得安装fcitx-libs-qt和fcitx-libs-qt5这两个库：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo apt-get install fcitx-libs-qt fcitx-libs-qt5
</code></pre></div></li>
<li>
<p>在<code>/usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/</code>目录下找到<code>libfcitxplatforminputcontextplugin.so</code>，将其拷贝到随便一个目录，比如Downloads，然后加运行权限：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">cp /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so ~/Downloads
<span class="nb">cd</span> ~/Downloads
chmod +x libfcitxplatforminputcontextplugin.so
</code></pre></div></li>
<li>
<p>然后将这个库文件分别放到两个目录下，下面命令中<code>QT安装目录</code>替换为QT的安装目录，比如我的<code>/opt/Qt5.7／</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="c1"># 解决Qt中不能输入中文的问题</span>
cp libfcitxplatforminputcontextplugin.so QT安装目录/Tools/QtCreator/lib/Qt/plugins/platforminputcontexts/
<span class="c1"># 解决Qt编译出的程序不支持中文输入的问题</span>
cp libfcitxplatforminputcontextplugin.so QT安装目录/5.7/gcc_64/plugins/platforminputcontexts/
</code></pre></div></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>笔尖痕迹-不畏浮云遮望眼</title>
			<link>https://blog.5km.studio/2016/12/11/noFear/</link>
			<pubDate>Sun, 11 Dec 2016 21:10:47 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/12/11/noFear/</guid>
			<description>&lt;blockquote&gt;
&lt;p&gt;眼下这堆破玩意儿，不足以阻挡我长远的“阴谋”！&lt;/p&gt;
&lt;/blockquote&gt;</description>
			<content type="html"><![CDATA[<blockquote>
<p>眼下这堆破玩意儿，不足以阻挡我长远的“阴谋”！</p>
</blockquote>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/1qb7s.jpg" alt="noFear"></p>]]></content>
		</item>
		
		<item>
			<title>又见google黑科技-Radarcat</title>
			<link>https://blog.5km.studio/2016/12/01/radarcat/</link>
			<pubDate>Thu, 01 Dec 2016 12:49:16 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/12/01/radarcat/</guid>
			<description>本次要分享的RadarCat就是基于这个Soli芯片的，它能通过机器学习实现对物体的识别，连瓶子里是什么液体都知道，芬达or可口可乐，看下面</description>
			<content type="html"><![CDATA[<p>本次要分享的RadarCat就是基于这个Soli芯片的，它能通过机器学习实现对物体的识别，连瓶子里是什么液体都知道，芬达or可口可乐，看下面的视频了解一下吧，又见google黑科技！</p>
<p>下面是youtube视频：</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/share/redacat/radarcat.mp4" poster="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/share/redacat/radarcat.jpg" width="100%" controls="controls">RadarCat</video></p>
]]></content>
		</item>
		
		<item>
			<title>vim中delete（backspace）键不能向左删除</title>
			<link>https://blog.5km.studio/2016/11/27/vim-backspace-invalid/</link>
			<pubDate>Sun, 27 Nov 2016 16:43:01 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/11/27/vim-backspace-invalid/</guid>
			<description>&lt;p&gt;MacOS下打开vim编辑文本，进入插入模式，要删除编辑之前的保存过的几个字符，按下&lt;strong&gt;delete&lt;/strong&gt;键删除它们，万万没想到呀！只听到&amp;quot;duang duang duang&amp;quot;，竟然无效！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>MacOS下打开vim编辑文本，进入插入模式，要删除编辑之前的保存过的几个字符，按下<strong>delete</strong>键删除它们，万万没想到呀！只听到&quot;duang duang duang&quot;，竟然无效！</p>
<p>有一点先明确一下，在mac的键盘中没有backspace键，有个键叫delete，其实相当于window下的backspace键，编辑文本时按下都具有向左删除字符的功能。</p>
<h3 id="原因">原因</h3>
<p>网上搜索得知，出现开头说的令人恼火的问题的原因是：</p>
<blockquote>
<p>VIM使用了 compatible 模式，或者把 backspace 变量设置为空了&hellip;其实compatible模式是VIM为了兼容vi而出现的配置，它的作用是使VIM的操作行为和规范和vi一致，而这种模式下backspace配置是空的。即意味着backspace无法删除 indent ， end of line ， start 这三种字符。</p>
</blockquote>
<p>在默认状态下，delete（backspace）按下只会删除本次插入模式下插入的文本，这跟backspace的模式设置有关，其模式可以设置为以下三种模式：</p>
<ul>
<li>0 same as “:set backspace=” (Vi compatible)</li>
<li>1 same as “:set backspace=indent,eol”</li>
<li>2 same as “:set backspace=indent,eol,start”</li>
</ul>
<h3 id="解决">解决</h3>
<p>知道了原因，就好解决了，只需要将backspace的模式设置成2就可以了，在<code>~/.vimrc</code>中添加了一下内容，保存，下次进入vim就可以在插入模式下任意使用delete（backspace）键了：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="s2">&#34; 解决插入模式下delete/backspce键失效问题
</span><span class="s2">set backspace=2
</span></code></pre></div><h3 id="参考">参考</h3>
<ol>
<li><a href="http://blog.csdn.net/jiang314/article/details/51941479">Mac 的 Vim 中 delete 键失效的原因和解决方案</a></li>
<li><a href="http://www.vim.org/">Vim</a></li>
</ol>]]></content>
		</item>
		
		<item>
			<title>macOS下运行非App Store下载的app提示损坏的解决方法</title>
			<link>https://blog.5km.studio/2016/11/27/macOS-app-damaged/</link>
			<pubDate>Sun, 27 Nov 2016 16:08:35 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/11/27/macOS-app-damaged/</guid>
			<description>&lt;p&gt;在网上下载了一个macOS平台下的软件，但是一运行就弹出提示框，大概意思是说app已损坏，一开始以为真的是损坏了，然后又重新下载了一遍安装运行还是这样，然后又去另外一个平台下载还是这样。这怎么可能？&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>在网上下载了一个macOS平台下的软件，但是一运行就弹出提示框，大概意思是说app已损坏，一开始以为真的是损坏了，然后又重新下载了一遍安装运行还是这样，然后又去另外一个平台下载还是这样。这怎么可能？</p>
<h3 id="原因">原因</h3>
<p>后来知道原来是升级到<code>macOS Seirra</code>的原因，这个版本macOS的设置中的安全与隐私下<code>允许从以下位置下载的应用</code>中只有两个选项，就是这搞的鬼，应该还有一个<strong>任何来源</strong>的才对。</p>
<h3 id="解决方法">解决方法</h3>
<p>下面解决一下这个问题：</p>
<ul>
<li>打开终端；</li>
<li>执行命令：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo spctl --master-disable
</code></pre></div><ul>
<li>重启mac；</li>
</ul>
<p>做完以上三步，就会发现<code>设置</code>中<code>安全性与隐私</code>下<code>允许从以下位置下载的应用</code>有了第三个选项<code>任何来源</code>，然后再去打开之前提示损坏的app就能运行了。</p>]]></content>
		</item>
		
		<item>
			<title>swift获取系统当前时间</title>
			<link>https://blog.5km.studio/2016/11/19/getnowtime-swift-dev/</link>
			<pubDate>Sat, 19 Nov 2016 21:53:23 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/11/19/getnowtime-swift-dev/</guid>
			<description>&lt;p&gt;今天尝试做一个macOS下的生成645协议的广播校时报文的小工具，做到一半竟然被“如何获取当前时间？”给难住了，百度搜索结果找来的方法都是过时的，经过尝试和研究找到了方法。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天尝试做一个macOS下的生成645协议的广播校时报文的小工具，做到一半竟然被“如何获取当前时间？”给难住了，百度搜索结果找来的方法都是过时的，经过尝试和研究找到了方法。</p>
<p>以在macOS的playground为例，首先新建一个macOS平台的playground，编写代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="c1">//: Playground - noun: a place where people can play</span>

<span class="kd">import</span> <span class="nc">Cocoa</span>

<span class="c1">// 获取当前时间</span>
<span class="kd">let</span> <span class="nv">now</span><span class="p">:</span> <span class="n">Date</span> <span class="p">=</span> <span class="n">Date</span><span class="p">()</span>
<span class="kd">let</span> <span class="nv">calendar</span> <span class="p">=</span> <span class="n">Calendar</span><span class="p">.</span><span class="n">current</span><span class="p">;</span>
<span class="kd">let</span> <span class="nv">components</span> <span class="p">=</span> <span class="n">calendar</span><span class="p">.</span><span class="n">dateComponents</span><span class="p">([.</span><span class="n">year</span><span class="p">,</span> <span class="p">.</span><span class="n">month</span><span class="p">,</span> <span class="p">.</span><span class="n">day</span><span class="p">,</span> <span class="p">.</span><span class="n">hour</span><span class="p">,</span> <span class="p">.</span><span class="n">minute</span><span class="p">,</span> <span class="p">.</span><span class="n">second</span><span class="p">],</span> <span class="n">from</span><span class="p">:</span> <span class="n">now</span><span class="p">)</span>
<span class="c1">// 获取当前时间对应年月日时分秒的整数值</span>
<span class="kd">let</span> <span class="nv">year</span> <span class="p">=</span> <span class="n">components</span><span class="p">.</span><span class="n">year</span><span class="p">!</span> <span class="o">%</span> <span class="mi">100</span>
<span class="kd">let</span> <span class="nv">month</span> <span class="p">=</span> <span class="n">components</span><span class="p">.</span><span class="n">month</span>
<span class="kd">let</span> <span class="nv">day</span> <span class="p">=</span> <span class="n">components</span><span class="p">.</span><span class="n">day</span>
<span class="kd">let</span> <span class="nv">hour</span> <span class="p">=</span> <span class="n">components</span><span class="p">.</span><span class="n">hour</span>
<span class="kd">let</span> <span class="nv">minute</span> <span class="p">=</span> <span class="n">components</span><span class="p">.</span><span class="n">minute</span>
<span class="kd">let</span> <span class="nv">second</span> <span class="p">=</span> <span class="n">components</span><span class="p">.</span><span class="n">second</span>
</code></pre></div><p>就会看到如下的结果：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/to9cv.png" alt=""></p>]]></content>
		</item>
		
		<item>
			<title>解决macOS下vim不能访问系统剪切板的问题</title>
			<link>https://blog.5km.studio/2016/11/17/vim-clipboard-macos/</link>
			<pubDate>Thu, 17 Nov 2016 22:25:42 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/11/17/vim-clipboard-macos/</guid>
			<description>&lt;p&gt;又好久没写博文了，最近忙工作的事情，焦头烂额，最近总算有些时间来干点自己的事情了，本文就说一下一直困扰我很久的问题，macOS下系统自带的vim不支持访问系统剪切板的问题，即不支持寄存器 &lt;strong&gt;+&lt;/strong&gt; 和 * 的问题。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>又好久没写博文了，最近忙工作的事情，焦头烂额，最近总算有些时间来干点自己的事情了，本文就说一下一直困扰我很久的问题，macOS下系统自带的vim不支持访问系统剪切板的问题，即不支持寄存器 <strong>+</strong> 和 * 的问题。</p>
<p>网上了解到用homebrew安装的vim是支持的，所以通过以下命令安装vim即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">brew install vim --override-system-vim
</code></pre></div><p>顺利的话就会安装成功！</p>
<p>但是执行which vim，你会发现还是指向系统自带的vim目录<code>/usr/bin/vim</code>，不要紧只需要重新开启一个终端窗口即可，再执行which vim，就会发现变成了<code>/usr/local/bin/vim</code>。</p>
<p>然后再重新打开的终端里，用vim打开一个文件就可以用 + 和 * 寄存器了，从而完成vim向系统其其他GUI软件中粘贴文本和将剪切板里的内容粘贴到vim里了。</p>]]></content>
		</item>
		
		<item>
			<title>《雷神之锤》丧心病狂的平方根倒数速算法</title>
			<link>https://blog.5km.studio/2016/09/28/q_sqrt-dev/</link>
			<pubDate>Wed, 28 Sep 2016 23:02:48 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/09/28/q_sqrt-dev/</guid>
			<description>&lt;p&gt;今天学习《Python科学计算》，看到了一个绝对丧心病狂、计算速度碾压标准sqrt库函数的平方根倒数速算法，据说平均比标准库的sqrt快4倍。这个算法是约翰-卡马克（John Carmack）创造的，据说他是一个伟大的3D引擎开发者，《雷神之锤3》用的就是他开发的3D引擎。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天学习《Python科学计算》，看到了一个绝对丧心病狂、计算速度碾压标准sqrt库函数的平方根倒数速算法，据说平均比标准库的sqrt快4倍。这个算法是约翰-卡马克（John Carmack）创造的，据说他是一个伟大的3D引擎开发者，《雷神之锤3》用的就是他开发的3D引擎。</p>
<p>贴出代码 <strong>1/sqrt(number)</strong> ：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">float</span> <span class="nf">Q_sqrt</span><span class="p">(</span> <span class="kt">float</span> <span class="n">number</span> <span class="p">)</span> <span class="p">{</span>
	<span class="kt">long</span> <span class="n">i</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">x2</span><span class="p">,</span> <span class="n">y</span><span class="p">;</span>
	<span class="k">const</span> <span class="kt">float</span> <span class="n">threeHalfs</span> <span class="o">=</span> <span class="mf">1.5F</span><span class="p">;</span>

	<span class="n">x2</span> <span class="o">=</span> <span class="n">number</span> <span class="o">*</span> <span class="mf">0.5F</span><span class="p">;</span>
	<span class="n">y</span> <span class="o">=</span> <span class="n">number</span><span class="p">;</span>
	<span class="n">i</span> <span class="o">=</span> <span class="o">*</span> <span class="p">(</span> <span class="kt">long</span> <span class="o">*</span> <span class="p">)</span> <span class="o">&amp;</span><span class="n">y</span><span class="p">;</span>
	<span class="n">i</span> <span class="o">=</span> <span class="mh">0x5f3759df</span> <span class="o">-</span> <span class="p">(</span><span class="n">i</span> <span class="o">&gt;&gt;</span> <span class="mi">1</span><span class="p">);</span>	<span class="c1">// 这是什么鬼？
</span><span class="c1"></span>	<span class="n">y</span> <span class="o">=</span> <span class="o">*</span> <span class="p">(</span> <span class="kt">float</span> <span class="o">*</span> <span class="p">)</span> <span class="o">&amp;</span><span class="n">i</span><span class="p">;</span>
	<span class="n">y</span> <span class="o">=</span> <span class="n">y</span> <span class="o">*</span> <span class="p">(</span> <span class="n">threeHalfs</span> <span class="o">-</span> <span class="p">(</span> <span class="n">x2</span> <span class="o">*</span> <span class="n">y</span> <span class="o">*</span> <span class="n">y</span> <span class="p">)</span> <span class="p">);</span>
	<span class="k">return</span> <span class="n">y</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>后续有机会的话我会写篇文章研究一下 让人摸不到头脑的 <strong>0x5f3759df</strong> 的推导，有个叫 <strong>Chris Lomont</strong> 疯子（其实他是个数学家）不服，怒了，然后暴力方法找到了一个更好的数 <strong>0x5f375a86</strong> ，呵呵了。</p>
<h3 id="python科学计算测试代码">Python科学计算测试代码</h3>
<p>这里先用Python科学计算的方法检验一下这个速算法的误差，在 <strong>IPython Notebook</strong> 中测试。</p>
<h4 id="代码">代码</h4>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="o">%</span><span class="n">matplotlib</span> <span class="kr">inline</span>
<span class="n">import</span> <span class="n">numpy</span> <span class="n">as</span> <span class="n">np</span>
<span class="n">import</span> <span class="n">matplotlib</span><span class="p">.</span><span class="n">pyplot</span> <span class="n">as</span> <span class="n">plt</span>

<span class="n">number</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linspace</span><span class="p">(</span><span class="mf">0.1</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">100</span><span class="p">)</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">number</span><span class="p">.</span><span class="n">astype</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">float32</span><span class="p">)</span>
<span class="n">x2</span> <span class="o">=</span> <span class="n">y</span> <span class="o">*</span> <span class="mf">0.5</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">y</span><span class="p">.</span><span class="n">view</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">int32</span><span class="p">)</span>
<span class="n">i</span><span class="p">[</span><span class="o">:</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x5f3759df</span> <span class="o">-</span> <span class="p">(</span><span class="n">i</span> <span class="o">&gt;&gt;</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">y</span> <span class="o">*</span> <span class="p">(</span><span class="mf">1.5</span> <span class="o">-</span> <span class="n">x2</span> <span class="o">*</span> <span class="n">y</span> <span class="o">*</span> <span class="n">y</span><span class="p">)</span>
<span class="n">y_s</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">/</span> <span class="n">np</span><span class="p">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>

<span class="n">error</span> <span class="o">=</span> <span class="n">y_s</span> <span class="o">-</span> <span class="n">y</span>

<span class="n">np</span><span class="p">.</span><span class="n">max</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">abs</span><span class="p">(</span><span class="n">error</span><span class="p">))</span>

<span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">4</span><span class="p">))</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">number</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;$Fast\ Inverse\ Square\ Root$&#34;</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">number</span><span class="p">,</span> <span class="n">y_s</span><span class="p">,</span> <span class="s">&#34;--&#34;</span><span class="p">,</span> <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;$Standard\ Inverse\ Square\ Root$&#34;</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">&#34;Number&#34;</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">&#34;Inverse Square Root Value Of Number&#34;</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">&#34;Inverse Square Root&#34;</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>

<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">number</span><span class="p">,</span> <span class="n">error</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">&#34;Number&#34;</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">&#34;The Error Of The two Methods&#34;</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">&#34;The Error&#34;</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div><h4 id="结果">结果</h4>
<p>两种方法得到曲线如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/z372t.png" alt="line"></p>
<p>偏差曲线如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/j8ec4.png" alt="error"></p>
<p>可以得到最大误差和最小误差：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">np</span><span class="p">.</span><span class="n">max</span><span class="p">(</span><span class="n">error</span><span class="p">),</span> <span class="n">np</span><span class="p">.</span><span class="n">min</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</code></pre></div><p>得到结果如下：</p>
<ul>
<li><strong>(0.0050456140410597428, -4.9357368359093101e-08)</strong></li>
</ul>
<p>可以看到误差还是很微小的，所以当对速度要求高并且数据不需太精确时，用这个速算法比较合适。</p>]]></content>
		</item>
		
		<item>
			<title>Anaconda包管理工具conda包下载加速</title>
			<link>https://blog.5km.studio/2016/09/24/anaconda-speed-dev/</link>
			<pubDate>Sat, 24 Sep 2016 22:30:33 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/09/24/anaconda-speed-dev/</guid>
			<description>&lt;p&gt;好久没有发文章了，最近有些迷茫，加上实习工作的原因，人变得有些疲劳，过去那种乐于研究有趣东西的兴致被冲淡了，但这几天终于鼓起勇气拿起了几个星期前买的书《Python科学计算》，决定学习一下，用作自己硕士课题的一个研究工具，来替代matlab。Matlab收费，用不起，而python是开源的，同时还有很多优秀的IDE，也都是免费的；但我更看重的是python在如今的地位，废话不多说了，进入主题吧。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>好久没有发文章了，最近有些迷茫，加上实习工作的原因，人变得有些疲劳，过去那种乐于研究有趣东西的兴致被冲淡了，但这几天终于鼓起勇气拿起了几个星期前买的书《Python科学计算》，决定学习一下，用作自己硕士课题的一个研究工具，来替代matlab。Matlab收费，用不起，而python是开源的，同时还有很多优秀的IDE，也都是免费的；但我更看重的是python在如今的地位，废话不多说了，进入主题吧。</p>
<p>我选择了 <strong>Anaconda</strong> 作为安装Python科学计算开发包的方式，因为最简单，只需下载安装包，一直下一步就安装好了，不但集成了几个开发环境，还附有很多科学计算的工具包，以及 <strong>conda</strong> 包管理工具。当使用 <strong>conda</strong> 安装包的时候，你会发现，那下载速度简直令人发指，等很长时间后往往是失败的结果，悲痛呀！</p>
<p>重点来了，本文就是来告诉你如何加速 <strong>conda</strong> 来的。</p>
<p><strong>以在macOS为例</strong></p>
<ul>
<li>
<p>下载<a href="https://www.continuum.io/downloads">Anaconda</a>，下载后运行安装包 <strong>Anaconda2-4.1.1-MacOSX-x86_64.pkg</strong> ，选择安装在磁盘，这样macOS的任何用户都可使用。安装目录是 <strong>/anaconda</strong> ，进入目录会看到安装的所有工具及工具包。</p>
</li>
<li>
<p>此时，在终端中无法执行 <strong>anaconda</strong> 的任何工具，因为并没有将工具所在目录加入环境变量， 工具目录是 <strong>/anaconda/bin</strong> ，可以执行以下命令加入到环境变量并生效。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"> <span class="c1">## 如果shell用的是bash，那就对.bashrc操作</span>
 <span class="c1"># echo &#39;export PATH=$PATH:/anaconda/bin&#39; &gt; .bashrc</span>
 <span class="c1"># source .bashrc</span>
 <span class="c1"># 如果shell用的是zsh，那就对.zshrc操作</span>
<span class="nb">echo</span> <span class="s1">&#39;export PATH=$PATH:/anaconda/bin&#39;</span> &gt; .zshrc
<span class="nb">source</span> .zshrc
</code></pre></div><div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">
</code></pre></div></li>
<li>
<p>此时就可以在终端中调用 <strong>conda</strong> 命令了，可以执行一次 <strong>conda update</strong> 感受一下奇慢的速度。</p>
</li>
<li>
<p>解决网速问题，在国内需要更换源来解决，<a href="https://mirrors.tuna.tsinghua.edu.cn">清华镜像源</a>中有 <strong>anaconda</strong> 的，简直就是福音，执行如下操作就可以更换源了：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">conda config --add channels <span class="s1">&#39;https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/&#39;</span>
conda config --set show_channel_urls yes
</code></pre></div><div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">
</code></pre></div></li>
<li>
<p><code>~/.condarc</code>文件中注释掉原来默认使用的官方通道，内容如下的样子：</p>
<div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">channels:
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
#  - defaults
show_channel_urls: true
ssl_verify: true
</code></pre></div><div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">
- 此时可以尝试一下 conda 的包安装，你会发现已经飞起来了。

</code></pre></div></li>
<li>
<p>然而这样只是更换了命令行下的包管理源，要想更改anaconda用户界面中的包管理源，还需如下操作：</p>
<ul>
<li>打开 <strong>navigator</strong>，左侧边栏选择 <strong>Environment</strong> ，如下：
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/unpmp.png" alt="navigator"></li>
<li>如下图，单击顶栏 <strong>Channels</strong>，在弹出的窗口会看到我们刚才添加的清华镜像源，但没有选中，选中即可，这样之后界面中管理包就会连接清华的镜像，速度很快。
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/fzrir.png" alt="channels"></li>
</ul>
</li>
</ul>]]></content>
		</item>
		
		<item>
			<title>iOS开发学习问题解决笔记</title>
			<link>https://blog.5km.studio/2016/06/22/iOS-Learn-Notes-Develop/</link>
			<pubDate>Wed, 22 Jun 2016 14:44:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/06/22/iOS-Learn-Notes-Develop/</guid>
			<description>&lt;p&gt;本文主要用来整理学习iOS开发过程中遇到的问题及解决办法，会持续更新。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>本文主要用来整理学习iOS开发过程中遇到的问题及解决办法，会持续更新。</p>
<h3 id="内容"><strong>内容</strong></h3>
<h4 id="xcode7中nsurl方法无法访问网络问题">Xcode7中NSURL方法无法访问网络问题</h4>
<p>date:   2016-02-25 08:00:00 +0800</p>
<p>在UIWebView调用LoadRequest方法时出现网络访问阻断问题，控制台提示如下：</p>
<blockquote>
<p>Application Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app&rsquo;s Info.plist file.</p>
</blockquote>
<h5 id="解决办法"><strong>解决办法</strong></h5>
<p>在info.plist中添加如下图红框中的键值对:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ptm8l.png" alt="1"></p>
<p>plist代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="o">&lt;</span><span class="n">dict</span><span class="o">&gt;</span>
	<span class="o">&lt;</span><span class="n">key</span><span class="o">&gt;</span><span class="n">NSAllowsArbitraryLoads</span><span class="o">&lt;/</span><span class="n">key</span><span class="o">&gt;</span>
	<span class="o">&lt;</span><span class="nb">true</span><span class="o">/&gt;</span>
<span class="o">&lt;/</span><span class="n">dict</span><span class="o">&gt;</span>
</code></pre></div><h4 id="清空xcode启动欢迎界面的最近打开工程的列表">清空Xcode启动欢迎界面的最近打开工程的列表</h4>
<p>date:   2016-06-22 14:44:00 +0800</p>
<p>有时曾经打开一些乱七八糟的xcode工程，依然会出现在欢迎界面最近工程列表中，这对于强迫症来说太折磨了。</p>
<h5 id="解决方法">解决方法</h5>
<p>唯有清除，才能变得和谐，很简单，打开xcode，菜单栏<strong>File</strong>-&gt;<strong>Open Recent</strong>-&gt;<strong>Clear Menu</strong>，就像下面图片所示：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/cgwi4.jpg" alt="clearRecent"></p>]]></content>
		</item>
		
		<item>
			<title>Apple平台开发-第三方库管理工具CocoaPods的安装和使用</title>
			<link>https://blog.5km.studio/2016/06/20/cocoapods-install-dev/</link>
			<pubDate>Mon, 20 Jun 2016 21:17:10 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/06/20/cocoapods-install-dev/</guid>
			<description>&lt;p&gt;今天了解macOS的串口编程，偶然了解到第三方库ORSSerialPort库，继而了解到了第三方库的管理工具CocoaPods，貌似这有助于在以后进行apple平台（iOS、watchOS、tvOS和macOS）开发，索性就安装一下吧。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天了解macOS的串口编程，偶然了解到第三方库ORSSerialPort库，继而了解到了第三方库的管理工具CocoaPods，貌似这有助于在以后进行apple平台（iOS、watchOS、tvOS和macOS）开发，索性就安装一下吧。</p>
<h3 id="什么是cocoapods">什么是CocoaPods</h3>
<p><a href="https://cocoapods.org">CocoaPods</a>官网中是这样描述的：</p>
<blockquote>
<p>CocoaPods is a dependency manager for Swift and Objective-C Cocoa projects. It has over eighteen thousand libraries and can help you scale your projects elegantly. </br></p>
</blockquote>
<p>所以它可以帮助我们有效管理Swift和Objective-C项目中使用的第三方库，所以非常有必要安装和使用，哈哈。</p>
<h3 id="安装">安装</h3>
<p><a href="https://cocoapods.org">CocoaPods</a>官网还告诉我们：</p>
<blockquote>
<p>CocoaPods is built with Ruby and is installable with the default Ruby available on OS X. We recommend you use the default ruby.</p>
</blockquote>
<p>CocoaPods由Ruby实现，因为macOS中默认安装了Ruby，可以用其gem工具进行安装，在国内你懂的，gem使用的源只可远观，所以要替换成国内的源，我在<a href="http://www.smslit.top/jekyll/2015/08/25/jekyllInstallFedoraLinux.html">Fedora下安装Jekyll</a>一文中有描述，可以参考。</p>
<p>更换为**<a href="https://ruby.taobao.org/">https://ruby.taobao.org/</a>**的源后，那么我们可以通过以下命令安装CocoaPods：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ sudo gem install cocoapods
</code></pre></div><p>安装成功后，我们就可以使用命令<code>pod</code>来使用<a href="https://cocoapods.org">CocoaPods</a>了。</p>
<h3 id="初始化">初始化</h3>
<p>安装完成后，还需要初始化一下，执行命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ pod setup
</code></pre></div><p>其实初始化的操作会先git clone在github上的CocoaPods源，可是在国内，你依旧懂得，都是泪，太慢了，甚至会出现下面的错误：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ pod setup
Setting up CocoaPods master repo
<span class="o">[</span>!<span class="o">]</span> /usr/bin/git clone https://github.com/CocoaPods/Specs.git master

Cloning into <span class="s1">&#39;master&#39;</span>...
error: RPC failed<span class="p">;</span> curl <span class="m">56</span> SSLRead<span class="o">()</span> <span class="k">return</span> error -36
fatal: The remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed
</code></pre></div><p>那怎么办？只好咱自己用命令clone，然后再用<code>pod setup</code>命令进行初始化配置了，setup默认操作是将源clone到目录<code>~/.cocoapods/repos</code>目录下，所以先进此目录进行<code>git clone</code>操作：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ <span class="nb">cd</span> ~/.cocoapods/repos
$ git clone git@github.com:CocoaPods/Specs.git master
</code></pre></div><p>做好心理准备，镜像很大而且速度很慢，完成后执行<code>pod setup</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ pod setup
Setting up CocoaPods master repo
  $ /usr/bin/git pull --ff-only
  From https://github.com/CocoaPods/Specs
     231c12b..1e7a611  master     -&gt; origin/master
  Updating 231c12b..1e7a611
  Fast-forward
   .../ADMozaicCollectionViewLayout.podspec.json      <span class="p">|</span>  <span class="m">23</span> +++++
   .../0.2/ASJGooglePlaces.podspec.json               <span class="p">|</span>  <span class="m">22</span> +++++
   .../AXPopoverView/0.5.3/AXPopoverView.podspec.json <span class="p">|</span>  <span class="m">39</span> ++++++++
   Specs/Adjust/4.7.1/Adjust.podspec.json             <span class="p">|</span>  <span class="m">69</span> ++++++++++++++
   Specs/Agrume/2.5.1/Agrume.podspec.json             <span class="p">|</span>  <span class="m">26</span> ++++++
	.
	.
	.
   Specs/eeGeo/1.0.635/eeGeo.podspec.json             <span class="p">|</span>  <span class="m">56</span> +++++++++++
   Specs/eeGeo/1.0.636/eeGeo.podspec.json             <span class="p">|</span>  <span class="m">56</span> +++++++++++
   .../0.0.1/ios-swift-utils.podspec.json             <span class="p">|</span>  <span class="m">22</span> +++++
   <span class="m">71</span> files changed, <span class="m">2200</span> insertions<span class="o">(</span>+<span class="o">)</span>, <span class="m">1</span> deletion<span class="o">(</span>-<span class="o">)</span>
   create mode <span class="m">100644</span> Specs/JeraUtils/0.3.2/JeraUtils.podspec.json
   create mode <span class="m">100644</span> Specs/KCBannerView/0.0.2/KCBannerView.podspec.json
   create mode <span class="m">100644</span> Specs/KDCircularProgress/1.5.0-beta1/KDCircularProgress.podspec.json
   create mode <span class="m">100644</span> Specs/KPPopView/0.0.6/KPPopView.podspec.json
	.
	.
	.
   create mode <span class="m">100644</span> Specs/eeGeo/1.0.635/eeGeo.podspec.json
   create mode <span class="m">100644</span> Specs/eeGeo/1.0.636/eeGeo.podspec.json
   create mode <span class="m">100644</span> Specs/ios-swift-utils/0.0.1/ios-swift-utils.podspec.json
Setup completed
</code></pre></div><p>会出现类似上面的初始化信息，主要是git pull，然后创建一些文件，下面我们就可以使用pod管理向我们工程中添加第三方库了。</p>
<h3 id="使用">使用</h3>
<p>我已经创建了一个Xcode的macOS工程<strong>SerialPlot</strong>，需要用到第三方库<a href="https://github.com/armadsen/ORSSerialPort">ORSSerialPort</a>，那么跟着我开始吧。</p>
<p>首先进入Xcode工程的目录，可以先查看目录中的内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ <span class="nb">cd</span> ~/Documents/workspace/OSX/SerialPlot/
$ ls -l
total <span class="m">0</span>
drwxr-xr-x  <span class="m">7</span> smslit  staff  <span class="m">238</span> Jun <span class="m">20</span> 17:25 SerialPlot
drwxr-xr-x  <span class="m">5</span> smslit  staff  <span class="m">170</span> Jun <span class="m">21</span> 08:40 SerialPlot.xcodeproj
drwxr-xr-x  <span class="m">3</span> smslit  staff  <span class="m">102</span> Jun <span class="m">21</span> 08:40 SerialPlot.xcworkspace
drwxr-xr-x  <span class="m">4</span> smslit  staff  <span class="m">136</span> Jun <span class="m">20</span> 17:25 SerialPlotTests
drwxr-xr-x  <span class="m">4</span> smslit  staff  <span class="m">136</span> Jun <span class="m">20</span> 17:25 SerialPlotUITests
</code></pre></div><p>然后初始化pod文件，再查看目录下文件，会发现增加了一个podfile：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ pod init
$ ls -l
total <span class="m">8</span>
-rw-r--r--  <span class="m">1</span> smslit  staff  <span class="m">427</span> Jun <span class="m">21</span> 09:29 Podfile
drwxr-xr-x  <span class="m">7</span> smslit  staff  <span class="m">238</span> Jun <span class="m">20</span> 17:25 SerialPlot
drwxr-xr-x  <span class="m">5</span> smslit  staff  <span class="m">170</span> Jun <span class="m">21</span> 08:40 SerialPlot.xcodeproj
drwxr-xr-x  <span class="m">3</span> smslit  staff  <span class="m">102</span> Jun <span class="m">21</span> 08:40 SerialPlot.xcworkspace
drwxr-xr-x  <span class="m">4</span> smslit  staff  <span class="m">136</span> Jun <span class="m">20</span> 17:25 SerialPlotTests
drwxr-xr-x  <span class="m">4</span> smslit  staff  <span class="m">136</span> Jun <span class="m">20</span> 17:25 SerialPlotUITests
</code></pre></div><p>编辑podfile，增加我们需要的第三方库信息，一般可以在第三方库的github网站找到相关使用cocoapods安装库的信息，比如我使用的<a href="https://github.com/armadsen/ORSSerialPort">ORSSerialPort</a>的<a href="https://github.com/armadsen/ORSSerialPort/wiki/Installing-ORSSerialPort">安装指导</a>页面：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/gyjaf.png" alt="usingCocoaPods"></p>
<p>按照指导用vim编辑Podfile，添加一行内容——<code>pod ORSSerialPort</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ vim Podfile
</code></pre></div><p>添加后文件内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="c1"># Uncomment this line to define a global platform for your project</span>
<span class="c1"># platform :ios, &#39;9.0&#39;</span>

pod <span class="s2">&#34;ORSSerialPort&#34;</span>

target <span class="s1">&#39;SerialPlot&#39;</span> <span class="k">do</span>
  <span class="c1"># Comment this line if you&#39;re not using Swift and don&#39;t want to use dynamic frameworks</span>
  use_frameworks!

  <span class="c1"># Pods for SerialPlot</span>

  target <span class="s1">&#39;SerialPlotTests&#39;</span> <span class="k">do</span>
    inherit! :search_paths
    <span class="c1"># Pods for testing</span>
  end

  target <span class="s1">&#39;SerialPlotUITests&#39;</span> <span class="k">do</span>
    inherit! :search_paths
    <span class="c1"># Pods for testing</span>
  end

end
</code></pre></div><p>然后，我们就可以用pod安装<code>ORSSerialPort</code>库了：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ pod install
Analyzing dependencies
Downloading dependencies
Installing ORSSerialPort <span class="o">(</span>2.0.2<span class="o">)</span>
Generating Pods project
Integrating client project
Sending stats
Pod installation complete! There is <span class="m">1</span> dependency from the Podfile and <span class="m">1</span> total pod installed.
</code></pre></div><p>然后可以查看一下目录下文件，会发现多了一个文件夹和文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ls -l
total <span class="m">16</span>
-rw-r--r--  <span class="m">1</span> smslit  staff  <span class="m">448</span> Jun <span class="m">21</span> 12:20 Podfile
-rw-r--r--  <span class="m">1</span> smslit  staff  <span class="m">218</span> Jun <span class="m">21</span> 12:23 Podfile.lock
drwxr-xr-x  <span class="m">8</span> smslit  staff  <span class="m">272</span> Jun <span class="m">21</span> 12:23 Pods
drwxr-xr-x  <span class="m">7</span> smslit  staff  <span class="m">238</span> Jun <span class="m">20</span> 17:25 SerialPlot
drwxr-xr-x  <span class="m">5</span> smslit  staff  <span class="m">170</span> Jun <span class="m">21</span> 08:40 SerialPlot.xcodeproj
drwxr-xr-x  <span class="m">3</span> smslit  staff  <span class="m">102</span> Jun <span class="m">21</span> 08:40 SerialPlot.xcworkspace
drwxr-xr-x  <span class="m">4</span> smslit  staff  <span class="m">136</span> Jun <span class="m">20</span> 17:25 SerialPlotTests
drwxr-xr-x  <span class="m">4</span> smslit  staff  <span class="m">136</span> Jun <span class="m">20</span> 17:25 SerialPlotUITests
</code></pre></div><p>然后我们打开自己的工程，就可以在源代码中添加库（<code>#import &lt;ORSSerialPort/ORSSerialPort.h&gt;</code>）就可以调用库的api了：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ open SerialPlot.xcworkspace
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>用KiCAD设计使用CH340G的USB转串口的模块</title>
			<link>https://blog.5km.studio/2016/06/15/usb2ttl_ch340-elec/</link>
			<pubDate>Wed, 15 Jun 2016 16:47:31 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/06/15/usb2ttl_ch340-elec/</guid>
			<description>&lt;p&gt;近期学习了KiCAD的使用，就决定设计一款USB转串口模块来练习一下，设计中使用性价比很高的CH340的USB转串口芯片。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>近期学习了KiCAD的使用，就决定设计一款USB转串口模块来练习一下，设计中使用性价比很高的CH340的USB转串口芯片。</p>
<p>KiCad - 是一个GPL的EDA（Electronic Design Automation - 电子设计自动化）软件包，我看中KiCAD的几个优点：</p>
<ul>
<li>开源，意味着免费使用全部功能，这对于我这种穷渣学生党简直是天大的福音。</li>
<li>跨平台，这很重要，我更喜欢用macOS或者linux，一般EDA软件只有windows版本，一提到跨平台，也就有支持我钟爱的macOS的版本了。</li>
<li>相对更好入门，功能很强大，目前中文资料较少。</li>
</ul>
<p>我参与了<a href="https://github.com/">guthub</a>上<a href="https://github.com/ocrobot">ocrobot</a>发起的KiCAD汉化工作，目前基本完成，项目是<a href="https://github.com/ocrobot/kicad-i18n">kicad-i18n</a>，如果您有兴趣可以一起加入到汉化工作中，其中肯定还有很多翻译不合适的地方。</p>
<p>为了练手，用Kicad做了一款以CH340G为核心的USB转USART的模块，废话不多说了，下面就展示一下版本v0.1.2（右）和v0.1（左）的模块的实物照片：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/t43zd.jpg" alt="module.jpg"></p>
<p>我已经将工程文件托管到github上了，有兴趣的可以任意使用和修改。现在是版本v0.1.2！</p>
<table>
<thead>
<tr>
<th style="text-align:left">平台</th>
<th style="text-align:left">项目地址</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">github</td>
<td style="text-align:left">usb2serial-CH340G:<a href="https://github.com/smslit/usb2serial-CH340G">https://github.com/smslit/usb2serial-CH340G</a></td>
</tr>
</tbody>
</table>
<h4 id="目标功能">目标功能</h4>
<ol>
<li>通用的usb转uart功能</li>
<li>为Arduino下载程序</li>
<li>自由选择3.3V或5V供电，并可输出。</li>
<li>电源LED指示、TX和RX的LED指示</li>
</ol>
<h4 id="原理图">原理图</h4>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/rjbiq.png" alt="sch"></p>
<h4 id="pcb">PCB</h4>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/hsc46.png" alt="pcb"></p>
<h4 id="pcb的3d预览">PCB的3D预览</h4>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/iew84.png" alt="sch"></p>
<h4 id="附">附：</h4>
<p><a href="http://www.wch.cn/product/CH340.html">CH340</a> 是一个 USB 总线的转接芯片,实现 USB 转串口、USB 转 IrDA 红外或者 USB 转打印口，其中设计中采用的CH340G是一款只有USB转串口功能的芯片。</p>
<h5 id="芯片驱动">芯片驱动</h5>
<ol>
<li><a href="http://www.wch.cn/download/CH341SER_ZIP.html">windows驱动</a></li>
<li><a href="http://www.wch.cn/download/CH341SER_MAC_ZIP.html">Mac OSX驱动</a></li>
<li><a href="http://www.wch.cn/download/CH341SER_LINUX_ZIP.html">Linux驱动</a></li>
</ol>
<h5 id="注意">注意</h5>
<p>电路完成焊接后，因为稳压二极管的性能误差，可能会造成输出的3.3V不准，电压低于3.2V时可能会造成Mac电脑在使用3.3V的工作模式时UART通信有问题。可以通过更换稳压二极管或者更小阻值的电阻R3来解决。</p>]]></content>
		</item>
		
		<item>
			<title>在MacOS终端下使用Ino进行Arduino开发</title>
			<link>https://blog.5km.studio/2016/06/15/ino-vim-arduino/</link>
			<pubDate>Wed, 15 Jun 2016 09:10:38 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/06/15/ino-vim-arduino/</guid>
			<description>&lt;p&gt;虽然我算不上真正的Vimer，但我还是惊叹与vim的强大，也正慢慢学习vim的使用，经朋友介绍可以用命令行工具&lt;strong&gt;Ino&lt;/strong&gt;进行Arduino开发，那么就可以在终端中使用vim进行Arduino的coding了。其实我使用了好长时间了，觉得不错，适合喜欢用终端敲命令的朋友，所以在此写一下安装和使用。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>虽然我算不上真正的Vimer，但我还是惊叹与vim的强大，也正慢慢学习vim的使用，经朋友介绍可以用命令行工具<strong>Ino</strong>进行Arduino开发，那么就可以在终端中使用vim进行Arduino的coding了。其实我使用了好长时间了，觉得不错，适合喜欢用终端敲命令的朋友，所以在此写一下安装和使用。</p>
<h3 id="ino工具安装">Ino工具安装</h3>
<p><a href="http://inotool.org/"><strong>Ino</strong></a>依赖于Arduino IDE，所以使用Ino之前保证已经安装了Arduino IDE，有了Ino你可以做以下事情：</p>
<ul>
<li>快速新建Arduino工程。</li>
<li>编译工程。</li>
<li>上传工程到Arduino开发板。</li>
<li>串口通信。</li>
</ul>
<h4 id="安装">安装</h4>
<p>可以通过下载最新的源码进行安装，不过这里我还是建议用工具进行安装，<strong>Ino</strong>使用python实现的，一般mac上都安装了python，所以不用担心，那么<strong>Ino</strong>可以用Python的包管理工具进行安装，<strong>pip</strong>或者<strong>easy_insyall</strong>。这里我们用<strong>pip</strong>，需要安装pip：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ curl https://bootstrap.pypa.io/ez_setup.py -o - <span class="p">|</span> sudo python
$ sudo easy_install pip
</code></pre></div><p><strong>pip</strong>可以用来安装、更新或卸载python软件包，那么我们就可以安装<strong>Ino</strong>了：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ sudo pip isntall ino
</code></pre></div><h4 id="依赖">依赖</h4>
<p>要正常使用<strong>Ino</strong>，依赖于以下工具：</p>
<ul>
<li><strong>Python</strong> 2.6+</li>
<li><strong>Arduino IDE</strong></li>
<li><strong>picocom</strong>，用来进行串口通信</li>
</ul>
<p>第一个Python需求macOS天然满足，Arduino IDE我们需要自己去<a href="https://www.arduino.cc/en/Main/OldSoftwareReleases">官网</a>下载，友情提示一定要安装1.0.x版本的，1.6.x或更新的有问题。</p>
<p><strong>picocom</strong>工具的话，我们可以用macOS下的homebrew进行安装，在这之前先来安装强大的mac软件包管理工具<a href="http://brew.sh/index_zh-cn.html"><strong>Homebrew</strong></a>，它的能力类似与Ubuntu Linux的apt-get。</p>
<p>安装<strong>Homebrew</strong></p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ /usr/bin/ruby -e <span class="s2">&#34;</span><span class="k">$(</span>curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install<span class="k">)</span><span class="s2">&#34;</span>
</code></pre></div><p>安装<strong>picocom</strong>：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ brew install picocom
</code></pre></div><h3 id="ino的使用">ino的使用</h3>
<p>如果安装成功了，顺利的话，终端下敲命令<code>ino --help</code>或<code>ino -h</code>，会出现简单的使用说明：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ino -h
usage: ino <span class="o">[</span>-h<span class="o">]</span> <span class="o">{</span>list-models,preproc,upload,init,build,clean,serial<span class="o">}</span> ...

Ino is a command-line toolkit <span class="k">for</span> working with Arduino hardware.

It is intended to replace Arduino IDE UI <span class="k">for</span> those who prefer to work in
terminal or want to integrate Arduino development in a 3rd party IDE.

Ino can build sketches, libraries, upload firmwares, establish
serial-communication. For this it is split in a bunch of subcommands, like git
or mercurial <span class="k">do</span>. The full list is provided below. You may run any of them with
--help to get further help. E.g.:

    ino build --help

positional arguments:
  <span class="o">{</span>list-models,preproc,upload,init,build,clean,serial<span class="o">}</span>
    build               Build firmware from the current directory project
    clean               Remove intermediate compilation files completely
    init                Setup a new project in the current directory
    list-models         List supported Arduino board models
    preproc             Transform a sketch file into valid C++ <span class="nb">source</span>
    serial              Open a serial monitor
    upload              Upload built firmware to the device

optional arguments:
  -h, --help            show this <span class="nb">help</span> message and <span class="nb">exit</span>
</code></pre></div><p>命令表如下：</p>
<table>
<thead>
<tr>
<th style="text-align:center">命令</th>
<th style="text-align:left">含义</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">build</td>
<td style="text-align:left">编译当前目录下的工程</td>
</tr>
<tr>
<td style="text-align:center">clean</td>
<td style="text-align:left">删除编译生成的中间文件</td>
</tr>
<tr>
<td style="text-align:center">init</td>
<td style="text-align:left">在当前目录下新建工程</td>
</tr>
<tr>
<td style="text-align:center">list-model</td>
<td style="text-align:left">列出所有支持的Arduino开发板</td>
</tr>
<tr>
<td style="text-align:center">preproc</td>
<td style="text-align:left">将工程文件转换为有效c++文件</td>
</tr>
<tr>
<td style="text-align:center">serial</td>
<td style="text-align:left">打开串口监视器</td>
</tr>
<tr>
<td style="text-align:center">upload</td>
<td style="text-align:left">给Arduino设备上传程序</td>
</tr>
</tbody>
</table>
<p>也可以访问<a href="http://inotool.org/quickstart">http://inotool.org/quickstart</a>简单了解<strong>Ino</strong>的使用，这里简单说一下使用。</p>
<h4 id="查看arduino型号">查看Arduino型号</h4>
<p>查看一下是否有支持的Arduino开发板。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ino list-models
Searching <span class="k">for</span> Board description file <span class="o">(</span>boards.txt<span class="o">)</span> ... /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/boards.txt
         uno: <span class="o">[</span>DEFAULT<span class="o">]</span> Arduino Uno
   atmega328: Arduino Duemilanove w/ ATmega328
   diecimila: Arduino Diecimila or Duemilanove w/ ATmega168
     nano328: Arduino Nano w/ ATmega328
        nano: Arduino Nano w/ ATmega168
    mega2560: Arduino Mega <span class="m">2560</span> or Mega ADK
        mega: Arduino Mega <span class="o">(</span>ATmega1280<span class="o">)</span>
    leonardo: Arduino Leonardo
     esplora: Arduino Esplora
       micro: Arduino Micro
     mini328: Arduino Mini w/ ATmega328
        mini: Arduino Mini w/ ATmega168
    ethernet: Arduino Ethernet
         fio: Arduino Fio
       bt328: Arduino BT w/ ATmega328
          bt: Arduino BT w/ ATmega168
  LilyPadUSB: LilyPad Arduino USB
  lilypad328: LilyPad Arduino w/ ATmega328
     lilypad: LilyPad Arduino w/ ATmega168
    pro5v328: Arduino Pro or Pro Mini <span class="o">(</span>5V, <span class="m">16</span> MHz<span class="o">)</span> w/ ATmega328
       pro5v: Arduino Pro or Pro Mini <span class="o">(</span>5V, <span class="m">16</span> MHz<span class="o">)</span> w/ ATmega168
      pro328: Arduino Pro or Pro Mini <span class="o">(</span>3.3V, <span class="m">8</span> MHz<span class="o">)</span> w/ ATmega328
         pro: Arduino Pro or Pro Mini <span class="o">(</span>3.3V, <span class="m">8</span> MHz<span class="o">)</span> w/ ATmega168
   atmega168: Arduino NG or older w/ ATmega168
     atmega8: Arduino NG or older w/ ATmega8
robotControl: Arduino Robot Control
  robotMotor: Arduino Robot Motor
</code></pre></div><p>上面就是支持的所有型号的开发板，冒号前的是我们用到配置文件中的，我的开发板是<strong>Arduino Pro Mini (5V, 16MHz) w/ ATmega328</strong>，所以对应的是pro5v328，你可以找到自己的板型，编译和上传的时候会用到，默认的是uno，所以一定要搞清楚自己的是哪一个型号。</p>
<h4 id="新建ino工程">新建Ino工程</h4>
<p>可以用blink模板新建工程，这之前需要创建自己的工程目录，比如<strong>blink</strong>：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ mkdir blink
$ <span class="nb">cd</span> blink
$ ino init -t blink
</code></pre></div><p>可以用tree命令列出文件树（tree命令可以用homebrew安装<code>brew install tree</code>）：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ tree
.
├── lib
└── src
    └── sketch.ino

<span class="m">2</span> directories, <span class="m">1</span> file
</code></pre></div><p>可以看到生成了两个目录，一个是lib一个是src，lib用来放第三方arduino的库，而src存放我们自己的代码，这里因为用了模板，所以生成了一个sketch.ino，可以查看其内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ cat src/sketch.ino

<span class="c1">#define LED_PIN 13</span>

void setup<span class="o">()</span>
<span class="o">{</span>
    pinMode<span class="o">(</span>LED_PIN, OUTPUT<span class="o">)</span><span class="p">;</span>
<span class="o">}</span>

void loop<span class="o">()</span>
<span class="o">{</span>
    digitalWrite<span class="o">(</span>LED_PIN, HIGH<span class="o">)</span><span class="p">;</span>
    delay<span class="o">(</span>100<span class="o">)</span><span class="p">;</span>
    digitalWrite<span class="o">(</span>LED_PIN, LOW<span class="o">)</span><span class="p">;</span>
    delay<span class="o">(</span>900<span class="o">)</span><span class="p">;</span>
<span class="o">}</span>
</code></pre></div><p>这样我们就可以用vim进行coding了，修改sketch.ino即可，当然，我们也可以将其改成其他的名字，比如blink.ino。</p>
<h4 id="编译工程">编译工程</h4>
<p>用ino build编译工程时，其实相关的是板子的mcu型号，所以使用此命令可以加-m参数指定开发板型号，就是我们之前看到的，我的是pro5v328，如果不指定-m参数的话，命令会使用默认的板型，就是list-model结果中带有**[DEFAULT]**标识的，一般为uno。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ino build -m pro5v328
Searching <span class="k">for</span> Board description file <span class="o">(</span>boards.txt<span class="o">)</span> ... /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/boards.txt
Searching <span class="k">for</span> Arduino lib version file <span class="o">(</span>version.txt<span class="o">)</span> ... /Applications/Arduino.app/Contents/Resources/Java/lib/version.txt
Detecting Arduino software version ...  1.0.6 <span class="o">(</span>1.0.6<span class="o">)</span>
Searching <span class="k">for</span> Arduino core library ... /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino
Searching <span class="k">for</span> Arduino variants directory ... /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/variants
Searching <span class="k">for</span> Arduino standard libraries ... /Applications/Arduino.app/Contents/Resources/Java/libraries
Searching <span class="k">for</span> make ... /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/make
Searching <span class="k">for</span> avr-gcc ... /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/avr-gcc
Searching <span class="k">for</span> avr-g++ ... /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/avr-g++
Searching <span class="k">for</span> avr-ar ... /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/avr-ar
Searching <span class="k">for</span> avr-objcopy ... /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/avr-objcopy
src/sketch.ino
Searching <span class="k">for</span> Arduino lib version file <span class="o">(</span>version.txt<span class="o">)</span> ... /Applications/Arduino.app/Contents/Resources/Java/lib/version.txt
Detecting Arduino software version ...  1.0.6 <span class="o">(</span>1.0.6<span class="o">)</span>
Scanning dependencies of src
Scanning dependencies of arduino
src/sketch.cpp
arduino/avr-libc/malloc.c
arduino/avr-libc/realloc.c
arduino/WInterrupts.c
arduino/wiring.c
arduino/wiring_analog.c
arduino/wiring_digital.c
arduino/wiring_pulse.c
arduino/wiring_shift.c
arduino/CDC.cpp
arduino/HardwareSerial.cpp
arduino/HID.cpp
arduino/IPAddress.cpp
arduino/main.cpp
arduino/new.cpp
arduino/Print.cpp
arduino/Stream.cpp
arduino/Tone.cpp
arduino/USBCore.cpp
arduino/WMath.cpp
arduino/WString.cpp
Linking libarduino.a
Linking firmware.elf
Converting to firmware.hex
</code></pre></div><p>当然我们还有另一种方法指定板子型号，就是添加配置文件，有三种类型的配置文件：</p>
<table>
<thead>
<tr>
<th style="text-align:left">配置文件</th>
<th style="text-align:left">作用域</th>
<th style="text-align:left">优先级</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">/etc/ino.ini</td>
<td style="text-align:left">对mac中所有用户下的所有ino工程有效</td>
<td style="text-align:left">最低</td>
</tr>
<tr>
<td style="text-align:left">~/.inorc</td>
<td style="text-align:left">对当前用户下的所有工程有效</td>
<td style="text-align:left">次之</td>
</tr>
<tr>
<td style="text-align:left">./ino.ini</td>
<td style="text-align:left">对当前工程有效</td>
<td style="text-align:left">最高</td>
</tr>
</tbody>
</table>
<p><strong>ino build</strong>不指定-m时，工具会优先查看当前工程目录下是否有ino.ini，如果有的话依据配置文件中的参数设置进行编译，如果没有，在去常看当前mac用户$HOME目录下是否有.inorc文件，如果有则据此文件中的配置编译，如果没有，再继续查找系统配置中是否有ino.ini文件，如果有则按配置编译，如果没有则按照默认板型uno进行编译，上传命令ino upload和调用串口监视器ino serial同样是此规则。</p>
<p>所以建议在每个工程下添加适配的配置文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ touch ino.ini
</code></pre></div><p>配置文件中可以指定build、upload和serial这三个功能的参数，用vim编辑ino.ini文件，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="o">[</span>build<span class="o">]</span>
board-model <span class="o">=</span> pro5v328

<span class="o">[</span>upload<span class="o">]</span>
board-model <span class="o">=</span> pro5v328
serial-port <span class="o">=</span> /dev/tty.wchusbserial1470

<span class="o">[</span>serial<span class="o">]</span>
serial-port <span class="o">=</span> /dev/tty.wchusbserial1470
</code></pre></div><p>上面的serial和upload参数用于后面。</p>
<p>这样的话，我们每次在自己的工程里只需执行命令<code>ino build</code>即可。</p>
<p>工程目录下加入配置文件后的文件树如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ tree
.
├── ino.ini
├── lib
└── src
    └── sketch.ino

	<span class="m">2</span> directories, <span class="m">2</span> files
</code></pre></div><h4 id="上传程序">上传程序</h4>
<p>上传程序会使用到串口，我用的串口工具，在系统中识别为<strong>tty.wchusbserial1470</strong>，所以配置文件中改为我们自己对应的串口，如果你不知道自己的串口是什么，可以查看<code>/dev/</code>目录下以tty为前缀的串口：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ls /dev/tty.*
/dev/tty.Bluetooth-Incoming-Port /dev/tty.wchusbserial1470
</code></pre></div><p>可以看到有效的串口，mac一般都会有蓝牙，其中有一个是蓝牙，那么另一个就是我们的串口***，所以这里配置文件就是<code>/dev/tty.wchusbserial1470</code>。</p>
<p>其实上传命令可以使用两个参数，一个是-m（同上）来指定板子型号，另一个是-p，用来指定使用串口，所以这里可以指定参数来上传程序到arduino设备：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ino upload -m pro5v328 -p /dev/tty.wchusbserial1470
</code></pre></div><p>如果不指定-p参数的话就会按照之前提到的规则去查找配置文件，使用配置文件中指定的串口进行上传，所以执行<code>ino upload</code>命令就可以了：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ino upload
Searching <span class="k">for</span> stty ... /bin/stty
Searching <span class="k">for</span> avrdude ... /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/avrdude
Searching <span class="k">for</span> avrdude.conf ... /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/etc/avrdude.conf

avrdude: AVR device initialized and ready to accept instructions

Reading <span class="p">|</span> <span class="c1">################################################## | 100% 0.02s</span>

avrdude: Device <span class="nv">signature</span> <span class="o">=</span> 0x1e950f
avrdude: reading input file <span class="s2">&#34;.build/pro5v328/firmware.hex&#34;</span>
avrdude: writing flash <span class="o">(</span><span class="m">1082</span> bytes<span class="o">)</span>:

Writing <span class="p">|</span> <span class="c1">################################################## | 100% 0.72s</span>

avrdude: <span class="m">1082</span> bytes of flash written
avrdude: verifying flash memory against .build/pro5v328/firmware.hex:
avrdude: load data flash data from input file .build/pro5v328/firmware.hex:
avrdude: input file .build/pro5v328/firmware.hex contains <span class="m">1082</span> bytes
avrdude: reading on-chip flash data:

Reading <span class="p">|</span> <span class="c1">################################################## | 100% 0.63s</span>

avrdude: verifying ...
avrdude: <span class="m">1082</span> bytes of flash verified

avrdude: safemode: Fuses OK

avrdude <span class="k">done</span>.  Thank you.<span class="o">)</span><span class="s2">&#34;&#34;</span>
</code></pre></div><h4 id="使用库文件">使用库文件</h4>
<p>如果要使用第三方库的话，可以将库放到工程目录的lib目录下，比如这里我们使用定时器来触发led的blink，用到定时器的库**<a href="https://github.com/PaulStoffregen/MsTimer2">MsTimer2</a>**，下载下来将库放到lib目录下即可，此时文件树类似如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ tree
.
├── ino.ini
├── lib
│   └── MsTimer2
│       ├── MsTimer2.cpp
│       ├── MsTimer2.h
│       └── examples
│           └── FlashLed
│               └── FlashLed.pde
└── src
    └── sketch.ino

	<span class="m">5</span> directories, <span class="m">5</span> files
</code></pre></div><p>然后修改我们的sketch.ino来使用库，可以修改为如下的内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&lt;MsTimer2.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="c1">// Switch on LED on pin 13 each second
</span><span class="c1"></span>
<span class="kt">void</span> <span class="nf">flash</span><span class="p">()</span> <span class="p">{</span>
	<span class="k">static</span> <span class="n">boolean</span> <span class="n">output</span> <span class="o">=</span> <span class="n">HIGH</span><span class="p">;</span>
	<span class="n">digitalWrite</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">output</span><span class="p">);</span>
	<span class="n">output</span> <span class="o">=</span> <span class="o">!</span><span class="n">output</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">pinMode</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>

	<span class="n">MsTimer2</span><span class="o">::</span><span class="n">set</span><span class="p">(</span><span class="mi">500</span><span class="p">,</span> <span class="n">flash</span><span class="p">);</span> <span class="c1">// 500ms period
</span><span class="c1"></span>	<span class="n">MsTimer2</span><span class="o">::</span><span class="n">start</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>

<span class="p">}</span>
</code></pre></div><p>然后我们就可以再次编译了，编译之前也可以先清除一下上一次编译生成的中间文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ino clean
</code></pre></div><p>然后再进行编译和上传即可。</p>
<h3 id="vim配合ino">vim配合Ino</h3>
<p>进行Arduino开发使用Ino可以用vim进行代码编辑，为了更方便，我们可以为vim配置快捷键来完成工程的编译、清除和上传(即build\clean\upload)，在<code>~/.vimrc</code>中添加配置即可，我的Vim配置如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">nmap &lt;F5&gt;b :!ino build&lt;CR&gt;
nmap &lt;F5&gt;c :!ino clean&lt;CR&gt;
nmap &lt;F5&gt;u :!ino upload&lt;CR&gt;
</code></pre></div><p>这样就可以在用vim编辑sketch.ino时，普通模式下按下<F5>键+b来进行编译，按下<F5>键+u键进行上传，按下<F5>键+c键进行清除编译生成的中间文件。</p>]]></content>
		</item>
		
		<item>
			<title>BLE开发笔记——协议栈HAL驱动控制LED</title>
			<link>https://blog.5km.studio/2016/05/05/halled-ble/</link>
			<pubDate>Thu, 05 May 2016 08:44:59 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/05/05/halled-ble/</guid>
			<description>&lt;p&gt;已经了解了OSAL工作原理和完成了启动过程的简单分析，那么我们可以尝试协议栈开发了，首先可以玩一下LED的控制，我有一个CC2541-DK MINI Keyfob，本文就以此为测试平台。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>已经了解了OSAL工作原理和完成了启动过程的简单分析，那么我们可以尝试协议栈开发了，首先可以玩一下LED的控制，我有一个CC2541-DK MINI Keyfob，本文就以此为测试平台。</p>
<h3 id="准备工作">准备工作</h3>
<h4 id="新建完整的协议栈工程">新建完整的协议栈工程</h4>
<p>这里以<strong>SimpleBLEPeripheral</strong>例程为基础新建工程，方法可以参考 <a href="/2016/03/21/newProject-BLE/">BLE开发笔记——简单新建属于自己的协议栈工程</a>。</p>
<h4 id="配置工程">配置工程</h4>
<ul>
<li>选择配置文件<strong>CC2541</strong></li>
<li>右键单击工程，选择<strong>Options&hellip;</strong>，启动Options窗口</li>
<li>选择左边栏General Options下的C/C++ Compiler，然后在右侧选择Preprocessor选项卡，操作正确的话，应该会看到<strong>Defined symbols:(one per line)</strong>，要修改其中的宏定义：
<ul>
<li>修改<strong>HAL_LED=FALSE</strong>为<strong>HAL_LED=TRUE</strong></li>
<li>修改<strong>POWER_SAVING</strong>为<strong>xPOWER_SAVING</strong>以禁用低功耗管理</li>
</ul>
</li>
</ul>
<p><strong>注：</strong></p>
<p>上述禁用低功耗的原因是协议栈默认LED为耗能元件，系统每次在进入休眠模式的时候会把LED关闭，当唤醒的时候有回复LED的状态，如果不禁用低功耗，LED会一直循环闪烁。</p>
<h4 id="配置硬件">配置硬件</h4>
<p>如果你所使用的硬件平台是TI官方开发套件或者是仿TI的硬件，此部分的操作可以忽略，不过我认为还是需要了解一下的。</p>
<p>如果是非官方的设计，对应HAL驱动中LED管脚有可能与官方蓝牙开发套件的设计有区别，官方开发板上LED1对应芯片的<strong>P1_0</strong>管脚，如果多于一个LED，那么LED2对应<strong>P1_1</strong>，LED3对应<strong>P1_4</strong>，LED的配置文件可以在IAR工程目录<code>HAL---&gt;Target---&gt;CC2540EB---&gt;Config</code>找到，名为<strong>hal_board_cfg.h</strong>，所以我们可以修改此文件来适配自己设计的硬件电路，默认LED配置代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cm">/* LED Configuration */</span>

<span class="cp">#if defined (HAL_BOARD_CC2530EB_REV17) &amp;&amp; !defined (HAL_PA_LNA) &amp;&amp; !defined (HAL_PA_LNA_CC2590)
</span><span class="cp"></span>    <span class="cp">#define HAL_NUM_LEDS                 3
</span><span class="cp">#elif defined (HAL_BOARD_CC2530EB_REV13) || defined (HAL_PA_LNA) || defined (HAL_PA_LNA_CC2590)
</span><span class="cp"></span>    <span class="cp">#define HAL_NUM_LEDS                 1
</span><span class="cp">#else
</span><span class="cp"></span>    <span class="cp">#error Unknown Board Indentifier
</span><span class="cp">#endif
</span><span class="cp"></span>
<span class="cp">#define HAL_LED_BLINK_DELAY()   st( { volatile uint32 i; for (i=0; i&lt;0x5800; i++) { }; } )
</span><span class="cp"></span>
<span class="cm">/* 1 - Green */</span>
<span class="cp">#define LED1_BV                        BV(0)
</span><span class="cp">#define LED1_SBIT                      P1_0
</span><span class="cp">#define LED1_DDR                       P1DIR
</span><span class="cp">#define LED1_POLARITY                  ACTIVE_HIGH
</span><span class="cp"></span>
<span class="cp">#ifdef HAL_BOARD_CC2530EB_REV17
</span><span class="cp"></span>    <span class="cm">/* 2 - Red */</span>
    <span class="cp">#define LED2_BV                      BV(1)
</span><span class="cp"></span>    <span class="cp">#define LED2_SBIT                    P1_1
</span><span class="cp"></span>    <span class="cp">#define LED2_DDR                     P1DIR
</span><span class="cp"></span>    <span class="cp">#define LED2_POLARITY                ACTIVE_HIGH
</span><span class="cp"></span>
    <span class="cm">/* 3 - Yellow */</span>
    <span class="cp">#define LED3_BV                      BV(4)
</span><span class="cp"></span>    <span class="cp">#define LED3_SBIT                    P1_4
</span><span class="cp"></span>    <span class="cp">#define LED3_DDR                     P1DIR
</span><span class="cp"></span>    <span class="cp">#define LED3_POLARITY                ACTIVE_HIGH
</span><span class="cp">#endif
</span></code></pre></div><p>还要注意<strong>LED1_POLARITY</strong>是来配置IO控制LED发光的有效电平的，默认的<strong>Active_HIGH</strong>代表IO输出高电平时LED亮，假如我们所用的硬件电路LED连接在了<strong>P0_3</strong>上，而且是低电平点亮LED，那么若想用LED1控制，那么可以修改LED1的四句配置为下面的样子：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cm">/* 1 - Green */</span>
<span class="cp">#define LED1_BV                        BV(3)
</span><span class="cp">#define LED1_SBIT                      P0_3
</span><span class="cp">#define LED1_DDR                       P0DIR
</span><span class="cp">#define LED1_POLARITY                  ACTIVE_LOW
</span></code></pre></div><h4 id="hal_led函数及宏定义"><strong>HAL_LED</strong>函数及宏定义</h4>
<p>控制LED使用的是HalLedSet函数，有两个参数，第一个参数是LED名称宏定义，第二个参数是LED工作模式宏定义，使用如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">HalLedSet</span><span class="p">(</span> <span class="n">HAL_LED_1</span><span class="p">,</span> <span class="n">HAL_LED_MODE_ON</span> <span class="p">);</span>
</code></pre></div><p>这里我们只需要操作LED1，所以第一个参数就用<strong>HAL_LED_1</strong>就行，LED工作模式宏定义如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#define HAL_LED_MODE_OFF     0x00   </span><span class="c1">// 关闭LED
</span><span class="c1"></span><span class="cp">#define HAL_LED_MODE_ON      0x01   </span><span class="c1">// 打开LED
</span><span class="c1"></span><span class="cp">#define HAL_LED_MODE_BLINK   0x02   </span><span class="c1">// 闪烁一次
</span><span class="c1"></span><span class="cp">#define HAL_LED_MODE_FLASH   0x04   </span><span class="c1">// 不断的闪烁，最多255次
</span><span class="c1"></span><span class="cp">#define HAL_LED_MODE_TOGGLE  0x08   </span><span class="c1">// 翻转LED状态
</span></code></pre></div><h3 id="点亮led">点亮LED</h3>
<ul>
<li>
<p>首先我们要保证初始化的时候LED是灭的，所以在<strong>SimpleBLEPeripheral_Init</strong>函数(在simpleBLEPeripheral.c文件中)的最后执行关闭LED的语句：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">void</span> <span class="nf">SimpleBLEPeripheral_Init</span><span class="p">(</span> <span class="n">uint8</span> <span class="n">task_id</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="n">simpleBLEPeripheral_TaskID</span> <span class="o">=</span> <span class="n">task_id</span><span class="p">;</span>

    <span class="c1">// Setup the GAP
</span><span class="c1"></span>    <span class="n">VOID</span> <span class="n">GAP_SetParamValue</span><span class="p">(</span> <span class="n">TGAP_CONN_PAUSE_PERIPHERAL</span><span class="p">,</span> <span class="n">DEFAULT_CONN_PAUSE_PERIPHERAL</span> <span class="p">);</span>

    <span class="p">.</span>
    <span class="p">.</span>
    <span class="p">.</span>

    <span class="c1">// 关闭LED
</span><span class="c1"></span>    <span class="n">HalLedSet</span><span class="p">(</span> <span class="n">HAL_LED_1</span><span class="p">,</span> <span class="n">HAL_LED_MODE_OFF</span> <span class="p">);</span>
    <span class="c1">// Setup a delayed profile startup
</span><span class="c1"></span>    <span class="n">osal_set_event</span><span class="p">(</span> <span class="n">simpleBLEPeripheral_TaskID</span><span class="p">,</span> <span class="n">SBP_START_DEVICE_EVT</span> <span class="p">);</span>

<span class="p">}</span>
</code></pre></div></li>
<li>
<p>然后在<strong>SimpleBLEPeripheral_ProcessEvent</strong>的<em>SBP_START_DEVICE_EVT</em>**事件处理中加上点亮LED的语句：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="k">if</span> <span class="p">(</span> <span class="n">events</span> <span class="o">&amp;</span> <span class="n">SBP_START_DEVICE_EVT</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Start the Device
</span><span class="c1"></span>    <span class="n">VOID</span> <span class="n">GAPRole_StartDevice</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">simpleBLEPeripheral_PeripheralCBs</span> <span class="p">);</span>

    <span class="c1">// Start Bond Manager
</span><span class="c1"></span>    <span class="n">VOID</span> <span class="nf">GAPBondMgr_Register</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">simpleBLEPeripheral_BondMgrCBs</span> <span class="p">);</span>

    <span class="c1">// 点亮LED
</span><span class="c1"></span>    <span class="n">HalLedSet</span><span class="p">(</span> <span class="n">HAL_LED_1</span><span class="p">,</span> <span class="n">HAL_LED_MODE_ON</span> <span class="p">);</span>

    <span class="c1">// Set timer for first periodic event
</span><span class="c1"></span>    <span class="n">osal_start_timerEx</span><span class="p">(</span> <span class="n">simpleBLEPeripheral_TaskID</span><span class="p">,</span> <span class="n">SBP_PERIODIC_EVT</span><span class="p">,</span> <span class="n">SBP_PERIODIC_EVT_PERIOD</span> <span class="p">);</span>

    <span class="k">return</span> <span class="p">(</span> <span class="n">events</span> <span class="o">^</span> <span class="n">SBP_START_DEVICE_EVT</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>编译下载到电路板上，顺利的话可以看到启动后LED保持常亮。</p>
</li>
</ul>
<h3 id="blink和flash">BLINK和FLASH</h3>
<p>LED的BLINK和FLASH工作模式效果都是闪烁，不过BLINK是闪烁一次，FLASH是闪烁多次，我们也可以用上面点亮LED的方式测试BLINK和FLASH工作模式，只需更改上面<strong>HalLedSet( HAL_LED_1, HAL_LED_MODE_ON );<strong>即可，工作模式改为</strong>HAL_LED_MODE_BLINK</strong>或<strong>HAL_LED_MODE_FLASH</strong>。_</p>
<p>其实我们可以配置闪烁的周期时间和LED亮所占时间比，FLASH还可以设置闪烁次数，虽然可以闪烁多次，但最多可以闪烁255次，找到工程文件的<code>hal_led.h</code>文件，会看到如下的配置代码（在这里加了注释）：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cm">/* Defaults */</span>
<span class="cp">#define HAL_LED_DEFAULT_MAX_LEDS      4         </span><span class="c1">// LED数量
</span><span class="c1"></span><span class="cp">#define HAL_LED_DEFAULT_DUTY_CYCLE    5         </span><span class="c1">// LED亮的占空比，数值是百分比，这里是闪烁周期时间的5%
</span><span class="c1"></span><span class="cp">#define HAL_LED_DEFAULT_FLASH_COUNT   50        </span><span class="c1">// FLASH工作模式的闪烁次数  
</span><span class="c1"></span><span class="cp">#define HAL_LED_DEFAULT_FLASH_TIME    1000      </span><span class="c1">// 闪烁周期时间，单位ms，这里周期是1s
</span></code></pre></div><p>可以根据自己需求更改上述的宏定义即可。</p>
<h3 id="led状态翻转">LED状态翻转</h3>
<p>工作模式使用宏定义<strong>HAL_LED_MODE_TOGGLE</strong>，在这里我们可以使用定时器触发事件的方式测试这个工作模式。</p>
<ul>
<li>
<p>任务初始化中关闭LED</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">void</span> <span class="nf">SimpleBLEPeripheral_Init</span><span class="p">(</span> <span class="n">uint8</span> <span class="n">task_id</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="n">simpleBLEPeripheral_TaskID</span> <span class="o">=</span> <span class="n">task_id</span><span class="p">;</span>

    <span class="c1">// Setup the GAP
</span><span class="c1"></span>    <span class="n">VOID</span> <span class="n">GAP_SetParamValue</span><span class="p">(</span> <span class="n">TGAP_CONN_PAUSE_PERIPHERAL</span><span class="p">,</span> <span class="n">DEFAULT_CONN_PAUSE_PERIPHERAL</span> <span class="p">);</span>

    <span class="p">.</span>
    <span class="p">.</span>
    <span class="p">.</span>

    <span class="c1">// 关闭LED
</span><span class="c1"></span>    <span class="n">HalLedSet</span><span class="p">(</span> <span class="n">HAL_LED_1</span><span class="p">,</span> <span class="n">HAL_LED_MODE_OFF</span> <span class="p">);</span>
    <span class="c1">// Setup a delayed profile startup
</span><span class="c1"></span>    <span class="n">osal_set_event</span><span class="p">(</span> <span class="n">simpleBLEPeripheral_TaskID</span><span class="p">,</span> <span class="n">SBP_START_DEVICE_EVT</span> <span class="p">);</span>

<span class="p">}</span>
</code></pre></div></li>
<li>
<p>定义LED翻转事件，这个需要在simpleBLEPeripheral.h文件中添加事件的宏定义<strong>SBP_LED_TOGGLE_EVT</strong>，默认有两个事件的宏定义，只需添加我们的就可以，事件标识ID是WORD，所以最多可以设置16个事件，一位一个事件，如下的样子：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="c1">// Simple BLE Peripheral Task Events
</span><span class="c1"></span><span class="cp">#define SBP_START_DEVICE_EVT                              0x0001
</span><span class="cp">#define SBP_PERIODIC_EVT                                  0x0002
</span><span class="cp">#define SBP_LED_TOGGLE_EVT                                0x0004
</span></code></pre></div></li>
<li>
<p>添加事件的触发定时器，在<strong>SimpleBLEPeripheral_ProcessEvent</strong>的设备启动事件处理中添加即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="k">if</span> <span class="p">(</span> <span class="n">events</span> <span class="o">&amp;</span> <span class="n">SBP_START_DEVICE_EVT</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Start the Device
</span><span class="c1"></span>    <span class="n">VOID</span> <span class="n">GAPRole_StartDevice</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">simpleBLEPeripheral_PeripheralCBs</span> <span class="p">);</span>

    <span class="c1">// Start Bond Manager
</span><span class="c1"></span>    <span class="n">VOID</span> <span class="nf">GAPBondMgr_Register</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">simpleBLEPeripheral_BondMgrCBs</span> <span class="p">);</span>

    <span class="c1">// 为LED翻转事件设置触发定时器，时间为500ms    
</span><span class="c1"></span>    <span class="n">osal_start_timerEx</span><span class="p">(</span> <span class="n">simpleBLEPeripheral_TaskID</span><span class="p">,</span> <span class="n">SBP_LED_TOGGLE_EVT</span><span class="p">,</span> <span class="mi">500</span> <span class="p">);</span>

    <span class="c1">// Set timer for first periodic event
</span><span class="c1"></span>    <span class="n">osal_start_timerEx</span><span class="p">(</span> <span class="n">simpleBLEPeripheral_TaskID</span><span class="p">,</span> <span class="n">SBP_PERIODIC_EVT</span><span class="p">,</span> <span class="n">SBP_PERIODIC_EVT_PERIOD</span> <span class="p">);</span>

    <span class="k">return</span> <span class="p">(</span> <span class="n">events</span> <span class="o">^</span> <span class="n">SBP_START_DEVICE_EVT</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>可以在设备启动事件处理代码的下面，添加LED翻转事件的处理代码，每个事件的处理后面一定要清掉事件标识：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="c1">// LED翻转事件
</span><span class="c1"></span><span class="k">if</span> <span class="p">(</span> <span class="n">events</span> <span class="o">&amp;</span> <span class="n">SBP_LED_TOGGLE_EVT</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="n">HalLedSet</span><span class="p">(</span> <span class="n">HAL_LED_1</span><span class="p">,</span> <span class="n">HAL_LED_MODE_TOGGLE</span> <span class="p">);</span>                                <span class="c1">// 翻转LED
</span><span class="c1"></span>    <span class="n">osal_start_timerEx</span><span class="p">(</span> <span class="n">simpleBLEPeripheral_TaskID</span><span class="p">,</span> <span class="n">SBP_LED_TOGGLE_EVT</span><span class="p">,</span> <span class="mi">500</span> <span class="p">);</span>  <span class="c1">// 重新设置定时器以实现周期触发事件
</span><span class="c1"></span>    <span class="k">return</span> <span class="p">(</span> <span class="n">events</span> <span class="o">^</span> <span class="n">SBP_LED_TOGGLE_EVT</span> <span class="p">);</span>                                     <span class="c1">// 处理完事件，消除事件标识
</span><span class="c1"></span><span class="p">}</span>
</code></pre></div></li>
<li>
<p>编译，下载程序到开发板，一切顺利的话，led每500ms就会翻转一次状态。</p>
</li>
</ul>]]></content>
		</item>
		
		<item>
			<title>BLE开发笔记——OSAL和协议栈启动流程</title>
			<link>https://blog.5km.studio/2016/05/04/osal-executeflow-ble/</link>
			<pubDate>Wed, 04 May 2016 09:32:48 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/05/04/osal-executeflow-ble/</guid>
			<description>&lt;p&gt;在进行协议栈开发之前，必须要了解协议栈中完成任务调度和资源管理的OSAL以及整个协议栈程序大体的启动流程，只有这样才能更好的开发协议栈程序。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>在进行协议栈开发之前，必须要了解协议栈中完成任务调度和资源管理的OSAL以及整个协议栈程序大体的启动流程，只有这样才能更好的开发协议栈程序。</p>
<h3 id="osal-操作系统抽象层">OSAL-操作系统抽象层</h3>
<p>BLE协议栈、配置文件和所有应用的创建都是围绕操作系统抽象层（OSAL）进行的。OSAL层不是传统意义上实际的操作系统，而是一个控制循环，它允许软件建立事件的执行。对于这一类型控制的软件的每一层，必须创建一个任务ID，也必须定义任务初始化子程序并把它添加到OSAL的初始化中，而且事件的处理程序也必须定义，可以选择定不定义消息处理程序。蓝牙栈的几个层都是OSAL任务，比如最高优先级的LL层任务（因为LL层有很严格的时间要求）。</p>
<p>除了任务管理，OSAL还提供了其它服务，如消息传递、存储管理和定时器，开发套件提供所有的OSAL代码。</p>
<h4 id="osal的实现">OSAL的实现</h4>
<p>为了方便硬件改造、升级以及软件的移植，必须实现软件和硬件的低耦合，使软件在改动很小的情况下可以应用到不同的硬件场合，这就是OSAL和HAL存在的意义。HAL是抽象各种硬件资源整合接口供OSAL所用的，BLE低功耗系统架构如下图所示：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/xtu0l.png" alt="frame.png"></p>
<p>OSAL实现任务调度，BLE协议栈、profiles和编写的应用都是围绕它实现的。软件的功能是由编写的任务事件实现的，可以按照以下步骤来创建任务：</p>
<ul>
<li>创建task identifier任务ID；</li>
<li>编写任务初始化进程，并将其添加到OSAL初始化中。</li>
<li>编写任务处理程序。</li>
<li>如果需要的话还需编写消息服务。</li>
</ul>
<h4 id="系统运行流程">系统运行流程</h4>
<p>任务循环执行过程如下图所示：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/cng73.png" alt="taskloop.png"></p>
<p>为了使用OSAL，系统启动后，会先进行板级硬件初始化和系统任务的初始化（ board init, tasks&rsquo;s init() ），在任务初始化中会逐个调用BLE协议栈每一层的启动进程来初始化协议栈，完成初始化便进入main函数中名叫<strong>osal_start_system</strong>的进程，设置一个8位的任务ID，然后进入循环逐个执行任务及任务中的事件。</p>
<blockquote>
<p>注意</p>
</blockquote>
<ul>
<li>任务优先级是由任务ID决定的，任务ID越小优先级越高。</li>
<li>BLE协议栈各层的任务优先级比应用程序的高。</li>
<li>初始化协议栈以后，越早调入的任务，任务ID越高，优先级越低，即系统倾向于处理新到的任务。</li>
</ul>
<p>每个事件任务由对应的16bit事件变量来标示，事件状态由taskflag来标示。如果事件处理程序已经完成，但taskflag并没有移除，OSAL会认为事情还没有完成而继续在该程序中不返回。比如，在SimpleBLEPeripheral实例工程中，当事件<strong>START_DEVICE_EVT</strong>发生，其处理函数<strong>SimpleBLEPeripheral_ProcessEvent</strong>就运行，结束后返回16bit事件变量，并清除<strong>SBP_START_DEVICE_EVT</strong>。</p>
<p>每当OSAL事件检测到了有任务事件，其相应的处理进程将被添加到由处理进程指针构成的事件处理表单中，该表单名叫taskArr（taskarray）。taskArr中各个事件进程的顺序和osalInitTasks初始化函数中任务ID的顺序是对应的。</p>
<p>触发事件的方法有三种：</p>
<ul>
<li>最简单的方法是使用<strong>osal_set_event</strong>函数（函数原型在OSAL.h文件中），在这个函数中，用户可以像定义函数参数一样设置任务ID和事件旗语。</li>
<li>第二种方法是使用<strong>osal_start_timerEx</strong>函数（函数原型在<strong>OSAL_Timers.h</strong>文件中），使用方法同<strong>osal_set_event</strong>函数，而第三个以毫秒为单位的参数<strong>osal_start_timerEx</strong>则指示该事件处理必须要在这个限定时间内，通过定时器来为事件处理计时。</li>
<li>还有一种就是定时器实现事件的周期触发，使用<strong>osal_start_reload_timer</strong>函数。</li>
</ul>
<p>类似于Linux嵌入式系统内存分配C函数<strong>mem_alloc</strong>，OSAL利用<strong>osal_mem_alloc</strong>提供基本的存储管理，但<strong>osal_mem_alloc</strong>只有一个用于定义byte数的参数。对应的内存释放函数为<strong>osal_mem_free</strong>。</p>
<p>不同的子系统通过OSAL的消息机制通信。消息即为数据，数据种类和长度都不限定。消息收发过程描述如下：</p>
<ul>
<li>接收信息，调用函数<strong>osal_msg_allocate</strong>创建消息占用内存空间（已经包含了<strong>osal_mem_alloc</strong>函数功能），需要为该函数指定空间大小，该函数返回内存空间地址指针，利用该指针就可把所需数据拷贝到该空间。</li>
<li>发送数据，调用函数<strong>osal_msg_send</strong>，需为该函数指定发送目标任务，OSAL通过<strong>SYS_EVENT_MSG</strong>告知目标任务，目标任务的处理函数调用<strong>osal_msg_receive</strong>来接收发来的数据。建议每个OSAL任务都有一个消息处理函数，每当任务收到一个消息后，通过消息的种类来确定需要本任务做相应处理。消息接收并处理完成，调用函数<strong>osal_msg_deallocate</strong>来释放内存（已经包含了<strong>osal_mem_free</strong>函数功能）。</li>
</ul>
<h3 id="协议栈启动流程分析">协议栈启动流程分析</h3>
<p>这里以协议栈例程中的简单从机工程<strong>SimpleBLEPeripheral</strong>为例进行说明协议栈启动过程，平台为CC2541芯片。</p>
<p>如果协议栈是安装在C盘的话一般可以在目录<code>C:\Texas Instruments\BLE-CC254x-1.4.0\Projects\ble\SimpleBLEPeripheral\CC2541DB</code>中找到<strong>SimpleBLEPeripheral.eww</strong>，双击即可用IAR打开工程了。</p>
<p>要了解一个C语言工程，我们应该先找到main函数，这是C程序的一般入口，IAR工程文件的APP下可以找到<strong>SimpleBLEPeripheral_Main.c</strong>，其中可以看到main函数，内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">Int</span>  <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="cm">/* Initialize hardware */</span>
    <span class="n">HAL_BOARD_INIT</span><span class="p">();</span>                       <span class="c1">// 硬件初始化
</span><span class="c1"></span>
    <span class="c1">// Initialize board I/O
</span><span class="c1"></span>    <span class="n">InitBoard</span><span class="p">(</span> <span class="n">OB_COLD</span> <span class="p">);</span>                   <span class="c1">// 板级初始化
</span><span class="c1"></span>
    <span class="cm">/* Initialze the HAL driver */</span>
    <span class="n">HalDriverInit</span><span class="p">();</span>                        <span class="c1">// Hal驱动初始化
</span><span class="c1"></span>
    <span class="cm">/* Initialize NV system */</span>
    <span class="n">osal_snv_init</span><span class="p">();</span>                        <span class="c1">// Flash存储SNV初始化
</span><span class="c1"></span>
    <span class="cm">/* Initialize LL */</span>

    <span class="cm">/* Initialize the operating system */</span>
    <span class="n">osal_init_system</span><span class="p">();</span>                     <span class="c1">// OSAL初始化
</span><span class="c1"></span>
    <span class="cm">/* Enable interrupts */</span>
    <span class="n">HAL_ENABLE_INTERRUPTS</span><span class="p">();</span>                <span class="c1">// 使能总中断
</span><span class="c1"></span>
    <span class="c1">// Final board initialization
</span><span class="c1"></span>    <span class="n">InitBoard</span><span class="p">(</span> <span class="n">OB_READY</span> <span class="p">);</span>                  <span class="c1">// 板级初始化
</span><span class="c1"></span>
    <span class="cp">#if defined ( POWER_SAVING )
</span><span class="cp"></span>    <span class="n">osal_pwrmgr_device</span><span class="p">(</span> <span class="n">PWRMGR_BATTERY</span> <span class="p">);</span>   <span class="c1">// 低功耗管理
</span><span class="c1"></span>    <span class="cp">#endif
</span><span class="cp"></span>
    <span class="cm">/* Start OSAL */</span>
      <span class="n">osal_start_system</span><span class="p">();</span>                  <span class="c1">// No Return from here  启动OSAL
</span><span class="c1"></span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>可以看到，进入main函数后，首先做了各种初始化，以及低功耗管理，最后启动了OSAL，进入**osal_start_system()**函数，函数代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">void</span> <span class="nf">osal_start_system</span><span class="p">(</span> <span class="kt">void</span> <span class="p">)</span>
<span class="p">{</span>
<span class="cp">#if !defined ( ZBIT ) &amp;&amp; !defined ( UBIT )
</span><span class="cp"></span>    <span class="k">for</span><span class="p">(;;)</span>  <span class="c1">// Forever Loop
</span><span class="c1"></span><span class="cp">#endif
</span><span class="cp"></span>    <span class="p">{</span>
        <span class="n">osal_run_system</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>根据上面代码，启动OSAL后，就会循环执行<strong>osal_run_system()</strong>，我们还得看一下这个函数的内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">void</span> <span class="nf">osal_run_system</span><span class="p">(</span> <span class="kt">void</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="n">uint8</span> <span class="n">idx</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

<span class="cp">#ifndef HAL_BOARD_CC2538
</span><span class="cp"></span>    <span class="n">osalTimeUpdate</span><span class="p">();</span>                           <span class="c1">// 定时器更新
</span><span class="c1"></span><span class="cp">#endif
</span><span class="cp"></span>
    <span class="n">Hal_ProcessPoll</span><span class="p">();</span>                          <span class="c1">// Hal层信息处理
</span><span class="c1"></span>
    <span class="k">do</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">tasksEvents</span><span class="p">[</span><span class="n">idx</span><span class="p">])</span>                   <span class="c1">// Task is highest priority that is ready.
</span><span class="c1"></span>        <span class="p">{</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="o">++</span><span class="n">idx</span> <span class="o">&lt;</span> <span class="n">tasksCnt</span><span class="p">);</span>                 <span class="c1">// 检查每个人任务是否有事件
</span><span class="c1"></span>
    <span class="k">if</span> <span class="p">(</span><span class="n">idx</span> <span class="o">&lt;</span> <span class="n">tasksCnt</span><span class="p">)</span>                         <span class="c1">// 有事件发生
</span><span class="c1"></span>    <span class="p">{</span>
        <span class="n">uint16</span> <span class="n">events</span><span class="p">;</span>
        <span class="n">halIntState_t</span> <span class="n">intState</span><span class="p">;</span>

        <span class="n">HAL_ENTER_CRITICAL_SECTION</span><span class="p">(</span><span class="n">intState</span><span class="p">);</span>   <span class="c1">// 进入临界区
</span><span class="c1"></span>        <span class="n">events</span> <span class="o">=</span> <span class="n">tasksEvents</span><span class="p">[</span><span class="n">idx</span><span class="p">];</span>
        <span class="n">tasksEvents</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>                   <span class="c1">// Clear the Events for this task. 清除事件标志
</span><span class="c1"></span>        <span class="n">HAL_EXIT_CRITICAL_SECTION</span><span class="p">(</span><span class="n">intState</span><span class="p">);</span>    <span class="c1">// 退出临界区
</span><span class="c1"></span>
        <span class="n">activeTaskID</span> <span class="o">=</span> <span class="n">idx</span><span class="p">;</span>
        <span class="n">events</span> <span class="o">=</span> <span class="p">(</span><span class="n">tasksArr</span><span class="p">[</span><span class="n">idx</span><span class="p">])(</span> <span class="n">idx</span><span class="p">,</span> <span class="n">events</span> <span class="p">);</span><span class="c1">// 执行事件处理函数
</span><span class="c1"></span>        <span class="n">activeTaskID</span> <span class="o">=</span> <span class="n">TASK_NO_TASK</span><span class="p">;</span>

        <span class="n">HAL_ENTER_CRITICAL_SECTION</span><span class="p">(</span><span class="n">intState</span><span class="p">);</span>   <span class="c1">// 进入临界区
</span><span class="c1"></span>        <span class="n">tasksEvents</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">|=</span> <span class="n">events</span><span class="p">;</span>             <span class="c1">// Add back unprocessed events to the current task.
</span><span class="c1"></span>        <span class="n">HAL_EXIT_CRITICAL_SECTION</span><span class="p">(</span><span class="n">intState</span><span class="p">);</span>    <span class="c1">// 退出临界区
</span><span class="c1"></span>    <span class="p">}</span>
<span class="cp">#if defined( POWER_SAVING )                     </span><span class="c1">// 没有事件发生，并且开启了低功耗模式
</span><span class="c1"></span>    <span class="k">else</span>                                        <span class="c1">// Complete pass through all task events with no activity?
</span><span class="c1"></span>    <span class="p">{</span>                                           <span class="c1">// 系统进入低功耗模式
</span><span class="c1"></span>        <span class="n">osal_pwrmgr_powerconserve</span><span class="p">();</span>            <span class="c1">// Put the processor/system into sleep
</span><span class="c1"></span>    <span class="p">}</span>
<span class="cp">#endif
</span><span class="cp"></span>
    <span class="cm">/* Yield in case cooperative scheduling is being used. */</span>
<span class="cp">#if defined (configUSE_PREEMPTION) &amp;&amp; (configUSE_PREEMPTION == 0)
</span><span class="cp"></span>    <span class="p">{</span>
        <span class="n">osal_task_yield</span><span class="p">();</span>
    <span class="p">}</span>
<span class="cp">#endif
</span><span class="cp"></span><span class="p">}</span>
</code></pre></div><p>阅读代码可以明白OSAL循环检测每一个任务是否有事件发生，如果有则执行相应的任务并处理触发的事件，如果没有事件并且宏定义中开启了低功耗模式，那么就会进入低功耗模式。但是OSAL怎么检测任务是否有事件的呢？我们可以看到有一句代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">events</span> <span class="o">=</span> <span class="p">(</span><span class="n">tasksArr</span><span class="p">[</span><span class="n">idx</span><span class="p">])(</span> <span class="n">idx</span><span class="p">,</span> <span class="n">events</span> <span class="p">);</span>
</code></pre></div><p>taskArr又是什么鬼？其实它是一个函数指针数组：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="k">const</span> <span class="n">pTaskEventHandlerFn</span> <span class="n">tasksArr</span><span class="p">[]</span> <span class="o">=</span>
<span class="p">{</span>
    <span class="n">LL_ProcessEvent</span><span class="p">,</span>                                              <span class="c1">// task 0
</span><span class="c1"></span>    <span class="n">Hal_ProcessEvent</span><span class="p">,</span>                                             <span class="c1">// task 1
</span><span class="c1"></span>    <span class="n">HCI_ProcessEvent</span><span class="p">,</span>                                             <span class="c1">// task 2
</span><span class="c1"></span>    <span class="cp">#if defined ( OSAL_CBTIMER_NUM_TASKS )
</span><span class="cp"></span>    <span class="n">OSAL_CBTIMER_PROCESS_EVENT</span><span class="p">(</span> <span class="n">osal_CbTimerProcessEvent</span> <span class="p">),</span>       <span class="c1">// task 3
</span><span class="c1"></span>    <span class="cp">#endif
</span><span class="cp"></span>    <span class="n">L2CAP_ProcessEvent</span><span class="p">,</span>                                           <span class="c1">// task 4
</span><span class="c1"></span>    <span class="n">GAP_ProcessEvent</span><span class="p">,</span>                                             <span class="c1">// task 5
</span><span class="c1"></span>    <span class="n">GATT_ProcessEvent</span><span class="p">,</span>                                            <span class="c1">// task 6
</span><span class="c1"></span>    <span class="n">SM_ProcessEvent</span><span class="p">,</span>                                              <span class="c1">// task 7
</span><span class="c1"></span>    <span class="n">GAPRole_ProcessEvent</span><span class="p">,</span>                                         <span class="c1">// task 8
</span><span class="c1"></span>    <span class="n">GAPBondMgr_ProcessEvent</span><span class="p">,</span>                                      <span class="c1">// task 9
</span><span class="c1"></span>    <span class="n">GATTServApp_ProcessEvent</span><span class="p">,</span>                                     <span class="c1">// task 10
</span><span class="c1"></span>    <span class="n">SimpleBLEPeripheral_ProcessEvent</span>                              <span class="c1">// task 11
</span><span class="c1"></span><span class="p">};</span>
</code></pre></div><p>tasksArr中的成员是每一个任务事件处理函数，是按照优先级排列的，排列顺序与任务初始化中任务初始化顺序是一致的，这样就保证了触发事件或产生消息能够准确传递到相应的任务处理函数中，任务初始化代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">void</span> <span class="nf">osalInitTasks</span><span class="p">(</span> <span class="kt">void</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="n">uint8</span> <span class="n">taskID</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="n">tasksEvents</span> <span class="o">=</span> <span class="p">(</span><span class="n">uint16</span> <span class="o">*</span><span class="p">)</span><span class="n">osal_mem_alloc</span><span class="p">(</span> <span class="k">sizeof</span><span class="p">(</span> <span class="n">uint16</span> <span class="p">)</span> <span class="o">*</span> <span class="n">tasksCnt</span><span class="p">);</span>
    <span class="n">osal_memset</span><span class="p">(</span> <span class="n">tasksEvents</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="k">sizeof</span><span class="p">(</span> <span class="n">uint16</span> <span class="p">)</span> <span class="o">*</span> <span class="n">tasksCnt</span><span class="p">));</span>

    <span class="cm">/* LL Task */</span>
    <span class="n">LL_Init</span><span class="p">(</span> <span class="n">taskID</span><span class="o">++</span> <span class="p">);</span>

    <span class="cm">/* Hal Task */</span>
    <span class="n">Hal_Init</span><span class="p">(</span> <span class="n">taskID</span><span class="o">++</span> <span class="p">);</span>

    <span class="cm">/* HCI Task */</span>
    <span class="n">HCI_Init</span><span class="p">(</span> <span class="n">taskID</span><span class="o">++</span> <span class="p">);</span>

<span class="cp">#if defined ( OSAL_CBTIMER_NUM_TASKS )
</span><span class="cp"></span>    <span class="cm">/* Callback Timer Tasks */</span>
    <span class="n">osal_CbTimerInit</span><span class="p">(</span> <span class="n">taskID</span> <span class="p">);</span>
    <span class="n">taskID</span> <span class="o">+=</span> <span class="n">OSAL_CBTIMER_NUM_TASKS</span><span class="p">;</span>
<span class="cp">#endif
</span><span class="cp"></span>
    <span class="cm">/* L2CAP Task */</span>
    <span class="n">L2CAP_Init</span><span class="p">(</span> <span class="n">taskID</span><span class="o">++</span> <span class="p">);</span>

    <span class="cm">/* GAP Task */</span>
    <span class="n">GAP_Init</span><span class="p">(</span> <span class="n">taskID</span><span class="o">++</span> <span class="p">);</span>

    <span class="cm">/* GATT Task */</span>
    <span class="n">GATT_Init</span><span class="p">(</span> <span class="n">taskID</span><span class="o">++</span> <span class="p">);</span>

    <span class="cm">/* SM Task */</span>
    <span class="n">SM_Init</span><span class="p">(</span> <span class="n">taskID</span><span class="o">++</span> <span class="p">);</span>

    <span class="cm">/* Profiles */</span>
    <span class="n">GAPRole_Init</span><span class="p">(</span> <span class="n">taskID</span><span class="o">++</span> <span class="p">);</span>
    <span class="n">GAPBondMgr_Init</span><span class="p">(</span> <span class="n">taskID</span><span class="o">++</span> <span class="p">);</span>

    <span class="n">GATTServApp_Init</span><span class="p">(</span> <span class="n">taskID</span><span class="o">++</span> <span class="p">);</span>

    <span class="cm">/* Application */</span>
    <span class="n">SimpleBLEPeripheral_Init</span><span class="p">(</span> <span class="n">taskID</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div><p>我们做BLE开发，主要是编写Application，而在SimpleBLEPeripheral工程中的应用程序则是SimpleBLEPeripheral，对应两个重要的函数就是任务初始化函数<strong>SimpleBLEPeripheral_Init</strong>和事件处理函数<strong>SimpleBLEPeripheral_ProcessEvent</strong>。</p>
<p>在<strong>SimpleBLEPeripheral_Init</strong>中主要是对GAP和GATT进行初始化配置，在最后会执行<strong>osal_set_event( simpleBLEPeripheral_TaskID, SBP_START_DEVICE_EVT )<strong>触发启动设备的事件，处理</strong>SBP_START_DEVICE_EVT</strong>事件的代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="k">if</span> <span class="p">(</span> <span class="n">events</span> <span class="o">&amp;</span> <span class="n">SBP_START_DEVICE_EVT</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Start the Device
</span><span class="c1"></span>    <span class="n">VOID</span> <span class="n">GAPRole_StartDevice</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">simpleBLEPeripheral_PeripheralCBs</span> <span class="p">);</span>

    <span class="c1">// Start Bond Manager
</span><span class="c1"></span>    <span class="n">VOID</span> <span class="nf">GAPBondMgr_Register</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">simpleBLEPeripheral_BondMgrCBs</span> <span class="p">);</span>
    <span class="c1">// Set timer for first periodic event
</span><span class="c1"></span>    <span class="n">osal_start_timerEx</span><span class="p">(</span> <span class="n">simpleBLEPeripheral_TaskID</span><span class="p">,</span> <span class="n">SBP_PERIODIC_EVT</span><span class="p">,</span> <span class="n">SBP_PERIODIC_EVT_PERIOD</span> <span class="p">);</span>

    <span class="k">return</span> <span class="p">(</span> <span class="n">events</span> <span class="o">^</span> <span class="n">SBP_START_DEVICE_EVT</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div><p>能够看到在执行上述代码的时候，设置定时触发事件<strong>SBP_PERIODIC_EVT</strong>，时间为<strong>SBP_PERIODIC_EVT_PERIOD</strong>，在事件处理函数中有对<strong>SBP_PERIODIC_EVT</strong>的处理，处理中又重新设置此事件的定时触发，也就实现了事件周期触发：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="k">if</span> <span class="p">(</span> <span class="n">event</span> <span class="o">&amp;</span> <span class="n">SBP_PERIODIC_EVT</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Restart timer
</span><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span> <span class="n">SBP_PERIODIC_EVT_PERIOD</span> <span class="p">)</span>
    <span class="p">{</span>
        <span class="n">osal_start_timerEx</span><span class="p">(</span> <span class="n">simpleBLEPeripheral_TaskID</span><span class="p">,</span> <span class="n">SBP_PERIODIC_EVT</span><span class="p">,</span> <span class="n">SBP_PERIODIC_EVT_PERIOD</span> <span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">// Perform periodic application task
</span><span class="c1"></span>    <span class="n">performPeriodicTassk</span><span class="p">();</span>

    <span class="k">return</span> <span class="p">(</span> <span class="n">events</span> <span class="o">^</span> <span class="n">SBP_PERIODIC_EVT</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div><p>另外还有两个重要的回调函数：</p>
<ul>
<li><strong>peripheralStateNotificationCB</strong>：这个函数主要是在设备的连接状态改变的时候由底层回调，可以在这个函数中查看设备状态以做出需要的处理。</li>
<li><strong>simpleProfileChangeCB</strong>：这个函数主要会在设备蓝牙射频接收到数据的时候底层回调用来及时获取接收到的数据。</li>
</ul>
<p>做协议栈的开发主要是做自己需要的应用，可以在例程基础上添加自己需要的处理完成应用，也可以新建自己的应用，只要按照OSAL中任务的添加步骤添加即可，了解了以上的内容，就可以尝试开发了。</p>]]></content>
		</item>
		
		<item>
			<title>Fedora使用笔记</title>
			<link>https://blog.5km.studio/2016/04/28/fedora-note/</link>
			<pubDate>Thu, 28 Apr 2016 15:17:29 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/04/28/fedora-note/</guid>
			<description>&lt;p&gt;俗话说得好，好记性不如烂笔头，曾经解决过的问题，再次遇到还要再花大量时间去解决，不能忍呀，在fedora的使用过程中，难免遇到好多问题，本文记录在使用Fedora过程中部分遇到的问题。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>俗话说得好，好记性不如烂笔头，曾经解决过的问题，再次遇到还要再花大量时间去解决，不能忍呀，在fedora的使用过程中，难免遇到好多问题，本文记录在使用Fedora过程中部分遇到的问题。</p>
<h3 id="安装eagle-pcb缺少库的问题">安装eagle pcb缺少库的问题</h3>
<p>在安装eagle pcb软件的时候，提示缺少库文件，如下：</p>
<blockquote>
<p>➜  CadSoft.Computer.EAGLE.Professional.v7.5.0.LINUX.X64 ./eagle-lin64-7.5.0.run
Ensure the following libraries are available:
libssl.so.1.0.0 =&gt; not found
libcrypto.so.1.0.0 =&gt; not found
/tmp/eagle-setup.6307/eagle-7.5.0/bin/eagle: error while loading shared libraries: libssl.so.1.0.0: cannot open shared object file: No such file or directory</p>
</blockquote>
<p>后来发现，原来系统中其实已经安装了依赖库，只不过是版本较高，eagle安装过程中不认，所以可以通过建立软链接，名为所需依赖库就可以了，可以执行以下命令，当然库的版本还是以当前电脑系统为准：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ <span class="nb">cd</span> /lib/lib64
$ sudo ln -s libssl.so.1.0.2g libssl.so.1.0.0
$ sudo ln -s libcrypto.so.1.0.2g libcrypto.so.1.0.0
</code></pre></div><h3 id="安装evopop主题">安装Evopop主题</h3>
<ul>
<li>
<p>添加主题的安装源：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ sudo dnf copr <span class="nb">enable</span> heikoada/gtk-themes
</code></pre></div></li>
<li>
<p>安装主题：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo dnf install evopop-gtk-theme-common.noarch evopop-gtk-theme-gtk3 evopop-gtk-theme-metacity evopop-gtk-theme-gtk2
</code></pre></div></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>系统及编辑器的字体设置</title>
			<link>https://blog.5km.studio/2016/04/28/font-settings/</link>
			<pubDate>Thu, 28 Apr 2016 09:20:28 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/04/28/font-settings/</guid>
			<description>&lt;p&gt;最近在用Linux，实在是受不了中文字体的显示了，终端里面设置了Source Code Pro字体，这英文字体很棒，适合做编程字体，但显示的中文可以难看到哭。索性在本文说一下怎么能在使用好看的英文字体的同时，可以显示你喜欢的中文字体，另外也整理一下编辑器中字体的设置方法，如Atom、ST等。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>最近在用Linux，实在是受不了中文字体的显示了，终端里面设置了Source Code Pro字体，这英文字体很棒，适合做编程字体，但显示的中文可以难看到哭。索性在本文说一下怎么能在使用好看的英文字体的同时，可以显示你喜欢的中文字体，另外也整理一下编辑器中字体的设置方法，如Atom、ST等。</p>
<h4 id="linux中设置喜欢的中英文字体混合显示">linux中设置喜欢的中英文字体混合显示</h4>
<p>测试平台是Fedora Linux，要使用的字体是：</p>
<ul>
<li>英文：Source Code Pro</li>
<li>中文：思源雅黑（Source Han Sans CN）</li>
</ul>
<p>为了能够混合使用这两个字体，我们需要编辑<code>/etc/fonts/fonts.conf</code>文件，这里使用vim编辑这个文件，要用sudo提高权限操作：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ sudo vim /etc/fonts/fonts.conf
</code></pre></div><p>可以看到文件就是个xml文件，其中很多match标签，只需要在最后一个match标签后加入以下代码就可以，加代码的位置应该只需要与match同级就可以：</p>
<div class="highlight"><pre class="chroma"><code class="language-xml" data-lang="xml"><span class="nt">&lt;alias&gt;</span>
	<span class="nt">&lt;family&gt;</span>Source Code Pro<span class="nt">&lt;/family&gt;</span>
	<span class="nt">&lt;prefer&gt;</span>
		<span class="nt">&lt;family&gt;</span>Source Han Sans CN <span class="nt">&lt;/family&gt;</span>
	<span class="nt">&lt;/prefer&gt;</span>
<span class="nt">&lt;/alias&gt;</span>
</code></pre></div><p>第一个family标签中是英文字体的名称，第二个family的标签中是中文字体的名称，对应修改成你喜欢的字体就可以了，然后去gnome-tweak-tool中设置字体为Source Code Pro就可以了，或者单独设置终端的配置，换成其他字体，然后再换到Source Code Pro（对应你喜欢的英文字体），然后再打开终端，输入一段中英文字符，可以看到，中英文各是你喜欢的样子。</p>
<h4 id="atom编辑器窗口字体设置">Atom编辑器窗口字体设置</h4>
<p>测试字体：</p>
<ul>
<li>英文：Source Code Pro</li>
<li>中文：Source Han Sans CN</li>
</ul>
<p>打开Atom，<code>CTRL</code>+<code>,</code>，启动到设置界面，向下滚动界面，会找到Editor Settings分组，其中有个font family，在编辑框中输入你喜欢的字体名称，如下的样子：</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="nt">Source</span> <span class="nt">Code</span> <span class="nt">Pro</span><span class="o">,</span> <span class="nt">Source</span> <span class="nt">Han</span> <span class="nt">Sans</span> <span class="nt">CN</span>
</code></pre></div><p>重新打开编辑窗口，可以看到字体已设置成功。</p>
<h4 id="iii-atom的markdown预览窗口显示字体设置">III. Atom的markdown预览窗口显示字体设置</h4>
<p>测试字体：</p>
<ul>
<li>英文：Source Code Pro</li>
<li>中文：Source Han Sans CN</li>
</ul>
<p>打开Atom，同样<code>CTRL</code>+`,``，打开设置窗口，左边栏选中Package，便会在右侧窗口显示插件列表，滚动页面，找到Markdown-Preview插件，单击进入插件设置页面，滚动页面，呀，咋找不到字体设置的地方呢？怎么办？不要紧，滚动到最下面会发现Customize分组，自定义，在看最下面有段话：</p>
<blockquote>
<p>To customize even further, the styling can be overridden in your styles.less file. For example:</p>
</blockquote>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">.</span><span class="nc">markdown-preview</span><span class="p">.</span><span class="nc">markdown-preview</span> <span class="p">{</span>
  <span class="k">background-color</span><span class="p">:</span> <span class="mh">#444</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>这倒提醒我了，atom其实就是一个本地的网页浏览器，样式是由css配置的，上面的提示中说要修改style.less文件，可以从菜单栏的edit菜单中找到一项StyleSheet&hellip;，单击便打开style.less文件了，在最后加入以下代码，保存，再去打开markdown文件，然后ctrl+shift+m打开预览窗口便会发现字体就是你希望看到的样子了：</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">.</span><span class="nc">markdown-preview</span><span class="p">.</span><span class="nc">markdown-preview</span> <span class="p">{</span>
  <span class="k">font-family</span><span class="p">:</span> <span class="n">Source</span> <span class="n">Code</span> <span class="n">Pro</span><span class="p">,</span> <span class="n">Source</span> <span class="n">Han</span> <span class="n">Sans</span> <span class="n">CN</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>Linux下配置Swift开发环境和初步使用</title>
			<link>https://blog.5km.studio/2016/04/16/install-use-swift/</link>
			<pubDate>Sat, 16 Apr 2016 16:13:36 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/04/16/install-use-swift/</guid>
			<description>&lt;p&gt;不想干活了，整理一下如何在Linux下配置Swift的开发环境和初步的使用，如果你是圈内人你肯定清楚Swift，否则你会有疑问Swift是什么鬼，你以为我会在这里告诉你Swift的详细内容吗？就一句话Swift是Apple公司在2014年开发者大会上发布的一门新的开发语言（详细内容还是问谷哥或者&lt;a href=&#34;https://swift.org/about/#swiftorg-and-open-source&#34;&gt;点我&lt;/a&gt;吧或者&lt;a href=&#34;http://wiki.jikexueyuan.com/project/swift/chapter1/01_swift.html&#34;&gt;戳我&lt;/a&gt;！）。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>不想干活了，整理一下如何在Linux下配置Swift的开发环境和初步的使用，如果你是圈内人你肯定清楚Swift，否则你会有疑问Swift是什么鬼，你以为我会在这里告诉你Swift的详细内容吗？就一句话Swift是Apple公司在2014年开发者大会上发布的一门新的开发语言（详细内容还是问谷哥或者<a href="https://swift.org/about/#swiftorg-and-open-source">点我</a>吧或者<a href="http://wiki.jikexueyuan.com/project/swift/chapter1/01_swift.html">戳我</a>！）。</p>
<h2 id="测试平台">测试平台</h2>
<p>Ubuntu15.10</p>
<h2 id="搭建开发环境">搭建开发环境</h2>
<p>安装相应依赖包和下载安装Swift包，做相应环境配置。</p>
<h3 id="安装依赖包">安装依赖包</h3>
<p>swift的使用和开发会依赖<code>clang</code>和<code>libicu-dev</code>开发包，所以在使用Swift之前先安装一下，终端下执行命令安装即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ sudo apt-get install clang libicu-dev
</code></pre></div><h3 id="下载最新swift包">下载最新Swift包</h3>
<p><a href="https://swift.org/download/#using-downloads">点我</a>可以跳转到下载页面，页面最顶部就是最新的发行包，其中格式为<code>swift-&lt;VERSION&gt;-&lt;PLATFORM&gt;.tar.gz</code>的文件便是Swift开发要用到的工具链，<code>.sig</code>文件是数字签名文件。</p>
<h3 id="导入pgp密钥">导入PGP密钥</h3>
<p>如果是第一次下载Swift包，需要将导入GPG密钥导入到你的密钥环中，两种方式：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ gpg --keyserver hkp://pool.sks-keyservers.net <span class="se">\
</span><span class="se"></span>      --recv-keys <span class="se">\
</span><span class="se"></span>      <span class="s1">&#39;7463 A81A 4B2E EA1B 551F  FBCF D441 C977 412B 37AD&#39;</span> <span class="se">\
</span><span class="se"></span>      <span class="s1">&#39;1BE1 E29A 084C B305 F397  D62A 9F59 7F4D 21A5 6D5F&#39;</span>
</code></pre></div><p>或者</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ wget -q -O - https://swift.org/keys/all-keys.asc <span class="p">|</span> gpg --import -
</code></pre></div><h3 id="验证gpg签名">验证GPG签名</h3>
<p>linux的<code>.tar.gz</code>压缩包都是使用Swift开源项目的密钥通过<strong>GnuPG</strong>签名了的，苹果建议和鼓励每个将要使用Swift软件包的人都进行签名验证，此步可以省略（不验证不影响使用），但还是按照他们的建议进行验证吧。</p>
<h3 id="更新密钥">更新密钥</h3>
<p>首先，如果需要的话，可以更新密钥使原来的证书作废：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ gpg --keyserver hkp://pool.sks-keyservers.net --refresh-keys Swift
</code></pre></div><h3 id="验证">验证</h3>
<p>然后，使用签名文件验证关联的Swift压缩包：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ gpg --verify swift-&lt;VERSION&gt;-&lt;PLATFORM&gt;.tar.gz.sig
...
gpg: Good signature from <span class="s2">&#34;Swift Automatic Signing Key #1 &lt;swift-infrastructure@swift.org&gt;&#34;</span>
</code></pre></div><p>如果验证失败，可能是因为没有公共密钥(<code>gpg: Can't check signature: No public key</code>)，可以按照<a href="#activeKey">激活签名密钥</a>的方法导入密钥。</p>
<p>不顺利的话，还有可能会看到一个警告，汗!!!：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
</code></pre></div><p>这个警告说明在密钥与系统间没有可信的网络路径，只要是按照上面的步骤从可信的的来源获取的密钥，警告几乎没有影响。</p>
<h3 id="解压压缩包">解压压缩包</h3>
<p>按照下面命令解压下载的压缩包，需要将下面命令中压缩包的名字改成下载的，解压后的文件目录里有<code>usr/</code>目录。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ tar xzf swift-&lt;VERSION&gt;-&lt;PLATFORM&gt;.tar.gz
</code></pre></div><h3 id="添加路径">添加路径</h3>
<p>需要将解压出来的Swift工具链添加到PATH变量里，可以执行下面的命令(**注：**需要将<code>/path/to/</code>换成解压后Swift工具链的目录)：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ <span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span>/path/to/usr/bin:<span class="s2">&#34;</span><span class="si">${</span><span class="nv">PATH</span><span class="si">}</span><span class="s2">&#34;</span>
</code></pre></div><h3 id="激活签名密钥">激活签名密钥</h3>
<p>Swift工程为开发版的使用一个密钥集，为每个官方发行版的提供独立的密钥，都是使用4096-bit RSA密钥。</p>
<p>下面是获取签名工具链密钥的两种方法：</p>
<ul>
<li>
<p><code>Swift自动签名密钥 #1 &lt;swift-infrastructure@swift.org&gt;</code></p>
<blockquote>
<p>Download: <a href="https://swift.org/keys/automatic-signing-key-1.asc">https://swift.org/keys/automatic-signing-key-1.asc</a></p>
</blockquote>
<blockquote>
<p>Fingerprint: 7463 A81A 4B2E EA1B 551F FBCF D441 C977 412B 37AD</p>
</blockquote>
<blockquote>
<p>Long ID: D441C977412B37AD</p>
</blockquote>
<p>执行下面命令导入密钥：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ gpg --keyserver hkp://pool.sks-keyservers.net <span class="se">\
</span><span class="se"></span>      --recv-keys <span class="se">\
</span><span class="se"></span>    <span class="s1">&#39;7463 A81A 4B2E EA1B 551F  FBCF D441 C977 412B 37AD&#39;</span>
</code></pre></div><p>或者：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ wget -q -O - https://swift.org/keys/automatic-signing-key-1.asc <span class="p">|</span> gpg --import -
</code></pre></div></li>
<li>
<p><code>Swift 2.2 发行版签名密钥 &lt;swift-infrastructure@swift.org&gt;</code></p>
<blockquote>
<p>Download: <a href="https://swift.org/keys/release-key-swift-2.2.asc">https://swift.org/keys/release-key-swift-2.2.asc</a>
Fingerprint: 1BE1 E29A 084C B305 F397 D62A 9F59 7F4D 21A5 6D5F
Long ID: 9F597F4D21A56D5F</p>
</blockquote>
<p>执行下面命令导入密钥：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ gpg --keyserver hkp://pool.sks-keyservers.net <span class="se">\
</span><span class="se"></span>      --recv-keys <span class="se">\
</span><span class="se"></span>    <span class="s1">&#39;1BE1 E29A 084C B305 F397  D62A 9F59 7F4D 21A5 6D5F&#39;</span>
</code></pre></div><p>或者：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ wget -q -O - https://swift.org/keys/release-key-swift-2.2.asc <span class="p">|</span> gpg --import -
</code></pre></div></li>
</ul>
<h2 id="使用repl">使用REPL</h2>
<p>如果在命令行中执行swift命令，就会启动<strong>REPL</strong>，它是一个可交互的Shell窗口，可以读取、计算和打印你所输入的Swift代码。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ swift
Welcome to Apple Swift version 2.2. Type :help <span class="k">for</span> assistance.
  1&gt;
</code></pre></div><p>与REPL交互是探索Swift极好的方式，比如输入表达式<code>1 + 2</code>，敲回车便会计算出结果，<code>3</code>，在下一行中打印出来：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">  1&gt; <span class="m">1</span> + <span class="m">2</span>
<span class="nv">$R0</span>: <span class="nv">Int</span> <span class="o">=</span> <span class="m">3</span>
</code></pre></div><p>可以为变量和常量赋值，后续可以使用它们。比如将<code>String</code>类型的<code>Hello, world!</code>赋值给<code>greeting</code>常量，然后可以使用<code>print(_:)</code>函数进行打印：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">  2&gt; <span class="nb">let</span> <span class="nv">greeting</span> <span class="o">=</span> <span class="s2">&#34;hello!&#34;</span>
greeting: <span class="nv">String</span> <span class="o">=</span> <span class="s2">&#34;Hello!&#34;</span>
  3&gt; print<span class="o">(</span>greeting<span class="o">)</span>
Hello!
</code></pre></div><p>如果输入一个无效的表达式，<strong>REPL</strong>会打印出错误并标明出错的位置，就像下面：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">let</span> <span class="nv">answer</span> <span class="o">=</span> <span class="s2">&#34;fourty&#34;</span>-<span class="s2">&#34;two&#34;</span>
error: binary operator <span class="s1">&#39;-&#39;</span> cannot be applied to two <span class="s1">&#39;String&#39;</span> operands
<span class="nb">let</span> <span class="nv">answer</span> <span class="o">=</span> <span class="s2">&#34;fourty&#34;</span>-<span class="s2">&#34;two&#34;</span>
             ~~~~~~~~^~~~~~
</code></pre></div><p>可以使用<code>UP</code>键和<code>DOWN</code>键循环查看之前在<strong>REPL</strong>中输入过的文本行。这让你可以稍微修改过去输入过的文本而不用输入全部内容，比如对于修改上面例子中错误就很方便：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">let</span> <span class="nv">answer</span> <span class="o">=</span> <span class="s2">&#34;fourty-two&#34;</span>
answer: <span class="nv">String</span> <span class="o">=</span> <span class="s2">&#34;fourty-two&#34;</span>
</code></pre></div><p><strong>REPL</strong>还有一个很有用的特性：在输入特定文本后可以提示函数和方法。比如，在一个<code>String</code>后输入<code>re</code>在按下<code>TAB</code>键，便会给出一个提示可用函数和方法的列表，就像下面那样：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">5&gt; <span class="s2">&#34;Hi!&#34;</span>.re⇥
Available completions:
	removeAll<span class="o">()</span> -&gt; Void
	removeAll<span class="o">(</span>keepCapacity: Bool<span class="o">)</span> -&gt; Void
	removeAtIndex<span class="o">(</span>i: Index<span class="o">)</span> -&gt; Character
	removeRange<span class="o">(</span>subRange: Range&lt;Index&gt;<span class="o">)</span> -&gt; Void
	replaceRange<span class="o">(</span>subRange: Range&lt;Index&gt;, with: C<span class="o">)</span> -&gt; Void
	replaceRange<span class="o">(</span>subRange: Range&lt;Index&gt;, with: String<span class="o">)</span> -&gt; Void
	reserveCapacity<span class="o">(</span>n: Int<span class="o">)</span> -&gt; Void
</code></pre></div><p>当开始编写一个代码块的时候，比如用<code>for-in</code>轮询数组时，<strong>REPL</strong>会在下一行自动缩进，此时行首的提示符会由<code>&gt;</code>变为<code>.</code>，表明代码还未输入完整需要输入完整后在进行评估：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">  6&gt; <span class="nb">let</span> <span class="nv">numbers</span> <span class="o">=</span> <span class="o">[</span>1,2,3<span class="o">]</span>
numbers: <span class="o">[</span>Int<span class="o">]</span> <span class="o">=</span> <span class="m">3</span> values <span class="o">{</span>
  <span class="o">[</span>0<span class="o">]</span> <span class="o">=</span> <span class="m">1</span>
  <span class="o">[</span>1<span class="o">]</span> <span class="o">=</span> <span class="m">2</span>
  <span class="o">[</span>2<span class="o">]</span> <span class="o">=</span> <span class="m">3</span>
<span class="o">}</span>
  7&gt; <span class="k">for</span> n in numbers.reverse<span class="o">()</span> <span class="o">{</span>
  8.     print<span class="o">(</span>n<span class="o">)</span>
  9. <span class="o">}</span>
<span class="m">3</span>
<span class="m">2</span>
<span class="m">1</span>
</code></pre></div><p>Swift的所有函数在<strong>REPL</strong>中都是可以调用的，从编写控制流到声明和实例化结构体和类都是可以的。另外可以导入可用的系统模块，比如OSX下的<code>Darwin</code>和Linux下的<code>Glibc</code>：</p>
<p>OSX下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">1&gt; import Darwin
2&gt; arc4random_uniform<span class="o">(</span>10<span class="o">)</span>
<span class="nv">$R0</span>: <span class="nv">UInt32</span> <span class="o">=</span> <span class="m">4</span>
</code></pre></div><p>Linux下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">1&gt; import Glibc
2&gt; random<span class="o">()</span> % <span class="m">10</span>
<span class="nv">$R0</span>: <span class="nv">Int32</span> <span class="o">=</span> <span class="m">4</span>
</code></pre></div><h2 id="使用编译系统">使用编译系统</h2>
<p>Swift的编译系统可以用来编译库、可执行程序和在不同工程之间分享代码。</p>
<p>在官方文档中，使用<code>swift build</code>来讲解的，但我安装了Swift 2.2后发现按照他们的教程操作发现，竟然没有了swift build工具，简直想骂娘，但我还是忍住了，经过本菜鸟探索了一下，用<code>swiftc</code>可以用来编译生成可执行程序。</p>
<p>可以调用<code>swift build --help</code>来看一下，结果会有以下提示：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ swift build --help
error: unable to invoke subcommand: /home/smslit/Applications/swift/usr/bin/swift-build <span class="o">(</span>No such file or directory<span class="o">)</span>
</code></pre></div><p>后来，我有重新安装了开发版的Swift，里面有build命令，所以如果用的是开发版的可以将下面的swiftc 换成swift build。</p>
<p>可以使用官方例程，来尝试一下<code>swiftc</code>，可以执行命令<code>swiftc -h</code>查看swiftc的使用方法。</p>
<h3 id="创建代码包">创建代码包</h3>
<ul>
<li>
<p>创建<code>hello/Sources</code>目录，并进入<code>hello</code>目录下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ mkdir -p hello/Sources
$ <span class="nb">cd</span> hello
</code></pre></div></li>
<li>
<p>创建包管理文件<code>Package.swift</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ touch Package.swift
</code></pre></div></li>
</ul>
<h3 id="编辑代码">编辑代码</h3>
<p>在<code>Sources</code>目录下新建<code>main.swift</code>文件，并输入代码<code>print(&quot;Hello, Monkey!&quot;)</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ touch Sources/main.swift
$ <span class="nb">echo</span> <span class="s2">&#34;print(\&#34;Hello, Monkey!\&#34;)&#34;</span> &gt; Sources/main.swift
</code></pre></div><h3 id="编译生成可执行文件">编译生成可执行文件</h3>
<p>使用<code>swiftc</code>进行编译，在包根目录下生成名为<code>hello</code>的可执行文件，使用<code>swiftc</code>命令的<code>-o</code>参数。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ swiftc Sources/main.swift -o hello
</code></pre></div><h3 id="测试执行">测试执行</h3>
<p>一切顺利的话，应该已经生成<code>hello</code>可执行文件，可以执行测试一下，正常的话应该是如下的结果：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ./hello
Hello, Monkey!
</code></pre></div><p>虽然已经学习过Swift语言的语法和规则，但实践很少，所以想在linux平台好进一步通过一定实践进一步学习swift，路慢慢其修远兮呀！</p>]]></content>
		</item>
		
		<item>
			<title>各类硬件接口定义</title>
			<link>https://blog.5km.studio/2016/04/16/HardwareInterface-Elec/</link>
			<pubDate>Sat, 16 Apr 2016 08:10:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/04/16/HardwareInterface-Elec/</guid>
			<description>&lt;p&gt;脑子存储不够用了，故专门开本文整理各类硬件接口，以备不时之需。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>脑子存储不够用了，故专门开本文整理各类硬件接口，以备不时之需。</p>
<h2 id="icd3">ICD3</h2>
<h3 id="定义">定义</h3>
<p>MPLAB ICD 3 在线调试器是一款由在 Windows ® 平台上运行 MPLAB IDE (v8.15 或更高版本)软件的 PC 控制的在线调试器。 MPLAB ICD 3 在线调试器是开发工程师工具包的不可或缺的组成部分。可用于从软件开发到硬件集成等各种应用领域。MPLAB ICD 3 在线调试器是一款支持硬件和软件开发的复杂调试器系统,专用于基于在线串行编程 (In-Circuit Serial ProgrammingTM, ICSPTM)和增强型在线串行编程双线串行接口的 Microchip PIC ® 单片机 (MCU)和 dsPIC ® 数字信号控制器 (Digital Signal Controller, DSC)。</p>
<h3 id="接口定义">接口定义</h3>
<p>贝能国际ICD3 In-Circuit Debugger采用6线接口连接目标板其接插件引脚定义如下表格：</p>
<table>
<thead>
<tr>
<th style="text-align:center">线型</th>
<th style="text-align:center">管脚</th>
<th style="text-align:left">定义</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">蓝色线</td>
<td style="text-align:center">PIN1</td>
<td style="text-align:left">VPP（编程电压）</td>
</tr>
<tr>
<td style="text-align:center">黄色线</td>
<td style="text-align:center">PIN2</td>
<td style="text-align:left">VDD（电源正极）</td>
</tr>
<tr>
<td style="text-align:center">绿色线</td>
<td style="text-align:center">PIN3</td>
<td style="text-align:left">VSS（电源负极）</td>
</tr>
<tr>
<td style="text-align:center">红色线</td>
<td style="text-align:center">PIN4</td>
<td style="text-align:left">PGD（编程数据）</td>
</tr>
<tr>
<td style="text-align:center">黑色线</td>
<td style="text-align:center">PIN5</td>
<td style="text-align:left">PGC（编程时钟）</td>
</tr>
<tr>
<td style="text-align:center">白色线</td>
<td style="text-align:center">PIN6</td>
<td style="text-align:left">LVP（低编程电压）</td>
</tr>
</tbody>
</table>
<h2 id="cc-debugger">CC Debugger</h2>
<h3 id="接口示意图">接口示意图</h3>
<p>CCdebugger的接口示意图如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/scmwy.jpg" alt="1"></p>
<h3 id="接口说明">接口说明</h3>
<p>详表如下：</p>
<table>
<thead>
<tr>
<th>引脚编号</th>
<th>引脚名称</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>GND</td>
<td>地线</td>
</tr>
<tr>
<td>2</td>
<td>VDD</td>
<td>目标板的正电源</td>
</tr>
<tr>
<td>3</td>
<td>DC</td>
<td>调试接口时钟线</td>
</tr>
<tr>
<td>4</td>
<td>DD</td>
<td>调试接口数据线</td>
</tr>
<tr>
<td>5</td>
<td>CSn</td>
<td>下载串口片选线（低电平有效）</td>
</tr>
<tr>
<td>6</td>
<td>SCLK</td>
<td>下载串口时钟线</td>
</tr>
<tr>
<td>7</td>
<td>RESETn</td>
<td>调试器复位接口</td>
</tr>
<tr>
<td>8</td>
<td>MOSI</td>
<td>下载串口数据输出线</td>
</tr>
<tr>
<td>9</td>
<td>3.3V</td>
<td>仿真器3.3V电源输出</td>
</tr>
<tr>
<td>10</td>
<td>MISO</td>
<td>下载串口数据输入线</td>
</tr>
</tbody>
</table>
<p>详见<a href="/ble/2015/10/26/CCDebugger-BLE.html">BLE开发笔记——CC Debugger的使用</a></p>
<h2 id="avr-isp">AVR ISP</h2>
<h3 id="接口定义-1">接口定义</h3>
<p>大部分AVR MCU的ISP数据端口亦为 SCK、MOSI、MISO引脚（如Attiny13/24/2313，Atmega48/88/168/329，Atmega16/32/162，Atmega8515/8535等），如下：</p>
<table>
<thead>
<tr>
<th style="text-align:center">ISP下载器接口</th>
<th style="text-align:center">AVR单片机</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">MISO</td>
<td style="text-align:center">MISO</td>
</tr>
<tr>
<td style="text-align:center">VCC</td>
<td style="text-align:center">VCC</td>
</tr>
<tr>
<td style="text-align:center">SCK</td>
<td style="text-align:center">SCK</td>
</tr>
<tr>
<td style="text-align:center">MOSI</td>
<td style="text-align:center">MOSI</td>
</tr>
<tr>
<td style="text-align:center">RESET</td>
<td style="text-align:center">RESET</td>
</tr>
<tr>
<td style="text-align:center">GND</td>
<td style="text-align:center">GND</td>
</tr>
</tbody>
</table>
<p>少部分AVRMCU的ISP数据端口则不是使用这些接口，而是：SCK、PDI、PDO引脚（如ATmega64/128/1281等），如下：</p>
<table>
<thead>
<tr>
<th style="text-align:center">ISP下载器接口</th>
<th style="text-align:center">AVR单片机</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">MISO</td>
<td style="text-align:center">PDO</td>
</tr>
<tr>
<td style="text-align:center">VCC</td>
<td style="text-align:center">VCC</td>
</tr>
<tr>
<td style="text-align:center">SCK</td>
<td style="text-align:center">SCK</td>
</tr>
<tr>
<td style="text-align:center">MOSI</td>
<td style="text-align:center">PDI</td>
</tr>
<tr>
<td style="text-align:center">RESET</td>
<td style="text-align:center">RESET</td>
</tr>
<tr>
<td style="text-align:center">GND</td>
<td style="text-align:center">GND</td>
</tr>
</tbody>
</table>
<p>以上仅例举出常用的AVR型号的连接方式，若您使用的AVR型号没有被列举到，请查看相关型号的PDF文档，里面的编程章节将有介绍使用ISP时，需连接哪些引脚。</p>
<h3 id="标准接口图">标准接口图</h3>
<p>10脚的ISP接口示意图如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/pj743.jpg" alt="10_pin_isp"></p>
<p>6脚的ISP接口示意图如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/w3p0u.jpg" alt="6_pin_isp"></p>]]></content>
		</item>
		
		<item>
			<title>使用rake生成jekyll静态博客新文章</title>
			<link>https://blog.5km.studio/2016/04/15/rake-new-post/</link>
			<pubDate>Fri, 15 Apr 2016 19:14:39 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/04/15/rake-new-post/</guid>
			<description>&lt;p&gt;使用了Jekyll也有9个月了，虽然写的博文不多，但也是会觉得每次写新文章是痛苦的。复制粘贴再更改也是显得不够条理，一个字一个字的敲也是痛苦的，索性搜索一下有没有好的方法，终于找到了一个好办法，利用ruby插件rake来完成。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>使用了Jekyll也有9个月了，虽然写的博文不多，但也是会觉得每次写新文章是痛苦的。复制粘贴再更改也是显得不够条理，一个字一个字的敲也是痛苦的，索性搜索一下有没有好的方法，终于找到了一个好办法，利用ruby插件rake来完成。</p>
<p>下面就讲一下怎么实现。</p>
<h4 id="安装rake">安装rake</h4>
<p>利用gem包管理工具进行安装rake模块，国内的话要将gem安装源更改为taobao的，详 <a href="/2015/08/24/jekyllInstallFedoraLinux/">Fedora Linux下安装Jekyll</a></p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="c1"># gem 安装 rake</span>
sudo gem install rake
</code></pre></div><h4 id="编写rakefile">编写Rakefile</h4>
<p>我们可以编写Rakefile，按照一定规则生成我们的博文，需要将Rakefile放到Jekyll博客工程的根目录下，如下是示例Rakefile的内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-ruby" data-lang="ruby"><span class="n">task</span> <span class="ss">:default</span> <span class="o">=&gt;</span> <span class="ss">:new</span>

<span class="nb">require</span> <span class="s1">&#39;fileutils&#39;</span>

<span class="n">desc</span> <span class="s2">&#34;创建新 post&#34;</span>
<span class="n">task</span> <span class="ss">:new</span> <span class="k">do</span>
  <span class="nb">puts</span> <span class="s2">&#34;请输入要创建的 post URL：&#34;</span>
	<span class="vi">@url</span> <span class="o">=</span> <span class="no">STDIN</span><span class="o">.</span><span class="n">gets</span><span class="o">.</span><span class="n">chomp</span>
	<span class="nb">puts</span> <span class="s2">&#34;请输入 post 标题：&#34;</span>
	<span class="vi">@name</span> <span class="o">=</span> <span class="no">STDIN</span><span class="o">.</span><span class="n">gets</span><span class="o">.</span><span class="n">chomp</span>
	<span class="nb">puts</span> <span class="s2">&#34;请输入 post 分类，以空格分隔：&#34;</span>
	<span class="vi">@categories</span> <span class="o">=</span> <span class="no">STDIN</span><span class="o">.</span><span class="n">gets</span><span class="o">.</span><span class="n">chomp</span>
	<span class="vi">@slug</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="vi">@url</span><span class="si">}</span><span class="s2">&#34;</span>
	<span class="vi">@slug</span> <span class="o">=</span> <span class="vi">@slug</span><span class="o">.</span><span class="n">downcase</span><span class="o">.</span><span class="n">strip</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="s1">&#39; &#39;</span><span class="p">,</span> <span class="s1">&#39;-&#39;</span><span class="p">)</span>
	<span class="vi">@date</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&#34;%F&#34;</span><span class="p">)</span>
	<span class="vi">@post_name</span> <span class="o">=</span> <span class="s2">&#34;_posts/</span><span class="si">#{</span><span class="vi">@date</span><span class="si">}</span><span class="s2">-</span><span class="si">#{</span><span class="vi">@slug</span><span class="si">}</span><span class="s2">.markdown&#34;</span>
	<span class="vi">@author</span> <span class="o">=</span> <span class="s2">&#34;5km（十里）&#34;</span>
	<span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">exist?</span><span class="p">(</span><span class="vi">@post_name</span><span class="p">)</span>
			<span class="nb">abort</span><span class="p">(</span><span class="s2">&#34;文件名已经存在！创建失败&#34;</span><span class="p">)</span>
	<span class="k">end</span>
	<span class="no">FileUtils</span><span class="o">.</span><span class="n">touch</span><span class="p">(</span><span class="vi">@post_name</span><span class="p">)</span>
	<span class="nb">open</span><span class="p">(</span><span class="vi">@post_name</span><span class="p">,</span> <span class="s1">&#39;a&#39;</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">file</span><span class="o">|</span>
			<span class="n">file</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;---&#34;</span>
			<span class="n">file</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;layout:     post&#34;</span>
			<span class="n">file</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;title:      </span><span class="se">\&#34;</span><span class="si">#{</span><span class="vi">@name</span><span class="si">}</span><span class="se">\&#34;</span><span class="s2">&#34;</span>
			<span class="n">file</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;date:       </span><span class="si">#{</span><span class="no">Time</span><span class="o">.</span><span class="n">now</span><span class="si">}</span><span class="s2">&#34;</span>
			<span class="n">file</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;author:     </span><span class="se">\&#34;</span><span class="si">#{</span><span class="vi">@author</span><span class="si">}</span><span class="se">\&#34;</span><span class="s2">&#34;</span>
			<span class="n">file</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;categories: </span><span class="si">#{</span><span class="vi">@categories</span><span class="si">}</span><span class="s2">&#34;</span>
			<span class="n">file</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;---&#34;</span>
	<span class="k">end</span>
	<span class="nb">exec</span> <span class="s2">&#34;vim </span><span class="si">#{</span><span class="vi">@post_name</span><span class="si">}</span><span class="s2">&#34;</span>
<span class="k">end</span>
</code></pre></div><p>看上面的Ruby代码，应该能看出实现思路，可以根据自己的需求作出修改，其中@author变量改成自己的就可以了。</p>
<h4 id="生成新文章">生成新文章</h4>
<p>使用rake new命令，生成新的post，<strong>new</strong>与上述ruby代码中task命名对应起来，也可以修改。执行命令的时候要在jekyll的工程目录中，执行<code>rake new</code>命令，执行过程如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">-&gt; % rake new
请输入要创建的 post URL：
rake-new-post
请输入 post 标题：
使用rake生成jekyll新文章  
请输入 post 分类，以空格分隔：
Jekyll

</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>linux下使用CC Debugger读写芯片程序</title>
			<link>https://blog.5km.studio/2016/04/15/ccdebugger-linux-ble/</link>
			<pubDate>Fri, 15 Apr 2016 15:34:19 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/04/15/ccdebugger-linux-ble/</guid>
			<description>&lt;p&gt;有个事情一直困扰着我，我一般使用ubuntu linux系统，但当要用CC Debugger为TI的一些芯片烧写程序的时候，不得不切换到windows系统，太麻烦了，难道linux下不能用CC Debugger吗？我就带着这个问题问了一下谷哥，他果然什么都知道，得知cc-tool的存在，是一个老开源项目了，什么？我咋才知道呀！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>有个事情一直困扰着我，我一般使用ubuntu linux系统，但当要用CC Debugger为TI的一些芯片烧写程序的时候，不得不切换到windows系统，太麻烦了，难道linux下不能用CC Debugger吗？我就带着这个问题问了一下谷哥，他果然什么都知道，得知cc-tool的存在，是一个老开源项目了，什么？我咋才知道呀！</p>
<p>嫌罗嗦可以直接<a href="#no1">点我</a>跳过下面一段。</p>
<p>在github可以找到好多cc-tool项目，不过大部分都是fork别人的，其实追根溯源会发现此项目来自sourceforge，<a href="http://sourceforge.net/projects/cctool/files/">我是传送门</a>，名为<strong>cctool</strong>，我试过其中一个，不过好久没更新了，下载下来尝试了一下，结果执行**./configure**时不能通过，提示libboost-all-dev版本太高，这是什么鬼？查看configure文件，大体知道是内部有这个依赖包的版本限制，只能说这个cctool太老了，然后又找到一个项目，这是fork自sourceforge的，可能就是作者本人的（我猜的，没去细究），在2015年10月份有更新过，尝试了一下，果然可以，查看15年10月份的commit，发现果然是修改了configure文件，不多说了，下面就说一下怎么编译安装和使用。</p>
<h3 id="测试平台">测试平台</h3>
<ul>
<li>Ubuntu系统</li>
<li>TI keyfob 开发套件（cc2541芯片）</li>
<li>CC Debugger</li>
</ul>
<h3 id="编译安装">编译安装</h3>
<p>本文使用github的<a href="https://github.com/dashesy">dashesy</a>的项目<a href="https://github.com/dashesy/cc-tool">cc-tool</a>，表示十分感谢！</p>
<h4 id="下载源文件">下载源文件</h4>
<p>切换到用户随便一个目录，比如<strong>Downloads</strong>，git拷贝下载，进入项目源文件目录。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">cd</span> ~/Downloads
git clone https://github.com/dashesy/cc-tool.git
</code></pre></div><p>然后，</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">cd</span> cc-tool-master
</code></pre></div><h4 id="安装依赖包">安装依赖包</h4>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo apt-get install libusb-1.0 libboost-all-dev
</code></pre></div><h4 id="配置编译安装">配置编译安装</h4>
<ul>
<li>
<p>配置</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">./configure
</code></pre></div></li>
<li>
<p>编译</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">make
</code></pre></div></li>
<li>
<p>安装</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo make install
</code></pre></div></li>
<li>
<p>由于linux对usb设备访问有权限限制，所以还需要将项目文件中的udev下的<strong>90-cc-debugger.rules</strong>拷贝到<code>/etc/udev/rules.d</code>目录下，然后重启电脑。（如果不拷贝rule文件可以在每次使用cc-tool时加上sudo）</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo cp udev/90-cc-debugger.rules /etc/udev/rules.d/
</code></pre></div></li>
</ul>
<h3 id="使用">使用</h3>
<p>如果一切顺利的话，此时你应该将cc-tool安装成功，可以用<strong>man</strong>命令查看如何使用。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="c1"># Read entire flash into binary file image.xxx</span>
<span class="c1"># 读取整个Flash保存为image.xxx文件</span>
cc-tmool -r image.xxx,bin
<span class="c1"># Erase flash, write intel hex file image.hex and verify flash using default method</span>
<span class="c1"># 擦出flash，使用默认方法烧写intel hex格式的hex文件，并验证flash</span>
cc-tool -v -e -w image.hex
<span class="c1"># Merge file image.hex and patch.bin (at offset 80), write  resulting  image,  verify  flash using read method</span>
<span class="c1"># 合并image.hex和偏移地址在0x80的patch.bin，并进行烧写，使用读模式验证flash</span>
cc-tool -v <span class="nb">read</span> -w image.hex --write patch.bin,80
<span class="c1"># Set debug lock bit</span>
<span class="c1"># 设置调试加锁位</span>
cc-tool --lock debug
<span class="c1"># Set debug lock bit and lock pages 0,1,2,3,4</span>
<span class="c1"># 设置调试加锁位，为0,1,2,3,4页加锁 </span>
cc-tool --lock debug<span class="p">;</span>pages:0-4
<span class="c1"># Set debug lock bit, boot lock bit, and set lock size 8K</span>
<span class="c1"># 设置调试加锁位、启动加锁位，设置加锁大小为8k</span>
cc-tool --lock debug<span class="p">;</span>boot<span class="p">;</span>flash:8
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>Tab键Soft和Hard之分</title>
			<link>https://blog.5km.studio/2016/04/10/SoftTabOrHardTab-Develop/</link>
			<pubDate>Sun, 10 Apr 2016 10:10:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/04/10/SoftTabOrHardTab-Develop/</guid>
			<description>&lt;p&gt;这怪之前孤陋寡闻，在使用编辑器Atom时，进入设置看到Tab type（Tab类型），咦，tab还有Soft和Hard之分呀，Settings中关于&lt;strong&gt;Tab Type&lt;/strong&gt;的提示如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Determine character inserted when Tab key is pressed. Possible values: &amp;ldquo;auto&amp;rdquo;, &amp;ldquo;soft&amp;rdquo; and &amp;ldquo;hard&amp;rdquo;. When set to &amp;ldquo;soft&amp;rdquo; or &amp;ldquo;hard&amp;rdquo;, soft tabs (spaces) or hard tabs (tab characters) are used. When set to &amp;ldquo;auto&amp;rdquo;, the editor auto-detects the tab type based on the contents of the buffer (it uses the first leading whitespace on a non-comment line), or uses the value of the Soft Tabs config setting if auto-detection fails.&lt;/p&gt;
&lt;/blockquote&gt;</description>
			<content type="html"><![CDATA[<p>这怪之前孤陋寡闻，在使用编辑器Atom时，进入设置看到Tab type（Tab类型），咦，tab还有Soft和Hard之分呀，Settings中关于<strong>Tab Type</strong>的提示如下：</p>
<blockquote>
<p>Determine character inserted when Tab key is pressed. Possible values: &ldquo;auto&rdquo;, &ldquo;soft&rdquo; and &ldquo;hard&rdquo;. When set to &ldquo;soft&rdquo; or &ldquo;hard&rdquo;, soft tabs (spaces) or hard tabs (tab characters) are used. When set to &ldquo;auto&rdquo;, the editor auto-detects the tab type based on the contents of the buffer (it uses the first leading whitespace on a non-comment line), or uses the value of the Soft Tabs config setting if auto-detection fails.</p>
</blockquote>
<p>根据上面内容可知，原来是在编辑文本文档时，当设置类型为<strong>Soft</strong>则按下Tab键插入空格符（个数便是tab size设置的大小），当设置为<strong>Hard</strong>则按下Tab键便插入一个Tab符，当设置为<strong>Auto</strong>的时候，编辑器便会根据所编辑文件内的风格进行设置，判断首次缩进位置所使用的风格。</p>
<p>其实，这也不难想到，Soft类型插入的缩进无论到哪儿大小肯定不变，会一直是你想要的样子，而Hard类型会根据在不同环境中设置的tab大小进行缩进，往往导致不统一，这对于强迫症的我简直不能忍，我还是希望编辑的代码到每个地方缩进是统一的，那么Soft才是我需要的，这印证一个说法：</p>
<blockquote>
<p>Soft方式是绝对的，Hard是相对的。</p>
</blockquote>
<p>无论什么类型，自己喜欢就好！</p>]]></content>
		</item>
		
		<item>
			<title>0x80070570错误解决</title>
			<link>https://blog.5km.studio/2016/04/05/0x80070570error-Life/</link>
			<pubDate>Tue, 05 Apr 2016 15:10:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/04/05/0x80070570error-Life/</guid>
			<description>&lt;p&gt;今天无聊的“虫子”拿着移动硬盘过来让我删除硬盘上的一个文件夹（在windows下），呀，一删除就弹出一个错误提示说是：&lt;code&gt;error0x80070570&lt;/code&gt;，文件无法删除。木有办法，看来只有我能拯救这个世界了，经过了解，呀，别人已经解决了这个世界性难题，好吧，不管怎样，我也学到了咋解决：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;没有正常插拔移动设备&lt;code&gt;系统没有完成完整的读写操作&lt;/code&gt;致使文件目录信息错乱和不完整&lt;br&gt;
比如&lt;br&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;复制一个文件到移动设备，关机重启，拔取后再次打开使用或到别的电脑上使用时出现症状病毒&lt;br&gt;&lt;/li&gt;
&lt;li&gt;硬盘硬件本身故障&lt;br&gt;&lt;/li&gt;
&lt;li&gt;工作期间突然停电。&lt;/li&gt;
&lt;/ol&gt;</description>
			<content type="html"><![CDATA[<p>今天无聊的“虫子”拿着移动硬盘过来让我删除硬盘上的一个文件夹（在windows下），呀，一删除就弹出一个错误提示说是：<code>error0x80070570</code>，文件无法删除。木有办法，看来只有我能拯救这个世界了，经过了解，呀，别人已经解决了这个世界性难题，好吧，不管怎样，我也学到了咋解决：</p>
<blockquote>
<p>没有正常插拔移动设备<code>系统没有完成完整的读写操作</code>致使文件目录信息错乱和不完整<br>
比如<br></p>
</blockquote>
<ol>
<li>复制一个文件到移动设备，关机重启，拔取后再次打开使用或到别的电脑上使用时出现症状病毒<br></li>
<li>硬盘硬件本身故障<br></li>
<li>工作期间突然停电。</li>
</ol>
<h4 id="检查磁盘来解决">检查磁盘来解决</h4>
<ul>
<li>
<p>运行命令行（这里说两种方式）</p>
<ul>
<li><strong>win + R</strong>或<strong>开始-&gt;运行</strong>，输入<strong>cmd</strong>回车</li>
<li><strong>win + x</strong>，选择<strong>命令行</strong></li>
</ul>
</li>
<li>
<p>确定文件所在磁盘执行以下命令，其中<strong>F</strong>为盘符</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">C:<span class="se">\W</span>indows<span class="se">\s</span>ystem32&gt;chkdsk F: /f
</code></pre></div></li>
<li>
<p>等待完成后，会出现类似如下的结果，再去尝试删除就一切OK了。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">The <span class="nb">type</span> of the file system is NTFS.
Volume label is 新加卷.

Stage 1: Examining basic file system structure ...
  <span class="m">70656</span> file records processed.
File verification completed.
  <span class="m">0</span> large file records processed.
  <span class="m">0</span> bad file records processed.

Stage 2: Examining file name linkage ...
Correcting error in index <span class="nv">$O</span> <span class="k">for</span> file 25.
CHKDSK discovered free space marked as allocated in the bitmap <span class="k">for</span> index <span class="nv">$O</span> <span class="k">for</span> file 25.
Sorting index <span class="nv">$O</span> in file 25.
Inserting an index entry into index <span class="nv">$O</span> of file 25.
Inserting an index entry into index <span class="nv">$O</span> of file 25.
.
.
.

Windows has made corrections to the file system.
No further action is required.

 <span class="m">347408383</span> KB total disk space.
 <span class="m">119105712</span> KB in <span class="m">59504</span> files.
     <span class="m">28084</span> KB in <span class="m">11046</span> indexes.
         <span class="m">0</span> KB in bad sectors.
    <span class="m">147219</span> KB in use by the system.
     <span class="m">65536</span> KB occupied by the log file.
 <span class="m">228127368</span> KB available on disk.

      <span class="m">4096</span> bytes in each allocation unit.
  <span class="m">86852095</span> total allocation units on disk.
  <span class="m">57031842</span> allocation units available on disk.
  <span class="sb">```</span>

</code></pre></div></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>Linux下Sublime Text支持中文输入</title>
			<link>https://blog.5km.studio/2016/03/26/subl-ChineseInput-Linux/</link>
			<pubDate>Sat, 26 Mar 2016 09:10:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/03/26/subl-ChineseInput-Linux/</guid>
			<description>&lt;p&gt;sublime Text是一款非常好用的代码文本编辑器，在我的其它文章中有过介绍它的安装，使用后发现在linux下Sublime text下中文输入法失效，不能输入中文，本文就讲解一下如何解决。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>sublime Text是一款非常好用的代码文本编辑器，在我的其它文章中有过介绍它的安装，使用后发现在linux下Sublime text下中文输入法失效，不能输入中文，本文就讲解一下如何解决。</p>
<h3 id="平台">平台</h3>
<ol>
<li>Ubuntu 15.10</li>
<li>sublime text 3</li>
<li>fcitx输入法</li>
</ol>
<h3 id="操作">操作</h3>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="c1"># 获取Github文件</span>
git clone https://github.com/smslit/SublimeText-IM-Fix.git <span class="o">&amp;&amp;</span> <span class="nb">cd</span> SublimeText-IM-Fix
<span class="c1"># 拷贝修复共享库</span>
sudo cp lib/libsublime-imfix.so /opt/sublime_text
<span class="c1"># 拷贝软件链接</span>
sudo cp bin/subl /usr/bin
<span class="c1"># 拷贝启动器快捷方式</span>
sudo cp applications/sublime_text.desktop /usr/share/applications/
</code></pre></div><p>命令行：subl或者Launcher启动运行软件，如果顺利的话应该支持中文输入了。</p>
<h3 id="详解">详解</h3>
<p>其实安装完<strong>Sublime text</strong>后，会在**/usr/bin/**下生成subl文件，在**/usr/share/applications/**下生成sublime_text.desktop，为了使sublime text支持中文输入，在启动软件的时候需要调用**libsublime-imfix.so**，在上述操作中拷贝的文件就是修改之后的subl和sublime_text.desktop。下面主要说明一下修改的地方。</p>
<h4 id="subl文件">subl文件</h4>
<p>原文件内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="cp">#!/bin/sh
</span><span class="cp"></span><span class="nb">exec</span> /opt/sublime_text/sublime_text <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span>
</code></pre></div><p>修改后的文件内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="cp">#!/bin/sh
</span><span class="cp"></span><span class="nv">LD_PRELOAD</span><span class="o">=</span>/opt/sublime_text/libsublime-imfix.so <span class="nb">exec</span> /opt/sublime_text/sublime_text <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span>
</code></pre></div><p>对比可发现，是在原来的语句前加了调用修复库libsublime-imfix.so的语句</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nv">LD_PRELOAD</span><span class="o">=</span>/opt/sublime_text/libsublime-imfix.so
</code></pre></div><h4 id="sublime_textdesktop文件">sublime_text.desktop文件</h4>
<p>修改的地方有三点，均在<strong>Exec</strong>的位置，也是在运行软件的语句前加了调用修复库libsublime-imfix.so的语句。</p>
<p>原文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="o">[</span>Desktop Entry<span class="o">]</span>
<span class="nv">Version</span><span class="o">=</span>1.0
<span class="nv">Type</span><span class="o">=</span>Application
<span class="nv">Name</span><span class="o">=</span>Sublime Text
<span class="nv">GenericName</span><span class="o">=</span>Text Editor
<span class="nv">Comment</span><span class="o">=</span>Sophisticated text editor <span class="k">for</span> code, markup and prose
<span class="nv">Exec</span><span class="o">=</span>/opt/sublime_text/sublime_text %F
<span class="nv">Terminal</span><span class="o">=</span><span class="nb">false</span>
<span class="nv">MimeType</span><span class="o">=</span>text/plain<span class="p">;</span>
<span class="nv">Icon</span><span class="o">=</span>sublime-text
<span class="nv">Categories</span><span class="o">=</span>TextEditor<span class="p">;</span>Development<span class="p">;</span>
<span class="nv">StartupNotify</span><span class="o">=</span><span class="nb">true</span>
<span class="nv">Actions</span><span class="o">=</span>Window<span class="p">;</span>Document<span class="p">;</span>

<span class="o">[</span>Desktop Action Window<span class="o">]</span>
<span class="nv">Name</span><span class="o">=</span>New Window
<span class="nv">Exec</span><span class="o">=</span>/opt/sublime_text/sublime_text -n
<span class="nv">OnlyShowIn</span><span class="o">=</span>Unity<span class="p">;</span>

<span class="o">[</span>Desktop Action Document<span class="o">]</span>
<span class="nv">Name</span><span class="o">=</span>New File
<span class="nv">Exec</span><span class="o">=</span>/opt/sublime_text/sublime_text --command new_file
<span class="nv">OnlyShowIn</span><span class="o">=</span>Unity<span class="p">;</span>
</code></pre></div><p>修改后的文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="o">[</span>Desktop Entry<span class="o">]</span>
<span class="nv">Version</span><span class="o">=</span>1.0
<span class="nv">Type</span><span class="o">=</span>Application
<span class="nv">Name</span><span class="o">=</span>Sublime Text
<span class="nv">GenericName</span><span class="o">=</span>Text Editor
<span class="nv">Comment</span><span class="o">=</span>Sophisticated text editor <span class="k">for</span> code, markup and prose
<span class="nv">Exec</span><span class="o">=</span>bash -c <span class="s2">&#34;LD_PRELOAD=/opt/sublime_text/libsublime-imfix.so exec /opt/sublime_text/sublime_text %F&#34;</span>
<span class="nv">Terminal</span><span class="o">=</span><span class="nb">false</span>
<span class="nv">MimeType</span><span class="o">=</span>text/plain<span class="p">;</span>
<span class="nv">Icon</span><span class="o">=</span>sublime-text
<span class="nv">Categories</span><span class="o">=</span>TextEditor<span class="p">;</span>Development<span class="p">;</span>
<span class="nv">StartupNotify</span><span class="o">=</span><span class="nb">true</span>
<span class="nv">Actions</span><span class="o">=</span>Window<span class="p">;</span>Document<span class="p">;</span>

<span class="o">[</span>Desktop Action Window<span class="o">]</span>
<span class="nv">Name</span><span class="o">=</span>New Window
<span class="nv">Exec</span><span class="o">=</span>bash -c <span class="s2">&#34;LD_PRELOAD=/opt/sublime_text/libsublime-imfix.so exec /opt/sublime_text/sublime_text -n&#34;</span>
<span class="nv">OnlyShowIn</span><span class="o">=</span>Unity<span class="p">;</span>

<span class="o">[</span>Desktop Action Document<span class="o">]</span>
<span class="nv">Name</span><span class="o">=</span>New File
<span class="nv">Exec</span><span class="o">=</span>bash -c <span class="s2">&#34;LD_PRELOAD=/opt/sublime_text/libsublime-imfix.so exec /opt/sublime_text/sublime_text --command new_file&#34;</span>
<span class="nv">OnlyShowIn</span><span class="o">=</span>Unity<span class="p">;</span>
</code></pre></div><h3 id="参考">参考</h3>
<ol>
<li><a href="http://jingyan.baidu.com/article/f3ad7d0ff8731609c3345b3b.html">Ubuntu下Sublime Text 3解决无法输入中文的方法</a></li>
<li><a href="http://www.jianshu.com/p/bf05fb3a4709">解决Ubuntu下Sublime Text 3无法输入中文</a></li>
</ol>]]></content>
		</item>
		
		<item>
			<title>Ubuntu下配置JDK</title>
			<link>https://blog.5km.studio/2016/03/24/JDKConfig-Linux/</link>
			<pubDate>Thu, 24 Mar 2016 09:10:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/03/24/JDKConfig-Linux/</guid>
			<description>&lt;p&gt;在使用某些软件或者要进行Android或者Java开发的话，需要配置java的环境变量，本文就来讲一下安装和配置jdk的步骤。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>在使用某些软件或者要进行Android或者Java开发的话，需要配置java的环境变量，本文就来讲一下安装和配置jdk的步骤。</p>
<h3 id="测试环境">测试环境</h3>
<p>ubuntu15.10</p>
<h3 id="安装步骤">安装步骤</h3>
<h4 id="下载jdk">下载JDK</h4>
<p>需要先到 <strong><a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html">java官网</a></strong> 下载相应JDK包，这里我下载的是<strong>jdk-8u45-linux-x64.tar.gz</strong>，将下载的压缩包放到Downloads目录下。</p>
<h4 id="解压安装">解压安装</h4>
<ul>
<li>打开终端，可以在launcher中启动Terminal也可以按快捷键<strong>CTRL+ALT+t</strong>来启动终端。</li>
<li>敲入命令，解压压缩包：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">tar -xf jdk-8u45-linux-x64.tar.gz
</code></pre></div><ul>
<li>将解压后的jdk拷贝到**/usr/lib/**下，执行命令：</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo cp -r jdk1.8.0_45 /usr/lib/
</code></pre></div><h4 id="配置环境变量">配置环境变量</h4>
<p>这里我们可以修改系统全局的环境变量配置文件，这样对系统的所有用户都起作用，在**/etc/profile**文件末尾添加java环境变量来完成本部分操作。</p>
<ul>
<li>
<p>编辑**/etc/profile**</p>
<p>使用编辑器编辑此文件，可以用gedit也可以用vim或者其他编辑器，我用vim，但都不要忘记要用sudo打开文件，可敲入命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo vim /etc/profile
</code></pre></div></li>
<li>
<p>在文件末尾添加以下三行内容，注意的是一定要与你的安装目录对应，可能版本不同解压出来的文件目录名不同。</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">export</span> <span class="nv">JAVA_HOME</span><span class="o">=</span>/usr/lib/jdk1.8.0_45
<span class="nb">export</span> <span class="nv">CLASSPATH</span><span class="o">=</span>.:<span class="nv">$JAVA_HOME</span>/lib:<span class="nv">$JAVA_HOME</span>/jre/lib:<span class="nv">$CLASSPATH</span>
<span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="nv">$JAVA_HOME</span>/bin:<span class="nv">$JAVA_HOME</span>/jre/bin:<span class="nv">$PATH</span>
</code></pre></div></li>
<li>
<p>:wq，保存退出。</p>
</li>
<li>
<p>执行下面命令使配置生效：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo <span class="nb">source</span> /etc/profile
</code></pre></div></li>
<li>
<p>检查是否安装成功，可以执行下面的命令查看：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">java -version
</code></pre></div></li>
<li>
<p>如果安装配置成功会出现下面的语句：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">java version <span class="s2">&#34;1.8.0_45&#34;</span>
Java<span class="o">(</span>TM<span class="o">)</span> SE Runtime Environment <span class="o">(</span>build 1.8.0_45-b14<span class="o">)</span>
Java HotSpot<span class="o">(</span>TM<span class="o">)</span> 64-Bit Server VM <span class="o">(</span>build 25.45-b02, mixed mode<span class="o">)</span>
</code></pre></div></li>
</ul>
<h3 id="注意">注意</h3>
<p>现在java只是针对终端可用，如果需要使用java还需要重启电脑。</p>]]></content>
		</item>
		
		<item>
			<title>Gem添加软件源时SSl错误解决</title>
			<link>https://blog.5km.studio/2016/03/21/gem-ssl-error-jekyll/</link>
			<pubDate>Mon, 21 Mar 2016 17:10:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/03/21/gem-ssl-error-jekyll/</guid>
			<description>&lt;p&gt;windows下在用gem添加taobao的软件源时出现了ssl认证错误，是因为ruby没有包含ssl证书，所以需要通过下载和配置证书来解决。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>windows下在用gem添加taobao的软件源时出现了ssl认证错误，是因为ruby没有包含ssl证书，所以需要通过下载和配置证书来解决。</p>
<h3 id="问题现象">问题现象</h3>
<p>在命令行下执行下面指令：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">gem sources -a https://ruby.taobao.org/
</code></pre></div><p>出现以下错误提示：</p>
<blockquote>
<p>Error fetching <a href="https://ruby.taobao.org/:">https://ruby.taobao.org/:</a> SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (<a href="https://api.rubygems.org/latest_specs.4.8.gz">https://api.rubygems.org/latest_specs.4.8.gz</a>)</p>
</blockquote>
<h3 id="解决方法">解决方法</h3>
<h4 id="下载证书">下载证书</h4>
<p>点<a href="http://curl.haxx.se/ca/cacert.pem">这里</a>是证书，另存到电脑上，目录自己决定，我是保存到了Ruby的安装目录。</p>
<h4 id="设置环境变量">设置环境变量</h4>
<p>需要设置环境变量指向该文件，系统才能找到证书。下面简单说一下步骤（以win10为例）：</p>
<h5 id="系统设置">系统设置</h5>
<p>按下<strong>win键</strong> + <strong>X</strong>，调出快捷选项，选择<strong>系统</strong>，进入系统设置界面：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/t6q6w.png" alt="1"></p>
<p>选择左侧中的<strong>高级系统设置（Advanced System settings）</strong>，进入高级系统设置界面：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/db7yg.png" alt="2"></p>
<p>选择<strong>环境变量（Environment Variables）</strong>，进入环境变量设置界面：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/crakn.png" alt="3"></p>
<h5 id="环境变量设置">环境变量设置</h5>
<p>选择<strong>新建（New&hellip;)</strong>，进入新建的界面，并填入变量名<code>SSL_CERT_FILE</code>和刚才的证书的位置（以我的为例）<code>C:\Ruby22-x64\cacert.pem</code>，如下所示：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/y3myc.png" alt="4"></p>
<h5 id="重启命令行">重启命令行</h5>
<p>重启命令行，然后在使用gem添加taobao源，即可成功了，这里要说明的是不一定添加源才出现这问题，只要是访问源的操作缺少证书的话都会出现此文的错误。</p>]]></content>
		</item>
		
		<item>
			<title>BLE开发笔记——简单新建属于自己的协议栈工程</title>
			<link>https://blog.5km.studio/2016/03/21/newProject-BLE/</link>
			<pubDate>Mon, 21 Mar 2016 10:10:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/03/21/newProject-BLE/</guid>
			<description>&lt;p&gt;本文主要讲一下新建TI的CC2541协议栈的工程。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>本文主要讲一下新建TI的CC2541协议栈的工程。</p>
<h3 id="前言">前言</h3>
<p>通过阅读BLE协议栈的开发手册，能够大体了解到协议栈的组成架构，并对每一层（PHY、LL、HCI、L2CAP、SM、ATT、GATT和GAP）具体是做什么也有了一定认识和理解，接下来通过一定的工程实践慢慢去深入学习，手册中也讲了内部的操作系统抽象层（OSAL），主要实现了轮询任务事件触发机制，是一个较简单的嵌入式系统，所以要做TI的蓝牙开发还要对OSAL的工作原理有一定理解，所有这些可以参考学习TI提供的官方手册。</p>
<h3 id="正文">正文</h3>
<p>言归正传，本文主要讲的是利用TI协议栈工程新建属于自己定制的工程。首先说明的是，我讲的这种方式比较粗暴，哈哈。</p>
<h4 id="简单说明工程结构">简单说明工程结构</h4>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/mqr4n.png" alt="0"></p>
<p>可以看到，一般情况下按照官方例程的工程来说就是上面的一种结构，根目录下有<strong>Components</strong>和<strong>Projects</strong>两个文件夹，<strong>Projects</strong>文件夹下又有<strong>common</strong>、<strong>config</strong>、<strong>Include</strong>、<strong>Libraries</strong>、<strong>Profiles</strong>和<strong>SimpleBLEPeripheral</strong>，<strong>SimpleBLEPeripheral</strong>文件夹下有<strong>CC2541DB</strong>和<strong>Source</strong>，根据这个结构，我们就可以通过复制粘贴来完成工程的复制新建了。</p>
<p><strong>CC2541DB</strong>文件夹下放的就是工程文件，<strong>Source</strong>文件夹下放的是自己编写的源文件。</p>
<h4 id="新建工程">新建工程</h4>
<h5 id="新建文件夹">新建文件夹</h5>
<p>自己选择合适的目录，然后在其下建立文件夹，比如<strong>TestLED</strong>，在<strong>TestLED</strong>下新建文件夹<strong>Projects</strong>，再在<strong>Projects</strong>文件夹下建立<strong>ble</strong>文件夹。</p>
<h5 id="拷贝文件夹">拷贝文件夹</h5>
<p>找到协议栈的安装目录，我的是<code>C:\Texas Instruments\BLE-CC254x-1.4.0</code>，将整个文件夹<strong>Components</strong>拷贝到<strong>TestLED</strong>中，然后进入协议栈的<strong>Projects</strong>下的<strong>ble</strong>，将<strong>common</strong>、<strong>config</strong>、<strong>Include</strong>、<strong>Libraries</strong>、<strong>Profiles</strong>和<strong>SimpleBLEPeripheral</strong>六个文件夹放到<strong>TestLED</strong>&ndash;<strong>Projects</strong>&ndash;<strong>ble</strong>目录下，完成后的文件结构如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/aegf9.png" alt="1"></p>
<p>可以将<strong>SimplePeripheral</strong>文件夹修改为自己想要的名字，如<strong>TestLED</strong>。</p>
<h5 id="验证工程">验证工程</h5>
<p>打开<strong>SimplePeripheral</strong>目录下的工程文件，进入IAR编译测试，一般是可以编译通过的。</p>
<h4 id="工程配置">工程配置</h4>
<ul>
<li>
<p>先清楚配置文件选择的位置，如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/m15za.png" alt="2"></p>
</li>
<li>
<p>可以看到默认的配置选择的是<code>CC2541DK-MINI Keyfob</code>，为了保证原配置文件的安全，我们可以新建自己的配置，点击菜单栏<code>Projects</code>-&gt;<code>Edit Configurations...</code>，就会跳出以下窗口：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/psxu1.png" alt="3"></p>
</li>
<li>
<p>选择<code>New...</code>，可以调出新建配置窗口：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/j1al4.png" alt="4"></p>
</li>
<li>
<p>可以填入自己想要的配置名称，比如这里是<strong>TestLED</strong>，Toolchain当然是选8051，还有就是Based on configuration，这个我们要选择合适的，我选的是<strong>CC2541</strong>，点击OK完成，就可以看到工程配置就是<strong>TestLED</strong>了。</p>
</li>
</ul>
<h4 id="简单看一下options">简单看一下Options</h4>
<p>右击工程<strong>SimpleBLEPeripheral</strong>，选择<strong>options</strong>调出配置窗口，选择左边栏<code>General Options</code>下的<code>C/C++ Compiler</code>，然后在右侧选择<code>Preprocessor</code>选项卡，如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/jb37x.png" alt="5"></p>
<ul>
<li>
<p>看到<strong>Additional include directiories:(one per line)</strong>，仔细观察每一行都有个<code>$PROJ_DIR$</code>，这代表的是工程文件所在的目录<strong>SimpleBLEPeripheral</strong>下的<strong>CC2541DB</strong>，再看就是还有<code>..\</code>，代表的是上一层目录，所以就明白了之前为什么复制那些文件夹到相应目录了吧。</p>
</li>
<li>
<p>再看<strong>Defined symbols:(one per line)</strong>，在这里编辑相应宏定义可以使用相应的HAL驱动和协议栈功能，比如其中的<code>HAL_LED=FALSE</code>，这就是说明不使用HAL的led灯控制功能，即IO输出。</p>
</li>
</ul>]]></content>
		</item>
		
		<item>
			<title>Mac 使用技巧</title>
			<link>https://blog.5km.studio/2016/03/01/Mac-Notes-Life/</link>
			<pubDate>Tue, 01 Mar 2016 10:10:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2016/03/01/Mac-Notes-Life/</guid>
			<description>&lt;p&gt;开本文的目的是整理和记录平常使用mac的技巧。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>开本文的目的是整理和记录平常使用mac的技巧。</p>
<h3 id="内容">内容</h3>
<h4 id="修改launchpad的图标大小">修改Launchpad的图标大小</h4>
<p>这里修改大小，并不是修改为确切的大小，而是通过调整每一行、每一列显示图标数量来调整的，需要用到终端（Terminal），执行命令完成修改，分三步：</p>
<ul>
<li>
<p>1.1 修改每一列显示图标数量：</p>
<p><strong>defaults write com.apple.dock springboard-rows -int 6</strong></p>
</li>
<li>
<p>1.2 修改每一行显示图标数量：</p>
<p><strong>defaults write com.apple.dock springboard-columns -int 10</strong></p>
</li>
<li>
<p>1.3 重置Launchpad并重启dock：</p>
<p><strong>defaults write com.apple.dock ResetLaunchPad -bool TRUE;killall Dock</strong></p>
</li>
</ul>]]></content>
		</item>
		
		<item>
			<title>解决adb对Android设备的识别问题</title>
			<link>https://blog.5km.studio/2015/12/13/adbT1-Develop/</link>
			<pubDate>Sun, 13 Dec 2015 08:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/12/13/adbT1-Develop/</guid>
			<description>&lt;p&gt;今天开始学习APICloud开发Android平台下的app，使用的是Sublime Text3进行开发，按照官网上的教程把插件安装成功，并按照教程新建了一个有底部导航栏的demo app，当右击选择真机调试时，却弹出对话框没有链接着的设备，可是明明我用usb连接着我的T1呀，本文就讲解一下我解决这个问题的过程。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天开始学习APICloud开发Android平台下的app，使用的是Sublime Text3进行开发，按照官网上的教程把插件安装成功，并按照教程新建了一个有底部导航栏的demo app，当右击选择真机调试时，却弹出对话框没有链接着的设备，可是明明我用usb连接着我的T1呀，本文就讲解一下我解决这个问题的过程。</p>
<h3 id="平台">平台</h3>
<ul>
<li>平台：Mac</li>
<li>手机：Smartisan T1（锤子手机）</li>
<li>开发技术平台：APICloud</li>
</ul>
<h3 id="问题分析">问题分析</h3>
<p>在sublime Text的APICloud插件中，有android真机调试的选项，但是我点击后却出现一下对话框：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/regid.png" alt="1"></p>
<p>正常情况下，应该可以直接进行真机调试，我首先想到会不会是官方教程中所说的在mac下权限的问题，我就进入插件安装包下找到adb，命令行下执行</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">chmod +x ./adb
</code></pre></div><p>但是，修改adb权限后还是这个问题，调用adb命令查看连接着的设备</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">./adb devices
</code></pre></div><p>但是结果中确实没有链接中的设备，这就排除插件的问题了，应该会是手机的问题，按理说adb应该可以查看到链接中的设备，我又去网上一搜，发现了<a href="http://bbs.smartisan.com/thread-60797-1-1.html">Smartisan T1 配置 ADB 的方法</a>这个帖子，这才明白原来是旧版的adb中没有包含T1的ID所致，但是帖子中没有Mac下解决此问题的方法，估计应该是类似的方法，无非就是添加T1的ID到adb_usb.ini文件中，后来搜索又看到了这个帖子<a href="http://my.oschina.net/u/698243/blog/139441">Mac下android手机的adb识别</a>，第2部分将讲解各平台下解决adb对T1的识别问题。</p>
<h3 id="解决adb对android设备的识别问题">解决adb对android设备的识别问题</h3>
<p>貌似最新的adb工具已经包含了Smartisan T1的Vendor ID 0X29A9，但是如果不想更换到最新的adb可以通过以下方法解决不能识别设备的问题，其实这些方法同样适于所有不能识别的android手机，因为可能都是因为adb没有包含对应手机的Vendor ID所致。</p>
<h4 id="查看android设备的vendor-id">查看Android设备的Vendor ID</h4>
<p>首先我们应该知道我们的android设备的Vendor ID。</p>
<h5 id="windows下">Windows下</h5>
<p>在windows下，通常的做法是进入设备管理器找到连接的手机右击选择属性进行查看。</p>
<h5 id="linux下">Linux下</h5>
<p>通常可以在终端下输入以下命令，敲回车可以查看连接的USB设备的信息，然后记下ID即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">lsusb -tv
</code></pre></div><h5 id="mac下">Mac下</h5>
<p>顶栏最左侧苹果标志-&gt;关于本机-&gt;更多信息-&gt;概览-&gt;系统报告-&gt;USB-&gt;Android-&gt;厂商ID(Vendor ID)</p>
<h4 id="配置中添加vendor-id">配置中添加Vendor ID</h4>
<p>我们知道了Vendor ID，下面就要修改一些文件来实现识别了，三大平台下的方法有所差异，但都是向文件中添加Vendor ID来解决问题。</p>
<h5 id="windows下-1">Windows下</h5>
<ul>
<li>
<p><strong>windows8/8.1</strong></p>
<ul>
<li>禁用强制驱动签名，参考教程－<strong><a href="http://bbs.pcbeta.com/viewthread-1122886-1-1.html">解决Win8/8.1强制要求驱动签名而导致驱动安装失败的问题</a></strong></li>
<li>从设备管理器中安装驱动<a href="http://pan.baidu.com/s/1sjXK1ml">adb_usb_driver_smartisan.zip</a>。</li>
<li>打开用户目录下的.android/adb_usb.ini文件，将得到的Vendor ID添加到最后一行（是新起一行），比如T1的是0x29A9.</li>
<li>在命令行中重启adb：
<ul>
<li>adb kill-server</li>
<li>adb start-server</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>windows7</strong></p>
<ul>
<li>从设备管理器中安装驱动<a href="http://pan.baidu.com/s/1sjXK1ml">adb_usb_driver_smartisan.zip</a>。</li>
<li>打开用户目录下的.android/adb_usb.ini文件，将得到的Vendor ID添加到最后一行（是新起一行），比如T1的是0x29A9.</li>
<li>在命令行中重启adb：
<ul>
<li>adb kill-server</li>
<li>adb start-server</li>
</ul>
</li>
</ul>
</li>
</ul>
<h5 id="linux下-1">Linux下</h5>
<ul>
<li>
<p>修改设备规则文件，向文件<code>/etc/udev/rules.d/51-android.rules</code>中添加以下内容：</p>
<blockquote>
<p>SUBSYSTEM==&ldquo;usb&rdquo;, ATTR{idVendor}==&ldquo;29a9&rdquo;, ATTR{idProduct}==&ldquo;701a&rdquo;, MODE=&ldquo;0666&rdquo;, OWNER=&ldquo;your name&rdquo;</p>
</blockquote>
<blockquote>
<p>SUBSYSTEM==&ldquo;usb&rdquo;, ATTR{idVendor}==&ldquo;29a9&rdquo;, ATTR{idProduct}==&ldquo;701b&rdquo;, MODE=&ldquo;0666&rdquo;, OWNER=&ldquo;your name&rdquo;</p>
</blockquote>
</li>
<li>
<p>打开用户目录下的.android/adb_usb.ini文件，将得到的Vendor ID添加到最后一行（是新起一行），比如T1的是0x29A9.</p>
</li>
<li>
<p>在命令行中重启adb：</p>
<ul>
<li>adb kill-server</li>
<li>adb start-server</li>
</ul>
</li>
</ul>
<h5 id="mac下-1">Mac下</h5>
<p>以T1为例，可以在终端下执行以下命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">echo</span> 0x29A9 &gt;&gt; ~/.android/adb_usb.ini
adb kill-server
adb start-server
</code></pre></div><h5 id="检查是否成功解决问题">检查是否成功解决问题</h5>
<p>执行命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">adb devices
</code></pre></div><p>如果能列出你的设备说明，就可以了。</p>
<h3 id="参考">参考</h3>
<ol>
<li><strong><a href="http://bbs.smartisan.com/thread-60797-1-1.html">Smartisan T1 配置 ADB 的方法</a></strong></li>
<li><strong><a href="http://my.oschina.net/u/698243/blog/139441">Mac下android手机的adb识别</a></strong></li>
<li><strong><a href="http://bbs.pcbeta.com/viewthread-1122886-1-1.html">解决Win8/8.1强制要求驱动签名而导致驱动安装失败的问题</a></strong></li>
</ol>]]></content>
		</item>
		
		<item>
			<title>Linux下Arduino USB to TTL 权限问题解决</title>
			<link>https://blog.5km.studio/2015/12/04/usbttlInLinux-Arduino/</link>
			<pubDate>Fri, 04 Dec 2015 10:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/12/04/usbttlInLinux-Arduino/</guid>
			<description>&lt;p&gt;在Linux中高高兴兴的安装完了Arduino，结果编译下载程序时发现竟然出错，不能下载程序，本文就来说一下这个问题。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>在Linux中高高兴兴的安装完了Arduino，结果编译下载程序时发现竟然出错，不能下载程序，本文就来说一下这个问题。</p>
<p>在linux下使用usbttl均会出现usb使用权限问题，下图是我在Ubuntu15.04中的问题截图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ivqks.png" alt="1"></p>
<p>可以看到问题是：<code>avrdude:ser_open():can't open device &quot;/dev/ttyUSB0&quot;:Permission denied</code></p>
<h3 id="解决方法">解决方法</h3>
<p>解决方法，我找到了两种，一个来自Arduino官网labs的<a href="http://labs.arduino.org/Arduino%20IDE%20on%20Linux-based%20OS">ARDUINO IDE ON LINUX-BASED OS</a>，一个来自文章<a href="http://blog.csdn.net/mtofum/article/details/50070543">让ubuntu串口和USB设备不用root权限访问</a>，经尝试均可解决，更喜欢用Arduino官网的解决方案。</p>
<h4 id="方法一">方法一</h4>
<ol>
<li>
<p>创建文件90-extraacl.rules，放到目录<code>/etc/udev/rulse.d/</code>下</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo vim /etc/udev/rules.d/90-etreacl.rules
</code></pre></div></li>
<li>
<p>粘贴以下内容：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nv">KERNEL</span><span class="o">=</span><span class="s2">&#34;ttyUSB[0-9]*&#34;</span>, <span class="nv">TAG</span><span class="o">+=</span><span class="s2">&#34;udev-acl&#34;</span>, <span class="nv">TAG</span><span class="o">+=</span><span class="s2">&#34;uaccess&#34;</span>, <span class="nv">OWNER</span><span class="o">=</span><span class="s2">&#34;&amp;lt;your_username&amp;gt;&#34;</span> 
<span class="nv">KERNEL</span><span class="o">=</span><span class="s2">&#34;ttyACM[0-9]*&#34;</span>, <span class="nv">TAG</span><span class="o">+=</span><span class="s2">&#34;udev-acl&#34;</span>, <span class="nv">TAG</span><span class="o">+=</span><span class="s2">&#34;uaccess&#34;</span>, <span class="nv">OWNER</span><span class="o">=</span><span class="s2">&#34;&amp;lt;your_username&amp;gt;&#34;</span>
</code></pre></div><p>其中OWNER的值是用户名，比如我的linux用户名是smslit，我就可以是下面的样子：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nv">KERNEL</span><span class="o">=</span><span class="s2">&#34;ttyUSB[0-9]*&#34;</span>, <span class="nv">TAG</span><span class="o">+=</span><span class="s2">&#34;udev-acl&#34;</span>, <span class="nv">TAG</span><span class="o">+=</span><span class="s2">&#34;uaccess&#34;</span>, <span class="nv">OWNER</span><span class="o">=</span><span class="s2">&#34;smslit&#34;</span>
<span class="nv">KERNEL</span><span class="o">=</span><span class="s2">&#34;ttyACM[0-9]*&#34;</span>, <span class="nv">TAG</span><span class="o">+=</span><span class="s2">&#34;udev-acl&#34;</span>, <span class="nv">TAG</span><span class="o">+=</span><span class="s2">&#34;uaccess&#34;</span>, <span class="nv">OWNER</span><span class="o">=</span><span class="s2">&#34;smslit&#34;</span>
</code></pre></div></li>
<li>
<p>开启权限，注销后有效，可执行下面命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">sudo usermod -a -G tty smslit
sudo usermod -a -G dialout smslit
</code></pre></div><p>**注：**smslit替换为你当前的用户名。</p>
</li>
</ol>
<h4 id="方法二">方法二</h4>
<p>从文章中我们知道ubuntu采用udev管理设备，因而插入设备的权限可以由udev的rules文件来定义，其实linux都是如此的。这里Arduino访问的硬件是USB转TTL串口，所以可以创建rules文件来解决，做法如下：</p>
<ul>
<li>
<p>在/etc/udev/rules.d/目录下新建一个文件，取名可以是90-tofu.rules
用vim创建文件，命令如下（需要用sudo的）：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">cd</span> /etc/udev/rules.d/
sudo vim 90-tofu.rules
</code></pre></div><p>这样我们就进入vim，按下<em>i</em>，进入编辑插入状态。</p>
</li>
<li>
<p>添加内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nv">SUBSYSTEMS</span><span class="o">==</span><span class="s2">&#34;usb-serial&#34;</span>, <span class="nv">KERNEL</span><span class="o">==</span><span class="s2">&#34;ttyUSB?&#34;</span>, <span class="nv">GROUP</span><span class="o">=</span><span class="s2">&#34;tofu&#34;</span>, <span class="nv">MODE</span><span class="o">=</span><span class="s2">&#34;0666&#34;</span>
</code></pre></div><p>按下 <em>ESC</em> 键，输入<code>:wq</code>，保存并退出。重新插拔Arduino或下载线。</p>
</li>
</ul>
<p>Arduino此时就有权限使用设备下载程序了。</p>]]></content>
		</item>
		
		<item>
			<title>ASCII字符的Arduino板图</title>
			<link>https://blog.5km.studio/2015/11/23/asciiBoardMap-Arduino/</link>
			<pubDate>Mon, 23 Nov 2015 22:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/11/23/asciiBoardMap-Arduino/</guid>
			<description>&lt;p&gt;在为Arduino写代码的时候，有时很头疼怎么更好的示意连线图呢，今天看Arduino官网博客时看到了一篇文章&lt;a href=&#34;https://blog.arduino.cc/2015/11/20/arduino-pinout-ascii-art-ready-to-go/&#34;&gt;ARDUINO PINOUT ASCII ART READY TO GO&lt;/a&gt;，用Ascii字符表示连线的话真的很形象。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>在为Arduino写代码的时候，有时很头疼怎么更好的示意连线图呢，今天看Arduino官网博客时看到了一篇文章<a href="https://blog.arduino.cc/2015/11/20/arduino-pinout-ascii-art-ready-to-go/">ARDUINO PINOUT ASCII ART READY TO GO</a>，用Ascii字符表示连线的话真的很形象。</p>
<p>我根据文中创意做了自己的文档化的板图，不过我做的都是基于ATmega328P芯片的，以后只需复制进注释中，更改添加一些字符就能使连线图文档化了，简单明了还直观。</p>
<h3 id="arduino-uno-r3">Arduino UNO r3</h3>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cm">/* Arduino UNO r3 板图
</span><span class="cm"> * 作者：十里（5km）
</span><span class="cm"> * 描述：可以用来示意Arduino连线
</span><span class="cm"> * 连线：
</span><span class="cm"> */</span>
                     <span class="o">+---+</span>                          <span class="o">+------+</span>
                <span class="o">+----|</span>   <span class="o">|--------------------------|</span>  <span class="n">USB</span> <span class="o">|-----+</span>
                <span class="o">|</span>    <span class="o">|</span><span class="n">PWR</span><span class="o">|</span>                          <span class="o">|</span><span class="n">______</span><span class="o">|</span>     <span class="o">|</span>
                <span class="o">|</span>    <span class="o">+---+</span>            <span class="n">GND</span><span class="o">/</span><span class="n">RST2</span> <span class="p">[</span> <span class="p">][</span> <span class="p">]</span>  <span class="n">A5</span><span class="o">/</span><span class="n">SCL</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span>                   <span class="n">MOSI2</span><span class="o">/</span><span class="n">SCK2</span> <span class="p">[</span> <span class="p">][</span> <span class="p">]</span>  <span class="n">A4</span><span class="o">/</span><span class="n">SDA</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span>                      <span class="mi">5</span><span class="n">V</span><span class="o">/</span><span class="n">MISO2</span><span class="p">[</span> <span class="p">][</span> <span class="p">]</span>    <span class="n">AREF</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span>                                         <span class="n">GND</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">N</span><span class="o">/</span><span class="n">C</span>                               <span class="n">SCK</span><span class="o">/</span><span class="mi">13</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">v</span><span class="p">.</span><span class="n">ref</span>                            <span class="n">MISO</span><span class="o">/</span><span class="mi">12</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">RST</span>      <span class="o">+----+</span>                  <span class="n">MOSI</span><span class="o">/</span><span class="mi">11</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">3</span><span class="n">V3</span>     <span class="o">-|</span>    <span class="o">|-</span>                      <span class="mi">10</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">5</span><span class="n">V</span>      <span class="o">-|</span> <span class="n">A</span>  <span class="o">|-</span>                       <span class="mi">9</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">GND</span>     <span class="o">-|</span> <span class="n">T</span>  <span class="o">|-</span>                       <span class="mi">8</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">GND</span>     <span class="o">-|</span> <span class="n">M</span>  <span class="o">|-</span>    <span class="n">ARDUINO</span>                 <span class="o">|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">Vin</span>     <span class="o">-|</span> <span class="n">E</span>  <span class="o">|-</span>           <span class="n">UNO_R3</span>      <span class="mi">7</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span>            <span class="o">-|</span> <span class="n">G</span>  <span class="o">|-</span>                       <span class="mi">6</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">A0</span>      <span class="o">-</span><span class="p">[</span> <span class="n">A</span>  <span class="p">]</span><span class="o">-</span>                       <span class="mi">5</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">A1</span>      <span class="o">-|</span>  <span class="mi">3</span> <span class="o">|-</span>                       <span class="mi">4</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">A2</span>      <span class="o">-|</span>  <span class="mi">2</span> <span class="o">|-</span>                  <span class="n">INT1</span><span class="o">/</span><span class="mi">3</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">A3</span>      <span class="o">-|</span>  <span class="mi">8</span> <span class="o">|-</span>   <span class="n">RST</span> <span class="n">SCK</span> <span class="n">MISO</span>   <span class="n">INT0</span><span class="o">/</span><span class="mi">2</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">A4</span><span class="o">/</span><span class="n">SDA</span>  <span class="o">-|</span>  <span class="n">P</span> <span class="o">|-</span>   <span class="n">GND</span> <span class="n">MOSI</span> <span class="mi">5</span><span class="n">V</span>     <span class="n">TX</span><span class="o">-&gt;</span><span class="mi">1</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">A5</span><span class="o">/</span><span class="n">SCL</span>  <span class="o">-|</span>    <span class="o">|-</span>   <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span>     <span class="n">RX</span><span class="o">&lt;-</span><span class="mi">0</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                <span class="o">|</span><span class="n">_</span>            <span class="o">+----+</span>    <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span>   <span class="n">__________</span><span class="o">/</span>
                   <span class="err">\</span><span class="n">________________________________</span><span class="o">/</span>
</code></pre></div><h3 id="官方版arduino-pro-mini">官方版Arduino Pro Mini</h3>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cm">/* 官方版Arduino Pro MINI 板图
</span><span class="cm"> * 作者：十里（5km）
</span><span class="cm"> * 描述：可以用来示意Arduino连线
</span><span class="cm"> * 连线：
</span><span class="cm"> */</span>
                     <span class="o">+---------------------------+</span>
                     <span class="o">|</span>  <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span>  <span class="o">|</span>
                     <span class="o">|</span>  <span class="n">BLK</span> <span class="n">GND</span> <span class="n">VCC</span> <span class="n">RXI</span> <span class="n">TXO</span> <span class="n">GRN</span>  <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">1</span><span class="o">/</span><span class="n">TXO</span>           <span class="n">RAW</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">0</span><span class="o">/</span><span class="n">RXI</span>           <span class="n">GND</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">RST</span>             <span class="n">RST</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">GND</span>             <span class="n">VCC</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">2</span>          <span class="n">A5</span><span class="p">[</span> <span class="p">]</span> <span class="n">A3</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">3</span>  <span class="o">-------</span> <span class="n">A4</span><span class="p">[</span> <span class="p">]</span> <span class="n">A2</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">4</span> <span class="o">|</span><span class="n">ATmega</span> <span class="o">|</span>      <span class="n">A1</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">5</span> <span class="o">|</span>   <span class="mi">328</span><span class="n">P</span><span class="o">|</span>      <span class="n">A0</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">6</span>  <span class="o">-------</span> <span class="n">A7</span><span class="p">[</span> <span class="p">]</span> <span class="mi">13</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">7</span>          <span class="n">A6</span><span class="p">[</span> <span class="p">]</span> <span class="mi">12</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">8</span>                <span class="mi">11</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">9</span>                <span class="mi">10</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                     <span class="o">|</span>      <span class="n">Arduino</span> <span class="n">Pro</span> <span class="n">MINI</span>     <span class="o">|</span>
                     <span class="o">+---------------------------+</span>
</code></pre></div><h3 id="改进版arduino-pro-mini">改进版Arduino Pro Mini</h3>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cm">/* 改进版Arduino Pro MINI 板图
</span><span class="cm"> * 作者：十里（5km）
</span><span class="cm"> * 描述：可以用来示意Arduino连线
</span><span class="cm"> * 连线：
</span><span class="cm"> */</span>
                     <span class="o">+---------------------------+</span>
                     <span class="o">|</span>  <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span>  <span class="o">|</span>
                     <span class="o">|</span>  <span class="n">GND</span> <span class="n">GND</span> <span class="n">VCC</span> <span class="n">RXD</span> <span class="n">TXD</span> <span class="n">DTR</span>  <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">RAW</span>             <span class="n">TXD</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">GND</span>  <span class="n">Arduino</span>    <span class="n">RXD</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">RST</span>    <span class="n">Pro</span> <span class="n">MINI</span> <span class="n">RST</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">VCC</span>             <span class="n">GND</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">A3</span>    <span class="o">-------</span>     <span class="mi">2</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">A2</span>   <span class="o">|</span><span class="n">ATmega</span> <span class="o">|</span>    <span class="mi">3</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">A1</span>   <span class="o">|</span>   <span class="mi">328</span><span class="n">P</span><span class="o">|</span>    <span class="mi">4</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">A0</span>    <span class="o">-------</span>     <span class="mi">5</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">13</span>                <span class="mi">6</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">12</span>                <span class="mi">7</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">11</span>  <span class="n">A6</span>   <span class="n">MIS</span> <span class="n">SCK</span>  <span class="mi">8</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">10</span>  <span class="n">A4</span>   <span class="n">VCC</span> <span class="n">MOS</span>  <span class="mi">9</span><span class="p">[</span> <span class="p">]</span><span class="o">~|</span>
                     <span class="o">|</span>    <span class="n">A7</span><span class="p">[</span> <span class="p">][</span> <span class="p">]</span> <span class="p">[</span> <span class="p">][</span> <span class="p">][</span> <span class="p">]</span><span class="n">RST</span>  <span class="o">|</span>
                     <span class="o">|</span>    <span class="n">A5</span><span class="p">[</span> <span class="p">][</span> <span class="p">]</span> <span class="p">[</span> <span class="p">][</span> <span class="p">][</span> <span class="p">]</span><span class="n">GND</span>  <span class="o">|</span>
                     <span class="o">+---------------------------+</span> 
</code></pre></div><h3 id="arduino-nano">Arduino Nano</h3>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cm">/* Arduino Nano 板图
</span><span class="cm"> * 作者：十里（5km）
</span><span class="cm"> * 描述：可以用来示意Arduino连线
</span><span class="cm"> * 连线：
</span><span class="cm"> */</span>
                     <span class="o">+----------------------------+</span>
                     <span class="o">|</span>     <span class="n">ICSP</span><span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span>        <span class="o">|</span>
                     <span class="o">|</span>         <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span> <span class="p">[</span> <span class="p">]</span>        <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">TX1</span>  <span class="mi">5</span><span class="n">V</span> <span class="n">MOSI</span> <span class="n">GND</span> <span class="n">Vin</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">RX0</span> <span class="n">MISO</span> <span class="n">SCK</span> <span class="n">RST</span> <span class="n">GND</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">RST</span>              <span class="n">RST</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="n">GND</span>               <span class="mi">5</span><span class="n">V</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">2</span>                 <span class="n">A7</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">3</span>     <span class="o">-------</span>     <span class="n">A6</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">4</span>    <span class="o">|</span><span class="n">ATmega</span> <span class="o">|</span>    <span class="n">A5</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">5</span>    <span class="o">|</span>   <span class="mi">328</span><span class="n">P</span><span class="o">|</span>    <span class="n">A4</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">6</span>     <span class="o">-------</span>     <span class="n">A3</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">7</span>                 <span class="n">A2</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">8</span>                 <span class="n">A1</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">9</span>   <span class="n">Arduino</span> <span class="n">Nano</span>  <span class="n">A0</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">10</span>               <span class="n">REF</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|~</span><span class="p">[</span> <span class="p">]</span><span class="mi">11</span>    <span class="o">+------+</span>   <span class="mi">3</span><span class="n">V3</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">|</span> <span class="p">[</span> <span class="p">]</span><span class="mi">12</span>    <span class="o">|</span> <span class="n">USB</span>  <span class="o">|</span>    <span class="mi">13</span><span class="p">[</span> <span class="p">]</span> <span class="o">|</span>
                     <span class="o">+----------|</span>      <span class="o">|----------+</span> 
                                <span class="o">+------+</span>
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>零欧姆电阻在电路中的作用</title>
			<link>https://blog.5km.studio/2015/11/20/0ohmFunction-elec/</link>
			<pubDate>Fri, 20 Nov 2015 22:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/11/20/0ohmFunction-elec/</guid>
			<description>&lt;p&gt;心中一直有个疑问，电路中为啥要弄上个0欧姆的电阻呢，所以就网上搜索请教了一下，看到**&lt;a href=&#34;http://www.eepw.com.cn/article/270119.htm&#34;&gt;零欧姆电阻的十二种作用&lt;/a&gt;**又长知识了，感谢文章的作者。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>心中一直有个疑问，电路中为啥要弄上个0欧姆的电阻呢，所以就网上搜索请教了一下，看到**<a href="http://www.eepw.com.cn/article/270119.htm">零欧姆电阻的十二种作用</a>**又长知识了，感谢文章的作者。</p>
<p>零欧姆电阻又称为跨接电阻器，是一种特殊用途的电阻，0欧姆电阻的并非真正的阻值为零(那是超导体干的事情)，正因为有阻值，也就和常规贴片电阻一样有误差精度这个指标。</p>
<h4 id="零欧姆电阻的作用如下">零欧姆电阻的作用如下：</h4>
<ol>
<li>
<p>在电路中没有任何功能，只是在PCB上为了调试方便或兼容设计等原因。</p>
</li>
<li>
<p>可以做跳线用，如果某段线路不用，直接不贴该电阻即可(不影响外观)</p>
</li>
<li>
<p>在匹配电路参数不确定的时候，以0欧姆代替，实际调试的时候，确定参数，再以具体数值的元件代替。</p>
</li>
<li>
<p>想测某部分电路的耗电流的时候，可以去掉0ohm电阻，接上电流表，这样方便测耗电流。</p>
</li>
<li>
<p>在布线时，如果实在布不过去了，也可以加一个0欧的电阻</p>
</li>
<li>
<p>在高频信号下，充当电感或电容。(与外部电路特性有关)电感用，主要是解决EMC问题。如地与地，电源和IC Pin间</p>
</li>
<li>
<p>单点接地(指保护接地、工作接地、直流接地在设备上相互分开，各自成为独立系统。)</p>
</li>
<li>
<p>熔丝作用</p>
</li>
<li>
<p>拟地和数字地单点接地</p>
<ul>
<li>
<p>只要是地，最终都要接到一起，然后入大地。如果不接在一起就是&quot;浮地&quot;，存在压差，容易积累电荷，造成静电。地是参考0电位，所有电压都是参考地得出的，地的标准要一致，故各种地应短接在一起。人们认为大地能够吸收所有电荷，始终维持稳定，是最终的地参考点。虽然有些板子没有接大地，但发电厂是接大地的，板子上的电源最终还是会返回发电厂入地。如果把模拟地和数字地大面积直接相连，会导致互相干扰。不短接又不妥，理由如上有四种方法解决此问题：</p>
<ul>
<li>(1)用磁珠连接;</li>
<li>(2)用电容连接;</li>
<li>(3)用电感连接;</li>
<li>(4)用0欧姆电阻连接。</li>
</ul>
</li>
<li>
<p>磁珠的等效电路相当于带阻限波器，只对某个频点的噪声有显著抑制作用，使用时需要预先估计噪点频率，以便选用适当型号。对于频率不确定或无法预知的情况，磁珠不合。电容隔直通交，造成浮地。电感体积大，杂散参数多，不稳定。</p>
</li>
</ul>
</li>
<li>
<p>跨接时用于电流回路</p>
<p>当分割电地平面后，造成信号最短回流路径断裂，此时，信号回路不得不绕道，形成很大的环路面积，电场和磁场的影响就变强了，容易干扰/被干扰。在分割区上跨接0欧电阻，可以提供较短的回流路径，减小干扰。</p>
</li>
<li>
<p>配置电路</p>
<p>一般，产品上不要出现跳线和拨码开关。有时用户会乱动设置，易引起误会，为了减少维护费用，应用0欧电阻代替跳线等焊在板子上。空置跳线在高频时相当于天线，用贴片电阻效果好。</p>
</li>
<li>
<p>其他用途</p>
</li>
</ol>
<ul>
<li>布线时跨线;</li>
<li>调试/测试用;</li>
<li>临时取代其他贴片器件;</li>
<li>作为温度补偿器件;</li>
<li>更多时候是出于EMC对策的需要。另外，0欧姆电阻比过孔的寄生电感小，而且过孔还会影响地平面(因为要挖孔)。</li>
</ul>
<p>还有就是不同尺寸0欧电阻允许通过电流不同，一般0603的1A，0805的2A，所以不同电流会选用不同尺寸的还有就是为磁珠、电感等预留位置时，得根据磁珠、电感的大小还做封装，所以0603、0805等不同尺寸的都有了。</p>]]></content>
		</item>
		
		<item>
			<title>iOS开发学习——解决Xcode7 Swift2中odjc的函数重载问题</title>
			<link>https://blog.5km.studio/2015/11/18/overloadingSwift-Develop/</link>
			<pubDate>Wed, 18 Nov 2015 22:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/11/18/overloadingSwift-Develop/</guid>
			<description>&lt;p&gt;最近在学习斯坦福大学白胡子老头去年的iOS8的开发教程，跟着他的视频教程走，刚开始不久就发现自己栽进了坑里，同样的代码，为啥我的就会报错呢：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/5djgg.png&#34; alt=&#34;1&#34;&gt;&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>最近在学习斯坦福大学白胡子老头去年的iOS8的开发教程，跟着他的视频教程走，刚开始不久就发现自己栽进了坑里，同样的代码，为啥我的就会报错呢：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/5djgg.png" alt="1"></p>
<p>错误内容是：</p>
<blockquote>
<p>/Users/smslit/Documents/workspace/iOS/learn/Calculator/Calculator/
ViewController.swift:55:10: Method &lsquo;performOperation&rsquo; with Objective-C selector &lsquo;performOperation:&rsquo; conflicts with previous declaration with the same Objective-C selector</p>
</blockquote>
<p>最后在<a href="http://www.cocoachina.com/bbs/read.php?tid=297461#1290696">新手求助：同一个名下两种功能的func，是不是不能自动判断了</a>找到了原因：</p>
<p>这是因为你的viewcontroller 继承了UIViewController.而UIViewController 继承自oc的NSObject. 在swift 中被修饰成@objc class. 那么就必须要遵循oc的selector，在oc中是不支持方法重载的。所以会报上面的错误。这跟使用的Xcode版本有关，白胡子老头使用的版本较低，而我使用的是Xcode7，已经是Swift2了，与之前有好多不同的地方。</p>
<p>在<a href="http://stackoverflow.com/questions/29457720/compiler-error-method-with-objective-c-selector-conflicts-with-previous-declara">Compiler error: Method with Objective-C selector conflicts with previous declaration with the same Objective-C selector</a>找到了解决办法：</p>
<p>在Xcode7的Swift2中有两种解决方法：</p>
<ul>
<li>
<p>一种是使用@objc(newNameMethod:)，如下</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">methodOne</span><span class="p">(</span><span class="n">par1</span><span class="p">,</span> <span class="n">par2</span><span class="p">)</span> <span class="p">{...}</span>

<span class="kr">@objc</span><span class="p">(</span><span class="n">methodTow</span><span class="p">:)</span>
<span class="kd">func</span> <span class="nf">methodOne</span><span class="p">(</span><span class="n">par1</span><span class="p">)</span> <span class="p">{...}</span>
</code></pre></div></li>
<li>
<p>另一种是使用 @nonobjc，如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">methodOne</span><span class="p">()</span> <span class="p">{...}</span>

<span class="p">@</span><span class="n">nonobjc</span>
<span class="kd">func</span> <span class="nf">methodOne</span><span class="p">()</span> <span class="p">{...}</span>
</code></pre></div></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>Mac OSX下Sublime Text配置使用ctags实现代码跳转</title>
			<link>https://blog.5km.studio/2015/11/14/macSTctags-Develop/</link>
			<pubDate>Sat, 14 Nov 2015 09:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/11/14/macSTctags-Develop/</guid>
			<description>&lt;p&gt;ctags是一款实现代码跳转的插件，可以提高查看代码的效率，开发尽管可能有IDE，IDE中有代码跳转，但有的时候还是不想打开IDE的或者本身就没IDE，所以有必要安装ctags，本文就介绍如何在mac osx下的sublime text3中使用ctags。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>ctags是一款实现代码跳转的插件，可以提高查看代码的效率，开发尽管可能有IDE，IDE中有代码跳转，但有的时候还是不想打开IDE的或者本身就没IDE，所以有必要安装ctags，本文就介绍如何在mac osx下的sublime text3中使用ctags。</p>
<h3 id="st安装ctags插件">ST安装ctags插件</h3>
<p>本文就不赘述Sublime Text3的安装了，可以参考**<a href="http://www.smslit.top/develop/2015/08/19/sublimeText.html">Sublime Text 3安装及简单配置</a>**进行安装。那么进入正题，这里认为ST已经装好了package control。</p>
<ul>
<li>
<p>快捷键<code>cmd+shift+p</code>呼出文本框中输入<strong>Package Control</strong>或者菜单栏-&gt;Sublime Text-&gt;Preferences-&gt;Package Control，就会出现类似下面的输入框。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/p28wf.png" alt="1"></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/hhso7.png" alt="2"></p>
</li>
<li>
<p>选择<strong>Install Package</strong>，便会联网获取插件列表，所以得稍等一会，完成后便会显示插件列表：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/t8rya.png" alt="3"></p>
</li>
<li>
<p>列表上面的文本框中输入ctags，便会找到CTags插件，选中敲回车键或者鼠标单击，就会联网下载安装插件了：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/bbui3.png" alt="4"></p>
</li>
<li>
<p>如果右击左边栏中文件夹，出现<strong>CTags:Rebuild Tags</strong>这一项，说明已经安装成功：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ut3sv.png" alt="5"></p>
</li>
</ul>
<h3 id="安装ctags">安装ctags</h3>
<p>虽然已经安装了ST的CTags插件，但是ctags还没有安装，可以去sourceforge下载<strong>ctags-5.8.tar.gz</strong>，也可以<a href="http://pan.baidu.com/s/1o6GOW8E">点我</a>去百度云下载。</p>
<ul>
<li>
<p>假设下载到<code>~/Downloads</code>目录下，打开终端<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/xmvvj.png" alt="6">，敲一下命令进入<code>Downloads</code>:</p>
<p><strong>cd ~/Downloads</strong></p>
</li>
<li>
<p>解压<strong>ctags-5.8.tar.gz</strong>：</p>
<p><strong>tar xzvf ctags-5.8.tar.gz</strong></p>
</li>
<li>
<p>进入解压目录：</p>
<p><strong>cd ctags-5.8</strong></p>
</li>
<li>
<p>编译安装：</p>
<p><strong>./configure</strong></p>
<p><strong>make</strong></p>
<p><strong>sudo make install</strong></p>
</li>
</ul>
<h3 id="配置st的ctags插件">配置ST的CTags插件</h3>
<p>需要配置一下CTags插件，更改一下默认配置，主要修改其中ctags目录，因为配置中没有写入ctags的目录。</p>
<ul>
<li>
<p>打开 菜单栏-&gt;Sublime Text-&gt;Preferences-&gt;Package Settings-&gt;CTags-&gt;Settings-Default/Settings-User这两个文件：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/w3y8e.png" alt="7"></p>
</li>
<li>
<p>将Settings-Default中的内容全选复制到Settings-User文件中，并将其中<strong>Command</strong>的值设置为：<strong>/usr/local/bin/ctags</strong>:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/1jmh5.png" alt="8"></p>
</li>
</ul>
<h3 id="测试">测试</h3>
<p>随便找到一个含有c代码的工程目录，用ST打开，我打开的是我的一个BLE开发的工程<strong>DJB001_150509_1700V1.14</strong>。</p>
<ul>
<li>
<p>右击ST中左边栏的文件夹，出现<strong>CTags：Rebuild Tags</strong>，点击会生成tags文件（这个文件就是函数、变量、宏定义等的 索引文件）：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/3qbdg.png" alt="9"></p>
</li>
<li>
<p>打开工程文件的DJB001_150509_1700V1.4-&gt;Projects-&gt;ble-&gt;CC2541F128-&gt;Source-&gt;OSAL_SimpleBLEPeripheral.c文件，_尝试跳转，按下<strong>shift+ctrl</strong>，鼠标左键点击<strong>SimpleBLEPeripheral_Init(taskID++)</strong> ：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/u3fkx.png" alt="10"></p>
</li>
<li>
<p>就会调转到<strong>SimpleBLEPeripheral_Init</strong>d的定义：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/i7ykk.png" alt="11"></p>
</li>
<li>
<p>然后<strong>Shift+Ctrl+鼠标右键单击</strong>，便会调回刚才的位置。</p>
<p>可以在菜单栏<strong>Sublime Text-&gt;Preferences-&gt;Package Settings-&gt;CTags</strong>下找到快捷键和鼠标操作的设定，根据自己喜欢在用户设置文件里更改就可以。</p>
</li>
</ul>]]></content>
		</item>
		
		<item>
			<title>Arduino IDE 1.6.6新特性之Serial Plotter</title>
			<link>https://blog.5km.studio/2015/11/04/serialplotter-arduino/</link>
			<pubDate>Wed, 04 Nov 2015 22:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/11/04/serialplotter-arduino/</guid>
			<description>&lt;p&gt;今天去Arduino官网看了一下，发现发布了Arduino IDE1.6.6，看介绍发现增添了好多新特性，于是乎下载下来玩耍了一番，觉得比较有意思的是Serial Plotter，感觉这一功能的增加对于看数据变化曲线方便了好多，不用开其他软件直接用Serial Plotter就可以实现。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>今天去Arduino官网看了一下，发现发布了Arduino IDE1.6.6，看介绍发现增添了好多新特性，于是乎下载下来玩耍了一番，觉得比较有意思的是Serial Plotter，感觉这一功能的增加对于看数据变化曲线方便了好多，不用开其他软件直接用Serial Plotter就可以实现。</p>
<h3 id="serial-plotter">Serial Plotter</h3>
<h4 id="硬件">硬件：</h4>
<ul>
<li>Arduino Pro Mini</li>
<li>USB to TTL（PL2303的）</li>
</ul>
<p>说明：</p>
<p>USB转TTL用来为Arduino Pro Mini下载程序.</p>
<h4 id="测试sketch">测试sketch</h4>
<p>官方的介绍中是下面的描述：</p>
<blockquote>
<p>Serial plotter: you can now plot your data in realtime, as easy as writing Serial.println(analogRead(A0)) inside your loop</p>
</blockquote>
<p>大概的意思是说：现在可以实时绘制数据了，只需要在Loop中加入Serial.println()就可以。</p>
<h5 id="实现正弦函数">实现正弦函数</h5>
<p>所以我编写一个测试sketch，实现正弦函数数据的绘制：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">115200</span><span class="p">);</span>
    <span class="k">while</span><span class="p">(</span><span class="o">!</span><span class="n">Serial</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">double</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;=</span><span class="mi">2</span><span class="o">*</span><span class="mf">3.1415926</span><span class="p">;</span><span class="n">i</span><span class="o">=</span><span class="n">i</span><span class="o">+</span><span class="mf">0.05</span><span class="o">*</span><span class="mf">3.1415926</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">sin</span><span class="p">(</span><span class="n">i</span><span class="p">));</span>
        <span class="n">delay</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><h4 id="设置arduino">设置Arduino</h4>
<p>板子选<code>Arduino Pro Mini</code>，处理器选择<code>ATmega328（5v，16MHz）</code>，串口选择<code>/dev/cu.usbserial</code></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/lr7ad.png" alt="1"></p>
<h4 id="serial-plotter-1">Serial Plotter</h4>
<p>编译下载，打开<code>Tools</code>-&gt;<code>Serial Plotter</code>。</p>
<p>编译sketch并下载到板子上后，可以通过菜单栏Tools找到Serial Plotter，点击打开：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/yr9hv.png" alt="2"></p>
<p>选择对应sketch的波特率，就会看到下面的样子：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/1kkw0.gif" alt="3"></p>
<h4 id="绘制多值曲线">绘制多值曲线</h4>
<p>上面的例子是利用Serial Plotter工具绘制单值曲线的，其实这个工具还支持多个值绘制多条曲线，只需在每个值之间加上逗号，这里要注意的是，只有最后一个值用<strong>println</strong>函数，前面的值用<strong>print</strong>函数。</p>
<p>测试代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">115200</span><span class="p">);</span>
    <span class="k">while</span><span class="p">(</span><span class="o">!</span><span class="n">Serial</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">double</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;=</span><span class="mi">2</span><span class="o">*</span><span class="mf">3.1415926</span><span class="p">;</span><span class="n">i</span><span class="o">=</span><span class="n">i</span><span class="o">+</span><span class="mf">0.05</span><span class="o">*</span><span class="mf">3.1415926</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">sin</span><span class="p">(</span><span class="n">i</span><span class="p">));</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="sc">&#39;,&#39;</span><span class="p">);</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">cos</span><span class="p">(</span><span class="n">i</span><span class="p">));</span>
        <span class="n">delay</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>然后下载程序到Arduino，用Serial  Plotter查看可以看到两条三角函数曲线。</p>
<h3 id="增加的新特性">增加的新特性</h3>
<p>可以看官网上的介绍<a href="https://blog.arduino.cc/2015/11/03/arduino-ide-1-6-6-released-and-available-for-download/"><strong>ARDUINO IDE 1.6.6 RELEASED AND AVAILABLE FOR DOWNLOAD</strong></a></p>
<ol>
<li><strong>久等全新的Arduino-builder</strong>这是一个纯命令行的工具，它用于处理代码、解决库依赖和设置编译单元。它也可以作为一个独立与IDE的程序进行使用。</li>
<li><strong>Pluggable USB core</strong>在不需要改变内核的情况下，你的Arduino可以变成各式各样的USB设备，这得力于新的模块架构。同时基于新的子系统的库已经诞生。</li>
<li><strong>Serial Plotter</strong>只需简单的在循环中使用Serial.println()就可以实时绘制数据了。</li>
<li>为库文件的开发者提供了新的好玩意，如开发库的时候解锁例程和可以选择连接到存档。</li>
<li>优化了的<strong>Arduino ISP</strong>例程提升了许多，现在你可以用<strong>任何</strong>其他板子（包括第三方的）为AVR片子烧写程序了。</li>
<li>如果库文件和内核文件有可用更新，库文件管理器会弹出提示框提醒，这样再也不用担心代码过时了。</li>
<li>还有很多bug的修复以及参考文档的完善。</li>
</ol>]]></content>
		</item>
		
		<item>
			<title>USBasp为Arduino板子烧写bootloader</title>
			<link>https://blog.5km.studio/2015/11/03/usbasp-arduino/</link>
			<pubDate>Tue, 03 Nov 2015 17:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/11/03/usbasp-arduino/</guid>
			<description>&lt;p&gt;在Arduino使用过程中有时候会造成bootloader的损坏，有时候我们还会做Arduino的最小系统板，那我们都需要为Arduino下载bootloader，本文将介绍用USBasp下载bootloader。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>在Arduino使用过程中有时候会造成bootloader的损坏，有时候我们还会做Arduino的最小系统板，那我们都需要为Arduino下载bootloader，本文将介绍用USBasp下载bootloader。</p>
<h2 id="试验硬件清单">试验硬件清单</h2>
<ul>
<li>USBasp</li>
<li>Arduino Pro Mini</li>
</ul>
<p><strong>说明：</strong></p>
<ol>
<li>
<p>我的USBasp是某宝上淘的，不贵，就是下面的样子，接口是10pin的：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/3fm5n.jpg" alt="1"></p>
<p>10pin的ISP接口图如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/029j1.jpg" alt="2"></p>
</li>
<li>
<p>这里我试验的是Arduino Pro Mini（5V，16MHz的版本），如下图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/rbitt.jpg" alt="4"></p>
<p>6pin的ISP接口图如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/oftpi.jpg" alt="3"></p>
</li>
</ol>
<h2 id="详细">详细</h2>
<h3 id="连线">连线</h3>
<p>直接用杜邦线将Arduino上的6Pin的ISP与USBasp上的10Pin的ISP对应管脚连接起来，就像这样：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2mq1l.jpg" alt="5"></p>
<p>然后连接到电脑（我用的这个USBasp是免驱的）。</p>
<h3 id="菜单栏tools下设置arduino-ide">菜单栏Tools下，设置Arduino IDE</h3>
<p>我的系统是Mac OSX，操作在win下应该是一样的。</p>
<h4 id="设置board为arduino-pro-mini">设置Board为Arduino Pro Mini</h4>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/27sxf.png" alt="6"></p>
<h4 id="设置processor为atmega285vv16mhz">设置Processor为ATmega28（5VV，16MHz）</h4>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/zsg2d.png" alt="7"></p>
<h4 id="设置programmer为usbasp">设置Programmer为USBasp</h4>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/5rnfx.png" alt="8"></p>
<h3 id="菜单栏tools-burn-bootloader">菜单栏Tools-&gt;Burn Bootloader</h3>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2nwji.png" alt="9"></p>
<p>如果烧写成功，Arduino IDE的Console下会出现提示：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/5wvw5.png" alt="10"></p>
<h4 id="烧写成功后板上的led灯就会闪动">烧写成功后，板上的Led灯就会闪动。</h4>]]></content>
		</item>
		
		<item>
			<title>改造hdmi转vga线让Macmini成功点亮VGA接口显示器</title>
			<link>https://blog.5km.studio/2015/11/02/hdmi-life/</link>
			<pubDate>Mon, 02 Nov 2015 17:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/11/02/hdmi-life/</guid>
			<description>&lt;p&gt;最近几天终于下定决心京东白条分期买了心仪已久的mac电脑，其实macbook pro才是我最想得到的，没办法，一个穷字道尽了多少辛酸与苦楚，虽然买的是mac mini但也是很心疼的，因为在公司申请了显示器，所以还另买了一个21.5寸的显示器放在宿舍，计划是每天背着宿舍与实习公司来回跑，哈哈。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>最近几天终于下定决心京东白条分期买了心仪已久的mac电脑，其实macbook pro才是我最想得到的，没办法，一个穷字道尽了多少辛酸与苦楚，虽然买的是mac mini但也是很心疼的，因为在公司申请了显示器，所以还另买了一个21.5寸的显示器放在宿舍，计划是每天背着宿舍与实习公司来回跑，哈哈。</p>
<p>宿舍显示器有hdmi接口，Mac mini有hdmi接口，所以买根hdmi线就OK了，但是在公司就麻烦了，显示器只有VGA接口，没办法就买了一根hdmi转vga的转接线，到了才悲剧了，试了一下屏幕竟然提示没有信号，愤怒的我打开这个东西的购买链接，发现在宝贝详情中有提示，mac mini要买带有电源接口线的转接线才可用，可是我买的是普通的，什么音频、电源接口都没有的，因为普通的最便宜，那怎么办？</p>
<p>本文重点来了，我灵光一闪，就把买到的转接拆开了一看，对比了一下网上有电源接口的，豁然开朗，在电源接口的位置，普通转接头留有焊盘，如下图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/xyj0d.png" alt="1"></p>
<p>我在想能否自己焊上一根USB线试一下，正好有闲置的USB线，说干就干，用万用表测试了一下，有两根较细的焊盘是需要焊的，一个+5V，一个GND，对应USB的红线（正）和黑线（负）进行焊接，如下图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/vot9f.png" alt="2"></p>
<p>如果焊接没问题，那就将HDMI头和USB头插到Mac mini上，一定可以点亮显示器了，我就成功了，哈哈！</p>]]></content>
		</item>
		
		<item>
			<title>Git之SSH密钥实现免密码</title>
			<link>https://blog.5km.studio/2015/11/02/sshkey-git/</link>
			<pubDate>Mon, 02 Nov 2015 12:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/11/02/sshkey-git/</guid>
			<description>&lt;p&gt;SSH密钥用于免密码git操作时验证信任的客户端。下面将说一下生成SSH密钥和将SSH密钥添加到对应git账户（不特指github，其他平台也可以如coding）的步骤。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>SSH密钥用于免密码git操作时验证信任的客户端。下面将说一下生成SSH密钥和将SSH密钥添加到对应git账户（不特指github，其他平台也可以如coding）的步骤。</p>
<p>本文以<strong>mac osx</strong>系统为例。</p>
<h2 id="检查">检查</h2>
<p>检查是否存在SSH密钥</p>
<p>首先，我们应该检查一下计算机中是否已经存在SSH密钥，打开终端输入：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ls -al ~/.ssh
<span class="c1"># 如果有文件存在，列出.ssh文件夹下的文件</span>
</code></pre></div><p>默认会有以下文件：</p>
<ul>
<li>id_dsa.pub</li>
<li>id_ecdsa.pub</li>
<li>id_ed25519.pub</li>
<li>id_rsa.pub</li>
</ul>
<p><strong>注：</strong></p>
<ul>
<li>如果已经存在了SSH公钥和私钥，可以直接跳过第二、三步。</li>
<li>如果出现错误<code>~/.ssh doesn't exist</code>，不用担心，只需要在~目录下创建.ssh目录即可<code>mkdir ~/.ssh</code></li>
</ul>
<h2 id="生成新的ssh密钥">生成新的SSH密钥</h2>
<h3 id="ssh-keygen">ssh-keygen</h3>
<p>在终端中执行一下命令，把邮箱换成自己的邮箱即可</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ssh-keygen -t rsa -b <span class="m">4096</span> -C <span class="s2">&#34;your_email@example.com&#34;</span>
<span class="c1"># Creates a new ssh key, using the provided email as a label</span>
<span class="c1"># Generating public/private rsa key pair.</span>
</code></pre></div><h3 id="一路回车">一路回车</h3>
<p>Github官方强烈建议保持默认设置，所以下面出现的三个需要输入的时候，直接回车就好：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ Enter file in which to save the key <span class="o">(</span>/Users/you/.ssh/id_rsa<span class="o">)</span>: <span class="o">[</span>Press enter<span class="o">]</span>
$ Enter passphrase <span class="o">(</span>empty <span class="k">for</span> no passphrase<span class="o">)</span>: <span class="o">[</span>Type a passphrase<span class="o">]</span>
$ Enter same passphrase again: <span class="o">[</span>Type passphrase again<span class="o">]</span>
</code></pre></div><h3 id="结果">结果</h3>
<p>成功完成上述操作后，就会出现下面类似的样子：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">Your identification has been saved in /Users/you/.ssh/id_rsa.
Your public key has been saved in /Users/you/.ssh/id_rsa.pub.
The key fingerprint is:
01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db your_email@example.com
</code></pre></div><h2 id="向ssh-agent中添加ssh密钥">向ssh-agent中添加SSH密钥</h2>
<h3 id="验证ssh-agent可用">验证ssh-agent可用</h3>
<p>确保ssh-agent是可用的：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="c1"># start the ssh-agent in the background</span>
$ <span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span>ssh-agent -s<span class="k">)</span><span class="s2">&#34;</span>
<span class="c1"># Agent pid 43686</span>
</code></pre></div><h3 id="添加ssh密钥">添加SSH密钥</h3>
<p>将ssh密钥添加到ssh-agent中：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ssh-add ~/.ssh/id_rsa
</code></pre></div><h2 id="git账户添加ssh密钥">git账户添加SSH密钥</h2>
<p>首先执行下面命令，将刚生成的SSH密钥拷贝到系统粘贴板：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ pbcopy &lt; ~/.ssh/id_rsa.pub
<span class="c1"># Copies the contents of the id_rsa.pub file to your clipboard</span>
</code></pre></div><p><strong>注：</strong></p>
<p>可以自己到HOME的.ssh目录下拷贝is_rsa.pub的内容，但一定注意不要有新行或空格，上述命令方式比较安全！</p>
<p>以github为例讲解向账户中添加SSH密钥，coding是类似的。</p>
<h3 id="进入settings">进入settings</h3>
<p>点击自己github首页右上角的头像，选择settings（设置）</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/w7xck.png" alt="1"></p>
<h3 id="选择ssh-keys">选择<strong>SSH Keys</strong></h3>
<p>在左边栏用户设置中选择<strong>SSH Keys</strong>：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/9cr7g.png" alt="2"></p>
<h3 id="添加ssh">添加<strong>SSH</strong></h3>
<p>点击右上角的<strong>Add SSH Key</strong>。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/oz68n.png" alt="3"></p>
<h3 id="输入ssh密钥">输入SSH密钥</h3>
<p>在下面跳出的输入框中分别输入用于区分SSH密钥的名称和贴入已经复制到系统粘贴板上的SSH密钥：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/rif0x.png" alt="4"></p>
<h3 id="添加即可">添加即可</h3>
<p>点击<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/42kz2.png" alt="5">，然后输入账户密码完成添加。</p>
<h2 id="测试ssh连接">测试SSH连接</h2>
<p>为了确保我们之前做的一切都是可用的，现在可以尝试SSH连接Github。</p>
<h3 id="ssh--t">ssh -T</h3>
<p>打开终端，输入命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ ssh -T git@github.com
</code></pre></div><h3 id="警告">警告</h3>
<p>可能会看到下面的警告：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">The authenticity of host <span class="s1">&#39;github.com (207.97.227.239)&#39;</span> can<span class="err">&#39;</span>t be established.
<span class="c1"># RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.</span>
<span class="c1"># Are you sure you want to continue connecting (yes/no)?</span>
</code></pre></div><p>输入<code>yes</code>继续：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">Hi username! You<span class="err">&#39;</span>ve successfully authenticated, but GitHub does not
<span class="c1"># provide shell access.</span>
</code></pre></div><h3 id="注意">注意</h3>
<p>如果上面的用户名是你的，就说明已经把SSH密钥绑定好了，以后可以用ssh免密码进行git推送了。</p>
<h2 id="参考"><strong>参考：</strong></h2>
<ul>
<li><strong><a href="https://help.github.com/articles/generating-ssh-keys/">Generating SSH keys</a></strong></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>BLE开发笔记——读取片上温度传感器数据</title>
			<link>https://blog.5km.studio/2015/10/30/temperature-BLE/</link>
			<pubDate>Fri, 30 Oct 2015 17:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/30/temperature-BLE/</guid>
			<description>&lt;p&gt;已经完成了串口打印就很方便调试了，学习其他外设的使用也更方便。本文就记录了温度传感器ADC采样的使用学习。片上的温度传感器输出的是模拟信号，所以需要配置AD采样对信号进行采集，然后进行换算获得温度。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>已经完成了串口打印就很方便调试了，学习其他外设的使用也更方便。本文就记录了温度传感器ADC采样的使用学习。片上的温度传感器输出的是模拟信号，所以需要配置AD采样对信号进行采集，然后进行换算获得温度。</p>
<h2 id="结构">结构</h2>
<p>可以从<a href="http://pan.baidu.com/s/1kTwBhpt">CC2541用户手册</a>中找到ADC的结构图如下，TMP_SENSOR就是温度传感器，ADC是Sigma-Delta模数转换器：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/9jxzp.png" alt="1"></p>
<h2 id="寄存器">寄存器</h2>
<h3 id="adc的使用">ADC的使用</h3>
<p>进行配置只需关心ADCCON1、ADCCON2或ADCCON3（ADCCON2和ADCCON3是一样的，配置哪个都可以，我配置的是ADCCON3）</p>
<ul>
<li>
<p>ADCCON1寄存器可用来查询转换状态，可以配置触发事件类型，默认采用手动触发方式启动转换（STSEL=11b），ADCCON1默认值0x33，满足我这里的需要，不需配置。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/flaax.png" alt="2"></p>
</li>
<li>
<p>这里选择配置ADCCON3(也可以配置ADCCON2)，ADCCON.EREF用来配置参考电压源，默认是内部参考电压，ADCCON.EDIV用来配置转换精度，这里选择最高12位精度，ADCCON3.ECH用来选择转换通道，选择温度传感器，最终ADCCON3=0x3E。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/fcv9i.png" alt="3"></p>
</li>
</ul>
<h3 id="adc转换数据">ADC转换数据</h3>
<p>ADC转换数据[13:6]存放到ADCH寄存器中，[5:0]存放在ADCL寄存器中。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/yp4gq.png" alt="4"></p>
<h3 id="开启温度传感器">开启温度传感器</h3>
<p>开启温度传感器通过配置TR0和ATEST两个寄存器</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/dkfes.png" alt="5"></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ntwtl.png" alt="6"></p>
<h2 id="温度传感器数据获取">温度传感器数据获取</h2>
<p>使用ADC对温度传感器采样前需要开启温度传感器，如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">void</span> <span class="nf">adcTempInit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 打开温度传感器
</span><span class="c1"></span>    <span class="n">TR0</span> <span class="o">=</span> <span class="mh">0x01</span><span class="p">;</span>
    <span class="c1">// 使能温度传感器
</span><span class="c1"></span>    <span class="n">ATEST</span> <span class="o">=</span> <span class="mh">0x01</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>编写手动读取温度传感器数据的函数，其中转换器获得的数据需要换算才能得到摄氏温度，代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="c1">// 获取温度传感器数据
</span><span class="c1"></span><span class="kt">float</span> <span class="nf">readTemperature</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span>  <span class="n">reading</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="kt">int</span>  <span class="n">Result</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">factor</span> <span class="o">=</span> <span class="mf">10.0</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">do</span><span class="p">{</span>
        <span class="n">ADCCON3</span> <span class="o">=</span> <span class="mh">0x0E</span> <span class="o">|</span> <span class="mh">0x20</span><span class="p">;</span>           <span class="c1">// 12位精度，启动转换
</span><span class="c1"></span>        <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">ADCCON1</span> <span class="o">&amp;</span> <span class="mh">0x80</span><span class="p">));</span>          <span class="c1">// 等待转换完成
</span><span class="c1"></span>
        <span class="c1">// 读取采样结果
</span><span class="c1"></span>        <span class="n">reading</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)(</span><span class="n">ADCL</span><span class="p">);</span>
        <span class="n">reading</span> <span class="o">|=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)(</span><span class="n">ADCH</span> <span class="o">&lt;&lt;</span> <span class="mi">8</span><span class="p">);</span>
        <span class="n">reading</span> <span class="o">&gt;&gt;=</span> <span class="mi">4</span><span class="p">;</span>                      <span class="c1">// 丢弃低位
</span><span class="c1"></span>        <span class="n">Result</span> <span class="o">+=</span> <span class="n">reading</span><span class="p">;</span>                  <span class="c1">// 累加
</span><span class="c1"></span>    <span class="p">}</span><span class="k">while</span><span class="p">(</span><span class="n">i</span><span class="o">++</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">);</span>   <span class="c1">// 连续采样10次
</span><span class="c1"></span>    
    <span class="k">return</span> <span class="p">((</span><span class="n">Result</span><span class="o">/</span><span class="mi">10</span><span class="p">)</span><span class="o">-</span><span class="mi">1340</span><span class="p">)</span> <span class="o">/</span> <span class="n">factor</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>在需要获取温度数据的地方调用函数即可，我在主函数获取温度数据并打印，每隔一秒PC串口助手就接收到一次温度数据，代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">oscInit</span><span class="p">();</span>              <span class="c1">// 晶振时钟初始化
</span><span class="c1"></span>    <span class="n">serialInit</span><span class="p">();</span>
    <span class="n">adcTempInit</span><span class="p">();</span>
    
    <span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printFloatln</span><span class="p">(</span><span class="n">readTemperature</span><span class="p">());</span>
        <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>BLE开发笔记——串口的使用</title>
			<link>https://blog.5km.studio/2015/10/30/serial-BLE/</link>
			<pubDate>Fri, 30 Oct 2015 14:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/30/serial-BLE/</guid>
			<description>&lt;p&gt;进行软件开发，串口打印是个有效的调试方式，特别适用于没有显示设备的开发平台，所以为了方便调试必须要学会串口的配置和使用，在后期实现串口数据蓝牙转发的时候，也必须清楚串口的使用。本文就针对我自己的情况说一下串口的使用，CC2541有两个USART，使用都是类似的，根据使用管脚和串口的不同做适当的调整就好。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>进行软件开发，串口打印是个有效的调试方式，特别适用于没有显示设备的开发平台，所以为了方便调试必须要学会串口的配置和使用，在后期实现串口数据蓝牙转发的时候，也必须清楚串口的使用。本文就针对我自己的情况说一下串口的使用，CC2541有两个USART，使用都是类似的，根据使用管脚和串口的不同做适当的调整就好。</p>
<h2 id="配置">配置</h2>
<p>我手头的是一个CC2541的最小系统，如下图所示，使用的串口是UART0，与IO的分配关系是：P1_5-&gt;TX，P1_4-&gt;RX。要实现串口的使用（不使用硬件流控制）需要进行IO的复用配置和针对串口的配置。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/25atx.png" alt="1"></p>
<h3 id="io配置">IO配置</h3>
<p>需要将P1_4和P1_5两个IO进行复用配置，同时将UART0配置到这两个IO的位置，关于这一项配置，首先得明确IO复用的map，如下很清晰地看到UART0映射到P1_4和P1_5，是配置UART0为位置2。还需要配置相应管脚优先使用串口，那么需要配置的寄存器分别是PERCFG、P1SEL和P2DIR，关于这三个寄存器的详细信息可以看我的另一篇文章 <a href="/2015/10/27/BaseRegiser-BLE/">BLE开发——CC2541寄存器整理</a>。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/f045v.png" alt="2"></p>
<p>配置代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">PERCFG</span> <span class="o">|=</span> <span class="mh">0x01</span><span class="p">;</span>                          <span class="c1">// 配置UART0为位置2
</span><span class="c1"></span><span class="n">P1SEL</span> <span class="o">=</span> <span class="mh">0x3c</span><span class="p">;</span>                            <span class="c1">// P1_2,P1_3,P1_4,P1_5用作外设，串口功能
</span><span class="c1"></span><span class="n">P2DIR</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="mh">0xc0</span><span class="p">;</span>                          <span class="c1">// P1优先用作串口
</span></code></pre></div><p>对了，这里得插一句，可能你不明白怎么看寄存器赋什么值，这里以上面的P1SEL为例讲一下:</p>
<p>我们知道此寄存器的描述：P1.7&ndash;P1.0的功能选择（0：通用I/O 1：外设功能）。我们需要的是P1_2,P1_3,P1_4,P1_5用作外设，所以这四个管脚对应的一个Byte的2、3、4、5bit位是1，其余bit位不管，设置为0为普通IO，所以值就是0011 1100那么对应十六进制数为0x3c。</p>
<h3 id="uart0配置">UART0配置</h3>
<p>IO配置好以后，需要对UART0的寄存器进行配置，首先得选择串口方式是UART异步通信方式，然后通过U0GCR.BAUD_E[4:0]和U0BAUD.BAUD[7:0]结合配置波特率，波特率的计算公式如下：</p>
<p>$$
BaudRate = \frac{(256+BAUD \_ M)\times2^{BAUD \_ E}}{2^{28}}\times f
$$</p>
<p>其中f是系统时钟，我设置的是32MHz外部晶振，对应32MHz时钟波特率设置配置表如下：</p>
<table>
<thead>
<tr>
<th style="text-align:center">波特率</th>
<th style="text-align:center">UxBAUD.BAUD_M</th>
<th style="text-align:center">UxBAUD.BAUD_E</th>
<th style="text-align:center">误差(%)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">2400</td>
<td style="text-align:center">59</td>
<td style="text-align:center">6</td>
<td style="text-align:center">0.14</td>
</tr>
<tr>
<td style="text-align:center">4800</td>
<td style="text-align:center">59</td>
<td style="text-align:center">7</td>
<td style="text-align:center">0.14</td>
</tr>
<tr>
<td style="text-align:center">9600</td>
<td style="text-align:center">59</td>
<td style="text-align:center">8</td>
<td style="text-align:center">0.14</td>
</tr>
<tr>
<td style="text-align:center">14,400</td>
<td style="text-align:center">216</td>
<td style="text-align:center">8</td>
<td style="text-align:center">0.03</td>
</tr>
<tr>
<td style="text-align:center">19,200</td>
<td style="text-align:center">59</td>
<td style="text-align:center">9</td>
<td style="text-align:center">0.14</td>
</tr>
<tr>
<td style="text-align:center">28,800</td>
<td style="text-align:center">216</td>
<td style="text-align:center">9</td>
<td style="text-align:center">0.03</td>
</tr>
<tr>
<td style="text-align:center">38,400</td>
<td style="text-align:center">59</td>
<td style="text-align:center">10</td>
<td style="text-align:center">0.14</td>
</tr>
<tr>
<td style="text-align:center">57,600</td>
<td style="text-align:center">216</td>
<td style="text-align:center">10</td>
<td style="text-align:center">0.03</td>
</tr>
<tr>
<td style="text-align:center">76,800</td>
<td style="text-align:center">59</td>
<td style="text-align:center">11</td>
<td style="text-align:center">0.14</td>
</tr>
<tr>
<td style="text-align:center">115,200</td>
<td style="text-align:center">216</td>
<td style="text-align:center">11</td>
<td style="text-align:center">0.03</td>
</tr>
<tr>
<td style="text-align:center">230,400</td>
<td style="text-align:center">216</td>
<td style="text-align:center">12</td>
<td style="text-align:center">0.03</td>
</tr>
</tbody>
</table>
<p>其实还可以根据自己需求设置U0UCR对停止位、校验、起始位、位长进行配置，我没有配置使用了默认设置：8位数据位、没有校验、1个停止位。</p>
<p>配置代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">U0CSR</span> <span class="o">|=</span> <span class="mh">0x80</span><span class="p">;</span>                           <span class="c1">// UART 方式
</span><span class="c1"></span><span class="n">U0GCR</span> <span class="o">|=</span> <span class="mi">11</span><span class="p">;</span>                             <span class="c1">// U0GCR与U0BAUD配合
</span><span class="c1"></span><span class="n">U0BAUD</span> <span class="o">|=</span> <span class="mi">216</span><span class="p">;</span>                           <span class="c1">// 波特率设为115200
</span></code></pre></div><h3 id="接收中断配置">接收中断配置</h3>
<p>我采用接收中断的方式接收串口数据，所以还得需要配置中断，查询<a href="http://pan.baidu.com/s/1kTwBhpt">CC2541用户手册</a>可以发现设置IEN0即可，寄存器详细如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/soj5t.png" alt="4"></p>
<p>设置第7位和低位即可，同时需要清空UART0串口接收中断标志，因为串口基本配置完毕，然后配置U0CSR允许接收，配置代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">UTX0IF</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>                              <span class="c1">// 清除中断标志
</span><span class="c1"></span><span class="n">U0CSR</span> <span class="o">|=</span> <span class="mi">0</span><span class="n">X40</span><span class="p">;</span>                           <span class="c1">// 允许接收
</span><span class="c1"></span><span class="n">IEN0</span> <span class="o">|=</span> <span class="mh">0x84</span><span class="p">;</span>                            <span class="c1">// 开总中断，接收中断
</span></code></pre></div><h2 id="串口使用">串口使用</h2>
<p>这里采用中断的方式接收串口数据，所以必须写中断处理程序，接受搞定，那么发送数据怎么办呢？无论接收还是发送数据都是在操作U0DBUF，接收到数据后硬件会把数据放在U0DBUF，读取即可取出数据，当往U0DBUF写数据后，串口就会把数据发送出去，为了更方便使用串口，我编写了判断接收，读取数据，发送字符串、整数和浮点数的处理函数，下面分别来说一下。</p>
<h3 id="串口接收中断">串口接收中断</h3>
<p>以下代码实现将接收到的数据发回去，将程序烧进芯片，连接串口到PC，在PC端打开串口助手（这个网上有很多，自己下载一个即可，比如<a href="http://www.openjumper.com/openjumper-serial-assistant/">OpenJumper串口助手</a>）进行测试，PC端往串口发数据，则会看到CC2541将数据返回发送，PC端串口助手接收区便收到发送的数据。</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="c1">// 中断处理
</span><span class="c1"></span><span class="cp">#pragma vector = URX0_VECTOR
</span><span class="cp"></span><span class="n">__interrupt</span> <span class="kt">void</span> <span class="nf">UART0_ISR</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">ch</span><span class="p">;</span>
    <span class="n">URX0IF</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>    <span class="c1">// 清中断标志
</span><span class="c1"></span>    <span class="n">ch</span> <span class="o">=</span> <span class="n">U0DBUF</span><span class="p">;</span>
    
    <span class="n">U0DBUF</span> <span class="o">=</span> <span class="n">ch</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><h3 id="串口函数">串口函数</h3>
<p>为了方便使用，写了一些函数，这里只说明一下接收：我是建立了一个缓存数组，长度64，还有一个标记指针，用来记录最新数据位置，每次接收到数据就往数组里塞，读取的时候取出数据，再逐个前移数据，标记指针减1。</p>
<p>具体实现，看我的代码，很好理解的。</p>
<ul>
<li>
<p>头文件serial.h</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#ifndef __SERIAL_H__
</span><span class="cp">#define __SERIAL_H__
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">oscInit</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">serialInit</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>
<span class="kt">unsigned</span> <span class="kt">char</span> <span class="nf">serialAvailable</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>
<span class="kt">unsigned</span> <span class="kt">char</span> <span class="nf">serialRead</span><span class="p">();</span>
<span class="kt">unsigned</span> <span class="kt">char</span> <span class="nf">serialWrite</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">dataSend</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">print</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">bytes</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">length</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">printInt</span><span class="p">(</span><span class="kt">int</span> <span class="n">num</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">printFloat</span><span class="p">(</span><span class="kt">float</span> <span class="n">num</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">println</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">bytes</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">length</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">printIntln</span><span class="p">(</span><span class="kt">int</span> <span class="n">num</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">printFloatln</span><span class="p">(</span><span class="kt">float</span> <span class="n">num</span><span class="p">);</span>

<span class="cp">#endif
</span></code></pre></div></li>
<li>
<p>源文件serial.c</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&#34;serial.h&#34;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&lt;ioCC2541.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="k">static</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">serialReData</span><span class="p">[</span><span class="mi">64</span><span class="p">];</span>
<span class="k">static</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">dataPoint</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

<span class="c1">// 启动外部32M晶振
</span><span class="c1"></span><span class="kt">void</span> <span class="nf">oscInit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">SLEEPCMD</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="mh">0x04</span><span class="p">;</span>                       <span class="c1">// 启动所有晶振
</span><span class="c1"></span>    <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">SLEEPSTA</span> <span class="o">&amp;</span> <span class="mh">0x40</span><span class="p">));</span>             <span class="c1">// 等待晶振稳定
</span><span class="c1"></span>
    <span class="n">CLKCONCMD</span> <span class="o">=</span> <span class="p">(</span><span class="n">CLKCONCMD</span> <span class="o">&amp;</span> <span class="mh">0x80</span><span class="p">)</span> <span class="o">|</span> <span class="mh">0x49</span><span class="p">;</span>   <span class="c1">// 使用16M晶振作为主时钟
</span><span class="c1"></span>    <span class="k">while</span> <span class="p">((</span><span class="n">CLKCONSTA</span> <span class="o">&amp;</span> <span class="o">~</span><span class="mh">0x80</span><span class="p">)</span> <span class="o">!=</span> <span class="mh">0x49</span> <span class="p">);</span>   <span class="c1">// 等待主时钟切换到16M晶振
</span><span class="c1"></span>
    <span class="n">CLKCONCMD</span> <span class="o">=</span> <span class="p">(</span><span class="n">CLKCONCMD</span> <span class="o">&amp;</span> <span class="o">~</span><span class="mh">0x80</span><span class="p">)</span> <span class="p">;</span>        <span class="c1">// 使用外部32K晶振作为休眠时钟
</span><span class="c1"></span>    <span class="k">while</span> <span class="p">(</span> <span class="p">(</span><span class="n">CLKCONSTA</span> <span class="o">&amp;</span> <span class="mh">0x80</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">);</span>      <span class="c1">// 等待睡眠时钟切换到外部32K晶振
</span><span class="c1"></span>
    <span class="n">CLKCONCMD</span> <span class="o">=</span> <span class="p">(</span><span class="n">CLKCONCMD</span> <span class="o">&amp;</span> <span class="mh">0x80</span><span class="p">)</span> <span class="p">;</span>        <span class="c1">// 使用32M晶振作为主时钟
</span><span class="c1"></span>    <span class="k">while</span> <span class="p">(</span> <span class="p">(</span><span class="n">CLKCONSTA</span> <span class="o">&amp;</span> <span class="o">~</span><span class="mh">0x80</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">);</span>     <span class="c1">// 等待主时钟切换到32M晶振
</span><span class="c1"></span>
    <span class="n">SLEEPCMD</span> <span class="o">|=</span> <span class="mh">0x04</span><span class="p">;</span>                       <span class="c1">// 关闭未使用的晶振
</span><span class="c1"></span><span class="p">}</span>

<span class="c1">// 串口初始化
</span><span class="c1"></span><span class="kt">void</span> <span class="nf">serialInit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 初始化串口的IO
</span><span class="c1"></span>    <span class="n">PERCFG</span> <span class="o">|=</span> <span class="mh">0x01</span><span class="p">;</span>                          <span class="c1">// 配置UART0为位置2
</span><span class="c1"></span>    <span class="n">P1SEL</span> <span class="o">=</span> <span class="mh">0x3c</span><span class="p">;</span>                            <span class="c1">// P1_2,P1_3,P1_4,P1_5用作串口功能
</span><span class="c1"></span>    <span class="n">P2DIR</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="mh">0xc0</span><span class="p">;</span>                          <span class="c1">// P1优先用作串口
</span><span class="c1"></span>    <span class="c1">// 配置串口
</span><span class="c1"></span>    <span class="n">U0CSR</span> <span class="o">|=</span> <span class="mh">0x80</span><span class="p">;</span>                           <span class="c1">// UART 方式
</span><span class="c1"></span>    <span class="n">U0GCR</span> <span class="o">|=</span> <span class="mi">11</span><span class="p">;</span>                             <span class="c1">// U0GCR与U0BAUD配合
</span><span class="c1"></span>    <span class="n">U0BAUD</span> <span class="o">|=</span> <span class="mi">216</span><span class="p">;</span>                           <span class="c1">// 波特率设为115200
</span><span class="c1"></span>    <span class="n">UTX0IF</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>                              <span class="c1">// 清除中断标志
</span><span class="c1"></span>    <span class="n">U0CSR</span> <span class="o">|=</span> <span class="mi">0</span><span class="n">X40</span><span class="p">;</span>                           <span class="c1">// 允许接收
</span><span class="c1"></span>    <span class="n">IEN0</span> <span class="o">|=</span> <span class="mh">0x84</span><span class="p">;</span>                            <span class="c1">// 开总中断，接收中断
</span><span class="c1"></span><span class="p">}</span>

<span class="kt">unsigned</span> <span class="kt">char</span> <span class="nf">serialAvailable</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">dataPoint</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// 读取一个Byte
</span><span class="c1"></span><span class="kt">unsigned</span> <span class="kt">char</span> <span class="nf">serialRead</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">ch</span><span class="p">;</span>
    <span class="n">ch</span> <span class="o">=</span> <span class="n">serialReData</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
    <span class="k">if</span><span class="p">(</span><span class="n">dataPoint</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">dataPoint</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="n">serialReData</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">serialReData</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
        <span class="n">dataPoint</span><span class="o">--</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">dataPoint</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">ch</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// 发送一个Byte数据
</span><span class="c1"></span><span class="kt">unsigned</span> <span class="kt">char</span> <span class="nf">serialWrite</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">dataSend</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">U0DBUF</span> <span class="o">=</span> <span class="n">dataSend</span><span class="p">;</span>
    <span class="k">while</span><span class="p">((</span><span class="n">U0CSR</span> <span class="o">&amp;</span> <span class="mh">0x01</span><span class="p">)</span> <span class="o">==</span> <span class="mh">0x01</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// 按个数发送字符串
</span><span class="c1"></span><span class="kt">void</span> <span class="nf">print</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">bytes</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">length</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">length</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">serialWrite</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">bytes</span><span class="o">+</span><span class="n">i</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="c1">// 发送整数
</span><span class="c1"></span><span class="kt">void</span> <span class="nf">printInt</span><span class="p">(</span><span class="kt">int</span> <span class="n">num</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span><span class="p">(</span><span class="n">num</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">serialWrite</span><span class="p">(</span><span class="mh">0x30</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">ch</span><span class="p">;</span>
        <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">nT</span><span class="p">;</span>
        <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">flag</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="k">if</span><span class="p">(</span><span class="n">num</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">serialWrite</span><span class="p">(</span><span class="sc">&#39;-&#39;</span><span class="p">);</span>
            <span class="n">nT</span><span class="o">=-</span><span class="n">num</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">nT</span> <span class="o">=</span> <span class="n">num</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">ch</span> <span class="o">=</span> <span class="n">nT</span><span class="o">/</span><span class="mi">10000</span><span class="o">+</span><span class="mh">0x30</span><span class="p">;</span>
            <span class="n">nT</span> <span class="o">%=</span> <span class="mi">10000</span><span class="p">;</span>
            <span class="n">nT</span> <span class="o">*=</span> <span class="mi">10</span><span class="p">;</span>
            <span class="k">if</span><span class="p">(</span><span class="n">flag</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">serialWrite</span><span class="p">(</span><span class="n">ch</span><span class="p">);</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="k">if</span><span class="p">(</span><span class="n">ch</span> <span class="o">&gt;</span> <span class="mh">0x30</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">flag</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
                    <span class="n">serialWrite</span><span class="p">(</span><span class="n">ch</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 发送两位小数
</span><span class="c1"></span><span class="kt">void</span> <span class="nf">printFloat</span><span class="p">(</span><span class="kt">float</span> <span class="n">numFloat</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">numI</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">numFloat</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">factor</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">ch</span><span class="p">;</span>
    <span class="n">printInt</span><span class="p">(</span><span class="n">numI</span><span class="p">);</span>
    <span class="n">numI</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)(</span><span class="n">numFloat</span><span class="o">*</span><span class="n">factor</span><span class="o">-</span><span class="n">numI</span><span class="o">*</span><span class="n">factor</span><span class="p">);</span>
    <span class="n">serialWrite</span><span class="p">(</span><span class="sc">&#39;.&#39;</span><span class="p">);</span>
    <span class="n">ch</span> <span class="o">=</span> <span class="n">numI</span> <span class="o">/</span> <span class="mi">10</span><span class="p">;</span>
    <span class="n">serialWrite</span><span class="p">(</span><span class="n">ch</span><span class="o">+</span><span class="mh">0x30</span><span class="p">);</span>
    <span class="n">ch</span> <span class="o">=</span> <span class="n">numI</span> <span class="o">%</span> <span class="mi">10</span><span class="p">;</span>
    <span class="n">serialWrite</span><span class="p">(</span><span class="n">ch</span><span class="o">+</span><span class="mh">0x30</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// 发送字符串（带换行）
</span><span class="c1"></span><span class="kt">void</span> <span class="nf">println</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">bytes</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">length</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">print</span><span class="p">(</span><span class="n">bytes</span><span class="p">,</span> <span class="n">length</span><span class="p">);</span>
    <span class="n">serialWrite</span><span class="p">(</span><span class="sc">&#39;\n&#39;</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// 发送整数（带换行）
</span><span class="c1"></span><span class="kt">void</span> <span class="nf">printIntln</span><span class="p">(</span><span class="kt">int</span> <span class="n">num</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printInt</span><span class="p">(</span><span class="n">num</span><span class="p">);</span>
    <span class="n">serialWrite</span><span class="p">(</span><span class="sc">&#39;\n&#39;</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// 发送两位小数（带换行）
</span><span class="c1"></span><span class="kt">void</span> <span class="nf">printFloatln</span><span class="p">(</span><span class="kt">float</span> <span class="n">num</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printFloat</span><span class="p">(</span><span class="n">num</span><span class="p">);</span>
    <span class="n">serialWrite</span><span class="p">(</span><span class="sc">&#39;\n&#39;</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// 中断处理
</span><span class="c1"></span><span class="cp">#pragma vector = URX0_VECTOR
</span><span class="cp"></span><span class="n">__interrupt</span> <span class="kt">void</span> <span class="nf">UART0_ISR</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">ch</span><span class="p">;</span>
    <span class="n">URX0IF</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>    <span class="c1">// 清中断标志
</span><span class="c1"></span>    <span class="n">ch</span> <span class="o">=</span> <span class="n">U0DBUF</span><span class="p">;</span>
    <span class="k">if</span><span class="p">(</span><span class="n">dataPoint</span> <span class="o">&lt;</span> <span class="mi">64</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">serialReData</span><span class="p">[</span><span class="n">dataPoint</span><span class="p">]</span> <span class="o">=</span> <span class="n">ch</span><span class="p">;</span>
        <span class="n">dataPoint</span><span class="o">++</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">63</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">serialReData</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">serialReData</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
        <span class="p">}</span>
        <span class="n">dataPoint</span> <span class="o">=</span> <span class="mi">63</span><span class="p">;</span>
        <span class="n">serialReData</span><span class="p">[</span><span class="mi">63</span><span class="p">]</span> <span class="o">=</span> <span class="n">ch</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">U0DBUF</span> <span class="o">=</span> <span class="n">ch</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>BLE开发笔记——时钟和晶振</title>
			<link>https://blog.5km.studio/2015/10/30/clockOSC-BLE/</link>
			<pubDate>Fri, 30 Oct 2015 11:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/30/clockOSC-BLE/</guid>
			<description>&lt;p&gt;一个系统运行要稳定，其时钟必须稳定，学习CC2541开发，应该了解时钟的配置，需要了解时钟的寄存器：CLKCONCMD和CLKCONSTA，CMD，command，是用于配置时钟；STA，state，看状态的，用来检测是否配置成功。为了低功耗的设置，还应该了解SLEEPCMD和SLEEPSTA这两个寄存器，与时钟的电源相关的，本文讲一下时钟的初始化。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>一个系统运行要稳定，其时钟必须稳定，学习CC2541开发，应该了解时钟的配置，需要了解时钟的寄存器：CLKCONCMD和CLKCONSTA，CMD，command，是用于配置时钟；STA，state，看状态的，用来检测是否配置成功。为了低功耗的设置，还应该了解SLEEPCMD和SLEEPSTA这两个寄存器，与时钟的电源相关的，本文讲一下时钟的初始化。</p>
<p>有关这四个寄存器的详细信息在 <a href="/2015/10/27/BaseRegiser-BLE/">BLE开发——CC2541寄存器整理</a> 里有列出来，这里就不再说明。</p>
<p>这里根据我自己的情况，需要将系统主时钟配置为32M外部晶振，要借助CLKCONCMD.OSC配置，选择32K时钟源为内部RC振荡器，要借助CLKCONCMD.OSC32K。再者就是配置工作模式，只需关心其中有关时钟的配置即可，SLEEPCMD.OSC_PD，这里要说明的是SLEEPCMD.OSC_PD为0时会开启所有振荡器电源，能保证32MHz晶振和16MHz RC振荡器都能起振，通过检查SLEEPSTA.XOSC_STB来确定32MHz晶振是否起振。</p>
<p>那么可以按照以下代码实现系统时钟的初始化，根据注释理解过程即可：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">void</span> <span class="nf">oscInit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">SLEEPCMD</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="mh">0x04</span><span class="p">;</span>                      <span class="c1">// 启动所有晶振，SLEEPCMD.OSC_PD设为0
</span><span class="c1"></span>    <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">SLEEPSTA</span> <span class="o">&amp;</span> <span class="mh">0x40</span><span class="p">));</span>             <span class="c1">// 等待晶振稳定，检查SLEEPSTA.XOSC_STB，确认32MHz外部晶振起振
</span><span class="c1"></span>
    <span class="n">CLKCONCMD</span> <span class="o">=</span> <span class="p">(</span><span class="n">CLKCONCMD</span> <span class="o">&amp;</span> <span class="mh">0x80</span><span class="p">)</span> <span class="o">|</span> <span class="mh">0x49</span><span class="p">;</span>  <span class="c1">// 使用16M晶振作为主时钟
</span><span class="c1"></span>    <span class="k">while</span> <span class="p">((</span><span class="n">CLKCONSTA</span> <span class="o">&amp;</span> <span class="o">~</span><span class="mh">0x80</span><span class="p">)</span> <span class="o">!=</span> <span class="mh">0x49</span> <span class="p">);</span>   <span class="c1">// 等待主时钟切换到16M晶振
</span><span class="c1"></span>
    <span class="n">CLKCONCMD</span> <span class="o">=</span> <span class="p">(</span><span class="n">CLKCONCMD</span> <span class="o">&amp;</span> <span class="o">~</span><span class="mh">0x80</span><span class="p">)</span> <span class="p">;</span>       <span class="c1">// 使用外部32K晶振作为休眠时钟
</span><span class="c1"></span>    <span class="k">while</span> <span class="p">(</span> <span class="p">(</span><span class="n">CLKCONSTA</span> <span class="o">&amp;</span> <span class="mh">0x80</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">);</span>      <span class="c1">// 等待睡眠时钟切换到外部32K晶振
</span><span class="c1"></span>
    <span class="n">CLKCONCMD</span> <span class="o">=</span> <span class="p">(</span><span class="n">CLKCONCMD</span> <span class="o">&amp;</span> <span class="mh">0x80</span><span class="p">)</span> <span class="p">;</span>        <span class="c1">// 使用32M晶振作为主时钟
</span><span class="c1"></span>    <span class="k">while</span> <span class="p">(</span> <span class="p">(</span><span class="n">CLKCONSTA</span> <span class="o">&amp;</span> <span class="o">~</span><span class="mh">0x80</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">);</span>     <span class="c1">// 等待主时钟切换到32M晶振
</span><span class="c1"></span>
    <span class="n">SLEEPCMD</span> <span class="o">|=</span> <span class="mh">0x04</span><span class="p">;</span>                       <span class="c1">// 关闭未使用的晶振
</span><span class="c1"></span><span class="p">}</span>
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>BLE开发笔记——遇到的问题及解决方法记录</title>
			<link>https://blog.5km.studio/2015/10/30/problem-BLE/</link>
			<pubDate>Fri, 30 Oct 2015 10:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/30/problem-BLE/</guid>
			<description>&lt;p&gt;在进行BLE开发的学习中总会遇到一些棘手的问题，开此文就是为了记录在进行CC2541开发中遇到的问题和解决的方法，应该会持续更新。。。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>在进行BLE开发的学习中总会遇到一些棘手的问题，开此文就是为了记录在进行CC2541开发中遇到的问题和解决的方法，应该会持续更新。。。</p>
<h2 id="更新情况">更新情况</h2>
<ul>
<li>2015-11-05 18:00:00 添加问题2</li>
<li>2015-10-30 10:00:00 添加问题1</li>
</ul>
<h2 id="问题1">问题1</h2>
<p>出现以下编译链接错误：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">Error<span class="o">[</span>e133<span class="o">]</span>: The output format intel-extended cannot handle multiple address spaces. Use format variants <span class="o">(</span>-y -O<span class="o">)</span> to specify which address space is wanted
Error <span class="k">while</span> running Linker
</code></pre></div><h3 id="解决方法"><strong>解决方法：</strong></h3>
<p>网上说出现此问题的原因是IAR中EEPROM使用设置的问题，调整编译链接设置可以解决，但不适用于我出现此问题的解决，分析出现此链接错误原因可能有好多，目前经过查找找到了原因：在调用函数传递float类型的数据的时候，不能直接写浮点数，类似这样</p>
<p><code>printFloat(1.02);</code></p>
<p>必须传递一个变量，例如</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">float</span> <span class="n">test</span> <span class="o">=</span> <span class="mf">1.02</span><span class="p">;</span>
<span class="n">printFloat</span><span class="p">(</span><span class="n">test</span><span class="p">);</span>
</code></pre></div><p>找到了根本一点的解决方法，右击工程，选择<code>Options</code>-&gt;<code>Linker</code>，在<code>Config</code>标签页下找到<code>Linker configuration file</code>，勾选<code>Override default</code>，选择安装的TI蓝牙协议栈工程例程里common文件夹下的<code>ti_51ew_cc2540b.xcl</code>，ok，编译链接就可以通过了。</p>
<h2 id="问题2">问题2</h2>
<p>出现编译连接错误：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">Error<span class="o">[</span>e104<span class="o">]</span>: Failed to fit all segments into specified ranges. Problem discovered in segment XDATA_N. Unable to  
place <span class="m">2</span> block<span class="o">(</span>s<span class="o">)</span> <span class="o">(</span>0xbba byte<span class="o">(</span>s<span class="o">)</span> total<span class="o">)</span> in 0x664 byte<span class="o">(</span>s<span class="o">)</span> of memory. The problem occurred <span class="k">while</span> processing the  
segment placement <span class="nb">command</span> <span class="s2">&#34;-P(XDATA)XDATA_N=_XDATA_START-_XDATA_END&#34;</span>, where at the moment of placement the  
available memory ranges were <span class="s2">&#34;XDATA:189c-1eff&#34;</span>
Error <span class="k">while</span> running Linker
</code></pre></div><h3 id="解决方法-1"><strong>解决方法：</strong></h3>
<p>右击工程，选择<code>Options</code>-&gt;<code>C/C++ Compiler</code>，在<code>Preprocessor</code>标签页下找到<code>Define symbols:(one per line)</code>，更改其中的<code>INT_HEAP_LEN</code>的值，默认值是3000，改到一个小一点的值，点击OK，重新编译，直到成功，我的改到了1500，就可以解决了。</p>
<h3 id="参考">参考：</h3>
<ul>
<li><a href="http://e2e.ti.com/support/wireless_connectivity/f/538/t/95098">CC2540 flash memory size</a></li>
<li><a href="http://www.xuebuyuan.com/2053073.html">解决CC2540 XDATA内存不足</a></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>Jekyll博文内链和返回顶部按钮</title>
			<link>https://blog.5km.studio/2015/10/28/backToTop-Jekyll/</link>
			<pubDate>Wed, 28 Oct 2015 12:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/28/backToTop-Jekyll/</guid>
			<description>&lt;p&gt;有些时候，写的文章会很长，在开始看的时候，有目录会很方便，能即刻跳到需要阅读的地方，为了更方便回到顶部目录的地方，还得需要添加返回顶部的按钮，本文就说一下Jekyll博文用markdown书写时为文章添加锚点和返回顶部按钮。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>有些时候，写的文章会很长，在开始看的时候，有目录会很方便，能即刻跳到需要阅读的地方，为了更方便回到顶部目录的地方，还得需要添加返回顶部的按钮，本文就说一下Jekyll博文用markdown书写时为文章添加锚点和返回顶部按钮。</p>
<h3 id="添加目录锚点">添加目录锚点</h3>
<p>其实说起来就是添加一个内部链接，能使点一下目录立马跳到内容链接处，分两步：</p>
<h4 id="添加目录">添加目录</h4>
<p>首先，得添加一个清晰醒目的目录，添加目录时只需按照markdown添加超链接的方式，为每一个目录点添加锚点链接，不同于一般超链接的是，链接以『#』开头，紧接着『锚点名』，如以下例子：</p>
<div class="highlight"><pre class="chroma"><code class="language-markdown" data-lang="markdown"><span class="k">1.</span> [<span class="nt">访问模式</span>](<span class="na">#no1</span>)
<span class="k">2.</span> [<span class="nt">时钟相关</span>](<span class="na">#no2</span>)
   <span class="k">*</span> [<span class="nt">时钟控制命令</span>](<span class="na">#no2-1</span>)
   <span class="k">*</span> [<span class="nt">时钟控制状态</span>](<span class="na">#no2-2</span>)
   <span class="k">*</span> [<span class="nt">睡眠控制命令</span>](<span class="na">#no2-3</span>)
   <span class="k">*</span> [<span class="nt">睡眠控制状态</span>](<span class="na">#no2-4</span>)
<span class="k">3.</span> [<span class="nt">端口寄存器</span>](<span class="na">#no3</span>)
<span class="k">4.</span> [<span class="nt">方向寄存器</span>](<span class="na">#no4</span>)
<span class="k">5.</span> [<span class="nt">外设控制</span>](<span class="na">#no5</span>)
<span class="k">6.</span> [<span class="nt">ADC输入配置</span>](<span class="na">#no6</span>)
<span class="k">7.</span> [<span class="nt">功能选择</span>](<span class="na">#no7</span>)
<span class="k">8.</span> [<span class="nt">输入模式</span>](<span class="na">#no8</span>)
<span class="k">9.</span> [<span class="nt">中断状态标志</span>](<span class="na">#no9</span>)
<span class="k">10.</span> [<span class="nt">中断控制</span>](<span class="na">#no10</span>)
<span class="k">11.</span> [<span class="nt">中断屏蔽</span>](<span class="na">#no11</span>)
<span class="k">12.</span> [<span class="nt">串口及SPI相关</span>](<span class="na">#no12</span>)
    <span class="k">*</span> [<span class="nt">USART0的控制和状态</span>](<span class="na">#no12-1</span>)
    <span class="k">*</span> [<span class="nt">UART的控制</span>](<span class="na">#no12-2</span>)
    <span class="k">*</span> [<span class="nt">USART0的通用控制</span>](<span class="na">#no12-3</span>)
    <span class="k">*</span> [<span class="nt">USART0的数据缓存</span>](<span class="na">#no12-4</span>)
    <span class="k">*</span> [<span class="nt">USART0波特率</span>](<span class="na">#no12-5</span>) 
</code></pre></div><h4 id="添加锚点">添加锚点</h4>
<p>紧接着在目录所对应内容处添加锚点，如下：</p>
<p><strong>注意：添加锚点要添加到内容标题的前一行，这样能保证跳转时能以标题顶头，主要是我之前总是与内容处的目录标题同行，导致跳转后跳到了目录下一行，就看不到目录对应内容的标题了。</strong></p>
<div class="highlight"><pre class="chroma"><code class="language-markdown" data-lang="markdown"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;no1&#34;</span><span class="p">/&gt;</span>

<span class="gu">#### 访问模式
</span><span class="gu"></span>
...

...

...

<span class="p">&lt;</span><span class="nt">a</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;no12&#34;</span><span class="p">/&gt;</span>

<span class="gu">#### 12. 串口及SPI相关
</span><span class="gu"></span>
<span class="p">&lt;</span><span class="nt">a</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;no12-1&#34;</span><span class="p">/&gt;</span>

<span class="gu">##### 12.1. USART0的控制和状态（U0CSR，0x86）
</span><span class="gu"></span>
...


<span class="p">&lt;</span><span class="nt">a</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;no12-5&#34;</span><span class="p">/&gt;</span>

<span class="gu">##### 12.5. USART0波特率（U0BAUD，0xC2）
</span><span class="gu"></span>
...

</code></pre></div><h4 id="13-效果展示">1.3 效果展示</h4>
<p>效果就像是下面展示的一样，点击目录即跳转到对应内容处：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/v9ekx.gif" alt="1"></p>
<h3 id="添加返回顶部按钮">添加返回顶部按钮</h3>
<p>只需两步就可以在页面添加一个固定的返回顶部按钮，这里没有加JS代码，没什么漂亮的效果，只是功能的实现。</p>
<h4 id="添加返回按钮块">添加返回按钮块</h4>
<p>将以下代码添加到博文采用的布局html文件的最后，比如我的是post.html。</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;backtop&#34;</span><span class="p">&gt;</span>
   <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;#&#34;</span><span class="p">&gt;</span>TOP<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> 
</code></pre></div><h4 id="添加按钮的css样式">添加按钮的css样式</h4>
<p>这里我把我的css样式粘上，可以按照自己喜欢修改样式即可。</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">#</span><span class="nn">backtop</span> <span class="nt">a</span> <span class="p">{</span> <span class="c">/* back to top button */</span>
    <span class="k">text-align</span><span class="p">:</span> <span class="kc">center</span><span class="p">;</span>
    <span class="k">line-height</span><span class="p">:</span> <span class="mi">50</span><span class="kt">px</span><span class="p">;</span>
    <span class="k">font-size</span><span class="p">:</span> <span class="mi">16</span><span class="kt">px</span><span class="p">;</span>
    <span class="k">width</span><span class="p">:</span><span class="mi">50</span><span class="kt">px</span><span class="p">;</span>
    <span class="k">height</span><span class="p">:</span> <span class="mi">50</span><span class="kt">px</span><span class="p">;</span>
    <span class="k">position</span><span class="p">:</span> <span class="kc">fixed</span><span class="p">;</span>
    <span class="k">bottom</span><span class="p">:</span> <span class="mi">10</span><span class="kt">px</span><span class="p">;</span> <span class="c">/* 小按钮到浏览器底边的距离 */</span>
    <span class="k">right</span><span class="p">:</span> <span class="mi">60</span><span class="kt">px</span><span class="p">;</span> <span class="c">/* 小按钮到浏览器右边框的距离 */</span>
    <span class="k">color</span><span class="p">:</span> <span class="nb">rgb</span><span class="p">(</span><span class="mi">64</span><span class="p">,</span><span class="mi">120</span><span class="p">,</span><span class="mi">192</span><span class="p">);</span> <span class="c">/* 小按钮中文字的颜色 */</span>
    <span class="k">z-index</span><span class="p">:</span> <span class="mi">1000</span><span class="p">;</span>
    <span class="k">background</span><span class="p">:</span> <span class="mh">#fff</span><span class="p">;</span> <span class="c">/* 小按钮底色 */</span>
    <span class="k">padding</span><span class="p">:</span> <span class="kc">auto</span><span class="p">;</span> <span class="c">/* 小按钮中文字到按钮边缘的距离 */</span>
    <span class="k">border-radius</span><span class="p">:</span> <span class="mi">50</span><span class="kt">px</span><span class="p">;</span> <span class="c">/* 小按钮圆角的弯曲程度（半径）*/</span>
    <span class="kp">-moz-</span><span class="k">border-radius</span><span class="p">:</span> <span class="mi">50</span><span class="kt">px</span><span class="p">;</span>
    <span class="kp">-webkit-</span><span class="k">border-radius</span><span class="p">:</span> <span class="mi">50</span><span class="kt">px</span><span class="p">;</span>
    <span class="k">font-weight</span><span class="p">:</span> <span class="kc">bold</span><span class="p">;</span> <span class="c">/* 小按钮中文字的粗细 */</span>
    <span class="k">text-decoration</span><span class="p">:</span> <span class="kc">none</span> <span class="cp">!important</span><span class="p">;</span>
    <span class="k">box-shadow</span><span class="p">:</span><span class="mi">0</span> <span class="mi">1</span><span class="kt">px</span> <span class="mi">2</span><span class="kt">px</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mf">.15</span><span class="p">),</span> <span class="mi">0</span> <span class="mi">1</span><span class="kt">px</span> <span class="mi">0</span> <span class="mh">#ffffff</span> <span class="kc">inset</span><span class="p">;</span>
<span class="p">}</span>

<span class="p">#</span><span class="nn">backtop</span> <span class="nt">a</span><span class="p">:</span><span class="nd">hover</span> <span class="p">{</span> <span class="c">/* 小按钮上有鼠标悬停时 */</span>
    <span class="k">background</span><span class="p">:</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">64</span><span class="p">,</span><span class="mi">120</span><span class="p">,</span><span class="mi">192</span><span class="p">,</span><span class="mf">0.8</span><span class="p">);</span> <span class="c">/* 小按钮的底色 */</span>
    <span class="k">color</span><span class="p">:</span> <span class="mh">#fff</span><span class="p">;</span> <span class="c">/* 文字颜色 */</span>
<span class="p">}</span>

</code></pre></div><h4 id="展示效果">展示效果</h4>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/lnxf0.gif" alt="2"></p>]]></content>
		</item>
		
		<item>
			<title>BLE开发笔记——CC2541寄存器整理</title>
			<link>https://blog.5km.studio/2015/10/27/BaseRegiser-BLE/</link>
			<pubDate>Tue, 27 Oct 2015 14:00:00 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/27/BaseRegiser-BLE/</guid>
			<description>&lt;p&gt;CC2541本质上是一个8051内核的单片机，对其编程必须了解其寄存器，本文整理了常用的寄存器。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>CC2541本质上是一个8051内核的单片机，对其编程必须了解其寄存器，本文整理了常用的寄存器。</p>
<h4 id="访问模式">访问模式</h4>
<table>
<thead>
<tr>
<th style="text-align:center">符号</th>
<th style="text-align:center">访问模式</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">R/W</td>
<td style="text-align:center">可读写</td>
</tr>
<tr>
<td style="text-align:center">R</td>
<td style="text-align:center">只读</td>
</tr>
<tr>
<td style="text-align:center">R0</td>
<td style="text-align:center">读0</td>
</tr>
<tr>
<td style="text-align:center">R1</td>
<td style="text-align:center">读1</td>
</tr>
<tr>
<td style="text-align:center">W</td>
<td style="text-align:center">只写</td>
</tr>
<tr>
<td style="text-align:center">W0</td>
<td style="text-align:center">写0</td>
</tr>
<tr>
<td style="text-align:center">W1</td>
<td style="text-align:center">写1</td>
</tr>
<tr>
<td style="text-align:center">H0</td>
<td style="text-align:center">硬件清除</td>
</tr>
<tr>
<td style="text-align:center">H1</td>
<td style="text-align:center">硬件设置</td>
</tr>
</tbody>
</table>
<h4 id="时钟相关">时钟相关</h4>
<h5 id="时钟控制命令clkconcmd0xc6">时钟控制命令（CLKCONCMD，0xC6）</h5>
<table>
<thead>
   <tr>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>   
   <tr>
      <td>7</td>
      <td>OSC32K</td>
      <td>1</td>
      <td>R/W</td>
      <td>32kHz时钟振荡器选择。设置该位只能发起一个时钟源改变。CLKCONSTA.OSC32K 反映当前的设置。当要改变该位必须选择16MHz RCOSC作为系统时钟。0:32 kHz XOSC;1:32 kHz RCOSC;</td>
   </tr>
   <tr>
      <td>6</td>
      <td>OSC</td>
      <td>1</td>
      <td>R/W</td>
      <td>系统时钟 源选择 。设置该位只能发起一个时钟源改变。CLKCONSTA.OSC 反映当前的设置。0:32 MHz XOSC;1:16 MHz RCOSC;</td>
   </tr>
   <tr>
      <td>5:3</td>
      <td>TICKSPD[2:0]</td>
      <td>001</td>
      <td>R/W</td>
      <td>定时器标记输出设置。不能高于通过 OSC 位设置的系统时钟设置。000:32 MHz;001:16 MHz;010:8 MHz;011:4 MHz;100:2 MHz;101:1 MHz;110:500 kHz;111:250 kHz;注意 CLKCONCMD.TICKSPD 可以设置为任意值，但是结果受CLKCONCMD.OSC 设置的限制，即如果 CLKCONCMD.OSC=1且 CLKCONCMD.TICKSPD=000，CLKCONCMD.TICKSPD 读出001 且实际 TICKSPD 是 16 MHz。</td>
   </tr>
   <tr>
      <td>2:0</td>
      <td>CLKSPD</td>
      <td>001</td>
      <td>R/W</td>
      <td>时钟速度。不能高于通过 OSC 位设置的系统时钟设置 。 表示当前系统时钟频率。000:32MHz;001:16MHz;010:8MHz;011:4MHz;100:2MHz;101:1MHz;110:500 kHz;111:250 kHz;注意CLKCONCMD.CLKSPD可以设置为任意值，但是结果受CLKCONCMD.OSC设置的限制，即如果CLKCONCMD.OSC=1且CLKCONCMD.CLKSPD=000 ， CLKCONCMD.CLKSPD读出001且实际CLKSPD是16MHz。还要注意调试器不能和一个划分过的系统时钟一起工作。当运行调试器，当CLKCONCMD.OSC=0，CLKCONCMD.CLKSPD的值必须设置为000，或当CLKCONCMD.OSC=1设置为001。</td>
   </tr>
</table>
<h5 id="时钟控制状态clkconsta0x9e">时钟控制状态（CLKCONSTA，0x9E）</h5>
<table>
<thead>
   <tr>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>  
   <tr>
      <td>7</td>
      <td>OSC32K</td>
      <td>1</td>
      <td>R</td>
      <td>当前选择的 32 kHz 时钟源。0:32kHz XOSC;1:32kHz RCOSC;</td>
   </tr>
   <tr>
      <td>6</td>
      <td>OSC </td>
      <td>1</td>
      <td>R</td>
      <td>当前选择的系统时钟。0:32MHz XOSC;1:16MHz RCOSC</td>
   </tr>
   <tr>
      <td>5:3</td>
      <td>TICKSPD[2:0]</td>
      <td>001</td>
      <td>R</td>
      <td>当前设置的定时器标记输出。000:32MHz;001:16MHz;010:8MHz;011:4MHz;100:2MHz;101:1MHz;110:500kHz;111:250 kHz;</td>
   </tr>
   <tr>
      <td>2:0</td>
      <td>CLKSPD</td>
      <td>001</td>
      <td>R</td>
      <td>当前时钟速度。000:32MHz;001:16MHz;010:8MHz;011:4MHz;100:2MHz;101:1MHz;110:500kHz;111:250 kHz;</td>
   </tr>
</table>
<h5 id="睡眠模式控制命令sleepcmd0xbe">睡眠模式控制命令（SLEEPCMD，0xBE）</h5>
<table>
<thead>
<tr>
<th style="text-align:center">Bit位</th>
<th style="text-align:center">名称</th>
<th style="text-align:center">初始化</th>
<th style="text-align:center">读写</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">7</td>
<td style="text-align:center">OSC32K_CALDIS</td>
<td style="text-align:center">0</td>
<td style="text-align:center">R/W</td>
<td>禁用32-kHz RC振荡器. 0: 使能32-kHz RC振荡器. 1: 禁用32-kHz RC振荡器.在任何时刻都可以设置此bit位，但在片上16-MHz高速RC振荡器起振前设置不会有作用。</td>
</tr>
<tr>
<td style="text-align:center">6:3</td>
<td style="text-align:center">-</td>
<td style="text-align:center">000 0</td>
<td style="text-align:center">R0</td>
<td>保留</td>
</tr>
<tr>
<td style="text-align:center">2</td>
<td style="text-align:center">OSC_PD</td>
<td style="text-align:center">1</td>
<td style="text-align:center">R/W</td>
<td>设置晶振、振荡器电源。1:关闭未使用振荡器和晶振的电源  0:开启所有振荡器和晶振的电源。</td>
</tr>
<tr>
<td style="text-align:center">1:0</td>
<td style="text-align:center">MODE[1:0]</td>
<td style="text-align:center">00</td>
<td style="text-align:center">R/W</td>
<td>电源模式   00: 活动和空闲模式 01:电源模式1(PM1) 10: 电源模式2(PM2) 11:电源模式3(PM3)</td>
</tr>
</tbody>
</table>
<h5 id="睡眠模式状态sleepsta0x9d">睡眠模式状态（SLEEPSTA，0x9D）</h5>
<table>
<thead>
<tr>
<th style="text-align:center">Bit位</th>
<th style="text-align:center">名称</th>
<th style="text-align:center">初始化</th>
<th style="text-align:center">读写</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">7</td>
<td style="text-align:center">OSC32K_CALDIS</td>
<td style="text-align:center">0</td>
<td style="text-align:center">R</td>
<td>禁用32-kHz RC振荡器. 0: 使能32-kHz RC振荡器. 1: 禁用32-kHz RC振荡器.在任何时刻都可以设置此bit位，但在片上16-MHz高速RC振荡器起振前设置不会有作用。</td>
</tr>
<tr>
<td style="text-align:center">6:5</td>
<td style="text-align:center">-</td>
<td style="text-align:center">00</td>
<td style="text-align:center">R</td>
<td>保留</td>
</tr>
<tr>
<td style="text-align:center">4:3</td>
<td style="text-align:center">RST[1:0]</td>
<td style="text-align:center">00</td>
<td style="text-align:center">R</td>
<td>记录最终复位信号源。有多种复位，寄存器只保留最后事件，00:上电复位和掉电检测  01:外部复位信号  10:看门狗时钟复位 11:时钟丢失复位。</td>
</tr>
<tr>
<td style="text-align:center">2</td>
<td style="text-align:center">OSC_PD</td>
<td style="text-align:center">1</td>
<td style="text-align:center">R</td>
<td>1:关闭未使用振荡器和晶振的电源  0:开启所有振荡器和晶振的电源。</td>
</tr>
<tr>
<td style="text-align:center">1</td>
<td style="text-align:center">-</td>
<td style="text-align:center">0</td>
<td style="text-align:center">R</td>
<td>保留.</td>
</tr>
<tr>
<td style="text-align:center">0</td>
<td style="text-align:center">CLK32K</td>
<td style="text-align:center">0</td>
<td style="text-align:center">R</td>
<td>32kHz时钟信号，与系统时钟同步。</td>
</tr>
</tbody>
</table>
<h4 id="io寄存器">IO寄存器</h4>
<h5 id="端口寄存器p0p1p2">端口寄存器（P0，P1，P2）</h5>
<table>
<thead>
<tr>
<th style="text-align:center">端口</th>
<th style="text-align:center">Bit位</th>
<th style="text-align:center">名称</th>
<th style="text-align:center">初始化</th>
<th style="text-align:center">读写</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">P0</td>
<td style="text-align:center">7:0</td>
<td style="text-align:center">P0[7:0]</td>
<td style="text-align:center">0XFF</td>
<td style="text-align:center">R/W</td>
<td>端口0，通用I/O端口，可以位寻址。 XDATA (0x7080).</td>
</tr>
<tr>
<td style="text-align:center">P1</td>
<td style="text-align:center">7:0</td>
<td style="text-align:center">P1[7:0]</td>
<td style="text-align:center">0XFF</td>
<td style="text-align:center">R/W</td>
<td>端口1，通用I/O端口，可以位寻址。 XDATA (0x7090).</td>
</tr>
<tr>
<td style="text-align:center">P2</td>
<td style="text-align:center">7:5</td>
<td style="text-align:center"></td>
<td style="text-align:center">000</td>
<td style="text-align:center">R0</td>
<td>未使用</td>
</tr>
<tr>
<td style="text-align:center">P2</td>
<td style="text-align:center">4:0</td>
<td style="text-align:center">P2[4:0]</td>
<td style="text-align:center">0x1F</td>
<td style="text-align:center">R/W</td>
<td>端口2，通用I/O端口，可以位寻址。 XDATA (0x70A0).</td>
</tr>
</tbody>
</table>
<h4 id="方向寄存器p0dirp1dirp2dir">方向寄存器（P0DIR，P1DIR，P2DIR）</h4>
<table>
<thead>
   <tr>
      <th>端口</th>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>  
   <tr>
      <td>P0DIR</td>
      <td>7:0</td>
      <td>DIRP0_[7:0]</td>
      <td>0x00</td>
      <td>R/W</td>
      <td>P0.7--P0.0的方向（0：输入 1：输出)</td>
   </tr>
   <tr>
      <td>P1DIR</td>
      <td>7:0</td>
      <td>DIRP1_[7:0]</td>
      <td>0x00</td>
      <td>R/W</td>
      <td>P1.7--P1.0的方向（0：输入 1：输出)</td>
   </tr>
   <tr>
      <td>P2DIR</td>
      <td>7:6</td>
      <td>PRIP0[1:0]</td>
      <td>00</td>
      <td>R/W</td>
      <td>端口0外设优先级控制，当PERCFG分配给一些外设相同引脚的时候，这些位将确定优先级。优先级从前到后如下：00：USART 0，USART 1，Timer 1; 01：USART 1，USART 0，Timer 1; 10：Timer 1 channels 0-1，USART 1，USART 0，Timer 1 channels 2-3; 11：Timer 1 channels 2-3，USART 0，USART 1，Timer 1 channels 0-1</td>
   </tr>
   <tr>
      <td>P2DIR</td>
      <td>5</td>
      <td>---</td>
      <td>0</td>
      <td>R0</td>
      <td>未使用</td>
   </tr>
   <tr>
      <td>P2DIR</td>
      <td>4:0</td>
      <td>DIRP2_[4:0]</td>
      <td>00000</td>
      <td>R/W</td>
      <td>P2.4—P2.0的方向（0：输入 1：输出)</td>
   </tr>
</table>
<h5 id="外设控制percfg">外设控制（PERCFG）</h5>
<table>
<thead>
   <tr>
      <th>端口</th>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>
   <tr>
      <td>PERCFG</td>
      <td>7</td>
      <td>---</td>
      <td>0</td>
      <td>R0</td>
      <td>未使用</td>
   </tr>
   <tr>
      <td>PERCFG</td>
      <td>6</td>
      <td>T1CFG</td>
      <td>0</td>
      <td>R/W</td>
      <td>计时器1的I/O位置：0：位置1; 1：位置2</td>
   </tr>
   <tr>
      <td>PERCFG</td>
      <td>5</td>
      <td>T3CFG</td>
      <td>0</td>
      <td>R/W</td>
      <td>计时器3的I/O位置：0：位置1; 1：位置2</td>
   </tr>
   <tr>
      <td>PERCFG</td>
      <td>4</td>
      <td>T4CFG</td>
      <td>0</td>
      <td>R/W</td>
      <td>计时器4的I/O位置：0：位置1; 1：位置2</td>
   </tr>
   <tr>
      <td>PERCFG</td>
      <td>3:2</td>
      <td>---</td>
      <td>00</td>
      <td>R/W</td>
      <td>未使用</td>
   </tr>
   <tr>
      <td>PERCFG</td>
      <td>1</td>
      <td>U1CFG</td>
      <td>0</td>
      <td>R/W</td>
      <td>USART 1的I/O位置：0：位置1; 1：位置2</td>
   </tr>
   <tr>
      <td>PERCFG</td>
      <td>0</td>
      <td>U0CFG</td>
      <td>0</td>
      <td>R/W</td>
      <td>USART 0的I/O位置：0：位置1; 1：位置2</td>
   </tr>
</table>
<h5 id="功能选择p0selp1selp2sel">功能选择（P0SEL，P1SEL，P2SEL）</h5>
<table>
<thead>
   <tr>
      <th>端口</th>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>
   <tr>
      <td>P0SEL</td>
      <td>7:0</td>
      <td>SELP0_[7:0]</td>
      <td>0x00</td>
      <td>R/W</td>
      <td>P0.7--P0.0的功能选择（0：通用I/O  1：外设功能）</td>
   </tr>
   <tr>
      <td>P1SEL</td>
      <td>7:0</td>
      <td>SELP1_[7:0]</td>
      <td>0x00</td>
      <td>R/W</td>
      <td>P1.7--P1.0的功能选择（0：通用I/O  1：外设功能）</td>
   </tr>
   <tr>
      <td>P2SEL</td>
      <td>7</td>
      <td>---</td>
      <td>0</td>
      <td>R0</td>
      <td>未使用</td>
   </tr>
   <tr>
      <td>P2SEL</td>
      <td>6</td>
      <td>PRI3P1</td>
      <td>0</td>
      <td>R/W</td>
      <td>端口1外设优先级控制，当PERCFG分配USART0和USART1相同引脚的时候，这些位将确定优先级。0：USART 0 优先; 1：USART 1 优先</td>
   </tr>
   <tr>
      <td>P2SEL</td>
      <td>5</td>
      <td>PRI2P1</td>
      <td>0</td>
      <td>R/W</td>
      <td>端口1外设优先级控制，当PERCFG分配USART1和TIMER3相同引脚的时候，这些位将确定优先级。0：USART 1 优先; 1：TIMER 3 优先</td>
   </tr>
   <tr>
      <td>P2SEL</td>
      <td>4</td>
      <td>PRI1P1</td>
      <td>0</td>
      <td>R/W</td>
      <td>端口1外设优先级控制，当PERCFG分配TIMER1和TIMER4相同引脚的时候，这些位将确定优先级。0：TIMER 1 优先; 1：TIMER 4 优先</td>
   </tr>
   <tr>
      <td>P2SEL</td>
      <td>3</td>
      <td>PRI0P1</td>
      <td>0</td>
      <td>R/W</td>
      <td>端口1外设优先级控制，当PERCFG分配USART0和TIMER1相同引脚的时候，这些位将确定优先级。0：USART 0 优先; 1：TIMER 1 优先</td>
   </tr>
   <tr>
      <td>P2SEL</td>
      <td>2:0</td>
      <td>SELP2_[2:0]</td>
      <td>000</td>
      <td>R/W</td>
      <td>P2.2--P2.0的功能选择（0：通用I/O  1：外设功能）</td>
   </tr>
</table>
<h5 id="输入模式p0inpp1inpp2inp">输入模式（P0INP，P1INP，P2INP）</h5>
<table>
<thead>
   <tr>
      <th>端口</th>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>
   <tr>
      <td>P0INP</td>
      <td>7:0</td>
      <td>MDP0_[7:0]</td>
      <td>0x00</td>
      <td>R/W</td>
      <td>P0.7--P0.0的输入模式: 0：上拉/下拉（具体看PDUP0设置）; 1：三态</td>
   </tr>
   <tr>
      <td>P1INP</td>
      <td>7:2</td>
      <td>MDP1_[7:2]</td>
      <td>000000</td>
      <td>R/W</td>
      <td>P1.7—P1.2的输入模式:0：上拉/下拉（具体看PDUP1设置）;1：三态</td>
   </tr>
   <tr>
      <td>P1INP</td>
      <td>1:0</td>
      <td>---</td>
      <td>00</td>
      <td>R0</td>
      <td>未使用</td>
   </tr>
   <tr>
      <td>P2INP</td>
      <td>7</td>
      <td>PDUP2</td>
      <td>0</td>
      <td>R/W</td>
      <td>端口2上拉/下拉选择，对所有端口2引脚设置为上拉/下拉输入:0：上拉;1：下拉</td>
   </tr>
   <tr>
      <td>P2INP</td>
      <td>6</td>
      <td>PDUP1</td>
      <td>0</td>
      <td>R/W</td>
      <td>端口1上拉/下拉选择，对所有端口1引脚设置为上拉/下拉输入:0：上拉;1：下拉</td>
   </tr>
   <tr>
      <td>P2INP</td>
      <td>5</td>
      <td>PDUP0</td>
      <td>0</td>
      <td>R/W</td>
      <td>端口0上拉/下拉选择，对所有端口0引脚设置为上拉/下拉输入:0：上拉;1：下拉</td>
   </tr>
   <tr>
      <td>P2INP</td>
      <td>4:0</td>
      <td>MDP2_[4:0]</td>
      <td>00000</td>
      <td>R/W</td>
      <td>P2.4—P2.0的输入模式: 0：上拉/下拉（具体看PDUP2设置）;1：三态</td>
   </tr>
</table>
<h5 id="中断状态标志p0ifgp1ifgp2ifg">中断状态标志（P0IFG，P1IFG，P2IFG）</h5>
<table>
<thead>
   <tr>
      <th>端口</th>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读/写</th>
      <th>描述</th>
   </tr>
</thead>
   <tr>
      <td>P0IFG</td>
      <td>7:0</td>
      <td>P0IF[7:0]</td>
      <td>0x00</td>
      <td>R/W0</td>
      <td>端口0，位7至位0输入中断状态标志。当某引脚上有中断请求未决信号时，其相应标志为设1。</td>
   </tr>
   <tr>
      <td>P1IFG</td>
      <td>7:0</td>
      <td>P1IF[7:0]</td>
      <td>0x00</td>
      <td>R/W0</td>
      <td>端口1，位7至位0输入中断状态标志。当某引脚上有中断请求未决信号时，其相应标志为设1。</td>
   </tr>
   <tr>
      <td>P2IFG</td>
      <td>7:5</td>
      <td>---</td>
      <td>000</td>
      <td>R0</td>
      <td>未使用</td>
   </tr>
   <tr>
      <td>P2IFG</td>
      <td>4:0</td>
      <td>P2IF[4:0]</td>
      <td>0x00</td>
      <td>R/W0</td>
      <td>端口2，位4至位0输入中断状态标志。当某引脚上有中断请求未决信号时，其相应标志为设1。</td>
   </tr>
</table>
<h5 id="中断控制pictl">中断控制（PICTL）</h5>
<table>
<thead>
   <tr>
      <th>端口</th>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>
   <tr>
      <td>PICTL</td>
      <td>7</td>
      <td>PADSC</td>
      <td>0</td>
      <td>R/W</td>
      <td>强制引脚在输出模式。选择输出驱动能力，由DVDD引脚提供。0：最小驱动能力;1：最大驱动能力</td>
   </tr>
   <tr>
      <td>PICTL</td>
      <td>6:4</td>
      <td>---</td>
      <td>000</td>
      <td>R0</td>
      <td>未使用</td>
   </tr>
   <tr>
      <td>PICTL</td>
      <td>3</td>
      <td>P2ICON</td>
      <td>0</td>
      <td>R/W</td>
      <td>端口2，引脚4至0输入模式下的中断配置，该位为端口2的4-0脚的输入选择中断请求条件。0：输入的上升沿引起中断;1：输入的下降沿引起中断</td>
   </tr>
   <tr>
      <td>PICTL</td>
      <td>2</td>
      <td>P1ICONH</td>
      <td>0</td>
      <td>R/W</td>
      <td>端口1，引脚7至4输入模式下的中断配置，该位为端口1的7-4脚的输入选择中断请求条件。0：输入的上升沿引起中断;1：输入的下降沿引起中断</td>
   </tr>
   <tr>
      <td>PICTL</td>
      <td>1</td>
      <td>P1ICONL</td>
      <td>0</td>
      <td>R/W</td>
      <td>端口1，引脚3至0输入模式下的中断配置，该位为端口1的3-0脚的输入选择中断请求条件。0：输入的上升沿引起中断;1：输入的下降沿引起中断</td>
   </tr>
   <tr>
      <td>PICTL</td>
      <td>0</td>
      <td>P0ICON</td>
      <td>0</td>
      <td>R/W</td>
      <td>端口0，引脚7至0输入模式下的中断配置，该位为端口0的7-0脚的输入选择中断请求条件。0：输入的上升沿引起中断;1：输入的下降沿引起中断</td>
   </tr>
</table>
<h5 id="中断屏蔽p0ienp1ienp2ien">中断屏蔽（P0IEN，P1IEN，P2IEN）</h5>
<table>
<thead>
   <tr>
      <th>端口</th>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>
   <tr>
      <td>P0IEN</td>
      <td>7:0</td>
      <td>P0_[7:0]IEN</td>
      <td>0x00</td>
      <td>R/W</td>
      <td>端口0，位7至位0中断使能。0：中断禁止;1：中断使能</td>
   </tr>
   <tr>
      <td>P1IEN</td>
      <td>7:0</td>
      <td>P1_[7:0]IEN</td>
      <td>0x00</td>
      <td>R/W</td>
      <td>端口1，位7至位0中断使能。0：中断禁止;1：中断使能</td>
   </tr>
   <tr>
      <td>P2IEN</td>
      <td>7:6</td>
      <td>---</td>
      <td>00</td>
      <td>R0</td>
      <td>未使用</td>
   </tr>
   <tr>
      <td>P2IEN</td>
      <td>5</td>
      <td>DPIEN</td>
      <td>0</td>
      <td>R/W</td>
      <td>USB D+ 中断使能。</td>
   </tr>
   <tr>
      <td>P2IEN</td>
      <td>4:0</td>
      <td>P2_[4:0]IEN</td>
      <td>00000</td>
      <td>R/W</td>
      <td>端口2，位4至位0中断使能。0：中断禁止;1：中断使能</td>
   </tr>
</table>
<h4 id="adc输入配置apcfg">ADC输入配置（APCFG）</h4>
<table>
<thead>
   <tr>
      <th>端口</th>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>   
   <tr>
      <td>APCFG</td>
      <td>7:0</td>
      <td>APCFG[7:0]</td>
      <td>0x00</td>
      <td>R/W</td>
      <td>模拟外围I/O配置（ADC输入配置），APCFG[7:0]选择P0.7—P0.0作为模拟输入口。0：模拟输入（ADC输入）禁止; 1：模拟输入（ACD输入）使能</td>
   </tr>
</table>
<h4 id="串口及spi相关">串口及SPI相关</h4>
<h5 id="usart0的控制和状态u0csr0x86">USART0的控制和状态（U0CSR，0x86）</h5>
<table>
<thead>
   <tr>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>  
   <tr>
      <td>7</td>
      <td>MODE</td>
      <td>0</td>
      <td>R/W</td>
      <td>USART模式选择: 0:SPI模式;1:UART模式</td>
   </tr>
   <tr>
      <td>6</td>
      <td>RE</td>
      <td>0</td>
      <td>R/W</td>
      <td>UART接收器使能。注意在UART完全配置之前不使能接收。0:禁用接收器;1:接收器使能</td>
   </tr>
   <tr>
      <td>5</td>
      <td>SLAVE</td>
      <td>0</td>
      <td>R/W</td>
      <td>SPI主或者从模式选择.0:SPI主模式;1:SPI从模式;</td>
   </tr>
   <tr>
      <td>4</td>
      <td>FE</td>
      <td>0</td>
      <td>R/W0</td>
      <td>UART帧错误状态.0:无帧错误检测;1:字节收到不正确停止位级别</td>
   </tr>
   <tr>
      <td>3</td>
      <td>ERR</td>
      <td>0</td>
      <td>R/W0</td>
      <td>UART奇偶错误状态.0:无奇偶错误检测;1:字节收到奇偶错误</td>
   </tr>
   <tr>
      <td>2</td>
      <td>RX_BYTE</td>
      <td>0</td>
      <td>R/W0</td>
      <td>接收字节状态。URAT模式和SPI从模式。当读U0DBUF该位自动清除，通过写0清除它，这样有效丢弃U0DBUF中的数据 。0:没有收到字节;1:准备好接收字节</td>
   </tr>
   <tr>
      <td>1</td>
      <td>TX_BYTE</td>
      <td>0</td>
      <td>R/W0</td>
      <td>传送字节状态。URAT模式和SPI主模式.0:字节没有被传送;1:写到数据缓存寄存器的最后字节被传送</td>
   </tr>
   <tr>
      <td>0</td>
      <td>ACTIVE</td>
      <td>0</td>
      <td>R</td>
      <td>USART传送/接收主动状态、在SPI从模式下该位等于从模式选择。0:USART空闲;1:在传送或者接收模式USART忙碌</td>
   </tr>
</table>
<h5 id="uart的控制u0ucr0xc4">UART的控制（U0UCR，0xC4）</h5>
<table>
<thead>
   <tr>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>  
   <tr>
      <td>7</td>
      <td>FLUSH</td>
      <td>0</td>
      <td>R0/W1</td>
      <td>清除单元。当设置时，该事件将会立即停止当前操作并且返回单元的空闲状态。</td>
   </tr>
   <tr>
      <td>6</td>
      <td>FLOW</td>
      <td>0</td>
      <td>R/W</td>
      <td>UART硬件流使能。用RTS和CTS引脚选择硬件流控制的使用。0:流控制禁止.1:流控制使能.</td>
   </tr>
   <tr>
      <td>5</td>
      <td>D9</td>
      <td>0</td>
      <td>R/W</td>
      <td>UART奇偶校验位。当使能奇偶校验，写入D9的值决定发送的第9位的值，如果收到的第9位不匹配收到字节的奇偶校验，接收时报告ERR。如果奇偶校验使能， 那么该位设置以下奇偶校验级别。0:奇校验;1:偶校验.</td>
   </tr>
   <tr>
      <td>4</td>
      <td>BIT9</td>
      <td>0</td>
      <td>R/W</td>
      <td>UART 9位数据使能。当该位是1时, 使能奇偶校验位传输（即第9位）。如果通过PARITY使能奇偶校验，第9位的内容是通过D9给出的。0:8位传送;1:9位传送</td>
   </tr>
   <tr>
      <td>3</td>
      <td>PARITY</td>
      <td>0</td>
      <td>R/W</td>
      <td>UART奇偶校验使能。除了为奇偶校验设置该位用于计算，必须使能9位模式。0:禁用奇偶校验;1:奇偶校验使能</td>
   </tr>
   <tr>
      <td>2</td>
      <td>SPB</td>
      <td>0</td>
      <td>R/W</td>
      <td>UART停止位的位数。选择要传送的停止位的位数.0:1位停止位;1:2位停止位</td>
   </tr>
   <tr>
      <td>1</td>
      <td>STOP</td>
      <td>1</td>
      <td>R/W</td>
      <td>UART停止位的电平必须不同于开始位的电平.0:停止位低电平;1:停止位高电平</td>
   </tr>
   <tr>
      <td>0</td>
      <td>START</td>
      <td>0</td>
      <td>R/W</td>
      <td>UART起始位电平。闲置线的极性采用选择的起始位级别的电平的相反的电平。0:起始位低电平;1:起始位高电平</td>
   </tr>
</table>
<h5 id="usart0的通用控制u0gcr0xc5">USART0的通用控制（U0GCR，0xC5）</h5>
<table>
<thead>
   <tr>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>  
   <tr>
      <td>7</td>
      <td>CPOL</td>
      <td>0</td>
      <td>R/W</td>
      <td>SPI的时钟极性.0:负时钟极性;1:正时钟极性;</td>
   </tr>
   <tr>
      <td>6</td>
      <td>CPHA</td>
      <td>0</td>
      <td>R/W</td>
      <td>SPI时钟相位.0:当SCK从CPOL倒置到CPOL时数据输出到MOSI,并且当SCK从CPOL倒置到CPOL时数据输入抽样到 MISO。1:当SCK从CPOL倒置到CPOL时数据输出到MOSI，并且当SCK从CPOL倒置到 CPOL 时数据输入抽样到 MISO 。</td>
   </tr>
   <tr>
      <td>5</td>
      <td>ORDER</td>
      <td>0</td>
      <td>R/W</td>
      <td>传送位顺序.0:LSB先传送;1:MSB先传送;</td>
   </tr>
   <tr>
      <td>4:0</td>
      <td>BAUD_E[4:0]</td>
      <td>0</td>
      <td>0000</td>
      <td>R/W 波特率指数值。BAUD_E 和 BAUD_M 决定了 UART 波特率 和 SPI 的主 SCK 时</td>
   </tr>
</table>
<h5 id="usart0的数据缓存u0buf0xc1">USART0的数据缓存（U0BUF，0xC1）</h5>
<table>
<thead>
   <tr>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>  
   <tr>
      <td>7:0</td>
      <td>DATA[7:0]</td>
      <td>0x00</td>
      <td>R/W</td>
      <td>USART接收和传送数据 。 当写这个寄存器的时候数据被写到内部，传送数据寄存</td>
   </tr>
</table>
<h5 id="usart0波特率u0baud0xc2">USART0波特率（U0BAUD，0xC2）</h5>
<table>
<thead>
   <tr>
      <th>Bit位</th>
      <th>名称</th>
      <th>初始化</th>
      <th>读写</th>
      <th>描述</th>
   </tr>
</thead>  
   <tr>
      <td>7:0</td>
      <td>BAUD_M[7:0]</td>
      <td>0x00</td>
      <td>R/W</td>
      <td>波特率小数部分的值。 BAUD_E和 BAUD_M决定了UART的波特率和SPI的主SCK</td>
   </tr>
</table>]]></content>
		</item>
		
		<item>
			<title>BLE开发笔记——第一个工程,IO的输出</title>
			<link>https://blog.5km.studio/2015/10/26/FirstProjectIOOUTPUT-BLE/</link>
			<pubDate>Mon, 26 Oct 2015 17:34:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/26/FirstProjectIOOUTPUT-BLE/</guid>
			<description>&lt;p&gt;CC2541本质上是一个8051内核的单片机，只要熟悉51单片机和c语言，也便很容易上手CC2541的程序设计吧。为了更好的熟悉IAR从简单的IO控制开始，逐步为以后进行蓝牙协议栈开发奠定基础。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>CC2541本质上是一个8051内核的单片机，只要熟悉51单片机和c语言，也便很容易上手CC2541的程序设计吧。为了更好的熟悉IAR从简单的IO控制开始，逐步为以后进行蓝牙协议栈开发奠定基础。</p>
<p>开发环境配置好了，CC Debugger程序下载调试也弄好了，那就新建一个IAR工程试一下IO。</p>
<h4 id="新建iar工程">新建IAR工程</h4>
<ul>
<li>
<p>创建工程：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/o590b.png" alt="1"></p>
</li>
<li>
<p>在新出现的对话框中，Tool chain选择8051，Project Template选择Empty Project：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/1dcnh.png" alt="2"></p>
</li>
<li>
<p>选择保存目录，输入工程名称，保存</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/fspcl.png" alt="3"></p>
</li>
<li>
<p>保存WorkSpace，在IAR中每个工程都必须有一个Workspace，而一个Workspace中可以有多个工程，这里我必须要保存一个Workspace，file-&gt;save Workspace As就会弹出如下对话框，这里同样取名为LED：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2igii.png" alt="4"></p>
</li>
</ul>
<h4 id="配置工程">配置工程</h4>
<ul>
<li>
<p>右击工程，点击Options&hellip;进入配置界面：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/tbo5m.png" alt="5"></p>
</li>
<li>
<p>在弹出的对话框中进行Target配置，Device选择CC2541F256（根据板上芯片型号决定），Data Model选择large，Number of virtual选择16，Location for constants and strings选择ROM mapped as data。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/qwb2y.png" alt="6"></p>
</li>
<li>
<p>进行linker设置，选中Output标签页，Format区勾选Allow C-SPY-specific extra output file：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/mx7qf.png" alt="7"></p>
</li>
<li>
<p>Extra Output标签页，勾选Generate extra output file，勾选Override default，更名为LED.hex，选择Out format为intel-extended：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/gw29r.png" alt="8"></p>
</li>
<li>
<p>进行Debugger设置，Setup标签页中Driver选择Texas Instruments：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/doi47.png" alt="9"></p>
</li>
<li>
<p>配置Debugger的Texas Instruments，Download标签页中勾选Erase flash，然后点击OK，至此项目配置工作就完成了：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/7fj76.png" alt="10"></p>
</li>
</ul>
<h4 id="添加源文件">添加源文件</h4>
<ul>
<li>
<p>单击工具栏<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/qxped.png" alt="11">新建按钮，新建文件，输入以下代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&lt;ioCC2541.h&gt;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">P1SEL</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="mh">0x01</span><span class="p">;</span>         <span class="c1">// 配置管脚为IO功能
</span><span class="c1"></span>    <span class="n">P1DIR</span> <span class="o">|=</span> <span class="mh">0x03</span><span class="p">;</span>          <span class="c1">// 配置IO，P1.0为输出
</span><span class="c1"></span>
    <span class="n">P1</span> <span class="o">|=</span> <span class="mh">0x01</span><span class="p">;</span>             <span class="c1">// 配置P1.0管脚输出高电平
</span><span class="c1"></span>
    <span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>

    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>保存源文件为LED.c:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/z7hiq.png" alt="12"></p>
</li>
<li>
<p>将LED.c源文件添加到工程，如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/6ljb2.png" alt="13"></p>
</li>
<li>
<p>保存工程，单击工具栏的<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ybgku.png" alt="14">编译按钮进行编译，保存工程为LED.eww，在确认另存为的对话框中选择<code>是</code>：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/yj4uy.png" alt="15"></p>
</li>
<li>
<p>编译结果类似一下，说明白编译成功：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/5nzjl.png" alt="16"></p>
</li>
</ul>
<h4 id="测试">测试</h4>
<ul>
<li>
<p>连接好电脑、CC Debugger和CC2541板，确认CC Debugger指示为绿灯（保证CC Debugger已经识别到CC2541芯片），然后点击工具栏<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/10f23.png" alt="7">下载和调试按钮，正常的话就会进入调试界面，然后点击工具栏的<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/93zdz.png" alt="17">GO按钮，使程序全速运行，测试芯片的P1.0管脚的电平，如果为3.3V左右的高电平，说明成功搞定IO输出。下面给出CC2541管脚MAP图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/e2gj1.png" alt="18"></p>
</li>
<li>
<p>编写简单的延时函数，测试IO的高低电平间隔输出，分别新建编写delay.c和delay.h文件，按照3中的方式添加到工程之中。</p>
<p>delay.h代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#ifndef __DELAY_H__
</span><span class="cp">#define __DELAY_H__
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">delay</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">timeDelay</span><span class="p">);</span>

<span class="cp">#endif
</span></code></pre></div><p>delay.c代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&#34;delay.h&#34;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">void</span> <span class="nf">delay</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">timeDelay</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">while</span><span class="p">(</span><span class="n">timeDelay</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">320</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">);</span>       <span class="c1">// 执行空命令模拟延时1ms
</span><span class="c1"></span>    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>然后更改LED.c文件的代码如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&lt;ioCC2541.h&gt;</span><span class="cp">
</span><span class="cp">#include</span> <span class="cpf">&#34;delay.h&#34;</span><span class="cp">
</span><span class="cp"></span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">P1SEL</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="mh">0x01</span><span class="p">;</span>         <span class="c1">// 配置管脚为IO功能
</span><span class="c1"></span>    <span class="n">P1DIR</span> <span class="o">|=</span> <span class="mh">0x03</span><span class="p">;</span>          <span class="c1">// 配置P1.0为输出
</span><span class="c1"></span>
    <span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">P1</span> <span class="o">|=</span> <span class="mh">0x01</span><span class="p">;</span>         <span class="c1">// 配置P1.0输出高电平
</span><span class="c1"></span>        <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>        <span class="c1">// 延时1000ms
</span><span class="c1"></span>        <span class="n">P1</span> <span class="o">&amp;=</span> <span class="p">(</span><span class="o">~</span><span class="mh">0x01</span><span class="p">);</span>      <span class="c1">// 配置P1.0输出高电平
</span><span class="c1"></span>        <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>        <span class="c1">// 延时1000ms
</span><span class="c1"></span>    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>然后编译并下载，GO运行，万用表测量P1.0管脚电平，大约经过1s电平便切换一次，说明可用。</p>
</li>
</ul>]]></content>
		</item>
		
		<item>
			<title>BLE开发笔记——CC Debugger的使用</title>
			<link>https://blog.5km.studio/2015/10/26/CCDebugger-BLE/</link>
			<pubDate>Mon, 26 Oct 2015 10:34:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/26/CCDebugger-BLE/</guid>
			<description>&lt;p&gt;配置好开发环境，话说那得要试一下下载程序，CC2541的BLE开发要用CC Debugger，这里就说一下它的使用。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>配置好开发环境，话说那得要试一下下载程序，CC2541的BLE开发要用CC Debugger，这里就说一下它的使用。</p>
<h2 id="接口说明">接口说明</h2>
<p>这一部分内容取自官方CC Debugger的使用说明。可以从我网盘里下载</p>
<p>链接: <a href="http://pan.baidu.com/s/1x9jMA">http://pan.baidu.com/s/1x9jMA </a> 密码: aefk</p>
<h3 id="接口示意图">接口示意图</h3>
<p>CCdebugger的接口示意图如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/og1zy.jpg" alt="1"></p>
<h3 id="接口说明-1">接口说明</h3>
<p>详表如下：</p>
<table>
<thead>
<tr>
<th>引脚编号</th>
<th>引脚名称</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>GND</td>
<td>地线</td>
</tr>
<tr>
<td>2</td>
<td>VDD</td>
<td>目标板的正电源</td>
</tr>
<tr>
<td>3</td>
<td>DC</td>
<td>调试接口时钟线</td>
</tr>
<tr>
<td>4</td>
<td>DD</td>
<td>调试接口数据线</td>
</tr>
<tr>
<td>5</td>
<td>CSn</td>
<td>下载串口片选线（低电平有效）</td>
</tr>
<tr>
<td>6</td>
<td>SCLK</td>
<td>下载串口时钟线</td>
</tr>
<tr>
<td>7</td>
<td>RESETn</td>
<td>调试器复位接口</td>
</tr>
<tr>
<td>8</td>
<td>MOSI</td>
<td>下载串口数据输出线</td>
</tr>
<tr>
<td>9</td>
<td>3.3V</td>
<td>仿真器3.3V电源输出</td>
</tr>
<tr>
<td>10</td>
<td>MISO</td>
<td>下载串口数据输入线</td>
</tr>
</tbody>
</table>
<h2 id="使用">使用</h2>
<p><strong>调试器接口下载调试根据使用环境所需可以用两种接线方式</strong></p>
<ul>
<li>
<p>IAR、SmartRF Flash Programmer/Studio环境的连接</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/t5d8v.jpg" alt="2"></p>
</li>
<li>
<p>SmartRF Packet Sniffer环境下的连接</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/c3iwg.jpg" alt="3"></p>
</li>
</ul>
<h3 id="注意">注意：</h3>
<ul>
<li>可以用管脚9来给目标板提供3.3V电源</li>
<li>第一个接线图中NOTE1:
<ul>
<li>早期版本的部分SOC（如：CC2430、CC2510、CC1110）需增加一个外部上拉电阻。</li>
<li>最新版本的所有SOC内部均有上拉电阻，所以，不需要该上拉电阻。</li>
</ul>
</li>
<li>第一个接线图中NOTE2：复位线对噪声敏感，这会导致无故复位芯片。（尤其当连接线较长的时候）建议增加一个外部RC滤波器。</li>
</ul>
<h2 id="测试">测试</h2>
<h3 id="我的测试条件">我的测试条件</h3>
<ul>
<li>IAR的BLE开发环境</li>
<li>CC Debugger</li>
<li>留有DD、DC、RST接口的CC2541最小系统（在一个东西的主板上）</li>
</ul>
<h3 id="连线">连线</h3>
<ul>
<li>
<p>按照上述第一个接线图，将CC Debugger的DD、DC、RST分别连接到板上的对应接口。我选择的是调试器给CC2541板供电，所以还应该将CC Debugger的9号管脚接到板上vcc，然后接地线共地，如下图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/c7fek.jpg" alt="4"></p>
</li>
<li>
<p>但会发现，CC Debugger不能识别芯片，现象是CC Debugger的指示灯是红色的，如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/nufej.jpg" alt="5"></p>
</li>
<li>
<p>经过排查发现，原来说明里说明了，CC Debugger的2号管脚需要与目标板共电源，连接好线，按一下调试器的复位按钮，就可以识别芯片了，现象是指示灯变成绿色，如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/8gas0.jpg" alt="6"></p>
</li>
<li>
<p>检验一下：IAR打开一个CC2541的例程工程，编译，连接CC Debugger到电脑，然后点击一下<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ixjuw.png" alt="7">，Download and Debug按钮，下载调试，一般就顺利进入调试界面了。</p>
</li>
</ul>
<h2 id="tip">Tip：</h2>
<p>杜邦线害死人，在使用CC Debugger时最好是处理一下杜邦线，保证两点：</p>
<ol>
<li>尽量保证连接线是紧连的。</li>
<li>做的短一些，否则影响信号。</li>
</ol>]]></content>
		</item>
		
		<item>
			<title>BLE开发笔记——CC254x的开发环境搭建</title>
			<link>https://blog.5km.studio/2015/10/20/BLEDevelopEnvironment-elec/</link>
			<pubDate>Tue, 20 Oct 2015 17:34:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/20/BLEDevelopEnvironment-elec/</guid>
			<description>&lt;p&gt;最近我开始学习CC2541的BLE开发，索性决定边学习边整理学习记录，这篇文章就讲一下基于IAR的开发环境的搭建。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>最近我开始学习CC2541的BLE开发，索性决定边学习边整理学习记录，这篇文章就讲一下基于IAR的开发环境的搭建。</p>
<p><strong>声明：</strong></p>
<p>这里用破解版的IAR，只用于学习和研究，不用于商业研发。还是要支持使用正版软件的！！！。</p>
<h3 id="准备">准备</h3>
<p><strong>先下载搭建开发环境所需要的一些软件。</strong></p>
<p>软件列表如下：</p>
<table>
<thead>
<tr>
<th>后缀名</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>BLE-CC254x-1.4.0</td>
<td>Ble 的协议栈,CC2541芯片开发需要用到此协议栈</td>
</tr>
<tr>
<td>IAR for 8051</td>
<td>BLE的开发环境</td>
</tr>
<tr>
<td>BTool</td>
<td>蓝牙开发的辅助工具包</td>
</tr>
<tr>
<td>Packet Sniffer</td>
<td>蓝牙抓包软件</td>
</tr>
<tr>
<td>SmartRF Studio 7</td>
<td></td>
</tr>
<tr>
<td>SmartRF Flash Programmer</td>
<td>Flash编程上位机软件</td>
</tr>
</tbody>
</table>
<p><em>其中安装完<strong>BLE-CC254x-1.4.0</strong>后可以在其安装目录下找到<strong>BTool</strong></em></p>
<p><strong>我已经把上面的软件放到了自己的百度云，下载上述软件可以点<a href="http://pan.baidu.com/s/1qWFxcIw">http://pan.baidu.com/s/1qWFxcIw</a>下载，密码是: d626</strong></p>
<h3 id="开发环境搭建">开发环境搭建</h3>
<h4 id="协议栈的安装">协议栈的安装</h4>
<p>安装包可以从上述百度云分享里下载也可以自己去TI官网下载，这里我所安装的版本是v1.40，<code>BLE-CC254x-1.4.0.exe</code></p>
<h5 id="安装步骤">安装步骤：</h5>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/y079a.png" alt="1-1"></p>
<p>next</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/xol42.png" alt="1-2"></p>
<p>accept，然后next</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/7df2x.png" alt="1-3"></p>
<p>next</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ha08m.png" alt="1-4"></p>
<p>然后点击install完成安装。</p>
<h4 id="安装iar">安装IAR</h4>
<p>从我的百度云下载<code>IAR for 8050 v9.10破解.zip</code>，解压压缩包。</p>
<h5 id="iar-for-8051的安装">IAR for 8051的安装</h5>
<h6 id="安装步骤-1">安装步骤：</h6>
<p>点击<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/bs8u5.png" alt="2-1">，安装IAR for 8051.</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/xxfb6.png" alt="2-2"></p>
<p>选中点击<code>INSTALL IAR Embedded Workbench</code>，一路点yes和next进行安装即可。</p>
<h6 id="注意">注意：</h6>
<p>安装过程中可能跳出对话框：IAR 要在系统上安装 dongle 驱动程序，一般点“是”即可。</p>
<h5 id="iar的破解">IAR的破解</h5>
<p>为了能够编译我们以后创建的工程，这一步还是必须的，破解之，是用注册机里先注册。</p>
<ul>
<li>
<p>要用注册机生成一个激活文件，管理员身份运行<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/st5cy.png" alt="2-3"></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/05l1p.png" alt="2-4"></p>
</li>
<li>
<p>在Product区的下拉列表选择<code>IAR Embedded Workbench for 8051,Standard</code>，然后点击<code>Generate</code>，就会生成一个License Number，如下图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/0gymj.png" alt="2-5"></p>
</li>
<li>
<p>管理员身份打开之前安装好的IAR，打开license Manager</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/auiah.png" alt="2-6"></p>
</li>
<li>
<p>选择<code>Offline Activation</code></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/x2jo9.png" alt="2-7"></p>
</li>
<li>
<p>在出现的窗口License Number一栏，填入上面生成的License Number，点击下一步，选择No，再点击下一步，如下</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/kk2c9.png" alt="2-8"></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/4cdod.png" alt="2-9"></p>
</li>
<li>
<p>点击按钮<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ba563.png" alt="2-10">，选择激活文件保存目录，我选择的是IAR的安装目录，如下：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/j6tl0.png" alt="2-11"></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/1w5ab.png" alt="2-12"></p>
</li>
<li>
<p>然后点击两次下一步，再去刚才选择的目录下，发现已经生成了Activation.txt激活文件，然后在注册机的Activate License区点击按钮<code>Browser</code>，找到刚才生成的Activation.txt</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/i1ojx.png" alt="2-13"></p>
</li>
<li>
<p>点击<code>Activate license</code>会在这个目录得到一个回应文件，然后回到License Wizard窗口选择这个回应文件点击下一步即可。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ch4si.png" alt="2-14"></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/jd8qt.png" alt="2-15"></p>
</li>
<li>
<p>点击Done，即可完成激活，如下的样子：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/zp0fi.png" alt="2-16"></p>
</li>
</ul>
<h5 id="ble开发工程需要做的修改">BLE开发工程需要做的修改</h5>
<ul>
<li>
<p>首先打开一个TI协议栈里BLE开发的例程工程，比如</p>
<p><code>C:\Texas Instruments\BLE-CC254x-1.4.0\Projects\ble\SimpleBLEPeripheral\CC2540DB</code></p>
</li>
<li>
<p>编译一下，能编译也说明已经激活成功，但会出现以下错误：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/he678.png" alt="2-17"></p>
</li>
<li>
<p>这里通过修改一个编译依赖文件</p>
<p><code>C:\Texas Instruments\BLE-CC254x-1.4.0\Projects\ble\common\cc2540\ti_51ew_cc2540b.xcl</code></p>
<p>来解决：</p>
<p>将第155行：<code>-Z(DATA)VREG+_NR_OF_VIRTUAL_REGISTERS=08-7F</code></p>
<p>修改为：<code>-Z(DATA)VREG=08-7F</code></p>
<p>保存。</p>
</li>
<li>
<p>编译即可成功：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/88n50.png" alt="2-18"></p>
</li>
</ul>
<h4 id="btool的安装">BTOOL的安装</h4>
<p>找到安装的蓝牙协议栈的目录，在期目录下会找的BTool目录，运行去目录下的<code>setup.exe</code>，按照默认设置一路下一步安装即可。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/h2fao.png" alt="3-1"></p>
<h4 id="packet-sniffer的安装">Packet Sniffer的安装</h4>
<p>这个软件可以去TI官网下载最新版本，也可以在我的百度云分享的BLE开发目录下下载，建议按照默认安装目录进行安装。</p>
<h4 id="smartrf-studio7的安装">SMARTRF STUDIO7的安装</h4>
<p>这个软件可以去TI官网下载最新版本，也可以在我的百度云分享的BLE开发目录下下载，建议按照默认安装目录进行安装。</p>
<h4 id="smartrf-flash-programmer的安装">SMARTRF FLASH Programmer的安装</h4>
<p>这个软件可以去TI官网下载最新版本，也可以在我的百度云分享的BLE开发目录下下载，建议按照默认安装目录进行安装。</p>
<p>这样，BLE的开发环境基本上就搭建好了。</p>]]></content>
		</item>
		
		<item>
			<title>Jekyll博文图片居中</title>
			<link>https://blog.5km.studio/2015/10/15/PostImgCenter-Jekyll/</link>
			<pubDate>Thu, 15 Oct 2015 17:34:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/15/PostImgCenter-Jekyll/</guid>
			<description>&lt;p&gt;在写jekyll博文时，发现写出的文章里图片都是居左的，不是很美观，为了使图片居中，试过了text-align，发现不好使，网上搜寻找到了有效的办法。&lt;/p&gt;
&lt;p&gt;当然此种居中方法适用于大部分css样式配置，不仅局限于jekyll博文。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>在写jekyll博文时，发现写出的文章里图片都是居左的，不是很美观，为了使图片居中，试过了text-align，发现不好使，网上搜寻找到了有效的办法。</p>
<p>当然此种居中方法适用于大部分css样式配置，不仅局限于jekyll博文。</p>
<h4 id="对齐方式的等效代码">对齐方式的等效代码</h4>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="c">/* Alignment */</span> 
<span class="p">.</span><span class="nc">alignleft</span> <span class="p">{</span> 
<span class="k">display</span><span class="p">:</span> <span class="kc">inline</span><span class="p">;</span> 
<span class="k">float</span><span class="p">:</span> <span class="kc">left</span><span class="p">;</span> 
<span class="p">}</span> 
<span class="p">.</span><span class="nc">alignright</span> <span class="p">{</span> 
<span class="k">display</span><span class="p">:</span> <span class="kc">inline</span><span class="p">;</span> 
<span class="k">float</span><span class="p">:</span> <span class="kc">right</span><span class="p">;</span> 
<span class="p">}</span> 
<span class="p">.</span><span class="nc">aligncenter</span> <span class="p">{</span> 
<span class="k">clear</span><span class="p">:</span> <span class="kc">both</span><span class="p">;</span> 
<span class="k">display</span><span class="p">:</span> <span class="kc">block</span><span class="p">;</span> 
<span class="k">margin</span><span class="p">:</span><span class="kc">auto</span><span class="p">;</span> 
<span class="p">}</span> 
</code></pre></div><h4 id="修改_layoutscss文件">修改_layout.scss文件</h4>
<p>修改jekyll目录中_sass下的 _ layout.scss文件</p>
<p>可以找到对应博文的样式设置代码，在<code>post-cotent{}</code>块中添加<code>img</code>项的配置即可，借鉴上述居中代码，如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="nt">img</span> <span class="p">{</span>
    <span class="k">clear</span><span class="p">:</span> <span class="kc">both</span><span class="p">;</span> 
    <span class="k">display</span><span class="p">:</span> <span class="kc">block</span><span class="p">;</span> 
    <span class="k">margin</span><span class="p">:</span><span class="kc">auto</span><span class="p">;</span> 
<span class="p">}</span>
</code></pre></div><p>如果开着jekyll serve本地服务的话，保存后，能立即访问127.0.0.1:4000，查看自己的博客网站，随便查看一篇博文就能看到，图片居中了，就像下面图片的效果：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/8w9kh.png" alt="1"></p>
<h4 id="附">附：</h4>
<p><strong>从上面可以看到，配置我们文章的样式的话，就是添加或修改_sass下的 _layout.scss文件的<code>post-cotent{}</code>里的代码，我还修改了表格的样式</strong></p>
<ul>
<li>
<p>代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="nt">table</span> <span class="p">{</span>
    <span class="k">border-collapse</span><span class="p">:</span><span class="kc">collapse</span><span class="p">;</span>
    <span class="k">margin</span><span class="p">:</span><span class="mi">8</span><span class="kt">px</span> <span class="kc">auto</span> <span class="mi">16</span><span class="kt">px</span> <span class="kc">auto</span><span class="p">;</span>
    <span class="k">width</span><span class="p">:</span><span class="mi">100</span><span class="kt">%</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">th</span><span class="o">,</span> <span class="nt">td</span> <span class="p">{</span>
    <span class="k">border</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span> <span class="kc">solid</span> <span class="mh">#828282</span><span class="p">;</span>
    <span class="k">padding</span><span class="p">:</span> <span class="mi">8</span><span class="kt">px</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p>效果就像下面的表格：</p>
</li>
</ul>
<table>
<thead>
<tr>
<th>后缀名</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>zip</td>
<td>zip程序打包压缩的文件</td>
</tr>
<tr>
<td>rar</td>
<td>rar程序压缩的文件</td>
</tr>
<tr>
<td>7z</td>
<td>7zip程序压缩的文件</td>
</tr>
<tr>
<td>tar</td>
<td>tar程序打包，未压缩的文件</td>
</tr>
<tr>
<td>gz</td>
<td>gzip程序压缩的文件</td>
</tr>
<tr>
<td>xz</td>
<td>xz程序压缩的文件</td>
</tr>
<tr>
<td>bz2</td>
<td>bzip2程序压缩的文件</td>
</tr>
<tr>
<td>tar.gz</td>
<td>tar打包，gzip程序压缩的文件</td>
</tr>
<tr>
<td>tar.xz</td>
<td>tar打包，xz程序压缩的文件</td>
</tr>
<tr>
<td>tar.bz2</td>
<td>tar打包，bzip2程序压缩的文件</td>
</tr>
<tr>
<td>tar.7z</td>
<td>tar打包，7zip程序压缩的文件</td>
</tr>
</tbody>
</table>
<p><strong>其他的元素的样式同样可以在<code>post-content</code>里配置。</strong></p>]]></content>
		</item>
		
		<item>
			<title>测试Arduino中digital函数的执行时间</title>
			<link>https://blog.5km.studio/2015/10/15/ArduinoDigitalWriteAndReadTime-Arduino/</link>
			<pubDate>Thu, 15 Oct 2015 12:34:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/15/ArduinoDigitalWriteAndReadTime-Arduino/</guid>
			<description>&lt;p&gt;好久没有玩Arduino了，也没记录过多少关于Arduino的东西，现在有了时间，就写点东西吧，因为Arduino封装了基本功能的函数，方便很多，但好多函数内部可能添加了我们不知道的代码，我很自然地想到一个问题，运行时间是怎么样的呢，影响多大呢？测试一下，看看什么情况。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>好久没有玩Arduino了，也没记录过多少关于Arduino的东西，现在有了时间，就写点东西吧，因为Arduino封装了基本功能的函数，方便很多，但好多函数内部可能添加了我们不知道的代码，我很自然地想到一个问题，运行时间是怎么样的呢，影响多大呢？测试一下，看看什么情况。</p>
<h3 id="测试digitalwrite和digitalread函数">测试digitalWrite和digitalRead函数</h3>
<h4 id="sketch代码如下">sketch代码如下</h4>
<p>程序中会使板上led以2秒为周期闪烁：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">int</span> <span class="n">ledState</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">long</span> <span class="n">exeTime</span>   <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">long</span> <span class="n">dletaTime</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">String</span> <span class="n">timeInfo</span> <span class="o">=</span> <span class="s">&#34;Delta Time(write):&#34;</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">pinMode</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
    <span class="n">pinMode</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="n">INPUT</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span><span class="p">(</span><span class="n">micros</span><span class="p">()</span> <span class="o">-</span> <span class="n">exeTime</span> <span class="o">&gt;=</span> <span class="mi">1000000</span><span class="p">){</span>
        <span class="c1">// 测试digitalWrite的执行时间
</span><span class="c1"></span>        <span class="n">exeTime</span> <span class="o">=</span> <span class="n">micros</span><span class="p">();</span>
        <span class="n">digitalWrite</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">ledState</span><span class="p">);</span>
        <span class="n">dletaTime</span> <span class="o">=</span> <span class="n">micros</span><span class="p">()</span> <span class="o">-</span> <span class="n">exeTime</span><span class="p">;</span>
        <span class="n">timeInfo</span> <span class="o">=</span> <span class="s">&#34;Delta Time: Write-&gt;&#34;</span> <span class="o">+</span> <span class="n">String</span><span class="p">(</span><span class="n">dletaTime</span><span class="p">)</span> <span class="o">+</span> <span class="s">&#34;us&#34;</span><span class="p">;</span>
        <span class="c1">// 测试digitalRead的执行时间
</span><span class="c1"></span>        <span class="n">exeTime</span> <span class="o">=</span> <span class="n">micros</span><span class="p">();</span>
        <span class="n">digitalRead</span><span class="p">(</span><span class="mi">12</span><span class="p">);</span>
        <span class="n">dletaTime</span> <span class="o">=</span> <span class="n">micros</span><span class="p">()</span> <span class="o">-</span> <span class="n">exeTime</span><span class="p">;</span>
        <span class="n">timeInfo</span> <span class="o">+=</span> <span class="s">&#34;, Read-&gt;&#34;</span> <span class="o">+</span> <span class="n">String</span><span class="p">(</span><span class="n">dletaTime</span><span class="p">)</span> <span class="o">+</span> <span class="s">&#34;us&#34;</span><span class="p">;</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">timeInfo</span><span class="p">);</span>
        <span class="n">ledState</span> <span class="o">=</span> <span class="n">HIGH</span> <span class="o">-</span> <span class="n">ledState</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><h4 id="结果及分析">结果及分析</h4>
<h5 id="结果如下">结果如下：</h5>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span><span class="p">,</span> <span class="n">Read</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span><span class="p">,</span> <span class="n">Read</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span><span class="p">,</span> <span class="n">Read</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span><span class="p">,</span> <span class="n">Read</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span><span class="p">,</span> <span class="n">Read</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span><span class="p">,</span> <span class="n">Read</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span><span class="p">,</span> <span class="n">Read</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span><span class="p">,</span> <span class="n">Read</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span><span class="p">,</span> <span class="n">Read</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span><span class="p">,</span> <span class="n">Read</span><span class="o">-&gt;</span><span class="mi">8u</span><span class="n">s</span>
<span class="p">...</span>
</code></pre></div><h5 id="分析">分析</h5>
<p>如上结果可以看到digitalWrite和digitalRead函数的执行时间大约8us，有时也会是4us，不太清楚这是怎么回事儿，不知道会不会是micros的时间误差导致的，不过看时间的话，这两个函数不会太影响运行时间的要求。</p>
<h3 id="测试digitalwrite与寄存器直接赋值">测试digitalWrite与寄存器直接赋值</h3>
<h4 id="sketch代码如下-1">sketch代码如下</h4>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">int</span> <span class="n">ledState</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">long</span> <span class="n">exeTime</span>   <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">long</span> <span class="n">dletaTime</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">String</span> <span class="n">timeInfo</span> <span class="o">=</span> <span class="s">&#34;Delta Time(write):&#34;</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">pinMode</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
    <span class="n">pinMode</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="n">INPUT</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span><span class="p">(</span><span class="n">micros</span><span class="p">()</span> <span class="o">-</span> <span class="n">exeTime</span> <span class="o">&gt;=</span> <span class="mi">2000000</span><span class="p">){</span>
        <span class="c1">// 测试digitalWrite的执行时间
</span><span class="c1"></span>        <span class="n">exeTime</span> <span class="o">=</span> <span class="n">micros</span><span class="p">();</span>
        <span class="n">digitalWrite</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">ledState</span><span class="p">);</span>
        <span class="n">dletaTime</span> <span class="o">=</span> <span class="n">micros</span><span class="p">()</span> <span class="o">-</span> <span class="n">exeTime</span><span class="p">;</span>
        <span class="n">timeInfo</span> <span class="o">=</span> <span class="s">&#34;Delta Time: Write&#34;</span> <span class="o">+</span> <span class="n">String</span><span class="p">(</span><span class="n">dletaTime</span><span class="p">)</span> <span class="o">+</span> <span class="s">&#34;us&#34;</span><span class="p">;</span>
        <span class="c1">// 测试直接配置寄存器的执行时间
</span><span class="c1"></span>        <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span> 
        <span class="n">exeTime</span> <span class="o">=</span> <span class="n">micros</span><span class="p">();</span>
        <span class="n">PORTB</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span>
        <span class="n">dletaTime</span> <span class="o">=</span> <span class="n">micros</span><span class="p">()</span> <span class="o">-</span> <span class="n">exeTime</span><span class="p">;</span>
        <span class="n">timeInfo</span> <span class="o">+=</span> <span class="s">&#34;, Reg-&gt;&#34;</span> <span class="o">+</span> <span class="n">String</span><span class="p">(</span><span class="n">dletaTime</span><span class="p">)</span> <span class="o">+</span> <span class="s">&#34;us&#34;</span><span class="p">;</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">timeInfo</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><h4 id="结果及分析-1">结果及分析</h4>
<h5 id="结果如下-1">结果如下：</h5>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write8us</span><span class="p">,</span> <span class="n">Reg</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write4us</span><span class="p">,</span> <span class="n">Reg</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write8us</span><span class="p">,</span> <span class="n">Reg</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write8us</span><span class="p">,</span> <span class="n">Reg</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write8us</span><span class="p">,</span> <span class="n">Reg</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write8us</span><span class="p">,</span> <span class="n">Reg</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span>
<span class="n">Delta</span> <span class="nl">Time</span><span class="p">:</span> <span class="n">Write8us</span><span class="p">,</span> <span class="n">Reg</span><span class="o">-&gt;</span><span class="mi">4u</span><span class="n">s</span>
<span class="p">...</span>
</code></pre></div><h5 id="分析-1">分析：</h5>
<p>根据结果可以看到，直接寄存器复制占用4us，虽比调用函数的速度快，但也差别不大，所以不用太担心调用digital函数会影响执行效率。</p>
<h3 id="对比两种实现led闪烁的方式">对比两种实现LED闪烁的方式</h3>
<h4 id="实现">实现：</h4>
<ul>
<li>
<p><strong>方式1：利用delay延时做到闪烁，每次loop打印Arduino运行的时间。</strong></p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">int</span> <span class="n">ledState</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">long</span> <span class="n">exeTime</span>   <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">String</span> <span class="n">timeInfo</span> <span class="o">=</span> <span class="s">&#34;Time(write):&#34;</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">pinMode</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>
    <span class="n">exeTime</span> <span class="o">=</span> <span class="n">millis</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">exeTime</span> <span class="o">=</span> <span class="n">millis</span><span class="p">();</span>
    <span class="n">timeInfo</span> <span class="o">=</span> <span class="s">&#34;time:&#34;</span> <span class="o">+</span> <span class="n">String</span><span class="p">(</span><span class="n">exeTime</span><span class="p">)</span> <span class="o">+</span> <span class="s">&#34;ms&#34;</span><span class="p">;</span>
    <span class="n">digitalWrite</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">ledState</span><span class="p">);</span>
    <span class="n">ledState</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">ledState</span><span class="p">;</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">timeInfo</span><span class="p">);</span>
    <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></li>
<li>
<p><strong>方式2：利用时间判断，做到精确1000ms循环，每个loop打印运行时间；</strong></p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="kt">int</span> <span class="n">ledState</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">long</span> <span class="n">exeTime</span>   <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">String</span> <span class="n">timeInfo</span> <span class="o">=</span> <span class="s">&#34;Time(write):&#34;</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">pinMode</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>
    <span class="n">exeTime</span> <span class="o">=</span> <span class="n">millis</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span><span class="p">(</span><span class="n">millis</span><span class="p">()</span> <span class="o">-</span> <span class="n">exeTime</span> <span class="o">&gt;=</span> <span class="mi">1000</span><span class="p">){</span>
        <span class="n">exeTime</span> <span class="o">=</span> <span class="n">millis</span><span class="p">();</span>
        <span class="n">timeInfo</span> <span class="o">=</span> <span class="s">&#34;time:&#34;</span> <span class="o">+</span> <span class="n">String</span><span class="p">(</span><span class="n">exeTime</span><span class="p">)</span> <span class="o">+</span> <span class="s">&#34;ms&#34;</span><span class="p">;</span>

        <span class="n">digitalWrite</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="n">ledState</span><span class="p">);</span>
        <span class="n">ledState</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">ledState</span><span class="p">;</span>

        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">timeInfo</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></li>
</ul>
<h4 id="对比结果">对比结果</h4>
<ul>
<li>
<p><strong>方式1</strong></p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="nl">time</span><span class="p">:</span><span class="mi">0</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">999</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">1999</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">3000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">4001</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">5002</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">6002</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">7003</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">8003</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">9004</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">10004</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">11004</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">12005</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">13005</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">14006</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">15006</span><span class="n">ms</span>
</code></pre></div></li>
<li>
<p><strong>方式2</strong></p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="nl">time</span><span class="p">:</span><span class="mi">1000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">2000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">3000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">4000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">5000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">6000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">7000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">8000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">9000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">10000</span><span class="n">m</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">11000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">12000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">13000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">14000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">15000</span><span class="n">ms</span>
<span class="nl">time</span><span class="p">:</span><span class="mi">16000</span><span class="n">ms</span>
</code></pre></div></li>
</ul>
<h4 id="分析-2">分析</h4>
<p>可以看到两种方式的结果对比，打印时间是ms级别的，第一种方式就明显的看到的时间差的累加，而第二种方式做到了精确时间的循环，有效的避免中间代码执行时间的不确定性所造成周期时间的变化，这种方法提供的思路还是比较有用处的。</p>
<ul>
<li>
<p><strong>方式1代码执行生命周期</strong></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/9qjar.png" alt="1"></p>
</li>
<li>
<p><strong>方式2代码执行生命周期</strong></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/do0in.png" alt="2"></p>
</li>
</ul>
<p><strong>其中有Loop time = Free time + Work time</strong></p>]]></content>
		</item>
		
		<item>
			<title>Jekyll编译出现ivalid GBK问题</title>
			<link>https://blog.5km.studio/2015/10/09/JekyllServeInvalidGBK-jekyll/</link>
			<pubDate>Fri, 09 Oct 2015 12:34:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/09/JekyllServeInvalidGBK-jekyll/</guid>
			<description>&lt;p&gt;不知什么原因，在用jekyll serve生成本地网页时出现了ivalid GBK问题，因为jekyll利用的是ruby，应该是ruby的编译错误。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>不知什么原因，在用jekyll serve生成本地网页时出现了ivalid GBK问题，因为jekyll利用的是ruby，应该是ruby的编译错误。</p>
<h4 id="现象">现象</h4>
<div class="highlight"><pre class="chroma"><code class="language-ruby" data-lang="ruby"><span class="no">Conversion</span> <span class="ss">error</span><span class="p">:</span> <span class="no">Jekyll</span><span class="o">::</span><span class="no">Converters</span><span class="o">::</span><span class="no">Scss</span> <span class="n">encountered</span> <span class="n">an</span> <span class="n">error</span> <span class="k">while</span> <span class="n">converting</span> <span class="s1">&#39;css/main.scss&#39;</span><span class="p">:</span>
                    <span class="no">Invalid</span> <span class="no">GBK</span> <span class="n">character</span> <span class="s2">&#34;</span><span class="se">\xE5</span><span class="s2">&#34;</span> <span class="n">on</span> <span class="n">line</span> <span class="mi">368</span>
</code></pre></div><p>搜索网上，果然发现有人也遇到了此问题，分析:<code>Invalid GBK character &quot;\xE5&quot; on line 368</code>提示的倒很清楚，但我的jekyll博客文件夹css的main.scss没有第368行呀，应该是编译时调用了其他文件，而这些文件包含了中文编码。</p>
<h4 id="解决">解决</h4>
<p>修改编译引擎文件设置外部默认编码是utf-8就可以了，所以修改ruby文件，根据自己的安装目录找到此文件,在<code>module Sass</code>之后添加<code>Encoding.default_external = Encoding.find('utf-8')</code>一行即可，我的是<code>C:\Ruby22-x64\lib\ruby\gems\2.2.0\gems\sass-3.4.18\lib\sass\engine.rb</code>，如下的样子：</p>
<div class="highlight"><pre class="chroma"><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s1">&#39;sass/script&#39;</span>
<span class="nb">require</span> <span class="s1">&#39;sass/scss&#39;</span>
<span class="nb">require</span> <span class="s1">&#39;sass/stack&#39;</span>
<span class="nb">require</span> <span class="s1">&#39;sass/error&#39;</span>
<span class="nb">require</span> <span class="s1">&#39;sass/importers&#39;</span>
<span class="nb">require</span> <span class="s1">&#39;sass/shared&#39;</span>
<span class="nb">require</span> <span class="s1">&#39;sass/media&#39;</span>
<span class="nb">require</span> <span class="s1">&#39;sass/supports&#39;</span>

<span class="k">module</span> <span class="nn">Sass</span>

<span class="no">Encoding</span><span class="o">.</span><span class="n">default_external</span> <span class="o">=</span> <span class="no">Encoding</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span>
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>解决windows下git bash中文乱码问题</title>
			<link>https://blog.5km.studio/2015/10/09/GitBashChineseCodeGarbled-Git/</link>
			<pubDate>Fri, 09 Oct 2015 12:14:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/10/09/GitBashChineseCodeGarbled-Git/</guid>
			<description>&lt;p&gt;因为windows下默认编码并不是一般项目使用的UTF-8，所以在使用git bash时总会出现乱码现象，本文仅就我在使用git bash时出现的问题提出解决办法，并整理别人出现的问题及解决方法。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>因为windows下默认编码并不是一般项目使用的UTF-8，所以在使用git bash时总会出现乱码现象，本文仅就我在使用git bash时出现的问题提出解决办法，并整理别人出现的问题及解决方法。</p>
<h3 id="解决中文输入问题">解决中文输入问题</h3>
<p>在git bash下输入以下两条命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ <span class="nb">set</span> output-meta on <span class="c1">#bash中可以正常输入中文</span>
$ <span class="nb">set</span> convert-meta off 
</code></pre></div><h3 id="git-log中文乱码问题">git log中文乱码问题</h3>
<p>解决此问题需要设置Git bash支持UTF-8编码，所以在git bash下输入以下命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ git config --global core.quotepath <span class="nb">false</span>          <span class="c1"># 显示 status 编码</span>
$ git config --global gui.encoding utf-8            <span class="c1"># 图形界面编码</span>
$ git config --global i18n.commit.encoding utf-8    <span class="c1"># 提交信息编码</span>
$ git config --global i18n.logoutputencoding utf-8  <span class="c1"># 输出 log 编码</span>
$ <span class="nb">export</span> <span class="nv">LESSCHARSET</span><span class="o">=</span>utf-8
<span class="c1"># 最后一条命令是因为 git log 默认使用 less 分页，所以需要 bash 对 less 命令进行 utf-8 编码</span>
</code></pre></div><h3 id="ls文件列表中文乱码">ls文件列表中文乱码</h3>
<p>在git bash下输入命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">alias</span> <span class="nv">ls</span><span class="o">=</span><span class="s1">&#39;ls --show-control-chars --color=auto&#39;</span> <span class="c1">#ls能够正常显示中文&#34;</span>
</code></pre></div><h3 id="解决git-status中文乱码问题">解决git status中文乱码问题</h3>
<p>git bash下输入以下命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">git config --global core.quotepath <span class="nb">false</span>
</code></pre></div>]]></content>
		</item>
		
		<item>
			<title>超酷的高科技魔方Cubli</title>
			<link>https://blog.5km.studio/2015/09/27/Cubli-share/</link>
			<pubDate>Sun, 27 Sep 2015 20:14:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/09/27/Cubli-share/</guid>
			<description>&lt;p&gt;怎么推都不倒！超酷的高科技魔方Cubli&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>怎么推都不倒！超酷的高科技魔方Cubli</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/share/cubi/cubli.mp4" poster="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/share/cubi/cubli.jpg" controls="controls" width="100%">Cubli</video></p>]]></content>
		</item>
		
		<item>
			<title>linux下文件打包及解压缩</title>
			<link>https://blog.5km.studio/2015/09/12/linux-zipUnzip/</link>
			<pubDate>Sat, 12 Sep 2015 14:00:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/09/12/linux-zipUnzip/</guid>
			<description>&lt;p&gt;本文是关于linux下文件打包和解压缩的学习记录。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>本文是关于linux下文件打包和解压缩的学习记录。</p>
<p>windows中常用的压缩格式不外乎zip，rar，7z三种格式，而到了linux除了这三种压缩格式还有很多：gz,xz,bz2,tar,tar.gz,tar.xz,tar.bz2，简单介绍如下：</p>
<table>
<thead>
<tr>
<th>后缀名</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>zip</td>
<td>zip程序打包压缩的文件</td>
</tr>
<tr>
<td>rar</td>
<td>rar程序压缩的文件</td>
</tr>
<tr>
<td>7z</td>
<td>7zip程序压缩的文件</td>
</tr>
<tr>
<td>tar</td>
<td>tar程序打包，未压缩的文件</td>
</tr>
<tr>
<td>gz</td>
<td>gzip程序压缩的文件</td>
</tr>
<tr>
<td>xz</td>
<td>xz程序压缩的文件</td>
</tr>
<tr>
<td>bz2</td>
<td>bzip2程序压缩的文件</td>
</tr>
<tr>
<td>tar.gz</td>
<td>tar打包，gzip程序压缩的文件</td>
</tr>
<tr>
<td>tar.xz</td>
<td>tar打包，xz程序压缩的文件</td>
</tr>
<tr>
<td>tar.bz2</td>
<td>tar打包，bzip2程序压缩的文件</td>
</tr>
<tr>
<td>tar.7z</td>
<td>tar打包，7zip程序压缩的文件</td>
</tr>
</tbody>
</table>
<p><strong>不过常用的只是zip，rar和tar命令。</strong></p>
<h4 id="zip压缩打包程序">zip压缩打包程序</h4>
<h5 id="使用zip打包文件">使用zip打包文件</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ zip -r -q -o test.zip ./test
</code></pre></div><p>上面命令将当前目录下的test目录打包压缩成test.zip。</p>
<ul>
<li>-r：表示递归打包目录及目录里的所有内容</li>
<li>-q：表示安静模式，不在终端里打印信息</li>
<li>-o：表示输出文件，后面需要紧跟打包生成的文件名</li>
</ul>
<h5 id="查看压缩包信息大小或其它">查看压缩包信息（大小或其它）</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ du -h test.zip
</code></pre></div><ul>
<li>-h：&ndash;human-readable</li>
<li>-d：&ndash;max-depth（查看文件深度）</li>
</ul>
<p>或</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ file test.zip
</code></pre></div><h5 id="设置压缩级别">设置压缩级别</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ zip -r -9 -q -o test9.zip ./test -x ./*.zip
</code></pre></div><ul>
<li>-[1~9]：1表示最快压缩体积最大，9表示体积最小耗时最长</li>
<li>-x：排除文件选项，后面加上文件</li>
</ul>
<h5 id="加密压缩">加密压缩</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ zip -r -e -o test.zip ./test
</code></pre></div><ul>
<li>-e：加密选项</li>
</ul>
<h5 id="兼容windows换行选项">兼容windows换行选项</h5>
<ul>
<li>关于zip命令，因为 Windows 系统与 Linux/Unix 在文本文件格式上的一些兼容问题，比如换行符（为不可见字符），在 Windows 为 CR+LF（Carriage-Return+Line-Feed：回车加换行），而在 Linux/Unix 上为 LF（换行），所以如果在不加处理的情况下，在 Linux 上编辑的文本，在 Windows 系统上打开可能看起来是没有换行的。如果你想让你在 Linux 创建的 zip 压缩文件在 Windows 上解压后没有任何问题，那么你还需要对命令做一些修改，需要加个选项<code>-l</code>，将<code>LF</code>转换为<code>CR+LF</code>：</li>
</ul>
<pre><code>``` bash
$ zip -r -l -o test.zip ./test
```
</code></pre>
<h4 id="unzip命令解压缩zip文件">unzip命令解压缩zip文件</h4>
<h5 id="普通解压zip">普通解压zip</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ unzip test.zip
</code></pre></div><h5 id="带参数解压">带参数解压</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ unzip -q test.zip -d ziptest
</code></pre></div><ul>
<li>-q：静默模式，不打印解压信息</li>
<li>-d：解压到指定目录，如果目录不存在就新建目录</li>
</ul>
<h5 id="换行兼容问题">换行兼容问题</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ unzip -l test.zip
</code></pre></div><h5 id="编码问题">编码问题</h5>
<ul>
<li>使用unzip解压文件时我们同样应该注意兼容问题，不过这里我们关心的不再是上面的问题，而是中文编码的问题，通常 Windows 系统上面创建的压缩文件，如果有有包含中文的文档或以中文作为文件名的文件时默认会采用 GBK 或其它编码，而 Linux 上面默认使用的是 UTF-8 编码，如果不加任何处理，直接解压的话可能会出现中文乱码的问题（有时候它会自动帮你处理），为了解决这个问题，我们可以在解压时指定编码类型。使用-O（英文字母，大写o）参数指定编码类型：</li>
</ul>
<pre><code>``` bash
$ unzip -O GBK test.zip
```
</code></pre>
<h4 id="rar打包压缩命令">rar打包压缩命令</h4>
<p>rar也是 Windows 上常用的一种压缩文件格式，在 Linux 上可以使用rar和unrar工具分别创建和解压 rar 压缩包。</p>
<h5 id="安装rar和unrar工具">安装rar和unrar工具</h5>
<ul>
<li>ubuntu:</li>
</ul>
<pre><code>``` bash
$ sudo apt-get update
$ sudo apt-get install rar unrar
```
</code></pre>
<ul>
<li>fedora</li>
</ul>
<pre><code>``` bash
$ sudo dnf update
$ sudo dnf install unrar
```
</code></pre>
<ul>
<li>目前，我只找到了fedora下unrar。</li>
</ul>
<h5 id="从指定文件或目录创建压缩包或添加文件到压缩包">从指定文件或目录创建压缩包或添加文件到压缩包</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ rar a test.rar .
</code></pre></div><ul>
<li>上面的命令使用a参数添加一个目录～到一个归档文件中，如果该文件不存在就会自动创建。</li>
<li><em>注：rar 的命令参数没有-，如果加上会报错。</em></li>
</ul>
<h5 id="从指定压缩包文件中删除某个文件">从指定压缩包文件中删除某个文件</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ rar d test.rar .zshrc
</code></pre></div><h5 id="查看不解压文件">查看不解压文件</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ rar l test.rar
</code></pre></div><h5 id="unrar解压文件">unrar解压文件</h5>
<ul>
<li>全路径解压：</li>
</ul>
<pre><code>``` bash
$ unrar x test.rar
```
</code></pre>
<ul>
<li>去掉解压路径</li>
</ul>
<pre><code>``` bash
$ mkdir tmp
$ unrar e test.rar tmp/
```
</code></pre>
<h4 id="tar打包解压工具">tar打包解压工具</h4>
<p>在 Linux 上面更常用的是tar工具，tar 原本只是一个打包工具，只是同时还是实现了对 7z，gzip，xz，bzip2 等工具的支持，这些压缩工具本身只能实现对文件或目录（单独压缩目录中的文件）的压缩，没有实现对文件的打包压缩，所以我们也无需再单独去学习其他几个工具，tar 的解压和压缩都是同一个命令，只需参数不同，使用比较方便。</p>
<h5 id="创建一个tar包">创建一个tar包</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ tar -cf test.tar ~
</code></pre></div><ul>
<li>-c：表示创建一个tar包</li>
<li>-f：用于指定创建的文件名，注意文件名必须紧跟在-f后面，比如不能写成<code>tar -fc test.tar</code>，可以写成<code>tar -f test.tar -c ~</code>。</li>
<li>-v：以可视的方式输出打包文件</li>
<li>-z：创建gzip压缩文件，如：</li>
</ul>
<pre><code>``` bash
$ tar -czf test.tar.gz ~
```
</code></pre>
<ul>
<li><em>注：tar会自动去掉表示绝对路径的/，你也可以使用-P保留绝对路径符。</em></li>
</ul>
<h5 id="解压tar文件包">解压tar文件包</h5>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ mkdir tardir
$ tar -xf test.tar -C tardir
</code></pre></div><ul>
<li>-x：解压文件</li>
<li>-C：解压到指定目录</li>
<li>-t：之查看不解压</li>
<li>-p：保留文件属性</li>
<li>-h：保留文件链接（符号链接和软链接）</li>
<li>-z：解压tar.gz压缩文件(即gzip文件)，如：</li>
</ul>
<pre><code>``` bash
$ tar -xzf test.tar.gz
```
</code></pre>
<p>现在我们要使用其他的压缩工具创建或解压相应文件只需要更改一个参数即可：</p>
<table>
<thead>
<tr>
<th>压缩文件格式</th>
<th>对应解压参数</th>
</tr>
</thead>
<tbody>
<tr>
<td>.tar.gz</td>
<td>-z</td>
</tr>
<tr>
<td>.tar.xz</td>
<td>-J</td>
</tr>
<tr>
<td>.tar.bz2</td>
<td>-j</td>
</tr>
</tbody>
</table>
<h4 id="参考">参考</h4>
<p><a href="https://www.shiyanlou.com/courses/1">实验楼网站课程-linux基础入门-文件打包与解压缩</a></p>]]></content>
		</item>
		
		<item>
			<title>Fedora Linux下将终端改用zsh</title>
			<link>https://blog.5km.studio/2015/08/26/zshFedoraLinux/</link>
			<pubDate>Wed, 26 Aug 2015 17:55:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/08/26/zshFedoraLinux/</guid>
			<description>&lt;p&gt;本文将介绍如何在Fedora Linux下从bash进入zsh阵营。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>本文将介绍如何在Fedora Linux下从bash进入zsh阵营。</p>
<h4 id="简说">简说</h4>
<p>据说zsh是很复杂的，但功能强大，因牛人robbyrussell弄了个oh-my-zsh，让zsh的使用门槛降了好多。看到有网友整理了<a href="http://www.linuxeden.com/html/develop/20120928/130477.html">使用 Zsh 的九个理由</a>，不知道这些是不是可以说服你使用zsh，^_^，配合oh-my-zsh，可以简单上手zsh，本文就说一下我在fedora22下安装zsh的过程。</p>
<h4 id="安装过程">安装过程</h4>
<h5 id="安装zsh"><strong>安装zsh</strong></h5>
<p><strong>fedora是不自带zsh，需要我们自己安装，执行下面命令安装：</strong></p>
<p><code>sudo dnf install zsh</code></p>
<h5 id="安装oh-my-zsh"><strong>安装oh-my-zsh</strong></h5>
<p><strong>oh-my-zsh确实很赞，这个必须安装，它是github上的一个项目，git clone到$HOME下就OK了：</strong></p>
<p><code>cd ~</code></p>
<p><code>git clone https://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh</code></p>
<p>**注：**没有特殊声明，均在$HOME目录下操作。</p>
<h5 id="生成zshrc文件"><strong>生成[.zshrc]文件</strong></h5>
<p><strong>只需从oh-my-zsh中的模板拷贝过来就可以了，这个文件类似[.bashrc]文件，执行下面命令搞定：</strong></p>
<p><code>cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc</code></p>
<h5 id="配置主题"><strong>配置主题</strong></h5>
<ul>
<li>
<p>编辑[.zshrc]文件，只需配置[ZSH_THEME]的值就可以，我是用默认的[robbyrussell]主题，还有个不错的主题[candy]。</p>
<ul>
<li><code>gedit .zshrc</code></li>
<li>修改[ZSH_THEME=&ldquo;robbyrussell&rdquo;]一项即可，将robbyrussell修改为你想要的主题就可以了。比如[ZSH_THEME=&ldquo;candy&rdquo;]。</li>
</ul>
</li>
<li>
<p>你可能会问都有什么主题，这个我们可以通过下面的命令来列出所有主题，自己试着看看，自己喜欢哪个就用哪个：</p>
<p><code>ls ~/.oh-my-zsh/themes/</code></p>
</li>
</ul>
<h5 id="配置插件"><strong>配置插件</strong></h5>
<ul>
<li>
<p>编辑[.zshrc]文件，只需配置[plugins]的值，默认是[plugins=(git)]，要用插件的话只需加入名称，空格相隔就可以，就像这样：</p>
<p><code>plugins=(git git-flow debian grails rvm history-substring-search github gradle svn node npm zsh-syntax-highlighting sublime)</code></p>
</li>
<li>
<p>同样通过下面命令，我们也可以查看已经安装的插件：</p>
<p><code>ls ~/.oh-my-zsh/plugins/</code></p>
</li>
</ul>
<h5 id="切换到zsh"><strong>切换到zsh</strong></h5>
<p><strong>最后一步，就是切换到zsh，然后重启就可以用上zsh了，感受其强大吧！</strong></p>
<p><code>chsh -s /bin/zsh</code></p>
<h4 id="尾述">尾述</h4>
<p>更多与oh-my-zsh相关，详见<a href="https://github.com/robbyrussell/oh-my-zsh"><strong>oh-my-zsh</strong></a></p>]]></content>
		</item>
		
		<item>
			<title>Fedora Linux下安装Jekyll</title>
			<link>https://blog.5km.studio/2015/08/24/jekyllInstallFedoraLinux/</link>
			<pubDate>Mon, 24 Aug 2015 17:55:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/08/24/jekyllInstallFedoraLinux/</guid>
			<description>&lt;p&gt;本文将介绍如何在Fedora Linux下安装Jekyll。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>本文将介绍如何在Fedora Linux下安装Jekyll。</p>
<h3 id="简说">简说</h3>
<p>经过一天时间折腾公司配发的thinkpad-x250安装了Fedora22，安装了Evopop主题和图标包，简直美翻了，原来linux也可以这么美，哈哈，安装各种软件和工具是免不了的，jekyll也不例外，今天就说一下我在Fedora下安装jekyll的过程。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/s9ybv.png" alt="1"></p>
<ul>
<li>平台：Fedora 22</li>
</ul>
<h3 id="准备工作">准备工作</h3>
<ol>
<li>
<p><strong>安装ruby</strong></p>
<p><strong>注：从fedora22开始yum停用，而使用了新的安装软件包的工具dnf，这里就不赘述添加源的问题。</strong></p>
<ul>
<li>
<p>在【terminal】中，执行命令：</p>
<p><code>sudo dnf install ruby</code></p>
</li>
<li>
<p>安装完成后，执行命令验证：</p>
<p><code>ruby -v</code></p>
<p>会出现类似下面的字样，说明安装成功：</p>
<p>ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]</p>
</li>
<li>
<p>执行命令验证gem：</p>
<p><code>gem -v</code></p>
<p>会出现版本号<code>2.4.8</code>，说明gem也安装成功。</p>
</li>
</ul>
</li>
<li>
<p><strong>安装nodejs</strong></p>
<ul>
<li>
<p>在【terminal】中，执行命令：</p>
<p><code>sudo dnf install nodejs</code></p>
</li>
<li>
<p>安装完成后，执行命令验证：</p>
<p><code>node -v</code></p>
<p>会出现类似下面的字样，说明安装成功：</p>
<p><code>v0.10.36</code></p>
</li>
</ul>
</li>
<li>
<p><strong>更改gem源</strong></p>
<p><strong>国内比较坑，原设置的源在墙外，所以要更改成国内的。</strong></p>
<ul>
<li>
<p>分别执行一下两条命令：</p>
<p><code>sudo gem sources --remove https://rubygems.org/</code></p>
<p><code>sudo gem sources -a https://ruby.taobao.org/</code></p>
</li>
<li>
<p>确保gem源只有【https://ruby.taobao.org/】，执行下面命令查看gem源列表：</p>
<p><code>gem sources -l</code></p>
</li>
</ul>
</li>
<li>
<p><strong>安装ruby-devel</strong></p>
<ul>
<li>
<p>执行命令安装jekyll发现，结果可能出现如下错误：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/tletd.png" alt="2"></p>
</li>
<li>
<p>出现这种情况是因为编译安装jekyll需要安装ruby-devel包：</p>
<p><code>sudo dnf install ruby-devel</code></p>
</li>
</ul>
</li>
</ol>
<h3 id="安装jekyll">安装jekyll</h3>
<ul>
<li>
<p>这时执行安装命令应该就没问题了：</p>
<p><code>sudo gem install jekyll</code></p>
</li>
<li>
<p>验证安装版本，版本号【jekyll 2.5.3】：</p>
<p><code>jekyll -v</code></p>
</li>
</ul>
<p><strong>至此为止，jekyll安装完成。</strong></p>
<h3 id="参考">参考</h3>
<ul>
<li><a href="http://www.it165.net/os/html/201408/9059.html">Linux下安装jekyll</a></li>
<li><a href="blog.csdn.net/cherry_sun/article/details/774362">Ruby gem install Error(mkmf.rb can&rsquo;t find header files for ruby at&hellip;)</a></li>
<li><a href="http://jekyll.bootcss.com/">jekyll</a></li>
</ul>]]></content>
		</item>
		
		<item>
			<title>Jekyll博客添中添加优酷视频</title>
			<link>https://blog.5km.studio/2015/08/22/jekyllAddVideoShare/</link>
			<pubDate>Sat, 22 Aug 2015 17:55:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/08/22/jekyllAddVideoShare/</guid>
			<description>&lt;p&gt;本文将介绍如何为用jekyll生成的博客添加优酷视频。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>本文将介绍如何为用jekyll生成的博客添加优酷视频。</p>
<h3 id="简说">简说</h3>
<p>分享科技乐趣，肯定是少不了视频分享的，youku的视频还是比较常用的，在优酷往往会看到新鲜的科技见闻，分享一下也是不错的。本文以我的一片分享【真 指点江山，谷歌超精细手势识别】为例。</p>
<h3 id="具体实现">具体实现</h3>
<h4 id="i-复制html代码"><strong>I. 复制HTML代码</strong></h4>
<ul>
<li>
<p>进入你要分享的优酷视频页面，比如这个视频：<a href="http://v.youku.com/v_show/id_XMTI1NzgyNjY1Ng==.html#paction">真 指点江山，谷歌超精细手势识别</a>，播放窗口下面找到【分享给好友】，点击<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/0iksy.png" alt="1"> 按钮，会出现如下图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/b6gxc.png" alt="2"></p>
</li>
<li>
<p>点击【html代码：】一栏右面的【复制】按钮，html代码就复制好了。</p>
</li>
</ul>
<h4 id="ii-post中添加html代码"><strong>II. post中添加html代码</strong></h4>
<ul>
<li>
<p>打开要编辑的文章，将代码粘贴到合适位置，注意要将最后的<code>&lt;/embed&gt;</code>删掉，例如我的文章，见下图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/yx60t.png" alt="3"></p>
</li>
<li>
<p>然后将文章推送到github就可以了，类似效果如下所示,<strong>然后就大功告成！！！(^_^)</strong>。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/fnn9j.png" alt="4"></p>
</li>
</ul>]]></content>
		</item>
		
		<item>
			<title>Project Soli 谷歌超精细手势识别</title>
			<link>https://blog.5km.studio/2015/08/22/googleProjectSoli-share/</link>
			<pubDate>Sat, 22 Aug 2015 08:14:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/08/22/googleProjectSoli-share/</guid>
			<description>&lt;p&gt;除了Google X，我们还应该了解Google的ATAP部门，这里将分享这个部门的Project Soli，绝对的黑科技，想象一下，今后打个响指，手指搓一搓，你的可穿戴设备就播放音乐，调节音量了，是不是想想都兴奋了？&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>除了Google X，我们还应该了解Google的ATAP部门，这里将分享这个部门的Project Soli，绝对的黑科技，想象一下，今后打个响指，手指搓一搓，你的可穿戴设备就播放音乐，调节音量了，是不是想想都兴奋了？</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/okls7.png" alt="1"></p>
<p>这一黑科技的核心其实就是将一整套雷达系统集成到一块大小相当于Mini SD卡的芯片中，60Ghz 雷达频段进行高速的扫描，通过雷达的信号对周边动作和手势进行捕捉，是不是相当高冷到没朋友的交互方式？还记得当第一代iPhone发布时，电容触摸屏的人机交互带来了震撼和用户体验，当这个黑科技应用到实际设备中的时候，可能会带来更加震撼的交互体验！</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/7t9s3.png" alt="2"></p>
<p>可应用场景之一：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/xht2t.gif" alt="3"></p>
<h4 id="视频">视频</h4>
<p>看视频感受一下吧！</p>
<p><video src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/share/soli/soli.mp4" poster="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/share/soli/soli.jpg" controls="controls" width="100%">Soli</video></p>]]></content>
		</item>
		
		<item>
			<title>Sublime Text 3安装及简单配置</title>
			<link>https://blog.5km.studio/2015/08/19/sublimeText/</link>
			<pubDate>Wed, 19 Aug 2015 14:14:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/08/19/sublimeText/</guid>
			<description>&lt;p&gt;本文将介绍安装Sublime Text3，以及安装成功后我会做的一些事情。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>本文将介绍安装Sublime Text3，以及安装成功后我会做的一些事情。</p>
<h3 id="前言"><strong>前言</strong></h3>
<p>记得第一次安装sublime text3应该是两年前，当时是因为觉得Arduino IDE太丑还不能输入中文，就决定找个它的替代品，不经意间找到了ST3，它最吸引我的应该是界面了，但之后其功能及扩展性更是吸引我，目前它已成为我经常用的代码及文本的编辑器。每次安装系统，都会安装ST3，本文就说一下安装Sublime Text3，以及安装成功后我会做的一些事情。</p>
<h3 id="简介"><strong>简介</strong></h3>
<p><a href="http://baike.baidu.com/link?url=vGvQYJCEabAC4iUDhw80qloKPvCSenPEm_u1iaLs5VlQqwyjN3sKIuIAESK7FRQvJOYDEG77mOn2dKW-7yKgy_z-ioUQqWO98MYEyeJprTZ7sTp4o3U36OPes-wH8nLlQ_I9BlJy_v85CYS_hg1sVK">Sublime Text 3</a>是一个代码编辑器（Sublime Text是收费软件，但可以无限期试用），也是HTML和散文先进的文本编辑器。Sublime Text是由程序员Jon Skinner于2008年1月份所开发出来，它最初被设计为一个具有丰富扩展功能的Vim，含有丰富的插件。</p>
<h3 id="正文"><strong>正文</strong></h3>
<h4 id="安装sublime-text-3">安装Sublime text 3</h4>
<ol>
<li>到Sublime Text 3官网下载这个跨平台的代码编辑器，<a href="http://www.sublimetext.com/3"><strong>点我下载</strong></a>，下载对应平台的安装包，最新版本号目前是3083。
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/0j96u.png" alt="1"></li>
<li>安装:
<ul>
<li>windows:双击运行exe文件安装即可。</li>
<li>macosx：打开dmg镜像文件，将软件包拖到Applications文件夹里，就算装完毕了。</li>
<li>ubuntu：下载下来是deb安装包，打开命令行，以deb文件下载到了【下载】目录下为例，先执行<code>cd ~/Download</code>，再执行<code>sudo dpkg -i xxxxx.deb</code>即可。</li>
</ul>
</li>
</ol>
<h4 id="插件管理工具">插件管理工具</h4>
<p>为了更方便使用，还需要安装一些自己需要的插件，这得需要安装插件管理工具Package Control，以及进行简单用户配置。</p>
<ul>
<li>
<p>安装Package Control，按下快捷键 <code>CTRL</code>+ ` ，复制粘贴以下命令，并回车就会自动安装，稍等即可，会有提示重启软件，重新启动Sublime就行了。</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">import urllib.request,os<span class="p">;</span> <span class="nv">pf</span> <span class="o">=</span> <span class="s1">&#39;Package Control.sublime-package&#39;</span><span class="p">;</span> <span class="nv">ipp</span> <span class="o">=</span> sublime.installed_packages_path<span class="o">()</span><span class="p">;</span> urllib.request.install_opener<span class="o">(</span> urllib.request.build_opener<span class="o">(</span> urllib.request.ProxyHandler<span class="o">())</span> <span class="o">)</span><span class="p">;</span> open<span class="o">(</span>os.path.join<span class="o">(</span>ipp, pf<span class="o">)</span>, <span class="s1">&#39;wb&#39;</span><span class="o">)</span>.write<span class="o">(</span>urllib.request.urlopen<span class="o">(</span> <span class="s1">&#39;http://sublime.wbond.net/&#39;</span> + pf.replace<span class="o">(</span><span class="s1">&#39; &#39;</span>,<span class="s1">&#39;%20&#39;</span><span class="o">))</span>.read<span class="o">())</span>
</code></pre></div><p>验证安装：按下快捷键【SHIFT+CTRL+P】，输入package的字样就会出现下图样子：
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/u57ni.png" alt="2"></p>
</li>
<li>
<p>简单配置用户配置文件，菜单栏【Preferences-&gt;Settings-User】,即可打开用户配置文件</p>
<ul>
<li>关闭检查更新，加入&quot;update_check&quot;:&ldquo;false&rdquo;。</li>
<li>设置字体，其中xxxxxx是你要设置的字体，我一般用Consolas，加入&quot;font_face&quot;:&ldquo;xxxxxx&rdquo;。</li>
<li>设置字体大小，加入&quot;font_size&quot;:11。
<strong>每个设置项之间要以逗号隔开，如下的样子：</strong>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/z40jx.png" alt="3"></li>
</ul>
</li>
</ul>
<h4 id="安装插件">安装插件</h4>
<p>我只安装了几个必须的插件，按下快捷键【SHIFT+CTRL+P】输入install，一般会选中【Package control:install package】敲回车，需要联网，等一会儿就会出现插件列表，找到对应插件选中安装即可。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/5cufb.png" alt="4"></p>
<ol>
<li>ConvertToUTF8:这是一个解决代码文件中文乱码问题的，这个是非常有必要安装的，可能还得需要安装依赖包codec33，同样的安装方法。</li>
<li>ctags：这是一个强大的插件，可以实现代码中函数跳转，非常有用，还得安装ctags58执行文件，具体安装，见外链&ndash;<a href="http://jingyan.baidu.com/article/63acb44afb532561fcc17ef4.html"><strong>sublime text2/3怎样在windows中配置ctags插件</strong></a></li>
<li>Alignment：这是一款对齐插件，比如等号对齐什么的。</li>
</ol>
<h4 id="配置主题">配置主题</h4>
<p>个人比较喜欢两款主题：flatland和Brogrammer。主题也是以插件的安装方式来安装的。下面以Brogrammer为例说一下安装及配置：</p>
<ol>
<li>安装，通过【package control】找到【Theme-Brogrammer】选中安装即可。</li>
<li>用户配置文件配置，加入下面两条保存即可完成主题配置：
<ul>
<li><code>&quot;theme&quot;: &quot;Brogrammer.sublime-theme&quot;</code></li>
<li><code>&quot;color_scheme&quot;: &quot;Packages/Theme - Brogrammer/brogrammer.tmTheme&quot;</code></li>
</ul>
</li>
<li>效果如下：
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/aiaa0.png" alt="5"></li>
</ol>
<h3 id="总结">总结</h3>
<p>Sublime Text 3是一款相当优秀的文本编辑器，如果经常跟代码打交道这绝对是一个很好的选择，偶然的收获带来想不到的惊喜！</p>]]></content>
		</item>
		
		<item>
			<title>Mac/Fedora22下STM32的Eclipse开发环境搭建</title>
			<link>https://blog.5km.studio/2015/07/25/stm32eclipse/</link>
			<pubDate>Sat, 25 Jul 2015 17:55:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/07/25/stm32eclipse/</guid>
			<description>&lt;p&gt;本文将介绍如何在MacOSX平台下STM32的Eclipse开发环境的搭建。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>本文将介绍如何在MacOSX平台下STM32的Eclipse开发环境的搭建。</p>
<h2 id="提示">提示</h2>
<p>十里发现了更好的MCU开发环境，如果您感兴趣可以移步了解下 <a href="http://platformio.org"><strong>platformio</strong></a>，这个工具可以帮您开发很多单片机平台，而且跨平台支持 <strong>win/mac/linux</strong> ，它能智能下载所开发平台需要的编译及烧写工具，再次说明这个方案比本文的开发方式更简单、更优！阅读 <a href="/2017/11/08/stm32InPIO-start/">stm32开发新方式-platformio</a>简单了解一下如何使用 <strong>paltformio</strong> 在命令行下开发 <strong>stm32</strong> 单片机程序，另外它也支持图形界面开发，<a href="https://docs.platformio.org/en/latest/ide.html#platformio-ide">点我</a>了解。也许 <strong>platformio</strong> 能带给您惊喜！</p>
<h2 id="前言">前言</h2>
<p>工欲善其事必先利其器，做开发，开发工具很重要，windows下STM32的开发环境很好解决，keil就可以。但在Mac OSX和Fedora Linux下这真是个棘手的问题，其实单片机开发，用命令行都一样，只要有交叉编译工具，加上对makefile了解的话，搭建命令编译下载环境也成，但对于makefile我还是个菜鸟，本身又不是专业学计算机的，更希望能找到一个GUI的开发方案。只能不断在网上寻找GUI的开发环境，经过不懈努力，找到了<a href="http://gnuarmeclipse.livius.net/blog/"><strong>Eclipse的arm嵌入式开发插件</strong></a>，<strong>真的特别感谢开发这个插件的开发者们</strong>，本文将介绍如何在MacOSX平台下STM32的Eclipse开发环境的搭建，linux平台下同理也可以搭建，可以参考<a href="http://gnuarmeclipse.livius.net/blog/install/">插件网站</a>。</p>
<h2 id="正文">正文</h2>
<p><strong>直接切入正题，下面就讲解一下，我搭建开发环境的过程，以Mac OSX为主进行叙述，Fedora22有不同的地方会指出，首先说明一下，我的情况：</strong></p>
<ul>
<li>Mac OSX系统/Fedora22</li>
<li>开发板是STM32F103RCT6的最小系统板</li>
<li>下载及仿真工具是stlink v2</li>
</ul>
<h3 id="安装eclipse">安装eclipse</h3>
<ol>
<li>
<p>到eclipse官网下载含CDT插件的eclipse版本：<a href="http://www.eclipse.org/downloads/packages/eclipse-ide-cc-developers/marsr">Eclipse IDE for C/C++ Developers</a>。</p>
</li>
<li>
<p>将下载包解压，并将解压出来的文件夹拖到<code>应用程序</code>文件夹中，完成安装eclipse，可以到lauchpad中找到下面的图标，单击就可启动eclipse。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/joqmh.png" alt="1"></p>
<p><em><strong>Fdora22下解压下载的压缩包，运行解压目录下的eclipse脚本即可启动eclipse。</strong></em></p>
</li>
</ol>
<h3 id="安装gnu-arm-eclipse插件">安装GNU ARM Eclipse插件</h3>
<p>这里只介绍联网安装，离线安装也可以，参考<a href="http://gnuarmeclipse.livius.net/blog/plugins-install/">GNU ARM Eclipse插件的安装指南</a>。联网的方式有两种，一种是利用Eclipse的<strong>Install New Software</strong>，另一种方式是从<strong>Eclipse Marketplace</strong>中安装，建议采用第二种方法。</p>
<h4 id="install-new-software方式">Install New Software方式</h4>
<ol>
<li>启动eclipse，选择菜单栏-『Help』-『Install New Software&hellip;』
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/245kc.png" alt="2"></li>
<li>安装GNU ARM Eclipse插件，输入下面的Name和URL，然后pending，选中所有的工具，一路[Next]，[Accept]，[Finish]即可，安装成功的话会提示要重启Eclipse。
<ul>
<li>name: GNU ARM Eclipse Plug-ins</li>
<li>URL: <a href="http://gnuarmeclipse.sourceforge.net/updates">http://gnuarmeclipse.sourceforge.net/updates</a>
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/x34vu.png" alt="3"></li>
</ul>
</li>
</ol>
<h4 id="eclipse-marketplace方式">Eclipse Marketplace方式</h4>
<ol>
<li>启动eclipse，选择菜单栏-『Help』-『Eclipse Marketplace』</li>
<li>等待一会儿，跳出的窗口会加载插件市场里可用的插件，然后在<strong>Find</strong>栏中输入<strong>GNU</strong>，单击后面的<strong>Go</strong>按钮。</li>
<li>如果操作正确的话应该会看到<strong>GNU ARM Eclipse</strong>的插件，点击<strong>Install</strong>进行安装，按照提示操作进行安装即可。</li>
</ol>
<h3 id="下载stm32的库函数文件包">下载STM32的库函数文件包</h3>
<p>保证插件已经安装成功</p>
<ol>
<li>
<p>在eclipse工具栏上找到<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/r5ued.png" alt="4">（Make the C/C++ packs perspective visible）此工具，并单击，显示pack窗口：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/2gfg5.png" alt="5"></p>
</li>
<li>
<p>点击图标<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/1pmo9.png" alt="6">，刷新，会联网加载最新的库函数文件包列表，如上图，列表会按照公司进行分类，stm32芯片是STmicroelectronics公司的，所以展开此公司列表，我的开发板是stm32f103rct6最小系统板，所以右击<code>STM32F1 Series</code>项『Install』即可安装，开始时是灰色的，等待一段时间下载安装成功后就会变成黑色。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/60w2w.png" alt="7"></p>
</li>
</ol>
<h3 id="iv--交叉编译链的安装">IV.  交叉编译链的安装</h3>
<p>这是一个开源的arm嵌入式交叉编译链工具，要配置好编译环境这是所必需的东西。</p>
<ul>
<li>
<p>首先最新的适合MacOSX版本的<a href="https://launchpad.net/gcc-arm-embedded/+download">TOLLCHAIN(s)</a>下载到桌面:</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/xx6jg.png" alt="8"></p>
</li>
<li>
<p>解压下载的文件，并将解压出来的文件夹移动到<code>/usr/local</code>目录下，因为安装的eclipse插件会自动在<code>/usr/local</code>目录下搜寻TOOLCHAIN(s)，此步的操作要在[Terminal]（即终端）下执行命令完成。其中『gcc-arm-none-eabi-xxxxxxxxxxxxxxxx-mac.tar.bz2』为下载的压缩包文件名，改为自己下载的相应的文件名即可，依次执行下面三条命令：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">sudo mkdir /usr/local
<span class="nb">cd</span> /usr/local
sudo tar xjf ~/Desktop/gcc-arm-none-eabi-xxxxxxxxxxxxx-mac.tar.bz2
</code></pre></div></li>
<li>
<p>在终端下可以验证是否安装成功：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">/usr/local/gcc-arm-none-eabi-xxxxxxxx/bin/arm-none-eabi-gcc --version
</code></pre></div><p>如果出现类似下面的语句，说明成功安装了：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">arm-none-eabi-gcc <span class="o">(</span>GNU Tools <span class="k">for</span> ARM Embedded Processors<span class="o">)</span> 4.8.4 <span class="m">20140526</span> <span class="o">(</span>release<span class="o">)</span> <span class="o">[</span>ARM/embedded-4_8-branch revision 211358<span class="o">]</span>
</code></pre></div></li>
</ul>
<h3 id="配置st-link">配置ST-Link</h3>
<p>因为性价比高，所以ST-Link用的还是比较多的，下面就讲解一下，如何实现在MacOSX下通过st-link对开发板进行仿真及下载程序。</p>
<ul>
<li>
<p>安装Xcode的『Command Line Tools』，这个非常容易的，因为我安装了Xcode所以很简单就能够安装了，不明白自行google解决。</p>
</li>
<li>
<p>安装<a href="http://brew.sh/index_zh-cn.html">Homebrew</a>工具（只适用于Mac，Fedora有dnf工具），此工具可以让mac实现ubuntu下apt-get的类似功能，此步骤主要方便为安装编译st-link源代码所需的依赖包进行的，在终端下输入以下命令敲回车，就可以自动安装homebrew：</p>
<ul>
<li>
<p>安装homebrew:</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">ruby -e <span class="s2">&#34;</span><span class="k">$(</span>curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install<span class="k">)</span><span class="s2">&#34;</span>
</code></pre></div></li>
<li>
<p>安装libusb-1.0，在终端下输入以下命令敲回车安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">sudo brew install libusb libusb-compat
</code></pre></div></li>
<li>
<p>安装pkg-config，在终端下输入以下命令敲回车安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">sudo brew install pkg-config
</code></pre></div></li>
<li>
<p>安装autotools，在终端下输入以下命令敲回车安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">sudo brew install autoconf automake libtool
</code></pre></div><p>这一步中，如果系统是Fedora22，其自带DNF工具，同Homebrew一样只是个工具而已，终端下执行下面的命令安装依赖包：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">sudo dnf install autoconf automake libtool libusb-devel
</code></pre></div></li>
</ul>
</li>
<li>
<p>依次按照以下步骤下载并编译st-link工具，并完成安装，下面都是在终端里执行命令完成的：</p>
<ul>
<li>
<p>进入之前创建的<code>/usr/local</code>目录：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"> <span class="nb">cd</span> /usr/local
</code></pre></div></li>
<li>
<p>利用git下载开源的源代码：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">git clone https://github.com/texane/stlink stlink.git**
</code></pre></div></li>
<li>
<p>进入clone到<code>/usr/local</code>目录下的<code>stlink.git</code>目录：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="nb">cd</span> stlink.git
</code></pre></div></li>
<li>
<p>执行特定脚本，完成配置并完成源代码的编译，从而完成工具的安装：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">./autogen.sh
./configure
make
</code></pre></div></li>
</ul>
</li>
<li>
<p>测试是否安装成功，将stm32f103rct6的最小系统板通过『st-link v2』连接到电脑，执行下面的片子清除命令：</p>
<pre><code>  st-flash erase

若出现`Mass erasing...`的字样说明成功。
</code></pre>
<p><strong>注</strong>：在linux下可能会遇到权限问题，这时需要将stlink.git文件下的rules文件拷贝到<code>/etc/udev/rules.d/</code>目录下，然后重启pc即可。</p>
</li>
<li>
<p>为了能够在任何目录下执行<code>st-flash</code>命令，需要配置用户环境变量，只需将<code>/usr/local/stlink.git</code>目录加入到环境变量里即可，下面编辑文件可以用『vim』，想了解『vim』使用可以看一下我的另一篇文章<a href="http://www.smslit.top/2015/07/16/vimtution"><strong>vim的使用入门</strong></a>：</p>
<ul>
<li>
<p>如果终端用的[bash]，进入**$HOME**，编辑『.bashrc』文件，加入：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span>/usr/local/stlink.git:<span class="nv">$PATH</span>
</code></pre></div></li>
<li>
<p>如果终端用的[zsh]，进入**$HOME**，编辑『.zshrc』文件，加入：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span>/usr/local/stlink.git:<span class="nv">$PATH</span>
</code></pre></div></li>
</ul>
</li>
<li>
<p>stlink的简单使用（以stlink-v2和stm32f103c8t6为例）。</p>
<ul>
<li>
<p>擦除片子命令</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">st-flash erase
</code></pre></div></li>
<li>
<p>烧写程序命令，只能烧写binary文件（二进制bin文件），所以编译应该生成bin文件进行烧写</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">st-flash write xxx.bin 0x8000000
</code></pre></div></li>
</ul>
</li>
</ul>
<h2 id="总结">总结</h2>
<p>总算完成了MacOSX下STM32的Eclipse开发环境的搭建的讲解，目前我也是在学习中，此环境下与windows下的Keil不同，使用的是C++开发语言，更加方便灵活一些，开发环境是，编写代码和编译是用eclipse，下载及仿真都是用终端命令，有空会大体讲解一下终端下简单的仿真。</p>
<h2 id="参考">参考</h2>
<ul>
<li>
<p><a href="http://gnuarmeclipse.livius.net/blog/">Eclipse的arm嵌入式开发插件</a></p>
</li>
<li>
<p><a href="http://www.cnblogs.com/humaoxiao/p/3576732.html">在Mac OS X中搭建STM32开发环境</a>&ndash;来自<a href="http://www.cnblogs.com/humaoxiao/">胡茂晓的BLOG</a>，讲解的是完全命令行的开发环境的搭建。</p>
</li>
</ul>]]></content>
		</item>
		
		<item>
			<title>Jekyll博客添加分享功能</title>
			<link>https://blog.5km.studio/2015/07/19/jekyllShare/</link>
			<pubDate>Sun, 19 Jul 2015 23:55:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/07/19/jekyllShare/</guid>
			<description>&lt;p&gt;本文将介绍如何为用jekyll生成的博客添加网页及图片分享的功能。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>本文将介绍如何为用jekyll生成的博客添加网页及图片分享的功能。</p>
<h3 id="简说">简说</h3>
<p>分享的功能不用多说吧，看到好的网页就会想分享给更多人，添加此功能是有必要的，万一我们写了一篇绝好的博文咋办呢，哈哈，这里介绍的是调用百度分享通过简单设置生成的代码。</p>
<h3 id="具体实现">具体实现</h3>
<h4 id="搜索百度分享">搜索[百度分享]</h4>
<p>搜索[百度分享]后，一般会出来如下图所示的条目，选择进入该条目：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/nf53f.png" alt="1"></p>
<h4 id="复制js代码">复制JS代码</h4>
<p>根据自己需要进行三步配置，复制JS代码</p>
<ol>
<li>
<p>进入条目后，选择免费获取代码：
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/f0l77.png" alt="2"></p>
</li>
<li>
<p>这里讲解[自由版式]选择，首先根据分享需求选择分享功能，有三种可选，三种我都选择了：
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/iaqzn.png" alt="3"></p>
</li>
<li>
<p>点击『下一步』，进入『页面按钮设置』，我的设置如下：
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/8boff.png" alt="4"></p>
</li>
<li>
<p>点击『下一步』，进入『图片按钮设置』，我的设置为默认设置：
<img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/8boff.png" alt="4"></p>
</li>
<li>
<p>点击『下一步』，即可『获取代码』，我的设置对应的<strong>JS代码</strong>如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-javascript" data-lang="javascript"><span class="o">&lt;</span><span class="nx">div</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&#34;bdsharebuttonbox&#34;</span><span class="o">&gt;&lt;</span><span class="nx">a</span> <span class="nx">href</span><span class="o">=</span><span class="s2">&#34;#&#34;</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&#34;bds_more&#34;</span> <span class="nx">data</span><span class="o">-</span><span class="nx">cmd</span><span class="o">=</span><span class="s2">&#34;more&#34;</span><span class="o">&gt;&lt;</span><span class="err">/a&gt;&lt;a href=&#34;#&#34; class=&#34;bds_qzone&#34; data-cmd=&#34;qzone&#34; title=&#34;分享到QQ空间&#34;&gt;&lt;/a&gt;&lt;a href=&#34;#&#34; class=&#34;bds_tsina&#34; data-cmd=&#34;tsina&#34; title=&#34;分享到新浪微博&#34;&gt;&lt;/a&gt;&lt;a href=&#34;#&#34; class=&#34;bds_tqq&#34; data-cmd=&#34;tqq&#34; title=&#34;分享到腾讯微博&#34;&gt;&lt;/a&gt;&lt;a href=&#34;#&#34; class=&#34;bds_renren&#34; data-cmd=&#34;renren&#34; title=&#34;分享到人人网&#34;&gt;&lt;/a&gt;&lt;a href=&#34;#&#34; class=&#34;bds_weixin&#34; data-cmd=&#34;weixin&#34; title=&#34;分享到微信&#34;&gt;&lt;/a&gt;&lt;/div&gt;</span>
<span class="o">&lt;</span><span class="nx">script</span><span class="o">&gt;</span><span class="nb">window</span><span class="p">.</span><span class="mi">_</span><span class="nx">bd_share_config</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;common&#34;</span><span class="o">:</span><span class="p">{</span><span class="s2">&#34;bdSnsKey&#34;</span><span class="o">:</span><span class="p">{},</span><span class="s2">&#34;bdText&#34;</span><span class="o">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="s2">&#34;bdMini&#34;</span><span class="o">:</span><span class="s2">&#34;2&#34;</span><span class="p">,</span><span class="s2">&#34;bdMiniList&#34;</span><span class="o">:</span><span class="kc">false</span><span class="p">,</span><span class="s2">&#34;bdPic&#34;</span><span class="o">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="s2">&#34;bdStyle&#34;</span><span class="o">:</span><span class="s2">&#34;1&#34;</span><span class="p">,</span><span class="s2">&#34;bdSize&#34;</span><span class="o">:</span><span class="s2">&#34;16&#34;</span><span class="p">},</span><span class="s2">&#34;share&#34;</span><span class="o">:</span><span class="p">{},</span><span class="s2">&#34;image&#34;</span><span class="o">:</span><span class="p">{</span><span class="s2">&#34;viewList&#34;</span><span class="o">:</span><span class="p">[</span><span class="s2">&#34;qzone&#34;</span><span class="p">,</span><span class="s2">&#34;tsina&#34;</span><span class="p">,</span><span class="s2">&#34;tqq&#34;</span><span class="p">,</span><span class="s2">&#34;renren&#34;</span><span class="p">,</span><span class="s2">&#34;weixin&#34;</span><span class="p">],</span><span class="s2">&#34;viewText&#34;</span><span class="o">:</span><span class="s2">&#34;分享到：&#34;</span><span class="p">,</span><span class="s2">&#34;viewSize&#34;</span><span class="o">:</span><span class="s2">&#34;16&#34;</span><span class="p">},</span><span class="s2">&#34;selectShare&#34;</span><span class="o">:</span><span class="p">{</span><span class="s2">&#34;bdContainerClass&#34;</span><span class="o">:</span><span class="kc">null</span><span class="p">,</span><span class="s2">&#34;bdSelectMiniList&#34;</span><span class="o">:</span><span class="p">[</span><span class="s2">&#34;qzone&#34;</span><span class="p">,</span><span class="s2">&#34;tsina&#34;</span><span class="p">,</span><span class="s2">&#34;tqq&#34;</span><span class="p">,</span><span class="s2">&#34;renren&#34;</span><span class="p">,</span><span class="s2">&#34;weixin&#34;</span><span class="p">]}};</span><span class="kd">with</span><span class="p">(</span><span class="nb">document</span><span class="p">)</span><span class="mi">0</span><span class="p">[(</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">&#39;head&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span><span class="o">||</span><span class="nx">body</span><span class="p">).</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;script&#39;</span><span class="p">)).</span><span class="nx">src</span><span class="o">=</span><span class="s1">&#39;http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion=&#39;</span><span class="o">+~</span><span class="p">(</span><span class="o">-</span><span class="k">new</span> <span class="nb">Date</span><span class="p">()</span><span class="o">/</span><span class="mf">36e5</span><span class="p">)];</span><span class="o">&lt;</span><span class="err">/script&gt;</span>
</code></pre></div><p><em><em>中途其实也可以都按照默认设置，点击</em>『直接获取代码』<em>来获取代码。</em></em></p>
</li>
</ol>
<h4 id="修改布局文件">修改布局文件</h4>
<p>将JS代码贴到对应html文件中</p>
<p>将<strong>JS代码</strong>拷贝到需要添加分享功能页面的HTML文件里即可，如果要在博客主页添加分享功能，就要把代码贴到<code>jekyll工程文件</code>根目录下的<code>index.html</code>文件中，如果要为博文页面添加分享功能那应该把代码贴到<code>_layout</code>文件夹下的<code>post.html</code>文件中。</p>
<p><strong>页面分享按钮就像本文中最下面的样子所示。</strong></p>
<h4 id="参考">参考</h4>
<p><a href="http://www.divcss5.com/wenzhai/h763.shtml"><strong>网站网页中加入各种分享按钮功能 百度分享</strong></a></p>]]></content>
		</item>
		
		<item>
			<title>Jekyll博客添加多说评论</title>
			<link>https://blog.5km.studio/2015/07/16/jekyllCommit/</link>
			<pubDate>Thu, 16 Jul 2015 23:07:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/07/16/jekyllCommit/</guid>
			<description>&lt;p&gt;jekyll生成的blog，虽然是静态轻量级的和静态的，但我们仍然可以尽量在功能上使其成为一个一个完整的博客。静态博客要实现评论功能必须依赖第三方评论系统，本文将介绍如何为自己的博客添加评论的功能。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>jekyll生成的blog，虽然是静态轻量级的和静态的，但我们仍然可以尽量在功能上使其成为一个一个完整的博客。静态博客要实现评论功能必须依赖第三方评论系统，本文将介绍如何为自己的博客添加评论的功能。</p>
<h3 id="多说的介绍">『多说』的介绍</h3>
<p>多说是追求最佳用户体验的社会化评论框，为中小网站提供新浪微博、QQ（QQ空间和腾讯微博）、人人、开心网、豆瓣、网易微博、搜狐微博、百度、淘宝、Google等多帐号登录并评论功能。它帮你搭建更活跃，互动性更强的评论平台。具有众多实用特性，功能强大且永久免费。</p>
<h3 id="具体实现">具体实现</h3>
<h4 id="注册多说httpduoshuocom">注册<a href="http://duoshuo.com/">多说</a></h4>
<p>注册账户，并创建多说站点。</p>
<h5 id="登录">登录</h5>
<p>先用现有的微博、qq或百度帐号什么的登录多说</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/u94vt.png" alt="1"></p>
<h5 id="跳转">跳转</h5>
<p>登录后，再回到首页，点击『我要安装』，然后就跳转到创建站点的页面。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/d0b1l.png" alt="2"></p>
<h5 id="创建站点">创建站点</h5>
<p>创建属于自己的站点。</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/zivhx.png" alt="3"></p>
<h4 id="配置">配置</h4>
<p>根据自己需要进行配置，复制JS代码</p>
<h5 id="js代码">JS代码</h5>
<p>这里我用默认配置生成的JS代码，依次选择标签『工具』-『获取代码』-『通用代码』，点击复制即可将代码复制到粘贴板上：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/w5x1p.png" alt="4"></p>
<p><strong>通用代码如下:</strong></p>
<div class="highlight"><pre class="chroma"><code class="language-javascript" data-lang="javascript"><span class="c">&lt;!--</span> <span class="nx">多说评论框</span> <span class="nx">start</span> <span class="o">--&gt;</span>
	<span class="o">&lt;</span><span class="nx">div</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&#34;ds-thread&#34;</span> <span class="nx">data</span><span class="o">-</span><span class="nx">thread</span><span class="o">-</span><span class="nx">key</span><span class="o">=</span><span class="s2">&#34;请将此处替换成文章在你的站点中的ID&#34;</span> <span class="nx">data</span><span class="o">-</span><span class="nx">title</span><span class="o">=</span><span class="s2">&#34;请替换成文章的标题&#34;</span> <span class="nx">data</span><span class="o">-</span><span class="nx">url</span><span class="o">=</span><span class="s2">&#34;请替换成文章的网址&#34;</span><span class="o">&gt;&lt;</span><span class="err">/div&gt;</span>
<span class="c">&lt;!--</span> <span class="nx">多说评论框</span> <span class="nx">end</span> <span class="o">--&gt;</span>
<span class="c">&lt;!--</span> <span class="nx">多说公共JS代码</span> <span class="nx">start</span> <span class="p">(</span><span class="nx">一个网页只需插入一次</span><span class="p">)</span> <span class="o">--&gt;</span>
<span class="o">&lt;</span><span class="nx">script</span> <span class="nx">type</span><span class="o">=</span><span class="s2">&#34;text/javascript&#34;</span><span class="o">&gt;</span>
<span class="kd">var</span> <span class="nx">duoshuoQuery</span> <span class="o">=</span> <span class="p">{</span><span class="nx">short_name</span><span class="o">:</span><span class="s2">&#34;请将此处替换为你的多说站点的短名short_name&#34;</span><span class="p">};</span>
	<span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
		<span class="kd">var</span> <span class="nx">ds</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;script&#39;</span><span class="p">);</span>
		<span class="nx">ds</span><span class="p">.</span><span class="nx">type</span> <span class="o">=</span> <span class="s1">&#39;text/javascript&#39;</span><span class="p">;</span><span class="nx">ds</span><span class="p">.</span><span class="kr">async</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
		<span class="nx">ds</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">protocol</span> <span class="o">==</span> <span class="s1">&#39;https:&#39;</span> <span class="o">?</span> <span class="s1">&#39;https:&#39;</span> <span class="o">:</span> <span class="s1">&#39;http:&#39;</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;//static.duoshuo.com/embed.js&#39;</span><span class="p">;</span>
		<span class="nx">ds</span><span class="p">.</span><span class="nx">charset</span> <span class="o">=</span> <span class="s1">&#39;UTF-8&#39;</span><span class="p">;</span>
		<span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">&#39;head&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> 
		 <span class="o">||</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">&#39;body&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]).</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">ds</span><span class="p">);</span>
	<span class="p">})();</span>
	<span class="o">&lt;</span><span class="err">/script&gt;</span>
<span class="c">&lt;!--</span> <span class="nx">多说公共JS代码</span> <span class="nx">end</span> <span class="o">--&gt;</span>
</code></pre></div><p><strong>按照代码中中文提示替换成对应自己网页的设置就可以了。</strong>
<strong>一般的:</strong></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/i234m.png" alt="5"></p>
<h5 id="编辑布局文件">编辑布局文件</h5>
<p>将修改好的JS代码粘贴到<code>_layout</code>文件夹下post中配置布局layout对应的html文件的最后，即可实现如此博文下面的评论框。</p>
<h4 id="修改样式">修改样式</h4>
<p>修改css进行评论框样式更改</p>
<p>你会发现我的评论框对应头像是圆的，就是添加样式配置实现的。</p>
<p>下面要添加的代码均添加到<code>_sass</code>文件夹下的的<code>_layout.scss</code>文件中。</p>
<h5 id="实现其他样式">实现其他样式</h5>
<p>实现圆形头像，并可以在鼠标置于上面时，头像旋转360度</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">#</span><span class="nn">ds-reset</span> <span class="p">.</span><span class="nc">ds-avatar</span> <span class="nt">img</span> <span class="p">{</span>
  <span class="k">width</span><span class="p">:</span> <span class="mi">54</span><span class="kt">px</span> <span class="cp">!important</span><span class="p">;</span>
  <span class="k">height</span><span class="p">:</span> <span class="mi">54</span><span class="kt">px</span> <span class="cp">!important</span><span class="p">;</span>
  <span class="kp">-webkit-</span><span class="k">border-radius</span><span class="p">:</span> <span class="mi">27</span><span class="kt">px</span> <span class="cp">!important</span><span class="p">;</span>
  <span class="kp">-moz-</span><span class="k">border-radius</span><span class="p">:</span> <span class="mi">27</span><span class="kt">px</span> <span class="cp">!important</span><span class="p">;</span>
  <span class="k">border-radius</span><span class="p">:</span> <span class="mi">27</span><span class="kt">px</span> <span class="cp">!important</span><span class="p">;</span>
  <span class="kp">-webkit-</span><span class="k">transition</span><span class="p">:</span> <span class="kp">-webkit-</span><span class="k">transform</span> <span class="mf">0.4</span><span class="kt">s</span> <span class="kc">ease-out</span><span class="p">;</span>
  <span class="kp">-moz-</span><span class="k">transition</span><span class="p">:</span> <span class="kp">-moz-</span><span class="k">transform</span> <span class="mf">0.4</span><span class="kt">s</span> <span class="kc">ease-out</span><span class="p">;</span>
  <span class="k">transition</span><span class="p">:</span> <span class="k">transform</span> <span class="mf">0.4</span><span class="kt">s</span> <span class="kc">ease-out</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">#</span><span class="nn">ds-reset</span> <span class="p">.</span><span class="nc">ds-avatar</span> <span class="nt">img</span><span class="p">:</span><span class="nd">hover</span> <span class="p">{</span>
  <span class="kp">-webkit-</span><span class="k">transform</span><span class="p">:</span> <span class="nb">rotateZ</span><span class="p">(</span><span class="mi">360</span><span class="kt">deg</span><span class="p">);</span>
  <span class="kp">-moz-</span><span class="k">transform</span><span class="p">:</span> <span class="nb">rotateZ</span><span class="p">(</span><span class="mi">360</span><span class="kt">deg</span><span class="p">);</span>
  <span class="k">transform</span><span class="p">:</span> <span class="nb">rotateZ</span><span class="p">(</span><span class="mi">360</span><span class="kt">deg</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div><h5 id="隐藏">隐藏</h5>
<p>隐藏评论框底下显示“xx正在使用多说”字样</p>
<div class="highlight"><pre class="chroma"><code class="language-javascript" data-lang="javascript"><span class="err">#</span><span class="nx">ds</span><span class="o">-</span><span class="nx">reset</span> <span class="p">.</span><span class="nx">ds</span><span class="o">-</span><span class="nx">powered</span><span class="o">-</span><span class="nx">by</span> <span class="p">{</span>
  <span class="nx">display</span><span class="o">:</span> <span class="nx">none</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><h4 id="留言板">留言板</h4>
<p>为博客开发留言板功能</p>
<p><strong>其实就是开辟一个新的导航项『Message』,在其中对应的layout布局文件中加入上面的评论JS代码。</strong></p>
<p><strong>效果如本网站的<a href="/message">Message页</a></strong></p>
<h4 id="参考">参考</h4>
<p><a href="http://liberize.me/tech/jekyll-use-duoshuo-comment-system.html"><strong>Jekyll 使用多说评论系统</strong></a></p>]]></content>
		</item>
		
		<item>
			<title>vim的使用入门</title>
			<link>https://blog.5km.studio/2015/07/16/vimtution/</link>
			<pubDate>Thu, 16 Jul 2015 02:44:46 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2015/07/16/vimtution/</guid>
			<description>&lt;p&gt;Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富，在程序员中被广泛使用。和Emacs并列成为类Unix系统用户最喜欢的编辑器。在Mac OSX下vim是自带的，是在命令行下文本编辑器。&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富，在程序员中被广泛使用。和Emacs并列成为类Unix系统用户最喜欢的编辑器。在Mac OSX下vim是自带的，是在命令行下文本编辑器。</p>
<p>###声明：
使用平台为Mac OSX。强调的是实践操作，所以在每一个命令使用时都应实际操练。文中描述的命令，不包含『』符号，只需输入内部的命令。</p>
<h3 id="入门">入门：</h3>
<h4 id="第一部分-光标移动及进入vim">第一部分 光标移动及进入vim</h4>
<ol>
<li>在Terminal中输入『vim 文件名 』&lt;敲回车&gt;，就能进入对于文件的vim界面，默认进入的是命令状态，等候命令输入。</li>
<li><ESC> 从编辑状态进入正常命令状态，然后输入『:q!』&lt;敲回车&gt;(不保存退出)或『:wq』&lt;敲回车&gt;(保存后退出)</li>
<li>在命令状态下输入『i』，即刻进入编辑插入状态，&lt;敲ESC&gt;可退出到正常命令状态。</li>
<li>在正常模式下，输入『x』，删除光标处的单个字符。</li>
<li>正常模式下，光标移动命令：
<ul>
<li>h:左移</li>
<li>j:下移</li>
<li>k:上移</li>
<li>l:右移</li>
</ul>
</li>
</ol>
<p><em>演示: 用vim打开文件test.md</em></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/xk5fi.gif" alt="1"></p>
<h4 id="第二部分-删除命令">第二部分 删除命令</h4>
<ol>
<li>从光标处删除到单字/词末尾，输入命令『dw』</li>
<li>从光标处删除到行末，输入命令『d$』</li>
<li>删除整行，输入命令『dd』</li>
<li>撤销操作，输入命令『u』；撤销在一行中的操作，输入命令『U』；恢复操作，输入命令『CTRL+R』</li>
<li>正常命令模式下，命令有一定格式：[command][number][object]或[number][command][object]
<ul>
<li>[command]-代表要做的事情，比如删除命令『d』</li>
<li>[number ]-代表命令要执行的次数</li>
<li>[object ]-代表要操作的对象，『w』代表单字/词，『$』代表到行末等等。</li>
</ul>
</li>
</ol>
<p><em>演示</em></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/93fgs.gif" alt="2"></p>
<h4 id="第三部分-更改字符单词">第三部分 更改字符/单词</h4>
<ol>
<li>粘贴已经删除的文本，输入命令『p』。该操作会把以及给你删除的内容粘贴到光标后面，如果删除的是整行文本，将会在光标的下面重新插入一行，内容为删除的文本。</li>
<li>要替换光标位置的字符，输入命令『r字符』，如『ra』，就是把光标处字符替换成字符a。</li>
<li>改变指定的对象，从光标位置知道对象末尾，比如『cw』，就是更改光标到单词末尾的的内容；『c$』，就是更改光标到行末的内容。</li>
</ol>
<p><em>演示</em></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/93cxi.gif" alt="3"></p>
<h4 id="第四部分-查找替换及跳转">第四部分 查找/替换及跳转</h4>
<ol>
<li>『ctrl-g』用来现实当前光标所在位置和文件状态信息。</li>
<li>『shift-g』用于将光标跳转到文件最后一行，如果先敲入一个数字，此数字就是代表文本行号，按下『shift-g』后光标就会跳转到此行。</li>
<li>查找字符串，输入命令『/要查找的字符串』，就会从当前光标位置向后查找，比如『/boy』&lt;敲回车&gt;，就会向后查找字符串boy；命令『?要查找的字符串』则是向前查找；完成一次查找命令后，输入『n』，会按前次查找方向继续查找。</li>
<li>查找光标位置配对的(、)、[、]、{、}，输入命令『%』。</li>
<li>替换字符
<ul>
<li>替换一行内首个字符串old为新字符串new，输入命令『:s/old/new』&lt;敲回车&gt;;</li>
<li>替换一行内所有字符串old为新字符串new，输入命令『:s/old/new/g』&lt;敲回车&gt;;</li>
<li>替换两行内所有字符串old为新字符串new，输入命令『:#,#s/old/new/g』&lt;敲回车&gt;;</li>
<li>替换文件内所有字符串old为新字符串new，输入命令『:%s/old/new/g』&lt;敲回车&gt;;</li>
<li>全文替换字符串old为新字符串new时询问用户确认，输入命令『:%s/old/new/gc』&lt;敲回车&gt;。</li>
</ul>
</li>
</ol>
<p><em>演示</em></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/yhiht.gif" alt="4"></p>
<h4 id="第五部分-执行sh命令提取文件内容">第五部分 执行sh命令/提取文件内容</h4>
<ol>
<li>执行一个外部命令cmd，输入命令『:!cmd』，如：『:!pwd』，用来显示当前目录。</li>
<li>将正在编辑的文件保存为FILENAME的文件，输入命令『:w FILENAME』&lt;敲回车&gt;,如『:w test.cpp』，就是将编辑的文件保存为test.cpp文件。</li>
<li>将当前编辑文件中第#行到第#行的内容保存到FILENAME文件中，输入命令『:#,#w FILENAME』，比如：『:12,67w test.c』，就是把编辑文件的第12行到67行的内容保存到test.c文件中。</li>
<li>提取文件FILENAME中的内容到光标处，输入命令『:r FILENAME』。</li>
</ol>
<p><em>演示</em></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/6a7hn.gif" alt="5"></p>
<h4 id="第六部分-插入新行文本替换模式">第六部分 插入新行/文本，替换模式</h4>
<ol>
<li>在光标所在行下方插入新的一行并将光标置于行首，输入命令『o』；在光标所在行上方插入新的一行并将光标置于行首，输入命令『O』。</li>
<li>在光标所在位置之后插入文本，输入命令『a』；在光标所在行的行末之后插入文本，输入命令『A』。</li>
<li>进入文本替换模式，输入命令『R』。</li>
<li>vim选项配置命令，格式为：『set xxx』</li>
</ol>
<p><em>演示</em></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/bysdd.gif" alt="6"></p>
<h4 id="第七部分-复制vim模板配置">第七部分 复制vim模板配置</h4>
<p>创建一个vim启动脚本，将例程配置导入脚本文件『~/.vimrc』，添加vim下代码高亮功能按照下面几部完成：</p>
<ol>
<li>进入~（用户目录），创建『.vimrc』文件，输入命令：『vim .vimrc』.</li>
<li>将例程配置导入到『.vimrc』文件，输入命令：『:read $VIMRUNTIME/vimrc_example.vim』&lt;敲回车&gt;</li>
<li>保存并退出，输入命令『:wq』&lt;敲回车&gt;</li>
</ol>
<p><em>演示</em></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ppx0b.gif" alt="7"></p>
<h3 id="鸣谢">鸣谢</h3>
<p>主要参考vimtutor，感谢作者 <strong>梁昌泰</strong> <a href="mailto:beos@turbolinux.com.cn">beos@turbolinux.com.cn</a></p>]]></content>
		</item>
		
		<item>
			<title>Arduino UNO R3 软件实现HID键盘功能</title>
			<link>https://blog.5km.studio/2014/03/27/ArduinoUnoHIDKeyboard/</link>
			<pubDate>Thu, 27 Mar 2014 17:11:40 +0800</pubDate>
			
			<guid>https://blog.5km.studio/2014/03/27/ArduinoUnoHIDKeyboard/</guid>
			<description>&lt;p&gt;本文将介绍如何用Arduino UNO R3 软件实现HID键盘功能。&lt;/p&gt;
&lt;p&gt;本文搬自我之前在Arduino中文社区的帖子-&lt;a href=&#34;http://www.arduino.cn/thread-5107-1-1.html&#34;&gt;UNO R3 软件实现HID键盘功能&lt;/a&gt;，所以本文的时间节点与帖子一致。在Arduino中文社区里我叫bboxer，我真的会bbox幺，B h S h~，哈哈！&lt;/p&gt;</description>
			<content type="html"><![CDATA[<p>本文将介绍如何用Arduino UNO R3 软件实现HID键盘功能。</p>
<p>本文搬自我之前在Arduino中文社区的帖子-<a href="http://www.arduino.cn/thread-5107-1-1.html">UNO R3 软件实现HID键盘功能</a>，所以本文的时间节点与帖子一致。在Arduino中文社区里我叫bboxer，我真的会bbox幺，B h S h~，哈哈！</p>
<h3 id="原理"><strong>原理</strong></h3>
<p>极客工坊有帖子是搭硬件实现HID设备功能，但经搜索和了解，不用搭电路照样让我们的Arduino uno r3作HID 键盘或鼠标，这全归功于板子上的<strong>atemga16u芯片</strong>，平常是作为usb转串口的其实这是内部程序实现的，也就是说通过给它换所谓的bootloader即可实现其keyboard或鼠标功能，我只尝试了键盘的实现，所以这里只讲做键盘这方面的。这里先讲述一下，基本的工作原理如下图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/gh5q3.jpg" alt="1"></p>
<p>根据上图，你可能会有疑问，HID识别码是个什么东西，其实这是我自己这么叫的，可以认为就是按键ID，用来告诉<strong>atmega16u2</strong>哪些按键按下了，这里有个规则，如图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/v9mg7.jpg" alt="2"></p>
<p><em><strong>说明：特殊按键，就是一些修饰键，如ctrl，shift，alt等，这里这么简单说，可能会不太理解，结合下面具体做法就会明白了。</strong></em></p>
<h3 id="实现"><strong>实现</strong></h3>
<h4 id="atmega328p的程序编写和下载">ATmega328p的程序编写和下载</h4>
<h5 id="下载对应支持库">下载对应支持库</h5>
<p>其实就是简单的串口通信封装的一些方法和对应按键的按键ID的宏定义，<a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/arduino/keyboard/USBKeyboard.zip">点我</a>下载库，库下载后解压安装。</p>
<p><em><strong>说明一下</strong></em>之前库是下载的网上的，不过有个小毛病，没考虑到同时按下多个键的情况，而且每发送一个按键按下的消息后都会发送按键松开的消息，我稍微修改了一下，将按键松开的消息独立出来，同时添加了一些同时按下按键的方法。代码里未做注释，不过能看懂，在这里稍稍说一下：</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>代码</th>
</tr>
</thead>
<tbody>
<tr>
<td>以字符串的形式发送消息的方法：</td>
<td>Keyboard.print(),例如Keyboard.print(&ldquo;helloWorld&rdquo;)；//区分大小写</td>
</tr>
<tr>
<td>只发送单个按键按下不松消息的方法：</td>
<td>Keyboard.sendKeyStrokeUnReleased(KEY_A);//按下按键A且不松开或Keyboard.sendKeyStroke(KEY_A， 0);//第二个参数是修饰键ID（shift等），用的话要使用对应宏定义见hid_keys.h</td>
</tr>
<tr>
<td>只发送按键松开的消息，其实就是HID码全是0：</td>
<td>Keyboard.sendKeyReleased();//没有按下按键</td>
</tr>
<tr>
<td>同时按下两个按键的方法：</td>
<td>如,Keyboard.sendKeyStrokeUnReleased(KEY_A, KEY_B, 0);//同时按下A、B键</td>
</tr>
</tbody>
</table>
<h5 id="编写代码及下载">编写代码及下载</h5>
<p>可打开刚安装的库的Example，其中有个helloworld，或者自己创建新的sketchbook如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">&lt;USBKeyboard.h&gt;</span><span class="cp">
</span><span class="cp"></span> 
<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> 
<span class="p">{</span>
  <span class="n">Keyboard</span><span class="p">.</span><span class="n">init</span><span class="p">();</span>
<span class="p">}</span>
 
<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> 
<span class="p">{</span>
  <span class="n">delay</span><span class="p">(</span><span class="mi">5000</span><span class="p">);</span>
  <span class="n">Keyboard</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">&#34;hello world&#34;</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div><p>编译上传到uno板子，当然你也可以编写属于自己的代码，如此就完成第一步工作了。</p>
<h4 id="atmega16u2的程序编写和下载">ATmega16u2的程序编写和下载</h4>
<p>下载bootloader的方法，论坛里有相关帖子，网上也有很多，自己多搜搜看看吧，搜到的台湾的一个论坛方法是让atmega16u2进入DFU模式，但悲催的是，我的板子是国内山寨货不能进入DFU模式，无语，便宜果然没好货，感兴趣的可以看最后我贴的帖子地址，之后又搜了好多资料，终于找到适合我自己用的方法，因为我只有ft232r usb转串口线，所以我参考了YFduino论坛里<strong>YFRobot</strong>的帖子：<a href="http://www.yfrobot.com/forum.php?mod=viewthread&amp;tid=2401">利用ft232r的Bitbang模式给arduino控制器重新烧写bootloader</a>，因为以我的条件我只能用这种方法，在此感谢！</p>
<h5 id="avrdude压缩包">avrdude压缩包</h5>
<p><a href="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/arduino/keyboard/avrdude.zip">点我</a>下载avrdude压缩包,
压缩包里有avrdude和其对应的avrdude-GUI以及用到的两个bootloader，一个是支持keyboard的Arduino-keyboard-0.3.hex和原来的做usb转串口的Arduino-usbserial-uno.hex</p>
<h5 id="atmega16u2烧写专用程序">Atmega16u2烧写专用程序</h5>
<p>将FT232R串口线与uno r3板子上的Atmega16u2连接,如图：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/rdq47.jpg" alt="3"></p>
<p>按照图示步骤操作：</p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/a6c4k.jpg" alt="4"></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/pwhvk.jpg" alt="5"></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/ni47v.jpg" alt="6"></p>
<p><img src="https://pichome-1254392422.cos.ap-chengdu.myqcloud.com/img/oga5y.jpg" alt="7"></p>
<p>这就完成了两大工作，之后将Ft232r串口线与板子断开，将Arduino UNO r3板子通过他的USb口连接电脑，在电脑上找个能输文本的地方，单击光标闪动，你会看到每隔5s就会有一个helloWorld字符串出现，怎么样不错吧，利用这个探索其它的玩法吧！</p>
<h6 id="如果想让板子回到原来的状态为atmega16u2下载arduino-usbserial-unohex即可">如果想让板子回到原来的状态，为Atmega16u2下载Arduino-usbserial-uno.hex即可</h6>
<h3 id="参考">参考</h3>
<ol>
<li>Cooper Maa论坛（台湾的）帖子：（可能需要翻墙）
<ul>
<li><a href="http://yehnan.blogspot.com/2013/08/arduino-unousb.html">讓Arduino Uno變成USB鍵盤</a></li>
<li><a href="http://coopermaa2nd.blogspot.tw/2011/11/usbkeyboard-library.html">USBKeyboard Library</a></li>
</ul>
</li>
<li>YFduino
<ul>
<li><a href="http://www.yfrobot.com/forum.php?mod=viewthread&amp;tid=2401">利用ft232r的Bitbang模式给arduino控制器重新烧写bootloader</a></li>
</ul>
</li>
<li>极客工坊
<ul>
<li><a href="http://www.geek-workshop.com/thread-658-1-1.html">arduino uno mega2560等各种不同板子bootloader烧写方法</a></li>
</ul>
</li>
</ol>]]></content>
		</item>
		
	</channel>
</rss>
