tag:blogger.com,1999:blog-54888037425377238232024-02-17T10:36:41.350-08:00Srool The KnifeProgramming Fun & AdventureIsrael Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.comBlogger34125tag:blogger.com,1999:blog-5488803742537723823.post-19973001248584059842024-02-17T09:51:00.000-08:002024-02-17T10:35:05.537-08:00Automating WhatsApp - Part 2: On Windows Machines<p> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuDxF2ogb-MbiVnokxA4n5B4FIg1QHSMak7nZInO8P8ywv5DJRsu7r6eFmGshUjYskFEgc3wx_dlUJqfyVjop8WiMbct6BF9Qcwb3yAV5kmpwy7UXuLc-O-N-kB_aikDwlwN63zMTgpCfxrntNQcVZUWhQBCm0VQf1V84gyGE3LKHTO6n_4JnXmnRnW04/s1024/whatappAuto.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="1024" data-original-width="1024" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuDxF2ogb-MbiVnokxA4n5B4FIg1QHSMak7nZInO8P8ywv5DJRsu7r6eFmGshUjYskFEgc3wx_dlUJqfyVjop8WiMbct6BF9Qcwb3yAV5kmpwy7UXuLc-O-N-kB_aikDwlwN63zMTgpCfxrntNQcVZUWhQBCm0VQf1V84gyGE3LKHTO6n_4JnXmnRnW04/s320/whatappAuto.png" width="320" /></a></p><p><br /></p><p>In my previous post (<a href="https://www.srooltheknife.com/2024/02/automating-whatsapp-using-applescript.html">Automating WhatsApp Using AppleScript</a>), I created a simple script that allows a personal user to make delivering personalized messages using WhatsApp streamlined (still requiring some manual work, but very little...).</p><p>The problem with the previous solution was that it only works on Mac, and I know many of my friends are using Windows machines. Some asked me to adapt my solution to Windows, so I had to install Parallels on my Mac (a very smooth and nice experience - more on that later), and instal Visual Basic .Net (the free community edition), and re-learn my VisualBasic/.Net skills... </p><p>The solution I came up with is not totally robust and has some drawbacks, but it might help someone, so here it is..</p><h4 style="text-align: left;">How To Use</h4><p>1. Install the app: and Installer for the app is available here: <a href="https://bit.ly/3I2mSu7">https://bit.ly/3I2mSu7</a>, the file is a zip file with a setup.exe installer. There is also a sample data files folder to help you get started.</p><p>2. Make Sure Microsoft Edge is your default browser, and log in to web.whatsapp.com on the browser before you run the tool. Close WhatsApp tabs, but leave the session open (have some other tab open).</p><p>Run the TellMyFriends app, select the data file (data.csv), which should live in a folder alongside the text templates (explained later). Once you select the file, a preview of your list is shown, and you can then click the start button when you want to send the messages.</p><p>3. For each recipient, the tool will open the browser with a whatsapp tab, and start a chat with the phone number, and the message for the user.</p><p>Example Files</p><p>1, Data.csv</p><p style="text-align: left;"></p><blockquote><p style="text-align: left;"><span style="font-family: courier; font-size: x-small;">name,phone,template</span></p><p style="text-align: left;"><span style="font-family: courier; font-size: x-small;">John,12123334444,friend.txt</span></p><p style="text-align: left;"><span style="font-family: courier; font-size: x-small;">Elena,12123335555,work.txt</span></p><p style="text-align: left;"><span style="font-family: courier; font-size: x-small;">Noa,12123336666,friend.txt</span></p></blockquote><p style="text-align: left;"></p><div><br /></div><div>2. Templates (in the above file three templates are referenced, here is an example of one - work.txt):</div><div><p><span></span></p><blockquote><div style="text-align: left;"><span style="font-family: courier;">Dear FNAME,<br />as a work colleague I would love to see you at my birthday party this Saturday 8pm<br />My address is 102 Main St. Someville<br />Hope to see you there!<br />Srool</span></div><div><br /></div></blockquote></div><div>Any occurence of the FNAME in the template will be replaced by the recipient name.</div><div><br /></div><h4 style="text-align: left;">Behind The Scenes - For Developers</h4><div>OK, the code is written in VB.Net (using the community edition of Visual Studio).</div><div><br /></div><div>This is the form I designed:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKY6APx0bbLilmN78-TIw1cw1iXUvD_R0sEr9_QPUnNw1C3ydjYD7cXWx2x3fWsUwIo36hzft8oWmAFmqAo1ocQDNQEzdgYxs5le_-mJfB6lkBNl17njalQmJveRHNcEDwIgja2zwO2UnlCfivqK-CAvWAbqmIn4inrLFxtuuWojeR_nRBY5Bf9RDitUA/s1379/TMF_Form.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="897" data-original-width="1379" height="366" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKY6APx0bbLilmN78-TIw1cw1iXUvD_R0sEr9_QPUnNw1C3ydjYD7cXWx2x3fWsUwIo36hzft8oWmAFmqAo1ocQDNQEzdgYxs5le_-mJfB6lkBNl17njalQmJveRHNcEDwIgja2zwO2UnlCfivqK-CAvWAbqmIn4inrLFxtuuWojeR_nRBY5Bf9RDitUA/w562-h366/TMF_Form.png" width="562" /></a></div><br /><div><br /></div><div>And the code can be found in this GitHub repo:</div><div><br /></div><div><a href="https://github.com/iroth/TellMyFriends">https://github.com/iroth/TellMyFriends</a></div><div><br /></div><div><br /></div><div><br /></div>Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-25475848891325716022024-02-15T23:54:00.000-08:002024-02-17T10:36:09.047-08:00Automating WhatsApp Using AppleScript<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuDxF2ogb-MbiVnokxA4n5B4FIg1QHSMak7nZInO8P8ywv5DJRsu7r6eFmGshUjYskFEgc3wx_dlUJqfyVjop8WiMbct6BF9Qcwb3yAV5kmpwy7UXuLc-O-N-kB_aikDwlwN63zMTgpCfxrntNQcVZUWhQBCm0VQf1V84gyGE3LKHTO6n_4JnXmnRnW04/s1024/whatappAuto.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1024" data-original-width="1024" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuDxF2ogb-MbiVnokxA4n5B4FIg1QHSMak7nZInO8P8ywv5DJRsu7r6eFmGshUjYskFEgc3wx_dlUJqfyVjop8WiMbct6BF9Qcwb3yAV5kmpwy7UXuLc-O-N-kB_aikDwlwN63zMTgpCfxrntNQcVZUWhQBCm0VQf1V84gyGE3LKHTO6n_4JnXmnRnW04/s320/whatappAuto.png" width="320" /></a></div><br /><p><br /></p><p>Edit: added a windows version, and all source code is available here: <a href="https://github.com/iroth/TellMyFriends">https://github.com/iroth/TellMyFriends</a></p><div><br /></div><div><br /></div><div><br /></div><p>I was looking for a way to send a message to a distribution list. This is a feature that in its simplest form is part of the WhatsApp mobile app, but its a bit cumbersome, and what's more - it does not allow to customize messages for each recipient.</p><p>There are third party tools, and WhatsApp business API, but I am just a simple guy who needs to send his friends some personal messages (think about invitations to a party or greetings for the holidays), so I was trying all kind of ways, and then found a reasonable solution using JavaScript in Mac Script Editor.</p><p>The idea is to create a data file with a list of recipients, at the minimum it is something like:</p><div style="text-align: left;"><span style="font-family: courier;">{<br /></span><span style="font-family: courier;">"recipients": [<br /></span><span style="font-family: courier;">{"name": "John", "phone": "12123334444"},<br /></span><span style="font-family: courier;">{"name": "Marry", "phone": "12123335555"},<br /></span><span style="font-family: courier;">{"name": "Anna", "phone": "12123336666"},<br /></span><span style="font-family: courier;">]<br /></span><span style="font-family: courier;">}</span></div><p>With a first name and phone number.</p><p>Save the list to a file named "data.json" in a new empty folder.</p><p>Create a message template file, like this:</p><div style="text-align: left;"><span style="background-color: #eeeeee;"><span style="font-family: courier;">Hello FNAME,<br /></span><span style="font-family: courier;">I would love to invite you to my birthday party on Saturday night at 8PM.<br /></span><span style="font-family: courier;">Lots of food and beer - don't eat too much before the party...<br /></span><span style="font-family: courier;">see ya!</span></span></div><p>Save this to a file called "template.txt" in that same folder as the data.json file.</p><p>Then create this script (since I use Hebrew, I needed to read the file as UTF-8):</p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;">ObjC.import(</span><span class="s2" style="color: #a10015; font-variant-ligatures: no-common-ligatures;">'Foundation'</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">)</span></p><p class="p2" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; min-height: 14px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"></span><br /></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">const</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> readFileEnc = </span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">function</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> (path, encoding) {</span></p><p class="p3" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"><span class="Apple-converted-space"> </span>!encoding && (encoding = $.NSUTF8StringEncoding)</span></p><p class="p2" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; min-height: 14px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"></span><br /></p><p class="p3" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> </span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">const</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> fm = $.NSFileManager.defaultManager</span></p><p class="p3" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> </span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">const</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> data = fm.contentsAtPath(path)</span></p><p class="p3" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> </span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">const</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> str = $.NSString.alloc.initWithDataEncoding(data, encoding)</span></p><p class="p3" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> </span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">return</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> ObjC.unwrap(str)</span></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;">}</span></p><p class="p2" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; min-height: 14px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"></span><br /></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">const</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> sendOneMessage = </span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">function</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> (chromeapp, window, url) {</span></p><p class="p2" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; min-height: 14px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"></span><br /></p><p class="p4" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 36.1px; text-indent: -36.1px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> </span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> ic = window.tabs.length;</span></p><p class="p4" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 36.1px; text-indent: -36.1px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"><span class="Apple-converted-space"> </span>window.tabs.push(</span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">new</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> chrome.Tab());</span></p><p class="p5" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 43.3px; text-indent: -43.4px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"><span class="Apple-converted-space"> </span>window.tabs[</span><span class="s4" style="color: #006d6f; font-variant-ligatures: no-common-ligatures;">1</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">].url = url;</span></p><p class="p5" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 43.3px; text-indent: -43.4px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> </span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">while</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> (window.tabs.length > ic) {</span></p><p class="p3" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"><span class="Apple-converted-space"> <span class="Apple-tab-span" style="white-space: pre;"> </span></span></span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">this</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">.console.log(</span><span class="s2" style="color: #a10015; font-variant-ligatures: no-common-ligatures;">"waiting..."</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">);</span></p><p class="p5" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 43.3px; text-indent: -43.4px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"><span class="Apple-converted-space"> </span>}</span></p><p class="p2" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; min-height: 14px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"></span><br /></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;">}</span></p><p class="p2" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; min-height: 14px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"></span><br /></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> app = Application(</span><span class="s2" style="color: #a10015; font-variant-ligatures: no-common-ligatures;">"Script Editor"</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">);</span></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;">app.includeStandardAdditions = </span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">true</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">;</span></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> folder = app.chooseFolder({ withPrompt: </span><span class="s2" style="color: #a10015; font-variant-ligatures: no-common-ligatures;">'Please select the data folder'</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> })</span></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> file = folder + </span><span class="s2" style="color: #a10015; font-variant-ligatures: no-common-ligatures;">"/data.json"</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">;</span></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> dataStr = readFileEnc(file)</span></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> data = JSON.parse(dataStr);</span></p><p class="p2" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; min-height: 14px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"></span><br /></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> chrome = Application(</span><span class="s2" style="color: #a10015; font-variant-ligatures: no-common-ligatures;">'Google Chrome'</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">);</span></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> window = chrome.Window().make();</span></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> countDoc = </span><span class="s4" style="color: #006d6f; font-variant-ligatures: no-common-ligatures;">0</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">;</span></p><p class="p2" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; min-height: 14px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"></span><br /></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">for</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> (</span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> i = </span><span class="s4" style="color: #006d6f; font-variant-ligatures: no-common-ligatures;">0</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> ; i < data.recipients.length ; i++) {</span></p><p class="p3" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> tfile = folder + </span><span class="s2" style="color: #a10015; font-variant-ligatures: no-common-ligatures;">"/template.txt"</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">;</span></p><p class="p3" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s1" style="font-variant-ligatures: no-common-ligatures;"> text = readFileEnc(tfile);</span></p><p class="p3" style="font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s1" style="font-family: Menlo; font-variant-ligatures: no-common-ligatures;"><span class="Apple-tab-span" style="white-space: pre;"> </span>text = encodeURIComponent(text.replace(</span><span class="s4" style="color: #006d6f; font-variant-ligatures: no-common-ligatures;"><span style="font-family: Menlo;">/</span><span style="font-family: Courier New;">FNAME</span></span><span class="s4" style="color: #006d6f; font-family: Menlo; font-variant-ligatures: no-common-ligatures;">/</span><span class="s1" style="font-family: Menlo; font-variant-ligatures: no-common-ligatures;">g, data.recipients[i].name));</span></p><p class="p6" style="color: #a10015; font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s6" style="color: black; font-variant-ligatures: no-common-ligatures;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span class="s3" style="color: #392ab7; font-variant-ligatures: no-common-ligatures;">var</span><span class="s6" style="color: black; font-variant-ligatures: no-common-ligatures;"> dest_url = </span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">"https://web.whatsapp.com/send/?phone="</span><span class="s7" style="color: #7e8080; font-variant-ligatures: no-common-ligatures;"> + data.recipients[i].phone + </span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">"&text="</span><span class="s7" style="color: #7e8080; font-variant-ligatures: no-common-ligatures;"> + text + </span><span class="s1" style="font-variant-ligatures: no-common-ligatures;">"&type=phone_number"</span><span class="s7" style="color: #7e8080; font-variant-ligatures: no-common-ligatures;">;</span></p><p class="p3" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sendOneMessage(chrome, window, dest_url);</span></p><p class="p3" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 57.8px; text-indent: -57.8px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"><span class="Apple-tab-span" style="white-space: pre;"> </span>countDoc++;</span></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;">}</span></p><p class="p2" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; min-height: 14px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"></span><br /></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;">window.close();</span></p><p class="p2" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; min-height: 14px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"></span><br /></p><p class="p1" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;">countDoc</span></p><p class="p2" style="font-family: Menlo; font-feature-settings: normal; font-kerning: auto; font-optical-sizing: auto; font-size: 12px; font-stretch: normal; font-variant-alternates: normal; font-variant-east-asian: normal; font-variant-numeric: normal; font-variant-position: normal; font-variation-settings: normal; line-height: normal; margin: 0px 0px 0px 28.9px; min-height: 14px; text-indent: -28.9px;"><span class="s1" style="font-variant-ligatures: no-common-ligatures;"></span><br /></p><p>Before you run this script, make sure you logged in to WhatsApp Web in Chrome. The script will open a new window in Chrome, and then a tab with WhatsApp Web setup with the phone number and text ready for you, click the send button manually, and close the tab to get the next record opened. </p><p>If you only have 30-40 recipients, this will be quite a smooth and fulfilling experience.</p><p>Enjoy!</p><p><br /></p>Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-73101100627425126142021-08-13T04:37:00.004-07:002021-08-13T04:40:56.136-07:00NanoEuclid - An Arduino Based Euclidean Sequencer<h1 style="text-align: left;"> <span style="font-family: Arial; font-size: 35pt; font-style: italic; font-weight: 700; white-space: pre-wrap;">NanoEuclid</span></h1><span id="docs-internal-guid-55dbeadd-7fff-3afb-5bc9-7cfb3da7472b"><h3 style="line-height: 1.38; margin-bottom: 3pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 15pt; font-style: italic; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Arduino-based Euclid Rhythm Sequencer</span></h3><h4 style="line-height: 1.38; margin-bottom: 6pt; margin-top: 20pt; text-align: left;"><span style="font-family: Arial; font-size: 20pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Features</span></h4><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">This sequencer is inspired by Euclidean Circles v2 by vpme.de.</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">It has 4 channels: A, B, C, D</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Internal (with tempo control) or external clock operation</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Each channel can adjust the pattern length (1-16) and number of active spots in the pattern (1 to pattern length) and the offset of the pattern start within the cycle</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">The euclidean “algorithm” places the active beats within the pattern of a given length in a way that spreads them equally as much as possible.</span></p><br /><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"></span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOWEDp8832zNAr53yLw2eCa16ihSG8I-IhL_XIvItx07wSOQDlZDZ4C8exVE55blemhUUwjPYNFuWHsNd9P6lGhV4TUp1Zd9DSDR4r6hjta7yV2Q4ZUT9-SPgY6a4YlZRYT72ZDs0DJEo/s707/euclid.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="327" data-original-width="707" height="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOWEDp8832zNAr53yLw2eCa16ihSG8I-IhL_XIvItx07wSOQDlZDZ4C8exVE55blemhUUwjPYNFuWHsNd9P6lGhV4TUp1Zd9DSDR4r6hjta7yV2Q4ZUT9-SPgY6a4YlZRYT72ZDs0DJEo/w419-h194/euclid.png" width="419" /></a></div><br /><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span><p style="text-align: left;"></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">A 16 color led circle shows the parameters and a rotary encoder at the center of the ring is used as the main interactive control</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">The module provides 4 gate signals for Eurorack.</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Each channel can have a different pattern, and the 4 channels together with their patterns constitute a “patch”.</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Up to 16 patches can be saved and later loaded from EEPROM, so if you got something cool, it will not be lost..</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">The module has a play/stop button, and once playing can be triggered from an internal clock whose rate is controlled with a potentiometer, or switched to use an external clock from Eurorack.</span></p><h4 style="line-height: 1.38; margin-bottom: 6pt; margin-top: 20pt; text-align: left;"><span style="font-family: Arial; font-size: 20pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">User Interface</span></h4><ol style="margin-bottom: 0px; margin-top: 0px; padding-inline-start: 48px; text-align: left;"><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">int/ext toggle switch - setting the clock source</span></p></li><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">Tempo pot. - used when clock is internal to set the tempo BPM</span></p></li><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">16 RGB Led circle (Nano Pixels) to show setup, pattern and real time playback</span></p></li><ol style="margin-bottom: 0px; margin-top: 0px; padding-inline-start: 48px;"><li aria-level="2" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">Channel Colors are Yellow, Orange, Light Red and Red, where the leds in those color show the pattern length.</span></p></li><li aria-level="2" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">According to the mode (selected by pushing the rotary encoder), the actrive beats are shown in Purple (edit number of active), Blue (edit pattern offset), Cyan (edit pattern length).</span></p></li><li aria-level="2" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">When in play mode, the current step is highlighted in green</span></p></li><li aria-level="2" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; white-space: pre-wrap;">In Store mode (entered by long clicking the rotaty encoder), the whole 16 pixels are turned red (if the corresponding slot is free) or green (if the corresponding memory slot is taken). Selected memory position is highlighted in white. Selected position is moved with the rotary encoder. Clicking the CHN/store button stores the current pattern to the selected position (overriding existing stored pattern if there was one). If the selected position is a programmed one (Green), clicking the PLAY/load button loades the stored patch to the active pattern.</span></p></li></ol></ol><p style="text-align: left;"></p><ol start="4" style="margin-bottom: 0px; margin-top: 0px; padding-inline-start: 48px; text-align: left;"><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">PLAY push button (toggles between start and stop modes)</span></p></li><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">Rotary encoder with push button action to set parameters as described above</span></p></li><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">CHN Select push button - cycles between the 4 channels</span></p></li></ol><div style="text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></div><div style="text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span id="docs-internal-guid-1dfd50bf-7fff-bf5b-12fd-51d3356deda4"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><span style="border: none; display: inline-block; height: 826px; overflow: hidden; width: 341px;"><img height="826" src="https://lh6.googleusercontent.com/JxiwkOAFoSlfV-u_E1oJlvaG44D8OnUacvgVr7u5pnNJOATiIZzpa_UX0RqDgLvQfwifNEUzomNR1c0fwtr9nG3UVo2qRWdu4-cQVbb7m0zThD6p9j7Vk7cAkYXYGpTOpBn_Nb0y" style="margin-left: 0px; margin-top: 0px;" width="341" /></span></span></span></span></div><div style="text-align: left;"><h1 style="line-height: 1.38; margin-bottom: 6pt; margin-top: 20pt;"><span style="font-family: Arial; font-size: 20pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;"><br /></span></h1><h4 style="line-height: 1.38; margin-bottom: 6pt; margin-top: 20pt; text-align: left;"><span style="font-family: Arial; font-size: 20pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">User Interface</span></h4></div><p style="text-align: left;"><span style="font-family: Arial; font-size: 14.6667px; white-space: pre-wrap;">The circuit is based on an Arduino Nano Every board, as shown below.</span></p><p style="text-align: left;"></p><p><span id="docs-internal-guid-a5d0181b-7fff-2f59-fe17-cf1cd78eff09"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span style="border: none; display: inline-block; height: 466px; overflow: hidden; width: 625px;"><img height="466" src="https://lh4.googleusercontent.com/bQRxrLImRgLfafKFxAxp2DUiQBmVw93d2gwML5lDA1cQIEQ6WIhy6OvIIOTaoyADl1EfZuUFr5aJhFOYxSA4jjzM6YO58W2D-8oBN8TUwxylKnFBDEsOJL-YwW6nd3n7YMwHNERK" style="margin-left: 0px; margin-top: 0px;" width="625" /></span></span></span></p><p></p><p style="text-align: left;"><span id="docs-internal-guid-c6592590-7fff-db98-c695-adc8bb91c464"></span></p><ol style="margin-bottom: 0px; margin-top: 0px; padding-inline-start: 48px; text-align: left;"><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">Clock input - a 2N3904 transistor is used as an inverting buffer to protect the arduino, two resistors and a diode are connected (one resistor from input to base, one resistor from collector to +5V, diode cathode to base, anode to ground). Input from collector to D10.</span></p></li><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">Gate outputs - digital pins 2,3,4,5 go to gate output</span></p></li><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">NeoPixels ring - digital pin 6 drives the LED ring</span></p></li><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">Rotary encoder inputs - D7, D8</span></p></li><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">Rotary encoder switch - D9</span></p></li><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">Play/Stop push connected as INPUT PULLUP to ground and D11</span></p></li><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">Channel select push is connected to ground and to A3, A3 connected with 1M resistor to v+</span></p></li><li aria-level="1" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">Int/Ext Switch - ground to D12</span></p></li></ol><div style="text-align: left;"><span style="font-family: Arial;"><span style="font-size: 14.6667px; white-space: pre-wrap;"><br /></span></span></div><div style="text-align: left;"><span id="docs-internal-guid-661a80b3-7fff-6728-a5fd-6eddd3e58240"><h4 style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt; text-align: left;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Storage</span></h4><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">EEPROM size on Nano Every is 256 bytes, we would like to store patches into it, so they can be called upon later and survive power downs.</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">This section explains the arrangement of the data in this rather small memory space.</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Each patch consists of 4 channels, each channel has 3 parameters: pattern length, number of active beats and offset. Each parameter can be stored in 4 bits, so the channel data is stored in 1.5 bytes, and the whole 4 channels in 8 bytes.</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">As the GUI consists of 16 LEDS, we will limit the pattern storage to 16 patches, to simplify the user interface.</span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">To simplify managing the memory, we can store 2 bytes of 16 bits telling us if the respective slot is already stored.</span></p><br /><div align="left" style="margin-left: 0pt;"><table style="border-collapse: collapse; border: none; table-layout: fixed; width: 504pt;"><colgroup><col></col><col></col><col></col><col></col><col></col></colgroup><tbody><tr style="height: 0pt;"><td style="border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">offset</span></p></td><td colspan="2" style="border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Byte 0</span></p></td><td colspan="2" style="border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Byte 1</span></p></td></tr><tr style="height: 21pt;"><td style="background-color: #ffd966; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">0</span></p></td><td colspan="4" style="background-color: #ffd966; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Magic Pattern ($AAAA)</span></p></td></tr><tr style="height: 21pt;"><td style="background-color: #ffd966; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">2</span></p></td><td colspan="4" style="background-color: #ffd966; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Bitmap of taken slots for patches (1=taken)</span></p></td></tr><tr style="height: 0pt;"><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">4</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Pattern Length A</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Active Beats A</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">UNUESDE Nibble (0x00)</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Offset A</span></p></td></tr><tr style="height: 0pt;"><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">6</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Pattern Length B</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Active Beats B</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">UNUESDE <span style="font-size: 10.6667px;">Nibble</span> (0x00)</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Offset B</span></p></td></tr><tr style="height: 0pt;"><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">8</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Pattern Length C</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Active Beats C</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">UNUESDE <span style="font-size: 10.6667px;">Nibble</span> (0x00)</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Offset C</span></p></td></tr><tr style="height: 0pt;"><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">10</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Pattern Length D</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Active Beats D</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">UNUESDE <span style="font-size: 10.6667px;">Nibble</span> (0x00)</span></p></td><td style="background-color: #fff2cc; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #0</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Offset D</span></p></td></tr><tr style="height: 0pt;"><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">12</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Pattern Length A</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Active Beats A</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">UNUESDE <span style="background-color: #fff2cc; font-size: 10.6667px;">Nibble</span> (0x00)</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Offset A</span></p></td></tr><tr style="height: 0pt;"><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">14</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Pattern Length B</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Active Beats B</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">UNUESDE <span style="background-color: #fff2cc; font-size: 10.6667px;">Nibble</span> (0x00)</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Offset B</span></p></td></tr><tr style="height: 0pt;"><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">16</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Pattern Length C</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Active Beats C</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">UNUESDE <span style="background-color: #fff2cc; font-size: 10.6667px;">Nibble</span> (0x00)</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Offset C</span></p></td></tr><tr style="height: 0pt;"><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">118</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Pattern Length D</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Active Beats D</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: #b7b7b7; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">UNUESDE <span style="background-color: #fff2cc; font-size: 10.6667px;">Nibble</span> (0x00)</span></p></td><td style="background-color: #ffe599; border-bottom: solid #000000 1pt; border-color: rgb(0, 0, 0); border-left: solid #000000 1pt; border-right: solid #000000 1pt; border-style: solid; border-top: solid #000000 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">PATCH #1</span></p><p style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 8pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Offset D</span></p></td></tr></tbody></table></div><br /><h4 style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt; text-align: left;"><span style="font-family: Arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Storage</span></h4><p style="text-align: left;"><span style="font-family: Arial;"><span style="font-size: 14.6667px; white-space: pre-wrap;">The source code is available at this repo here:<br /></span></span><span style="font-family: Arial;"><span style="font-size: 14.6667px; white-space: pre-wrap;"><a href="https://github.com/iroth/nanoeuclid">https://github.com/iroth/nanoeuclid</a></span></span><span style="font-family: Arial;"><span style="font-size: 14.6667px; white-space: pre-wrap;"><br /></span></span><span style="font-family: Arial;"><span style="font-size: 14.6667px; white-space: pre-wrap;">See the readme for additional info, and feel free to comment, or branch...</span></span></p><div style="text-align: left;"><span style="font-family: Arial;"><span style="font-size: 14.6667px; white-space: pre-wrap;"><br /></span></span></div></span></div></span>Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-83583749416314219422016-04-22T03:58:00.001-07:002016-04-29T05:19:02.650-07:00Using Signed URLs with CloudFront, CarrierWave and Rails<h2>
<i style="color: #999999;">Setting it all up and how to avoid some stupid pitfalls..</i></h2>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIeUzrvqaZ0uhC3xQ3-6AtcVmdfUh-eakGAkzemFRWlBvLQ0vH4yO_-QdK0HrZB3uNKw0swTBwJnfdgVKMiCAoODmfW1pCUb0nhhsUYhasvZ9Z5UY92XDVUrOapgTP3cxiKda5C62gdLg/s1600/rails-253134_1280.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIeUzrvqaZ0uhC3xQ3-6AtcVmdfUh-eakGAkzemFRWlBvLQ0vH4yO_-QdK0HrZB3uNKw0swTBwJnfdgVKMiCAoODmfW1pCUb0nhhsUYhasvZ9Z5UY92XDVUrOapgTP3cxiKda5C62gdLg/s320/rails-253134_1280.jpg" width="320" /></a></div>
<br />
I created a rails app to server users with the ability for them to upload images and files.<br />
To improve the performance of the site, I serve all app assets (JavaScript, Images, CSS files, etc.) from a CloudFront distribution. I then decided to also add a CloudFront in front of the bucket I use for user uploaded files.<br />
Using carrier wave gem is quite straightforward and even adding the CloudFront CDN was quite easy. Just in case you have no experience, here are the basics:<br />
<br />
1. add the carrierwave and carrierwave-aws gems - this is much better than using fog which bloats your app with multiple unneeded gems. It also supports more of the AWS API.<br />
<br />
2. add a field to your relevant table to store the uploaded image or asset, like:<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> add_column :users, :avatar, :string
</code></pre>
<div>
<br /></div>
<div>
3. In your model class, mount an uploader for this field:</div>
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> mount_uploader :avatar, AvatarUploader
</code></pre>
<br />
4. Implement an uploader class (AvatarUploader in this exmple):
<br />
<div>
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> class AvatarUploader &lt; CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :aws
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Create different versions of your uploaded files:
# version :thumb do
# process :resize_to_fit =&gt; [50, 50]
# end
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_white_list
%w(jpg jpeg gif png)
end
def fix_exif_rotation #this is my attempted solution
manipulate! do |img|
img.tap(&amp;:auto_orient)
end
end
process :fix_exif_rotation
process resize_to_limit: [200, 200]
end
</code></pre>
</div>
<br />
In the above code, I am including MiniMagick to manipulate the uploaded image (resize and fix orientation - the orientation was to fix issue with landscape oriented images uploaded from iPhone).<br />
If you want to use MiniMagick, you will have to add the mini_magick gem.<br />
<br />
5. You will then need to configure carrierwave with AWS credentials, bucket name and region like that:<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> CarrierWave.configure do |config|
config.storage = :aws
config.aws_bucket = ENV['S3_BUCKET'] || 'default-bucket'
config.aws_acl = 'public-read'
config.aws_attributes = {
expires: 1.week.from_now.httpdate,
cache_control: 'max-age=604800'
}
config.aws_credentials = {
access_key_id: ENV['S3_ACCESS_KEY'] || 'your-key',
secret_access_key: ENV['S3_SECRET_KEY'] || 'your-secret',
region: 'eu-west-1' # Required
}
end
</code></pre>
<br />
BTW, you could use local file storage for development by changing both carrier_wave initializer to use storage of :file as well as your uploader class.<br />
<br />
6. To serve app assets from AWS CloudFront (for much improved performance and less load on your poor web server...), you can add an assets CDN host in your config/environments/production.rb. That's what I have:<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> if ENV['APP_CDN_HOST'].present?
config.action_controller.asset_host = ENV['APP_CDN_HOST']
end
</code></pre>
<br />
7. To server those app assets from CloudFront, you will need to create a distribution pointing to your domain as origin. To do that, go to your AWS console, select the CloudFront service, and click "Create Distribution" at the top. In the following screen, click "Get Started" under the "Web" section ("RTMP" is used for streaming media). In the Origin Domain Name field, type in your domain (where your application is served from). If you want to support both http and https request you should pick "Match Viewer".<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikw3LX9VWrh5bEdo3c-IAwtYccDEvBsf163IdzIsTFthgzSAKpaU3Vise45wGqq-VQEeOXugTACfeALTNxENsKmQZPPbQ20kH4s1-8JAWeibFVohulOM-YRt_C6XdErlKaPutAFJYGwxA/s1600/AWS+CloudFront+Management+Console+2016-04-22+13-27-16.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="253" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikw3LX9VWrh5bEdo3c-IAwtYccDEvBsf163IdzIsTFthgzSAKpaU3Vise45wGqq-VQEeOXugTACfeALTNxENsKmQZPPbQ20kH4s1-8JAWeibFVohulOM-YRt_C6XdErlKaPutAFJYGwxA/s320/AWS+CloudFront+Management+Console+2016-04-22+13-27-16.png" width="320" /></a></div>
<span id="goog_2085723677"></span><span id="goog_2085723678"></span><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Once created, set the APP_CDN_HOST to point to the CloudFront distribution URL.<br />
<br />
8. To place CloudFront in front of your user uploaded files, you need to add the following to your carrier_wave configuration file:<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> config.asset_host = ENV['CDN_HOST'] || 'http://<your distribution="" suffix="">.cloudfront.net'
</your></code></pre>
I recommend of course to store the distribution in an environment variable, but sometimes it is convenient to have it as above for your development environment. You will also need to create a cloud front distribution where the origin is the bucket you use for carrier wave (as defined above in the carrier_wave.rb configuration file).<br />
<br />
9. Now all is smooth and performant. There is one small issue (which may or may not be relevant to you): anyone with a link to a user uploaded file, can share that link and so others can access the files. If you care about privacy and security of those files, you may want to protect them with a CloudFront signed URLs. Signed URLs are signed with a trusted private key, and can encode an expiration time for the link. This means that anyone trying to access a cloud front signed URL after the expiration time, will get an access denied result. To add this extra security step, you should follow the following steps...<br />
<br />
10. Create an IAM user identity that will be used as a trusted signer. This can be the user creating the bucket, or another user. I recommend NOT using your root identity for either of those operations. What I did, is I created a user for both creating the bucket, as well as the cloud front distribution and gave that user the trusted signer role. I automated all those actions in a ruby script (assuming the aws_boto user is an admin user I have with the given credentials):<br />
<br />
<br />
<pre style="background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="word-wrap: normal;"> require 'aws-sdk'
# get admin credentials from ENV variables:
abk = `echo $aws_boto_key`.strip
abs = `echo $aws_boto_secret`.strip
Aws.config.update(
{
region: 'eu-west-1',
credentials: Aws::Credentials.new(abk, abs),
}
)
# create bucket if does not exist
s3 = Aws::S3::Resource.new
sd = `echo $subdomain`.strip
bucket_name = "#{sd}-mydomain"
bucket = s3.bucket(bucket_name)
if bucket.exists?
puts "bucket already exists, no need to worry..."
else
puts "no such bucket, create it now"
bucket.create
# create IAM user and get the access key and secret
iam = Aws::IAM::Resource.new
user_name = "#{sd}-mydomain"
user = iam.user(user_name)
if user.exists?
puts "user already exists, no need to worry..."
else
puts "no such user, create it now"
user.create
accesskeypair = user.create_access_key_pair
File.open("userkey.cfg", 'w') { |file| file.write("#{accesskeypair.access_key_id}") }
File.open("usersecret.cfg", 'w') { |file| file.write("#{accesskeypair.secret}") }
# build a custom policy
policy_doc = '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1444035127000",
"Effect": "Allow",
"Action": [
"s3:Delete*",
"s3:Get*",
"s3:List*",
"s3:Put*"
],
"Resource": [
"arn:aws:s3:::'+"#{bucket_name}"+'/*"
]
}
]
}'
user.create_policy({
policy_name: "S3_MyDomain_Access", # required
policy_document: policy_doc, # required
})
end
# create cloud front distribution for app assets (origin = app root directory)
cloudfront = Aws::CloudFront::Client.new()
app_dist = cloudfront.create_distribution(
{
distribution_config: {
# required
caller_reference: "app-dist-caller-#{Time.now.to_i}", # required - unique string for the request
aliases: {
quantity: 0
},
default_root_object: "",
origins: {
# required
quantity: 1, # required
items: [
{
id: "1", # required - unique within distribution
domain_name: "#{sd}.mydomain.com", # required
origin_path: "",
custom_origin_config: {# use only for custom origin, not for bucket
http_port: 80, # required
https_port: 443, # required
origin_protocol_policy: "match-viewer", # required, accepts http-only, match-viewer
},
},
],
},
default_cache_behavior: {
# required
target_origin_id: "1", # required
forwarded_values: {# required
query_string: true, # required
cookies: {# required
forward: "none", # required, accepts none, whitelist, all
whitelisted_names: {
quantity: 0
},
},
headers: {
quantity: 0
},
},
trusted_signers: {# required
enabled: false, # required
quantity: 0
},
viewer_protocol_policy: "allow-all", # required, accepts allow-all, https-only, redirect-to-https
min_ttl: 600, # required
allowed_methods: {
quantity: 2, # required
items: ["GET", "HEAD"], # required, accepts GET, HEAD, POST, PUT, PATCH, OPTIONS, DELETE
cached_methods: {
quantity: 2, # required
items: ["GET", "HEAD"], # required, accepts GET, HEAD, POST, PUT, PATCH, OPTIONS, DELETE
},
},
smooth_streaming: false,
default_ttl: 86400,
max_ttl: 2592000,
},
cache_behaviors: {
quantity: 0
},
custom_error_responses: {
quantity: 0
},
comment: "created automatically by SroolTheKnife", # required
logging: {
enabled: false, # required
include_cookies: false, # required
bucket: "", # required
prefix: "", # required
},
price_class: "PriceClass_100", # accepts PriceClass_100 (US and Europe), PriceClass_200, PriceClass_All
enabled: true, # required
viewer_certificate: {
cloud_front_default_certificate: true,
minimum_protocol_version: "TLSv1", # accepts SSLv3, TLSv1
},
restrictions: {
geo_restriction: {# required
restriction_type: "none", # required, accepts blacklist, whitelist, none
quantity: 0
},
},
},
})
File.open("appdist.cfg", 'w') { |file| file.write("#{app_dist.distribution.domain_name}") }
# create cloud front distribution for uploaded assets (origin = above bucket)
cloudfront = Aws::CloudFront::Client.new()
bucket_dist = cloudfront.create_distribution(
{
distribution_config: {
# required
caller_reference: "bucket-dist-caller-#{Time.now.to_i}", # required - unique string for the request
aliases: {
quantity: 0
},
default_root_object: "",
origins: {
# required
quantity: 1, # required
items: [
{
id: "1", # required - unique within distribution
domain_name: "#{bucket_name}.s3.amazonaws.com", # required
origin_path: "",
s3_origin_config: {# use only for bucket
origin_access_identity: "", # required
},
},
],
},
default_cache_behavior: {
# required
target_origin_id: "1", # required
forwarded_values: {# required
query_string: true, # required
cookies: {# required
forward: "none", # required, accepts none, whitelist, all
whitelisted_names: {
quantity: 0
},
},
headers: {
quantity: 0
},
},
trusted_signers: {
# required
enabled: true, # required
quantity: 1,
items: ['self'] # <span style="color: red;"><strong>same user creating the bucket is the trusted signer</strong></span>
},
viewer_protocol_policy: "allow-all", # required, accepts allow-all, https-only, redirect-to-https
min_ttl: 600, # required
allowed_methods: {
quantity: 2, # required
items: ["GET", "HEAD"], # required, accepts GET, HEAD, POST, PUT, PATCH, OPTIONS, DELETE
cached_methods: {
quantity: 2, # required
items: ["GET", "HEAD"], # required, accepts GET, HEAD, POST, PUT, PATCH, OPTIONS, DELETE
},
},
smooth_streaming: false,
default_ttl: 86400,
max_ttl: 2592000,
},
cache_behaviors: {
quantity: 0
},
custom_error_responses: {
quantity: 0
},
comment: "created automatically by SroolTheKnife", # required
logging: {
enabled: false, # required
include_cookies: false, # required
bucket: "", # required
prefix: "", # required
},
price_class: "PriceClass_100", # accepts PriceClass_100 (US and Europe), PriceClass_200, PriceClass_All
enabled: true, # required
viewer_certificate: {
cloud_front_default_certificate: true,
minimum_protocol_version: "TLSv1", # accepts SSLv3, TLSv1
},
restrictions: {
geo_restriction: {# required
restriction_type: "none", # required, accepts blacklist, whitelist, none
quantity: 0
},
},
},
})
File.open("bucketdist.cfg", 'w') { |file| file.write("#{bucket_dist.distribution.domain_name}") }
end
</code></pre>
<br />
<br />
11. Once you have that, you can add the following to carrier_wave initializer, to add code signing:
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> config.aws_signer = -> (unsigned_url, options) { Aws::CF::Signer.sign_url unsigned_url, options }
</code></pre>
<br />
12. You should add the cloudfront-signer gem, and initializers/cloudfront-signer.rb with the following code:<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> Aws::CF::Signer.configure do |config|
config.key = ENV["CLOUDFRONT_KEY"] || '-----BEGIN RSA PRIVATE KEY-----
paste your private key here if you like...
-----END RSA PRIVATE KEY-----'
config.key_pair_id = ENV["CLOUDFRONT_KEY_PAIR_ID"] || 'paste key pair'
config.default_expires = 600
end
</code></pre>
<br />
That key and key pair ID are required to be set in the AWS console as explained here: <a href="http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html#private-content-creating-cloudfront-key-pairs" target="_blank">Specifying Trusted Signers</a>.<br />
<br />
One last issue that caused me a lot of confusion and few hours of wasted time is that if you find that some of the uploaded assets "disappear" - for example, an uploaded image shows as a broken image. The reason might be (and was on my end) the rails cache kicking in, and serving the fragment (including the image URL) beyond the signed URL expiration time. You better not cache such fragments or else they will be broken once the signed URL expiration time arrives.<br />
<br />
<br />
<hr />
And a message from our advertisers:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today" target="_blank"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFDPS7zTTDuthRShMn6UNbpFdmVebdH2hQJYajI3cAFIdzJC5lQRdQNcfF3O8oe0uowMN2kZ09fFkn6yMNU-0ZPU70RxLNUUNUCMB6JHreJQm_n4Xuz2iA4M5XVjawdGB1TvAbLbauP9I/s1600/toptal.png" /></a></div>
<br />
Toptal provides remote engineers and designers of high quality. I recommend them. Follow this link (full disclosure: this is my affiliate link):<br />
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today">https://www.toptal.com/#engage-honest-computer-engineers-today</a><br />
<br />
<br />
<br />
<br />Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com1tag:blogger.com,1999:blog-5488803742537723823.post-29911668550149025212016-01-28T08:18:00.000-08:002016-04-29T05:19:39.649-07:00Using Froala Editor with RailsI am using Froala Text Editor (www.froala.com) to have a rich text editor.<br />
In order to use its image upload (including support of pasting images), you need to implement a server side component. The Froala site documentation has an example for php, but I thought I would write a little post on how to achieve this with Rails.<br />
<br />
So my main model is called Article, and I wanted to edit the content of the articles using Froala. To support uploading images, I created another model called ArticleAttachment and implemented it with a mounted uploader using CarrierWave:<br />
<br />
<pre style="background-color: #2b2b2b; color: #a9b7c6; font-family: Menlo;"><span style="font-size: x-small;"><span style="color: #cc7832; font-weight: bold;">class </span><span style="color: #9876aa; font-style: italic;">ArticleAttachment </span><span style="color: #cc7833;">< </span><span style="color: #da4939;">ActiveRecord</span>::<span style="color: #da4939;">Base</span><span style="color: #da4939;"> belongs_to </span><span style="color: #6e9cbe;">:article</span><span style="color: #6e9cbe;">
</span><span style="color: #6e9cbe;"> </span>mount_uploader <span style="color: #6e9cbe;">:attachment</span><span style="color: #cc7832;">, </span><span style="color: #da4939;">ArticleImageUploader</span><span style="color: #da4939;">
</span><span style="color: #cc7832; font-weight: bold;">end</span></span></pre>
<span style="font-size: x-small;"><br /></span>
<br />
In the Article model, I added this has_many line:<br />
<br />
<pre style="background-color: #2b2b2b; color: #a9b7c6; font-family: Menlo;"><span style="font-size: x-small;"><span style="background-color: #344134;">has_many</span> <span style="color: #6e9cbe;">:article_attachments</span><span style="color: #cc7832;">, </span><span style="color: #6e9cbe;">dependent</span>: <span style="color: #6e9cbe;">:destroy</span></span></pre>
<br />
In the article controller, I implemented two methods: one for adding an image and one for deleting an image:<br />
<br />
<div style="background: #272822; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"> <span style="color: #66d9ef;">def</span> <span style="color: #a6e22e;">attach</span>
<span style="color: #f8f8f2;">attachment</span> <span style="color: #f92672;">=</span> <span style="color: #f8f8f2;">@article</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">article_attachments</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">create(</span><span style="color: #e6db74;">attachment</span><span style="color: #f8f8f2;">:</span> <span style="color: #f8f8f2;">params</span><span style="color: #f92672;">[</span><span style="color: #e6db74;">:attachment</span><span style="color: #f92672;">]</span><span style="color: #f8f8f2;">)</span>
<span style="color: #66d9ef;">if</span> <span style="color: #f8f8f2;">attachment</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">save</span>
<span style="color: #f8f8f2;">render</span> <span style="color: #e6db74;">:json</span> <span style="color: #f92672;">=></span> <span style="color: #f8f8f2;">{</span><span style="color: #e6db74;">link</span><span style="color: #f8f8f2;">:</span> <span style="color: #f8f8f2;">attachment</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">attachment</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">url}</span>
<span style="color: #66d9ef;">else</span>
<span style="color: #f8f8f2;">render</span> <span style="color: #e6db74;">:json</span> <span style="color: #f92672;">=></span> <span style="color: #f8f8f2;">{</span><span style="color: #e6db74;">error</span><span style="color: #f8f8f2;">:</span> <span style="color: #e6db74;">'failed to save attachment'</span><span style="color: #f8f8f2;">},</span> <span style="color: #e6db74;">status</span><span style="color: #f8f8f2;">:</span> <span style="color: #ae81ff;">500</span>
<span style="color: #66d9ef;">end</span>
<span style="color: #66d9ef;">end</span>
<span style="color: #66d9ef;">def</span> <span style="color: #a6e22e;">detach</span>
<span style="color: #f8f8f2;">src</span> <span style="color: #f92672;">=</span> <span style="color: #f8f8f2;">params</span><span style="color: #f92672;">[</span><span style="color: #e6db74;">:src</span><span style="color: #f92672;">]</span>
<span style="color: #f8f8f2;">uri</span> <span style="color: #f92672;">=</span> <span style="color: #66d9ef;">URI</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">parse(src)</span>
<span style="color: #f8f8f2;">fname</span> <span style="color: #f92672;">=</span> <span style="color: #66d9ef;">File</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">basename(uri</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">path)</span>
<span style="color: #f8f8f2;">attachment</span> <span style="color: #f92672;">=</span> <span style="color: #f8f8f2;">@article</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">article_attachments</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">find_by(</span><span style="color: #e6db74;">attachment</span><span style="color: #f8f8f2;">:</span> <span style="color: #f8f8f2;">fname)</span>
<span style="color: #66d9ef;">if</span> <span style="color: #f8f8f2;">attachment</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">present?</span> <span style="color: #f92672;">&&</span> <span style="color: #f8f8f2;">attachment</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">destroy</span>
<span style="color: #f8f8f2;">render</span> <span style="color: #e6db74;">:json</span> <span style="color: #f92672;">=></span> <span style="color: #f8f8f2;">{</span><span style="color: #e6db74;">success</span><span style="color: #f8f8f2;">:</span> <span style="color: #e6db74;">'deleted'</span><span style="color: #f8f8f2;">}</span>
<span style="color: #66d9ef;">else</span>
<span style="color: #f8f8f2;">render</span> <span style="color: #e6db74;">:json</span> <span style="color: #f92672;">=></span> <span style="color: #f8f8f2;">{</span><span style="color: #e6db74;">error</span><span style="color: #f8f8f2;">:</span> <span style="color: #f8f8f2;">attachment</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">attachment</span><span style="color: #f92672;">.</span><span style="color: #f8f8f2;">url},</span> <span style="color: #e6db74;">status</span><span style="color: #f8f8f2;">:</span> <span style="color: #ae81ff;">500</span>
<span style="color: #66d9ef;">end</span>
<span style="color: #66d9ef;">end</span>
</pre>
</div>
<br />
for that to work, you need to assign a unique file name in the uploader as explained here:<br />
<a href="https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Create-random-and-unique-filenames-for-all-versioned-files">https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Create-random-and-unique-filenames-for-all-versioned-files</a><br />
<br />
<br />
<br />
<hr />
And a message from our advertisers:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today" target="_blank"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFDPS7zTTDuthRShMn6UNbpFdmVebdH2hQJYajI3cAFIdzJC5lQRdQNcfF3O8oe0uowMN2kZ09fFkn6yMNU-0ZPU70RxLNUUNUCMB6JHreJQm_n4Xuz2iA4M5XVjawdGB1TvAbLbauP9I/s1600/toptal.png" /></a></div>
<br />
Toptal provides remote engineers and designers of high quality. I recommend them. Follow this link (full disclosure: this is my affiliate link):<br />
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today">https://www.toptal.com/#engage-honest-computer-engineers-today</a><br />
<br />
<br />
<br />Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-30228780775823808282015-01-03T01:17:00.001-08:002016-04-29T05:20:00.404-07:00Developing with the latest OpenSSL on Mac OSX Yosemite and XCode 6.1.1<h2>
Developing OpenSSL on Mac OSX Yosemite, XCode 6.1.1</h2>
Had to write some encryption/decryption code for Mac OSX, and as I intend to use this on multiple platforms, I prefer to use openssl rather than Apple's code for all key generation and enc/dec functions.<br />
Trying to use some sample code using openssl I discovered that Apple stopped supporting and updating OpenSSL a few years ago. If you try to compile code including <openssl> headers, you will get a warning saying it was deprecated as of OSX Lion.</openssl><br />
So I searched for how to update openssl on OSX and found few useful links, so this is the summary of what I needed to do:<br />
<br />
1. Check what openssl version is installed in your system:<br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;"><b style="background-color: #444444;"><br /></b></span>
<span style="background-color: white; color: #6aa84f; font-family: "courier new" , "courier" , monospace;">$ openssl version</span><br />
<br />
with the latest version as of this writing you should get this:<br />
<br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">OpenSSL 1.0.1j 15 Oct 2014</span><br />
<br />
If you have a fresh OSX Yosemite (mine is 10.10.1), you most likely will get this response:<br />
<br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">OpenSSL 0.9.8za 5 Jun 2014</span><br />
<br />
This version does not contain some of the vulnurability fixes introduced in later versions (including the well-known Heartbleed).<br />
So a quick search on updating openssl will bring you this <a href="http://apple.stackexchange.com/questions/126830/how-to-upgrade-openssl-in-os-x" target="_blank">answer</a>.<br />
On my machine, I had to install brew from scratch (using the instructions at <a href="http://brew.sh/">brew.sh</a>) and then following these:<br />
<br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">$ brew update</span><br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">$ brew install openssl</span><br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">$ brew link --force openssl</span><br />
<br />
I then had to rename old openssl and create a symbolic link to the brew installed one using:<br />
<br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">$ sudo mv /usr/bin/openssl /usr/bin/openssl_OLD</span><br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">$ sudo ln -s /usr/local/Cellar/openssl/1.0.1j_1/bin/openssl /usr/bin/openssl</span><br />
<br />
Note: the 1.0.1j_1 is for the time of writing, it probably will change as openssl new versions introduced.<br />
<br />
Now, this updates openssl to the latest for command line use. We now need to make sure the include directories are updated as well, so to check the version you have on your machine, look into /usr/include/openssl/opensslv.h - most likely it will contain the 0.9.8za version (or whatever was there before), so we need to link that directory to the brew installed one:<br />
<br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">$ sudo mv /usr/include/openssl /usr/include/openssl_OLD</span><br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">$ sudo ln -s /usr/local/Cellar/openssl/1.0.1j_1/include/openssl /usr/include/openssl</span><br />
<div>
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;"><br /></span></div>
Now, if you try to create an xcode console app with this code:<br />
<br />
<div style="background-color: white; font-size: 12.8000001907349px;">
<span style="color: #0b5394; font-family: "courier new" , "courier" , monospace;">#include <stdio .h=""></stdio></span></div>
<div style="background-color: white; font-size: 12.8000001907349px;">
<span style="color: #0b5394; font-family: "courier new" , "courier" , monospace;">#include <openssl opensslv.h=""></openssl></span></div>
<div style="background-color: white; font-size: 12.8000001907349px;">
<span style="color: #0b5394; font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div style="background-color: white; font-size: 12.8000001907349px;">
<span style="color: #0b5394; font-family: "courier new" , "courier" , monospace;">int main(int argc, const char * argv[]) {</span></div>
<div style="background-color: white; font-size: 12.8000001907349px;">
<span style="color: #0b5394; font-family: "courier new" , "courier" , monospace;"> // insert code here...</span></div>
<div style="background-color: white; font-size: 12.8000001907349px;">
<span style="color: #0b5394; font-family: "courier new" , "courier" , monospace;"> printf("Hello, World!\n");</span></div>
<div style="background-color: white; font-size: 12.8000001907349px;">
<span style="color: #0b5394; font-family: "courier new" , "courier" , monospace;"> printf("OpenSSL version is: %s\n", OPENSSL_VERSION_TEXT);</span></div>
<div style="background-color: white; font-size: 12.8000001907349px;">
<span style="color: #0b5394; font-family: "courier new" , "courier" , monospace;"> return 0;</span></div>
<div style="background-color: white; font-size: 12.8000001907349px;">
<span style="color: #0b5394; font-family: "courier new" , "courier" , monospace;">}</span></div>
<div style="background-color: white; color: #222222; font-family: arial, sans-serif; font-size: 12.8000001907349px;">
<br /></div>
<div style="background-color: white; color: #222222; font-family: arial, sans-serif; font-size: 12.8000001907349px;">
<br /></div>
You will be disappointed to see that it still, stubbornly, keeps referring to the old openssl...<br />
To find out what happens, hold the command key and click on the <openssl opensslv.h=""> to open that header file, and you will see that xcode is actually looking for it under the osx sdk (on my machine it is: <i><span style="font-family: "courier new" , "courier" , monospace;">/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/openssl</span></i>)</openssl><br />
<br />
so to fix that, I created a symbolic link inside the sdk folder to the brew installed version as well:<br />
<br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">$ sudo mv /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/openssl /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/openssl</span><span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">_OLD</span><br />
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;">$ sudo ln -s /usr/local/Cellar/openssl/1.0.1j_1/include/openssl /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/openssl</span><br />
<div>
<span style="color: #6aa84f; font-family: "courier new" , "courier" , monospace;"><br /></span></div>
Now, this should work which can be tested by building the above console app (you may need to first clean it to remove any cached headers).<br />
Note: XCode may contain multiple SDKs (on my machine there is also MacOSX10.9.sdk) - so make sure you update all the ones you use.<br />
<br />
<br />
<br />
<hr />
And a message from our advertisers:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today" target="_blank"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFDPS7zTTDuthRShMn6UNbpFdmVebdH2hQJYajI3cAFIdzJC5lQRdQNcfF3O8oe0uowMN2kZ09fFkn6yMNU-0ZPU70RxLNUUNUCMB6JHreJQm_n4Xuz2iA4M5XVjawdGB1TvAbLbauP9I/s1600/toptal.png" /></a></div>
<br />
Toptal provides remote engineers and designers of high quality. I recommend them. Follow this link (full disclosure: this is my affiliate link):<br />
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today">https://www.toptal.com/#engage-honest-computer-engineers-today</a><br />
<br />
<br />
<br />
Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com5tag:blogger.com,1999:blog-5488803742537723823.post-44913095625995039322014-03-23T13:29:00.000-07:002016-04-29T05:20:18.061-07:00Debugging Android HTTP traffic with Charles Proxy and Genymotion emulatorGenymotion (<a href="http://www.genymotion.com/">http://www.genymotion.com/</a>) is a great tool for debugging your Android code.<br />
It is much faster than the Android emulator as it is based on x-86 native code, and unless you really do some low level work, is indistinguishable from a real device. It actually is faster and smoother than most devices, so make sure you also test on real devices...<br />
Anyway, it became an important tool for me while developing. Highly recommended!<br />
<br />
Occasionally you need to debug HTTP calls (to web sites, or more often to server API end points), and for that an HTTP proxy as Charles (http://www.charlesproxy.com/) is another great tool. Its paid version already returned its cost 10 fold for me...<br />
<br />
So, first you will need to setup charles on your Mac as the proxy for the emulated Android machine. The key is to know the local mac IP is 10.0.3.2 and the default port for the proxy is 8888 (if you have not played with Charles settings).<br />
Detailed instructions for setting the proxy on the Android virtual machine in Genymotion are available here: <a href="http://rexstjohn.com/using-genymotion-charles-proxy/">http://rexstjohn.com/using-genymotion-charles-proxy/</a>:<br />
And repeated for your convenience<br />
<ul>
<li>In your Genymotion Android emulator…</li>
<li>Settings -> Wifi -> Press and hold your active network</li>
<li>Select “Modify Network”</li>
<li>Select “Show Advanced Options”</li>
<li>Select “Proxy Settings -> Manual”</li>
<li>Set your Proxy to: 10.0.3.2 (Genymotion’s special code for the local workstation)</li>
<li>Set your Port to: 8888</li>
<li>Press Save</li>
</ul>
Now, this worked fine as long as I was using unencrypted http, but if you need to work with SSL encrypted APIs and need to get the clear data to debug your stuff, you can set Charles to provide the SSL certificate.<br />
To do that you need to follow these instructions (the instructions are available here: <a href="http://stackoverflow.com/questions/3976728/how-to-configure-ssl-certificates-with-charles-web-proxy-and-the-latest-android">http://stackoverflow.com/questions/3976728/how-to-configure-ssl-certificates-with-charles-web-proxy-and-the-latest-android</a> between the "brakertech" answer and "bkurzius: answer):<br />
<ol>
<li>Install fully licensed charles version</li>
<li>In Charles, goto Proxy -> Proxy Settings -> check “Enable Transparent HTTP Proxying”</li>
<li>Proxy -> Proxy Settings -> SSL TAB -> check “enable SSL Proxying”</li>
<li> Add your host to the list of Locations (e.g. www.secure.com) with port 443</li>
<li>Download the Charles certificate here: <a href="http://www.charlesproxy.com/documentation/using-charles/ssl-certificates/">http://www.charlesproxy.com/documentation/using-charles/ssl-certificates/</a></li>
<li>unzip the certificate, and then send it by email to your emulated device (or make it available for download from the emulated device browser).</li>
<li>When you get it on the device and open it, you will be able to install it by giving it a name. Depending on the Android OS version, you may need to first set a screen lock PIN or password</li>
</ol>
Once the certificate is installed, you can intercept encrypted SSL traffic using Charles.<br />
<br />
<br />
<br />
<hr />
And a message from our advertisers:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today" target="_blank"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFDPS7zTTDuthRShMn6UNbpFdmVebdH2hQJYajI3cAFIdzJC5lQRdQNcfF3O8oe0uowMN2kZ09fFkn6yMNU-0ZPU70RxLNUUNUCMB6JHreJQm_n4Xuz2iA4M5XVjawdGB1TvAbLbauP9I/s1600/toptal.png" /></a></div>
<br />
Toptal provides remote engineers and designers of high quality. I recommend them. Follow this link (full disclosure: this is my affiliate link):<br />
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today">https://www.toptal.com/#engage-honest-computer-engineers-today</a><br />
<br />
<br />
<br />Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com1tag:blogger.com,1999:blog-5488803742537723823.post-46493780816509453912014-01-03T06:32:00.001-08:002014-04-06T00:49:18.597-07:00Setting up a local Google App Engine Development Environment for Python<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 28px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Installing Google App Engine Dev. Environment</span></div>
<b id="docs-internal-guid-5aa453f7-5883-1de3-49f9-0ea5771cc39e" style="font-weight: normal;"><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></b>
<br />
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I have a mac running latest OSX 10.9 Mavericks. I want to be able to develop and test a Python Google App Engine app on this machine. This is the list of steps I took to make it happen. I am starting from a fairly clean machine as my older mac have multiple installations of Python, and I seem to get stuck attempting to setup my dev environment on it.</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><br /></span>
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I placed this list here, as some of the documentation around the web is scattered and sometimes confusing or outdated. This was done on OSX Mavericks.</span><br />
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><br /></span>
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Some of the steps below are specific to my own app (pycrypto, PIL and lxml may or may not be needed by your app).</span></div>
<br />
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">1. Python is installed by default (version 2.7.5 is bundled with OSX)</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">2. sudo easy_install pip</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">3. Install XCode from App Store</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">4. Install XCode command line tools: xcode-select --install</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">5. install wget: </span><a href="http://coolestguidesontheplanet.com/install-and-configure-wget-on-os-x/" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">http://coolestguidesontheplanet.com/install-and-configure-wget-on-os-x/</span></a><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span>had to use the ./configure --with-ssl=openssl command and not ./configure as mentioned</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">6. install setuptools: wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | sudo python (</span><a href="https://pypi.python.org/pypi/setuptools#installation-instructions" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">https://pypi.python.org/pypi/setuptools#installation-instructions</span></a><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">)</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">7. install PIL: sudo pip install Pillow</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">8. install Crypto (used by my app): sudo pip install pycrypto</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">9. install lxml (used by goose): sudo pip install lxml (if fails, you can try: sudo apt-get install python-lxml)</span></div>
<b style="font-weight: normal;"><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></b>
<br />
<b style="font-weight: normal;">UPDATE: had to install libjpeg: sudo apt-get install libjpeg-dev which forced me to re-install Pillow</b><br />
<b style="font-weight: normal;"><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></b>
<br />
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">SUCCESS!</span></div>
<br />
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: 'Trebuchet MS'; font-size: 28px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Cleaning Up My Old Mac Python</span></div>
<b id="docs-internal-guid-5faa5870-5e75-3e42-2ca8-519e007fa78c" style="font-weight: normal;"><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></b>
<br />
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Ok, now that I have the app engine development environment setup on a new mac, and some test scripts running fine, I am going to clean up my old mac (which is actually my work machine - a Mid 2012 Mac Book Air 13” with OSX 10.9.1).</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Starting from here: </span><a href="http://stackoverflow.com/questions/1909249/how-to-clean-up-my-python-installation-for-a-fresh-start" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">http://stackoverflow.com/questions/1909249/how-to-clean-up-my-python-installation-for-a-fresh-start</span></a><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<b style="font-weight: normal;"><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></b>
<br />
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">My understanding is that I can remove any python.org installations that are under /Library/Frameworks/Python.framework.</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I want to be left with the Apple installed python 2.7.5 which is under /System/Library/Frameworks/Python.framework</span></div>
<b style="font-weight: normal;"><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></b>
<br />
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">So, I am following the link suggested in the above SO answer: </span><a href="http://bugs.python.org/issue7107" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">http://bugs.python.org/issue7107</span></a><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">It seems a bit radical, but after looking into what I got from running the list of files, it seemed to be fine. So I just did that and deleted all the directories and files listed under /Library/Frameworks/Python.framework various old versions, the Applications folder and the Receipts folder.</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Finally, I made a symbolic link to the Apple python from /Library/Frameworks by:</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">cd /Library/Frameworks</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">then (just to be on the safe side):</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">sudo mv Python.framework Python.framework.orig</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">and then</span></div>
<div dir="ltr" style="margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: Arial;"><span style="font-size: 14.999999046325684px; line-height: 14.166666030883789px; white-space: pre-wrap;">ln -s /System/Library/Frameworks/Python.framework/ Python.framework</span></span></div>
<div dir="ltr" style="margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: Arial;"><span style="font-size: 14.999999046325684px; line-height: 14.166666030883789px; white-space: pre-wrap;"><br /></span></span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Next, I changed the python in the GAE Launcher preferences to /usr/bin/python2.7 and Voila!</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">All is well, and I can move on to do the tests here.</span></div>
<b style="font-weight: normal;"><span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></b><br />
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Tests are running on my local machine.</span></div>
<br />
<span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;"></span><br />
<span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;"></span><br />
<span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;"></span>Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com2tag:blogger.com,1999:blog-5488803742537723823.post-27383815625282794272013-08-22T04:10:00.000-07:002013-08-22T04:11:34.831-07:00Detecting If An Android App Is In The BackgroundWas trying to understand when does my app go to the background. The Activity lifecycle methods does not help, as they can be called when switching between activities inside my app.<br />
After a bit of googling, I found this: http://www.apptentive.com/blog/detecting-android-app-starts-and-stops/<br />
<br />
Very good, and works like a charm, but please replace the calls to commit() with calls to apply() - see my previous post here: <a href="http://srooltheknife.blogspot.com/2013/08/stackoverflow-programming.html">http://srooltheknife.blogspot.com/2013/08/stackoverflow-programming.html</a><br />
<br />
<br />Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-25536465217345109682013-08-20T01:08:00.000-07:002013-08-23T05:34:26.398-07:00StackOverflow ProgrammingProgramming is my profession. I am doing this for a long time (damn, its over 30 years by now).<br />
<br />
In the early stages of my career, the way to learn the trade, was by one of three ways:<br />
- get subscription to some early stage hobbyist magazine (I fondly remember Byte magazine)<br />
- get some books<br />
- get a mentor<br />
<br />
I was lucky enough to have a great mentor that introduced me to the field - my uncle Dani who was a programmer in the 70s doing really cool stuff (real-time, complex systems that were ahead of their time). He introduced me to object-oriented programming before it was IN, using languages as Forth, and Modula. He helped me get my first Mac (the cost of which was like a used car here).<br />
<br />
Anyway, at that time you had to read a lot. From paper.<br />
<br />
Fast forward 15 years, and the internet was taking the stage.<br />
<br />
Now, knowledge quickly started to spread, be shared, and be available to everyone.<br />
<br />
Fast forward 15 more years, and now our brains are re-wired. We don't remember anything we learn. We don't have to... (see the book "The Shallows" for more on this subject - link below).<br />
<br />
Its the age of StackOverflow programming.<br />
<br />
Sometimes, it is great. Sometimes its a dangerous, or problematic way... Here's a little anecdote.<br />
<br />
Doing Android programming, I stored some long term values in the SharedPreferences object. Turns out that some examples on the internet tell you to use editor.commit() after you store or modify your values.<br />
Well, if you don't need the result of this, you would be better off if you use editor.apply().<br />
<br />
As the Android documentation say:<br />
<br />
<div style="background-color: #f9f9f9; color: #222222; font-family: Roboto, sans-serif; font-size: 14.166666030883789px; line-height: 18.997394561767578px; margin-bottom: 1em; margin-left: 1em;">
Unlike <code style="color: #006600; font-size: 13px; line-height: 14px;"><a href="http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#commit()" style="color: #258aaf; text-decoration: none;">commit()</a></code>, which writes its preferences out to persistent storage synchronously, <code style="color: #006600; font-size: 13px; line-height: 14px;"><a href="http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply()" style="color: #258aaf; text-decoration: none;">apply()</a></code> commits its changes to the in-memory <code style="color: #006600; font-size: 13px; line-height: 14px;"><a href="http://developer.android.com/reference/android/content/SharedPreferences.html" style="color: #258aaf; text-decoration: none;">SharedPreferences</a></code> immediately but starts an asynchronous commit to disk and you won't be notified of any failures. If another editor on this <code style="color: #006600; font-size: 13px; line-height: 14px;"><a href="http://developer.android.com/reference/android/content/SharedPreferences.html" style="color: #258aaf; text-decoration: none;">SharedPreferences</a></code> does a regular <code style="color: #006600; font-size: 13px; line-height: 14px;"><a href="http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#commit()" style="color: #258aaf; text-decoration: none;">commit()</a></code> while a <code style="color: #006600; font-size: 13px; line-height: 14px;"><a href="http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply()" style="color: #258aaf; text-decoration: none;">apply()</a></code> is still outstanding, the <code style="color: #006600; font-size: 13px; line-height: 14px;"><a href="http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#commit()" style="color: #258aaf; text-decoration: none;">commit()</a></code>will block until all async commits are completed as well as the commit itself.</div>
<div style="background-color: #f9f9f9; color: #222222; font-family: Roboto, sans-serif; font-size: 14.166666030883789px; line-height: 18.997394561767578px; margin-bottom: 1em; margin-left: 1em;">
As <code style="color: #006600; font-size: 13px; line-height: 14px;"><a href="http://developer.android.com/reference/android/content/SharedPreferences.html" style="color: #258aaf; text-decoration: none;">SharedPreferences</a></code> instances are singletons within a process, it's safe to replace any instance of <code style="color: #006600; font-size: 13px; line-height: 14px;"><a href="http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#commit()" style="color: #258aaf; text-decoration: none;">commit()</a></code> with <code style="color: #006600; font-size: 13px; line-height: 14px;"><a href="http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply()" style="color: #258aaf; text-decoration: none;">apply()</a></code> if you were already ignoring the return value.</div>
<div style="background-color: #f9f9f9; color: #222222; font-family: Roboto, sans-serif; font-size: 14.166666030883789px; line-height: 18.997394561767578px; margin-bottom: 1em; margin-left: 1em;">
You don't need to worry about Android component lifecycles and their interaction with <code style="color: #006600; font-size: 13px; line-height: 14px;">apply()</code> writing to disk. The framework makes sure in-flight disk writes from <code style="color: #006600; font-size: 13px; line-height: 14px;">apply()</code> complete before switching states</div>
References: http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply()<br />
<br />
Maybe the lesson here is really "Read the Documentation" and don't just code by copy and paste - at this age of Stack Overflow programming, spend the time reading. At least from time to time...<br />
<br />
<br />
<div style="background-color: #ffeedd;">
<i>Edit: some people raised a concern that with apply() you need to worry about "race conditions" or other issues. Again, READ THE DOCS (as quoted above):</i><br />
<br />
<b>As SharedPreferences instances are singletons within a process, it's safe to replace any instance of commit() with apply() if you were already ignoring the return value.</b>
</div>
<br />
<div>
<b><span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></b></div>
<br />
<iframe frameborder="0" marginheight="0" marginwidth="0" scrolling="no" src="http://rcm-na.amazon-adsystem.com/e/cm?lt1=_blank&bc1=000000&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=sroo-20&o=1&p=8&l=as4&m=amazon&f=ifr&ref=ss_til&asins=0393339750" style="height: 240px; width: 120px;"></iframe>Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-54162493694647687572013-08-05T04:36:00.000-07:002013-08-05T04:38:22.423-07:00Keerbot Drawing ProgramMy friend Gilad is building a wall drawing robot.<br />
<br />
A very cool one.<br />
<br />
You can see some of his work here: <a href="http://www.keerbot.com/">http://www.keerbot.com/</a><br />
<br />
Gilad asked me to write a little utility that will allow him to better debug the robot.
This little tool allows you to draw on the PC, and then save the resulting drawing to a text file with G-Codes the robot can understand.
It's a C# Visual Studio 2010 express project, but you can easily adopt it to any .Net environment.<br />
<br />
I think it's a good tutorial for anyone who wants to create a simple Windows drawing program.<br />
<br />
Hope you like that, and go check the Keerbot site and stay tuned for more cool stuff from Gilad.<br />
<br />
So, here is the code, and you can get the whole VisualStudio project here: <a href="https://dl.dropboxusercontent.com/u/3647610/KeerbotProject.zip">https://dl.dropboxusercontent.com/u/3647610/KeerbotProject.zip</a><br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">using System;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">using System.Collections.Generic;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">using System.ComponentModel;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">using System.Data;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">using System.Drawing;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">using System.Linq;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">using System.Text;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">using System.Windows.Forms;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// Keerbot Drawing Creator Program</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// Written by: Israel Roth</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// This code is provided with no warranties, but its free to use for any purpose.</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// (c) 2013. Israel Roth</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">namespace Keerbot1</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">{</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> public partial class Form1 : Form</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private bool isMouseDown = false;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private List<point> points;</point></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private List<list oint="">> shapes;</list></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> // scalePoint translate a point from the screen coordinates to the Keerbot coordinate space</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private Point scalePoint(Point inPoint)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> int xx = (inPoint.X - 175) * 2;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> int yy = (250 - inPoint.Y) * 2;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> return new Point(xx, yy);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> public Form1()</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> InitializeComponent();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> points = new List<point>(100);</point></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> shapes = new List<list oint="">>(10);</list></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private void panel1_MouseDown(object sender, MouseEventArgs e)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> isMouseDown = true;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private void panel1_MouseUp(object sender, MouseEventArgs e)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> isMouseDown = false;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> if (points.Count > 2)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> shapes.Add(points);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> points = new List<point>(100);</point></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private void panel1_MouseMove(object sender, MouseEventArgs e)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> Point scaled = scalePoint(e.Location);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> toolStripStatusLabel1.Text = scaled.X + "," + scaled.Y;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> if (isMouseDown)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> points.Add(new Point(e.X, e.Y));</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> panel1.Invalidate();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private void panel1_Paint(object sender, PaintEventArgs e)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> if (shapes.Count > 0)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> foreach (List<point> s in shapes)</point></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> DrawPoints(s, e);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> DrawPoints(points, e);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private void DrawPoints(List<point> points, PaintEventArgs e)</point></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> if (points.Count > 2)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> Pen pen = new Pen(Color.Black, 1);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> Point[] pArray = points.ToArray();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> e.Graphics.DrawLines(pen, pArray);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private void button1_Click(object sender, EventArgs e)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> saveFileDialog1.ShowDialog();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private void saveFileDialog1_FileOk(object sender, CancelEventArgs e)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> string name = saveFileDialog1.FileName;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> using (System.IO.StreamWriter file = new System.IO.StreamWriter(name))</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> foreach (List<point> s in shapes)</point></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> bool isFirst = true;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> foreach (Point q in s)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> Point p = scalePoint(q);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> if (isFirst)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> file.WriteLine(startLineText.Text + " X" + p.X + " Y" + p.Y + " Z" + zUpText.Text);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> isFirst = false;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> else</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> file.WriteLine(startLineText.Text + " X" + p.X + " Y" + p.Y + " Z" +</span><br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;"> </span><span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;"> </span><span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;"> </span><span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;"> </span><span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;"> </span><span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;"> </span><span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;">zDownText.Text);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> private void clearButton_Click(object sender, EventArgs e)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> shapes.Clear();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> points.Clear();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> panel1.Invalidate();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">}</span><br />
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div>
<br /></div>
Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com1tag:blogger.com,1999:blog-5488803742537723823.post-78110809332732140862013-07-25T14:28:00.003-07:002013-07-25T14:31:47.700-07:00Automating Android TestingSo, I am dealing now with improving the performance of an Android app I am working on.<br />
The app is an education app where users can view their books, collaborate and do many more things. There are many things happening in this app, and some of them negatively impact the user experience in terms of performance and responsiveness.<br />
As I am approaching the task, I decided that I must have some ways to measure the current performance of the app, and its various parts.<br />
I basically want to perform a user operation, and measure the time it takes the app to respond.<br />
Assuming the way this works is:<br />
User Input -> Various lengthy operations -> Display updated<br />
If I can improve the lengthy operations performance, I will get a better user experience.<br />
To establish a method for measuring the performance and any progress I make, and not just basing this on subjective feeling, I wanted to make some automated framework, whereby a script can activate user actions, and report back the time it took the app operations until the desired feedback is presented to the user.<br />
There are various methods described elsewhere, some rely on accessibility and automating various widget actions based on that. In my case, I do not have widgets, and I wanted a simpler way.<br />
So my simple solution consists of a small footprint web server (or http daemon) embedded in my app. I based this on Nano HTTPD: https://github.com/NanoHttpd/nanohttpd which is a very simple http daemon you use by subclassing the NanoHTTPD class.<br />
When you subclass NanoHTTPD, you are expected to override the serve method to respond to http requests.<br />
<br />
I wrote mine to look for parameters in the query string this way:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>
public Response serve(String uri, Method method, Map<string string=""> headers,
Map<string string=""> parms, Map<string string=""> files) {
for (Map.Entry<string string=""> kv : parms.entrySet()) {
String key = kv.getKey();
String strValue = kv.getValue();
if (key.compareTo("cmd") == 0) {
_callbackHandler.TA_PerformCommand(strValue);
break;
}
else {
Log.d("httpd", "Invalid param: " + key);
}
final String html = "Command Received";
return new NanoHTTPD.Response(Response.Status.OK, MIME_HTML, html);
}
</string></string></string></string></code></pre>
<br />
The callback handler is an object passed to the constructor of my NanoHTTPD sub-class.<br />
<br />
Since some of the operations I try to measure take time, and I want to measure that time, I added logic to my serve method to wait and sleep (since it is anyway being run on a separate thread) until the operation is done, and to report back the timing of this operation. This is the basic code:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>
public Response serve(String uri, Method method, Map<string string=""> headers,
Map<string string=""> parms, Map<string string=""> files) {
for (Map.Entry<string string=""> kv : parms.entrySet()) {
String key = kv.getKey();
String strValue = kv.getValue();
if (key.compareTo("cmd") == 0) {
startMeasurements();
_callbackHandler.TA_PerformCommand(strValue);
while (_inMeasurement) {
try {
Thread.sleep(1L, 0);
long delta = System.currentTimeMillis() -
_startTime;
if (delta > 6000L) { // timeout after 6 sec
addMeasurement("timeout");
endMeasurements();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String htmlResult = measurementsToHTML();
return new NanoHTTPD.Response(Response.Status.OK,
MIME_HTML, htmlResult);
}
else {
Log.d("httpd", "Invalid param: " + key);
}
final String html = "Command Received";
return new NanoHTTPD.Response(Response.Status.OK, MIME_HTML, html);
}
</string></string></string></string></code></pre>
<br />
The callback handler's TA_PerformCommand calls my server class endMeasurements once the operation is done setting the _inMeasurement flag to false.<br />
If the action takes more than 6 seconds (a safe upper limit in my case), the loop times out and we end the measurement.<br />
<br />
Once I have this little server set in my app, it is quite easy to write a testing script using JMeter, or a nice little app I found for my Mac: <a href="http://fakeapp.com/">http://fakeapp.com/</a><br />
<br />
It is now quite easy for me to repeat a sequence of tests, capture the times, tweak some of the code and test again.<br />
<br />
<br />
<br />Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-23417466772066492742013-05-26T22:58:00.001-07:002013-05-26T22:58:37.164-07:00Analyzing SQLite on AndroidWorking on an Android project with quite a sophisticated SQLite DB, I am now trying to optimize the program access to the DB, and wanted to get access to the DB file. I don't like to root my devices (maybe I am over-sensitive about that), so I used this answer by RRTW from Stack Overflow (http://stackoverflow.com/questions/4452538/android-location-of-sqlite-database-on-the-device):<br />
<br />
<div class="p1">
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>
File f=new File("/data/data/your.app.package/databases/your_db.db3");
FileInputStream fis=null;
FileOutputStream fos=null;
try
{
fis=new FileInputStream(f);
fos=new FileOutputStream("/mnt/sdcard/db_dump.db");
while(true)
{
int i=fis.read();
if(i!=-1)
{fos.write(i);}
else
{break;}
}
fos.flush();
Toast.makeText(this, "DB dump OK", Toast.LENGTH_LONG).show();
}
catch(Exception e)
{
e.printStackTrace();
Toast.makeText(this, "DB dump ERROR", Toast.LENGTH_LONG).show();
}
finally
{
try
{
fos.close();
fis.close();
}
catch(IOException ioe)
{}
}
</code>
</pre>
</div>
<br />
So I can now grab the file with DDMS and examine it on my Mac.
Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-36503138225079179832012-10-07T04:32:00.002-07:002013-05-26T22:59:00.657-07:00Arduino Adventures - part IIOK, I am stuck trying to transmit an IR code to turn my air condition on (see previous post here: <a href="http://srooltheknife.blogspot.co.il/2012/10/adventures-with-arduino.html">http://srooltheknife.blogspot.co.il/2012/10/adventures-with-arduino.html</a>). It is getting hot, and I want to turn the damn thing on...<br />
<br />
So, next step is trying to debug what's wrong with my IR transmission. I decided that I need to somehow visually see how my signal looks and compare it to the (working) original remote control signal. So decided to hack a little Oscilloscope - hey, I already made a wave printing arduino sketch in the last round. If I can just measure the signal from the IR receiver using Arduino analog in pin, decode it and send it over the serial connection to the Mac, I can display it nicely on the screen. Ummm... interesting... a new sub-project.<br />
<br />
First, had to get the serial connection between the mac and arduino working. The power of the internet and the cool people helped. Found this:<br />
<a href="http://code.google.com/p/xcode-arduino-serial-communication/">http://code.google.com/p/xcode-arduino-serial-communication/</a><br />
thanks to Pat OKeefe (and in turn to Andreas Mayer's AMSerialPort).<br />
<br />
Next was a simple Arduino sketch to measure and send the analog input from pin 1 using the fastest baud rate possible. Quite simple:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>// The Arduino code.
#define ANALOG_IN 1
void setup() {
Serial.begin(115200); // possible values: 9600, 14400, 19200, 28800, 38400, 57600, or 115200
pinMode(ANALOG_IN, INPUT);
}
void loop() {
int val = analogRead(ANALOG_IN);
Serial.write( val & 0xff);
}
</code></pre>
<br />
Using the Mac code on this github repo [https://github.com/iroth/simplearduinoscope] you select and connect the right serial port, and can see the signal on the screen. If you select the wait for trigger (or click the re-trigger button), the scope will freeze when a signal is detected so you can examine the signal.<br />
<br />
I added a copy to ref button, so I can record the original remote signal, display it as a reference and compare to my transmission.<br />
Here is a screen dump of the two signals. I am not sure why my signal is not affecting the air condition, they look quite similar, no?<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuzFqMpl9El_RlTzD8_WHrrwvfm4szTGpBRBqofgEHTDOrMWZ_mQJnPaIUCizpRskiejxqkHaWzepPbdsKFXuoZtj4IhdUBRwUHg9t20YDgVcpflSNJjeU76ZOXoDGhGRiabT17jldwX4/s1600/scope.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="238" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuzFqMpl9El_RlTzD8_WHrrwvfm4szTGpBRBqofgEHTDOrMWZ_mQJnPaIUCizpRskiejxqkHaWzepPbdsKFXuoZtj4IhdUBRwUHg9t20YDgVcpflSNJjeU76ZOXoDGhGRiabT17jldwX4/s400/scope.png" width="400" /></a></div>
<br />
<br />
The top white line is the signal from the original remote control, the bottom is from my IR transmitter.<br />
Note that my little scope has a scale up and down buttons too - this helps. I am not sure how accurate my scope is, but the signals look quite similar. Still not working.<br />
<br />
Well, at least I hope the digital scope projects helps someone. It is not very accurate, but can be useful for debugging the IR codes (note that the signal above is the decoded signal from the TSOP382 sensor, not the 38 kHz modulated signal which is way too fast for this scope). It can be useful for simple audio frequency examination (but still at the low range of the audio spectrum).<br />
I will need a much faster processor, and probably a much faster communication link to get the samples fast enough to the computer to really make it a useful scope for a wider range of frequencies.<br />
<br />
<br />Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-12472101020223776502012-10-06T11:51:00.001-07:002016-04-29T05:21:00.972-07:00Adventures with ArduinoAttending Geekcon last month really energized me to get to do more "physical" kind of things. We built an arduino based drawing robot. An instructable was posted about it here: <a href="http://www.instructables.com/id/Artibot-a-portrait-painting-robot/">http://www.instructables.com/id/Artibot-a-portrait-painting-robot/</a><br />
<br />
<span style="background-color: #ffe599;">[Edit: part two of the story is available here: <a href="http://srooltheknife.blogspot.co.il/2012/10/arduino-adventures-part-ii.html">http://srooltheknife.blogspot.co.il/2012/10/arduino-adventures-part-ii.html</a>]</span><br />
<br />
So, I was thinking of a next project, and this idea came up - the weather here is quite hot and humid this time of year, so often I go to sleep with the air condition on. This is very wasteful and in addition, makes my wife environment too cold for her taste (is it true that women are often feeling more cold than men?).<br />
So the idea was to make an arduino talk to the air condition mimicking its remote control, allowing me to turn the air condition for 10minutes, get the room pleasant, and while sleeping make the arduino kick the aircon on for 10 minutes or so every hour to keep the room pleasant (but not freezing cold).<br />
<br />
So, got the following items to get going:<br />
1. Arduino Uno board<br />
2. IR sensor - TSOP382 from Vishay (if you are Israeli, this is an excellent source: <a href="http://www.4project.co.il/product/757?sectid=79">http://www.4project.co.il/product/757?sectid=79</a>). This is a very cool sensor that filters the IR modulation frequency used by the remote (38kHz) and gives you a clean signal to work with<br />
3. IR led - <a href="http://www.4project.co.il/product/868">http://www.4project.co.il/product/868</a><br />
<br />
I started with trying to decode my existing air-con remote control signals. Here is the hardware setup I used for this (very simple... the item on the right is the TSOP382 withits input going to pin 2):<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTz6RgGDjwL1ZzSNrMcaoJz9kdtfLpqkBMJzvwX9YNcUKNn0Qjz0PKD-xnFid6VxhKPb7VJXelbqjvZuZKy76IQrAFiVzi2bl1q-Tsas8yW-qnRHONYeOvw0TubPTsnjYM8cBMD6_aqzg/s1600/IRSensor2.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="235" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTz6RgGDjwL1ZzSNrMcaoJz9kdtfLpqkBMJzvwX9YNcUKNn0Qjz0PKD-xnFid6VxhKPb7VJXelbqjvZuZKy76IQrAFiVzi2bl1q-Tsas8yW-qnRHONYeOvw0TubPTsnjYM8cBMD6_aqzg/s320/IRSensor2.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Circuit 1 - IR remote sensor circuit</td></tr>
</tbody></table>
<br />
<br />
This was much harder than I expected...<br />
I started with this code:<br />
http://www.instructables.com/id/Clone-a-Remote-with-Arduino/<br />
simply recording the stream of signals<br />
I assume playing this sequence back through the IR diode (modulating to 38 kHz) will work. But I first wanted to really understand the logic behind this sequence.<br />
This proved to be quite difficult to begin with. First step forward was when I found this great library for reading all kind of IR remote protocols made by Ken Shirrif: http://www.arcfn.com/2009/08/multi-protocol-infrared-remote-library.html<br />
I was disappointed to find that it does not recognise my air-con remote at all. But it did recognise my Apple TV remote, so it gave me some hope...<br />
I decided I need some way to visualize the remote signal. So I wrote this little sketch:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>#include <IRremote.h>
int RECV_PIN = 2;
int BUTTON_PIN = 12;
int STATUS_PIN = 13;
IRrecv irrecv(RECV_PIN);
IRsend irsend;
decode_results results;
void setup()
{
Serial.begin(9600);
irrecv.enableIRIn(); // Start the receiver
pinMode(BUTTON_PIN, INPUT);
pinMode(STATUS_PIN, OUTPUT);
Serial.println("Ready to receive");
}
// Storage for the recorded code
int codeType = -1; // The type of code
unsigned long codeValue; // The code value if not raw
unsigned int rawCodes[RAWBUF]; // The durations if raw
int codeLen; // The length of the code
int toggle = 0; // The RC5/6 toggle state
void printCodeWave() {
char ch;
for (int i = 0 ; i < codeLen ; i++) {
if (i % 2) {
ch = '_';
}
else {
ch = '^';
}
int len = ceil(rawCodes[i] / 300.0);
for (int j = 0 ; j < len ; j++) {
Serial.print(ch);
}
}
Serial.println(";");
}
void printCodeNumeric() {
char ch;
for (int i = 0 ; i < codeLen ; i++) {
if (i % 2) {
Serial.print("s");
}
else {
Serial.print("m");
}
Serial.print(rawCodes[i], DEC);
Serial.print(", ");
}
Serial.println(";");
}
void printCode() {
boolean have_half = false;
boolean half_val = false;
for (int i = 0 ; i < codeLen ; i++) {
boolean isHigh = ((i % 2) == 0);
if (rawCodes[i] > 3400) { //--------------- sync mark + half bit
Serial.print("<SYN+>");
have_half = true;
half_val = isHigh;
}
else if (rawCodes[i] > 2400) { //--------------- sync mark with no extra
Serial.print("<SYN>");
have_half = false;
}
else if (rawCodes[i] > 1400) { //--------------- double len
if (half_val) {
Serial.print("0");
}
else {
Serial.print("1");
}
have_half = true;
half_val = isHigh;
}
else {
if (have_half) { //--------------- last in pair
have_half = false;
if (half_val) {
Serial.print("0");
}
else {
Serial.print("1");
}
}
else { // first in pair
have_half = true;
half_val = isHigh;
}
}
} // end loop
Serial.println(";");
printCodeNumeric();
printCodeWave();
}
// Stores the code for later playback
// Most of this code is just logging
void storeCode(decode_results *results) {
codeType = results->decode_type;
int count = results->rawlen;
codeLen = results->rawlen - 1;
// To store raw codes:
// Drop first value (gap)
// Convert from ticks to microseconds
// Tweak marks shorter, and spaces longer to cancel out IR receiver distortion
for (int i = 1; i <= codeLen; i++) {
if (i % 2) {
// Mark
rawCodes[i - 1] = results->rawbuf[i]*USECPERTICK - MARK_EXCESS;
}
else {
// Space
rawCodes[i - 1] = results->rawbuf[i]*USECPERTICK + MARK_EXCESS;
}
}
codeValue = results->value;
}
void loop() {
if (irrecv.decode(&results)) {
digitalWrite(STATUS_PIN, HIGH);
storeCode(&results);
printCode();
irrecv.resume(); // resume receiver
digitalWrite(STATUS_PIN, LOW);
}
}
</code></pre>
<br />
Opening the serial monitor and clicking the temperature down button twice, gave me this:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>
<SYN><SYN>0001000000010110000000000000000010<SYN><SYN>0001000000010110000000000000000010<SYN><SYN>;
m3000, s3050, m900, s1000, m850, s1100, m850, s2050, m1800, s1100, m850, s1050, m900, s1050, m850, s1050, m850, s1150, m800, s1100, m850, s2000, m1850, s2000, m900, s1100, m1750, s1150, m850, s1000, m900, s1100, m800, s1100, m850, s1050, m850, s1100, m900, s1000, m900, s1100, m800, s1100, m850, s1100, m850, s1050, m850, s1050, m900, s1050, m850, s1050, m850, s1150, m800, s1100, m850, s2050, m1800, s1100, m2800, s3100, m850, s1050, m850, s1100, m850, s2000, m1900, s1000, m900, s1050, m850, s1100, m850, s1100, m800, s1100, m850, s1050, m850, s2100, m1800, s2000, m850, s1100, m1850, s1050, m850, s1100, m800, s1100, m850, s1050, m900, s1100, m850, s1050, m900, s1000, m850, s1100, m850, s1050, m900, s1050, m850, s1100, m850, s1050, m850, s1100, m850, s1100, m800, s1100, m900, s1000, m900, s2000, m1850, s1050, m2900, s3000, m900, ;
^^^^^^^^^^___________^^^____^^^____^^^_______^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^^_______^^^____^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^____^^^^^^^^^^___________^^^____^^^____^^^_______^^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^_______^^^____^^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^^____^^^^^^^^^^__________^^^;
<SYN><SYN>0001000000010100000000000000000010<SYN><SYN>0001000000010100000000000000000010<SYN><SYN>;
m3000, s3050, m850, s1050, m850, s1100, m800, s2100, m1800, s1100, m850, s1050, m900, s1000, m850, s1100, m850, s1100, m800, s1150, m850, s2000, m1800, s2100, m1850, s1000, m850, s1100, m850, s1050, m850, s1100, m850, s1100, m800, s1100, m900, s1050, m800, s1100, m850, s1100, m850, s1100, m800, s1100, m850, s1100, m850, s1050, m900, s1000, m850, s1100, m850, s1100, m850, s1050, m850, s2050, m1800, s1150, m2850, s3050, m800, s1100, m850, s1100, m850, s2050, m1800, s1100, m800, s1100, m850, s1100, m800, s1100, m850, s1100, m800, s1100, m950, s1950, m1800, s2050, m1900, s1000, m850, s1050, m850, s1100, m850, s1100, m800, s1100, m850, s1100, m850, s1050, m850, s1100, m850, s1100, m850, s1100, m800, s1100, m800, s1100, m900, s1050, m850, s1050, m850, s1100, m850, s1050, m950, s1000, m850, s2050, m1850, s1050, m2850, s3050, m900, ;
^^^^^^^^^^___________^^^____^^^____^^^_______^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^_______^^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^____^^^^^^^^^^___________^^^____^^^____^^^_______^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^^_______^^^^^^_______^^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^^____^^^_______^^^^^^^____^^^^^^^^^^___________^^^;
</code></pre>
<br />
In the output avobe, the first raw (starting with <syn>) shows my attempt at decoding the bits, the second raw is the numeric times of marks and spaces, and the bottom one is a graphical view which helped me a lot (trying to understand from the times the code was impossible).<br />
<br />
Seing that bit string and assuming the whole remote control status is transmitted, I was able to decode what each bit (or the important ones) mean.<br />
<br />
This is what I was able to make of it:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>normal mode:
| p | mmm | ff | 0HV00 | tttt |0000 0000 0000 0000 010 |
I feel mode:
| p | mmm | ff | 1HV11 | tttt |0000 0000 0000 0000 010 |
</code></pre>
<br />
each character above is a bit where:<br />
p = 1 when the power button is pressed (same message for on and off) ; 0 for any other button<br />
mmm = mode where 001 = cool, 010 = heat, 011 is a drop (not sure what this means), 100 is fan only, 101 is "recycle" symbol (not sure what this means either).<br />
ff = fan speed where 0 is low, 1 is medium, 2 is high and 3 is auto<br />
H and V are the vertical and horizontal sweeping blades state<br />
tttt = Temperature in degrees celsius - 15 (i.e. 0010 = 17, 0110 = 21, etc.)<br />
<br />
At this stage I was ready to try and control my air condition. I used a 2n3904 transistor to switch on the IR LED (after finding out that it is a bad idea to drive it directly from the Arduino pin). Here is the hardware setup:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLiHGc74rZQ7fahM74DkrOpJ1Pvapw2LDTTiVANMzGi9Pae6UbEjpbraCDP3-i5SeK4jiUuyrY8wRCsX3KEbB9BIJ2YhyctRMXHhsVDKjADYEz6XGieBKHuMbZdb9Ku45nJLLJdCEFPPM/s1600/IRRemoteCircuit_bb.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="236" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLiHGc74rZQ7fahM74DkrOpJ1Pvapw2LDTTiVANMzGi9Pae6UbEjpbraCDP3-i5SeK4jiUuyrY8wRCsX3KEbB9BIJ2YhyctRMXHhsVDKjADYEz6XGieBKHuMbZdb9Ku45nJLLJdCEFPPM/s320/IRRemoteCircuit_bb.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Circuit 2 - IR LED push button transmitter</td></tr>
</tbody></table>
<br />
It has a push button connected to pin 12 (with a 10k resistor to ground), and pin 3 driving with PWM the IR LED via a 10k resistor going to the base of the 2N3904 transistor and an 82 ohm resistor to limit the current to the LED.<br />
<br />
Started experimenting with the following sketch which takes a command according to the bit fields I explained above (in this case an on/off command with temperature set at 24 degrees and fan speed at low).<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>#include <IRremote.h>
int BUTTON_PIN = 12;
int STATUS_PIN = 13;
IRsend irsend;
decode_results results;
void setup()
{
Serial.begin(9600);
pinMode(BUTTON_PIN, INPUT);
pinMode(STATUS_PIN, OUTPUT);
Serial.println("Ready to send");
}
unsigned int rawCodes[RAWBUF]; // The durations if raw
int codeLen; // The length of the code
unsigned char turnOnOffCmd[34] = {
1, // power on
0, 0, 1, // cool (010 - heat, 011 - drop, 100 - fan, 101 - circ)
0, 0, // fan (00 - low, 01 - med, 10 - high, 11 - auto)
0, // i feel off (1 - on)
0, // horiz vents movement
0, // up-down vents movement
0, 0,
1, 0, 0, 1, // 24 deg = T(C) - 15
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 1, 0
};
int pulseWidthM = 1000;
int pulseWidthS = 950;
void sendCode(int repeat) {
codeLen = 0;
rawCodes[codeLen++] = 2900; // sync high
rawCodes[codeLen++] = 2900; // sync low
unsigned char lastVal = 0;
unsigned char lead, trail;
int leadW, trailW;
for (int i = 0 ; i < 34 ; i++) {
unsigned char bit = turnOnOffCmd[i];
if (bit) {
lead = 0;
trail = 1;
leadW = pulseWidthS;
trailW = pulseWidthM;
}
else {
lead = 1;
trail = 0;
leadW = pulseWidthM;
trailW = pulseWidthS;
}
if (lead == lastVal) { // add to last valuse and emit short for next
rawCodes[codeLen-1] += leadW;
rawCodes[codeLen++] = trailW;
}
else { // emit two short pulse
rawCodes[codeLen++] = leadW;
rawCodes[codeLen++] = trailW;
}
lastVal = trail;
}
// Assume 38 KHz
irsend.sendRaw3Times(rawCodes, codeLen);
}
int lastButtonState;
void loop() {
// If button pressed, send the code.
int buttonState = digitalRead(BUTTON_PIN);
if (lastButtonState == HIGH && buttonState == LOW) {
Serial.println("Released");
}
if (buttonState) {
Serial.println("Pressed, sending");
digitalWrite(STATUS_PIN, HIGH);
sendCode(lastButtonState == buttonState);
digitalWrite(STATUS_PIN, LOW);
delay(50); // Wait a bit between retransmissions
}
lastButtonState = buttonState;
}
</code></pre>
<br />
Note that in the code above I am using the IRRemote library of Ken Shirriff, but I am using a method not included with the library - sendRaw3Times. This is part of my attempts to make this thing work. So far it does not... I am stuck here, and I hope that the decoding part was useful enough for you. If any of the readers can help with that - it would be great if you add this to the comments section below. Otherwise, stay tuned to my continuing adventrures... I do not intend to stop here...<br />
<br />
OK, made some progress (not there yet, but had lots of fun). see the second part of this here: <a href="http://srooltheknife.blogspot.co.il/2012/10/arduino-adventures-part-ii.html">http://srooltheknife.blogspot.co.il/2012/10/arduino-adventures-part-ii.html</a>
<br />
<br />
<hr />
And a message from our advertisers:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today" target="_blank"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFDPS7zTTDuthRShMn6UNbpFdmVebdH2hQJYajI3cAFIdzJC5lQRdQNcfF3O8oe0uowMN2kZ09fFkn6yMNU-0ZPU70RxLNUUNUCMB6JHreJQm_n4Xuz2iA4M5XVjawdGB1TvAbLbauP9I/s1600/toptal.png" /></a></div>
<br />
Toptal provides remote engineers and designers of high quality. I recommend them. Follow this link (full disclosure: this is my affiliate link):<br />
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today">https://www.toptal.com/#engage-honest-computer-engineers-today</a><br />
<br />
<br />
<br />Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com10tag:blogger.com,1999:blog-5488803742537723823.post-30466754951346557802012-05-13T22:08:00.002-07:002013-08-05T12:01:27.664-07:00Background loading a Cocos2d Sprite from URLBased on Steffen Itterheim's excellent article <a href="http://www.learn-cocos2d.com/2012/02/cocos2d-webcam-viewer-part-2-asynchronous-texture-loading/">here</a>, I created a CCSprite class to download itself in the background. You create such a sprite giving it a default image:<br />
<br />
<div class="p1">
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code> LGNetworkSprite *myImage = [LGNetworkSprite spriteWithFile:@"fbDefault.png"];
myImage.position = ccp(_screenSize.width * 0.75, _screenSize.height * 0.6);
[self addChild:myImage];
</code></pre>
</div>
<br />
Then, you can set the URL of the image to download, and it will do the magic in the background:<br />
Note: the local file name should be unique if you download different images.<br />
<br />
<div class="p1">
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code> </code><span class="s1" style="background-color: transparent;">[myImage </span><span style="background-color: transparent;">loadFromURLString</span><span class="s1" style="background-color: transparent;">:imgUrl </span><span style="background-color: transparent;">withLocalFileName</span><span class="s1" style="background-color: transparent;">:myID];</span></pre>
</div>
<br />
<br />
The code itself is quite simple (once you learn Steffen's teachings...):
<br />
<pre class="brush: csharp">//
// LGNetworkSprite.h
// CatchTheBall
//
// Created by Israel Roth on 5/12/12.
// Based on Steffen Iterheim code:
// http://www.learn-cocos2d.com/2012/02/cocos2d-webcam-viewer-part-2-asynchronous-texture-loading/
// Copyright 2012 Labgoo LTD. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "cocos2d.h"
@interface LGNetworkSprite : CCSprite {
}
- (void) loadFromServerAddress: (NSString*) serverAddr fileName: (NSString *) fileName;
- (void) loadFromURLString: (NSString *) urlString withLocalFileName: (NSString *) fileName;
@end
</pre>
<br />
And the m file:
<br />
<pre class="brush: csharp">//
// LGNetworkSprite.m
// CatchTheBall
//
// Created by Israel Roth on 5/12/12.
// Based on Steffen Iterheim code:
// http://www.learn-cocos2d.com/2012/02/cocos2d-webcam-viewer-part-2-asynchronous-texture-loading/
// Copyright 2012 Labgoo LTD. All rights reserved.
//
#import "LGNetworkSprite.h"
#import "AsyncFileDownloadData.h"
@implementation LGNetworkSprite
-(NSString*) cacheDirectory
{
NSString* cacheDirectory = nil;
NSArray* pathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
NSUserDomainMask, YES);
if ([pathArray count] > 0)
{
cacheDirectory = [pathArray objectAtIndex:0];
}
return cacheDirectory;
}
- (void) loadFromURLString: (NSString *) urlString withLocalFileName: (NSString *) fileName {
NSString* localFile = [[self cacheDirectory] stringByAppendingPathComponent:fileName];
AsyncFileDownloadData* afd = [[[AsyncFileDownloadData alloc] init] autorelease];
afd.url = [NSURL URLWithString:urlString];
afd.localFile = localFile;
[self performSelectorInBackground:@selector(downloadFileFromServerInBackground:) withObject:afd];
}
-(void) logError:(NSError*)error {
if (error) {
CCLOG(@"%@: %@", error, [error localizedDescription]);
}
}
-(void) downloadFileFromServerInBackground:(AsyncFileDownloadData*)afd
{
NSError* error = nil;
NSData* data = [NSData dataWithContentsOfURL:afd.url options:NSDataReadingMappedIfSafe error:&error];
[self logError:error];
[data writeToFile:afd.localFile options:NSDataWritingAtomic error:&error];
[self logError:error];
// wait until done in this case means that the background thread waits for completion of the task
// manipulating or creating sprites must be done on the main thread
[self performSelectorOnMainThread:@selector(updateTexturesWithAsyncData:) withObject:afd waitUntilDone:NO];
}
-(void) updateTexturesWithAsyncData:(AsyncFileDownloadData*)afd
{
[self updateTexturesFromFile:afd.localFile];
}
-(void) updateTexturesFromFile:(NSString*)file
{
CCTextureCache* texCache = [CCTextureCache sharedTextureCache];
[texCache addImageAsync:file
target:self
selector:@selector(asyncTextureLoadDidFinish:)];
}
-(void) asyncTextureLoadDidFinish:(CCTexture2D*)texture
{
CCTextureCache* texCache = [CCTextureCache sharedTextureCache];
[texCache removeTexture:self.texture];
CGSize prevSize = [self contentSize];
self.texture = texture;
CGSize size = [texture contentSize];
[self setTextureRect:CGRectMake(0.0f, 0.0f, size.width,size.height)];
[self setScale:prevSize.width / size.width];
}
@end
</pre>
<br />
Note: Since I am updating the sprite itself, I do not need to keep the sprite tag. Also, I am using the Library/Caches directory to keep the local file. I think it is the right thing to do (in face, Apple will reject your app if you save to the Documents directory files that should not be there unless you mark them specifically).<br />
Thank you Steffen, and to all of you doing cocos2d - go get yourself some help by purchasing Steffen's book here:
<br />
<br />
<iframe frameborder="0" marginheight="0" marginwidth="0" scrolling="no" src="http://rcm.amazon.com/e/cm?lt1=_blank&bc1=000000&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=sroo-20&o=1&p=8&l=as4&m=amazon&f=ifr&ref=ss_til&asins=1430238135" style="height: 240px; width: 120px;"></iframe>Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com3tag:blogger.com,1999:blog-5488803742537723823.post-4555153852589605972012-03-04T10:58:00.000-08:002016-04-29T05:22:20.449-07:00Custom Transition Animation for Modal View ControllersNeeded to implement a custom animation (push from right/left when presenting a modal view controller, and then pushing out to the other direction when dismissing the dialog).<br />
Yosi found this snippet: http://blog.radi.ws/post/5924267283/custom-modal-uiviewcontroller-transitions<br />
by Evadne Wu. This was so cool, and I think I made it a bit more fun by creating a UIViewController category to do that (I was a bit lazy, so I only implemented the Push in/out transition, but this can be easily extended):<br />
<br />
<div class="p1">
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>
//
// UIViewController+Transitions.h
// Labgoo Misc
//
// Created by Israel Roth on 3/4/12.
// Copyright (c) 2012 Labgoo LTD. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIViewController(Transitions)
- (void) presentModalViewController:(UIViewController *)modalViewController withPushDirection: (NSString *) direction;
- (void) dismissModalViewControllerWithPushDirection:(NSString *) direction;
@end
</code></pre>
</div>
<div class="p5">
<br />
and the implementation:</div>
<div class="p5">
<br /></div>
<div class="p5">
</div>
<div class="p1">
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>//
// UiViewController+Transitions.m
// Labgoo Misc
//
// Created by Israel Roth on 3/4/12.
// Copyright (c) 2012 Labgoo LTD. All rights reserved.
//
#import "UIViewController+Transitions.h"
@implementation UIViewController(Transitions)
- (void) presentModalViewController:(UIViewController *)modalViewController withPushDirection: (NSString *) direction {
[CATransaction begin];
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = direction;
transition.duration = 0.25f;
transition.fillMode = kCAFillModeForwards;
transition.removedOnCompletion = YES;
[[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"transition"];
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[CATransaction setCompletionBlock: ^ {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(transition.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
});
}];
[self presentModalViewController:modalViewController animated:NO];
[CATransaction commit];
}
- (void) dismissModalViewControllerWithPushDirection:(NSString *) direction {
[CATransaction begin];
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = direction;
transition.duration = 0.25f;
transition.fillMode = kCAFillModeForwards;
transition.removedOnCompletion = YES;
[[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"transition"];
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[CATransaction setCompletionBlock: ^ {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(transition.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
});
}];
[self dismissModalViewControllerAnimated:NO];
[CATransaction commit];
}
@end
</code></pre>
</div>
<br />
<div class="p5">
<br /></div>
<div class="p5">
to use it, you import "UIViewController+Transitions.h" in your file and call the presentModalViewController:withPushDirection:</div>
<div class="p5">
the direction parameter receives core animation transition sub-types such as kCATransitionFromRight</div>
<div class="p5">
<br /></div>
<div class="p5">
Enjoy...</div>
<div class="p5">
<br /></div>
<br />
<br />
<hr />
And a message from our advertisers:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today" target="_blank"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFDPS7zTTDuthRShMn6UNbpFdmVebdH2hQJYajI3cAFIdzJC5lQRdQNcfF3O8oe0uowMN2kZ09fFkn6yMNU-0ZPU70RxLNUUNUCMB6JHreJQm_n4Xuz2iA4M5XVjawdGB1TvAbLbauP9I/s1600/toptal.png" /></a></div>
<br />
Toptal provides remote engineers and designers of high quality. I recommend them. Follow this link (full disclosure: this is my affiliate link):<br />
<a href="https://www.toptal.com/#engage-honest-computer-engineers-today">https://www.toptal.com/#engage-honest-computer-engineers-today</a><br />
<br />
<br />
<br />Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com14tag:blogger.com,1999:blog-5488803742537723823.post-91152038678037875062011-11-30T15:49:00.001-08:002013-08-05T12:02:29.310-07:00Growing your revenues in new markets<br />
As we indie developers struggle to get our apps noticed, downloaded, used and pay our bills, we should maximize whatever means we have to get the app available to as many users.<br />
One often neglected direction is localizing your app to serve users in non-English speaking countries.<br />
Publishing your app in a local language can open for you new markets and maximize your revenues, and the effort needed is really not that great.<br />
<br />
There are three parts for making your app available in multiple languages:<br />
<br />
<b>Design Time</b><br />
<br />
While at the design and conception stage of your app, give some thoughts to issues such as:<br />
<br />
<ul>
<li><b><i>Using text vs. using images for buttons, menus or other app elements.</i></b> Using images gives you more flexibility as to the appearance, fonts, effects of your app text. Using strings makes it easier to change your texts or localize them.</li>
<li><b><i>Using special fonts which may or may not have the characters you need in foreign languages.</i></b> If you did decide to use strings, and opt for using custom fonts, make sure the font has the characters for all the languages you want to support. In many cases it is simpler to use unicode fonts from the set of built-in fonts, but sometimes you do need a custom font to get that special look. Keep the character set issue in your thoughts.</li>
<li><b><i>Designing for variable size strings for labels or other GUI elements</i></b>. Text may change size when you switch to a different langauage - width, height and in some cases number of lines will have to be flexible. At the design stage just keep this in mind, and allow your self room for flexibility</li>
</ul>
<b>Development</b><br />
<br />
During development, you should follow Apple guidelines for localization. Refrain from using hard coded text strings, and go for the NSLocalizedString, localized resources, etc.<br />
Also bear in mind the issue of text size and location. You can use NSString method sizeWithFont to get the dimension of text:<br />
<br />
<br />
<div class="p1">
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><span class="Apple-tab-span"> </span>font = [<span class="s1">UIFont</span> <span class="s1">fontWithName</span>:name <span class="s1">size</span>:size];</span></div>
<div class="p1">
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><span class="Apple-tab-span"> </span><span class="s2">if</span>( font )</span></div>
<div class="p1">
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> CGSize dim = [string <span class="s1">sizeWithFont</span>:font];</span></div>
<div class="p1">
<br /></div>
<br />
Sean Berry published an excellent tutorial with examples on Ray Wanderlich's blog site: <a href="http://www.raywenderlich.com/2876/how-to-localize-an-iphone-app-tutorial">http://www.raywenderlich.com/2876/how-to-localize-an-iphone-app-tutorial</a><br />
<br />
<b>Translation</b><br />
<b><br /></b>
In many cases I found Google Translate sufficient (especially for languages I have some experience with), but a more professional approach which is not so expensive is available. I am recommending a service called <a href="http://bit.ly/ua9TeF">One Hour Translation</a> This service is very reasonably priced (around 5-10 cents per word) and the turn around time is phenomenal - I got all my translations within 30 minutes (!!!)<br />
I have no way to judge the translation quality, but they do give you an option (for slightly more money) to use an expert translator for your particular domain, and an option to get the translation proof read by another person.<br />
<br />
<a href="http://bit.ly/ua9TeF">One Hour Translation</a><br />
<br />Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-17909164681251688082011-11-02T23:50:00.000-07:002011-11-24T03:59:56.220-08:00My Art and other interestsI decided to separate my art related posts and place them in a separate blog. You can check them here: <a href="http://sroolart.blogspot.com/">http://sroolart.blogspot.com/</a><br />
I also created a new blog for posts on social / technology / employment issues I am interested in here: <a href="http://societyredesigned.com/">http://societyredesigned.com/</a><br />
<br />
<br class="Apple-interchange-newline" />The Knife (this blog) will be just about programming...<br />
<br />Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-36664341792894444952011-08-30T04:51:00.000-07:002013-08-05T12:03:02.730-07:00OpenGL ES alternate texture renderringA friend asked me for help with getting the sample here: <a href="http://www.mat.ucsb.edu/a.forbes/blog/?p=245">http://www.mat.ucsb.edu/a.forbes/blog/?p=245</a> to work on the iPhone.<br />
<br />
Since I had no experience with OpenGL ES 2 shaders, I thought it would be a good chance to get my hands dirty and learn something about it. I am still not an expert (far from it), so take it all with healthy skepticism.<br />
<br />
This is an interim report, just to let you know the basics are working and provide with the sample code.<br />
<br />
So the code is available here: <a href="http://iroth.net/prog/PingPong.zip">http://iroth.net/prog/PingPong.zip</a><br />
<br />
In addition to the article that was the inspiration of this, I could have not made this work without the help of the great tutorials of Ray Wenderlich and in particular this series of two:<br />
<a href="http://www.raywenderlich.com/3664/opengl-es-2-0-for-iphone-tutorial">http://www.raywenderlich.com/3664/opengl-es-2-0-for-iphone-tutorial</a><br />
<a href="http://www.raywenderlich.com/4404/opengl-es-2-0-for-iphone-tutorial-part-2-textures">http://www.raywenderlich.com/4404/opengl-es-2-0-for-iphone-tutorial-part-2-textures</a><br />
<br />
One more thing - there seems to be some black triangle artifact in the upper left corner I still was unable to understand. If any of you find out - please comment below...<br />
<br />
And while at it, here are some books that really help me get up to speed on iPhone game programming:<br />
<br />
<iframe align="left" frameborder="0" marginheight="0" marginwidth="0" scrolling="no" src="http://rcm.amazon.com/e/cm?t=sroo-20&o=1&p=8&l=bpl&asins=1430233036&fc1=000000&IS2=1&lt1=_blank&m=amazon&lc1=0000FF&bc1=000000&bg1=FFFFFF&f=ifr" style="align: left; height: 245px; padding-right: 10px; padding-top: 5px; width: 131px;"></iframe><br />
<br />
<iframe align="left" frameborder="0" marginheight="0" marginwidth="0" scrolling="no" src="http://rcm.amazon.com/e/cm?t=sroo-20&o=1&p=8&l=bpl&asins=0321735625&fc1=000000&IS2=1&lt1=_blank&m=amazon&lc1=0000FF&bc1=000000&bg1=FFFFFF&f=ifr" style="align: left; height: 245px; padding-right: 10px; padding-top: 5px; width: 131px;"></iframe><br />
<br />
<iframe align="left" frameborder="0" marginheight="0" marginwidth="0" scrolling="no" src="http://rcm.amazon.com/e/cm?t=sroo-20&o=1&p=8&l=bpl&asins=B0043M56Y6&fc1=000000&IS2=1&lt1=_blank&m=amazon&lc1=0000FF&bc1=000000&bg1=FFFFFF&f=ifr" style="align: left; height: 245px; padding-right: 10px; padding-top: 5px; width: 131px;"></iframe><br />Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-13926136144003255822011-06-13T13:52:00.000-07:002013-08-05T12:03:37.059-07:00Improved CCSlider included with Cocos2d iPhone ExtensionsI was happy to get an email from Stepan Generalov asking for permission to publish an improved version he made of CCSlider under an MIT license, which I approved immediately. Good work Stepan!<br />
You can find the improved CCSlider and other cool stuff here:<br />
<a href="http://www.cocos2d-iphone.org/archives/1506">http://www.cocos2d-iphone.org/archives/1506</a>Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-69573946169344493232011-03-24T12:53:00.000-07:002013-08-05T12:04:05.219-07:00Pah! is on the app store - the joy of iOS programming...It is so much fun to create apps for iOS devices. The first time you get your code to run, the first reaction from other people, but the most fun is publishing your product on the app store and watching how the world reacts...<br />
<br />
We just published today the first truely voice-activated game for the iPhone - it is called Pah! and it is guaranteed to make you look silly, and laugh hard (or at least make the people around you laugh), and is there a more nobel cause than to make people laugh??<br />
<br />
The game idea and design are by Eyal Shahar, and programming was done by Yosi Taguri and myself.<br />
Eyal implemented the game few years ago using Flash, and left it alone for a while, but when he showed us the game we laughed so hard, we knew we must make it into an iPhone game. The first working version was done in few hours, and then it took another two months or so of late night work to make it happen...<br />
<br />
The game went up 18 hours ago, and the responses are overwhelming. Here are some:<br />
<br />
<a href="http://www.mobilecrunch.com/2011/03/24/pah-iphone-game-mouth-sounds/">http://www.mobilecrunch.com/2011/03/24/pah-iphone-game-mouth-sounds/</a><br />
<br />
<a href="http://www.theiphoneguru.net/2011/03/24/app-of-the-day-pah-for-iphone-is-voice-activated-and-hilarity-inducing/">http://www.theiphoneguru.net/2011/03/24/app-of-the-day-pah-for-iphone-is-voice-activated-and-hilarity-inducing/</a><br />
<br />
<a href="http://www.igoiphone.com/?p=1593">http://www.igoiphone.com/?p=1593</a><br />
<br />
<a href="http://dailyapp.mobi/index.php/2011/03/pah-for-iphone/">http://dailyapp.mobi/index.php/2011/03/pah-for-iphone/</a><br />
<br />
Check our site too: <a href="http://ahhhpah.com/">http://ahhhpah.com/</a><br />
<br />
I want to give credit here to the amazing <a href="http://www.cocos2d-iphone.org/">Cocos2d</a> framework that really is fun to work with...Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com1tag:blogger.com,1999:blog-5488803742537723823.post-36539612889137752442011-01-05T16:00:00.000-08:002013-08-05T12:04:24.297-07:00Creating a Slider Control in cocos2dI wanted to add a little slider control to allow the user to set the background music level in a game I am working on. So I created this little CCSliderControl class that I think is cute and useful.<br />
<br />
The CCSliderControl is sub-classed from CCLayer. Here is the .h file for it:<br />
<br />
<pre style="background: url("https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpVJFxPPJCynGBcje6cNLC8oAHffL1AxXgS20ogaOJNmV7DPhHuD1dj1K6vHgmZ-zbAmA8ouLBi130dmewINsGh9_7Oyrhev5gu7f6lIntFp4468GDhk9wnZEyXW8BrukKXF3wqF0_jTze/s320/codebg.gif") repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> @protocol CCSliderControlDelegate
- (void) valueChanged: (float) value tag: (int) tag;
@end
@interface CCSliderControl : CCLayer {
float value;
id<CCSliderControlDelegate> delegate;
float minX;
float maxX;
}
@property (nonatomic, assign) float value;
@property (nonatomic, retain) id<CCSliderControlDelegate> delegate;
@end
</code></pre>
<br />
In the init of this class instance, I add two sprites - one for the background, and one for the slider thumb:<br />
<br />
<pre style="background: url("https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpVJFxPPJCynGBcje6cNLC8oAHffL1AxXgS20ogaOJNmV7DPhHuD1dj1K6vHgmZ-zbAmA8ouLBi130dmewINsGh9_7Oyrhev5gu7f6lIntFp4468GDhk9wnZEyXW8BrukKXF3wqF0_jTze/s320/codebg.gif") repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> -(id) init
{
if ((self = [super init]))
{
CCLOG(@"init %@", self);
self.isTouchEnabled = YES;
value = 0;
// add the slider background
CCSprite *bg = [CCSprite spriteWithFile:@"slider_background.png"]; //- TODO: add this to some texture atlas
[self setContentSize:[bg contentSize]];
bg.position = CGPointMake([bg contentSize].width / 2, [bg contentSize].height / 2);
[self addChild:bg];
// add the slider thumb
CGSize thumb_size;
CCSprite *thumb = [CCSprite spriteWithFile:@"slider_thumb.png"]; //- TODO: add this to some texture atlas
thumb_size = [thumb contentSize];
minX = thumb_size.width / 2;
maxX = [self contentSize].width - thumb_size.width / 2;
thumb.position = CGPointMake(minX, [self contentSize].height / 2);
[self addChild:thumb];
}
return self;
}
</code></pre>
<br />
The setValue method that sets the value property of the instance also updates the thumb sprite position:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); color: black; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>- (void) setValue:(float) newValue
{
if (newValue < 0) newValue = 0;
if (newValue > 1.0) newValue = 1.0;
value = newValue;
CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
CGPoint pos = thumb.position;
pos.x = minX + newValue * (maxX - minX);
thumb.position = pos;
}
</code></pre>
<br />
Next step is to register for touch events:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); color: black; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>-(void) registerWithTouchDispatcher
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:-1 swallowsTouches:YES];
}
</code></pre>
<br />
and then handle the touch events:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); color: black; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>-(CGPoint) locationFromTouch:(UITouch *)touch
{
CGPoint touchLocation = [touch locationInView: [touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
CGRect bbox = [self boundingBox];
touchLocation.x -= bbox.origin.x;
touchLocation.y -= bbox.origin.y;
return touchLocation;
}
-(bool) isTouchForMe:(CGPoint)touchLocation
{
CCSprite *bg = (CCSprite *)[[self children] objectAtIndex:0];
return CGRectContainsPoint([bg boundingBox], touchLocation);
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint location = [self locationFromTouch:touch];
bool isTouchHandled = [self isTouchForMe:location];
if (isTouchHandled) {
CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
thumb.color = ccYELLOW;
CGPoint pos = thumb.position;
pos.x = location.x;
thumb.position = pos;
}
return isTouchHandled; // YES for events I handle
}
-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint location = [self locationFromTouch:touch];
if ((location.x < minX) || (location.x > maxX))
return;
CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
CGPoint pos = thumb.position;
pos.x = location.x;
thumb.position = pos;
}
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
thumb.color = ccWHITE;
value = (thumb.position.x - minX) / (maxX - minX);
[delegate valueChanged:value tag:self.tag];
}
</code></pre>
<br />
And the result looks like this:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG5hDGdoRG6rMXLAx3P0tf2zmyTqeKYzSizczmleplG01DPDnirAUQQcRQnZwgMDttZg4qn_bzAWqkofbPol6mrcJwVLW7Xtq3sbm1BXdH_8-TeWV85KI9qdKL0TpzHjkfU0EqBnvw-U8/s1600/Slider.png"><img border="0" height="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG5hDGdoRG6rMXLAx3P0tf2zmyTqeKYzSizczmleplG01DPDnirAUQQcRQnZwgMDttZg4qn_bzAWqkofbPol6mrcJwVLW7Xtq3sbm1BXdH_8-TeWV85KI9qdKL0TpzHjkfU0EqBnvw-U8/s400/Slider.png" width="400" /></a><br />
<br />
You can get the source code from bitbucket by using mercurial:<br />
<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>$ hg clone https://iroth_net@bitbucket.org/iroth_net/ccslider
</code></pre>
Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com8tag:blogger.com,1999:blog-5488803742537723823.post-52046214485220988592010-12-25T00:29:00.000-08:002013-08-05T12:43:37.476-07:00Cocos2D support new and old devices texturesGot a new iPhone game project going - an opportunity to learn some new things which is always fun.<br />
I am using the <b><a href="http://www.cocos2d-iphone.org/">cocos2d</a></b> framework, which really saves me a lot of work.<br />
I still would like to get deeper into OpenGL ES some time later, but for this project it will be enough to have <b>cocos2d</b> handle the low level details.<br />
<br />
One issue I struggled with is that I need to create fairly detailed animation of the scene background.<br />
<br />
Due to the limit on OpenGL ES texture size (especially on older devices where the texture is limited to 1024x1024 pixels), I am faced with two options:<br />
<br />
<ul>
<li>Limit the number of frames (In a 1024x1024 texture I can get 9 frames of size 320x340 which is the size of my background area in the game).</li>
<li>Construct the background area from multiple animations and compose them programmatically. While possible, this makes the design work more cumbersome as the designer needs to supply the needed graphics and their location. </li>
</ul>
Using a larger texture size of 2048x2048 gives me enough room for 36 frames, which is sufficient for quite a nice background animation loop, but should I give up all older devices (prior to iPhone 3Gs)?<br />
<br />
There are millions of those!<br />
<br />
So this leads me to the current solution I am going to use, which is:<br />
<br />
Use a 2048x2048 texture for newer devices, and substitute that for a smaller 1024x1024 texture on older devices in run time.<br />
<br />
But how should I decide which texture to use?<br />
<br />
Well, I will save you all my google searches, as the answer was found right under my nose:<br />
<br />
<b>Cocos2d CCConfiguration class!</b><br />
<br />
Here's the code:<br />
<br />
<span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;"> </span></span><span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;">NSString *plistFile;</span></span><br />
<span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;"> int numFrames; </span></span><br />
<span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;"> </span></span><span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;">if ([[CCConfiguration sharedConfiguration] maxTextureSize] < 2048)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> plistFile = @"highQualityAnimation.plist"];</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> numFrames = 36;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> else</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> plistFile = @"highQualityAnimation.plist"];</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> numFrames = 9;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // cache the frames to the sharedSpriteFrameCache</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> CCSpriteFrameCache * cache = [CCSpriteFrameCache sharedSpriteFrameCache];</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> [cache addSpriteFramesWithFile:plistFile];</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // create the animation</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> CCAnimation * animation = [[CCAnimation alloc] initWithName:@"animation" delay:0.1]; // set the frame rate as needed</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> for ( int i=1; i <= numFrames; ++i )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> NSString * fname = [NSString stringWithFormat:@"animation_frame_%i.png", i];</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> [animation addFrame:[cache spriteFrameByName: fname]];</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> CCSprite *backgroundSprite = [CCSprite spriteWithSpriteFrameName:[NSString stringWithFormat:@"animation_frame_1.png"]];</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> id action = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:animation]];</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> [backgroundSprite runAction:action];</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // add the sprite to the scene layer</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> [self addChild:backgroundSprite];</span></span><br />
That's easy enough, gives good animation on newer devices, and still reasonably support for older devices.Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0tag:blogger.com,1999:blog-5488803742537723823.post-305703869089821242010-12-18T23:48:00.000-08:002013-08-05T12:43:59.728-07:00Easily making the different size icons for iPhone and iPadThis is a small tip for scaling your icon to the different sizes requested by iPhone and iPad applications. I used to do that with Photoshop actions in the past, but if that is not available, you can use this tip with no extra software using Mac OS X Automator:<br />
1. I Start with a high quality 512x512 png file (which you will later rename to iTunesArtwork without the png extension) as this is needed for the App Store.<br />
2. In Automator, create a new Service and add the following steps:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTvrOydr_0XxAIJF6vGYKKPnyGUkeANS5ibJRVjbqans7FKJ2DTGLkXkn_KnE4B6sFVEFa8zG8V6lwv8XyW2NhjtIGKcyTMiqRezb5dZ9qVjBYR7OAVivGwB0UbRhy1mxT386QphKTwxM/s1600/automatorWF.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTvrOydr_0XxAIJF6vGYKKPnyGUkeANS5ibJRVjbqans7FKJ2DTGLkXkn_KnE4B6sFVEFa8zG8V6lwv8XyW2NhjtIGKcyTMiqRezb5dZ9qVjBYR7OAVivGwB0UbRhy1mxT386QphKTwxM/s320/automatorWF.png" width="320" /></a></div>
Save this service as iPhoneIcon (or icon57, or whatever).<br />
3. Similarly, make services for the different sizes as listed here:<br />
Icon-72.png (72x72)<br />
Icon@2x.png (114x114)<br />
Icon-Small.png (29x29)<br />
Icon-Small-50.png (50x50)<br />
Icon-Small@2x.png (58x58)<br />
4. Now place your 512 icon in a folder, in the finder, right-click the icon file and select from the services sub-menu the different services, and voila! you got the scaled icon with the right name.<br />
5. Add to your project resources and you are ready to go...Israel Rothhttp://www.blogger.com/profile/05282134357400070378noreply@blogger.com0