
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
>
  <channel>
    <atom:link href="https://1024kilobyte.de//rss.xml" rel="self" type="application/rss+xml" />
    <title>1024kilobyte</title>
    <link>https://1024kilobyte.de/</link>
    <description>Yet another human written blog.</description>
    <image>
      <url>https://1024kilobyte.de//favicons/favicon-32x32.png</url>
      <title>1024kilobyte</title>
      <link>https://1024kilobyte.de/</link>
      <width>32</width>
      <height>32</height>
    </image>
    
        <item>
          <guid>https://1024kilobyte.de//my-first-ar-app-thingstar</guid>
          <title>My first AR App: ThingstAR</title>
          <description>It has been some time since my last post, but I have a good excuse: I made a new app. And it is already on the Apple App Store 🎉</description>
          <link>https://1024kilobyte.de//my-first-ar-app-thingstar</link>
          <pubDate>Sun, 16 May 2021 19:07:15 +0200</pubDate>
          <category>3d printing</category><category>app development</category><category>AR</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//my-first-ar-app-thingstar">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/App_Icon_Web-e5c09b.avif, /images/posts/migrated/App_Icon_Web-e5c09b.webp, /images/posts/migrated/App_Icon_Web-e5c09b.png" src="/images/posts/migrated/App_Icon_Web-e5c09b.png" alt="ThingstAR App Icon" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">ThingstAR App Icon</figcaption><!--]--></figure><!----> <p>It has been some time since my last post, but I have a good excuse: I made a new app. And it is already on the <a href="https://apps.apple.com/us/app/id1547199958" rel="nofollow">Apple App Store</a> 🎉</p> <p>Since ARKit was released in 2017 I always wanted to create an AR app. But it should at least be useful in any way and not only showcase the AR functionality. As a 3D print hobbyist, I am a big fan of Thingiverse, a platform to share your 3D print projects and models. But you often don’t recognize all the details of a 3D model before slicing or even 3D printing. So it seemed almost obvious to present the 3D models in AR, allowing you to inspect them from every angle.</p> <p>So I developed “ThingstAR”, an iPad AR app to explore Thingiverse. You can browse by lists or a search term, experience the projects in detail and place the 3D print models using AR. Manipulate the model as you like (e.g. scale, move or change color) and even share it as a usdz file with other iOS users.</p> <p>I also added some extra features to make this a well rounded app. To name a few:</p> <ul><li><p>Bookmarks. You can bookmark your favorite projects during browsing and easily access and manage your bookmarks via the “quick access menu” a.k.a. “the boat button”.</p></li> <li><p>Size and Orientation optimized. There are basically three different iPad sizes out there, which are used in portrait or landscape. I specifically optimized the app to adapt to all of these situations.</p></li> <li><p>Light and Dark Mode. Ok this feature is nowadays more of a must have for new apps. But maybe nice to know as a fellow “everything dark” user.</p></li></ul> <p>To get a better understanding of the app, I added some screenshots:</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/02_Mockup_landscape-888b40.avif, /images/posts/migrated/02_Mockup_landscape-888b40.webp, /images/posts/migrated/02_Mockup_landscape-888b40.png" src="/images/posts/migrated/02_Mockup_landscape-888b40.png" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/04_Mockup-6e0812.avif, /images/posts/migrated/04_Mockup-6e0812.webp, /images/posts/migrated/04_Mockup-6e0812.png" src="/images/posts/migrated/04_Mockup-6e0812.png" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/01_Mockup-62a597.avif, /images/posts/migrated/01_Mockup-62a597.webp, /images/posts/migrated/01_Mockup-62a597.png" src="/images/posts/migrated/01_Mockup-62a597.png" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <p>If you‘re interested, you may want to take a look at the <a href="https://thingstar.app" rel="nofollow">App Website</a> or even check it out on the <a href="https://apps.apple.com/us/app/id1547199958" rel="nofollow">Apple App Store</a>.</p> <p>Let me know if you have any feedback, feature requests or just some nice words 😉</p><!--]-->
          ]]></content:encoded>
          <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://1024kilobyte.de///images/posts/migrated/01_Mockup-4b128a.png"/>
          <media:content xmlns:media="http://search.yahoo.com/mrss/" medium="image" url="https://1024kilobyte.de///images/posts/migrated/01_Mockup-4b128a.png"/>          
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//new-project-ribbalight5-a-native-homekit-lamp-using-an-esp8266</guid>
          <title>New Project: RibbaLight5 - A native HomeKit lamp using an ESP8266</title>
          <description>Home automation can be a very complex thing. And as a maker you can already integrate many different gadgets into your home. But when it comes to…</description>
          <link>https://1024kilobyte.de//new-project-ribbalight5-a-native-homekit-lamp-using-an-esp8266</link>
          <pubDate>Mon, 11 Jan 2021 16:01:48 +0100</pubDate>
          <category>ESP8266</category><category>ESP32</category><category>HomeKit</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//new-project-ribbalight5-a-native-homekit-lamp-using-an-esp8266">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><h2 id="the-problem"><a class="heading-link" title="Permalink" aria-hidden="true" href="#the-problem"><span>#</span></a>The Problem</h2> <p>Home automation can be a very complex thing. And as a maker you can already integrate many different gadgets into your home. But when it comes to HomeKit, the apple universe regarding home automation, the most common way is to add Home Assistant (or similar) on a Raspberry Pi to act as the HomeKit broker. The lamps, sensors, a.s.o. then only “talk” to the broker using MQTT. This allows you to create pretty complex scenarios and automations, but it also forces you to add an additional single point of failure into your setup, which can reduce the overall reliability. I always kept back from this solution, as I don’t want to power and service the Raspberry Pi.</p> <h2 id="the-solution"><a class="heading-link" title="Permalink" aria-hidden="true" href="#the-solution"><span>#</span></a>The Solution</h2> <p>I then found a <a href="https://github.com/maximkulkin/esp-homekit" rel="nofollow">github respository</a> which allows you to create native HomeKit devices, running FreeRTOS on an ESP8266 or even an ESP32. You may only need the <a href="https://github.com/maximkulkin/esp-homekit-demo" rel="nofollow">demo repository</a>, which contains all of the demo code, many different examples and the build instructions. The development setup is a bit complex, but the documentation in the Wiki section worked perfectly for me (using the docker stack on a Mac). The idea is basically to write your own gadget code, which then runs as a Task on FreeRTOS, a MIT licensed realtime operating system. There are already a lot of examples in the repository, like for all sorts of sonoff devices, but it is also pretty easy to create your own task for your IoT device by taking a look at the example code.</p> <h2 id="another-homekit-lamp"><a class="heading-link" title="Permalink" aria-hidden="true" href="#another-homekit-lamp"><span>#</span></a>Another HomeKit lamp</h2> <p>This sounded alluring, so now I only needed a use case to build such a thing. As I have quite a history with the WS28XX family of LED strips, meaning having them laying around, it seemed obvious to build a simple lamp. Also obvious was the choice of the enclosure, the <a href="https://www.ikea.com/de/de/p/ribba-rahmen-bambus-90476178/" rel="nofollow">IKEA RIBBA</a> picture frame (the 23cm by 23cm version, white. currently only available in bamboo). It has some great features for DIY projects, as its cheap, offers enough space for the electronics and has a simple yet stylish design. For the ESP part, I wanted to use a cheap development board allowing me to program and power the board via USB but also to power the LEDs without a separate power circuit. Fortunately the NodeMCU v3 offers all those features, as it has a 5V output pin and is less than 7€ on amazon. And as we now know, we should use a 5V led strip, in my case a WS2812B RGB LED strip with 60 leds per meter. This fits easily 144 leds inside the picture frame (12 rows, 12 leds each). So I cut the strip to segments of 12 leds and simply connected the wires on the end of each row to the beginning of the next row, a.s.o. But there is one speciality. Some led strips have such high resistance (per meter), that the leds further away from the power input get darker. To prevent this I simply fed the power into the strip on three different positions. This reduces the maximum resistance to a third, each line only powering four rows of leds.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/RibbaLight_LEDStrip-da1df3.avif, /images/posts/migrated/RibbaLight_LEDStrip-da1df3.webp, /images/posts/migrated/RibbaLight_LEDStrip-da1df3.png" src="/images/posts/migrated/RibbaLight_LEDStrip-da1df3.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <p>Then I only had to connect the strip to the NodeMCU. Power to power (Ground and VV) and data to RX (the port is fixed inside the software as it is using hardware i2s). And the hardware build was finished.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/RibbaLight_Wiring-aca009.avif, /images/posts/migrated/RibbaLight_Wiring-aca009.webp, /images/posts/migrated/RibbaLight_Wiring-aca009.png" src="/images/posts/migrated/RibbaLight_Wiring-aca009.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/RibbaLight_Front-77276a.avif, /images/posts/migrated/RibbaLight_Front-77276a.webp, /images/posts/migrated/RibbaLight_Front-77276a.png" src="/images/posts/migrated/RibbaLight_Front-77276a.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <p>As you can see the enclosure is almost unchanged. I just added a cutout for the NodeMCU, giving it more space to breathe and added a notch to the bottom of the frame for the usb power cable. And finally to create a more defuse light, so you can’t see the single leds, I put a frosted film behind the front glass. Done.</p> <h2 id="the-software"><a class="heading-link" title="Permalink" aria-hidden="true" href="#the-software"><span>#</span></a>The Software</h2> <p>In my case the demo project “led_strip” almost did the trick, I just needed set my wifi credentials, change the number of leds and be aware of the wiring. Ok, I also overclocked the ESP to 160MHz, as in my opinion this should be the default mode for the ESP8266. Although the initial setup was a bit scary, building and flashing the software were just two simple commands from the documentation.</p> <h2 id="pairing"><a class="heading-link" title="Permalink" aria-hidden="true" href="#pairing"><span>#</span></a>PAIRING</h2> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/qrcode-example-d37d9b.avif, /images/posts/migrated/qrcode-example-d37d9b.webp, /images/posts/migrated/qrcode-example-d37d9b.png" src="/images/posts/migrated/qrcode-example-d37d9b.png" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <p>Now you have a new HomeKit device and need to add it your home setup. There are different ways to achieve this, but the easiest might be using HomeKit QR Codes. You initially set the accessory password and setup id inside your code and use those to create a HomeKit compatible QR code to add the device inside the home app. A script to create those codes is also available in the main repository, you can see the result on the right.</p> <p>So if done right, the setup process should be as simple as: Open the Home app, tap add device, scan QR code. And the new device should be added to your home. As our device is not apple certified you will get a warning, but you can simply ignore it.</p> <p>You can alternatively also enter the device password manually inside the home app, as you pair the device only very few times, this is also not too much of a hassle.</p> <h2 id="usage"><a class="heading-link" title="Permalink" aria-hidden="true" href="#usage"><span>#</span></a>USAGE</h2> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/RibbaLight_Red-c3cac8.avif, /images/posts/migrated/RibbaLight_Red-c3cac8.webp, /images/posts/migrated/RibbaLight_Red-c3cac8.png" src="/images/posts/migrated/RibbaLight_Red-c3cac8.jpeg" alt="Sorry, but the light conditions make this hard to catch." loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Sorry, but the light conditions make this hard to catch.</figcaption><!--]--></figure><!----> <p>Now that the lamp is available inside the Home app you can also use HomeKit automation. In my case the lamp was meant as a corridor light switched by a motion sensor. It should actually also be possible to make your own motion sensor based on this stack, but I used a Philips Hue Motion Sensor instead. It is a really great little device, as it also monitors brightness and temperature and runs forever on 2 AAA batteries. This also requires a Hue Bridge in your environment, but you’ll need it anyway if you want to integrate any Philips Hue or IKEA TRÅDFRI device s.</p> <p>By default the “led_strip” demo project already supports switching the light on and off and setting the color, brightness and saturation, but you can add and remove “characteristics”, as they’re called, as you like.</p> <p>If we talk brightness, this is definitely not meant as the only light for a room. It is more a HomeKit mood light, which supports scenes and automation, maybe best comparable to a Philips Hue Bloom (around 60€ retail). I then measured the power usage on standby and on max brightness. So it seems the light strip draws around 8 Watts of power and the NodeMCU itself takes additional 0.8 Watts. So if the lamp is permanently connected to power, even when switched off, it will add to your power bill, but it is so minimal, that it won’t make you poor. Just to keep that in mind.</p> <h2 id="almost-done"><a class="heading-link" title="Permalink" aria-hidden="true" href="#almost-done"><span>#</span></a>Almost done</h2> <p>So everything is was working, but if the wifi password changes, we would have to change the password in code, recompile and flash again. This is not very convenient, but there also is a ready made solution to this. The example project “wifi_config” offers a process for wifi configuration similar to my coffeePID solution. So if no wifi credentials are stored the esp starts its own wifi, you can then select the wifi and enter the credentials.</p> <h2 id="pro--con-list"><a class="heading-link" title="Permalink" aria-hidden="true" href="#pro--con-list"><span>#</span></a>Pro / Con List</h2> <p>To close up this project, I want to give a retrospective in form of a pro/con-list. It shows why I like this project so much…</p> <h3 id="pros"><a class="heading-link" title="Permalink" aria-hidden="true" href="#pros"><span>#</span></a>Pros:</h3> <ul><li>native HomeKit support</li> <li>pretty cheap</li> <li>low build barrier</li> <li>no need to recompile on wifi credentials change</li></ul> <h3 id="cons"><a class="heading-link" title="Permalink" aria-hidden="true" href="#cons"><span>#</span></a>Cons:</h3> <ul><li>kind of complex development setup</li></ul><!--]-->
          ]]></content:encoded>
          <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://1024kilobyte.de///images/posts/migrated/RibbaLight_LEDStrip-888107.jpeg"/>
          <media:content xmlns:media="http://search.yahoo.com/mrss/" medium="image" url="https://1024kilobyte.de///images/posts/migrated/RibbaLight_LEDStrip-888107.jpeg"/>          
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//coffeepid-optimizations</guid>
          <title>CoffeePID: optimizations</title>
          <description>As we saw in the last post (CoffeePID: the build) the project integration was quite successful. Unfortunately the resulting temperature curve is not…</description>
          <link>https://1024kilobyte.de//coffeepid-optimizations</link>
          <pubDate>Mon, 14 Dec 2020 01:46:34 +0100</pubDate>
          <category>CoffeePID</category><category>ESP8266</category><category>PID</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//coffeepid-optimizations">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>As we saw in the last post (<a href="/coffeepid-the-build">CoffeePID: the build</a>) the project integration was quite successful. Unfortunately the resulting temperature curve is not what I was hoping for. But to be able to fix this issue, we first need to understand the problem a bit better. The target temperature was set to 97°C, so the heater stopped at that limit. So lets start taking a closer look at that first measurement:</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Gaggia_Classic_initial_heatup_CoffeePID-6f54d8.avif, /images/posts/migrated/Gaggia_Classic_initial_heatup_CoffeePID-6f54d8.webp, /images/posts/migrated/Gaggia_Classic_initial_heatup_CoffeePID-6f54d8.png" src="/images/posts/migrated/Gaggia_Classic_initial_heatup_CoffeePID-6f54d8.png" alt="Initial heatup of the Gaggia Classic with CoffeePID" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Initial heatup of the Gaggia Classic with CoffeePID</figcaption><!--]--></figure><!----> <p>So what can we take away from this?</p> <ul><li>It seems that the heater is quite powerful, as it quickly manages reheating the boiler at falling temperatures. This means it should be possible to keep the temperature pretty stable, or at least keep the “underswing” of the temperature at a minimum.</li> <li>The system is very inert. After only 2 seconds of heating the measured temperature is still increasing for more than 5 seconds. This means that we have to be more careful with (re-)heating phases and that it may be a good idea to add some delay after heating, before we take the next measurement and react on it.</li> <li>Fun fact: We can clearly see that the frequency of the oscillation decreases over time, as the heat takes longer to dissipate and therefore increasing the cool down duration. So we basically know the state of the system (all components cold or heated up) by just taking a look at that frequency.</li> <li>The last thing to state is, that the current heating strategy is crap. After reaching a “stable” system the temperature was still swinging between -2°C and +7°C around the target value.</li></ul> <p>Note: This curve is also exactly what we would expect from the static thermostat originally used. It just switches the heater at a “fixed” temperature. At this point I just digitized the original problem. Yay!</p> <h2 id="lets-fix-this"><a class="heading-link" title="Permalink" aria-hidden="true" href="#lets-fix-this"><span>#</span></a>Let’s fix this</h2> <p>At this point I already tried at least 3 different strategies to optimize the temperature regulation and I am somehow sure that I’m not done. But this was the first real promising attempt:</p> <p>Based on the measured temperature we need to calculate the duration to heat up to the target temperature. This will be a proportional factor, increasing the heating duration proportional to the temperature delta. For example heating double as long to heat up +20°C compared to +10°C. But this part is not enough, as it would never heat at or above the target temperature, but this may be a good idea on a quickly falling temperature. So we need a history of the temperature readings and calculate the incline. If the temperature is rapidly falling we need to heat longer to achieve the same heating effect as if the temperature was raising. We will later only have to find the constants needed to incorporate the formula to describe this system. So the formula simply looks like:</p> <p>t = A * ΔT + B * a</p> <p>A and B are the constants and a is the incline of the temperature curve, which is measured over the last 5 values in history. We can even simplify this by directly using the temperature delta as the incline. So the formula reads: the time to heat up to a specific temperature is equal to the sum of a constant times the temperature delta to overcome and a constant times the temperature delta of the last 5 values. It is obvious that B has to be a negative value here, as a temperature decrease (negative delta) should lead to a larger heating duration. I tested this a lot with my Gaggia Classic on my desk close to my computer and I came to these magic values:</p> <p>A = 620, B = -8600</p> <p>I’m sorry that those values most likely won’t work for you out of the box, as these are very system specific and timing critical. Even adding or removing Serial.println()-statements in the code will change the timing and therefore you have to optimize these values again. As I said, I spent some time doing this. But the values are actually not that magical, as we need a duration in milli seconds the heater should run. So A is just the time the heater runs to heat up the water 1°C. Whereas B is a more abstract factor which is important for the differential part. The factor can be substantial, e.g. when the current temperature is very close to the target temperature but rapidly falling. The first factor will then be close to zero, so the heating duration has to come from this part. So with my current values, if the temperature fell 0.25°C in the past, we would add 2150 ms to the heating duration. Reading these values the first time in written form, I have to say that it sounds really plausible. And the results were also pretty promising:</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/CoffeePID_Gaggia_Classic_optimized_heating-dfc7d4.avif, /images/posts/migrated/CoffeePID_Gaggia_Classic_optimized_heating-dfc7d4.webp, /images/posts/migrated/CoffeePID_Gaggia_Classic_optimized_heating-dfc7d4.png" src="/images/posts/migrated/CoffeePID_Gaggia_Classic_optimized_heating-dfc7d4.png" alt="Optimized heatup of CoffeePID in my Gaggia Classic" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Optimized heatup of CoffeePID in my Gaggia Classic</figcaption><!--]--></figure><!----> <p>We can still see some minor oscillation in the curve, but if you check the y scaling, you will see that the temperature oscillation is less than 1°C in total amplitude.</p> <h2 id="one-more-thing"><a class="heading-link" title="Permalink" aria-hidden="true" href="#one-more-thing"><span>#</span></a>One more thing</h2> <p>After I finished tweaking the values I was quite happy with the result. But that only lasted till the next day, when I made my first coffee. I realized that the values didn’t work as great in a cold-start scenario. The machine initially needed many heating phases to come close the target temperature although it was designed to “shoot for the target” with every heating phase. This is also plausible in retrospective, as the machine itself needs more “energy” on the first start to heat up all internal components and materials. The simple solution was to change the magic values a bit (A = 1000, B = -9000). This reduced the duration to reach a stable target temperature a lot and after around 10 minutes the system was swung in to less than ±0.3°C.</p> <h2 id="conclusion"><a class="heading-link" title="Permalink" aria-hidden="true" href="#conclusion"><span>#</span></a>Conclusion</h2> <p>After using my Gaggia Classic with CoffeePID for some time now, I’m still satisfied. The machine obviously still takes about the same time to heat up completely, but it then manages to keep the temperature very stable. Even after pulling a shot of espresso it recovers pretty quickly. From this point of view I would declare this project successfully finished. Yay!</p> <h2 id="outlook"><a class="heading-link" title="Permalink" aria-hidden="true" href="#outlook"><span>#</span></a>Outlook</h2> <p>It took me some time to write all these findings down and while I was working silently, a tweet and a <a href="https://github.com/1024kilobyte/CoffeePID/pull/1" rel="nofollow">github pull request</a> reached me. A reader of this blog, built a CoffeePID himself and made quite some additions to the source code. This is so great and it promises us many features that I wanted to add in the future, namely:</p> <ul><li>Over-The-Air updating</li> <li>a “real” PID-control</li> <li>a PID-autotuner</li></ul> <p>I am currently in the state of checking this pull request and hope to come back to you pretty soon. Thanks for reading till the end, if you did ;) Stay tuned.</p> <h3 id="other-parts-of-this-series"><a class="heading-link" title="Permalink" aria-hidden="true" href="#other-parts-of-this-series"><span>#</span></a>Other parts of this series:</h3> <ul><li><a href="/new-project-coffeepid-a-generic-pid-controller-for-coffee-machines">project idea and hardware choice</a></li> <li><a href="/coffeepid-the-software">writing the arduino code</a></li> <li><a href="/coffeepid-the-web-interface">creating the web interface</a></li> <li><a href="/coffeepid-the-build">putting it all together</a></li></ul><!--]-->
          ]]></content:encoded>
          <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://1024kilobyte.de///images/posts/migrated/image-asset-63bde5.jpg"/>
          <media:content xmlns:media="http://search.yahoo.com/mrss/" medium="image" url="https://1024kilobyte.de///images/posts/migrated/image-asset-63bde5.jpg"/>          
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//coffeepid-the-build</guid>
          <title>CoffeePID: the build</title>
          <description>Finally it’s time to put the CoffeePID to work. I have testet the software and hardware outside the Gaggia Classic and am very confident, that the…</description>
          <link>https://1024kilobyte.de//coffeepid-the-build</link>
          <pubDate>Sun, 06 Sep 2020 20:06:00 +0200</pubDate>
          <category>Arduino</category><category>ESP8266</category><category>WEMOS D1 mini pro</category><category>Gaggia Classic</category><category>PID</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//coffeepid-the-build">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>Finally it’s time to put the CoffeePID to work. I have testet the software and hardware outside the Gaggia Classic and am very confident, that the integration will not be that big of a deal. Let’s see how this turns out… But first a little service and security advice:</p> <p>Be aware, that the machine is internally working with 230V (or 110V, depending, where you are). So working on a powered machine can be very dangerous! This is why you should <strong>ALWAYS</strong> disconnect the power cable in the back <strong>BEFORE</strong> opening the machine and start fiddling with its internals.</p> <h2 id="placement"><a class="heading-link" title="Permalink" aria-hidden="true" href="#placement"><span>#</span></a>Placement</h2> <p>The first time I took a closer look inside the machine to plan the component placement I was a bit disappointed, as the first plan to simply screw the SSR to the slots on the backside the machine didn’t work out. Besides that setback it turned out that the placement should be quite easy. I wanted to place the power supply, the WEMOS and the MAX31865-breakout on a mounting plate and put that on the backside of the machine, right behind the water filling funnel. That funnel makes it a bit tricky and is why I placed the components as close to the bottom as possible. Keeping those parts close together is generally a good idea, so we can use shorter cables and reduce the mess a bit. The SSR will be placed to the left side of the machine, this way it is close to the position of the original thermostat, simplifying wiring. There is also a lot of space below the front panel, but putting the relay there would be too much of a hassle, if you need to access the ports. I fixed the SSR using double sided thermal adhesive tape, this way it can dissipate the heat via the machine body.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/IMG_2047-7f49e3.avif, /images/posts/migrated/IMG_2047-7f49e3.webp, /images/posts/migrated/IMG_2047-7f49e3.png" src="/images/posts/migrated/IMG_2047-7f49e3.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/IMG_2043-26db39.avif, /images/posts/migrated/IMG_2043-26db39.webp, /images/posts/migrated/IMG_2043-26db39.png" src="/images/posts/migrated/IMG_2043-26db39.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <h2 id="the-pt1000-element"><a class="heading-link" title="Permalink" aria-hidden="true" href="#the-pt1000-element"><span>#</span></a>The PT1000-element</h2> <p>Obviously I had to find a solution to the fact, that the thermal element is a small cylindrical element and the thermostat it replaces has a male M4 screw thread. But there is a simple fix, I just glued the PT1000-element inside a M4 spacer using thermal adhesive. That way the PT1000 is just a simple drop-in replacement to the current boiler thermostat. At least that is what I thought. Unfortunately if you only remove the top plate / water funnel, it is very difficult to screw in the PT1000. I only managed this with the help of my wife and a lot of patience. For the not so patient type it is a good idea to unscrew the boiler from the machine to put the new thermoelement in place.</p> <h2 id="wiring"><a class="heading-link" title="Permalink" aria-hidden="true" href="#wiring"><span>#</span></a>Wiring</h2> <p>As I already had a look at wiring schematics of the machine, I was confident, that the integration should be not too hard. And as it turns out the modifications are really minimal. Starting at the thermostat, it is initially wired to the main power switch at the front and to the heater (and heating light). As this is now the job of the SSR (switching the heater), I simply connected the thermostat wires to the relay. Now there is only one thing missing: the power supply for the CoffeePID. As I want the CoffeePID to switch on with the machine, I need one wire from the just connected “input-wire” of the relay, which is switched by the main switch in the front and one wire that I can easily grab from the back.</p> <h2 id="last-minute-changes"><a class="heading-link" title="Permalink" aria-hidden="true" href="#last-minute-changes"><span>#</span></a>LAST MINUTE CHANGES</h2> <p>During the integration planning I realized one possible issue. I want to be able to connect to the ESP (e.g. via USB serial monitor) after the CoffeePID has been integrated, also when the coffee machine is running. But it was planned to permanently connect the power supply to the power switch, so the ESP would be powered from the power supply and via USB, if connected. It is generally not advised to run a microcontroller board this way, as the power distribution on most boards is not designed for this mode and may damage the board permanently. To prevent this I simply added a connector to the AC side of the power supply, this way I can disconnect the power when connecting to the Wemos via USB. This is how the final build looks like and how I placed it in the Gaggia Classic.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/CoffePID_MAX31865_wiring-1eb1a1.avif, /images/posts/migrated/CoffePID_MAX31865_wiring-1eb1a1.webp, /images/posts/migrated/CoffePID_MAX31865_wiring-1eb1a1.png" src="/images/posts/migrated/CoffePID_MAX31865_wiring-1eb1a1.jpg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Adjustments-c3baa8.avif, /images/posts/migrated/Adjustments-c3baa8.webp, /images/posts/migrated/Adjustments-c3baa8.png" src="/images/posts/migrated/Adjustments-c3baa8.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Gaggia_Classic_CoffeePID_build_0-c504d5.avif, /images/posts/migrated/Gaggia_Classic_CoffeePID_build_0-c504d5.webp, /images/posts/migrated/Gaggia_Classic_CoffeePID_build_0-c504d5.png" src="/images/posts/migrated/Gaggia_Classic_CoffeePID_build_0-c504d5.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Gaggia_Classic_CoffeePID_build_1-35b681.avif, /images/posts/migrated/Gaggia_Classic_CoffeePID_build_1-35b681.webp, /images/posts/migrated/Gaggia_Classic_CoffeePID_build_1-35b681.png" src="/images/posts/migrated/Gaggia_Classic_CoffeePID_build_1-35b681.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Gaggia_Classic_CoffeePID_build_2-a9f0dd.avif, /images/posts/migrated/Gaggia_Classic_CoffeePID_build_2-a9f0dd.webp, /images/posts/migrated/Gaggia_Classic_CoffeePID_build_2-a9f0dd.png" src="/images/posts/migrated/Gaggia_Classic_CoffeePID_build_2-a9f0dd.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Gaggia_Classic_CoffeePID_build_3-d58cd0.avif, /images/posts/migrated/Gaggia_Classic_CoffeePID_build_3-d58cd0.webp, /images/posts/migrated/Gaggia_Classic_CoffeePID_build_3-d58cd0.png" src="/images/posts/migrated/Gaggia_Classic_CoffeePID_build_3-d58cd0.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <h2 id="power-on"><a class="heading-link" title="Permalink" aria-hidden="true" href="#power-on"><span>#</span></a>POWER ON</h2> <p>As noted before, the temperature regulation is currently very simple, but I wanted to see the results first before tweaking the code. So I used the serial plotter of the Arduino IDE to create a chart of the initial heat up:</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Gaggia_Classic_CoffeePID_temperature_curve-30081f.avif, /images/posts/migrated/Gaggia_Classic_CoffeePID_temperature_curve-30081f.webp, /images/posts/migrated/Gaggia_Classic_CoffeePID_temperature_curve-30081f.png" src="/images/posts/migrated/Gaggia_Classic_CoffeePID_temperature_curve-30081f.png" alt="Initial temperature curve of CoffeePID in my Gaggia Classic" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Initial temperature curve of CoffeePID in my Gaggia Classic</figcaption><!--]--></figure><!----> <p>I am actually pretty ambivalent about this first graph. But what was I expecting? On the one hand it clearly shows that the build is working as planned. Below the target temperature the ESP switches the relay and therefore the heater on. Above the target temperature the relay is switched off, letting the water cool down. Unfortunately this is a pretty inert system leading to substantial overshoot and a quite large oscillation. Regarding temperature stability this is not what I was looking for, but I already indicated, that this might not be the final implementation. And with this new task of optimizing the temperature regulation, it seems that this will not be the last post in this series.</p> <p>I hope you enjoyed the journey so far and stay with me on my way to a better coffee experience.</p> <h3 id="other-parts-of-this-series"><a class="heading-link" title="Permalink" aria-hidden="true" href="#other-parts-of-this-series"><span>#</span></a>Other parts of this series:</h3> <ul><li><a href="/new-project-coffeepid-a-generic-pid-controller-for-coffee-machines">project idea and hardware choice</a></li> <li><a href="/coffeepid-the-software">writing the arduino code</a></li> <li><a href="/coffeepid-the-web-interface">creating the web interface</a></li> <li><a href="/coffeepid-optimizations">optimizations</a></li></ul><!--]-->
          ]]></content:encoded>
          <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://1024kilobyte.de///images/posts/migrated/Gaggia_Classic_CoffeePID_build_0-71f1a9.jpeg"/>
          <media:content xmlns:media="http://search.yahoo.com/mrss/" medium="image" url="https://1024kilobyte.de///images/posts/migrated/Gaggia_Classic_CoffeePID_build_0-71f1a9.jpeg"/>          
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//coffeepid-the-web-interface</guid>
          <title>CoffeePID: the web interface</title>
          <description>This part was the most interesting one yet. I wanted to create a real simple user interface, so I can see the current temperature and if the machine…</description>
          <link>https://1024kilobyte.de//coffeepid-the-web-interface</link>
          <pubDate>Sun, 02 Aug 2020 16:26:43 +0200</pubDate>
          <category>Arduino</category><category>ESP8266</category><category>WEMOS D1 mini pro</category><category>Gaggia Classic</category><category>PID</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//coffeepid-the-web-interface">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>This part was the most interesting one yet. I wanted to create a real simple user interface, so I can see the current temperature and if the machine is currently heating. This could be done writing all from scratch, but to make this more convenient I included Bootstrap 4 and jQuery to the page. As the libraries are also needed in access point mode, I couldn’t load them from a CDN and had to serve them from the ESP. 159 KB for BS4 and 88 KB for jQuery may not sound very much, but already have a very noticeable impact on loading time. More on that later.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/IMG_2020-159f24.avif, /images/posts/migrated/IMG_2020-159f24.webp, /images/posts/migrated/IMG_2020-159f24.png" src="/images/posts/migrated/IMG_2020-159f24.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/IMG_2021-4d7e4d.avif, /images/posts/migrated/IMG_2021-4d7e4d.webp, /images/posts/migrated/IMG_2021-4d7e4d.png" src="/images/posts/migrated/IMG_2021-4d7e4d.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/IMG_2022-dd9c3a.avif, /images/posts/migrated/IMG_2022-dd9c3a.webp, /images/posts/migrated/IMG_2022-dd9c3a.png" src="/images/posts/migrated/IMG_2022-dd9c3a.jpeg" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <h2 id="the-frontend"><a class="heading-link" title="Permalink" aria-hidden="true" href="#the-frontend"><span>#</span></a>The Frontend</h2> <p>As I wanted to keep this really simple, the main page only shows the current temperature of the boiler and an indicator if it is heating. And then of course there is the settings page to set the target temperature and change the wifi settings. I also added some convenience functions for setting up the wifi connection, this way I can see the signal strength of the received wifi networks.</p> <p>After initial load of the site, the content is updated via ajax calls. Changing the settings or rebooting the ESP is also implemented via additional ajax endpoints.</p> <p>Although the website is not complex at all, there was quite a some potential to optimize it for the ESP8266. This is mainly due to the poor data transfer rate you get in real life situations, especially compared to current computers and smartphones. But if the web server and -site are implemented correctly the result can be pretty snappy. Which brings me to the next point:</p> <h2 id="optimize-optimize-optimize"><a class="heading-link" title="Permalink" aria-hidden="true" href="#optimize-optimize-optimize"><span>#</span></a>Optimize, Optimize, Optimize</h2> <p>I spent quite some time on this step, as everything should be as fast as possible, so I optimized the web server and website for minimal transfer size and maximum performance. To achieve this I</p> <ul><li>optimized html files using preload, async and defer</li> <li>reduced overall file count (reducing the number of requests) - some people might think that combining files is a bad idea, because this way the files could not be loaded in parallel. But the ESP8266 doesn’t transfer the files over WiFi in parallel; in this case it’s a good idea to reduce the total number of requests to eliminate server processing and latencies from those extra calls.</li> <li>purged all unused css from css-files (using purgecss) - in my case this reduces the size of the Bootstrap library from 159KB to 34KB 🎉</li> <li>minified all js and css files</li> <li>optimized all image files</li> <li>compressed ALL files (assuring all files are transferred gzipped to the client)</li> <li>optimized the critical code path on the server</li></ul> <p>After all optimizations I ended up at around 135KB transfer size to the client and less than 1 second of initial load time. As all files are cached on the client, all subsequent calls are much faster. Also I want to mention that my background image alone is 62 KB in (compressed) size, so the rest of the site is around 73 KB, which is pretty good.</p> <p>To make the process of editing the website and prepare it for deployment more comfortable, I added a small shell script (included in the <a href="https://github.com/1024kilobyte/CoffeePID" rel="nofollow">Github repo</a>), which mainly executes all optimization steps on the website. After executing the script, the directory is ready to be copied to the LittleFS.</p> <p>The ESP can obviously handle much more complex websites, even if the website itself doesn’t fit into the LittleFS area of the flash, you could for example use an SD card breakout to serve your site from a SD card. Also a complex site doesn’t have be slow, it depends a lot on the purpose and content. The optimization steps above can be applied to every website and are especially advised in such a resource constrained environment. But if you don’t need the site to work on access point mode and can assume that the client has an internet connection, you should offload as many resources to “the web” as you can. Fonts, CSS and JS (especially Bootstrap and jQuery) can easily be loaded via CDN and most other assets, like images, should also be loaded from a “real” web server on the internet. Just be sure to check the constraints of your final application before starting to optimize.</p> <p>But I didn’t “just” optimize the website, I also tested different web servers on the ESP for their performance. That is why I also gave “ESPAsyncWebServer” a try, which works completely asynchronous and should be able to perform a lot better than “ESP8266WebServer” under certain conditions. Unfortunately in our special scenario, it didn’t show any performance improvements at all. This is most likely due to the fact, that there will be (at least most of the time) just one client accessing the web server at a time, so it seems the async implementation can’t really show its potential here. That is why I ended up using the package “ESP8266WebServer”.</p> <h2 id="the-web-server"><a class="heading-link" title="Permalink" aria-hidden="true" href="#the-web-server"><span>#</span></a>The web server</h2> <p>Regarding the implementation of the “ESP8266WebServer” library, there are basically two ways to serve files from LittleFS. You can use sth. like:</p> <p>This is cool, as it serves all files from the LittleFS-folder “/www/”, sets the cache header and automatically serves a “.gz” version of the requested file if available. So in many cases this is all you need to host a whole website.</p> <p>But after a lot of testing I decided against the “serveStatic”-way, as it is slightly, but measurable, slower than handling the file serving yourself. This is only logical, as we can optimize the code executed on every request much better to our specific use-case. You can do this by defining handler functions (per route) or even easier, just only define a onNotFound-handler. It will then be called on every unregistered route, so for every file of the website. Our handler then loads the requested file from LittleFS, sets the correct file type and cache headers and serves the file using the “streamFile” functionality.</p> <p>As we created the website ourself we can take an opportunistic approach in the handler function, therefore we assume all files exist as gzipped version and we can limit the support of file types to the ones used in the project. This way the critical path, meaning the code on the microcontroller that gets executed on every request, can be simplified. Even sorting the file types by frequency of occurrence will have a positive performance impact (although this most likely wont have a noticeable effect).</p> <h2 id="finishing-touches"><a class="heading-link" title="Permalink" aria-hidden="true" href="#finishing-touches"><span>#</span></a>Finishing touches</h2> <p>Now we have a fast frontend to check our water temperature, but you still have to open a browser and navigate to “<a href="http://coffepid.local%E2%80%9C." rel="nofollow">http://coffepid.local“.</a> Even if you bookmarked the address, this is not really user friendly. To make this more convenient I added some meta tags to the head of the page and added an app icon, so you can use it as a web app. I don’t have any Android devices around, but on iOS you can easily create a link to a website on your home screen (this is most likely also true for Android, I just can test it). Now I can open the web interface using the app icon on my iPhone or iPad. Cool.</p> <h2 id="not-finished-at-all"><a class="heading-link" title="Permalink" aria-hidden="true" href="#not-finished-at-all"><span>#</span></a>NOT FINISHED AT ALL</h2> <p>After playing around with this once called “sweet solution” of web applications, I reached one limitation. My website was generally based on two files (with additional css and js), a homepage and a settings page. That meant navigating between the sites changes the route in the browser and therefore leaving fullscreen mode of the web application. To overcome this limitation the page had to be served from one file, to implement navigation I used hash routing. It was very interesting to implement this, what is basically a very simple SPA-framework. This is mainly done just using one html file to load all the resources and act as a template and one javascript file managing routing and AJAX calls. I won’t go into the details here, as it would go beyond the scope of this post, but you can take a look into the code over at <a href="https://github.com/1024kilobyte/CoffeePID" rel="nofollow">Github</a>.</p> <p>This solution works fine, but changing the content of the website is now pretty uncomfortable, as the html content of the site is now dynamically injected as a javascript string (from the file “content.js”). Also for such a minimal web interface using Bootstrap is definitely a bit heavy, but as my CSS skills are quite limited, this was the obvious choice. But if you are a designer / frontend developer and want to help out, feel free to reach out to me. A little help would be really appreciated.</p> <h2 id="wrap-up"><a class="heading-link" title="Permalink" aria-hidden="true" href="#wrap-up"><span>#</span></a>Wrap up</h2> <p>As the hardware setup, “firmware” and frontend are somehow finished, the next step will be the integration into the Gaggia Classic. I will share the details on the build and of course the first results on how the heat up process looks like.</p> <h3 id="other-parts-of-this-series"><a class="heading-link" title="Permalink" aria-hidden="true" href="#other-parts-of-this-series"><span>#</span></a>Other parts of this series:</h3> <ul><li><a href="/new-project-coffeepid-a-generic-pid-controller-for-coffee-machines">project idea and hardware choice</a></li> <li><a href="/coffeepid-the-software">writing the arduino code</a></li> <li><a href="/coffeepid-the-build">putting everything together</a></li> <li><a href="/coffeepid-optimizations">optimizations</a></li></ul><!--]-->
          ]]></content:encoded>
          <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://1024kilobyte.de///images/posts/migrated/IMG_2020-6fb1f8.jpg"/>
          <media:content xmlns:media="http://search.yahoo.com/mrss/" medium="image" url="https://1024kilobyte.de///images/posts/migrated/IMG_2020-6fb1f8.jpg"/>          
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//coffeepid-the-software</guid>
          <title>CoffeePID: the software</title>
          <description>After I had decided on all the hardware (see here), I was really motivated to get the software part running quickly. I love these kind of projects,…</description>
          <link>https://1024kilobyte.de//coffeepid-the-software</link>
          <pubDate>Thu, 16 Jul 2020 16:33:00 +0200</pubDate>
          <category>Arduino</category><category>ESP8266</category><category>WEMOS D1 mini pro</category><category>Gaggia Classic</category><category>PID</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//coffeepid-the-software">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>After I had decided on all the hardware (see <a href="/new-project-coffeepid-a-generic-pid-controller-for-coffee-machines">here</a>), I was really motivated to get the software part running quickly. I love these kind of projects, as I always learn a lot of new stuff.</p> <p>But first we need to know how the components are wired together and how they talk to each other. The PT1000 (2-wire) is simply connected via the terminal block to the MAX31865-board, which itself is connected to the ESP8266 using SPI. The SSR can then be controlled by almost any remaining free IO-pin on the ESP. Two little anecdotes regarding this:</p> <ul><li>Somebody taking a look at my final pinout may think: “Why didn’t he just use the <strong>S</strong>lave<strong>S</strong>elect port of the WEMOS D1 mini pro as the <strong>C</strong>hip<strong>S</strong>elect port for SPI? This way you can’t use hardware SPI and the wiring is a bit odd.” - I would reply: “Thanks for the hint, but I already tried that. And while SS was connected to CS of the MAX31865, the ESP8266 couldn’t be programmed anymore. This may be related to the fact that the flash of the WEMOS D1 mini pro is itself connected to the ESP8266 via SPI. The funny part is, that this only effected programming, the code was actually running fine using hardware SPI. To fix the programming issue, I simply switched the CS port to another free IO-pin, as I don’t really care for hardware SPI anyway.”</li> <li>Don’t use the port “D4” of the WEMOS to switch the relay. DON’T. Believe me. RealIy really! I actually initially chose that port randomly, but experienced some strange issues during uploading, when the SSR was connected to the ESP. This is simply because D4 is connected to the on-board led, which is switched on multiple times during programming. This meant the SSR was switched multiple times pretty quickly while uploading the firmware, what apparently can lead to problems. I also simply changed to another free port and finally everything is working as intended.</li></ul> <p>So the setup I ended up with for developing the software looked like this. I’ll redo the wiring later, when I know where and how I want to place the components inside the machine.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/IMG_1982-4f4e6d.avif, /images/posts/migrated/IMG_1982-4f4e6d.webp, /images/posts/migrated/IMG_1982-4f4e6d.png" src="/images/posts/migrated/IMG_1982-4f4e6d.jpeg" alt="Components of CoffeePID (PT1000 bottom left, MAX31865 top left, WEMOS D1 mini pro top right and the Solid-State-Relay bottom right)" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Components of CoffeePID (PT1000 bottom left, MAX31865 top left, WEMOS D1 mini pro top right and the Solid-State-Relay bottom right)</figcaption><!--]--></figure><!----> <h2 id="but-now-to-the-software-really-really"><a class="heading-link" title="Permalink" aria-hidden="true" href="#but-now-to-the-software-really-really"><span>#</span></a>But now to the software, really really</h2> <p>First we have to decide on the platform we want to use. I usually use the Arduino environment in most of my ESP8266-projects, as it offers a great ecosystem regarding programming and libraries and has pretty good ESP-support. So there were also libraries for every needed component in this project, like web server, pid, MAX31865, mdns and persistent storage. This will make the coding of those components very lightweight and simple, as you will see later on. Any finally it’s very easy to program the WEMOS D1 using the Arduino IDE.</p> <p>Before writing any code you need to setup the Arduino environment including all esp8266 stuff (see <a href="https://github.com/esp8266/Arduino" rel="nofollow">here</a>). Then you should be able to select your board via the tools menu. And here (in the tools menu) is another quality I like about the ESP8266: You can easily overclock it. It runs on a base clock of 80 MHz, but you can set it to 160 MHz via Tools > CPU Frequency. I run all my ESPs overclocked, for years now, and I never ran into any thermal or power issues. So from my perspective it is totally safe to permanently run these chips on 160 MHz which greatly improves performance.</p> <p>As it would go beyond the scope of this post, I will not explain every detail of the final code here, but I commented the code quite thoroughly to make it easier to understand what’s happening. The full source code is available at the <a href="https://github.com/1024kilobyte/CoffeePID" rel="nofollow">Github repository</a>, feel free to use and modify it, as you like. Also if you have any issues or questions regarding the code, just create an issue in the repository.</p> <h2 id="using-the-pt1000"><a class="heading-link" title="Permalink" aria-hidden="true" href="#using-the-pt1000"><span>#</span></a>Using the PT1000</h2> <p>Let’s start with the code right at the core functionality: measuring the temperature. As I use a breakout board similar to the Adafruit MAX31865, I can simply use the Adafruit library. And that is quite nice, as Adafruit has always great examples and tutorials (<a href="https://learn.adafruit.com/adafruit-max31865-rtd-pt100-amplifier/overview" rel="nofollow">here</a>), so the implementation was a breeze. I just had to install the “Adafruit_MAX31865“ library via the Arduino IDE and the integration of the breakout and measuring the temperature were reduced to just these few lines of code:</p> <p>Be aware, that there are two different “versions” of the MAX31865 breakout board. The PT100 and the PT1000 version, which only differ in the on-board reference resistor (PT100 -> 430 Ohm, PT1000 -> 4,3 kOhm). I actually had to switch this resistor on my board, which was a bit tricky, as it is a pretty small SMD component.</p> <h2 id="integrating-the-solid-state-relay"><a class="heading-link" title="Permalink" aria-hidden="true" href="#integrating-the-solid-state-relay"><span>#</span></a>Integrating the Solid-State-Relay</h2> <p>Next up is switching the relay. As we use a relay that can be triggered with 3,3V TTL-level we just define an output pin and initialize it with “LOW” (meaning the heating being switched off). Then later, depending on the temperature reading, we can switch it on and off as we like.</p> <p>I actually don’t know yet how I should implement this for best temperature stability / accuracy. Of course it is quite simple to implement a real software PID, but as our controlled variable (switching the SSR) is digital and not analog, I doubt that this will give us an ideal result. It is also a lot of work finding optimal PID parameters preventing a temperature overshoot and reducing the (damped) oscillation around the target value. At this point the firmware simply works as a binary switch, heating below the target temperature and switching off, when it’s reached. I’ll come back to this later, when I can test different heating strategies in real live.</p> <h2 id="wifi"><a class="heading-link" title="Permalink" aria-hidden="true" href="#wifi"><span>#</span></a>WiFi</h2> <p>Let’s continue with the next part: make it connect to the wifi and start a web server. It was important to me, that the wifi credentials are not hardcoded in any way, so you can change your wifi password without recompiling the software. There is a great project called “WifiManager” (<a href="https://github.com/tzapu/WiFiManager" rel="nofollow">Github repository</a>) that solves the problem for you by implementing a captive portal to enter the wifi credentials. But as the frontend is not very appealing and we want to optimize the process as much as possible, I created my own somehow similar implementation. This is the setup process I came up with:</p> <ol><li><p>Check for saved credentials, if there are credentials stored, the ESP will try to connect to the network.</p> <ul><li>On success: the CoffeePID is now reachable in your wifi network.</li> <li>On fail: If no credentials are found or a connect is not possible for more than 20 seconds, the controller starts in access point mode and you can connect to its network.</li></ul></li> <li><p>Now the chip is reachable via any device connected to its network.</p></li></ol> <p>The code to connect to a wifi network or create an access point is also pretty straight forward:</p> <p>The microcontroller is now reachable in the network, but I didn’t want to type the IP address into the browser, so addressing it by name would be great. This can simply be achieved by using mDNS responder (aka: Apple Bonjour) which makes devices announce their name on the network and are therefore discoverable by all other network clients. And again, there is a library for that (named “esp8266_mdns”), reducing the code to:</p> <p>Great, we now can reach the web server of the ESP8266 at the address “<a href="http://coffeepid%E2%80%9C." rel="nofollow">http://coffeepid“</a> (Depending on the network it may be necessary to append “.local” to the url).</p> <h2 id="webserver"><a class="heading-link" title="Permalink" aria-hidden="true" href="#webserver"><span>#</span></a>Webserver</h2> <p>As I will go into the details in a later post, let me just show you the basic integration. I used the library “ESP8266WebServer“ (installed via the library manager) to host the website on the microcontroller.</p> <h2 id="littlefs"><a class="heading-link" title="Permalink" aria-hidden="true" href="#littlefs"><span>#</span></a>LittleFS</h2> <p>So finally we need to have some kind of internal logic which manages the wifi credentials, being able to persistently store and retrieve them from some kind of memory. The easiest way to store data permanently on a ESP8266 is to use its integrated file system. You can choose between two different filesystems, SPIFFS is the “classic” implementation and generally very lightweight, LittleFS is a pretty new implementation and focused on real directory support and performance. I did a performance comparison and in this scenario LittleFS was much faster for serving the files. And again, using the according Arduino library you can access files with very few code. But how do you write files to the LittleFS area using the Arduino IDE, because we want to write all files needed for the website in this area. There is a plugin for the Arduino IDE which allows you to write all files from a folder named “data” in the project directory (<a href="https://github.com/earlephilhower/arduino-esp8266littlefs-plugin" rel="nofollow">GitHub repository</a>). For security reasons I separated the paths for the configuration file and the web server content, so the web server has no access to the configuration file.</p> <p>Now we can check for a valid wifi configuration and switch the network mode correspondingly. The controller starts in wifi client mode if there are saved wifi credentials or in access point mode if not. So it’s up to the enduser if you want to use the coffeePID inside your existing wifi network, or if you want it to create its own.</p> <h2 id="recap"><a class="heading-link" title="Permalink" aria-hidden="true" href="#recap"><span>#</span></a>RECAP</h2> <p>So, the ESP8266 is now connecting to the network (or creates its own), can handle local configuration files, measure the temperature and switch the relay. We now “just” need the functionality to set and show the temperature to the user. And as already mentioned, this will be done using a web frontend. But that is definitely a completely different story / post. I will report on my journey of creating and optimizing the frontend soon, so stay tuned…</p> <h3 id="other-parts-of-this-series"><a class="heading-link" title="Permalink" aria-hidden="true" href="#other-parts-of-this-series"><span>#</span></a>Other parts of this series:</h3> <ul><li><a href="/new-project-coffeepid-a-generic-pid-controller-for-coffee-machines">project idea and hardware choice</a></li> <li><a href="/coffeepid-the-web-interface">creating the web interface</a></li> <li><a href="/coffeepid-the-build">putting everything together</a></li> <li><a href="/coffeepid-optimizations">optimizations</a></li></ul><!--]-->
          ]]></content:encoded>
          <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://1024kilobyte.de///images/posts/migrated/image-asset-960400.jpg"/>
          <media:content xmlns:media="http://search.yahoo.com/mrss/" medium="image" url="https://1024kilobyte.de///images/posts/migrated/image-asset-960400.jpg"/>          
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//new-project-coffeepid-a-generic-pid-controller-for-coffee-machines</guid>
          <title>New Project: CoffeePID - a generic PID controller for coffee machines</title>
          <description>As many people: I love coffee, I would even call myself a coffee enthusiast, owning a Gaggia Classic (R9303/11 to be precise) since 2014. I’m…</description>
          <link>https://1024kilobyte.de//new-project-coffeepid-a-generic-pid-controller-for-coffee-machines</link>
          <pubDate>Wed, 25 Mar 2020 00:40:29 +0100</pubDate>
          
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//new-project-coffeepid-a-generic-pid-controller-for-coffee-machines">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Gaggia_Classic-7d8348.avif, /images/posts/migrated/Gaggia_Classic-7d8348.webp, /images/posts/migrated/Gaggia_Classic-7d8348.png" src="/images/posts/migrated/Gaggia_Classic-7d8348.jpeg" alt="Gaggia Classic / Model R9303/11" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Gaggia Classic / Model R9303/11</figcaption><!--]--></figure><!----> <p>As many people: I love coffee, I would even call myself a coffee enthusiast, owning a Gaggia Classic (R9303/11 to be precise) since 2014. I’m generally very happy with the machine, but there is one specific weak point: the stability of the water temperature, especially during the brewing process.</p> <p>This is a common issue with many machines, where the water temperature is controlled by a simple fixed temperature thermostat. That’s also the reason why you can find quite a lot DIY projects where coffee machines are upgraded with a PID controlled temperature regulation. There are even kits sold, specifically tailored for many different makes and models. All those projects work basically the same: The boiler / water temperature is measured using some kind of thermo element, a solid-state relay to control the heater and the PID controller itself. Most versions show the current and set temperature on a small seven-segment display. You can set the water temperature via buttons below the display.</p> <h2 id="motivation"><a class="heading-link" title="Permalink" aria-hidden="true" href="#motivation"><span>#</span></a>Motivation</h2> <p>Unfortunately most projects are either “invasive”, meaning the machine casing needs to be modified and / or live in small extra cases attached to the machine. Some kits are also pretty pricy, the Auber PID Kit for my machine for example is around 200$. So none of the existing solutions would work for me, as I want to preserve the machine (casing) as is, so the project is completely non-destructive and reversible. I also planned to spend a lot less money on the project compared to the ready-made kit.</p> <p>As I have some experience in the field of microcontrollers, measurement and control, etc. I thought this might actually be a nice little project. So how would I design a solution for the problem? There were the following constraints / guidelines:</p> <ul><li>no case modifications</li> <li>be completely invisible from the outside</li> <li>ease-of-use (no extra startup tasks, just switch on)</li></ul> <p>But there is actually one aspect that I will ignore at the moment. As I never use the steam function, it is currently not important to me to keep it working. But for those of you who need this, it should actually be no problem to use the steam function with CoffeePID, it’s just not on my todo list for now. It is also very likely that I will come back to this in a later post, so stay tuned. In general I want to note, that this is somehow a first stage of the project. It is very likely that I will change some hardware decisions after I received and tested the whole setup. There is also a lot of potential for other modifications, but I wanted to start with a “minimal” setup. Especially after placing the components inside the machine we will see, if there is any room left to fill. But for now let’s stick to a quite simple setting.</p> <h2 id="hardware-choice"><a class="heading-link" title="Permalink" aria-hidden="true" href="#hardware-choice"><span>#</span></a>Hardware Choice</h2> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/WEMOS_D1_mini_pro-079f17.avif, /images/posts/migrated/WEMOS_D1_mini_pro-079f17.webp, /images/posts/migrated/WEMOS_D1_mini_pro-079f17.png" src="/images/posts/migrated/WEMOS_D1_mini_pro-079f17.jpg" alt="WEMOS D1 mini pro V1.1.0" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">WEMOS D1 mini pro V1.1.0</figcaption><!--]--></figure><!----> <p>The controller part could be implemented using one of many different microcontrollers available. To keep the complexity of the project low I wanted to use sth. Arduino compatible yet powerful. In other projects the ESP8266 has always been a good friend, so it should cope with this challenge easily. I especially like the WEMOS D1 mini pro board for development and even static use, as it is very compact, can be directly programmed, has enough CPU power, flash and IO and is pretty cheap (around 3€ a piece at AliExpress). It also offers the possibility to connect an external antenna. At this moment I want to have this option as a backup, because I intend to use WiFi functionality of the ESP8266, what is very likely a very bad idea. Putting the chip inside a massive stainless steel case may impact wifi performance, that’s when an external antenna should come in handy.</p> <p>The next decision was on how to set and display the temperature: buttons and a display? But then how about the original look of the machine? I had some doubts about it, but I decided to go with a web interface served via the ESP. It is plenty quick, can act as an access point or as a wifi client, and with some love to the details it should be very easy to use.</p> <p>As of now, I already have a lot of code running, but I will come back to this later and first complete the list of hardware choices.</p> <p>Next up: measuring the temperature. This was actually pretty hard, as I wanted it to be really precise. And as I looked around I was intrigued by the idea to have a simple replacement component for the original thermostat, which has an M4 thread. And thanks to the 3D printer world there are a lot of M4 threaded type K thermocouples out there. Unfortunately they don’t really fall in the “precise” category regarding measuring accuracy. An error of at least ±2 °C is not acceptable if you aim at .1 precision for an optimal result. There are other and better thermocouple types, but they are in bad supply and not M4 thread compatible, so I started all over. If I had started with a simple google search on “measure temperature esp8266“ I would have learned very quickly that the most accurate (and also easy and cheap) solution would be a PT100 or PT1000 thermo element. So I finally came to the MAX31865 controller and a PT1000 1/3Din (class AA) element. It is very important to choose an element with small error ratings, I also recommend small (short) elements, as they are easier to integrate. In this case the best I could find was class AA (3mm x 15mm), translating into the following error-formula:</p> <p>E = 0.1 °C + 0.0017 | t | °C</p> <p>So for our preferred working range around 100 °C the maximum measurement error is 0.1 °C + 0.0017 * 100 °C = 0,27 °C. As this is the max error rating, we can generally assume that the error will be much smaller in reality, especially as we don’t use the thermo element in extreme temperature ranges (mine has a measurement range of -40 °C to 400 °C). An error of around a fourth of a degree in measuring the boiler temperature actually sounds really great to me.</p> <p>If you want some more details on the type k thermocouple vs. RTD (PT100 / PT1000) question, you should check this <a href="https://www.sterlingsensors.co.uk/rtd_vs_thermocouple" rel="nofollow">comparison</a>.</p> <p>The last two components where finally pretty easy to find. Still missing are the solid state relay to control the heating and the power supply for the microcontroller. The relay has to switch 230V AC just above 5.5 Ampere, as the heater draws a lot of power. So I needed an AC solid state relay that could be switched with an 3,3V input and have a currency rating of at least 10A (better save than sorry here). I finally chose the SSR-25DA (25A AC); yes, the 10A version may also suffice, but if you own the 110V-version of this machine, you would exceed the 10A heating current. Also it is not advised to use these “cheap” SSRs close to their rated limit. And as both versions (10A vs. 25A) are around the same price and size, the choice was simple.</p> <p>And lastly, an almost boring component, a simple 230V to 3,3V power brick, to power the electronics. In this case I went for a small Meanwell 8.2W print-type power supply (IRM-10-3.3). There are a lot of different models and I expect the actual power drain to less than 1W, so we are again clearly on the safe side.</p> <p>So finally, all parts ordered……</p> <p>And I think this is already a good introduction to the project and components I plan to use. In the next posts I will explain the other aspects of this project like the software, website and build in more detail. I hope you enjoyed reading.</p> <h2 id="ps"><a class="heading-link" title="Permalink" aria-hidden="true" href="#ps"><span>#</span></a>P.S.:</h2> <p>As mentioned there are many projects like this out there, this is just my opinionated approach. I want to give you some links to sources that are related and partially inspired me to this project:</p> <ul><li><a href="https://github.com/shmick/Espresso-PID-Controller" rel="nofollow">espresso-PID</a> (WEMOS D1 mini, type k thermocouple, SSR and OLED display)</li> <li><a href="http://protofusion.org/wordpress/2019/02/gaggia-classic-seamless-pid-upgrade/" rel="nofollow">Gaggia Classic Seamless PID Upgrade</a> (protofusion board, type k thermocouple, SSR)</li> <li><a href="http://protofusion.org/wordpress/?s=ZonCoffee&amp;submit=Go" rel="nofollow">ZonCoffee</a> (custom hardware, type k thermocouple, SSR, LCD)</li> <li><a href="http://rancilio-pid.de/bauberichte/baubericht-toppo78-einbau-in-eine-gaggia-classic-9303/" rel="nofollow">Gaggia Classic Custom PID build</a> (german)</li></ul> <h3 id="other-parts-of-this-series"><a class="heading-link" title="Permalink" aria-hidden="true" href="#other-parts-of-this-series"><span>#</span></a>OTHER PARTS OF THIS SERIES:</h3> <ul><li><a href="/coffeepid-the-software">writing the arduino code</a></li> <li><a href="/coffeepid-the-web-interface">creating the web interface</a></li> <li><a href="/coffeepid-the-build">putting everything together</a></li> <li><a href="/coffeepid-optimizations">optimizations</a></li></ul><!--]-->
          ]]></content:encoded>
          <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://1024kilobyte.de///images/posts/migrated/Gaggia_Classic-397706.jpeg"/>
          <media:content xmlns:media="http://search.yahoo.com/mrss/" medium="image" url="https://1024kilobyte.de///images/posts/migrated/Gaggia_Classic-397706.jpeg"/>          
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//better-location-sharing-with-shareloc</guid>
          <title>Better Location Sharing with Shareloc</title>
          <description>It's finally been done and I'm very happy to see Shareloc in the iOS App Store. The first prototype was created almost a year ago, the backend api has been rewr</description>
          <link>https://1024kilobyte.de//better-location-sharing-with-shareloc</link>
          <pubDate>Tue, 27 Jun 2017 12:44:00 +0200</pubDate>
          <category>location sharing</category><category>shareloc</category><category>Allgemein</category><category>Software</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//better-location-sharing-with-shareloc">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>It’s finally been done and I’m very happy to see Shareloc in the <a href="https://appsto.re/us/WMHakb.i" rel="nofollow">iOS App Store</a>. The first prototype was created almost a year ago, the backend api has been rewritten 3 times and the app already shows its second redesinged UI. Overall I’m very satisfied with the current state and looking forward to push the app further.</p> <h2 id="why-yet-another-location-sharing-app"><a class="heading-link" title="Permalink" aria-hidden="true" href="#why-yet-another-location-sharing-app"><span>#</span></a>Why yet another location sharing app?</h2> <p>Good question and I heard it a lot telling people about Shareloc. So I want to explain you how it all came together. In the beginning there was a simple need. I just wanted to share my location with my wife and my friends. And in this case there are not only alreadx exisiting some apps for that, it’s an iOS integrated feature. So I used it and I was disappointed because it didn’t work as expected. Then I tried other location sharing services on iOS including glympse, facebook and google maps. But none did satisfy all of my requirements and those are not that special in my opinion. Basically there are 3 obvious features that I want:</p> <ul><li>Ease of use: It has to be really easy and fast to use. If it takes me more than 5 seconds to share my location, I probably won’t use the app.</li> <li>Accuracy: The shared location should be as accurate as possible. If the person I shared my location with only knows my position up to 100 meters accurate, then location sharing looses some use cases for me.</li> <li>Speed: The update interval for my location should not be limited. If there is a new location available, it should be available to my shared contact immediately.</li></ul> <p>And then there is a fourth and implied feature: security! It’s not last because it’s least important, it’s here because the user can’t see it. You maybe can check if the data transfer is encrypted, but you don’t know how long the location data are stored and with whom they are shared.</p> <p>As all services I tested did at least fail two of those features I created a small prototype testing if the GPS on the iPhone was accurate enough to fulfill my needs. As it turns out: Yes! And now here I’m presenting Shareloc the location sharing service living up to my and hopefully also your needs and expectations.</p> <h2 id="how-are-you-making-money-with-a-free-app"><a class="heading-link" title="Permalink" aria-hidden="true" href="#how-are-you-making-money-with-a-free-app"><span>#</span></a>How are you making money with a free app?</h2> <p>In the current state: I’m not and that is fine for me. I’m a <a href="https://megabyte.solutions" rel="nofollow">freelance software developer</a> and Shareloc is a some kind of work sample for people who may want to hire me. Also the operating costs of Shareloc are pretty low, so don’t expect the app to loose it’s current “free”-pricetag.</p> <p>That doesn’t mean there are no plans to monetize the app at all. In the future it might be possible to buy some extra features via an in-app-purchase or make little donations, but that are still dreams of the future.</p> <h2 id="whats-next"><a class="heading-link" title="Permalink" aria-hidden="true" href="#whats-next"><span>#</span></a>What’s next?</h2> <p>Shareloc is working great as it is, but it is still an 1.0, and the plans for 1.x- and 2.x-versions have been around since the beginning. During the developement and beta testing I created a backlog of ideas and feature requests that I’m now prioritizing. Also if you have a feature request please use the <a href="https://shareloc.io/contact/" rel="nofollow">contact form on the Shareloc website</a> to share it with me.</p> <p>Also if you like Shareloc and would like to see new features before everybody else, just let me know and I will add you as a beta tester.</p><!--]-->
          ]]></content:encoded>
          <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://1024kilobyte.de///images/posts/App_Icon_Default@1x.png"/>
          <media:content xmlns:media="http://search.yahoo.com/mrss/" medium="image" url="https://1024kilobyte.de///images/posts/App_Icon_Default@1x.png"/>          
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//meine-erste-eigene-iphone-location-sharing-app</guid>
          <title>Meine erste eigene iPhone (Location Sharing) App</title>
          <description>Der Vorsatz eine eigene App im Apple Store zu haben besteht schon lange, doch irgendwie hat es bis jetzt nie geklappt. Und das obwohl ich schon 6 Jahre für die</description>
          <link>https://1024kilobyte.de//meine-erste-eigene-iphone-location-sharing-app</link>
          <pubDate>Wed, 18 Jan 2017 14:08:55 +0100</pubDate>
          <category>Allgemein</category><category>Programmieren</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//meine-erste-eigene-iphone-location-sharing-app">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>Der Vorsatz eine eigene App im Apple Store zu haben besteht schon lange, doch irgendwie hat es bis jetzt nie geklappt. Und das obwohl ich schon 6 Jahre für die iOS Plattform programmiere. Nachdem ich inzwischen sogar selbständiger iOS Entwickler bin, konnte ich diesen Umstand so nicht belassen. Es blieb die Frage was die eigene App eigentlich machen bzw. welches Problem sie für mich, aber vor allem die Nutzer, lösen soll. Die kurze Antwort darauf ist: Location Sharing. Der primäre Grund dafür ist, dass ich mit den vorhandenen Lösungen unzufrieden war. Entweder waren diese zu umständlich, die Nutzeroberfläche nicht durchdacht, der Dienst zu langsam, ungenau oder unzuverlässig. Damit waren auch schon die Ziele der neuen App definiert: Einfach, Schnell, Präzise, Zuverlässig und da es hier um Positionsdaten von Nutzern geht, mit höchster Priorität auch Sicherheit.</p> <h3 id="was-ist-dieses-location-sharing-und-wozu-brauche-ich-es"><a class="heading-link" title="Permalink" aria-hidden="true" href="#was-ist-dieses-location-sharing-und-wozu-brauche-ich-es"><span>#</span></a>Was ist dieses “Location Sharing” und wozu brauche ich es?</h3> <p>Mit dem Begriff “Location Sharing” wird die Möglichkeit beschrieben, den eigenen Standort mit einer anderen Person zu teilen. In der Regel wird dieser Person einfach eine Nachricht mit einem Link zu einer Webseite gesendet, über die der eigene Standort auf einer Karte angezeigt werden kann. Der Andere weiß somit wann ich mich wo befinde. Doch wozu nutze ich diese Funktion?</p> <ul><li>Jemanden wissen zu lassen, dass ich mich gerade auf dem Weg befinde. Egal ob der ich von der Arbeit nach Hause fahre, oder mich mit Freunden treffe.</li> <li>Treffen ohne genauen Treff- und Zeitpunkt, wie zum Beispiel beim Shopping, in einem Park oder einem Konzert.</li> <li>Den richtigen Weg finden. Wenn man in einer unbekannten Umgebung nicht weiter weiß, kann man einfach den Standort teilen und sich von einem Bekannten den Weg erklären lassen.</li></ul> <p>Es gibt aber viele weitere Situationen, in denen ich inzwischen gerne meinen Standort teile.</p> <h3 id="aktueller-stand"><a class="heading-link" title="Permalink" aria-hidden="true" href="#aktueller-stand"><span>#</span></a>Aktueller Stand</h3> <p>Seit der Idee und Konzeption ist nun schon einige Zeit vergangen. Da es sich bei einem solchen Dienst nicht “einfach” nur um eine App handelt, sondern auch um einen Webservice, eine Webseite und ein paar weitere Dinge, war auch wirklich viel zu tun. Das meiste ist nun aber geschafft und ein Ende der Entwicklung in Sicht. Mit dem Ergebnis bin ich bisher sehr zufrieden, seit wenigen Monaten läuft bereits ein geschlossener Betatest des Dienstes. Entsprechend aktueller Planung wird die App im ersten Halbjahr 2017 im App Store veröffentlicht. Mehr möchte ich jetzt hier aber noch nicht verraten.</p> <h3 id="betatester-gesucht"><a class="heading-link" title="Permalink" aria-hidden="true" href="#betatester-gesucht"><span>#</span></a><del>Betatester gesucht</del></h3> <p><del>Um die Zuverlässig- und Skalierbarkeit der App besser beurteilen zu können, benötige ich aktuell noch ein paar Betatester. Wer andere Dienste, wie das in iOS integrierte Teilen des Standortes oder Apps wie Glympse regelmäßig nutzt und daran interessiert ist meine App zu Testen, kann sich über das folgende Formular zum Betatest anmelden. Wichtig ist, dass die App nur für das iPhone gemacht ist, Nutzer eines iPads oder anderer Smartphones können diese somit aktuell nicht installieren. Nach erfolgreicher Anmeldung sende ich Dir eine TestFlight Einladung zur App, diese enthält eine Anleitung wie Du die App auf Deinem iPhone installieren kannst. Zusätzlich erhältst Du eine Einladung zu einem Slack Channel, den ich für die Kommunikation mit den Testern nutze. Dort kann man mich am einfachsten direkt erreichen, ob mit Fragen, Ideen oder Problemen.</del></p><!--]-->
          ]]></content:encoded>
          
                    
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//custom-compass-position-in-mkmapview</guid>
          <title>Custom compass position in MKMapView</title>
          <description>Although the second option sounded tempting, the first one seemed to be the obvious choice. But then I learned that the MKMapView doesn't let you do anything wi</description>
          <link>https://1024kilobyte.de//custom-compass-position-in-mkmapview</link>
          <pubDate>Tue, 30 Aug 2016 16:07:33 +0200</pubDate>
          <category>Compass</category><category>iOS</category><category>MapKit</category><category>MKMapView</category><category>Programmieren</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//custom-compass-position-in-mkmapview">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>For map views in native iOS apps MKMapView is my first choice. While implementing an map based app I came to the point where the compass was showing under a menu component. This is because the default compass position is in the top right corner of the map view. So there were two solutions to the problem:</p> <ol><li>Change the position of the compass</li> <li>Remove the compass and implement a custom one</li></ol> <p>Although the second option sounded tempting, the first one seemed to be the obvious choice. But then I learned that the MKMapView doesn’t let you do anything with its compass besides the possibility to show or hide it via the ‘showCompass’ function. I was surprised by this fact, because on the iOS default map app you can see the compass on a custom position.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/apple_maps_custom_compass_position-626174.avif, /images/posts/migrated/apple_maps_custom_compass_position-626174.webp, /images/posts/migrated/apple_maps_custom_compass_position-626174.png" src="/images/posts/migrated/apple_maps_custom_compass_position-626174.png" alt="Custom compass position in apple maps app" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Custom compass position in apple maps app</figcaption><!--]--></figure><!----> <h2 id="solving-the-puzzle"><a class="heading-link" title="Permalink" aria-hidden="true" href="#solving-the-puzzle"><span>#</span></a>Solving the puzzle</h2> <p>By analyzing the issue I saw the compass is a subview of the map with a custom class called ‘MKCompassView’. As the class itself is private API it is not exposed for external use, but I don’t need that. To set a new position for the compass I subclassed the MKMapView component and overrode the layoutSubviews function to change the frame of the MKCompassView.</p> <p>As you can see this is really easy. Then I just had tell the app to use the map view subclass as the map component and my problem was solved.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/custom_compass_position-1865dc.avif, /images/posts/migrated/custom_compass_position-1865dc.webp, /images/posts/migrated/custom_compass_position-1865dc.png" src="/images/posts/migrated/custom_compass_position-1865dc.png" alt="App showing the compass at a custom position on a MKMapView" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">App showing the compass at a custom position on a MKMapView</figcaption><!--]--></figure><!----> <h2 id="make-it-useful"><a class="heading-link" title="Permalink" aria-hidden="true" href="#make-it-useful"><span>#</span></a>Make it useful</h2> <p>Then I realized that a static position isn’t good at all in my case, you will see the reason later. So I made the position variable and added a simple animation. The final code looks like this:</p> <p>Inside the app it looks like this:</p> <figure class="svelte-14w3c6y"><!--[0--><img src="/images/posts/migrated/animated_custom_compass_position-119efb.gif" alt="Animated custom compass position on a MKMapView" loading="lazy" decoding="async" class="svelte-14w3c6y"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Animated custom compass position on a MKMapView</figcaption><!--]--></figure><!----><!--]-->
          ]]></content:encoded>
          
                    
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//awake-os-x-wake-on-lan-dashboard-widget</guid>
          <title>Awake - An OS X Wake-on-Lan Dashboard Widget</title>
          <description>Looking for an easy-to-use OS X Wake-on-Lan Dashboard Widget? Then you are right here. But first things first.</description>
          <link>https://1024kilobyte.de//awake-os-x-wake-on-lan-dashboard-widget</link>
          <pubDate>Tue, 29 Sep 2015 23:46:04 +0200</pubDate>
          <category>Software</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//awake-os-x-wake-on-lan-dashboard-widget">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>Looking for an easy-to-use OS X Wake-on-Lan Dashboard Widget? Then you are right here. But first things first.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/awake_widget-315117.avif, /images/posts/migrated/awake_widget-315117.webp, /images/posts/migrated/awake_widget-315117.png" src="/images/posts/migrated/awake_widget-315117.png" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <h3 id="what-is-wake-on-lan"><a class="heading-link" title="Permalink" aria-hidden="true" href="#what-is-wake-on-lan"><span>#</span></a>What is Wake-on-Lan</h3> <p>Wake-on-Lan is a feature many network enabled devices implement to switch on the device via the network. If Wake-on-Lan is enabled on the device you just have to send a so called “magic packet” to the device and it turns on. All you need to know about the device is its MAC address, a globally unique id, every network device has. This is especially helpful if you don’t have physical access to the device like a server in a datacenter. As there is no real magic in the creation of the magic packet there are lots of applications out there supporting Wake-on-Lan.</p> <h3 id="why-using-a-dashboard-widget"><a class="heading-link" title="Permalink" aria-hidden="true" href="#why-using-a-dashboard-widget"><span>#</span></a>Why using a dashboard widget</h3> <p>Even though it seems that the dashboard will die in OS X in the not so far future, there are still some cases where I love to have it. One of these cases is Wake-on-Lan to start up my NAS server whenever I need it. I wanted a tool that doesn’t waste any menubar space and just does its job fast and easy.</p> <h3 id="do-it-yourself"><a class="heading-link" title="Permalink" aria-hidden="true" href="#do-it-yourself"><span>#</span></a>Do it yourself</h3> <p>As I couldn’t find an existing Wake-on-Lan dashboard widget I decided to make one myself. Long story short, here is “Awake”:</p> <p><strong>Awake - An OS X Wake-on-Lan Dashboard Widget</strong><br/> Version: 0.1<br/> Release Date: 29.09.2015</p> <h3 id="open-up"><a class="heading-link" title="Permalink" aria-hidden="true" href="#open-up"><span>#</span></a>Open up</h3> <p>To make the project available for everybody I released it under the MIT-Zero License. So feel free to use is it in any way you like and check out the <a href="https://github.com/1024kilobyte/osx-dashboard-wol-widget" rel="nofollow">github project repository</a>.</p><!--]-->
          ]]></content:encoded>
          
                    
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//macos-mail-google-konto-anmeldeversuch-blockiert</guid>
          <title>MacOS Mail: "Google-Konto: Anmeldeversuch blockiert"</title>
          <description>Wer die Meldung "Google-Konto: Anmeldeversuch blockiert" schon einmal bekommen hat, wird sich fragen woran das liegt. In meinem Fall wurde der E-Mail-Abruf auf</description>
          <link>https://1024kilobyte.de//macos-mail-google-konto-anmeldeversuch-blockiert</link>
          <pubDate>Mon, 02 Feb 2015 23:06:48 +0100</pubDate>
          <category>Allgemein</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//macos-mail-google-konto-anmeldeversuch-blockiert">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>Wer die Meldung “Google-Konto: Anmeldeversuch blockiert” schon einmal bekommen hat, wird sich fragen woran das liegt. In meinem Fall wurde der E-Mail-Abruf auf meinem MacBook Pro in Mail verhindert und ich habe folgende Meldung erhalten.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/MacOS_Mail_Google_Konto_gesperrt-c99fd9.avif, /images/posts/migrated/MacOS_Mail_Google_Konto_gesperrt-c99fd9.webp, /images/posts/migrated/MacOS_Mail_Google_Konto_gesperrt-c99fd9.png" src="/images/posts/migrated/MacOS_Mail_Google_Konto_gesperrt-c99fd9.png" alt="Anmeldeversuch gesperrt – Google-Konto in MacOS Mail gesperrt" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Anmeldeversuch gesperrt – Google-Konto in MacOS Mail gesperrt</figcaption><!--]--></figure><!----> <h2 id="ursache"><a class="heading-link" title="Permalink" aria-hidden="true" href="#ursache"><span>#</span></a>Ursache</h2> <p>Wenn man sich durch den Prozess klickt, erfährt man auch aus welchem Grund Google den Account gesperrt hat.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Anmeldeversuch_verhindert-213fc3.avif, /images/posts/migrated/Anmeldeversuch_verhindert-213fc3.webp, /images/posts/migrated/Anmeldeversuch_verhindert-213fc3.png" src="/images/posts/migrated/Anmeldeversuch_verhindert-213fc3.png" alt="Ursache der Account-Sperrung" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Ursache der Account-Sperrung</figcaption><!--]--></figure><!----> <p>In meinem Fall handelt es sich hier aber nicht um ein ungewöhnliches Ereignis. Die Ursache war ein regulärer E-Mail-Abruf. Das besonders ärgerliche an dieser Meldung ist, dass das Fenster nicht geschlossen werden kann. Es ist modal und auch nach Abschluss der Sicherheitsüberprüfung ist die “Fertig”-Schaltfläche nicht aktiviert. Man muss das Mail-Programm also entweder über die Aktivitätsanzeige oder schneller durch Drücken der Alt-Taste und Rechtsklick auf das Mail-Symbol “Sofort Beenden”. Wenn man die Sicherheitsüberprüfung des Google-Kontos erfolgreich durchgeführt hat, kann nach einem Neustart von Mail das Google-Konto wieder online geschaltet werden und der E-Mail-Abruf funktioniert wieder.</p> <h2 id="die-quelle-allen-übels"><a class="heading-link" title="Permalink" aria-hidden="true" href="#die-quelle-allen-übels"><span>#</span></a>Die Quelle allen Übels</h2> <p>Doch was war jetzt eigentlich die Ursache für diese in meinem Fall sporadische Meldung? “Power Nap!” Seit MacOS 10.8 gibt es diese Funktion, die es dem Rechner ermöglicht auch im Schlafmodus verschiedene Dienste auszuführen und hierzu gehört auch das Abrufen von E-Mails. “Power Nap” lässt sich in den Systemeinstellungen unter “Energie sparen” aktivieren. Welche Betriebssysteme und Geräte “Power Nap” unterstützen und weitere Informationen findet man unter folgendem Link bei Apple: <a href="http://support.apple.com/de-de/HT5394" rel="nofollow">Informationen zu Power Nap</a>.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Energie_sparen_Power_Nap-59efd9.avif, /images/posts/migrated/Energie_sparen_Power_Nap-59efd9.webp, /images/posts/migrated/Energie_sparen_Power_Nap-59efd9.png" src="/images/posts/migrated/Energie_sparen_Power_Nap-59efd9.png" alt="Systemeinstellungen &amp;gt; Energie sparen" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Systemeinstellungen &amp;gt; Energie sparen</figcaption><!--]--></figure><!----> <p>Ich konnte bei mir nachvollziehen, dass der Google-Account immer gesperrt wurde, wenn die Mails im Ruhezustand meines MacBook Pros abgerufen wurden. Doch auch hier stellt sich die Frage, warum das passiert. Meine Erklärung hierfür ist, dass der während des Ruhezustands genutzte Systemdienst sich beim Abrufen von Mails anders verhält als das Programm Mail selbst und so die Kontosperrung verursacht.</p> <h2 id="lösung"><a class="heading-link" title="Permalink" aria-hidden="true" href="#lösung"><span>#</span></a>Lösung</h2> <p>Was kann man also machen um das Sperren des Google-Kontos zu verhindern? Hierfür gibt es entweder die naheliegende Möglichkeit die Funktion “Power Nap” über die Systemeinstellungen zu deaktivieren, was ich getan habe, oder man aktiviert im Google-Konto den Zugriff durch weniger sichere Apps (<a href="https://www.google.com/settings/security/lesssecureapps" rel="nofollow">Link zur dieser Einstellung</a>).</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Google-weniger-sichere-Apps-aktivieren-e798c3.avif, /images/posts/migrated/Google-weniger-sichere-Apps-aktivieren-e798c3.webp, /images/posts/migrated/Google-weniger-sichere-Apps-aktivieren-e798c3.png" src="/images/posts/migrated/Google-weniger-sichere-Apps-aktivieren-e798c3.png" alt="Google-Account: Sicherheitseinstellungen bezüglich weniger sicherer Apps" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Google-Account: Sicherheitseinstellungen bezüglich weniger sicherer Apps</figcaption><!--]--></figure><!----> <p>Das ist allerdings nur möglich, wenn man für seinen Google-Account nicht die Zwei-Faktor-Authentifizierung aktiviert hat.</p><!--]-->
          ]]></content:encoded>
          
                    
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//e-mail-bilder-mit-ocr-auslesen</guid>
          <title>E-Mail-Bilder mit OCR auslesen - Harvester lernen lesen</title>
          <description>Die Impressumspflicht zwingt quasi jeden Betreiber einer Webseite ein Impressum einzubinden. Hier müssen einige persönliche Informationen angegeben werden, was</description>
          <link>https://1024kilobyte.de//e-mail-bilder-mit-ocr-auslesen</link>
          <pubDate>Sat, 01 Nov 2014 18:13:00 +0100</pubDate>
          <category>E-Mail-Bilder</category><category>OCR</category><category>Programmieren</category><category>Sicherheit</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//e-mail-bilder-mit-ocr-auslesen">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>Die Impressumspflicht zwingt quasi jeden Betreiber einer Webseite ein Impressum einzubinden. Hier müssen einige persönliche Informationen angegeben werden, was im Internet auch schnell mal missbraucht wird. So ist schon lange bekannt, dass sogenannte Harvester durch einfaches parsen von Webseiten unter anderem E-Mail-Adressen sammeln, die dann für den Spamversand genutzt werden.
Aus diesem Grund wurden schon viele Methoden entwickelt, um das einfache Auslesen der E-Mail-Adresse zu verhindern. Einige basieren auf der Manipulation der Anzeige über CSS oder Javascript, da die Harvester, zumindest bis heute, nur den html-Quelltext der Webseiten durchsuchen ohne Javascript und CSS zu interpretieren. Einen interessantes Projekt in diesem Zusammenhang findet man hier: <a href="http://www.drweb.de/magazin/wirklich-wirksamer-schutz-fr-e-mail-adressen/" title="Wirklich wirksamer Schutz für E-Mail-Adressen" rel="nofollow">http://www.drweb.de/magazin/wirklich-wirksamer-schutz-fr-e-mail-adressen/</a></p> <p>Doch in diesem Artikel möchte ich mich mit einem anderen Schutzmechanismus beschäftigen. Mit der Verwendung von Bildern, auf denen die E-Mail-Adresse geschrieben steht, sollen Harvester ausgetrickst werden, da sie den Text nicht lesen können. Oder etwa doch?</p> <p>Da ich in mehreren Projekten bereits mit OCR (Abkürzung für: Optical Charater Recognition) in Berührung gekommen bin, also dem computergestützten Erkennen von Schrift in Bilddateien, stellte ich mir die Frage wie sicher diese E-Mail-Bilder wirklich sind. Einem entsprechend programmierten Harvester sollte es doch möglich sein den Text auf den Bilddateien zu erkennen und somit die E-Mail-Adresse zu ermitteln.</p> <p>Aus der Neugier heraus, ob das wirklich funktioniert, habe ich hierfür ein Testprogramm als Proof-Of-Concept geschrieben. Die Basis für die Erkennung sind 30 selbst erstellte E-Mail-Bilder, die ich mittels verschiedener Onlinedienste erzeugt habe. Um ein möglichst realistätsnahes Ergebnis zu erhalten, habe ich außerdem mehrere übliche Variationen beim Erzeugen angewendet.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_1_0-b39cce.avif, /images/posts/migrated/service_1_0-b39cce.webp, /images/posts/migrated/service_1_0-b39cce.png" src="/images/posts/migrated/service_1_0-b39cce.png" alt="service_1_0" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_1_0</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_1_1-cd95d3.avif, /images/posts/migrated/service_1_1-cd95d3.webp, /images/posts/migrated/service_1_1-cd95d3.png" src="/images/posts/migrated/service_1_1-cd95d3.png" alt="service_1_1" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_1_1</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_1_2-ac2b22.avif, /images/posts/migrated/service_1_2-ac2b22.webp, /images/posts/migrated/service_1_2-ac2b22.png" src="/images/posts/migrated/service_1_2-ac2b22.png" alt="service_1_2" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_1_2</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_2_0_0-a5bdb0.avif, /images/posts/migrated/service_2_0_0-a5bdb0.webp, /images/posts/migrated/service_2_0_0-a5bdb0.png" src="/images/posts/migrated/service_2_0_0-a5bdb0.png" alt="service_2_0_0" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_2_0_0</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_2_0_1-f9cee1.avif, /images/posts/migrated/service_2_0_1-f9cee1.webp, /images/posts/migrated/service_2_0_1-f9cee1.png" src="/images/posts/migrated/service_2_0_1-f9cee1.png" alt="service_2_0_1" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_2_0_1</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_2_0_2-d96b57.avif, /images/posts/migrated/service_2_0_2-d96b57.webp, /images/posts/migrated/service_2_0_2-d96b57.png" src="/images/posts/migrated/service_2_0_2-d96b57.png" alt="service_2_0_2" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_2_0_2</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_2_1_0-b47089.avif, /images/posts/migrated/service_2_1_0-b47089.webp, /images/posts/migrated/service_2_1_0-b47089.png" src="/images/posts/migrated/service_2_1_0-b47089.png" alt="service_2_1_0" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_2_1_0</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_2_1_1-821e51.avif, /images/posts/migrated/service_2_1_1-821e51.webp, /images/posts/migrated/service_2_1_1-821e51.png" src="/images/posts/migrated/service_2_1_1-821e51.png" alt="service_2_1_1" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_2_1_1</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_2_1_2-052d31.avif, /images/posts/migrated/service_2_1_2-052d31.webp, /images/posts/migrated/service_2_1_2-052d31.png" src="/images/posts/migrated/service_2_1_2-052d31.png" alt="service_2_1_2" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_2_1_2</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_2_2_0-c4122d.avif, /images/posts/migrated/service_2_2_0-c4122d.webp, /images/posts/migrated/service_2_2_0-c4122d.png" src="/images/posts/migrated/service_2_2_0-c4122d.png" alt="service_2_2_0" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_2_2_0</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_2_2_1-d38c78.avif, /images/posts/migrated/service_2_2_1-d38c78.webp, /images/posts/migrated/service_2_2_1-d38c78.png" src="/images/posts/migrated/service_2_2_1-d38c78.png" alt="service_2_2_1" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_2_2_1</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_2_2_2-f787f4.avif, /images/posts/migrated/service_2_2_2-f787f4.webp, /images/posts/migrated/service_2_2_2-f787f4.png" src="/images/posts/migrated/service_2_2_2-f787f4.png" alt="service_2_2_2" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_2_2_2</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_3_0-d7e3b1.avif, /images/posts/migrated/service_3_0-d7e3b1.webp, /images/posts/migrated/service_3_0-d7e3b1.png" src="/images/posts/migrated/service_3_0-d7e3b1.png" alt="service_3_0" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_3_0</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_3_1-1eb3f4.avif, /images/posts/migrated/service_3_1-1eb3f4.webp, /images/posts/migrated/service_3_1-1eb3f4.png" src="/images/posts/migrated/service_3_1-1eb3f4.png" alt="service_3_1" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_3_1</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_3_2-e69cb2.avif, /images/posts/migrated/service_3_2-e69cb2.webp, /images/posts/migrated/service_3_2-e69cb2.png" src="/images/posts/migrated/service_3_2-e69cb2.png" alt="service_3_2" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_3_2</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_4_0-912c8a.avif, /images/posts/migrated/service_4_0-912c8a.webp, /images/posts/migrated/service_4_0-912c8a.png" src="/images/posts/migrated/service_4_0-912c8a.png" alt="service_4_0" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_4_0</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_4_1-3d4a8b.avif, /images/posts/migrated/service_4_1-3d4a8b.webp, /images/posts/migrated/service_4_1-3d4a8b.png" src="/images/posts/migrated/service_4_1-3d4a8b.png" alt="service_4_1" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_4_1</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_4_2-1f5239.avif, /images/posts/migrated/service_4_2-1f5239.webp, /images/posts/migrated/service_4_2-1f5239.png" src="/images/posts/migrated/service_4_2-1f5239.png" alt="service_4_2" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_4_2</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_5_0-09ec33.avif, /images/posts/migrated/service_5_0-09ec33.webp, /images/posts/migrated/service_5_0-09ec33.png" src="/images/posts/migrated/service_5_0-09ec33.png" alt="service_5_0" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_5_0</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_5_1-4223ac.avif, /images/posts/migrated/service_5_1-4223ac.webp, /images/posts/migrated/service_5_1-4223ac.png" src="/images/posts/migrated/service_5_1-4223ac.png" alt="service_5_1" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_5_1</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_5_2-eb316d.avif, /images/posts/migrated/service_5_2-eb316d.webp, /images/posts/migrated/service_5_2-eb316d.png" src="/images/posts/migrated/service_5_2-eb316d.png" alt="service_5_2" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_5_2</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_6_0_0-79099f.avif, /images/posts/migrated/service_6_0_0-79099f.webp, /images/posts/migrated/service_6_0_0-79099f.png" src="/images/posts/migrated/service_6_0_0-79099f.png" alt="service_6_0_0" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_6_0_0</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_6_0_1-451b52.avif, /images/posts/migrated/service_6_0_1-451b52.webp, /images/posts/migrated/service_6_0_1-451b52.png" src="/images/posts/migrated/service_6_0_1-451b52.png" alt="service_6_0_1" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_6_0_1</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_6_0_2-cda388.avif, /images/posts/migrated/service_6_0_2-cda388.webp, /images/posts/migrated/service_6_0_2-cda388.png" src="/images/posts/migrated/service_6_0_2-cda388.png" alt="service_6_0_2" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_6_0_2</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_6_1_0-34c67a.avif, /images/posts/migrated/service_6_1_0-34c67a.webp, /images/posts/migrated/service_6_1_0-34c67a.png" src="/images/posts/migrated/service_6_1_0-34c67a.png" alt="service_6_1_0" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_6_1_0</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_6_1_1-4b9b22.avif, /images/posts/migrated/service_6_1_1-4b9b22.webp, /images/posts/migrated/service_6_1_1-4b9b22.png" src="/images/posts/migrated/service_6_1_1-4b9b22.png" alt="service_6_1_1" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_6_1_1</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_6_1_2-0e9729.avif, /images/posts/migrated/service_6_1_2-0e9729.webp, /images/posts/migrated/service_6_1_2-0e9729.png" src="/images/posts/migrated/service_6_1_2-0e9729.png" alt="service_6_1_2" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_6_1_2</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_7_0-dd8dc7.avif, /images/posts/migrated/service_7_0-dd8dc7.webp, /images/posts/migrated/service_7_0-dd8dc7.png" src="/images/posts/migrated/service_7_0-dd8dc7.png" alt="service_7_0" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_7_0</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_7_1-e54fa7.avif, /images/posts/migrated/service_7_1-e54fa7.webp, /images/posts/migrated/service_7_1-e54fa7.png" src="/images/posts/migrated/service_7_1-e54fa7.png" alt="service_7_1" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_7_1</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/service_7_2-560e83.avif, /images/posts/migrated/service_7_2-560e83.webp, /images/posts/migrated/service_7_2-560e83.png" src="/images/posts/migrated/service_7_2-560e83.png" alt="service_7_2" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">service_7_2</figcaption><!--]--></figure><!----> <p>Der Programmablauf ist dabei wie folgt:</p> <ol><li>Optimieren der Bilder für die OCR - Da eine OCR meist für die Erkennung von Text auf gescannten Dokumenten optimiert ist, hat diese bestimmte Anforderungen an das Quellmaterial. Die hier verwendeten E-Mail-Bilder wurden deshalb vor der Texterkennung optimiert, um bessere Ergebnisse zu erhalten. Nach vielen Versuchen bin ich bei folgendem Algorithmus angekommen: Bild in Graustufen umzuwandeln, skalieren und dann den Kontrast erhöhen. Ein interessanter Effekt hierbei ist, dass sich dadurch nicht nur die Erkennungsqoute gegenüber dem unbearbeiteten Bild verbessert hat, sondern auch die Verarbeitungsgeschwindigkeit der OCR deutlich erhöht wurde.</li> <li>Auslesen der E-Mail-Adresse aus den Bildern - Da ich es auch schon in anderen Projekten gemacht hatte, lag es nahe die freie OCR-Engine “tesseract” zu verwenden¹. Sie liefert ausreichend gute Ergebnisse, auch wenn die proprietäre Konkurrenz bekanntermaßen bessere Erkennungsraten bietet.</li></ol> <p>Zum Schluß habe ich das Ergebnis noch manuell kontrolliert, um zu prüfen ob die OCR richtig lag.</p> <h2 id="lets-go"><a class="heading-link" title="Permalink" aria-hidden="true" href="#lets-go"><span>#</span></a>Lets go</h2> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/EMailImageReader-072dc5.avif, /images/posts/migrated/EMailImageReader-072dc5.webp, /images/posts/migrated/EMailImageReader-072dc5.png" src="/images/posts/migrated/EMailImageReader-072dc5.png" alt="Programmdurchlauf des EMailImageReader" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Programmdurchlauf des EMailImageReader</figcaption><!--]--></figure><!----> <p>Nun sollen endlich ein paar Zahlen zu den Ergebnissen folgen, wobei ich mir natürlich bewusst bin, dass die verwendeten Inputbilder keine repräsentative Stichprobe, aber für diesen Test absolut ausreichend sind. Die Verarbeitungszeit der 30 Bilder betrug über mehrere Läufe gemittelt etwa 4,5 Sekunden. Die Erkennungsquote der OCR lag dabei im Durchschnitt bei 33 %. Wenn genügend Inputbilder zur Verfügung stehen ergeben sich daraus 133 korrekt ermittelte E-Mail-Adressen pro Minute, was hochgerechnet fast 8000 E-Mail-Adressen pro Stunde ergibt. Auch wenn klassische Harvester vermutlich eine deutlich größere Leistung durch einfaches Parsen von Webseiten erreichen, ist dies ein beachtlicher Wert.</p> <p>Wie erwartet war die Erkennung bei Bildern mit sehr speziellen Fonts oder Bildrauschen sehr schlecht. Man muss aber auch anmerken, dass das hier verwendete Programm nur als Proof-Of-Concept bezeichnet werden kann. Es gibt noch einige Möglichkeiten die Erkennungsquote und Performance deutlich zu erhöhen und damit die Anzahl korrekt erkannter E-Mail-Adressen pro Minute.</p> <ul><li>Der oben beschriebene Algorithmus zur Bildoptimierung lässt sich zum Beispiel um das Entfernen von Bildrauschen oder eine dynamische Kontrastverbesserung erweitern.</li> <li>Die einzelnen Anwendungsschritte lassen sich gut parallelisieren. Durch den Einsatz von Multithreading kann das Suchen und Laden der Bilddateien, die Optimierung der Bilddateien und die OCR gleichzeitig für verschiedene E-Mail-Bilder erfolgen. Die Geschwindigkeit dürfte sich dadurch leicht um den Faktor 5 - 10 erhöhen lassen und ist dann hauptsächlich durch die Systemleistung (CPU für OCR und Internetanbindung wegen Webseiten- und Bilder-Download) beschränkt.</li> <li>Durch Optimierung der OCR lässt sich die Erkennungsrate noch deutlich erhöhen. Zum Beispiel ließe sich die OCR trainieren, so dass E-Mail-Adressen besser erkannt werden. Alternativ kann auch eine sogenannte Whitelist angegeben werden, die nur Zeichen enthält, die auch in E-Mail-Adressen zulässig sind. Über ein Wörterbuch, dass die häufigsten E-Mail-Provider-Domains enthält, kann die OCR-Erkennung nochmals deutlich verbessert werden. Man könnte das Wörterbuch sogar beim Crawlen dynamisch um die Domainnamen erweitern, von welchen entsprechende Bildern geladen wurden.</li></ul> <h2 id="wie-wahrscheinlich-ist-der-einsatz-von-ocr-bei-harvestern"><a class="heading-link" title="Permalink" aria-hidden="true" href="#wie-wahrscheinlich-ist-der-einsatz-von-ocr-bei-harvestern"><span>#</span></a>Wie wahrscheinlich ist der Einsatz von OCR bei Harvestern?</h2> <p>Auch wenn es keine technischen Hürden gibt mittels dem hier beschriebenen Verfahren E-Mail-Adressen zu sammeln, gibt es keinen Grund zur Panik. Es ist doch sehr unwahrscheinlich, dass diese Technik von Harvestern verwendet wird, auch in absehbarer Zukunft. Folgende Punkte müssen nämlich bedacht werden:</p> <ul><li>Da die OCR-Technologie relativ viel CPU-Leistung benötigt, ist das hier beschriebenene Verfahren deutlich rechen- und somit kostenintesiver als das herkömmliche Parsen von Webseiten.</li> <li>Damit ein Crawler möglichst effizient arbeitet, müsste er für die vielen verschiedenen Anbieter und Varianten von E-Mail-Bildern speziell angepasst werden, was einen hohen Aufwand verursacht.</li> <li>Manche Anbieter von E-Mail-Bildern bieten die Möglichkeit die Providerdomain als separate Grafik abzubilden (z.B.: @gmail.com) und Textfarben und -größen zu ändern um OCR zur erschwehren. Diese Techniken bieten zwar keinen echten Schutz, empfehle ich aber trotzdem jedem, der auf E-Mail-Bilder nicht verzichten kann oder möchte. Der Aufwand dem Crawler das Erkennen der Grafiken “beizubringen” dürfte auch kaum im Verhältnis zum daraus resultierenden Nutzen stehen.</li> <li>Tendenziell werden Personen, die Ihre E-Mail-Adresse mit so viel Aufwand versuchen zu schützen, kaum auf den versendeten Spam “klicken”. Die Opferquote dürfte hier viel geringer sein, als unter Personen, deren E-Mail-Adressen unbedarft ins Internet gelangen. Eine teuere Optimierung durchzuführen um danach nicht die primäre Zielgruppe zu erreichen</li></ul> <h2 id="e-mail-bilder-sicher-nutzen"><a class="heading-link" title="Permalink" aria-hidden="true" href="#e-mail-bilder-sicher-nutzen"><span>#</span></a>E-Mail-Bilder sicher nutzen</h2> <p>Auch wenn man vor Lesenden-Harvestern aus oben genannten Gründen keine Angst haben muss, lässt sich der Einsatz von E-Mail-Bildern mit ein paar einfachen Richtlinien weiter absichern:</p> <ul><li>Binden Sie das Bild nicht über einen Link zum Hersteller ein, da dieser über das Parsen der Seite Rückschlüsse zulässt. Legen Sie das Bild einfach auf dem eigenen Webspace ab.</li> <li>Wenn das Bild über JavaScript / CSS eingebunden wird, kann der Link zum Bild in der Regel nicht durch einfaches Parsen der Webseite ermittelt werden.</li> <li>Verwenden Sie keinen Dateinamen, der darauf hinweist, dass auf dem Bild eine E-Mail-Adresse vorhanden ist.</li></ul> <p>¹ Es ist zwar möglich eine in .NET verwendbare Version von tesseract aus dem Quelltext zu erzeugen allerdings gibt es hierfür auch schon fertige Projekte im Netz, die das Einbinden der OCR deutlich vereinfachen. Ich habe in diesem Fall das Projekt von Charles Weld verwendet: <a href="https://github.com/charlesw/tesseract" rel="nofollow">.NET Wrapper für tesseract (GitHub-Link)</a></p><!--]-->
          ]]></content:encoded>
          <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://1024kilobyte.de///images/posts/migrated/service_1_2-ac2b22.png"/>
          <media:content xmlns:media="http://search.yahoo.com/mrss/" medium="image" url="https://1024kilobyte.de///images/posts/migrated/service_1_2-ac2b22.png"/>          
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//pdfs-verkleinern-mit-dem-mac</guid>
          <title>PDFs verkleinern mit dem Mac</title>
          <description>Auf einem Mac lassen sich PDF-Dateien ohne zusätzliche, meist kostenpflichtige, Anwendungen verkleinern. Hierfür reicht es das Dokument mit der integrierten Vor</description>
          <link>https://1024kilobyte.de//pdfs-verkleinern-mit-dem-mac</link>
          <pubDate>Thu, 19 Jun 2014 23:32:37 +0200</pubDate>
          <category>Allgemein</category><category>Tipp</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//pdfs-verkleinern-mit-dem-mac">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>Auf einem Mac lassen sich PDF-Dateien ohne zusätzliche, meist kostenpflichtige, Anwendungen verkleinern. Hierfür reicht es das Dokument mit der integrierten Vorschau zu öffnen und über den Menüpunkt “Ablage > Exportieren” erneut abzuspeichern. Dabei muss nur der vordefinierte Filter “Reduce File Size” gewählt und gesichert werden.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/exportwithfilter-7ada60.avif, /images/posts/migrated/exportwithfilter-7ada60.webp, /images/posts/migrated/exportwithfilter-7ada60.png" src="/images/posts/migrated/exportwithfilter-7ada60.png" alt="Mit Filter exportieren" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Mit Filter exportieren</figcaption><!--]--></figure><!----> <p>Die Einstellungen des Filters können dabei aber nicht auf eigene Anforderungen angepasst werden. Da Bilder und Fotos mit der Standardeinstellung sehr drastisch komprimiert werden, habe ich einen anderen Weg gesucht und gefunden, um die Grafiken nicht unnötig zu verpixeln. Hierfür muss man lediglich einen eigenen Quartz-Filter erstellen, der die gewünschte Konfiguration enthält. In der Systemanwendung “ColorSync-Dienstprogramm” dupliziert man den Eintrag “Reduce File Size”, gibt ihm einem sprechenden Namen und passt die Einstellungen wie gewünscht an.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/customFilterCreate-166512.avif, /images/posts/migrated/customFilterCreate-166512.webp, /images/posts/migrated/customFilterCreate-166512.png" src="/images/posts/migrated/customFilterCreate-166512.png" alt="Duplizieren des System-Filters" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Duplizieren des System-Filters</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/customfiltersettings-88dff5.avif, /images/posts/migrated/customfiltersettings-88dff5.webp, /images/posts/migrated/customfiltersettings-88dff5.png" src="/images/posts/migrated/customfiltersettings-88dff5.png" alt="Beispiel für höhere Qualitätseinstellungen" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Beispiel für höhere Qualitätseinstellungen</figcaption><!--]--></figure><!----> <p>Der auf diese Weise erstellte Filter steht leider nicht in der oben beschriebenen Auswahl zur Verfügung, da hier nur die vordefinierten Systemfilter verwendet werden können. Es gibt aber noch einen anderen Weg, der es ermöglicht den eigenen Filter anzuwenden. Man wählt einfach im Kontextmenü der zu verkleinernden Datei im Finder den Punkt “Öffnen mit > ColorSync-Dienstprogramm”, wählt unten links den selbst erstellten Filter und wendet diesen an.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/openwithcolorsync-b5dae9.avif, /images/posts/migrated/openwithcolorsync-b5dae9.webp, /images/posts/migrated/openwithcolorsync-b5dae9.png" src="/images/posts/migrated/openwithcolorsync-b5dae9.png" alt="Öffnen mit Color-Sync" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Öffnen mit Color-Sync</figcaption><!--]--></figure><!----> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/selectcutomfilter-6e5421.avif, /images/posts/migrated/selectcutomfilter-6e5421.webp, /images/posts/migrated/selectcutomfilter-6e5421.png" src="/images/posts/migrated/selectcutomfilter-6e5421.png" alt="Eigenen Filter wählen" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[0--><figcaption class="svelte-14w3c6y">Eigenen Filter wählen</figcaption><!--]--></figure><!----> <p>Wenn das PDF-Dokument vorher große Grafiken enthielt, sollte es nun, abhängig von den verwendeten Einstellungen, verkleinert worden sein. In einem konkreten Fall hatte die Ausgabedatei nur noch 10% der Größe der Ursprünglichen. Aber Achtung: bei zu hohen Qualitätseinstellungen und bereits gut komprimierten PDF-Dokumenten kann es auch vorkommen, dass die Datei durch den Vorgang sogar vergrößert wird.</p> <p>In abgewandelter Form habe ich diese Vorgehensweise auch an anderer Stelle im Netz gefunden, aber die Kombination mit der Erstellung eines eigenen Filters ist hier entscheidend, um den Grad der Kompression optimieren zu können.</p><!--]-->
          ]]></content:encoded>
          
                    
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//wieviel-hd-brauchen-wir-wirklich</guid>
          <title>Zu scharf - Wieviel HD brauchen wir wirklich?</title>
          <description>Mit Einführung und Verbreitung von HD-Technologie und -Inhalten, besonders im TV-Bereich, hat es angefangen. Doch HD ist uns heute schon nicht mehr gut genug, e</description>
          <link>https://1024kilobyte.de//wieviel-hd-brauchen-wir-wirklich</link>
          <pubDate>Tue, 29 Apr 2014 23:00:35 +0200</pubDate>
          <category>HD</category><category>Pixeldichte</category><category>Retina</category><category>Hardware</category><category>Mobile Hardware</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//wieviel-hd-brauchen-wir-wirklich">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>Mit Einführung und Verbreitung von HD-Technologie und -Inhalten, besonders im TV-Bereich, hat es angefangen. Doch HD ist uns heute schon nicht mehr gut genug, es muss schon mindestens FullHD sein. Dass sich der Trend weiter fortsetzt, zeigt die sich bereits in den Startlöchern befindliche 4K-Technologie mit der alles noch schärfer und besser werden soll. Aber unser Alltag wird immermehr von Bildschirmen aller Art dominiert, ob Werbetafel, Smartphone, Smartwatch, Tablet PC, Notebook oder der gute alte Monitor. Dabei kommen viele verschiedene Größen, Auflösungen und Technologien zum Einsatz, doch für eine gute Darstellungsqualität sind zwei Faktoren ganz entscheidend: Die Pixeldichte und der Betrachtungsabstand. Idealerweise können wir mit bloßem Auge die Pixelstruktur der Displays nicht mehr wahrnehmen, was die biologisch maximale Bildschärfe beim Menschen darstellt. Technisch lässt sich die Pixeldichte zwar noch erhöhen, wir werden aber keinen Unterschied mehr “sehen” können. Das kann sogar negative Effekte haben, bei einer zu hohen Pixeldichte im Verhältnis zum Abstand lassen sich nicht mehr alle Details wahrnehmen.</p> <h2 id="mobile-innovation--revolution"><a class="heading-link" title="Permalink" aria-hidden="true" href="#mobile-innovation--revolution"><span>#</span></a>Mobile Innovation / Revolution</h2> <p>Der Trend zu hochauflösenden Bildschirmen bei mobilen Geräten wurde am 07. Juni 2010 von Apple mit Vorstellung des iPhone 4 eingeläutet. Eine große Neuerung war der Einsatz eines sogenannten Retina-Displays mit der 2-fachen Auflösung im Gegensatz zum Vorgänger. Einen so hochauflösenden Bildschirm gab es bis dahin in keinem mobilen Gerät, dass auch noch in solch immensen Stückzahlen produziert und verkauft wird. Mobiles HD ist hier somit erstmals bei der breiten Masse angekommen. Auch wenn die Konkurrenz recht lange gebraucht hat gleichzuziehen, bietet sich heute eine Vielfalt hochauflösender Displays in den Geräten unseres Alltags. Es folgt der Vergleich von einigen Geräten bezüglich der Pixeldichte ihrer Displays.</p> <p>Auch wenn die technischen Eckdaten von manch neuem Gerät in der Hochglanzbroschüre oder im direkten Gerätevergleich beeindrucken mögen, gilt hier auch: Weniger ist manchmal mehr. Wird nämlich die Auflösungsgrenze des menschlichen Auges überschritten, kann man keine Auflösungsunterschiede mehr wahrnehmen. Konkret heißt das, dass Sie im normalen Gebrauch keinen Unterschied sehen werden ob das Display Ihres Mobiltelefons nun mit 300 ppi oder mit 450 ppi auflöst.</p> <h2 id="zu-scharf"><a class="heading-link" title="Permalink" aria-hidden="true" href="#zu-scharf"><span>#</span></a>Zu Scharf</h2> <p>Wo diese Auflösungsgrenze nun genau liegt ist strittig und nicht nur davon abhängig, ob wir gut oder schlecht sehen, sondern auch vom individuellen Betrachtungsabstand und von der verwendeten Displaytechnologie. Durch die meist unterschiedliche Pixelstruktur von TFT- und OLED-Displays, genügen bei Ersteren schon etwa 220 ppi, wobei letztere bis zu 300 ppi benötigen um die Grenze der menschlichen Sehschärfe, im folgenden Graph gemittelt bei 275 ppi eingezeichnet, zu erreichen.</p> <figure class="svelte-14w3c6y"><!--[-1--><img srcset="/images/posts/migrated/Pixeldichte_geschnitten-75414e.avif, /images/posts/migrated/Pixeldichte_geschnitten-75414e.webp, /images/posts/migrated/Pixeldichte_geschnitten-75414e.png" src="/images/posts/migrated/Pixeldichte_geschnitten-75414e.png" alt="" loading="lazy" decoding="async" class="svelte-10v4ak0"/><!--]--> <!--[-1--><!--]--></figure><!----> <p>Alle Geräte, deren Pixeldichte über der horizontalen roten Linie liegt, weißen damit eine für das menschliche Auge nicht mehr erkennbare Pixelstruktur auf. Wie viel der Wert über der Linie liegt ist dabei egal. Wenn man das Diagramm betrachtet muss man spätestens ab dem Sony Experia Z2 von einer unnötig hohen Auflösung sprechen. Ein konkretes Beispiel hierfür ist Folgendes:</p> <p>“Der Bildschirm hat 1920 × 1080 Bildpunkte statt 1280 × 768 wie beim Nexus 4. Mit bloßem Auge lässt sich der Unterschied allerdings kaum erkennen, denn beide Anzeigen wirkten gestochen scharf.” (<a href="http://www.heise.de/newsticker/meldung/Erster-Eindruck-Google-Nexus-5-im-Test-2039343.html" rel="nofollow">Google Nexus 5 Gerätetest bei Heise.de</a>)</p> <p>Dabei sollte man nun noch anmerken, dass diese Übertreibung für den Nutzer keinen echten Vorteil, dafür aber Nachteile mit sich bringt. Die wichtigsten negativen Effekte einer zu hohen Pixeldichte dürften Folgende sein:</p> <ul><li>Die Kosten für Displays mit höherer Pixeldichte (bei gleicher Panelgröße) sind höher, somit die entsprechenden Geräte teuerer.</li> <li>Bei gleicher Paneltechnologie z.B. TFT führt eine Erhöhung der Pixeldichte in der Regel zu einem höheren Energiebedarf und somit zu geringerer Laufzeit.</li> <li>Zur Darstellung von Inhalten in höherer Auflösung wird zusätzlich der Prozessor und die Grafikeinheit stärker belastet, was wiederum zu einer geringeren Leistung und Laufzeit führt.</li></ul> <p>Für den Endnutzer bedeutet das konkret: Teurere Geräte mit kürzeren Akkulaufzeiten. Besonders bei Smartphones spielt der letzte Faktor aber eine ganz entscheidende Rolle.</p> <h2 id="fazit"><a class="heading-link" title="Permalink" aria-hidden="true" href="#fazit"><span>#</span></a>Fazit</h2> <p>Auch wenn der Griff zu einem bestimmten Smartphone-Modell häufig nicht auf Grund detaillierter Analyse technischer Details erfolgt, lohnt es sich die Sinnhaftigkeit mancher “Features” zu hinterfragen. Es scheint allerdings, als würden vielen Herstellern Mut und Ideen für weitere Innovationen im Smartphone-Sektor fehlen, so dass man von Modell zu Modell die Devise verfolgt: “Größer, Schneller und Hochauflösender”, egal ob das für den Nutzer überhaupt vorteilhaft ist, oder nicht.</p><!--]-->
          ]]></content:encoded>
          
                    
        </item>
      
        <item>
          <guid>https://1024kilobyte.de//dynamisches-ribbon-xml-in-microsoft-office-addins</guid>
          <title>Ribbon XML in Microsoft Office Addins</title>
          <description>Der folgende Artikel befasst sich mit der Erstellung von Oberflächenelementen in Microsoft Office Addins, welche Probleme autreten können und wie man Sie lösen</description>
          <link>https://1024kilobyte.de//dynamisches-ribbon-xml-in-microsoft-office-addins</link>
          <pubDate>Sat, 31 Aug 2013 21:36:07 +0200</pubDate>
          <category>Programmieren</category>
          <content:encoded><![CDATA[
            <div style="margin: 50px 0; font-style: italic;">
              If anything looks wrong, 
              <strong>
                <a href="https://1024kilobyte.de//dynamisches-ribbon-xml-in-microsoft-office-addins">
                  read on the site!
                </a>
              </strong>
            </div>

            <!--[--><p>Der folgende Artikel befasst sich mit der Erstellung von Oberflächenelementen in Microsoft Office Addins, welche Probleme autreten können und wie man Sie lösen kann. Die Code-Snippets und das am Ende des Posts bereitgestellte Beispielprojekt beziehen sich auf ein Outlook-Addin, die verwendeten Techniken lassen sich aber auch direkt auf Word- oder Excel-Addins anwenden.</p> <h3 id="designer-vs-xml"><a class="heading-link" title="Permalink" aria-hidden="true" href="#designer-vs-xml"><span>#</span></a>Designer vs. XML</h3> <p>Wenn man mit Visual Studio eine GUI für ein Microsoft Office Addin erstellen möchte hat man die Wahl, ob man mit dem grafischen Designer oder direkt im XML-Editor arbeiten möchte. Sicherlich ist die erste Variante einfacher, beschränkt sich aber auf das Erstellen von Ribbons. Manche Anpassungen der GUI, wie zum Beispiel das Kontextmenü, können somit nur per XML-Datei manipuliert werden. Leider lassen sich die unterschiedlichen Methoden nicht mischen¹, man muss in komplexeren Szenarien durchgängig die Oberfläche in XML beschreiben.</p> <p>Neben dem geringeren Komfort für den Entwickler gibt es aber noch weitere Schattenseiten. Besonders dynamische Oberflächen, die Lokalisierung verwenden oder Elemente bedingt ausblenden, sind hiermit recht umständlich abzubilden. Der größte Nachteil kommt aber zum tragen, wenn man verschiedene Oberflächenelemente, wie Ribbon und Kontextmenu, unabhängig voneinander bei unterschiedlichen RiddonIDs verwenden möchte. Hierzu muss man nämlich für jede Kombination der Elemente eine eigene XML-Datei erstellen. Damit sinkt wiederum die Wartbarkeit und Wiederverwendbarkeit des Quellcodes. Doch dieses Problem lässt sich beheben. Warum beschreibt man nicht einfach jedes Oberflächenelement in einer XML-Datei und fügt sie dann im Programmcode zur konkreten Oberfläche zusammen?</p> <h3 id="funktionsweise-von-ribbon-xml"><a class="heading-link" title="Permalink" aria-hidden="true" href="#funktionsweise-von-ribbon-xml"><span>#</span></a>Funktionsweise von Ribbon XML</h3> <p>Zum besseren Verständnis der ganzen Lösung beschreibe ich nochmal kurz die Funktionsweise von Ribbon XML. Wenn man dem AddIn-Projekt ein Ribbon XML Element hinzufügt wird nicht nur eine XML-Datei, sondern auch eine zugehörige Klasse erzeugt. Der Hook für die Ausführung von Ribbon XML ist die Funktion “CreateRibbonExtensibilityObject()” in der ThisAddin-Klasse.</p> <p>Hiermit wird beim Laden der grafischen Oberfläche die Funktion “GetCustomUI” in der Klasse “CustomUIManager” aufgerufen und bietet damit die Möglichkeit eigene Oberflächenelemente zu erstellen. Wenn man mehrere getrennte Oberflächenelemente erzeugen können möchte und damit mehrere XML-Dateien verwendet, muss eine der automatisch erzeugten Klassen als Controller verwenden, die dann die Oberflächenerstellung übernimmt. Genau dort steckt auch der Kern der ganzen Lösung. Abhängig von der an die Funktion “GetCustomUI” übergebenen RibbonID, wird das entsprechende RibbonXML-String erzeugt und zurückgegeben. Die RibbonID ist ein String der angibt welcher Teil der Anwendungsoberfläche gerade geladen wird. Eine vollständige Liste der RibbonIDs in Microsoft Outlook kann im <a href="http://msdn.microsoft.com/en-us/library/office/ff868522.aspx" rel="nofollow">MSDN</a> nachgelesen werden. Die restlichen Office-Anwendungen besitzen keine so komplexe Unterteilung der Oberfläche und verwenden jeweils nur eine RibbonID (im Einzelnen Microsoft.Word.Document, Microsoft.Excel.Workbook und Microsoft.PowerPoint.Presentation).</p> <p>Auf den ersten Blick mag es vielleicht etwas unübersichtlich aussehen, ist aber ganz einfach. Jedes mal wenn die Oberfläche geladen wird, z.B. beim Anwendungsstart oder durch Interaktion des Nutzers, wird die Funktion “GetCustomUI” aufgerufen. In dieser wird dann die XML-Repräsenation der eigenen Oberflächenelemente erzeugt und zurückgegeben. Die einzelnen Schritte sind:</p> <ol><li>Prüfen ob für die übergebene RibbonID eine eigene Oberfläche erzeugt werden soll. <ul><li>ja: weiter mit den folgenden Schritten 2 bis 4</li> <li>nein: sofort zurückkehren um die Ausführung nicht zu blockieren</li></ul></li> <li>Lokalisieren der erzeugten Oberfläche mit der Funktion “LocalizeRibbonXML”. Hierfür werden lediglich alle Einträge einer Ressourcendatei geladen um die Texte der Oberfläche zu setzen.</li> <li>Bedingtes Ausblenden von Elementen mit der Funktion “HideRibbonXMLElement”. Hiermit könnnen einzelne Oberflächenelemente, falls sie aus bestimmten Gründen nicht angezeigt werden sollen, ausgeblendet werden.</li> <li>Zurückgeben des erzeugten XML-Strings.</li></ol> <p>Außerdem habe ich noch die Funktion “OnRibbonElement_Click” für das Eventhandling hinzugefügt um Nutzereingaben entsprechend behandeln zu können.</p> <p>Das Vorgehen setzt natürlich vorraus, dass die verwendeten XML-Dateien keine XML-Header und CustomUI-Tags enthalten, der Inhalt der Datei “OUI_ContextMenu.xml” sieht bei mir zum Beispiel so aus:</p> <p>Zum Ausprobieren, Weiterentwicklen und Wiederverwenden habe ich mein vollständiges Visual Studio 2010-DemoProjekt für ein Outlook 2010-AddIn zum Download angehängt. Das Konzept wurde ebenfalls mit Visual Studio 2012 und Office 2013 getestet, kann also auch in neueren Projekten angewendet werden.</p> <p>1 Es ist nicht ganz richtig, dass sich Designer- und XML-Methode nicht in einem Projekt gemeinsam verwenden lassen. Wenn man die Funktion “CreateRibbonExtensibilityObject()” in der ThisAddin-Klasse, die für die Verwendung der XML-Methode benötigt wird.</p><!--]-->
          ]]></content:encoded>
          
                    
        </item>
      
  </channel>
</rss>