tag:blogger.com,1999:blog-89224400281265064892024-03-17T22:59:05.557-04:00Blog de Joe FreemanCloud and Software Architecture, Soft skills, IOT and embeddedJoe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.comBlogger439125tag:blogger.com,1999:blog-8922440028126506489.post-52531049396811771292024-03-03T17:42:00.006-05:002024-03-03T18:01:14.956-05:00Resetting and using Philips Hue bulbs with the Amazon Echo or hub<p>The Amazon Alex app is a unifying console for IOT and other experiences. I recently struggled trying to reset some legacy Zigbee devices that were paired with controllers I no longer had. It turns out I can use that app to reset my bulbs even though:</p><p></p><ol style="text-align: left;"><li>It isn't obvious how you reset devices.</li><li>The failure connect screen doesn't offer reset as an option.</li><li>The program doesn't list my Amazon Echo as being able to do that on the screen where it describes resetting the devices.</li></ol><h3 style="text-align: left;">Backstory</h3><p></p><p>I received a pair of white Philips Hue A19 bulbs as part of an Amazon promotion. Later I sold my Echo and got a new Amazon Echo with the built-in hub. Newer Hue lighting supports both Zigbee and Bluetooth while older devices are controllable over Zigbee networks. </p><p>Never have I ever owned a Philips Hue hub.</p><p>The bulbs must be reset to factory settings to onboard them to a new hub. In my case, my 4th gen Amazon Echo. The older bulbs can't be reset over Bluetooth. I couldn't get the rapid on/off power reset sequence to work. Frustrated, I put the bulbs into the closet for a couple years until Amazon added a serial number-based reset hook in the Echo app.</p><h2 style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMhrYniGayJUdvd-Bx7P7-fFW0-9lrKH-8RZcd0q9q5-_vXXturTVeeQm6dsrdWFJ_TyyHj96U53_2lGVNyE_P0U2721RpIfVBU2NCRirPqlcPK8tigXxOHCd-FVtWljoobfuueC1_2dIapqVdp_SO-EdNfW16j918XyAl2qfhYJCQ_xhPDQs1E-6w2p7z/s2079/IMG_0421.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="962" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMhrYniGayJUdvd-Bx7P7-fFW0-9lrKH-8RZcd0q9q5-_vXXturTVeeQm6dsrdWFJ_TyyHj96U53_2lGVNyE_P0U2721RpIfVBU2NCRirPqlcPK8tigXxOHCd-FVtWljoobfuueC1_2dIapqVdp_SO-EdNfW16j918XyAl2qfhYJCQ_xhPDQs1E-6w2p7z/s320/IMG_0421.png" width="148" /></a></div>Launch the Echo app and start adding a device<br /></h2><div>This is the standard new device startup screen</div><div><br /></div><h2 style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwpQovm7ybRvkn4r9ZYXkXQ7GCv7bKmIaPTe-kI882XdaGm9QcOGuTdrUBWB6cRLERmMrJKcyjvu0lNBOLCQw-Jd37-cPnwSngh52sRDkZK740pdKkW9imkmHQl2MZH4ifvG9JxdY0GDO2urPhcK5IZ28v1kuFbzaIwO0Y1wPyD-5qk50V0LSrZ0HUE-9u/s2079/IMG_0422.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwpQovm7ybRvkn4r9ZYXkXQ7GCv7bKmIaPTe-kI882XdaGm9QcOGuTdrUBWB6cRLERmMrJKcyjvu0lNBOLCQw-Jd37-cPnwSngh52sRDkZK740pdKkW9imkmHQl2MZH4ifvG9JxdY0GDO2urPhcK5IZ28v1kuFbzaIwO0Y1wPyD-5qk50V0LSrZ0HUE-9u/s320/IMG_0422.png" width="148" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwpQovm7ybRvkn4r9ZYXkXQ7GCv7bKmIaPTe-kI882XdaGm9QcOGuTdrUBWB6cRLERmMrJKcyjvu0lNBOLCQw-Jd37-cPnwSngh52sRDkZK740pdKkW9imkmHQl2MZH4ifvG9JxdY0GDO2urPhcK5IZ28v1kuFbzaIwO0Y1wPyD-5qk50V0LSrZ0HUE-9u/s2079/IMG_0422.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: left;"><br /></a></div>You are adding a device</h2><div>There is a lot of stuff in this application. I just want to add my bulbs.</div><div><br /></div><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghg4Mbz3mVNHsgoqloeUEemLl5CVNb6Nzl7c63D8id44bsg8fXO9LuweEEjKhWYXxw5UceI-Gx_sXeK9prdh-t0_nrSqtfVXg4s5W40tqDpTmE48C5MQtk2Y5a4RW6a4fzD8-BppMjbV9-bwqWmeEmZa7XM3PeKUM3pDL5Kz2wN27Uvkirs4W8VTwIdc2i/s2079/IMG_0423.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghg4Mbz3mVNHsgoqloeUEemLl5CVNb6Nzl7c63D8id44bsg8fXO9LuweEEjKhWYXxw5UceI-Gx_sXeK9prdh-t0_nrSqtfVXg4s5W40tqDpTmE48C5MQtk2Y5a4RW6a4fzD8-BppMjbV9-bwqWmeEmZa7XM3PeKUM3pDL5Kz2wN27Uvkirs4W8VTwIdc2i/s320/IMG_0423.png" width="148" /></a><h2 style="text-align: left;">It is a light bulb</h2><div style="text-align: left;">Yup just a bulb here. </div><div style="text-align: left;"><br /></div><div style="text-align: left;"><br /></div></div><div class="separator" style="clear: both; text-align: left;"><br /></div><h2 style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: right;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRWwNMb6xuObq-5Cw7q5aTN2p7i6SZ2wFsGVivBlNytCoc8-LyhFvjnEhZiiIjuGvmgHYAI9LVNldsdwbJUMGYZnOuoLVDKHAT499VeMlwmJkInp7QON0ZIYZ3_sSViNXjNvE-ISSt34rixssSZWlfkcXPMB5ynBQSAMlesU8QHxEG6eFaTTgAVgiyFj37/s2079/IMG_0424.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRWwNMb6xuObq-5Cw7q5aTN2p7i6SZ2wFsGVivBlNytCoc8-LyhFvjnEhZiiIjuGvmgHYAI9LVNldsdwbJUMGYZnOuoLVDKHAT499VeMlwmJkInp7QON0ZIYZ3_sSViNXjNvE-ISSt34rixssSZWlfkcXPMB5ynBQSAMlesU8QHxEG6eFaTTgAVgiyFj37/s320/IMG_0424.png" width="148" /></a></div>Scroll down until we find the Philips devices</h2><div>They are Philips Hue devices.</div><h2 style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: right;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguHQgXNhUDXnqkaustYO8vV-2UvUv0Zxjw1BxuLCjT0KsJHbPQev51poP2elXYypIoaNt13kFlN7DxXo17EpS387_ksRc88GtfdL0JUMBp-3WwvYVAKOEkuF9yal9CthFgMF53VLbSct99816IH5DytcVjIWjUYZh2ZHKoyK5wAVMRrsqL64cOC6fXD0fL/s2079/IMG_0425.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguHQgXNhUDXnqkaustYO8vV-2UvUv0Zxjw1BxuLCjT0KsJHbPQev51poP2elXYypIoaNt13kFlN7DxXo17EpS387_ksRc88GtfdL0JUMBp-3WwvYVAKOEkuF9yal9CthFgMF53VLbSct99816IH5DytcVjIWjUYZh2ZHKoyK5wAVMRrsqL64cOC6fXD0fL/s320/IMG_0425.png" width="148" /></a></div>There it is</h2><div>Phillips Hue is one of dozens of vendor options.</div><h2 style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhq5F1WvbI-gW6e05qfKATkhD9khrH2zCAp9hvul4b42VkldxxYWjUryN8D_4PYW50vwYwNdniRnBihTRt99XoboZjTb2iSBl7V_hzsRfqZq0NMovf0AIaphSmVT7uI9Y-2WP6jyA2iomku34a_grj2-bXb5pxhqIgEi_esxJbcz7FbTaiG6wxpl95effDP/s2079/IMG_0426.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhq5F1WvbI-gW6e05qfKATkhD9khrH2zCAp9hvul4b42VkldxxYWjUryN8D_4PYW50vwYwNdniRnBihTRt99XoboZjTb2iSBl7V_hzsRfqZq0NMovf0AIaphSmVT7uI9Y-2WP6jyA2iomku34a_grj2-bXb5pxhqIgEi_esxJbcz7FbTaiG6wxpl95effDP/s320/IMG_0426.png" width="148" /></a></div>This would work for a new un-initialized device</h2><div>There is no obvious way to reset the devices so we can just try auto-connect.</div><div><br /></div><div>I'm a guy so it wouldn't occur to me to click on the question mark help icon in the upper right corner.</div><h2 style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPx9xTOzVp3BWQB4jNkEsH_qfpcEbxnxx1bymtRt2u_DkuMseeUi-mnJLE1Dx7CXY8CwK3wKXr6mJDMTAm9kVpXdn4PJDoJvG-ChDj_5lxRQ17L1lRdtUr3NEIfrTXLt8nq5qAydADj2gR_fDx2-rA6pyRSSms46SttRJy-oMeDk5_Dk-WE8cGZzyqCVLo/s2079/IMG_0427.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPx9xTOzVp3BWQB4jNkEsH_qfpcEbxnxx1bymtRt2u_DkuMseeUi-mnJLE1Dx7CXY8CwK3wKXr6mJDMTAm9kVpXdn4PJDoJvG-ChDj_5lxRQ17L1lRdtUr3NEIfrTXLt8nq5qAydADj2gR_fDx2-rA6pyRSSms46SttRJy-oMeDk5_Dk-WE8cGZzyqCVLo/s320/IMG_0427.png" width="148" /></a></div>Hope springs eternal</h2><div>It won't work because the bulb is still bound to the Amazon Echo or hub that we got rid of or we bought these bulbs used.</div><h2 style="clear: both; text-align: left;">Find the serial number on the bulbs</h2><div>The Amazon app can signal a hard reset to Philips Hue bulbs if we know their serial numbers. We are going to need the serial number of the bulb. The bulbs can be reset or controlled if we have physical access to the serial numbers written on the sides.</div><div class="separator" style="clear: both; text-align: center;"><br /></div><h2 style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixjS5EvEefBnqq20l5vVUqqFFj05sSvx0qepFQ590nioN9MYU6SQJj8AB104U9BwctINB9Whkrs9uO-ZSc7JLpTMHPPSvSv4FoIubgdKsXzaeJqE0cyLhmDXpryaItfTXUUoIvuQVpkNzCS_78RN0UgPV_984RLod-5HOudcsmyW3cJ80UobpE9e1AgISi/s2079/IMG_0428.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixjS5EvEefBnqq20l5vVUqqFFj05sSvx0qepFQ590nioN9MYU6SQJj8AB104U9BwctINB9Whkrs9uO-ZSc7JLpTMHPPSvSv4FoIubgdKsXzaeJqE0cyLhmDXpryaItfTXUUoIvuQVpkNzCS_78RN0UgPV_984RLod-5HOudcsmyW3cJ80UobpE9e1AgISi/s320/IMG_0428.png" width="148" /></a></div>Try again with the serial number</h2><div>The Alexa app can reset a bulb if it has the serial number. Lets try and find the device again.</div><h2 style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicbzJ-iW7VrHr_JImjqYA-ZlL24a83IS36kcL12YOEjHldF6B8bUTMn7Oj-DaOMlDpLAt5i2UlT5fzNikhFqYczfMz4-PbmlCrSNJtOBjozqA9pcTfLcm2SLfcw_1yDjLK2NcTPRVLsuzMf_l_tOC9Ri5tYXg85j-I1MLSxPBMHIUKguiVaLyNSDn_ZDIJ/s2079/IMG_0429.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicbzJ-iW7VrHr_JImjqYA-ZlL24a83IS36kcL12YOEjHldF6B8bUTMn7Oj-DaOMlDpLAt5i2UlT5fzNikhFqYczfMz4-PbmlCrSNJtOBjozqA9pcTfLcm2SLfcw_1yDjLK2NcTPRVLsuzMf_l_tOC9Ri5tYXg85j-I1MLSxPBMHIUKguiVaLyNSDn_ZDIJ/s320/IMG_0429.png" width="148" /></a></div>Find Philips in the device list again</h2><div>Here we're searching instead of scrolling. We end up in the same place.</div><h2 style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiniCjcHcUz6t3074p_sn_-KtvQTQS4JOMcqQjo1BAdoIdjwZr_ur043MmQIJG7vv1jbKEmurBlW1YR4t3gvmi22Ee4rUfuuspqU752gb8Ueur-FjHt_nINtqqXckc42MMYK69HSZBEuzBEEzUMRFI_bgG080GNWqhGqPXgxc6h5zVyVbKQqjZcarlHWT7P/s2079/IMG_0430.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiniCjcHcUz6t3074p_sn_-KtvQTQS4JOMcqQjo1BAdoIdjwZr_ur043MmQIJG7vv1jbKEmurBlW1YR4t3gvmi22Ee4rUfuuspqU752gb8Ueur-FjHt_nINtqqXckc42MMYK69HSZBEuzBEEzUMRFI_bgG080GNWqhGqPXgxc6h5zVyVbKQqjZcarlHWT7P/s320/IMG_0430.png" width="148" /></a></div>Click on the help question mark</h2><div>This time we're going to ask for help.</div><div><br /></div><div>This totally isn't obvious. The help screen is a mash-up of functionality and some incomplete text and comments.</div><h2 style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj4XKPkmzAVhwSajcq-kySulX71vFTm5q-xum0ABTdPNr2L3b1BU9toEBVEyrJCZH2_PBTxMBzEhojJe78cbkwCOjXfINTfXXXtEeTHCGPS5fvPlVvzSVA345joDukT9IRZ42Hruwo5I5lR1qFKDR0ebj8TsE4afdpAPXt1yLj8l889LRF0sPdF2TpdvbT/s2079/IMG_0431.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj4XKPkmzAVhwSajcq-kySulX71vFTm5q-xum0ABTdPNr2L3b1BU9toEBVEyrJCZH2_PBTxMBzEhojJe78cbkwCOjXfINTfXXXtEeTHCGPS5fvPlVvzSVA345joDukT9IRZ42Hruwo5I5lR1qFKDR0ebj8TsE4afdpAPXt1yLj8l889LRF0sPdF2TpdvbT/s320/IMG_0431.png" width="148" /></a></div>Scroll until you get to the Philips section</h2><div>You can't see what you need at first so scroll. You may be able to see the start of your section. </div><div><b><br /></b></div><div><b>Connect smart home devices to Echo Show Plus or Echo Show. </b></div><div><br /></div><div>The Amazon Echo isn't mentioned even though it can act as a Zigbee hub. I stared at that a while because I don't understand why the Echo isn't mentioned.</div><div><br /></div><div><br /></div><br /><br /><br /><br /><br /><br /><h2 style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuYtGWqGx7m84OXagzU6bTBRa4Cy2VHRC_9t1Povq_3oq_u_gtHsT3s3Cie5dKEXmXOnzWuo-QWQ9NcGHQ9vpzkQNdgnx_PL-QgoalzKkH_IMFAbAv2OVOyORL7JS_AFIn0MwG8Cxau0jfUWuI-s_CeSYIxE-WGiRK_G3Q8tny-_8ZrhrQ7Mtr5ETS2GTu/s2079/IMG_0432.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuYtGWqGx7m84OXagzU6bTBRa4Cy2VHRC_9t1Povq_3oq_u_gtHsT3s3Cie5dKEXmXOnzWuo-QWQ9NcGHQ9vpzkQNdgnx_PL-QgoalzKkH_IMFAbAv2OVOyORL7JS_AFIn0MwG8Cxau0jfUWuI-s_CeSYIxE-WGiRK_G3Q8tny-_8ZrhrQ7Mtr5ETS2GTu/s320/IMG_0432.png" width="148" /></a></div>Click on Reset Phillips Light</h2><div>The page only mentions the Echo Plus and the Echo Show but it works for the ball-shaped (spherical) Amazon Echo!</div><div><br /></div><div>Click on the <b>Reset Philips Light</b> in blue at the bottom.</div><h2 style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAgwvko0xc2Ib3JEUa85_2Okd1Ik4o3YDbwwSL0t0ruwgrKd-hLfI4b3f7pVTQ6yT2U6ImlDy-JIGZOmfepDHkcFcGxgtP3Uny3YKqg34UafrL-UEuqpuyoofE-B-1p38VLuymZZMOTuFEisLxAMQe42ZFfq_ifzs67uo2NBC72vm1CHvQXvZPA6wqAbrf/s2079/IMG_0433.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2079" data-original-width="962" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAgwvko0xc2Ib3JEUa85_2Okd1Ik4o3YDbwwSL0t0ruwgrKd-hLfI4b3f7pVTQ6yT2U6ImlDy-JIGZOmfepDHkcFcGxgtP3Uny3YKqg34UafrL-UEuqpuyoofE-B-1p38VLuymZZMOTuFEisLxAMQe42ZFfq_ifzs67uo2NBC72vm1CHvQXvZPA6wqAbrf/s320/IMG_0433.png" width="148" /></a></div>Reset by serial number</h2><div>Enter the serial number and click on the button that you can just see partially off the bottom of the screen. You may need to scroll to see the button after entering the serial number.</div><h2 style="clear: both; text-align: left;">Follow the normal device naming and onboarding.</h2><div>The bulb will reset and appear as a new device. From here on you just follow the normal new device procedure.</div><div><br /></div><h3 style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEib74HgDV_7A1f9kfcTvGipEF-_ObDQ8CrdQHblRE2vn1nK1WBDARkqoYzg6BuMb9CVSfADni5BO7T5G0hA-uqGlTITcz2UFjMg1Df0kVO4bIijG0j2repKFMFes91GOIgcMK2DO0TEcd9FVvYV4q8RSRDBIUAabD_rE02zZYslwE55hqfC4hfIPVToulKN/s3099/IMG_0435.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="3065" data-original-width="3099" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEib74HgDV_7A1f9kfcTvGipEF-_ObDQ8CrdQHblRE2vn1nK1WBDARkqoYzg6BuMb9CVSfADni5BO7T5G0hA-uqGlTITcz2UFjMg1Df0kVO4bIijG0j2repKFMFes91GOIgcMK2DO0TEcd9FVvYV4q8RSRDBIUAabD_rE02zZYslwE55hqfC4hfIPVToulKN/w200-h198/IMG_0435.png" width="200" /></a></div>My bulbs</h3><div>Philips Hue White A19 2-Pack 60W Dimmable LED Smart Bulbs Works with Alexa, HomeKit & Google Assistant</div><div><ul style="text-align: left;"><li>929001136979</li><li>840 lumens</li><li>2700K</li><li>9.5 watts used</li></ul></div><div><br /></div><div><br /></div><h3 style="text-align: left;">Revision History</h3><div>Created 2024 03</div><br /><br /> <p></p>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-41948392951076246822024-03-03T10:30:00.014-05:002024-03-03T17:45:28.761-05:00Did anyone dogfood this experience? A bog standard gate experience gone bad.<p>I had a consulting job at a company that got new gates. The new gates have a different experience between doors and based on whether you are going in or out.</p><p>Between 1000 and 4000 people use the gates per day. A back-of-the-napkin analysis says this interface costs the company $300/day or over $80,000 per year plus any frustration. They often pause at this gate system to deal with the interface inconsistencies. </p><p></p><ul style="text-align: left;"><li><i>3000 people * 5 seconds / 3600 sec/hr * $80/hour fully loaded * 5 d/w * 50 w/y</i></li></ul><h2 style="text-align: left;">The gate system as a use case</h2><p></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh89QchXFza7BMqpnf4DbBWFmGf1jvexrxx8cruKZLcfVxniOrJfgBtRE8MqjNi0sbpuI0qDlXWXpYmrEWZ6ey6MuZrv9G-QiEiKJPGVc8oEaesCEcRm8jqyGfRk_F6szuWymSiGr4ALKSq8ixD5YORpJyxMWbmLAmCCfvbvBu5PPyhtTeLQjaA8rEkPvDH/s2016/IMG_0211%20(1).png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="1512" data-original-width="2016" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh89QchXFza7BMqpnf4DbBWFmGf1jvexrxx8cruKZLcfVxniOrJfgBtRE8MqjNi0sbpuI0qDlXWXpYmrEWZ6ey6MuZrv9G-QiEiKJPGVc8oEaesCEcRm8jqyGfRk_F6szuWymSiGr4ALKSq8ixD5YORpJyxMWbmLAmCCfvbvBu5PPyhtTeLQjaA8rEkPvDH/w320-h240/IMG_0211%20(1).png" width="320" /></a></div>They bought a combined entry/exit system. Looks good from here.<p></p><p></p><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBFqhg2uiySS2g-4QELHtOZCfOGOTB_HMeZ0VU6L1a47Dz8dPIRKO8yyg-uR0mVU3buSQwBmU16Qpl99u-Huob_L6YYvESoyDnvEnOWp3gBR62f7o2WoDVMzUr7EG9lAvcLsBCcUXMt-nb0T8bfC5YExE6OJZBNXiTvwdkX4gBuAklrJoPGyQ4T0aOF9Pt/s4032/IMG_0402.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: center;"><img border="0" data-original-height="2517" data-original-width="4032" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBFqhg2uiySS2g-4QELHtOZCfOGOTB_HMeZ0VU6L1a47Dz8dPIRKO8yyg-uR0mVU3buSQwBmU16Qpl99u-Huob_L6YYvESoyDnvEnOWp3gBR62f7o2WoDVMzUr7EG9lAvcLsBCcUXMt-nb0T8bfC5YExE6OJZBNXiTvwdkX4gBuAklrJoPGyQ4T0aOF9Pt/s320/IMG_0402.png" width="320" /></a></div>Here is a different set of gates by the same vendor in the same building. I was going to say that these were on the way out but this picture is also on the way in.</div><h2 style="text-align: left;"><br /></h2><h2 style="text-align: left;"><br /></h2><h2 style="text-align: left;"><br /></h2><h2 style="text-align: left;">Bi-directional gate control - flow optimization</h2><div>The gates support traffic in both directions. Each turnstile has two card readers, one for each direction through the gate. The card reader is "right-handed" on entry and "left-handed" on exit. </div><h2 style="text-align: left;">Entry arrows show the way.</h2><div>This shows the markings in the entry direction. The <span style="color: #38761d;">green arrows </span>on the front try to show you which turnstyle is tied to a specific card reader. Pretty standard stuff.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTJuxnDKCx-ttovVCPsCEkjw16NvnyWaVKHuXNS5lgIQD0baYj4BVEGsTmzxzUpBNjueObIp2rglXtTtogVptBj6qKl_CA5Zim-jUKWvuiSO76BnFr6aB6cfvCs6tf7pa0Wfl6X5bU01H3TY-Pd0BS5MRh9Yd3dBslinOlLqJdg8b6EMK5kUAM1aZjpdGY/s2018/IMG_0211.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1506" data-original-width="2018" height="478" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTJuxnDKCx-ttovVCPsCEkjw16NvnyWaVKHuXNS5lgIQD0baYj4BVEGsTmzxzUpBNjueObIp2rglXtTtogVptBj6qKl_CA5Zim-jUKWvuiSO76BnFr6aB6cfvCs6tf7pa0Wfl6X5bU01H3TY-Pd0BS5MRh9Yd3dBslinOlLqJdg8b6EMK5kUAM1aZjpdGY/w640-h478/IMG_0211.png" width="640" /></a></div><div><br /></div><div>There is one weird thing I didn't show here. The green arrow on the card reader turns to a <span style="color: #990000;">red X</span> when it accepts your badge to tell the next person they can't use their badge yet. So you use your badge and it changes to a symbol that means "no". Why? You can see it below.</div><h2 style="text-align: left;">What is happening at this set of gates?</h2><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBFqhg2uiySS2g-4QELHtOZCfOGOTB_HMeZ0VU6L1a47Dz8dPIRKO8yyg-uR0mVU3buSQwBmU16Qpl99u-Huob_L6YYvESoyDnvEnOWp3gBR62f7o2WoDVMzUr7EG9lAvcLsBCcUXMt-nb0T8bfC5YExE6OJZBNXiTvwdkX4gBuAklrJoPGyQ4T0aOF9Pt/s4032/IMG_0402.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2517" data-original-width="4032" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBFqhg2uiySS2g-4QELHtOZCfOGOTB_HMeZ0VU6L1a47Dz8dPIRKO8yyg-uR0mVU3buSQwBmU16Qpl99u-Huob_L6YYvESoyDnvEnOWp3gBR62f7o2WoDVMzUr7EG9lAvcLsBCcUXMt-nb0T8bfC5YExE6OJZBNXiTvwdkX4gBuAklrJoPGyQ4T0aOF9Pt/s320/IMG_0402.png" width="320" /></a></div>These gates are bi-directional with all of them marked the same. The system is set up so people can exit using any lane. Here, I'm trying to leave the building. Why do these look different from the inbound direction? I just realized that these same-style gates are marked differently at this entrance.</div><div><br /></div><div>These gates all show red.</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>I walk up to the gates. From 10 feet away all I see are <span style="color: #990000;">red X</span>s on the front. All the gates have a <span style="color: #990000;">red "X"</span> that seems to say there is no way out. I can't see the top of the gate controllers so I can't see the <span style="color: #38761d;">green arrows</span> <i>buried</i> in the top. The <span style="color: #38761d;">green arrows</span> on the card reader are now on the left hand, the opposite side from where they were on the way in. </div><div><br /></div><div>Looking at the Xs. I have no idea what they mean 😕 The X here may mean that the turnstile is locked. 🤷 Is this <a href="https://en.wikipedia.org/wiki/Hotel_California" target="_blank">Hotel California where you can enter but never leave</a>? I really have no idea why the front has a<span style="color: #cc0000;"> red X</span> in this direction while showing <span style="color: #38761d;">green arrows</span> on the way in. The <span style="color: #990000;">red X</span> seems to say <i>don't go</i> <i>here</i> but I want to leave the building.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsVhPQ7alShS9T7u-UeGzHX3f8I4Z9u05kNRI5q0dH6BSXrDGCxyJQNMGQPUdEiI-_rxNQGiw8aD1eEO9-1jAH6hbI769-mzR1nZ70jftACzprnhTFwwLhFpnH5a-sF_GeEcPhvgXecPOP0a5LuBUt099qBM1Jh5qOYqze5YdOLO3qASF80W7MRwWmYzif/s2016/IMG_0404.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1512" data-original-width="2016" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsVhPQ7alShS9T7u-UeGzHX3f8I4Z9u05kNRI5q0dH6BSXrDGCxyJQNMGQPUdEiI-_rxNQGiw8aD1eEO9-1jAH6hbI769-mzR1nZ70jftACzprnhTFwwLhFpnH5a-sF_GeEcPhvgXecPOP0a5LuBUt099qBM1Jh5qOYqze5YdOLO3qASF80W7MRwWmYzif/w640-h480/IMG_0404.png" width="640" /></a></div><div><br /></div><h2 style="text-align: left;">I let muscle memory be my guide and got a bruise on my thigh</h2><div>So I walked up, held out my key card to the card reader on the right and it unlocked <i>a</i> gate. It unlocked the gate to my right, that you can't see in the picture. We know the gate to my right is unlocked because the light <i>I can't see</i> on the front turned green and because the key card light on the right turned to a <span style="color: #990000;">red X. </span>You would think that means <i>rejected.</i> Nope. In this case, the <span style="color: #990000;">red X </span>means "don't use the card reader until a person goes through the gate".</div><div><span style="color: #990000;"> </span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNP1YirGgB_Y3fWVOto2SNYR475d6BMtzohKYoRuY1Cn_fG-YQyx2uFt5mzX14GpV8ueX-OZMPuNasym422xxobegzucJmHVCHr3sk71C2iY2p_2XT5T77rR8BWWcHJU3Wt2R8e_vlGm6qVRj4qSopDW-DSo8hROMGctJT28jMHQtCS-KBVJu1Ace5rvs9/s2118/IMG_0405.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1512" data-original-width="2118" height="456" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNP1YirGgB_Y3fWVOto2SNYR475d6BMtzohKYoRuY1Cn_fG-YQyx2uFt5mzX14GpV8ueX-OZMPuNasym422xxobegzucJmHVCHr3sk71C2iY2p_2XT5T77rR8BWWcHJU3Wt2R8e_vlGm6qVRj4qSopDW-DSo8hROMGctJT28jMHQtCS-KBVJu1Ace5rvs9/w640-h456/IMG_0405.png" width="640" /></a></div><div><br /></div><h2 style="text-align: left;">The card reader turned red when it accepted me?</h2><div>I try again and use the card reader on the <i>left</i>. The card reader symbol turns into a <span style="color: #990000;">red X</span>. The lane symbol that I am probably directly next to, turns into a <span style="color: #38761d;">green arrow</span>. Now I can pass through the turn style and leave the building.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA-gWUzS7rC4a-hgv6zZ5nzbN_Uhsg9iTSFFwDSb4_iTV9d7jqtKKr_f_yv6dtopgzBCkInRo6bJxS8qEOcIKzjipWbooVPetEVx6IXUApbE1S3U7GZZagtc6Surd__sCZEgbQam3ePaLRNPX01sSri4HkU2VxZlLFqq9vyHCNadlbYJUXI_LswN1dDtVf/s2016/IMG_0406.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1512" data-original-width="2016" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA-gWUzS7rC4a-hgv6zZ5nzbN_Uhsg9iTSFFwDSb4_iTV9d7jqtKKr_f_yv6dtopgzBCkInRo6bJxS8qEOcIKzjipWbooVPetEVx6IXUApbE1S3U7GZZagtc6Surd__sCZEgbQam3ePaLRNPX01sSri4HkU2VxZlLFqq9vyHCNadlbYJUXI_LswN1dDtVf/w640-h480/IMG_0406.png" width="640" /></a></div><br /><h2 style="text-align: left;">What were they thinking?</h2><div>There are two major issues.</div><div><ol style="text-align: left;"><li>The first is the change in experience between <i>left-hand and right-hand</i> controls. The customer is cheap. I'm sure someone got a bonus for saving a few bucks here. I suspect the vendor sells an extra card reader pod that can be configured so the controls are the same in both directions. Either that or the vendor was lazy and didn't test their system with <i>real</i> humans.</li><li>The weird differences between the two sets. I don't know why the default lane light configurations are different.</li></ol></div><h2 style="text-align: left;">An editorial comment</h2><div>The experience reminded me that one cares about inefficiencies and incremental employee costs because they don't appear anywhere in a report or spreadsheet. The customer could have redone this system or demanded updates. It doesn't appear that anyone cares other than the users.</div><h3 style="text-align: left;">Revision History</h3><div>Created 2024 03</div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-8124761235313718752024-02-26T23:13:00.017-05:002024-02-28T09:00:46.271-05:00Using OpenTelemetry to send Python metrics and traces to Azure Monitor and App Insights<p>Microsoft has updated its Python libraries that let us send 3rd party library invocation metrics and traces and application-specific custom metrics and traces to Application Insights. Their OpenTelemetry Exporters make it simple to route the standard Python OpenTelemetry library observations to Application Insights. You bootstrap the Azure config and credentials and then use standard OT calls to capture metrics and trace information.</p><h2 style="text-align: left;">SpeedTest network Metrics in Azure using OpenTelmetry</h2><div>A couple of years ago I wrote a program to record the health of my home internet connection and store that information in Azure Application Insights. I did this with a Python program that leveraged Microsoft's OpenCensus Azure exporter. That library is becoming obsolete with the move from OpenCensus to OpenTelemetry. </div><div><br /></div><div>That project has now been ported to Open Telemetry! It involved 6 hours and 40 lines of code that was preceded by 20 hours of whining and internet surfing. The hard part was figuring out exactly what needed to change between OpenCensus and OpenTelemetry. It helped that the latest OT Python SDK added a synchronous Gauge that aligned with the previous OC API. It also helped that Microsoft's Azure SDK Python team recently released a simple starter function that handled most of the config with a single call and no exporter configuration. You may wish to do manual configuration for fine-tuning but configure_azure_monitor() was a great starting point. See the code <a href="https://github.com/freemansoft/speedtest-app-insights" target="_blank">on GitHub</a>,</div><div><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWoM6Dj1ex2d4abCSNjSMypM2nqCUGUhjf3C5Qy4Qb3ldqTZ6pNwfxZFVNId90vrwLoYZDpJI6XoCXYYWaxK86f86kJSphchYoILxPokTa-PPLeNPlMNmXUah7V3firat2yFM0cv_xsnUtYtNNPDHJWTUYEgFWftrVMkn_cjrZY36fpG8RUtCTIXZk16cA/s1526/Speedtest-Dashboard.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="805" data-original-width="1526" height="338" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWoM6Dj1ex2d4abCSNjSMypM2nqCUGUhjf3C5Qy4Qb3ldqTZ6pNwfxZFVNId90vrwLoYZDpJI6XoCXYYWaxK86f86kJSphchYoILxPokTa-PPLeNPlMNmXUah7V3firat2yFM0cv_xsnUtYtNNPDHJWTUYEgFWftrVMkn_cjrZY36fpG8RUtCTIXZk16cA/w640-h338/Speedtest-Dashboard.png" width="640" /></a></div><div><br /></div><h2>Automatic Instrumentation of Libraries vs Custom</h2><div>The Python Open Telemetry library can easily add metrics and tracing probes to standard Python modules and libraries resulting in end-to-end call tracing across endpoints. The speedtest program is a batch job that is essentially using Azure Monitor as my operational dashboard. I only wanted custom metrics and tracing and did not want to capture any instrumentation injected by OpenTelemetry itself.</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEi_Qn2nw4Oop2p799KfDrXxNjxQFRnFRE3-ow5lms3G4WTMmQO8GH2LNB05ZEijJ1jvHGi_WLpr0rQ6Sn8spH38ZxoufD_qachqtPjBwcUYOQTEPqEMbN0bc1AW9dBzOwlds003R9oJ8Mw3en-0H5j2JpMARo47WYjR9yHkfEMZ490nWnr2CuBWAoDUk215" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="2640" data-original-width="2728" height="387" src="https://blogger.googleusercontent.com/img/a/AVvXsEi_Qn2nw4Oop2p799KfDrXxNjxQFRnFRE3-ow5lms3G4WTMmQO8GH2LNB05ZEijJ1jvHGi_WLpr0rQ6Sn8spH38ZxoufD_qachqtPjBwcUYOQTEPqEMbN0bc1AW9dBzOwlds003R9oJ8Mw3en-0H5j2JpMARo47WYjR9yHkfEMZ490nWnr2CuBWAoDUk215=w400-h387" width="400" /></a></div><br /></div><h2 style="text-align: left;">Configure OpenTelemtry and Azure exporters</h2><div>The following code is a point-in-time snapshot of the of <a href="https://github.com/freemansoft/speedtest-app-insights/blob/main/src/AppInsights.py" target="_blank">ApplicationInsights.py in the speedtest-app-insights GitHub Repository</a></div><div>I capture only my specific metrics and traces. This meant I had to disable the automatic integrations. This is done by setting environment variables. I do this <i>from inside</i> the Python program.</div><div><br /></div><div>OpenTelemetry already captures my host name but I wanted to know which program or service generated the metrics. I set the AppInsights cloud role to be my program name as an environment variable by setting the <i>service.name</i> property in OpenTelemetry.</div><div><br /></div><div>
<div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"> <div><span style="color: #6a9955;"># call this if you want to send logs to Azure App Insight</span></div><div><span style="color: #6a9955;"># after this,</span></div><div><span style="color: #6a9955;"># every log(warn) will end up in azure as a log event "trace" !"tracing"</span></div><div><span style="color: #569cd6;">def</span> <span style="color: #dcdcaa;">register_azure_monitor</span>(</div><div> <span style="color: #9cdcfe;">azure_connection_string</span>: <span style="color: #4ec9b0;">str</span>,</div><div> <span style="color: #9cdcfe;">cloud_role_name</span>: <span style="color: #4ec9b0;">str</span>,</div><div> <span style="color: #9cdcfe;">capture_logs</span>: <span style="color: #4ec9b0;">bool</span> <span style="color: #d4d4d4;">=</span> <span style="color: #4fc1ff;">False</span>,</div><div>) -> <span style="color: #4fc1ff;">None</span>:</div><div> <span style="color: #6a9955;"># Cloud Role Name uses service.namespace and service.name attributes,</span></div><div> <span style="color: #6a9955;"># it falls back to service.name if service.namespace isn't set.</span></div><div> <span style="color: #6a9955;"># actually is concatenated ${service.namespace}.${service.name}</span></div><div> <span style="color: #6a9955;"># Cloud Role Instance uses the service.instance.id attribute value.</span></div><div> <span style="color: #4ec9b0;">os</span>.<span style="color: #9cdcfe;">environ</span>[<span style="color: #ce9178;">"OTEL_RESOURCE_ATTRIBUTES"</span>] <span style="color: #d4d4d4;">=</span> <span style="color: #569cd6;">f</span><span style="color: #ce9178;">"service.name=</span><span style="color: #569cd6;">{</span><span style="color: #9cdcfe;">cloud_role_name</span><span style="color: #569cd6;">}</span><span style="color: #ce9178;">"</span></div><div> <span style="color: #6a9955;">#</span></div><div> <span style="color: #6a9955;"># Disable exporters by setting these variables to "none"</span></div><div> <span style="color: #6a9955;">#</span></div><div> <span style="color: #6a9955;"># Netchecks</span></div><div> <span style="color: #6a9955;"># 7 items sent with or without integrations enabled</span></div><div> <span style="color: #c586c0;">if</span> <span style="color: #569cd6;">not</span> <span style="color: #9cdcfe;">capture_logs</span>:</div><div> <span style="color: #4ec9b0;">os</span>.<span style="color: #9cdcfe;">environ</span>[<span style="color: #4ec9b0;">environment_variables</span>.<span style="color: #9cdcfe;">OTEL_LOGS_EXPORTER</span>] <span style="color: #d4d4d4;">=</span> <span style="color: #ce9178;">"none"</span></div><div> <span style="color: #6a9955;"># NetChecks</span></div><div> <span style="color: #6a9955;"># 4 traces , 6 if integrations are enabled</span></div><div> <span style="color: #6a9955;"># os.environ[environment_variables.OTEL_TRACES_EXPORTER] = "none"</span></div><div> <span style="color: #6a9955;"># NetChecks</span></div><div> <span style="color: #6a9955;"># 3 metrics, 5 if integrations are enabled</span></div><div> <span style="color: #6a9955;"># os.environ[environment_variables.OTEL_METRICS_EXPORTER] = "none"</span></div><div> <span style="color: #6a9955;">#</span></div><div> <span style="color: #6a9955;"># Upload and download operations involve multiple HTTP packets which </span></div><div> <span style="color: #6a9955;"># are all captured as metrics, traces and logs if we leave</span></div><div> <span style="color: #6a9955;"># the urllib integration enabled</span></div><div> <span style="color: #4ec9b0;">os</span>.<span style="color: #9cdcfe;">environ</span>[<span style="color: #ce9178;">"OTEL_PYTHON_DISABLED_INSTRUMENTATIONS"</span>] <span style="color: #d4d4d4;">=</span> (</div><div> <span style="color: #ce9178;">"azure_sdk,django,fastapi,flask,psycopg2,urllib,urllib3"</span></div><div> )</div><br /><div> <span style="color: #6a9955;"># not sure what value to put here</span></div><div> <span style="color: #6a9955;"># os.environ[environment_variables.LOGGER_NAME_ARG] = "__name__"</span></div><br /><div> <span style="color: #dcdcaa;">configure_azure_monitor</span>(</div><div> <span style="color: #9cdcfe;">connection_string</span><span style="color: #d4d4d4;">=</span><span style="color: #9cdcfe;">azure_connection_string</span>,</div><div> <span style="color: #9cdcfe;">disable_offline_storage</span><span style="color: #d4d4d4;">=</span><span style="color: #4fc1ff;">True</span>,</div><div> )</div><br /></div></div><div><br /></div>
<div>The main program has to call <i>register_azure_monitor() </i>on startup.</div><div><br /></div>
<h3 style="text-align: left;">Azure Cloud RoleName and RoleInstance</h3><div><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/a/AVvXsEieZAQwehWlmxZOic5RPJMNKvVTds83D6xpp32ZEyuUx6k3MdbqTzq9oURoXOY4zj_yA3NvgxfcAyfh1EEKvOvUAZG5GeyIMZ-tFRYr7WwaG-spzRV5rzmfw1W6Ihbrk_ygwA2hPQl8fshH_0kHKr5wVp18PtDlJonHPrtLOWCurhKX96H82GAwlF-xL82k" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img alt="" data-original-height="193" data-original-width="289" height="214" src="https://blogger.googleusercontent.com/img/a/AVvXsEieZAQwehWlmxZOic5RPJMNKvVTds83D6xpp32ZEyuUx6k3MdbqTzq9oURoXOY4zj_yA3NvgxfcAyfh1EEKvOvUAZG5GeyIMZ-tFRYr7WwaG-spzRV5rzmfw1W6Ihbrk_ygwA2hPQl8fshH_0kHKr5wVp18PtDlJonHPrtLOWCurhKX96H82GAwlF-xL82k" width="320" /></a></div>Azure App Insights has standard metadata fields including the <b>cloud_RoleName</b> and the <b>cloud_RoleInstance</b>. These historical AppInsights fields are populated from specific OpenTelemetry properties.<br /></div><div><ul style="text-align: left;"><li>The <b>cloud_RoleInstance</b> represents the host or runtime container of the program. In my case, it is the hostname of my PC, my Raspberry Pi, or IOT device. I was OK with the default mapped in by OpenTelemetry and didn't have to override.</li><li>The <b>cloud_RoleName</b> often contains the application name, function name, or process name. The default OpenTelemetry field value didn't work for me so I had to set the <b>service.name</b> environment variable that is picked up by OTel and then mapped into the AppInsight field of a different name.</li></ul>All the instances of a given service share the same <b>cloud_RoleName</b> but have different <b>cloud_RoleInstance </b>values.</div><h2 style="text-align: left;">Generating Metrics</h2><h3 style="text-align: left;">Creating the OT meter </h3><div>Metrics/gauges/counters are grouped under a meter. This function lets my various modules spen up a meter with the passed-in name</div><div><br /></div><div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div><span style="color: #6a9955;"><br /></span></div><div><span style="color: #6a9955;"> # Returns a meter that gauges can be connected to</span></div><div><span style="color: #569cd6;"> def</span> <span style="color: #dcdcaa;">create_ot_meter</span>(<span style="color: #9cdcfe;">meter_name</span>: <span style="color: #4ec9b0;">str</span>, <span style="color: #9cdcfe;">azure_connection_string</span>: <span style="color: #4ec9b0;">str</span>) -> <span style="color: #4ec9b0;">Meter</span>:</div><div> <span style="color: #9cdcfe;">meter</span> <span style="color: #d4d4d4;">=</span> <span style="color: #4ec9b0;">metrics</span>.<span style="color: #dcdcaa;">get_meter_provider</span>().<span style="color: #dcdcaa;">get_meter</span>(<span style="color: #9cdcfe;">name</span><span style="color: #d4d4d4;">=</span><span style="color: #9cdcfe;">meter_name</span>)</div><div> <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">meter</span></div><br /></div></div><div><br /></div><h3 style="text-align: left;">Create a Gauge for the meter</h3><div>The speed test program captures server acquisition times, ping times, and upload/download speeds. I did all this in gauges tied to the same meter. This code creates the meter and gauges, one in this case. </div><div><br /></div><div>This particular program </div><div><ol style="text-align: left;"><li>Starts</li><li>Runs a speed test</li><li>Records the results of the test.</li><li>Terminates</li></ol></div><div>The new non-callback gauge is perfect for this.</div><div><br /></div><div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div> </div><div> <span style="color: #9cdcfe;">meter</span> <span style="color: #d4d4d4;">=</span> <span style="color: #dcdcaa;">create_ot_meter</span>(</div><div> <span style="color: #9cdcfe;">meter_name</span><span style="color: #d4d4d4;">=</span><span style="color: #ce9178;">"SpeedTest"</span>, <span style="color: #9cdcfe;">azure_connection_string</span><span style="color: #d4d4d4;">=</span><span style="color: #dcdcaa;">load_insights_key</span>()</div><div> )</div><br /><div> <span style="color: #6a9955;"># perf data gathered while running the test</span></div><div> <span style="color: #9cdcfe;">get_servers_gauge</span> <span style="color: #d4d4d4;">=</span> <span style="color: #9cdcfe;">meter</span>.<span style="color: #dcdcaa;">create_gauge</span>(</div><div> <span style="color: #9cdcfe;">name</span><span style="color: #d4d4d4;">=</span><span style="color: #ce9178;">"ST_Servers_Time"</span>,</div><div> <span style="color: #9cdcfe;">unit</span><span style="color: #d4d4d4;">=</span><span style="color: #ce9178;">"ms"</span>,</div><div> <span style="color: #9cdcfe;">description</span><span style="color: #d4d4d4;">=</span><span style="color: #ce9178;">"Amount of time it took to get_servers()"</span>,</div><div> )</div><div><br /></div><div></div></div></div><div><br /></div><h3 style="text-align: left;">Recording a value in a gauge without a callback</h3><div>The gauge records server acquisition time. This program runs, measures, records, and exists. We imperatively set the gauge so we don't have to hang around waiting for callbacks.</div><div><br /></div><div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div><br /></div><div> <span style="color: #9cdcfe;">get_servers_gauge</span>.<span style="color: #dcdcaa;">set</span>(</div><div> <span style="color: #9cdcfe;">amount</span><span style="color: #d4d4d4;">=</span><span style="color: #dcdcaa;">round</span>(<span style="color: #9cdcfe;">number</span><span style="color: #d4d4d4;">=</span><span style="color: #4ec9b0;">float</span>(<span style="color: #9cdcfe;">json_data</span>[<span style="color: #ce9178;">"get_servers"</span>]), <span style="color: #9cdcfe;">ndigits</span><span style="color: #d4d4d4;">=</span><span style="color: #b5cea8;">3</span>),</div><div> <span style="color: #9cdcfe;">attributes</span><span style="color: #d4d4d4;">=</span><span style="color: #9cdcfe;">run_attributes</span>,</div><div> )</div><div><br /></div><div></div></div></div><div><br /></div><h2 style="text-align: left;">Generating Traces</h2><div>A single run involved 3 to 5 steps. I wanted to capture the top-level span as a trace with 3-5 sub-spans so I could graphically see how much time was in each stage.</div><h3 style="text-align: left;">Creating a Tracer named after this program</h3><div>This creates the root trace with the same name as this program.</div><div><br /></div><div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div><span style="color: #6a9955;"><br /></span></div><div><span style="color: #6a9955;"> # Returns an OpenTelemetry Tracer that is bound to Azure Application Insights</span></div><div><span style="color: #569cd6;"> def</span> <span style="color: #dcdcaa;">create_ot_tracer</span>() -> <span style="color: #4ec9b0;">Tracer</span>:</div><div> <span style="color: #6a9955;"># Get a tracer for the current module.</span></div><div> <span style="color: #9cdcfe;">tracer</span> <span style="color: #d4d4d4;">=</span> <span style="color: #4ec9b0;">trace</span>.<span style="color: #dcdcaa;">get_tracer</span>(<span style="color: #9cdcfe;">__name__</span>)</div><div> <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">tracer</span></div><div><span style="color: #9cdcfe;"><br /></span></div></div></div><div><br /></div><h3 style="text-align: left;">Generating a Custom Trace</h3><div>Here we record a nested tracer span. The outer span time will include the entire runtime for all the spans inside of it. This code shows 1 outer span with 4-5 inner spans</div><div><br /></div>
<div>
<div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><br /><div style="font-family: Consolas, "Courier New", monospace; font-size: 14px;"><span style="color: #6a9955;"> # Nested Tracing spans will be children to this one</span></div><div> <span style="color: #c586c0;">with</span> <span style="color: #9cdcfe;">tracer</span>.<span style="color: #dcdcaa;">start_as_current_span</span>(<span style="color: #9cdcfe;">name</span><span style="color: #d4d4d4;">=</span><span style="color: #ce9178;">"main"</span>):</div><div> <span style="color: #6a9955;"># getting the servers does a ping</span></div><div> <span style="color: #9cdcfe;">s</span> <span style="color: #d4d4d4;">=</span> <span style="color: #4ec9b0;">speedtest</span>.<span style="color: #4ec9b0;">Speedtest</span>(<span style="color: #9cdcfe;">secure</span><span style="color: #d4d4d4;">=</span><span style="color: #b5cea8;">1</span>)</div><div> <span style="color: #c586c0;">with</span> <span style="color: #9cdcfe;">tracer</span>.<span style="color: #dcdcaa;">start_as_current_span</span>(<span style="color: #9cdcfe;">name</span><span style="color: #d4d4d4;">=</span><span style="color: #ce9178;">"get_servers"</span>):</div><div> <span style="color: #9cdcfe;">retrieved_servers</span> <span style="color: #d4d4d4;">=</span> <span style="color: #9cdcfe;">s</span>.<span style="color: #dcdcaa;">get_servers</span>(<span style="color: #9cdcfe;">servers</span><span style="color: #d4d4d4;">=</span><span style="color: #9cdcfe;">servers</span>)</div><div> <span style="color: #c586c0;">with</span> <span style="color: #9cdcfe;">tracer</span>.<span style="color: #dcdcaa;">start_as_current_span</span>(<span style="color: #9cdcfe;">name</span><span style="color: #d4d4d4;">=</span><span style="color: #ce9178;">"get_best_servers"</span>):</div><div> <span style="color: #9cdcfe;">retrieved_best_server</span> <span style="color: #d4d4d4;">=</span> <span style="color: #9cdcfe;">s</span>.<span style="color: #dcdcaa;">get_best_server</span>(<span style="color: #9cdcfe;">servers</span><span style="color: #d4d4d4;">=</span><span style="color: #9cdcfe;">servers</span>)</div><div> <span style="color: #c586c0;">with</span> <span style="color: #9cdcfe;">tracer</span>.<span style="color: #dcdcaa;">start_as_current_span</span>(<span style="color: #9cdcfe;">name</span><span style="color: #d4d4d4;">=</span><span style="color: #ce9178;">"measure_download"</span>):</div><div> <span style="color: #9cdcfe;">s</span>.<span style="color: #dcdcaa;">download</span>(<span style="color: #9cdcfe;">threads</span><span style="color: #d4d4d4;">=</span><span style="color: #9cdcfe;">threads</span>)</div><div> <span style="color: #c586c0;">with</span> <span style="color: #9cdcfe;">tracer</span>.<span style="color: #dcdcaa;">start_as_current_span</span>(<span style="color: #9cdcfe;">name</span><span style="color: #d4d4d4;">=</span><span style="color: #ce9178;">"measure_upload"</span>):</div><div> <span style="color: #9cdcfe;">s</span>.<span style="color: #dcdcaa;">upload</span>(<span style="color: #9cdcfe;">threads</span><span style="color: #d4d4d4;">=</span><span style="color: #9cdcfe;">threads</span>)</div><div> <span style="color: #c586c0;">if</span> <span style="color: #9cdcfe;">should_share</span>:</div><div> <span style="color: #c586c0;">with</span> <span style="color: #9cdcfe;">tracer</span>.<span style="color: #dcdcaa;">start_as_current_span</span>(<span style="color: #9cdcfe;">name</span><span style="color: #d4d4d4;">=</span><span style="color: #ce9178;">"sharing_is_caring"</span>):</div><div> <span style="color: #9cdcfe;">s</span>.<span style="color: #9cdcfe;">results</span>.<span style="color: #dcdcaa;">share</span>()</div><br /></div></div><div><br /></div><div><br /></div>
<h3 style="text-align: left;">Speedtest application flow</h3><div>The program captures metrics and traces for a network measurement application. This flow is described in the docs on GitHub</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEir0TIvfRQPEUjg5LMQsbmDTVm6g39A4cAfuHWfMIa5fASZGBuDKnpkWvLLQCuTUv7zrXR7VrBBYkCka-nljqaJxVc77auig0NCWVCv_j1oKA9Q6x2pIgGIs11WxRJHz1R089NkwRcIzqgM-FKG355BpTgdj8mppkO0Th57EfoAkqDFkvl8hTKfcxw6jiWQ" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="601" data-original-width="701" height="549" src="https://blogger.googleusercontent.com/img/a/AVvXsEir0TIvfRQPEUjg5LMQsbmDTVm6g39A4cAfuHWfMIa5fASZGBuDKnpkWvLLQCuTUv7zrXR7VrBBYkCka-nljqaJxVc77auig0NCWVCv_j1oKA9Q6x2pIgGIs11WxRJHz1R089NkwRcIzqgM-FKG355BpTgdj8mppkO0Th57EfoAkqDFkvl8hTKfcxw6jiWQ=w640-h549" width="640" /></a></div><br /> </div><div></div><h2 style="text-align: left;">Video</h2>
<div>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/34np53BGv3o" width="640"></iframe>
</div>
<div>
<h2 style="text-align: left;">GitHub</h2></div><div>This sample code comes from <a href="https://github.com/freemansoft/speedtest-app-insights" target="_blank">freemansoft speedtest-app-insights github repo</a></div><h2>References</h2><div>The primary references used in converting this app from OpenCensus to Open Telemetry</div><div><br /></div>Azure Monitor, Python and OpenTelemtry<br /><ul style="text-align: left;"><li>https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-enable?tabs=python</li><li>https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-python-opencensus-migrate?tabs=aspnetcore</li><li>https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-add-modify</li><li>https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-configuration</li></ul><div>Azure Monitor OpenTelemetry SDK for Python<br /><ul></ul></div><ul style="text-align: left;"><li>https://learn.microsoft.com/en-us/python/api/overview/azure/monitor-opentelemetry-exporter-readme?view=azure-python-preview</li></ul>OpenTelmetry and Python<br /><ul style="text-align: left;"><li>https://opentelemetry-python.readthedocs.io/en/latest/sdk/environment_variables.html</li></ul><h3 style="text-align: left;">Revision History</h3><div>Created 2024 02</div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-13135981698128026822024-02-22T21:09:00.009-05:002024-02-23T19:49:43.995-05:00T-shirt sizing - Estimating workload and capacity without false precision<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhMGqAQ1pJFj4lLxQLPTD2Bv1p6OhTyjuDJkCPACRBnR3dlw1TXVVcANJMpN60wfCs76RMDKnUGo3FXR-xWPbl_o8WBF2utqCVQ6uE2mhfSnGkks0Lu4B_dLDyhy4FlzcFOvHsH5CkhmKdXljdQpGQXuRGO32r6sgRRhAbV1pXHez-RDvQ60UF-RvLj1XSH" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img alt="" data-original-height="1041" data-original-width="636" height="640" src="https://blogger.googleusercontent.com/img/a/AVvXsEhMGqAQ1pJFj4lLxQLPTD2Bv1p6OhTyjuDJkCPACRBnR3dlw1TXVVcANJMpN60wfCs76RMDKnUGo3FXR-xWPbl_o8WBF2utqCVQ6uE2mhfSnGkks0Lu4B_dLDyhy4FlzcFOvHsH5CkhmKdXljdQpGQXuRGO32r6sgRRhAbV1pXHez-RDvQ60UF-RvLj1XSH=w390-h640" width="390" /></a></div><p>T-shirt sizing is a great way of estimating the relative size or level of effort of a set of tasks. Effort and cost discussions are significantly easier when decoupled from hours and dollars. It lets people focus on the problem definition and possible approaches without getting hung up on absolute values.</p><ol style="text-align: left;"><li>Size some work items relative to each other. "This one feels maybe twice the size of this other one". </li><li>Bin the sorted items in arbitrary groups (T-Shirt sizes) Small, Medium, Large, etc. based on the amount of work or complexity. </li><li>New items are sized the same way when they are first introduced. They are compared to the existing backlog and slotted into the buckets.</li></ol>This process gives you a feel for work and relative complexity without resorting to false precision for specific days and hours. Velocity and Capacity can be extrapolated later after tasks of various sizes are completed. Start with relative sizing and then iterate closer to absolute sizing as you know more.<br /><br />People struggle with sizing. Management wants to how much it will cost or how many people and days it will take. We want to provide a way of describing effort before we really know what a unit of work costs or hours they take. The two most common issues are<div><ol style="text-align: left;"><li>The desire for precise estimates before enough is known for those estimates to be real.</li><li> Confusion on whether the <i>size</i> represents complexity or the amount of work. It represents both. This is not precise. It can't be. Assume that complexity and scope combine in some way to create a single value that will eventually convert into hours and material. </li></ol></div><div><div class="separator" style="clear: both;"><h2 style="text-align: left;">Plan, Do, Measure, Adjust</h2><div>You'll have to work out the size ranges for Small Medium and Large and then possibly adjust them. I tend to use <a href="https://www.mountaingoatsoftware.com/blog/why-the-fibonacci-sequence-works-well-for-estimating">Planning Poker style Fibbonici sizing</a> for bin sizes. In other situations, just the notion of size being <i>a couple times larger</i> than the previous bucket is good enough.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEie5I4XIQz0cAy-5FJI02sGxmq0I56LQDmivoCY5WNeivV8xRWhljk014G__t4rEZgErs54fb-EBAQUkh4KH-Vj8f_HBPoEVePYeFE9MJMA-dGacL0IxvwCQSZXZF2GM7urOulhCj01rljkevCrxEIgX3w2AVgK4S7WgYZc7c12mQ2TnrWyEheiLD6NdJfQ/s698/t-shirt-sizing.drawio.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="426" data-original-width="698" height="390" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEie5I4XIQz0cAy-5FJI02sGxmq0I56LQDmivoCY5WNeivV8xRWhljk014G__t4rEZgErs54fb-EBAQUkh4KH-Vj8f_HBPoEVePYeFE9MJMA-dGacL0IxvwCQSZXZF2GM7urOulhCj01rljkevCrxEIgX3w2AVgK4S7WgYZc7c12mQ2TnrWyEheiLD6NdJfQ/w640-h390/t-shirt-sizing.drawio.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><h3 style="text-align: left;">Links</h3><div>You can find Agile sizing techniques via any search engine. Wikipedia has a <a href="https://en.wikipedia.org/wiki/Software_sizing" target="_blank">general article on software sizing</a>.</div><div><br /></div><h3 style="text-align: left;">Revision History</h3><div style="text-align: left;">Created 2024 02</div></div><br /><p></p></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-58315187985595696882024-02-18T09:54:00.001-05:002024-02-18T09:58:33.612-05:00NVIDIA Broadcast - Eye Contact - No more looking down in videos<p>NVIDIA added a new beta feature to NVIDIA Broadcast that manipulates the speaker's eye position to make it appear as if they are looking at the camera instead of the prompt or contact. It isn't perfect but it really helps. You can see the effect in the two pictures below.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHjMDEOrKE2jzQAGCFRR55JhLcjjFDRNtiFBY3YkaUDRN92SP25lQIhGGgCBsMb10toCwKLLZHKd7wF6dZoE0RZuvjE1zf5Q4Vee6Z_AgkTzWRaADCcJuEHo2jgNR4Jyy-6DuqdXkqN5rtvXyQar5F6alRL2nBjDZvOJOLmYH6YMOdyM10jcO-JciaNNw2/s1120/NVIDIA%20Eye%20Contact.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="768" data-original-width="1120" height="438" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHjMDEOrKE2jzQAGCFRR55JhLcjjFDRNtiFBY3YkaUDRN92SP25lQIhGGgCBsMb10toCwKLLZHKd7wF6dZoE0RZuvjE1zf5Q4Vee6Z_AgkTzWRaADCcJuEHo2jgNR4Jyy-6DuqdXkqN5rtvXyQar5F6alRL2nBjDZvOJOLmYH6YMOdyM10jcO-JciaNNw2/w640-h438/NVIDIA%20Eye%20Contact.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div>This has been around for a while but I never noticed. NVIDIA added support in Jan 2023. Camtasia added support for NVidia Broadcast in Camtasia 2022 update 2 or 3.<br />
<h2 style="text-align: left;">Virtual Camera works with other programs like Camtasia</h2>
<div>The NVIDIA broadcast acts as a virtual camera that can be used in other programs like Camtasia 2022. You'll want the latest version of Camtasia</div>
<div><br /></div>
<div><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/DDHERSEb_CU" width="640"></iframe>
</div>
<div><br /></div>
<h3 style="text-align: left;">Revision History</h3><div>Created 2024 02</div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-90509470574247050462024-02-18T08:32:00.007-05:002024-02-18T09:21:23.314-05:00Want to run a local LLM? How much memory does your NVIDIA RTX have?<p>Want to run LLMs locally? The new NVIDIA <a href="https://www.nvidia.com/en-us/ai-on-rtx/chat-with-rtx-generative-ai/">Chat with RTX</a> requires 8GB of VRAM. Other language models require even more. </p><p>You can use the Windows utility '<b>dxdiag</b>' to see how much memory your NVIDIA RTX card has.</p><p></p><ol style="text-align: left;"><li><i>Windows-R </i>or click on the Windows key and type in the search bar.</li><li>Enter <i>dxdiag</i></li><li>Click on <i>Display </i>or in my case <i>Display 1</i></li></ol><div>We're looking for the amount of <b>Display Memory (VRAM)</b> I have 12,086MB or 12GB so I'm good to go.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH4zM1Xy8QqZKDabsjOsKGG-XGHqW9oVKatYDNf8O1vPXO_QSDvVuXUyWFMfybXq0lRkY0CcIf8-ZDVRm8zbXpzHm1liXGtnMzNCkDjmsysSDILxMjBX-M2YXj_5L8qfmtZMHBXBl9FR_kvWI9uiWHE8iFgUJr4yTL6mcnnyClzxxsfV4enGLTGZ2NdgJ7/s385/dxdiag-display1-vram.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="385" data-original-width="367" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH4zM1Xy8QqZKDabsjOsKGG-XGHqW9oVKatYDNf8O1vPXO_QSDvVuXUyWFMfybXq0lRkY0CcIf8-ZDVRm8zbXpzHm1liXGtnMzNCkDjmsysSDILxMjBX-M2YXj_5L8qfmtZMHBXBl9FR_kvWI9uiWHE8iFgUJr4yTL6mcnnyClzxxsfV4enGLTGZ2NdgJ7/s16000/dxdiag-display1-vram.png" /></a></div><br /><div><p>Machine Learning models suddenly made me care how much VRAM my NVIDIA graphics card has. I can never remember the exact model let alone how much VRAM it has. Now I have this handy article to remind me how to find the numbers I need.</p>
<h2 style="text-align: left;">Vido</h2>
<div><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/sq_QOkwraPE" width="640"></iframe>
</div>
<h3 style="text-align: left;">Revision History</h3>
</div><div>2024 02 created</div><div><br /></div><p></p>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-85385934666498039682024-02-10T19:19:00.011-05:002024-02-10T21:37:40.659-05:00Supporting command line arguments in Dart programs<div style="text-align: left;">Dart programs, really all programs, are either hardcoded against certain targets and configurations or they configurable at startup. Not sure about you but the word "hardcoded" always sounds bad. </div><div style="text-align: left;"><br /></div><div style="text-align: left;">The most common way of providing configuration information is probably:</div><div style="text-align: left;"><ul><li>Command-line arguments</li><li>Environmental variables</li><li>Properties files or other accessible storage</li><li>Properties services</li></ul><div>The approach you pick depends on the nature ephemeral nature of the program, the environment you run in, and if the configuration can change <i>while the program is running</i>. Sometimes you use a combination, one configuration method at bootstrap, and then a different one as the program continues running.</div><div><br /></div><div>In all command-style programs, I've found that I need some way to bootstrap the process. It almost always has command-line arguments or environmental variables. Environment variables are great but can be a bit opaque depending on how the program is run. My go-to is to include run-time argument support often seen as command-line arguments.</div></div><h2 style="text-align: left;">Command-line Arguments?</h2><div>Command-line arguments are passed as part of the run commands. The arguments below consist of an argument name and an argument value.</div><div><br /></div><div><div style="font-family: Consolas, "Courier New", monospace; font-size: 14px; white-space: pre;"><div style="background-color: #1f1f1f; line-height: 19px;"><div style="color: #cccccc;"><span style="color: #cccccc;"><br /></span></div><div style="color: #cccccc;"><span> dart</span><span style="color: #cccccc;"> </span><span>run</span><span style="color: #cccccc;"> </span><span>example</span><span style="color: #cccccc;">.</span><span>dart</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">--address localhost --port 8080 --targetAddress HTTP://pub.dev </span></div><span style="color: #cccccc;"><br /></span></div></div></div><div><br /></div><div>Command-line arguments are values made directly available to a program, often as a generic list of strings passed into the main() program. They extend the utility of your dart programs letting you change their behavior without having to commit new code. A lot of my Dart command line programs are utilities. They have always needed some configuration information. Initially, we just hard-coded the program to "get it done". Then we come back and need to make changes for some similar tasks. So we either fork a copy or do a little work to parameter drive the behavior, remote addresses, file names, logging levels, or other settings. </div><div><br /></div><div>Command-line arguments also provide a framework for identifying the configuration parameters that are used by your program and pulling those to one location. It is great that the Dart team provides a simple package for defining arguments, their types, and their defaults. </div><div><br /></div><div><h2>Command-line Flags?</h2><div>Command-line flags are a variant of the parameterized argument where the presence of the flag has meaning without any supplemental value.</div><div><br /></div><div><div style="font-family: Consolas, "Courier New", monospace; font-size: 14px; white-space: pre;"><div style="background-color: #1f1f1f; line-height: 19px;"><div style="color: #cccccc;"><span style="color: #cccccc;"><br /></span></div><div style="color: #cccccc;"><span> dart</span><span style="color: #cccccc;"> </span><span>run</span><span style="color: #cccccc;"> </span><span>example</span><span style="color: #cccccc;">.</span><span>dart</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">--verbose --ignore-certs --no-cors</span></div><span style="color: #cccccc;"><br /></span></div></div></div><div><br /></div><div>Command-line flags are made directly available to the program as a type of argument. A sample is included below.</div></div><h2 style="text-align: left;">Dart Args add polish </h2><div>The example program has three command line arguments that do <b>not include the --help option</b>. I implemented --help as a command line option in my programs but didn't want to add another item to this example. The command run here is for a Dart-based proxy server that listens on a port and forwards all traffic to a target address (site).</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit0quD2ZNl1S8XAjOFFXoxUOrTfkLqKHnDPyukD3qUgU9B_5gO9uvuDLJI_mlFSaUEkk5OzRdnhSwGR9SePDrp28pQqtljNBk10ZXKsmG1H8cusePM9eCBacgGVQVB07O07lTq_UfBNbD3e-LaF3P5i2Pml_e2CXqbZER1tCh9237ZNA_YzfGgeVQcGx-R/s801/CommandLine-Options-Proxy.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="181" data-original-width="801" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit0quD2ZNl1S8XAjOFFXoxUOrTfkLqKHnDPyukD3qUgU9B_5gO9uvuDLJI_mlFSaUEkk5OzRdnhSwGR9SePDrp28pQqtljNBk10ZXKsmG1H8cusePM9eCBacgGVQVB07O07lTq_UfBNbD3e-LaF3P5i2Pml_e2CXqbZER1tCh9237ZNA_YzfGgeVQcGx-R/s16000/CommandLine-Options-Proxy.drawio.png" /></a></div><div><br /></div><div>Here I ran the program and provided an unsupported argument. The Dart Args package fallback behavior lists all the command line options if an unrecognized one is provided. It is a lazy programmer's implementation of help. </div><div><br /></div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div><span style="color: #d4d4d4;">PS</span> <span style="color: #9cdcfe;">C</span><span style="color: #d4d4d4;">:</span>\<span style="color: #9cdcfe;">Users</span>\<span style="color: #9cdcfe;">joe\...\shelf_proxy</span><span style="color: #d4d4d4;">></span> <span style="color: #9cdcfe;">dart</span> <span style="color: #dcdcaa;">run</span> .\<span style="color: #dcdcaa;">example</span>.<span style="color: #9cdcfe;">dart</span> <span style="color: #d4d4d4;">--</span><span style="color: #d4d4d4;">help</span></div><div><span style="color: #9cdcfe;">Could</span> <span style="color: #d4d4d4;">not</span> <span style="color: #9cdcfe;">find</span> <span style="color: #d4d4d4;">an</span> <span style="color: #9cdcfe;">option</span> <span style="color: #9cdcfe;">named</span> <span style="color: #ce9178;">"help"</span>.</div><div><span style="color: #d4d4d4;">-</span><span style="color: #9cdcfe;">p</span>, <span style="color: #d4d4d4;">--</span><span style="color: #d4d4d4;">port</span> <span style="color: #9cdcfe;">Port</span> <span style="color: #d4d4d4;">to</span> <span style="color: #9cdcfe;">listen</span> <span style="color: #dcdcaa;">on</span></div><div> (<span style="color: #d4d4d4;">defaults</span> <span style="color: #9cdcfe;">to</span> <span style="color: #ce9178;">"8080"</span>)</div><div><span style="color: #d4d4d4;">-</span><span style="color: #9cdcfe;">a</span>, <span style="color: #d4d4d4;">--</span><span style="color: #d4d4d4;">address</span> <span style="color: #9cdcfe;">Address</span> <span style="color: #d4d4d4;">to</span> <span style="color: #9cdcfe;">listen</span> <span style="color: #dcdcaa;">on</span></div><div> (<span style="color: #d4d4d4;">defaults</span> <span style="color: #9cdcfe;">to</span> <span style="color: #ce9178;">"localhost"</span>)</div><div><span style="color: #d4d4d4;">-</span><span style="color: #9cdcfe;">t</span>, <span style="color: #d4d4d4;">--</span><span style="color: #d4d4d4;">targetAddress</span> <span style="color: #9cdcfe;">Address</span> <span style="color: #d4d4d4;">proxying</span> <span style="color: #dcdcaa;">for</span></div><div> (<span style="color: #d4d4d4;">defaults</span> <span style="color: #9cdcfe;">to</span> <span style="color: #ce9178;">"https://dart.dev"</span>)</div></div><p>This program is a proxy server that accepts HTTP requests on an <i>address</i> and <i>port</i> and forwards those to <i>targetAddress</i>. The Args implementation below lets the caller override those values while providing the original default values, the values that were previously hard coded.</p><h2 style="text-align: left;">Shelf Proxy Server example.dart</h2><div>This is a simple example taken from the Dart team's shelf project. The enhanced argument handling here has been submitted in a PR and so may not be visible in the official shelf repository.</div><div><br /></div><div style="background-color: #1f1f1f; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div style="color: #cccccc;"><span style="color: #6a9955;">// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file</span></div><div style="color: #cccccc;"><span style="color: #6a9955;">// for details. All rights reserved. Use of this source code is governed by a</span></div><div style="color: #cccccc;"><span style="color: #6a9955;">// BSD-style license that can be found in the LICENSE file.</span></div><span style="color: #cccccc;"><br /></span><div style="color: #cccccc;"><span style="color: #569cd6;">import</span> <span style="color: #ce9178;">'dart:io'</span>;</div><span style="color: #cccccc;"><br /></span><div style="color: #cccccc;"><span style="color: #569cd6;">import</span> <span style="color: #ce9178;">'package:args/args.dart'</span>;</div><div style="color: #cccccc;"><span style="color: #569cd6;">import</span> <span style="color: #ce9178;">'package:shelf/shelf_io.dart'</span> <span style="color: #569cd6;">as</span> <span style="color: #9cdcfe;">shelf_io</span>;</div><div style="color: #cccccc;"><span style="color: #569cd6;">import</span> <span style="color: #ce9178;">'package:shelf_proxy/shelf_proxy.dart'</span>;</div><span style="color: #cccccc;"><br /></span><div style="color: #cccccc;"><span style="color: #4ec9b0;">Future</span><<span style="color: #569cd6;">void</span>> <span style="color: #dcdcaa;">main</span>(<span style="color: #4ec9b0;">List</span><<span style="color: #4ec9b0;">String</span>> <span style="color: #9cdcfe;">args</span>) <span style="color: #c586c0;">async</span> {</div><div style="color: #cccccc;"><span style="color: #6a9955;"> // Create our configured parser</span></div><div style="color: #cccccc;"> <span style="color: #569cd6;">final</span> <span style="color: #9cdcfe;">parser</span> <span style="color: #d4d4d4;">=</span> <span style="color: #dcdcaa;">_getParser</span>();</div><span style="color: #cccccc;"><br /></span><div style="color: #cccccc;"><span style="color: #6a9955;"> // Where our arguments end up</span></div><div style="color: #cccccc;"><span style="color: #6a9955;"> // Defaults are provided provided to Args at configuration time</span></div><div style="color: #cccccc;"> <span style="color: #4ec9b0;">String</span> <span style="color: #9cdcfe;">address</span>;</div><div style="color: #cccccc;"> <span style="color: #4ec9b0;">int</span> <span style="color: #9cdcfe;">port</span>;</div><div style="color: #cccccc;"> <span style="color: #4ec9b0;">String</span> <span style="color: #9cdcfe;">targetAddress</span>;</div><div style="color: #cccccc;"><br /></div><div style="color: #cccccc;"><span style="color: #6a9955;"> // Ingest the parameters converting them to the correct type.</span></div><div><span style="color: #6a9955;"> // Exit with an error return code if they fail to parse</span></div><div style="color: #cccccc;"> <span style="color: #c586c0;">try</span> {</div><div style="color: #cccccc;"> <span style="color: #569cd6;">final</span> <span style="color: #9cdcfe;">result</span> <span style="color: #d4d4d4;">=</span> <span style="color: #9cdcfe;">parser</span>.<span style="color: #dcdcaa;">parse</span>(<span style="color: #9cdcfe;">args</span>);</div><div style="color: #cccccc;"> <span style="color: #9cdcfe;">address</span> <span style="color: #d4d4d4;">=</span> <span style="color: #9cdcfe;">result</span>[<span style="color: #ce9178;">'address'</span>] <span style="color: #569cd6;">as</span> <span style="color: #4ec9b0;">String</span>;</div><div style="color: #cccccc;"> <span style="color: #9cdcfe;">port</span> <span style="color: #d4d4d4;">=</span> <span style="color: #4ec9b0;">int</span>.<span style="color: #dcdcaa;">parse</span>(<span style="color: #9cdcfe;">result</span>[<span style="color: #ce9178;">'port'</span>] <span style="color: #569cd6;">as</span> <span style="color: #4ec9b0;">String</span>);</div><div style="color: #cccccc;"> <span style="color: #9cdcfe;">targetAddress</span> <span style="color: #d4d4d4;">=</span> <span style="color: #9cdcfe;">result</span>[<span style="color: #ce9178;">'targetAddress'</span>] <span style="color: #569cd6;">as</span> <span style="color: #4ec9b0;">String</span>;</div><div style="color: #cccccc;"> } <span style="color: #c586c0;">on</span> <span style="color: #4ec9b0;">FormatException</span> <span style="color: #c586c0;">catch</span> (<span style="color: #9cdcfe;">e</span>) {</div><div style="color: #cccccc;"> <span style="color: #9cdcfe;">stderr</span></div><div style="color: #cccccc;"> ..<span style="color: #dcdcaa;">writeln</span>(<span style="color: #9cdcfe;">e</span>.<span style="color: #9cdcfe;">message</span>)</div><div style="color: #cccccc;"> ..<span style="color: #dcdcaa;">writeln</span>(<span style="color: #9cdcfe;">parser</span>.<span style="color: #9cdcfe;">usage</span>);</div><div style="color: #cccccc;"> <span style="color: #dcdcaa;">exit</span>(<span style="color: #b5cea8;">64</span>);</div><div style="color: #cccccc;"> }</div><span style="color: #cccccc;"><br /></span><span style="color: #6a9955;"> // Use the provided or default parameters.</span><div style="color: #cccccc;"> <span style="color: #569cd6;">final</span> <span style="color: #9cdcfe;">server</span> <span style="color: #d4d4d4;">=</span> <span style="color: #c586c0;">await</span> <span style="color: #9cdcfe;">shelf_io</span>.<span style="color: #dcdcaa;">serve</span>(</div><div style="color: #cccccc;"> <span style="color: #dcdcaa;">proxyHandler</span>(<span style="color: #9cdcfe;">targetAddress</span>),</div><div style="color: #cccccc;"> <span style="color: #9cdcfe;">address</span>,</div><div style="color: #cccccc;"> <span style="color: #9cdcfe;">port</span>,</div><div style="color: #cccccc;"> );</div><span style="color: #cccccc;"><br /></span><div style="color: #cccccc;"> <span style="color: #dcdcaa;">print</span>(</div><div style="color: #cccccc;"> <span style="color: #ce9178;">'Proxying for </span><span style="color: #d4d4d4;">$</span><span style="color: #9cdcfe;">targetAddress</span><span style="color: #ce9178;"> at http://</span><span style="color: #d4d4d4;">${</span><span style="color: #9cdcfe;">server</span><span style="color: #d4d4d4;">.</span><span style="color: #9cdcfe;">address</span><span style="color: #d4d4d4;">.</span><span style="color: #9cdcfe;">host</span><span style="color: #d4d4d4;">}</span><span style="color: #ce9178;">:</span><span style="color: #d4d4d4;">${</span><span style="color: #9cdcfe;">server</span><span style="color: #d4d4d4;">.</span><span style="color: #9cdcfe;">port</span><span style="color: #d4d4d4;">}</span><span style="color: #ce9178;">'</span>);</div><div style="color: #cccccc;">}</div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><br /></div><span style="color: #6a9955;">// Configure the ArgParser with our command line arguments.</span></div><div style="background-color: #1f1f1f; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><span style="color: #6a9955;">// Note that all of these have default values and help<br /></span><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><span style="color: #4ec9b0;">ArgParser</span> <span style="color: #dcdcaa;">_getParser</span>() <span style="color: #d4d4d4;">=></span> <span style="color: #4ec9b0;">ArgParser</span>()</div><div style="color: #cccccc;"> ..<span style="color: #dcdcaa;">addOption</span>(<span style="color: #ce9178;">'port'</span>, <span style="color: #9cdcfe;">abbr</span><span style="color: #d4d4d4;">:</span> <span style="color: #ce9178;">'p'</span>, <span style="color: #9cdcfe;">defaultsTo</span><span style="color: #d4d4d4;">:</span> <span style="color: #ce9178;">'8080'</span>, <span style="color: #9cdcfe;">help</span><span style="color: #d4d4d4;">:</span> <span style="color: #ce9178;">'Port to listen on'</span>)</div><div style="color: #cccccc;"> ..<span style="color: #dcdcaa;">addOption</span>(<span style="color: #ce9178;">'address'</span>,</div><div style="color: #cccccc;"> <span style="color: #9cdcfe;">abbr</span><span style="color: #d4d4d4;">:</span> <span style="color: #ce9178;">'a'</span>, <span style="color: #9cdcfe;">defaultsTo</span><span style="color: #d4d4d4;">:</span> <span style="color: #ce9178;">'localhost'</span>, <span style="color: #9cdcfe;">help</span><span style="color: #d4d4d4;">:</span> <span style="color: #ce9178;">'Address to listen on'</span>)</div><div style="color: #cccccc;"> ..<span style="color: #dcdcaa;">addOption</span>(<span style="color: #ce9178;">'targetAddress'</span>,</div><div style="color: #cccccc;"> <span style="color: #9cdcfe;">abbr</span><span style="color: #d4d4d4;">:</span> <span style="color: #ce9178;">'t'</span>, <span style="color: #9cdcfe;">defaultsTo</span><span style="color: #d4d4d4;">:</span> <span style="color: #ce9178;">'https://dart.dev'</span>, <span style="color: #9cdcfe;">help</span><span style="color: #d4d4d4;">:</span> <span style="color: #ce9178;">'Address proxying for'</span>);</div><span style="color: #cccccc;"><br /></span></div>
<div><br /></div><h2 style="text-align: left;">Code Reviewers</h2><div>Consider rejecting all Pull Requests that include command-line programs with hard-coded parameters that cannot be overridden by environment variables or command-line arguments.</div><div><br /></div>
<h2 style="text-align: left;">Video</h2>
<div>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/RLN66HAg12o" width="640"></iframe>
</div>
<h3 style="text-align: left;">Revision History </h3><div>Created 2024 02</div><div><br /></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-36466927123444866322024-02-06T22:27:00.025-05:002024-02-07T20:19:20.803-05:00Creating Service Accounts for programmatic access to Google Drive APIs<p><span style="font-family: verdana;">Google Drive is one of those cloud technologies that democratized cloud access to data storage. It lets you securely push all kinds of data into and out of the cloud via Google-provided APIs. APIs and documents are bound to permissions, roles, and identities. Programs accessing Google Docs require credentials, preferably least-privilege credentials, which exist just for a single program's needs. Google IAM supports <b>Service Accounts</b> that are <b>not tied to any human</b>. They can be enabled and disabled without impacting individual users. </span></p><p><span style="font-family: verdana;">Accessing Google Docs via API means you have to enable <b>Google Drive API</b> in a project, create an identity/credentials for the program, and then give the identity access to the docs or the API. There are plenty of good tutorials that walk you through setting up an account. They are often light on the overall process or how the steps tie together. It can be confusing the first time or 10 you go through it.</span></p><ol style="text-align: left;"><li><span style="font-family: verdana;">Google services are enabled on a per-project basis. This lets you isolate access and capabilities through judicious project management. Projects are sometimes referred to as domains in the user interface.</span></li><li><span style="font-family: verdana;">Credentials like Service Accounts, API Keys, or OAuth IDs exist within the scope of a<b> project</b> sometimes referred to as a <b>domain</b>. Credentials/Identities can exist in the root domain or any lower-level domain. Each of these identity types has their own advantages and disadvantages. I mention Service Accounts here because Pysheet and other Python libraries support JSON-based Service Account <b>keys </b>as identity credentials. </span></li><li><span style="font-family: verdana;"><b>Keys</b> provide a way of authenticating as a <b>Service Account </b>type of <b>Identity</b> when making a request. Google supports JSON and legacy P12 key formats. Libraries can associate these tokens with requests made to Google APIs.</span></li></ol><div><span style="font-family: verdana;">Those three tasks flow as follows. Start with the <a href="https://console.developers.google.com/" target="_blank">Google API Console</a> or the <a href="https://console.cloud.google.com/cloud-resource-manager" target="_blank">Google Cloud Resource Manager</a> I <a href="https://youtu.be/5UO_-_WZ7Fw" target="_blank">recorded a video</a> explaining this diagram.</span></div><div><span style="font-family: verdana;"><br /></span></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpOlxZKxyhO6uovBaxjQbxHmhQ-VuY7bxI5GMtANjgrlzvvaOBnogfe7AGyplRE9wUyTDnX_Pc2pYiszhRhUEqbi0Wq61jCEeiUo0cWm15xqMTlOULoH1cTEK9gy0sa_4-7IeFaS4hSaUNIEq7n2S0cwF4uxzsCpNDjTTstBZTxaxwajqj6Z0p3CAEMSAw/s752/Google-Service-Account-Creation.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="752" data-original-width="692" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpOlxZKxyhO6uovBaxjQbxHmhQ-VuY7bxI5GMtANjgrlzvvaOBnogfe7AGyplRE9wUyTDnX_Pc2pYiszhRhUEqbi0Wq61jCEeiUo0cWm15xqMTlOULoH1cTEK9gy0sa_4-7IeFaS4hSaUNIEq7n2S0cwF4uxzsCpNDjTTstBZTxaxwajqj6Z0p3CAEMSAw/s16000/Google-Service-Account-Creation.drawio.png" /></a></div></div><div><div class="separator" style="clear: both; text-align: center;"><br /></div></div><div class="separator" style="clear: both; text-align: left;"><span style="font-family: verdana;"><br /></span></div><div><div class="separator" style="clear: both; text-align: left;"><span style="font-family: verdana;">All that is left to do is to grant the Service Account Access to your Google Drive Document. A short explanation is in the section below.</span></div></div><h2 style="text-align: left;"><span style="font-family: verdana;">Domain/API/AIM/Account/Document Relationships</span></h2><span style="font-family: verdana;">I started with the step-by-step because people want to get stuff done. Now we can look at how it all comes together.</span><div><span style="font-family: verdana;"><br /></span><div class="separator" style="clear: both;"><span style="font-family: verdana;">The flow described above creates a Service Account that has a role that has access to APIs that were enabled within a project. The flow creates a JSON key that can be presented by a program to the APIs to authenticate as the Service Account. That key identifies the Service Account and is validated as both having access to APIs and having read-only or read-write permissions to a specific document.<br /><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><h2 style="text-align: left;"><span style="font-family: verdana;">Detailed Walkthrough</span></h2><div><span style="font-family: verdana;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQ7ypOplFvjUzkO-XZvjVkRNAcMErXM_kuRo9DIAPqxVbhHNZeG0QolIa0wM-Mq2rAm0nagNI8ndr4MX8Zg33xzsOLeCPPo6Bk8uzgkUzXMj8Ta7i5O5SP7ThKldeTrlahhUv1onuMb9lqPNL9ouoeDY79ckgw_dYL-4KrtWh8YDrOWbHkAnTSlSqIBY3W/s751/Google-API-Domain-AIM-Relationship.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="561" data-original-width="751" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQ7ypOplFvjUzkO-XZvjVkRNAcMErXM_kuRo9DIAPqxVbhHNZeG0QolIa0wM-Mq2rAm0nagNI8ndr4MX8Zg33xzsOLeCPPo6Bk8uzgkUzXMj8Ta7i5O5SP7ThKldeTrlahhUv1onuMb9lqPNL9ouoeDY79ckgw_dYL-4KrtWh8YDrOWbHkAnTSlSqIBY3W/s16000/Google-API-Domain-AIM-Relationship.drawio.png" /></a></div><br />A detailed, screen-by-screen, walkthrough will have to wait for another article. I used this setup guide <a href="https://erikrood.com/Posts/py_gsheets.html">by Erik Rood</a> targeted at <a href="https://github.com/nithinmurali/pygsheets">Python pygsheets</a> users when I set up my credentials.</span></div><h2 style="text-align: left;"><span style="font-family: verdana;">Example Domain / Project Hierarchy</span></h2><div><span style="font-family: verdana;">This shows a domain with an associated set of projects. Each project can have its own set of enabled Google services and domain-specific identities. My exploration project here is <i>My Project 2237</i></span></div><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEn8rkpxi7fWC1OEbw9r2bLi9r1XquP33rkgfTL3uSWyzVbfj09gKyV6KQy8R_FPXpL5Fl8Acsj22SpyipVEYDLQnfOvNA-wDINb0zOQ1rHUrwbl0IvDCxbIfdV2RRUtzv5iL9AOqVZhwfossKaKrI2KYByng-0Ug312YxSBU7WcPV88MudYJOstt2wyJY/s277/Google-Domain-And-Projects.png" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: verdana;"><img border="0" data-original-height="229" data-original-width="277" height="229" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEn8rkpxi7fWC1OEbw9r2bLi9r1XquP33rkgfTL3uSWyzVbfj09gKyV6KQy8R_FPXpL5Fl8Acsj22SpyipVEYDLQnfOvNA-wDINb0zOQ1rHUrwbl0IvDCxbIfdV2RRUtzv5iL9AOqVZhwfossKaKrI2KYByng-0Ug312YxSBU7WcPV88MudYJOstt2wyJY/s1600/Google-Domain-And-Projects.png" width="277" /></span></a></div><h2 style="clear: both; text-align: left;"><span style="font-family: verdana;">Granting access to an existing Google Drive document</span></h2><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-iEABOKadE3npoXPFtajmxbMXUFGuWKD_ONeEBFxGXeQe1zjWWgVyAMJs8QO7SejCaXSxNlAThsRn1r_7wEHbP9k5nVqSfGMQr5ltCzjMj6Q8kZYpQ2MNqu2IQGOC06darKW6NW4ziLU4TCtEtd6r48wxnVOJVLhxTwDn-GcT8zPDhNYh8vYztXfxJhVE/s520/Google-Grant-Access.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><span style="font-family: verdana;"><img border="0" data-original-height="476" data-original-width="520" height="293" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-iEABOKadE3npoXPFtajmxbMXUFGuWKD_ONeEBFxGXeQe1zjWWgVyAMJs8QO7SejCaXSxNlAThsRn1r_7wEHbP9k5nVqSfGMQr5ltCzjMj6Q8kZYpQ2MNqu2IQGOC06darKW6NW4ziLU4TCtEtd6r48wxnVOJVLhxTwDn-GcT8zPDhNYh8vYztXfxJhVE/s320/Google-Grant-Access.png" width="320" /></span></a></div></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><b>Service Accounts</b> have email addresses like a user account. This means you can grant access to individual Google Drive documents in the same way you grant to any human user. </span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg12kZ0TfaXQ8w26p0ONmckZhE9NFQMxGLD_aY5kldjGHqypD8t0bKI-2Z79KEyALQsP50f0QgciAFNiae9nHzWkAYuqchjpoi5i5kZgFxLUx8LwEE_RCzMGe74sVd53WC-XqvhMcnfOlOpEGU-Ux83Ezw3VJiQNo3jTs8lneofc0wEaXgSaX3cZ92tGY3O/s502/Google-Grant-Access-2.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><span style="font-family: verdana;"><br /></span></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg12kZ0TfaXQ8w26p0ONmckZhE9NFQMxGLD_aY5kldjGHqypD8t0bKI-2Z79KEyALQsP50f0QgciAFNiae9nHzWkAYuqchjpoi5i5kZgFxLUx8LwEE_RCzMGe74sVd53WC-XqvhMcnfOlOpEGU-Ux83Ezw3VJiQNo3jTs8lneofc0wEaXgSaX3cZ92tGY3O/s502/Google-Grant-Access-2.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><span style="font-family: verdana;"><img border="0" data-original-height="296" data-original-width="502" height="189" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg12kZ0TfaXQ8w26p0ONmckZhE9NFQMxGLD_aY5kldjGHqypD8t0bKI-2Z79KEyALQsP50f0QgciAFNiae9nHzWkAYuqchjpoi5i5kZgFxLUx8LwEE_RCzMGe74sVd53WC-XqvhMcnfOlOpEGU-Ux83Ezw3VJiQNo3jTs8lneofc0wEaXgSaX3cZ92tGY3O/s320/Google-Grant-Access-2.png" width="320" /></span></a></div></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;">Just add the <b>Service Account email address</b> to the list of users granted view or edit permissions on a document.</span></div><div><span style="font-family: verdana;"><br /></span></div><span style="font-family: verdana;"><br /><br /></span><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;"><br /></span></div><h2 style="text-align: left;"><span style="font-family: verdana;">Walking through </span></h2><h3 style="text-align: left;"><span style="font-family: verdana;">Enabling APIs, creating identities, and granting keys</span></h3>
Creating Google Identity and Credentials for Programmatic Access to Google Drive<br/>
<div>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/5UO_-_WZ7Fw" width="640"></iframe>
</div>
The moving parts when programs access Google drive APIs<br/>
<div>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/82N-H7iLeBk" width="640"></iframe>
</div>
<h3 style="text-align: left;"><span style="font-family: verdana;">Revision History</span></h3><div><span style="font-family: verdana;">Created 2024 02</span></div><div><span style="font-family: verdana;">Thanks to https://www.linkedin.com/in/kenny-freeman-denver/ for working on this with me.</span></div><p></p></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-5627145282738359872024-01-15T21:37:00.021-05:002024-01-15T21:44:09.488-05:00Flutter mobile - certificate and SSL exclusions for development domains<p><span face="Roboto, Noto, sans-serif" style="background-color: white; color: #0d0d0d; font-size: 15px; white-space-collapse: preserve;">Flutter mobile development gets twisty when working behind corporate firewalls and with self-signed development certificates. We look at a bit of code that can be used in debug builds that sets the emulator/simulator proxies and disables cert checks.</span></p><p> </p><h2 style="text-align: left;">Example Code</h2><div>See <a href="https://github.com/freemansoft/fs-dart-tools/tree/main/packages/network" target="_blank">fs-dart-tools on GitHub</a> for the latest versions of this code.</div><p>You can use this sample code wrapped in an `if (isKDebug)` when running Flutter on mobile. It lets you exclude your self-signed certificates for your internal domains while leaving certificate checks enabled for production and external domains. The code also sets the proxy when running in the Android Emulator or the iOS Simulator. Setting the proxy is optional, enabled when you pass in the proxy port. </p><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div><span style="color: #569cd6;"><br /></span></div><div><div style="line-height: 19px;"><div><span style="color: #569cd6;">import</span> <span style="color: #ce9178;">'dart:io'</span>;</div><br /><div><span style="color: #6a9955;">///</span></div><div><span style="color: #6a9955;">/// Ignores certificates in developermode for limited set of domains</span></div><div><span style="color: #6a9955;">/// Simplifies development in corporate self signed environment</span></div><div><span style="color: #6a9955;">/// Configures the emulator or simulator proxy if passed proxy port.</span></div><div><span style="color: #6a9955;">/// Assumes there is a proxy running locally if proxy port provided</span></div><div><span style="color: #6a9955;">/// Proxy only used for mobile devices. We assume browser is already configured.</span></div><div><span style="color: #6a9955;">///</span></div><div><span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">DevelopmentHttpConfig</span> <span style="color: #569cd6;">extends</span> <span style="color: #4ec9b0;">HttpOverrides</span> {</div><div> <span style="color: #4ec9b0;">DevelopmentHttpConfig</span>({</div><div> <span style="color: #569cd6;">required</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">certExclusionDomains</span>,</div><div> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">iosProxyHost</span> <span style="color: #d4d4d4;">=</span> <span style="color: #ce9178;">'127.0.0.1'</span>,</div><div> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">androidProxyHost</span> <span style="color: #d4d4d4;">=</span> <span style="color: #ce9178;">'10.0.2.2'</span>,</div><div> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">proxyPort</span>,</div><div> });</div><br /><div> <span style="color: #6a9955;">/// Proxy used for android</span></div><div><span style="color: #6a9955;"> </span><span style="color: #6a9955;">/// Defaults to the 10.0.2.x network</span></div><div> <span style="color: #4ec9b0;">String</span> <span style="color: #9cdcfe;">androidProxyHost</span>;</div><br /><div> <span style="color: #6a9955;">/// Proxy used for IOS because it uses the host network. defaults to 127.0.0.1</span></div><div><span style="color: #6a9955;"> </span><span style="color: #6a9955;">/// Android has its own network</span></div><div> <span style="color: #4ec9b0;">String</span> <span style="color: #9cdcfe;">iosProxyHost</span>;</div><br /><div> <span style="color: #6a9955;">/// list of domains we exclude from cert check</span></div><div> <span style="color: #4ec9b0;">List</span><<span style="color: #4ec9b0;">String</span>> <span style="color: #9cdcfe;">certExclusionDomains</span>;</div><br /><div> <span style="color: #6a9955;">/// proxyPort != null means setup the proxy if isAndroid or isIOS</span></div><div> <span style="color: #4ec9b0;">int</span><span style="color: #d4d4d4;">?</span> <span style="color: #9cdcfe;">proxyPort</span>;</div><br /><div> <span style="color: #9cdcfe;">@</span><span style="color: #9cdcfe;">override</span></div><div> <span style="color: #4ec9b0;">HttpClient</span> <span style="color: #dcdcaa;">createHttpClient</span>(<span style="color: #4ec9b0;">SecurityContext</span><span style="color: #d4d4d4;">?</span> <span style="color: #9cdcfe;">context</span>) {</div><div> <span style="color: #569cd6;">final</span> <span style="color: #9cdcfe;">exclusion</span> <span style="color: #d4d4d4;">=</span> <span style="color: #569cd6;">super</span>.<span style="color: #dcdcaa;">createHttpClient</span>(<span style="color: #9cdcfe;">context</span>);</div><br /><div> <span style="color: #6a9955;">// ignore self signed certs in these domains</span></div><div> <span style="color: #9cdcfe;">exclusion</span>.<span style="color: #9cdcfe;">badCertificateCallback</span> <span style="color: #d4d4d4;">=</span> (<span style="color: #9cdcfe;">cert</span>, <span style="color: #9cdcfe;">host</span>, <span style="color: #9cdcfe;">port</span>) <span style="color: #d4d4d4;">=></span></div><div> <span style="color: #9cdcfe;">certExclusionDomains</span>.<span style="color: #dcdcaa;">any</span>((<span style="color: #9cdcfe;">element</span>) <span style="color: #d4d4d4;">=></span> <span style="color: #9cdcfe;">host</span>.<span style="color: #dcdcaa;">endsWith</span>(<span style="color: #9cdcfe;">element</span>));</div><br /><div> <span style="color: #c586c0;">if</span> (<span style="color: #9cdcfe;">proxyPort</span> <span style="color: #d4d4d4;">!=</span> <span style="color: #569cd6;">null</span>) {</div><div> <span style="color: #c586c0;">if</span> (<span style="color: #4ec9b0;">Platform</span>.<span style="color: #9cdcfe;">isAndroid</span>) {</div><div> <span style="color: #9cdcfe;">exclusion</span>.<span style="color: #9cdcfe;">findProxy</span> <span style="color: #d4d4d4;">=</span> (<span style="color: #9cdcfe;">url</span>) <span style="color: #d4d4d4;">=></span> <span style="color: #ce9178;">'PROXY </span><span style="color: #d4d4d4;">$</span><span style="color: #9cdcfe;">androidProxyHost</span><span style="color: #ce9178;">:</span><span style="color: #d4d4d4;">$</span><span style="color: #9cdcfe;">proxyPort</span><span style="color: #ce9178;">'</span>;</div><div> }</div><div> <span style="color: #c586c0;">if</span> (<span style="color: #4ec9b0;">Platform</span>.<span style="color: #9cdcfe;">isIOS</span>) {</div><div> <span style="color: #9cdcfe;">exclusion</span>.<span style="color: #9cdcfe;">findProxy</span> <span style="color: #d4d4d4;">=</span> (<span style="color: #9cdcfe;">url</span>) <span style="color: #d4d4d4;">=></span> <span style="color: #ce9178;">'Proxy </span><span style="color: #d4d4d4;">$</span><span style="color: #9cdcfe;">iosProxyHost</span><span style="color: #ce9178;">:</span><span style="color: #d4d4d4;">$</span><span style="color: #9cdcfe;">proxyPort</span><span style="color: #ce9178;">'</span>;</div><div> }</div><div> }</div><div> <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">exclusion</span>;</div><div> }</div><div>}</div><br /></div></div></div><p style="margin: 0px;"><br /></p><p>I've worked in corporate environments where the Android 10.0.2.1 doesn't work for reaching sites and I have to use the corporate proxy instead.</p><p style="margin: 0px;"><br /></p><h3 style="margin: 0px; text-align: left;">Example usage from test case</h3><div><br /></div><div>Here are two test cases. "wrong.host.badssl.com" is a test site with a bad certificate. We test connecting to it with that domain in our "do not check list" and again with it not in the list.</div><div><br /></div><div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><br /><div><div style="line-height: 19px;"><div><span style="color: #dcdcaa;"> test</span>(<span style="color: #ce9178;">'Succeed with an exclusion'</span>, () <span style="color: #c586c0;">async</span> {</div><div> <span style="color: #569cd6;">const</span> <span style="color: #9cdcfe;">badSSLDomain</span> <span style="color: #d4d4d4;">=</span> <span style="color: #ce9178;">'wrong.host.badssl.com'</span>;</div><div> <span style="color: #569cd6;">const</span> <span style="color: #9cdcfe;">requestDomain</span> <span style="color: #d4d4d4;">=</span> <span style="color: #ce9178;">'wrong.host.badssl.com'</span>;</div><div> <span style="color: #4ec9b0;">HttpOverrides</span>.<span style="color: #9cdcfe;">global</span> <span style="color: #d4d4d4;">=</span> <span style="color: #4ec9b0;">DevelopmentHttpConfig</span>(</div><div> <span style="color: #9cdcfe;">certExclusionDomains</span><span style="color: #d4d4d4;">:</span> [<span style="color: #9cdcfe;">badSSLDomain</span>], <span style="color: #6a9955;">/*proxyPort: 8080*/</span></div><div> );</div><br /><div> <span style="color: #569cd6;">final</span> <span style="color: #9cdcfe;">url</span> <span style="color: #d4d4d4;">=</span> <span style="color: #4ec9b0;">Uri</span>.<span style="color: #dcdcaa;">https</span>(<span style="color: #9cdcfe;">requestDomain</span>, <span style="color: #ce9178;">'/'</span>);</div><div> <span style="color: #569cd6;">final</span> <span style="color: #9cdcfe;">response</span> <span style="color: #d4d4d4;">=</span> <span style="color: #c586c0;">await</span> <span style="color: #9cdcfe;">http</span>.<span style="color: #dcdcaa;">get</span>(<span style="color: #9cdcfe;">url</span>);</div><div> <span style="color: #dcdcaa;">expect</span>(<span style="color: #9cdcfe;">response</span>.<span style="color: #9cdcfe;">statusCode</span>, <span style="color: #dcdcaa;">equals</span>(<span style="color: #4ec9b0;">HttpStatus</span>.<span style="color: #9cdcfe;">ok</span>));</div><div> });</div><br /></div></div> <span style="color: #dcdcaa;">test</span>(<span style="color: #ce9178;">'Fail Without exclusion'</span>, () <span style="color: #c586c0;">async</span> {<div></div></div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div> <span style="color: #569cd6;">const</span> <span style="color: #9cdcfe;">requestDomain</span> <span style="color: #d4d4d4;">=</span> <span style="color: #ce9178;">'wrong.host.badssl.com'</span>;</div><br /><div> <span style="color: #569cd6;">final</span> <span style="color: #9cdcfe;">url</span> <span style="color: #d4d4d4;">=</span> <span style="color: #4ec9b0;">Uri</span>.<span style="color: #dcdcaa;">https</span>(<span style="color: #9cdcfe;">requestDomain</span>, <span style="color: #ce9178;">'/'</span>);</div><div> <span style="color: #6a9955;">// ignore: unawaited_futures</span></div><div> <span style="color: #dcdcaa;">expectLater</span>(() <span style="color: #d4d4d4;">=></span> <span style="color: #9cdcfe;">http</span>.<span style="color: #dcdcaa;">get</span>(<span style="color: #9cdcfe;">url</span>), <span style="color: #9cdcfe;">throwsException</span>);</div><div> });</div><div><br /></div></div></div><p style="margin: 0px;"><br /></p><h2 style="margin: 0px; text-align: left;">Video</h2><div><br /></div><div>A quick walkthrough of the code.</div><div><br /></div>
<div>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/9WWBRBqC82c" width="640"></iframe>
</div>
<h3 style="text-align: left;">Revision History</h3><div>Created 2024 01</div><h3 style="text-align: left;"><br /></h3>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-12281973237619756602024-01-07T20:19:00.005-05:002024-01-07T21:33:10.594-05:00Building your brand - Being visibile in a cross ranking environment<p>Your personal brand and reputation are the most reliable tools for succeeding and being perceived as succeeding in an organization. A lot of people rely on their nearest manager to do the heavy lifting. That can become a single point of career failure. They may move or be moved. They may not have the personality for ratings combat. A better plan is to help them help you when it comes time for new opportunities, promotions, and compensation. </p><p>Cross-group rankings are objective, in theory, but are really a cage match of discussions between management where they put everyone into a pool and rank them against each other. This means management speaks up about their people to increase perception and question other management about their staff to create doubt about their people's relative position. The best way to come out on top of this is to have strong management and to be visible to others to create familiarity before the process. This is especially true in environments where the people still around at the end of the year are forced to fit into some type of curve with a fixed percentage at each level of rated competency. </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidkBM6zmsju6oe0IIpa-1tAT8bEkEk7rWH_XeDwzBONEILJAp7pxI4DC-wXVULshzC_rSoCXaP2fpLTl9A2K0URoJoxF32ejVhJraKyT5X3VTMzebgPiXxpWv-Z2MddBNZiRyomhV7_uedMplgtuxBXQNZ2ZSeEMR26bwKHkvOSsNCk016nGbYyZnxn8iJ/s471/YourGlobalBrand.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="451" data-original-width="471" height="383" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidkBM6zmsju6oe0IIpa-1tAT8bEkEk7rWH_XeDwzBONEILJAp7pxI4DC-wXVULshzC_rSoCXaP2fpLTl9A2K0URoJoxF32ejVhJraKyT5X3VTMzebgPiXxpWv-Z2MddBNZiRyomhV7_uedMplgtuxBXQNZ2ZSeEMR26bwKHkvOSsNCk016nGbYyZnxn8iJ/w400-h383/YourGlobalBrand.drawio.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Your management and <i>others</i> push and question each other when it comes time to make retention, compensation, and retention discussions. You need as many <i>others</i> on your side as you can get. The best people will be buried if they have a weak manager and their capabilities are not well known. A great manager may not be enough to offset someone invisible.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">The workplace is a competitive cooperative environment. It is a cooperative environment because the organization only succeeds if people work together towards the same goals. It is competitive because promotions, money, and opportunities are based on perceived capabilities and motivation measured in absolute values and relative to other staff.</div><h2 style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyu7yw2P_OfLsFnGCQ6DbK2ZuzD2evacukzi-wdnmS3mPCLelfFdKSf04O4Sw1_IwayenQvYhdTA26203d1ldmLutkaWsvO6iRLt_3NPH1Zyd0UlHlT_zPeeBU8Z1bGzQZrEp1IEbXAknkQZ-RWhkwpyKXDUiAyUtfZj3lKhhA5UqfZ19hohpil2CeWp3c/s1662/Screenshot%202024-01-01%20at%202.56.08%E2%80%AFPM.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="1662" data-original-width="996" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyu7yw2P_OfLsFnGCQ6DbK2ZuzD2evacukzi-wdnmS3mPCLelfFdKSf04O4Sw1_IwayenQvYhdTA26203d1ldmLutkaWsvO6iRLt_3NPH1Zyd0UlHlT_zPeeBU8Z1bGzQZrEp1IEbXAknkQZ-RWhkwpyKXDUiAyUtfZj3lKhhA5UqfZ19hohpil2CeWp3c/w240-h400/Screenshot%202024-01-01%20at%202.56.08%E2%80%AFPM.png" width="240" /></a></div>Building your brand</h2><div>Building your brand means </div><div><ol style="text-align: left;"><li>Having a roadmap of today's and tomorrow brand building actions.</li><li>Excelling at work on your team in a way that is visible to others.</li><li>Helping other teams in cross-functional areas so that their success is partially because of you.</li><li>Being visible in management discussions and cross-team communication and projects.</li></ol><div>Your manager should help you create a personal brand and become more visible. It makes them look good and makes it easier in any cross-team discussions. Ask your manager</div></div><blockquote><div>How can I build my brand?</div></blockquote><blockquote><p>What can I do to become more visible to others? </p></blockquote>Some situations may be outside your control. Reorganizations or executive leadership changes can disconnect you from those that respect your work. That can result in a situation where you have to start over. I've been able to move into another group because of a good brand and then had that all vanish as part of turmoil and turnover. <div><br /></div><div>Contractor life can be different. They need to impress a smaller set of people to be renewed. <br /><h2 style="text-align: left;">Video</h2>
<div>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/8NF3ri6PyWc" width="640"></iframe>
</div>
<h2 style="text-align: left;">Identifying good and bad leadership</h2><div>I had two managers for the same position in the same organization. One of them got promoted quickly but had no direct reports promoted in the two years I tracked them. The other one asked us regularly </div><blockquote><div>What are you doing to build your brand to help me push you up at year end ranking time?</div></blockquote><p>The former got mad when we asked questions outside our group. The latter had people follow them to other companies.</p><h3 style="text-align: left;">Revision History</h3><div>Created 2024 01</div></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-9544395407511787252023-12-25T19:24:00.017-05:002023-12-25T21:13:38.220-05:00Coding challenge accepted - game board navigation using open and completed tasks FIFOs<span face="Roboto, Noto, sans-serif" style="background-color: white; color: #0d0d0d; font-size: 15px; white-space-collapse: preserve;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioSCMt7tPN6JEnUR8IsWOz_OPRFaZl7GedF5oPKcxZWEkwqnPC-v0CrCoxfiKAwIvncA8N1f6asXfdPcuzUYXVVbj9hP_0eU7_a-2ikYFxhXDF86qjd_RbYv-x_q3SNd5sqbSfPlmXGKQ7d3jZpDVxtJ9a54ipqsRRJNww33F0RDT3iYoAbo-OYF9LFS1y/s431/Board%20Tasks%20Splash%20Icon.drawio.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="431" data-original-width="231" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioSCMt7tPN6JEnUR8IsWOz_OPRFaZl7GedF5oPKcxZWEkwqnPC-v0CrCoxfiKAwIvncA8N1f6asXfdPcuzUYXVVbj9hP_0eU7_a-2ikYFxhXDF86qjd_RbYv-x_q3SNd5sqbSfPlmXGKQ7d3jZpDVxtJ9a54ipqsRRJNww33F0RDT3iYoAbo-OYF9LFS1y/s320/Board%20Tasks%20Splash%20Icon.drawio.png" width="172" /></a></div>I was involved in a couple of minor coding challenges that involved exploring a square space or board. I don't know any navigation algorithms so I implemented breadth first and depth first searching of the board using an open task list and a completed tasks list. Legal moves are added to the open task list. Each legal move is then checked to determine the following legal moves. Those are posted to the open task list where they are then picked up later and further explored.</span><div><span face="Roboto, Noto, sans-serif" style="background-color: white; color: #0d0d0d; font-size: 15px; white-space-collapse: preserve;">
Two of those challenges involved taking a specific number of steps. One asked how many squares could be touched in a specific number of steps. The other asked how many places you could end up in exactly a specific number of steps.
</span><span face="Roboto, Noto, sans-serif" style="background-color: white; color: #0d0d0d; font-size: 15px; white-space-collapse: preserve;"><br /></span><div><span face="Roboto, Noto, sans-serif" style="background-color: white; color: #0d0d0d; font-size: 15px; white-space-collapse: preserve;">Sample `Dart` code can be found in this GitHub repo <a href="https://github.com/freemansoft/AdventOfCode-2023-Dart/tree/features/2023/solutions">https://github.com/freemansoft/AdventOfCode-2023-Dart/tree/features/2023/solutions</a> on the 2023 branch.</span><h2 style="text-align: left;">Video</h2>
<div><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/A010pzPOYIA" width="640"></iframe>
</div>
<h2 style="text-align: left;">Content</h2><div>I've been calling these open and completed tasks but you can also think of it as a FIFO of moves with a set of completed operations. The two problems are</div><div><ol style="text-align: left;"><li>We have a rectangular game board</li><li>We want to know how many squares we can cover within a specific number of moves.</li><li>We want to know what squares we can end up on with an exact number of moves.</li></ol></div><div>Assumptions: </div><div><ol style="text-align: left;"><li>The field in the problem is too large to use recursion. We have plenty of memory but not stack space for 1,000,000 calls.</li><li>We are going to use a similar technique for two similar problems.</li><li>We are going to track all our next steps on various paths by storing them in a FIFO as they are identified.</li></ol><div>Algorithm</div></div><div><ol style="text-align: left;"><li>Create a task for the first square. This bootstraps the process by giving us something to act on</li><li>Pull the first task/square from the open items FIFO. Consider this the <i>current</i> square</li><li>Create a task for each possible square we can move to that is immediately adjacent to the <i>current</i> square. Put those tasks into the <i>open tasks </i> FIFO</li><ol><li>Ignore any task that has an exact match in the active or completed task <i>sets. </i>This truncates the exploration of nodes that were reached from more than one place.</li></ol><li>Optionally can prune completed squares when the items stored in the completed set with some temporal issue like the # of steps that were taken to get there.</li><li>Repeat steps 2 and 3</li></ol></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9QrqSRUzPpyfx9T-RjaD7uLoyHIimXCdvK4nLBkco3VF5jUxT4TIDmvs7lITmYcNOrFqwiYSy7ICYQ6hWV23zR4ci4IoAWVvoEzMCCsOJzEOudG2FNk1ICKMP2ZJgvcK0BgqIGRkbDctRAkVrA_Xtc5q7SxF5aFJOGheq0MQa-Wpxninje_ZFJ9zDSPZQ/s871/Board%20Tasks%20Problem.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="473" data-original-width="871" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9QrqSRUzPpyfx9T-RjaD7uLoyHIimXCdvK4nLBkco3VF5jUxT4TIDmvs7lITmYcNOrFqwiYSy7ICYQ6hWV23zR4ci4IoAWVvoEzMCCsOJzEOudG2FNk1ICKMP2ZJgvcK0BgqIGRkbDctRAkVrA_Xtc5q7SxF5aFJOGheq0MQa-Wpxninje_ZFJ9zDSPZQ/s16000/Board%20Tasks%20Problem.drawio.png" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div></div><div><b>Breadth First vs Depth First</b></div><div><ul style="text-align: left;"><li>We can explore all the first steps and then all the next steps of the same count. This is a <i>breadth-first </i>search that consumes all of the options at a given distance before moving on to the next depth/step.</li><li>We can explore all the way down from a first state to an end state and then explore the peers of the last step and the peers of the steps above that. This is a <i>depth-first</i> calculation that threads out all the way to the end before backing up and doing that again.</li></ul></div><div><b>Implementation Comment</b></div><div><ul style="text-align: left;"><li>Open Tasks as a FIFO is breadth-first</li><li>Open Tasks as a LIFO is depth-first</li></ul></div><div><div>The duplicate rejection process means that we search the Active Tasks and Completed Tasks stores every time we put a new square into the processing queue. My Dart program was 20x faster when I implemented the Tasks stores using <i>Sets</i> instead rather than <i>Lists </i>when my task objects implemented equals() and hashCode() for the fields we dedupe against.</div></div><div><br /></div><div><b>Four steps</b></div><div>The playing field on the left shows that we covered 5 squares when moving 4 squares to the right. The original square and the 4 squares we stepped in.</div><div><br /></div><div>The playing field on the right shows where we would end up if we took exactly 4 steps. Even numbers of steps results in possible landing places being every other step because we can always step back one and then out one again. So a 4-step operation could generate results 2 and 4 steps away from the start point.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxPPr_i6z176dVkZTQpmSJo7vetLUIr49f8gMGGTCF3HpWPORiPL1Z1IVq39iGME9d0x1LB9iAYKPCjcKmOF4sIV3cnGfbosR56KEbTEROYawOdJiJFEybfcxTM0qUuw-xHa_PRefwFNHVbHZJxtCZwvaREmacbuL_Yqh8ARLNohknemRIzGTLUzYF4akE/s861/Board%20Tasks%20Four%20Steps%20Bigger.drawio.png" style="margin-left: 1em; margin-right: 1em;"></a></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfvAWWVHrreZ4T4Q1fCWBpb__GemBNeXgHdqYLVWx_oeFfgFFHVT8mLluP59gTwYq34CyzO8i1opXIs6Vmty-C8B2uptoeKLSdqPdsofZqfQDvu5kmHwaYAkCP6ZIc_Uq4nU3E2sD_HhKGsG0a1dmsQyVkFshNoM5IkKIeLZlpZYlay_RxAO4bUv-qc9c-/s871/Board%20Tasks%20Four%20Steps.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="477" data-original-width="871" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfvAWWVHrreZ4T4Q1fCWBpb__GemBNeXgHdqYLVWx_oeFfgFFHVT8mLluP59gTwYq34CyzO8i1opXIs6Vmty-C8B2uptoeKLSdqPdsofZqfQDvu5kmHwaYAkCP6ZIc_Uq4nU3E2sD_HhKGsG0a1dmsQyVkFshNoM5IkKIeLZlpZYlay_RxAO4bUv-qc9c-/s16000/Board%20Tasks%20Four%20Steps.drawio.png" /></a></div><div class="separator" style="clear: both; text-align: left;">The left-hand diagram shows all the locations we could visit taking 4 steps in all directions. The right -hand diagram shows all the locations we could end up on if we took <i>exactly </i>4 steps.</div><div class="separator" style="clear: both; text-align: center;"><img border="0" data-original-height="473" data-original-width="861" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxPPr_i6z176dVkZTQpmSJo7vetLUIr49f8gMGGTCF3HpWPORiPL1Z1IVq39iGME9d0x1LB9iAYKPCjcKmOF4sIV3cnGfbosR56KEbTEROYawOdJiJFEybfcxTM0qUuw-xHa_PRefwFNHVbHZJxtCZwvaREmacbuL_Yqh8ARLNohknemRIzGTLUzYF4akE/s16000/Board%20Tasks%20Four%20Steps%20Bigger.drawio.png" /></div><div><br /></div>This is a partial example of the first couple of rounds of stepping and step evaluations when looking for total square coverage. The table shows how we populate and consume the open task set as we move and fill the completed task set. Duplicates, those that are to be processed or have already been completed, are ignored.</div><div><br /></div><div>Our tasks contain just the row and column of the square being evaluated.</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCysB5RNiVrbg5iOMMT2Jddb0LpQN3eYzhyivpwg8BvdnkG_eTVaAeSleyPVPQfQYh90LgNHgMvODqtvkgC2HPpAxJ7TB8xI75mR4tbHpFlo4g5tOQGfTQdnV8P-QCeOnNJtCHTVEmxuZjrI8YUlsm8jUAMDAtd6ae6gzDNhrjm5Jm77T5hNjjl2UjlRQw/s981/Board%20Tasks%20Two%20Steps%20Coverage%20Detailed.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="541" data-original-width="981" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCysB5RNiVrbg5iOMMT2Jddb0LpQN3eYzhyivpwg8BvdnkG_eTVaAeSleyPVPQfQYh90LgNHgMvODqtvkgC2HPpAxJ7TB8xI75mR4tbHpFlo4g5tOQGfTQdnV8P-QCeOnNJtCHTVEmxuZjrI8YUlsm8jUAMDAtd6ae6gzDNhrjm5Jm77T5hNjjl2UjlRQw/s16000/Board%20Tasks%20Two%20Steps%20Coverage%20Detailed.drawio.png" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div>This is a partial example of the first couple of rounds of stepping and step evaluations. The table shows how we populate and consume the open task set as we move and fill the completed task set. Duplicates, those that are to be processed or have already been completed, are ignored.</div><div><br /></div><div><div>Our tasks contain the number of steps to get here <i>plus</i> the row and column of the square being evaluated. This lets us move back over a square as long as it is part of a different step.</div><div></div></div><div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0nEpJ2fBhlq22Z_b-PKtxG5oQ42Ei-ZD2-38FsnaE5R9CHMnM0dXKwflwNEt59XLW02Wgrd6XsCWWmqvdE7EQIRrOZPhyCBgdp0Mu8jqu9MJJLbGM6Wd70IAHErdufB5w3noeOjERH22NtuhESbmUFftT94KWQJUCF4Mfw9rWwY_8AJ3gwtb-K_7_pTwm/s981/Board%20Tasks%20Two%20Steps%20End%20State%20Detailed.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="650" data-original-width="981" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0nEpJ2fBhlq22Z_b-PKtxG5oQ42Ei-ZD2-38FsnaE5R9CHMnM0dXKwflwNEt59XLW02Wgrd6XsCWWmqvdE7EQIRrOZPhyCBgdp0Mu8jqu9MJJLbGM6Wd70IAHErdufB5w3noeOjERH22NtuhESbmUFftT94KWQJUCF4Mfw9rWwY_8AJ3gwtb-K_7_pTwm/s16000/Board%20Tasks%20Two%20Steps%20End%20State%20Detailed.drawio.png" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Most of these problems include impediments or restrictions on movement through squares. The evaluation table generates fewer open tasks because the blockers remove some of the options. The table above had 8 open tasks by the end of this table while this one only has one.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDJWcml4h5rSoquyjdF5PrVB-yuafSDyxwc4gt3POdk0VmST6FIZUpF_zj8un_NO_9HY-XhOfJBS5eqz-C-j62Ttk32s4By01JWxj8o82ARDsJDvvJFXg9T0HDxCWsxu2T8l-c1X24K-meqx2_ofPt_UAyXr1nnFIZN6K37XKJZA2aPqDwgWpkwuc4SjFM/s881/Board%20Tasks%20Two%20Steps%20Coverage%20Blocked.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="573" data-original-width="881" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDJWcml4h5rSoquyjdF5PrVB-yuafSDyxwc4gt3POdk0VmST6FIZUpF_zj8un_NO_9HY-XhOfJBS5eqz-C-j62Ttk32s4By01JWxj8o82ARDsJDvvJFXg9T0HDxCWsxu2T8l-c1X24K-meqx2_ofPt_UAyXr1nnFIZN6K37XKJZA2aPqDwgWpkwuc4SjFM/s16000/Board%20Tasks%20Two%20Steps%20Coverage%20Blocked.drawio.png" /></a></div><h2 style="text-align: left;">Detours and Routing Squares</h2><div><div>Squares can have different features that change how we travel through them. Some force a direction change or split our travel into multiple directions. </div><div><br /></div><div>I created a definition for each space type that tells us how we leave a square based on the direction vector we came from. Each square is bound to the definition for its square type. When we enter a square, we can look at the 4 definitions and find the one that matches our entry delta (row-delta, col-delta). That definition tells us all the ways we leave the square. We add the outbound row and col delta to the current cell location to give us a list of target squares.</div></div><div><br /></div><div>A pass-through square would let (-1,0) → (+1,0) and (+1,0) → (-1,0). A right angle splitter could have two exits so (-1,0) could exit on →(0,-1) and →(0,+1). Both of the exits are posted as tasks to be processed separately later. </div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkmSxf-CvcdVV9y5G2JfjIC6MDwapnhCGieEhl6kfNOXwV1-2R6ssIJ4pgDrQCQzmeOriUNjkSWXgVfHP7-VaLEcJDP8jutgxyQZtwoZKfpv6cQw-ORKCCNHg5G9sebb11gLQ-hiPdRlwewbVJvI2V9WlWG8wJ_OTW9ofl8qhIXZS1oVJloZje6lfY9_f9/s729/Board%20Tasks%20Detours%20and%20Routes.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="473" data-original-width="729" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkmSxf-CvcdVV9y5G2JfjIC6MDwapnhCGieEhl6kfNOXwV1-2R6ssIJ4pgDrQCQzmeOriUNjkSWXgVfHP7-VaLEcJDP8jutgxyQZtwoZKfpv6cQw-ORKCCNHg5G9sebb11gLQ-hiPdRlwewbVJvI2V9WlWG8wJ_OTW9ofl8qhIXZS1oVJloZje6lfY9_f9/s16000/Board%20Tasks%20Detours%20and%20Routes.drawio.png" /></a></div><div><br /></div><div>Ex: We enter square (3.3) from the left or (0,-1). It is a splitter from that direction so the exit vectors are [(-1,0), (+1,0)] going out the top and the bottom. Add the square location to the exit vectors and we know we are entering [(3-1, 3+-), (3+1, 3+0)] or [(2,3), (4,3)].</div><div><br /></div><div><br /></div>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/vHUA_Lpx4z8" width="640"></iframe>
<h3 style="text-align: left;">Revision History</h3><div>Created 2023/12</div></div></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-30041419650160381532023-12-10T21:33:00.008-05:002023-12-13T18:00:20.890-05:00Using emojii for Yesterday, Today and Blockers on Slack and other channels<div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpxUn_xNQNfWDW3Wp2DVDhbXmukOmj_MNQq6pL2cP79bw7qL9HoYIRsUhdah5nfNdm2buVH5w5CHpXKfOSc1fiIPzC1fhPfSTkAzcovFfN-aXypxdss8AFirnAM2IcHuJujgqLrp3aQ0rH6g9KkUzreiH18rHGGITijx1l8Rf8tjNH0J3okKGnvcySUxZd/s616/agile-emoticons-y-t-b.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="350" data-original-width="616" height="182" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpxUn_xNQNfWDW3Wp2DVDhbXmukOmj_MNQq6pL2cP79bw7qL9HoYIRsUhdah5nfNdm2buVH5w5CHpXKfOSc1fiIPzC1fhPfSTkAzcovFfN-aXypxdss8AFirnAM2IcHuJujgqLrp3aQ0rH6g9KkUzreiH18rHGGITijx1l8Rf8tjNH0J3okKGnvcySUxZd/s320/agile-emoticons-y-t-b.png" width="320" /></a></div>Slack and other messaging platforms are mainstream platforms in many companies. They provide great asynchronous ways of detailing activity and issues so that people can stay up to date while preserving standups or other meetings for interactive troubleshooting and planning.</div><div><br /></div><div>The teams I've been on have used Slack or other platforms to share <i>Yesterday, Today, </i>and<i>, Blockers</i> for the last 5 years. I've given up my geezer opposition to emojis and fallen in with using them to signify the category for everything reported. As an example:</div><div><h2 style="text-align: left;">Yesterday, Today, and Blockers the <i>emoticon </i>way</h2><p><span color="rgba(0, 0, 0, 0.87)" style="font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Android Emoji", EmojiSymbols, "EmojiOne Mozilla", "Twemoji Mozilla", "Segoe UI Symbol", "Noto Color Emoji Compat", emoji, __notoEmoji_c30de8, __notoEmoji_Fallback_c30de8; font-size: 57px;">👈Yesterday</span></p><ul style="text-align: left;"><li>Yesterday's events that affect others or the backlog</li></ul></div><div><div style="text-align: left;"><span color="rgba(0, 0, 0, 0.87)" style="font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Android Emoji", EmojiSymbols, "EmojiOne Mozilla", "Twemoji Mozilla", "Segoe UI Symbol", "Noto Color Emoji Compat", emoji, __notoEmoji_c30de8, __notoEmoji_Fallback_c30de8; font-size: 57px;">👉Today's Activity</span></div><div style="text-align: left;"><ul style="text-align: left;"><li>The main things we want to get done today</li></ul></div><div style="text-align: left;"><span color="rgba(0, 0, 0, 0.87)" style="font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Android Emoji", EmojiSymbols, "EmojiOne Mozilla", "Twemoji Mozilla", "Segoe UI Symbol", "Noto Color Emoji Compat", emoji, __notoEmoji_c30de8, __notoEmoji_Fallback_c30de8; font-size: 57px;">✋Blockers</span></div><p></p><ul style="text-align: left;"><li>Things in my way that others can help with or that are holding us up</li></ul><p></p><p><span color="rgba(0, 0, 0, 0.87)" style="font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Android Emoji", EmojiSymbols, "EmojiOne Mozilla", "Twemoji Mozilla", "Segoe UI Symbol", "Noto Color Emoji Compat", emoji, __notoEmoji_c30de8, __notoEmoji_Fallback_c30de8; font-size: 57px;">👊Thank you</span></p><p></p><ul style="text-align: left;"><li>Callout for someone's timely help or them taking an extra effort. </li></ul><p></p><p><br /></p><h2 style="text-align: left;">Example for Joe's weekend</h2><div><div><b>Joe for 2023/MM/DD</b></div><div>👈Worked on Advent of Code 2023</div><div><div>👈Created content for blog article on <strike>emoticons</strike> I mean emojis</div><div><div>👈Picked up stuff at the home supply store to calk</div><div>👈Loaded the new faster firmware for my 3d printer</div><div></div></div></div><div><br />👉Worked on Advent of Code 2023</div><div>👉3d Printed new holder for my coffee pods</div><div><br />✋Forgot the air filters at the home supply store</div><div><div>✋Can't figure out how to do an area fill calculation for AoC2023 without looking on the internet</div><div></div><div><br />👊 My spouse picked up my favorite snack</div></div></div><h2 style="text-align: left;">Not a good idea</h2><div>All traffic is recorded and can be used by HR or Legal including those from other organizations who subpoena conversation histories. </div><div><br /></div><div><div><br /></div></div><h1 class="text-left hidden md:block" style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid; box-sizing: border-box; color: rgba(0, 0, 0, 0.87); font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 34px; font-weight: 500; line-height: 43px; margin: 0px;"><span style="font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Android Emoji", EmojiSymbols, "EmojiOne Mozilla", "Twemoji Mozilla", "Segoe UI Symbol", "Noto Color Emoji Compat", emoji, __notoEmoji_c30de8, __notoEmoji_Fallback_c30de8; font-size: 57px;">🖕Nope</span></h1><div><span style="font-family: Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, Android Emoji, EmojiSymbols, EmojiOne Mozilla, Twemoji Mozilla, Segoe UI Symbol, Noto Color Emoji Compat, emoji, __notoEmoji_c30de8, __notoEmoji_Fallback_c30de8;"><span style="font-size: 57px;">💩Nope </span></span> </div><div><br /></div><div><div><i>I had the middle finger here but Google Blogger filters the entire line out with that emoticon.</i></div><div><br /></div></div><h2 style="text-align: left;">Video</h2>
<div>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/MQMQc-cgV5w" width="640"></iframe>
</div>
<h3 style="text-align: left;">Revision History</h3></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-19353314460042183212023-12-10T19:50:00.007-05:002023-12-10T21:41:57.131-05:00The Crucible - Melting the status quo with targeted strategic ignorance<p></p><div><div class="separator" style="clear: both; text-align: center;"></div></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDZquhv16Of4vnWPM5veElM4zmk0PTVwL-UhNqFDL6VhFYAGPbckxXxOJdzE2EDiezxT2ZlZOOxfGGXDvUkX4LnOyU0c2ZZBEchKPS-pJdwa3I83DMGx4Yd8SDMWZbj9JjcrKuepNplbezNknjF8KaJbWNyhaUMCRb1oIsTTkioddy-LKLRh8QQzfE7LxM/s1024/Slide1.PNG" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="768" data-original-width="1024" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDZquhv16Of4vnWPM5veElM4zmk0PTVwL-UhNqFDL6VhFYAGPbckxXxOJdzE2EDiezxT2ZlZOOxfGGXDvUkX4LnOyU0c2ZZBEchKPS-pJdwa3I83DMGx4Yd8SDMWZbj9JjcrKuepNplbezNknjF8KaJbWNyhaUMCRb1oIsTTkioddy-LKLRh8QQzfE7LxM/w400-h300/Slide1.PNG" width="400" /></a></div>Executives have several tools that can re-forge an organization from the top. They can reorganize changing the boundaries created by Conway's law. New executives can push out embedded interests and bring people in from the outside. They can brutally question everything through targeted ignorance. </div><div><br /></div><div>A main tactic is to hire senior people with no knowledge of internal complexities or implementations. They come in questioning everyone and everything with an assumption that the hard stuff exists because of complacency or because people are locked it was always done that way. New people propose or demand refutation of approaches in past lives or other industries or with previous engagements. The legacy organization's natural reaction is that the new ideas won't work and defend the status quo. New people apply additional heat declaring that the status quo won't stand often replacing people who stand in the way. This applies indirect heat to the rest of the organization.</div><div><br /></div><h3 style="text-align: left;">Secondary</h3><div>Existing organizations become mired in the complexity of their existing business and personnel processes. They have overlapping rules and systems that implement government regulations, business rules, and technical or process debt as systems and processes evolve over time. Eventually, it becomes difficult to discriminate between necessary, accidental, and evolutionary complexity.<p></p></div><div>Regulated industries may have additional rules or constraints that the new people are ignorant of. Separating regulatory, privacy and legal constraints from legacy requirements and evolutionary processes can be difficult. Existing organizations front-load those constraints when making changes. New people and the imperative for change back end load those constraints to heat a company to force change. New people are hired with industry experience or they may be deliberately hired from other industries or from smaller less mature companies. </div><h2 style="clear: both; text-align: left;">Deliberate Ignorance</h2><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL8wNd4ePwdOGOOUEIP1RmQOp-QL1eFI6MwG_BTvZueX-dB-qkguZiGSoAO_b4kYb_CI3TNR8ZDKwhSdZeHKBIVojVXSp-EAwDNK8BdFF4PiM0v2OLTPrtGYscmKnPrrKRrtzrSzMrIxphBBP9X67fWlPt2KTmxX0YB6SiSxdw33f6FTs23j_Bx8VVwk9-/s1926/Question-Lound.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: center;"><img border="0" data-original-height="675" data-original-width="1926" height="140" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL8wNd4ePwdOGOOUEIP1RmQOp-QL1eFI6MwG_BTvZueX-dB-qkguZiGSoAO_b4kYb_CI3TNR8ZDKwhSdZeHKBIVojVXSp-EAwDNK8BdFF4PiM0v2OLTPrtGYscmKnPrrKRrtzrSzMrIxphBBP9X67fWlPt2KTmxX0YB6SiSxdw33f6FTs23j_Bx8VVwk9-/w400-h140/Question-Lound.png" width="400" /></a></div>Start from the assumption that existing knowledge is tainted or hidebound. We use deliberate ignorance to simplify by starting with fewer rules and constraints. Every added constraint, regulation to requirement is brutally challenged. Executives start with a blank slate and force people to justify adding complexity. Every issue is a "it isn't that complicated" discussion. People use this technique in different ways depending on how much heat needs to be applied. One of the problems with <i>deliberate ignorance</i> is people can't tell if the <i>ignorant one</i> is challenging everything to shake out requirements or if the <i>ignorant one</i> thinks all the existing people and processes are just stupid, Delivery is everything here. </div><h3 style="clear: both; text-align: left;">Driven Deliberate Ignorance - We should start here...</h3><div>One approach is to demand that existing processes be stripped of components and pieces until they stop working and then add that last piece back again. New processes are built in the simplest manner possible ignoring the existing processes or systems. "This is just a ...." is a common refrain when new people explain how the existing pieces are too complex. One might say this approach is a version of "I want you to do this..." suppressing any comments about complexity or reasons for the existing processes.</div><h3 style="text-align: left;">Interrogative Driven Ignorance - Why do we need that...</h3><div>Another approach is to question everything forcing people to defend why some process or delay exists. This often happens as part of an analysis cycle or in ideation or iteration reviews. This approach probably surfaces deeper requirements but gives existing power structures the chance to push back early.</div><h3 style="text-align: left;">Sidebar</h3><div>I once worked on a project where we were tactically deliberately ignorant. We wanted a market mover advantage and we thought the existing management was too "stuck". We captured what our system needed and not what the company needed as a whole. We picked up a couple smart people who gave us the minimum implementation rules for various operations and we built around that. The program ignored any of the complaints from other channels and changed the way the company operated in that space. It wasn't completely an MVP because the team had experience and layered in some of the Non-Functional Requirements needed to monitor the processes and leave space to implement the minimum legal and regulatory requirements.</div><div><br /></div><div><h2>Video</h2><div><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/9zzrTr8nNpg" width="640"></iframe></div><div><br /></div></div><h2 style="text-align: left;">Desperate Ignorance</h2><div>This strategy is where there just isn't time to know the right way to implement a system or process. It is a <i>deliberately not looking where you are going while running with scissors</i> kind of approach. Desperate ignorance says that pretty much everything will be ignored. Don't ask legal because they might give you some rules kind of problems. It is different than the previously discussed <i>Deliberate Ignorance</i> which uses a lack of knowledge as a path to simplification</div><h2 style="text-align: left;">Blind Ignorance</h2><div>You don't ask questions. You don't want anyone asking questions. The MVP is built by people without regulatory, privacy, financial, or other constraints. They operate in a vacuum ignoring any outside sources.</div><h2 style="text-align: left;">Revision History</h2><div><div>Created 2023/11</div><div class="separator" style="clear: both; text-align: center;"><br /></div><br /><br /></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-86487412754469431312023-12-03T19:32:00.004-05:002023-12-03T19:41:51.870-05:00Ask - Propose - Ask Why Not - Repeat - When new to a roleYou've just joined an org as an employee or a contractor and you want to contribute now. You have to contribute now to validate being there. now. This can lead to the urge to start spewing solutions based on your previous experience or what you saw others do somewhere successful. <div><br /></div><div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQyD3sInd11u0laTzVZFOi3lMgeCVXvxBEBXlJ59LgHzebgp843AifH_roeMhJ_-XE2IqpREWoqOL1_agD9Pj13h_eG5pD-WmWOJyZZ9Do384IhWvgskc1y06vADpnbfp7ncefZ9weG7DPEh_ugV39Wvm8fNHfJgutwIQkjn3WqXMt14wXTmOjDZB_Ltbk/s1280/Slide1.PNG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: center;"><img border="0" data-original-height="720" data-original-width="1280" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQyD3sInd11u0laTzVZFOi3lMgeCVXvxBEBXlJ59LgHzebgp843AifH_roeMhJ_-XE2IqpREWoqOL1_agD9Pj13h_eG5pD-WmWOJyZZ9Do384IhWvgskc1y06vADpnbfp7ncefZ9weG7DPEh_ugV39Wvm8fNHfJgutwIQkjn3WqXMt14wXTmOjDZB_Ltbk/s320/Slide1.PNG" width="320" /></a></div><div>Famous billionaires can force their will to what they want. The rest of us are best served by asking questions, asking the right questions, learning the language of the domain, and building credibility inside the organization. Then you have to differentiate the real from the imagined and drive towards improvement wall iteratively asking questions, proposing solutions, and teasing out the real from the imagined in the responses.</div><div><br /></div><div>I'm not saying to wait until you know everything until you contribute. Instead, work on two paths. The one where you can make contributions now and the one where you increase your credibility, create relationships, and become deep enough to have an impact.<br /><div><span style="color: #0d0d0d;"><span style="font-size: 15px; white-space-collapse: preserve;"><br /></span></span>
<div>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/AIorZJ6xdrA" width="640"></iframe>
</div>
<div>
<span face="Roboto, Noto, sans-serif" style="background-color: white; color: #0d0d0d; font-size: 15px; white-space-collapse: preserve;"><br /></span>
</div>
<div><span style="color: #0d0d0d;"><span style="background-color: white; font-size: 15px; white-space-collapse: preserve;">I've seen people drive real change even when they don't deeply understand the issues and are not domain experts. They do it by asking questions, exposing issues, suggesting a path and then repeating when people raise real and perceived issues.</span></span></div><div><span style="color: #0d0d0d;"><span style="background-color: white; font-size: 15px; white-space-collapse: preserve;"><br /></span></span></div><h2 style="text-align: left;">Presentation Materials</h2><div>Eventually, I'll get around to adding the presentation transcript</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQyD3sInd11u0laTzVZFOi3lMgeCVXvxBEBXlJ59LgHzebgp843AifH_roeMhJ_-XE2IqpREWoqOL1_agD9Pj13h_eG5pD-WmWOJyZZ9Do384IhWvgskc1y06vADpnbfp7ncefZ9weG7DPEh_ugV39Wvm8fNHfJgutwIQkjn3WqXMt14wXTmOjDZB_Ltbk/s1280/Slide1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="720" data-original-width="1280" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQyD3sInd11u0laTzVZFOi3lMgeCVXvxBEBXlJ59LgHzebgp843AifH_roeMhJ_-XE2IqpREWoqOL1_agD9Pj13h_eG5pD-WmWOJyZZ9Do384IhWvgskc1y06vADpnbfp7ncefZ9weG7DPEh_ugV39Wvm8fNHfJgutwIQkjn3WqXMt14wXTmOjDZB_Ltbk/w640-h360/Slide1.PNG" width="640" /></a></div><div><br /></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSNspFoBCIKWFYq_SNkL00XEa_RCBtDAh20zJdX8Kvl7ZxUJqQaBgp_aPouS5IfxHwQPr-fIUSsRYcPLwEuPISv_-Uyk-liFKLSkD4p0uDOtVsDPOCRoMWGauXJAEqg7-nOX99wTPcoMhP2r7TiJeFBC9RBSzcOhKmSfZ2iRp-FOwncwQBuuu3hQJ0d5_d/s1280/Slide2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="720" data-original-width="1280" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSNspFoBCIKWFYq_SNkL00XEa_RCBtDAh20zJdX8Kvl7ZxUJqQaBgp_aPouS5IfxHwQPr-fIUSsRYcPLwEuPISv_-Uyk-liFKLSkD4p0uDOtVsDPOCRoMWGauXJAEqg7-nOX99wTPcoMhP2r7TiJeFBC9RBSzcOhKmSfZ2iRp-FOwncwQBuuu3hQJ0d5_d/w640-h360/Slide2.PNG" width="640" /></a></div><div><br /></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgR6yW3ICJRoHwYb6DXosiBVNKrm1gbY6Fpb0Q-5SDy1esJrzElDSDmMzwGHPfZ_FUSol4PSpmywkKTzL4xRSucaRFSqRBf_DYaEhSnz-GGjUxohYxvSnab8lglLlQQveVar4N0GVFWsS90E6snzD_NtGgDhFFtmtw5JkSgkyzNfzLrBQRfFVv7_XGBG0n6/s1280/Slide3.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="720" data-original-width="1280" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgR6yW3ICJRoHwYb6DXosiBVNKrm1gbY6Fpb0Q-5SDy1esJrzElDSDmMzwGHPfZ_FUSol4PSpmywkKTzL4xRSucaRFSqRBf_DYaEhSnz-GGjUxohYxvSnab8lglLlQQveVar4N0GVFWsS90E6snzD_NtGgDhFFtmtw5JkSgkyzNfzLrBQRfFVv7_XGBG0n6/w640-h360/Slide3.PNG" width="640" /></a></div><div><br /></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgGTbdsHHkOO-Dt0tOWeift5AcwG-pKixpkY26R-z-RB7FOQPH3n-aCXG-l26Z9e5azurmZutH2ZA3xhMU9gJydbTgCsbMjYk4uhVP9J9HdPywCw826VBvLrfW2b_Hwr1FdMf8-m4d2eJSIvNtihAzjPZfLHNe_gCLTr-9eR2RE1korSc1vs_S1Z8p8m2S/s1280/Slide4.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="720" data-original-width="1280" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgGTbdsHHkOO-Dt0tOWeift5AcwG-pKixpkY26R-z-RB7FOQPH3n-aCXG-l26Z9e5azurmZutH2ZA3xhMU9gJydbTgCsbMjYk4uhVP9J9HdPywCw826VBvLrfW2b_Hwr1FdMf8-m4d2eJSIvNtihAzjPZfLHNe_gCLTr-9eR2RE1korSc1vs_S1Z8p8m2S/w640-h360/Slide4.PNG" width="640" /></a></div><div><br /></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKeUV174TSzBa8TBqYWFAkGLfcnoOQPqt9wLN4qG1Xv6pyg3ZPv2jzclNl0SbpYI7FpCfmYj6bKWi4gP6r64XFkrvVDDr1kroIZoWnWD0w3LPK7W6OzVSE1JIfy5IxDoKemcmQ9fefCG7e_s5hcsYf_3WpXG3Wkjo3kBp6_ipQfwOEh8-O8yaV10_XNW60/s1280/Slide5.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="720" data-original-width="1280" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKeUV174TSzBa8TBqYWFAkGLfcnoOQPqt9wLN4qG1Xv6pyg3ZPv2jzclNl0SbpYI7FpCfmYj6bKWi4gP6r64XFkrvVDDr1kroIZoWnWD0w3LPK7W6OzVSE1JIfy5IxDoKemcmQ9fefCG7e_s5hcsYf_3WpXG3Wkjo3kBp6_ipQfwOEh8-O8yaV10_XNW60/w640-h360/Slide5.PNG" width="640" /></a></div><br /><div><br /></div><div><span style="color: #0d0d0d;"><span style="background-color: white; font-size: 15px; white-space-collapse: preserve;"><br /></span></span></div>
<h3 style="text-align: left;">
<span face="Roboto, Noto, sans-serif" style="background-color: white; color: #0d0d0d; font-size: 15px; white-space-collapse: preserve;">Revision History</span>
</h3>
<div>
<span face="Roboto, Noto, sans-serif" style="background-color: white; color: #0d0d0d; font-size: 15px; white-space-collapse: preserve;">Created 2023 11</span>
</div>
</div></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-10675601295890280182023-12-01T19:38:00.007-05:002023-12-02T22:26:11.241-05:00Advent of Code 2023 -- A bootstrap article<p>Advent of Code is 25 days of coding problems, a kind of coding dojo with a daily exercise. You can use any language for any of the problems. The code isn't analyzed or shared. Only the results count. I know people working in Python, Dart, Haskell, Rust, and C#. </p><p>There are two problems per day, solved in order. The second problem only becomes visible once the first problem is solved. Each problem is driven by an input data set customized to your login session. The solution is often a number or some other small piece of data. You won't be able to copy someone else's answer.</p><p>Any scoring or tracking is up to you. You can create your own team leader board or just do the problems without tracking against anyone else.</p><p>The problem workflow is</p><p></p><ol style="text-align: left;"><li>Go to the website to read today's problem.</li><li>Request today's input data set using your session cookie</li><li>Write some code to generate a result</li><li>Post the result into to today's page</li><li>Earn stars and accolades.</li></ol><h3 style="text-align: left;">Show me! Don't make me read stuff</h3><div><a href="https://youtu.be/Zj6sT79o9cI?feature=shared">Advent of Code on YouTube - Joe's intro</a></div><p></p>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/Zj6sT79o9cI" width="640"></iframe>
<h2 style="text-align: left;">Sign Up</h2>
<div>Sign up at <a href="https://adventofcode.com">https://adventofcode.com</a>. This login session will stay active for the life of the 25-day exercise. Your session cookie is live for that full-time and can be programmatically used to get the input data and post your solution. </div><div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieYH7AC3NwdXkHo1xyAXDijaVzR521KimpqhX4Zl4yONXVSRphoIuefCmjYSv6vmcMza0HtUW3OXUv-E8TIpUuU9F0aKoIbU7g0QbA0S3Iu-DjxNTTaBc1XGCpnnTAaMD6S8tYEpUXjuMblDWJnJVimzAGUJHfszRfkkoWdCoQHuaR1BPDl9cB1youjSe_/s662/AdventOfCodeDayPageHomeUrl.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="100" data-original-width="662" height="96" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieYH7AC3NwdXkHo1xyAXDijaVzR521KimpqhX4Zl4yONXVSRphoIuefCmjYSv6vmcMza0HtUW3OXUv-E8TIpUuU9F0aKoIbU7g0QbA0S3Iu-DjxNTTaBc1XGCpnnTAaMD6S8tYEpUXjuMblDWJnJVimzAGUJHfszRfkkoWdCoQHuaR1BPDl9cB1youjSe_/w640-h96/AdventOfCodeDayPageHomeUrl.png" width="640" /></a></div><br /></div><div>You can sign up even if the event has already started. There is <b>no penalty</b> for joining late other than ranking on the leader board which you may not care about anyway.</div><h2 style="text-align: left;">Finding your session cookie</h2><div></div><div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFE2tdnhht-U4daapr-m4fg9hbT1aCAJ7GpL6t7u2IE7PIUXjBz0awTrAUpOcZMDeaIKh2Wmnq2EWmE0AmWYNBI4sWJAJ2ijPvH9GNkqN5q0IddserRpLaiBGDINjuUlaK6UDarbIUL5NxECbVh-llwf94H7k3AVAricN8h0iqmMYgJv55zOkE2sUzIVcV/s418/AdventOfCodeInspectPage.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: right;"><img border="0" data-original-height="409" data-original-width="418" height="196" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFE2tdnhht-U4daapr-m4fg9hbT1aCAJ7GpL6t7u2IE7PIUXjBz0awTrAUpOcZMDeaIKh2Wmnq2EWmE0AmWYNBI4sWJAJ2ijPvH9GNkqN5q0IddserRpLaiBGDINjuUlaK6UDarbIUL5NxECbVh-llwf94H7k3AVAricN8h0iqmMYgJv55zOkE2sUzIVcV/w200-h196/AdventOfCodeInspectPage.png" width="200" /></a>Your session cookie is live for that full-time and can be programmatically used to get the input data and post your solution. The cookie is part of every programmatic action. </div><div><div style="text-align: right;"><br /></div>Inspect the Advent of Code web page using your browser <i>inspect </i>capability. The basic startup workflow is</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><h2></h2></div><div><ol style="text-align: left;"><li><div class="separator"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipoAWE9Re_X_gMIxXxD1O0xZ0gO8LH91vBZLO4MvHmEOFAadZrGwk7zxZpn6PUwEm27H7EnqS4LQKA_gQu6oVKg-FSF6y3UY2WGcYN_bKtUMdcJv5p0KAEbRmUlRIPExvs8QVu5m9Eey93ApiEHNBKuI5febs81oH-py3aHTufZpdlli7X8W6rlJSeaF19/s691/AdventOfCodeSessionCookie.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: center;"><img border="0" data-original-height="653" data-original-width="691" height="302" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipoAWE9Re_X_gMIxXxD1O0xZ0gO8LH91vBZLO4MvHmEOFAadZrGwk7zxZpn6PUwEm27H7EnqS4LQKA_gQu6oVKg-FSF6y3UY2WGcYN_bKtUMdcJv5p0KAEbRmUlRIPExvs8QVu5m9Eey93ApiEHNBKuI5febs81oH-py3aHTufZpdlli7X8W6rlJSeaF19/s320/AdventOfCodeSessionCookie.png" width="320" /></a></div>Open your browser to the Advent of Code web page.</li><li>Right mouse and select <i>inspect</i>.</li><li><div>This should pull up the inspector panel</div></li><li>Refresh the page to populate the Network panel</li><li>Select the home page request, "2023" in this picture.</li><li>Select <i>Network </i>in the browser inspector's top pane</li><li>Select <i>Headers</i> in the browser inspector's bottom pane.</li><li>Find the header called <i>session</i></li><li>Copy the value of that header. You will need it to get access to <i>your </i>data sets.</li></ol><div><br /></div></div><div><br /></div><h2 style="text-align: left;">Start coding</h2><h3 style="text-align: left;">From Scratch</h3><div>You can just download your input data set to your machine and then start hacking code. </div><h3 style="text-align: left;">With a Template in your language of choice</h3><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAJdfSF9vragvNJLwlaYlr9tXI0R71LxFk2fJfq9nGLdrmVEM53DWdivd_uj5yQMTrJow-DY9L5MVPB8WWjc9KiatPODFOim540cjK9UGDWBrIlRnZotBkxesxwyqFcfZB7kA9lnsVSKiEyovZxwBwPe15zA8I3LMyuHqjf7a8KhWtZi063AoQzmWUlCnk/s1028/AdventOfCodeTemplateRepo.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="267" data-original-width="1028" height="104" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAJdfSF9vragvNJLwlaYlr9tXI0R71LxFk2fJfq9nGLdrmVEM53DWdivd_uj5yQMTrJow-DY9L5MVPB8WWjc9KiatPODFOim540cjK9UGDWBrIlRnZotBkxesxwyqFcfZB7kA9lnsVSKiEyovZxwBwPe15zA8I3LMyuHqjf7a8KhWtZi063AoQzmWUlCnk/w400-h104/AdventOfCodeTemplateRepo.png" width="400" /></a></div>A lot of people will use a template so that they can create a fresh working file every day for that day's problem. The template retrieves that day's data, writes the data to a file and generates a shell program for that day. People like the templates because they may have basic data manipulation dependencies already included along with helper methods.</div><div><br /></div><div>I am using a template for Dart that I Forked on GitHub. <a href="https://github.com/freemansoft/AdventOfCode-2023-Dart/tree/features/2023" target="_blank">Some 2023 work is in the 2023 branch</a>. It is a <a href="https://github.com/S-ecki/AdventOfCode-Starter-Dart">Fork of this Dart Template on GitHub</a>. The template manages new <i>Day-xx.dart</i> files with hooks for timers and other metrics. You should be able to find a template in the language of your choice if you exercise your Google-Fu.</div><div><h2>Problems appear</h2><div>Problems appear at fixed intervals, currently midnight ET. The interface is minimal. You will only see the title page if the problem isn't ready yet.</div></div><h2 style="text-align: left;">Looking at a Problem</h2><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ83POy_zyaHIZfsB5iYu1oaab8jBkhOr8vfQiGusd4EijLi40C9YPYesexF7TQtZfxmE1YAD-LkUm3eNuldsJRO7ff6RG-DZM3-KbxVy4y5PavC3fWMx5F14yHl3m3udj5acqZn3RlNdOl1pZc48zTi55Crpvpd9yKfbukUYXeA2N3NjVhKNbhi8NkHKL/s908/AdventOfCodeFindTheProblemAndScore.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="726" data-original-width="908" height="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ83POy_zyaHIZfsB5iYu1oaab8jBkhOr8vfQiGusd4EijLi40C9YPYesexF7TQtZfxmE1YAD-LkUm3eNuldsJRO7ff6RG-DZM3-KbxVy4y5PavC3fWMx5F14yHl3m3udj5acqZn3RlNdOl1pZc48zTi55Crpvpd9yKfbukUYXeA2N3NjVhKNbhi8NkHKL/s320/AdventOfCodeFindTheProblemAndScore.png" width="320" /></a></div>Problems appear under the current year. </div><div><br /></div><div>1. Click on the year in the upper left corner</div><div>1. You should see the number "1" or other day in the bottom right corner. Those are hyperlinks to that day's problems.</div><div>1. The highest number page may have a timer running. That is how long it will be until the next problem becomes available.</div><div><br /></div><div><br /></div><br /><h2 style="clear: both; text-align: left;"><div style="text-align: right;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNrfwPAqcvz6qFwCNkeQ3L33jM93R6hxfUqxFJlkcPhq_4q-AyBtsTDdI1puojJTeh2XIrKUNoEyetXDbstA3a4TXn0RwDfUoA3srj2VPfLgiXKTuxZIm2KIzBIZCs-gTYeOmjMproelHYm3fXa_SE79UL547EBDLxd9Wj96Y5vtKhtOfbnD5HJA_nHvPH/s1283/AdventOfCodeDayPage.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="1283" data-original-width="622" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNrfwPAqcvz6qFwCNkeQ3L33jM93R6hxfUqxFJlkcPhq_4q-AyBtsTDdI1puojJTeh2XIrKUNoEyetXDbstA3a4TXn0RwDfUoA3srj2VPfLgiXKTuxZIm2KIzBIZCs-gTYeOmjMproelHYm3fXa_SE79UL547EBDLxd9Wj96Y5vtKhtOfbnD5HJA_nHvPH/w310-h640/AdventOfCodeDayPage.png" width="310" /></a></div>The problem definition</h2>You can see the problem definition page once you click on that day's link. The picture to the right is a Day-1 problem.<div><br /></div><div>There are two problems per day. The 2nd problem only appears once the first problem is completed.</div><div><br /></div><div>Problems include a problem definition and backstory along with a tiny sample data set and the solution to that problem for that tiny dataset.</div><div><br /></div><div>The problem to the right accepts a single number as the result. </div><div><br /></div><div>The 2nd problem space is formatted the same way as the first. It appears when the first problem is completed. <br /><br /><div class="separator" style="clear: both; text-align: center;"><br /></div><h2 style="text-align: left;">Retrieving the Data - a Sample</h2><div>You can use Curl or your programming language of choice to download an input data set. I decided to use a template that included code that downloads the sample to be used while writing code and testing.</div><div><br /></div><div>This is a snippet of Dart code that downloads the input. You can see that it has a very regular URL and expects the session cookie that we looked at above.</div><div><br /></div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div> <span style="color: #569cd6;">final</span> <span style="color: #9cdcfe;">request</span> <span style="color: #d4d4d4;">=</span> <span style="color: #c586c0;">await</span> <span style="color: #4ec9b0;">HttpClient</span>().<span style="color: #dcdcaa;">getUrl</span>(</div><div> <span style="color: #4ec9b0;">Uri</span>.<span style="color: #dcdcaa;">parse</span>(</div><div> <span style="color: #ce9178;">'https://adventofcode.com/</span><span style="color: #d4d4d4;">$</span><span style="color: #9cdcfe;">year</span><span style="color: #ce9178;">/day/</span><span style="color: #d4d4d4;">${</span><span style="color: #4ec9b0;">int</span><span style="color: #d4d4d4;">.</span><span style="color: #dcdcaa;">parse</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">dayNumber</span><span style="color: #d4d4d4;">)}</span><span style="color: #ce9178;">/input'</span>,</div><div> ),</div><div> );</div><div> <span style="color: #9cdcfe;">request</span>.<span style="color: #9cdcfe;">cookies</span>.<span style="color: #dcdcaa;">add</span>(<span style="color: #4ec9b0;">Cookie</span>(<span style="color: #ce9178;">'session'</span>, <span style="color: #9cdcfe;">session</span>));</div><div></div></div><h2 style="text-align: left;">On Reddit</h2><div><a href="https://www.reddit.com/r/adventofcode/">https://www.reddit.com/r/adventofcode/</a></div><div><br /></div><h3 style="text-align: left;">Revision History</h3><div>Created 2023 11</div><p><br /></p><p><br /></p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><br /></div><br /></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-2062554752339296782023-11-30T20:57:00.006-05:002023-11-30T21:09:21.778-05:00Programmers' Credo: "We do these things not because they are easy...<p>Yet another tool in our understimation arsenal.</p><p></p><blockquote><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwBev8COjkJihb1m17dvuYx1vEvI7FIEjZ3ErJ93Dv1CmyoORwtYD3eRGk-RR0PN4kY2_B12zsJ9oX8PS7Cp0sypJaf_XPGoXqFCsxyV5reEag-sWqDP7uZ0aBy6lZdiCm39czl2TU-w3AOhruXpObKzCMtzgwCM89TC30Tcy5lBfGrg_XErvKEQTIatFw/s603/4c2uieuetqc41.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="179" data-original-width="603" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwBev8COjkJihb1m17dvuYx1vEvI7FIEjZ3ErJ93Dv1CmyoORwtYD3eRGk-RR0PN4kY2_B12zsJ9oX8PS7Cp0sypJaf_XPGoXqFCsxyV5reEag-sWqDP7uZ0aBy6lZdiCm39czl2TU-w3AOhruXpObKzCMtzgwCM89TC30Tcy5lBfGrg_XErvKEQTIatFw/s16000/4c2uieuetqc41.png" /></a></blockquote><div class="separator" style="clear: both; text-align: center;"><blockquote style="text-align: left;">We do these things not because they are easy, but because we <i>thought</i> they were going to be easy.</blockquote><p style="text-align: left;"></p></div><div><br /></div>This applies to more than programming. I'm looking at you, my most recent repair project.<br /><p>Credit goes to <a href="https://en.wikipedia.org/wiki/Jeff_Atwood?ref=blog.codinghorror.com" target="_blank">Jeff Atwood</a> around 2016</p><h3 style="text-align: left;">Revision History</h3><div>2023 11</div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-89953440547125965502023-11-20T18:40:00.008-05:002023-11-20T20:19:45.349-05:00The Crucible - Heating up teams with an agile coding dojo.<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5lKFvQjAs-zAzEwZyhKkH-ua1tOS_ZfyMO2v55ImTYhasLG3chpNazkf15TWMnm-08zkh0DnTElIexvRVtOicDWqmhAOMmtje-vHSmwG18zc8YYeesY4W1F7qe-S6-Mp_IBEdKt3eUttyGKIC7NeOU4Xq6UeIwIh5tyNqGJ1VvpRafUibt1BDlWP7z6AA/s220/220_F_367628781_ISfH3pSNSPf9NRwdAJYOFakJR2EPOMTX.jpg" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><span style="font-family: verdana;"><img border="0" data-original-height="147" data-original-width="220" height="147" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5lKFvQjAs-zAzEwZyhKkH-ua1tOS_ZfyMO2v55ImTYhasLG3chpNazkf15TWMnm-08zkh0DnTElIexvRVtOicDWqmhAOMmtje-vHSmwG18zc8YYeesY4W1F7qe-S6-Mp_IBEdKt3eUttyGKIC7NeOU4Xq6UeIwIh5tyNqGJ1VvpRafUibt1BDlWP7z6AA/s1600/220_F_367628781_ISfH3pSNSPf9NRwdAJYOFakJR2EPOMTX.jpg" width="220" /></span></a></div><div><span style="font-family: verdana;">An Agile coding dojo is a technique that can be used to create high-pressure software development situations at the team or project. That pressure exposes weaknesses and forces change and adaptations in short iterations. The idea is to change the game and accelerate the lifecycle to force behavioral change. It is a crucible where you either learn to deal with transparency and change your model or you fail. </span><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;">A coding dojo is a <i>local </i>optimization. The crucible operates at the team or set of teams level. It creates individual team cohesion and learning. It keeps the overall organization the same. It can <i>only</i> be the crucible for organizational change if more strategic and systemic re-forging is done above the effort.<br /></span><div><p><span style="font-family: verdana;">I was once involved in a 6-week Agile coding dojo where we compressed our standard 2-week iterations into 2.5-day iterations. All the work of a Scrum-style sprint in 2.5 days. Teams had to break down their work into smaller chunks and become fast at estimating, planning, and creating. It was the best of times and the worst of times. </span></p><p><span style="font-family: verdana;">2/3 of the teams crushed it. They adjusted to the accelerated time frame for planning execution, always had something to demonstrate, and gracefully handled frank feedback cycles. The teams continually adapted the short iteration feedback into their processes. </span></p><p><span style="font-family: verdana;">1/3 of the teams deliberately failed to understand the purpose of the exercise and accept the feedback. They continued to operate as they had in the past and effectively failed an exercise, which was about rapid failure and adjustment. Their management hadn't bought in and killed any future efforts effectively protecting the 1/3 from change.</span></p><h3 style="clear: both; text-align: left;"><span style="font-family: verdana;">The Martial Arts view of the dojo</span></h3><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><p class="sqsrte-large" style="background-color: white; font-size: 19.2px; line-height: var(--body-font-line-height); margin: 1rem 0px; overflow-wrap: break-word; text-align: left; white-space-collapse: preserve;"><span style="font-family: verdana;">A dojo is a crucible designed to uncover your weaknesses so that you can train through them.</span></p></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><p class="sqsrte-large" style="background-color: white; font-size: 19.2px; line-height: var(--body-font-line-height); margin: 1rem 0px; overflow-wrap: break-word; text-align: left; white-space-collapse: preserve;"><span style="font-family: verdana;">You can’t get weird around your weaknesses and failures, or try to avoid them, you have to actively seek them out!</span></p></blockquote><p><a href="https://www.lucianjames.com/30dojominddojobody"><span style="font-family: verdana;">https://www.lucianjames.com/30dojominddojobody</span></a></p><p><span style="font-family: verdana;">Frank appraisals and short failure cycles are expected.</span></p><h3><span style="font-family: verdana;">Other Concerns</span></h3><p><span style="font-family: verdana;"></span></p><div style="font-family: verdana;">The crunch time dojo experience may be better or worse for marginalized or neurodivergent workers. They may not want a high level of interaction or they may thrive under pressure with well-understood processes and goals. This should be monitored during the dojo.</div><h3 style="text-align: left;"><span style="font-family: verdana;">Thinking about the idea of a dojo</span></h3><p></p><p></p><ul style="text-align: left;"><li><a href="https://resources.scrumalliance.org/Article/enter-the-dojo"><span style="font-family: verdana;">Target and the dojo</span></a></li><li><a href="https://www.leadingagile.com/2021/05/why-investing-in-dojos-wont-transform-your-organization/"><span style="font-family: verdana;">Why investing in coding dojos won't transform your organization</span></a></li><li><a href="https://owasp.org/www-project-secure-coding-dojo/"><span style="font-family: verdana;">https://owasp.org/www-project-secure-coding-dojo/</span></a></li><li><a href="https://leanpub.com/codingdojohandbook" target="_blank"><span style="font-family: verdana;">The coding dojo handbook</span></a></li><li>Please suggest more links</li></ul><h3 style="text-align: left;"><span style="font-family: verdana;">Other Approaches</span></h3><div><span style="font-family: verdana;">SAFe agile has a methodology for transitioning from project to product and for changing the way teams think and operate. It is supposed to operate across the entire organization but I've only seen it be taken seriously at the individual program level. Even then it doesn't seem to work unless enough heat is applied for enough time. That year you budgeted for the SAFe transition isn't going cut it.</span></div>
<h3 style="text-align: left;"><span style="font-family: verdana;">Video</span></h3>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/vlgvKZmnCOU" width="640"></iframe>
<h3 style="text-align: left;">
<span style="font-family: verdana;">Revision History</span></h3><div><span style="font-family: verdana;">Created 2023 11</span></div></div></div></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-10656300988714613032023-11-19T22:28:00.008-05:002023-11-20T19:57:00.917-05:00The Crucible - Reforming an organization with heat<p></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFM0aUMSrj2IrSjFL_onI1DkbwfW7DO8gmDSLVROaRgC7pkyRc1Y-QyHKz7h9OF6I8nwpTycoqmcWExBidfuja0Sk5rVE6xCE_BrJFOi7ey9UERXBacrQrj-hHU5QUzoy0izGcCnoIfOe2u8LRbs7cx4ww0frYKIBIZN13PxbFwIO3baN24Y65RoHpUBL4/s825/liquid-workshop-orange-flame-fire-factory-1086757-pxhere.com.jpg" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="768" data-original-width="825" height="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFM0aUMSrj2IrSjFL_onI1DkbwfW7DO8gmDSLVROaRgC7pkyRc1Y-QyHKz7h9OF6I8nwpTycoqmcWExBidfuja0Sk5rVE6xCE_BrJFOi7ey9UERXBacrQrj-hHU5QUzoy0izGcCnoIfOe2u8LRbs7cx4ww0frYKIBIZN13PxbFwIO3baN24Y65RoHpUBL4/s320/liquid-workshop-orange-flame-fire-factory-1086757-pxhere.com.jpg" width="320" /></a></div><span style="font-family: verdana;">A <i>crucible</i> is where you melt down your existing metal and add new metals to create and cast a different alloy that fits the current need. This is often the only way to reshape an organization for the future. Major changes driven by external forces require dramatic adjustments outside the standard processes in order to drive change at a faster pace. </span><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;">Corporations and groups are like an alloy comprised of the company's stated culture, its actual culture, and the attitudes and beliefs of the people who work for it. The initial cast defines how a company operates and determines how it succeeds or fails. Big changes require attacking that historic casting.</span><p></p><p><span style="font-family: verdana;">Mature organizations struggle with deep change because they have hardened around policies and behavior that are broadly executed but may not be well understood. There is strength in this as it can resist or overcome transient business, regulatory, or economic issues. That strength is a weakness that makes it difficult to adapt and make smaller decisions before they become painfully large painful decisions. </span></p><p><span style="font-family: verdana;">Groups must often be broken into constituent parts and rebuilt with new blood or by empowering those that were not previously at the center. We need to make sure we are pressuring, melting, and mixing the right ingredients. The existing processes and people will resist saying that you are scrapping stuff that works or melting down something you will just have to make again in the same shape. Sometimes they are right but often that resistance is more broad-based and some people feel that just melting down pretty much everything is the way to go. You see this in organizations that bring in new leadership that drives out most of the old leadership culture guards. </span></p><p><span style="font-family: verdana;">I'll talk about approaches for creating a <i>crucible of change </i>at different levels in future articles.</span></p><h3 style="text-align: left;"><span style="font-family: verdana;">Getting it right</span></h3><div><span style="font-family: verdana;">Power structures defend the status quo. People worry about their current skills, the power that their knowledge of the existing system delivers, their rent payments, and family obligations. </span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;">One of the problems of a giant remake, putting the entire company into the crucible, is that you can mistake heat for progress. The worst outcome is that you go through the effort, melt everything down, and rebuild exactly what you had before. No one wants a wartime scrap-drive event where people's pots and pans were remade into pots and pans because the alloy wasn't right for the new purpose of making airplanes.</span></div><div><span style="font-family: verdana;"><h3 style="font-family: "Times New Roman";"><span style="font-family: verdana;">People Matter</span></h3><div style="font-family: "Times New Roman";"><span style="font-family: verdana;">This process doesn't need to be people-hostile. People matter and this can be done with dignity and in a way that can provide opportunity. Reforging is stressful. It is hard and some people won't make it out the other end. </span></div></span></div><h3 style="text-align: left;"><span style="font-family: verdana;">Putting every layer under stress</span></h3><div><span style="font-family: verdana;">Transformation must operate at multiple levels. Heat has to be applied at the team level and at the executive level and the management layers in between. I've seen people make change happen top down I've seen individual areas try and push change from the bottom up. </span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;">Top-down can fail when the intermediate layers play <i>passive resistance </i>knowing that the attention span from the top is often only one or two years, too short a period to make real change. Excessive turnover at the executive or senior level means continual priority changes as they try and prove they have what it takes.</span></div><div><span style="font-family: verdana;"><br /></span></div><div><span style="font-family: verdana;">Bottom-up can fail because the management tier and executives don't change, sticking with their same management and control patterns. This smothers the attempts from the bottom.</span></div><div><span style="font-family: verdana;"><h3 style="font-family: "Times New Roman";"><span style="font-family: verdana;">Organizations return to their previous shape if...</span></h3><div style="font-family: "Times New Roman";"><span style="font-family: verdana;">Organizations are like metal. They will bend under pressure but return pretty closely back to their previous shape unless they are heated past some temperature. We can take the opportunity at that time to change the mix/alloy in order to give the organization different properties. Failure to push the organization past its level of resistance results in a lot of wasted money and effort. Inevitably the people that supported that effort will leave in frustration or be driven out by the status quo.</span></div><div style="font-family: "Times New Roman";"><span style="font-family: verdana;"><br /></span></div>
<h2 style="font-family: "Times New Roman"; text-align: left;"><span style="font-family: verdana;">Video</span></h2><div style="font-family: "Times New Roman";"><span style="font-family: verdana;">
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/pAOX_DYXYJQ" width="640"></iframe>
</span></div></span></div>
<h3 style="text-align: left;"><span style="font-family: verdana;">What is a Crucible?</span></h3><div><p><span style="font-family: verdana;"><a href="https://www.merriam-webster.com/dictionary/crucible" target="_blank">The Meriam-Webster dictionary</a> has several definitions of <i>crucible</i> that fit what we are talking about</span></p><p></p><ol style="text-align: left;"><li><span style="font-family: verdana;">a vessel of a very refractory material (such as porcelain) used for melting and calcining a substance that requires a high degree of heat</span></li><li><span style="font-family: verdana;">a severe test; “He’s ready to face the crucible of the Olympics.”</span></li><li><span style="font-family: verdana;">a place or situation in which concentrated forces interact to cause or influence change or development</span></li></ol></div><p>
<a href="https://en.wikipedia.org/wiki/Crucible#:~:text=A%20crucible%20is%20a%20ceramic,or%20otherwise%20alter%20its%20contents." rel="" target="_blank"><span style="font-family: verdana;">A crucible is a vessel in which metallic elements are melted to be cast into new objects or to create a new alloy. </span></a></p>
<p>
<span style="font-family: verdana;"><a href="https://www.icscollaborative.com/webinars/transforming-organizations-the-crucible-of-change" target="_blank">A crucible is defined as a place or situation in which concentrated forces interact to cause or influence change or development. </a>
</span></p><p><span style="font-family: verdana;"><br /></span></p><h3 style="text-align: left;"><span style="font-family: verdana;">I've written about transformation and challenges before:</span></h3><p></p><ul style="text-align: left;"><li>
<span style="font-family: verdana;"><a href="https://joe.blog.freemansoft.com/2023/06/does-using-safe-mean-you-recognize-that.html">Does using SAFe mean you recognize that evolution is the best your bureaucracy can hope for?</a>
</span></li><li>
<a href="https://joe.blog.freemansoft.com/2023/05/companies-can-punish-those-that-buy-in.html"><span style="font-family: verdana;">
Companies can punish those who buy into transformation</span></a></li><li><a href="https://joe.blog.freemansoft.com/2023/03/organizational-response-to.html"><span style="font-family: verdana;">Organizational response to transformation - white blood cells swarming to stop an infection</span></a></li></ul><p></p><p><span style="font-family: verdana;"><br /></span></p><h3 style="text-align: left;"><span style="font-family: verdana;">A mess of messy links - to be updated</span></h3><p><span style="font-family: verdana;">https://govleaders.org/crucible.htm</span></p><h3 style="text-align: left;"><span style="font-family: verdana;">Revision History</span></h3><div><span style="font-family: verdana;">Created 2023 11</span></div></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-52818307350284420852023-11-19T12:26:00.008-05:002023-11-19T22:24:07.623-05:00Home FIOS Network - Exploring the 4 boxes and their connections<p>We have Verizon FIOS with cable TV service. I've never really paid attention to how the Verizon side is wired up until Verizon recently upgraded my FIOS router and tuner box. After breaking my TV tuner by disconnecting an "<i>unneeded" </i>connection, I created <i>yet another diagram</i> of how the FIOS connections work.</p><p>This is a basic wiring diagram of the house network missing a bunch of devices.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDwde5771vco-qk0HdE2tx1ZG0MMaKbsYSwaBmTZfrmwoXg7gESdme6oNcFNh9ClJnjI-Gmc2L2T6MkT5ILRRNLdfXbDKX0MTL0cU7HRQaL-NWBFnQdEcsZYmRpFeyjirTbhjV6PK9Z0HRMHoDIAsEuo-jqO8JLAC7Ps1VmFgoEi0tonQhr3JoNRNPX0-u/s612/Home-FIOS-Network.drawio.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="443" data-original-width="612" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDwde5771vco-qk0HdE2tx1ZG0MMaKbsYSwaBmTZfrmwoXg7gESdme6oNcFNh9ClJnjI-Gmc2L2T6MkT5ILRRNLdfXbDKX0MTL0cU7HRQaL-NWBFnQdEcsZYmRpFeyjirTbhjV6PK9Z0HRMHoDIAsEuo-jqO8JLAC7Ps1VmFgoEi0tonQhr3JoNRNPX0-u/s16000/Home-FIOS-Network.drawio.png" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><h2 style="text-align: left;">Verizon ONT</h2><div>The Verizon optical network terminal converts the optical connection into TV and network standard connections. The ONT is actually two boxes in my situation. One outside connects to the optical and one inside converts something into an Ethernet WAN connection. </div><p>This results in me connecting a TV COAX and an Ethernet WAN.</p><h2 style="text-align: left;">Verizon TV Tuner</h2><div>The Verizon TV tuner decodes and decrypts TV data that it receives over coax. The TV tuner must talk back to Verizon for any video control operations. It could talk back wireless, over an extra ethernet connection to back over the coax cable. My tuner communicates back over the coax cable. That coax signal must be converted onto some other data channel as it goes back through the optical network. The Verizon Router can handle this conversion.</div><h2 style="text-align: left;">Verizon Router</h2><div>I'm using the Verizon FIOS Gateway. It has 1 WAN connection, one COAX connection, and a few LAN connections. The Verizon GL3100A can be WAN attached over the Ethernet WAN connection or over the COAX connection if the WAN speed is 100Mbps or less. </div><div><br /></div><div>We have a 1GB WAN connection so I didn't connect the COAX to my router. It turns out that the TV Tuner communicates over WAN COAX which then needs to ride on a data channel back to Verizon. I needed to also connect the COAX network to the router in addition to the WAN/Ethernet connection we use for our 1GB data service.</div><h2 style="text-align: left;">Computers and Devices</h2><div>All computers, IoT devices, streaming devices, and others can be directly attached to the FIOS Gateway that has several wired Ethernet ports or they can join the Gateway's 2.4Ghz or 5Ghz wireless networks.</div><h2 style="text-align: left;">Other Options</h2><div>Cable cutters don't have a Verizon Router or COAX so they can ignore part of this.</div><div><br /></div><div>I once had a tuner box that communicated with Verizon over a private Wi-Fi channel on the Amazon Gateway / wireless router. It appears that the new system does not do that.</div><div><br /></div>
<h2 style="text-align: left;">Video</h2>
<div>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/QrkO4eevGTE" width="640"></iframe>
</div>
<h3 style="text-align: left;">Revision History</h3><div>Created 2023 11</div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-8966194939418978832023-11-12T19:01:00.003-05:002023-11-12T19:39:26.109-05:00Microsoft Authenticator Push Notification Variations<p>Have you ever noticed some behavior that made you think "That's different"? I noticed this with push notifications a while back.</p><p>Microsoft supports more than one Push Notification usage pattern in their Microsoft Authenticator application. Sometimes you will see more than one of them with the same account across different sites within the same organization.</p><ul><li>Push Notifications are more integrated with the authenticator application. The server indirectly sends a message to registered Authenticator applications. The user interacts with the Authenticator app and the app sends the response back to the server. MFA happens in the authenticator app from the human's point of view.</li></ul><div><div>Microsoft Authenticator also supports TOTP tokens as an alternative to Push Notifications.</div><ul><li>TOTP generates numeric sequences that can be used as part of MFA. Numbers change on a regular cadence. The user provides the current token value shown in the Authenticator app when a server asks for MFA data. The human copies the token value to a text field and submits it. MFA happens on the server from the human's point of view.</li></ul></div><h2>Push Notification</h2><div>Microsoft supports at least 3 different variations of Push notifications. You can acknowledge the push notification. You can select one of 4 options to approve or disapprove the push notification. Or, you can enter a multi-digit numeric code to acknowledge the notification.</div><div><br /></div><div>They imply varying levels of trust or complexity. I have no idea what server-side configurations determine which ones you get. </div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEAZ-lPKuHmyjh5t4BNUXCEObFf0ZsWm7N8KKQn6lrNmEWWfWX10SUEPdlItLIG5Cu557LMJ4fhkD3HUqwsEbbbT6yKjnN0Zkh4VT50ZdVUE58ncPqw96YrlzXO4fmUoqMkLSHIWRPdPU-nhS3zsrdIbw5EeCv4Yar8iq6g2kCSKtM3phyjkPzoM7nE8cC/s3840/Authenticator-Push.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="2274" data-original-width="3840" height="380" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEAZ-lPKuHmyjh5t4BNUXCEObFf0ZsWm7N8KKQn6lrNmEWWfWX10SUEPdlItLIG5Cu557LMJ4fhkD3HUqwsEbbbT6yKjnN0Zkh4VT50ZdVUE58ncPqw96YrlzXO4fmUoqMkLSHIWRPdPU-nhS3zsrdIbw5EeCv4Yar8iq6g2kCSKtM3phyjkPzoM7nE8cC/w640-h380/Authenticator-Push.png" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Click to enlarge</td></tr></tbody></table><br /><div><ul style="text-align: left;"><li>The simplest is a simple acknowledgment. The push notification just asks you to confirm you made a request. It expects you to examine the request code, "UIMYQ" in this case. You are expected to match that code against a code on the screen.</li><li>The middle model in the picture above expects you to select one of the codes. The numeric code appeared on the server prior to receiving this request. This forces you to verify a code and acknowledge the request. It is a higher level of verification than the first approach even though you have a 1/3 chance of guessing the code.</li><li>The push notification pictured on the right requests that the user enter the exact code presented on the screen or in some other way. The odds of getting this correct in a single guess are significantly lower than the two previous approaches. </li></ul></div><h2 style="text-align: left;">Push Flow</h2><div>Push notification MFA has several moving parts. The user makes a request to the server. The server determines that it requires some type of credentials that weren't provided. The server tells the Identity Provider that it requires valid credentials. The IDP asks the user for a username. The IDP then sends a Push Notification via Authenticator. The user acknowledges the Push Notification and approves the authentication request probably using one of the 3 paradigms described above. The IDP receives the response and presents the derived credentials to the original service.</div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV48E3FUfZurgOv97_FAGBlRBgH6PZ7LasTOQgNpBW7R1S0oIsX59ldtJSGn-FyEswXSSlQPWb75vr5477N8DcSQNWAU4UxerPs2P3aTCffL5BqAb1GKzrdGAouWrHmecjG0TkH3n55ZUX89PYP2x1cVGUpvc8p6KY2G-A1snyn_lrrbmP49rwDauLegrl/s589/Push-Flow.drawio.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="541" data-original-width="589" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV48E3FUfZurgOv97_FAGBlRBgH6PZ7LasTOQgNpBW7R1S0oIsX59ldtJSGn-FyEswXSSlQPWb75vr5477N8DcSQNWAU4UxerPs2P3aTCffL5BqAb1GKzrdGAouWrHmecjG0TkH3n55ZUX89PYP2x1cVGUpvc8p6KY2G-A1snyn_lrrbmP49rwDauLegrl/s16000/Push-Flow.drawio.png" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Click to Enlarge</td></tr></tbody></table><div class="separator" style="clear: both; text-align: center;"><br /></div><div><h2>TOTP - Not Push Notification</h2><div>Microsoft Authenticator, and others, can generate cryptographic tokens that can be used as MFA credentials. Those tokens change at regular intervals. </div><div><br /></div><div>Assuming the IDP has a token-based MFA. A user attempts to use some system. That system asks the IDP to ask for credentials. Users are asked for the current value of their specific token which they then provide. In some ways, this is a simpler flow.</div><div><br /></div><div>The following image shows an Authenticator app that is generating tokens for various sites. </div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjugCQ6fWYruuqbeyQtX6fkyJUjRvWu8iNfwBY91eBUSOYNkLUBuPR3MAMZGAVBLZHPvBLebKaslEgcLLLCX5KL4KTi8Ca0arkG4Rbzvdc5Qe-TGwzEJfbPq6wF9wZAEzx7Xtr7GKslf9OfZIuacNj52Y_ZbNp3bmQO78ujm83xKVRiQKm5clDCt7rTZVNs/s2079/IMG_0073.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2079" data-original-width="960" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjugCQ6fWYruuqbeyQtX6fkyJUjRvWu8iNfwBY91eBUSOYNkLUBuPR3MAMZGAVBLZHPvBLebKaslEgcLLLCX5KL4KTi8Ca0arkG4Rbzvdc5Qe-TGwzEJfbPq6wF9wZAEzx7Xtr7GKslf9OfZIuacNj52Y_ZbNp3bmQO78ujm83xKVRiQKm5clDCt7rTZVNs/w185-h400/IMG_0073.png" width="185" /></a></div><p></p>
<h2 style="text-align: left;">Video</h2>
</div>
<div>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/wmJhO3f5WJ0" width="640"></iframe>
</div>
<h3 style="clear: both; text-align: left;">Revision History </h3><div>Created 2023/11</div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-19044314107166940652023-11-08T22:14:00.008-05:002024-01-10T19:52:16.096-05:00Presentation and Communications ChecklistMake a checklist and review it before, during, and after creating a presentation. Here are 4 things that I have to remind myself whenever I create a presentation or information transfer content.<div><ol style="text-align: left;"><li>What are the top 3 things you wish to communicate?</li><li>Who is the Audience?</li><li>What outcomes are you trying to achieve? </li><li>How will the materials be used over time?</li></ol></div><div><br /><div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1yN6Vy_oFVhFIczS32FITnpV6Z-X8gxZvWLDBzst4zIqOJatk3xqVtlJXDGd7QmK5gLYF3_ZkaeYvJx1V_31TQKzfei90XmRMGN1pKv4tPvuYo6IRp-7KHi1p63WboCgJTq64QCMhl4Nt1fwK8wWszAzfABnR_rtkssvZB74PL4MIIsfHxLGVR0f1EMpl/s1259/Presentation-Checklist.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="724" data-original-width="1259" height="368" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1yN6Vy_oFVhFIczS32FITnpV6Z-X8gxZvWLDBzst4zIqOJatk3xqVtlJXDGd7QmK5gLYF3_ZkaeYvJx1V_31TQKzfei90XmRMGN1pKv4tPvuYo6IRp-7KHi1p63WboCgJTq64QCMhl4Nt1fwK8wWszAzfABnR_rtkssvZB74PL4MIIsfHxLGVR0f1EMpl/w640-h368/Presentation-Checklist.png" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Click to Enlarge</td></tr></tbody></table>
<div style="text-align: left;">The mindmap was created in <a href="https://app.mural.co/" target="_blank">Mural</a>. You may be able to see it <a href="https://app.mural.co/t/junkyard8497/m/junkyard8497/1699494526143/79e943459582b611cf02fe04b2eb6f5c26e4ad1c?sender=ueedea42e08f83a626b174736" target="_blank">with this Mural link</a> if it hasn't yet expired.</div><h2 style="text-align: left;">Video</h2><h2 style="text-align: left;"><div></div><div><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/imsnozELtPw" width="640"></iframe></div><div><br /></div><div><div style="font-size: medium; font-weight: 400;"><h2 style="text-align: left;">Partial transcript</h2><div><i>The following text is from the talk transcript and require clean up.</i></div><div><i><br /></i></div><div>This is really about how we're going to communicate? When we communicate with other people, we create a presentation or create some type of canned or interactive communication. What are the things we should think about? What are the kind of checklists we should have to make sure this communication is going to work the way we want?</div><div><br /></div><div>This really came out of reviewing a PowerPoint presentation. There were parts of it I didn't like about my contributions. There were parts I didn’t like about other people's contributions. How much was too much message in the time we had? What was the right level of depth?</div></div><div style="font-size: medium; font-weight: 400;"></div></div></h2><h2 style="text-align: left;">What are the top 3-5 things you wish to communicate?</h2><div>What are the top three to five things we want to get across? We used to call this the elevator pitch. If I were in an elevator for 30 seconds with somebody, what would the pitch be? What are the top three to five things I'm going to communicate? 30 seconds is all the time I've got. </div><div><br /></div><div>If I'm doing a communication in something canned, I still think what are the top three to five top-level bullet points. Then you can have a whole bunch of detail depending on the audience. The first question is what are the top three to five things I showed three topics and then a couple of those had subtopics. I'm drilling I'm giving a little more detail on the subtopics. </div><div><h2>Who is the Audience?</h2><div>Who is the audience? That determines the level of detail and it also gets bound into what outcome. If I'm doing this communication with specialists, that's going to be a lot different level of abstraction than it is a C-level. Now sometimes C-levels or VPs have been in your space before so we have got to put enough detail in and they'll go nitpick against what they know. But you know a specialist conversation might be very technical if it was an engineer. The VP is going to be very strategic. If I'm doing a supply chain presentation the supply chain team gets details and the executive team gets rollups. They need enough supporting detail in case somebody asks a question. Who's the audience? Are they leadership or are they your team? This is something you're going to iterate on while you're communicating with your team. Is it training documentation for your team or is it for partner teams? ?s it for a partner or friend-emy team. You know the teams you're supposed to be working with that really have no interest in working with you because they only care about what they're working on. Is it a customer we trying to communicate with? If so, what are we trying to communicate? Always ask who is the audience? That's going to determine the level of detail and how much background we need to provide. </div><div><h2>What is the desired outcome?</h2><div><ul style="text-align: left;"><li>What outcome do you want? </li><li>Why are you doing this communication? </li><li>Are you looking for buy-in? We've already we're making this decision or we think we want to make this decision we're trying to get people to buy in. </li><li>Are you expecting them to take action right then? I was in one presentation the other day and the entire talk was about what we needed another team to do. We had a certain set of requirements right. In that case, we needed them to take action or make a decision. “Yes, they're going to support this” or “No they're not” We want them to make a decision based on this communication.</li><li>This whole discussion may be a fait accompli and we’re just informing them with illusion of buy in.</li><li>Are we expecting to get a follow-up out of this? </li></ul></div><div>We want to avoid the horrible meeting type thing when you set up a meeting and you don't know what the desired outcome of the meeting is. When I give a presentation or we give a talk, maybe I want a follow-up. </div><div><ul style="text-align: left;"><li>“Hey we're going to do training on this here's the basics of this to get you interested” </li><li>“Now we need to schedule follow-up tasks”. </li><li>“We're going to do a follow-up communication with you to go flush this” </li></ul></div><div><h2>How will they use the materials?</h2><div><i>This section is a recording transcription and you can tell that by the flow.</i></div><div><p class="MsoNormal" style="line-height: normal; margin-bottom: 0in;"><span style="font-family: "Times New Roman",serif; font-size: 12.0pt; mso-fareast-font-family: "Times New Roman"; mso-font-kerning: 0pt; mso-ligatures: none;">Ask yourself<o:p></o:p></span></p>
<p class="MsoNormal" style="line-height: normal; margin-bottom: 0in;"></p><ul style="text-align: left;"><li><span style="font-family: "Times New Roman", serif; font-size: 12pt;">How will those
materials be used?</span></li><li>Will they be just
used in a presentation and communication? Maybe they will only be visible in a
recording like this one.</li><li>Are they going to be
used for train-the-trainer? If so, the docs that we provide actually
you're going to use the exact same way for the same purpose. </li></ul><p></p>
<p class="MsoNormal" style="line-height: normal; margin-bottom: 0in;"><span style="font-family: "Times New Roman",serif; font-size: 12.0pt; mso-fareast-font-family: "Times New Roman"; mso-font-kerning: 0pt; mso-ligatures: none;">Sometimes the
content is going to be primary documentation. I worked at a large Bank in
McLean VA. All our docs and all the executive documentation were
PowerPoint. They were super dense almost like Word documents with a lot
of text. Every executive communication was created in PowerPoint with a lot of
text, often with few diagrams making them feel more dense. </span></p>
<p class="MsoNormal" style="line-height: normal; margin-bottom: 0in;"><span style="font-family: "Times New Roman",serif; font-size: 12.0pt; mso-fareast-font-family: "Times New Roman"; mso-font-kerning: 0pt; mso-ligatures: none;">This is different
from the usual situation where we do a few words and lots of diagrams and
graphics in a presentation This lets you use it for different altitudes in
different audiences. You can tune what you say based on the audience or
what you're trying to make happen at that time.</span></p>
<p class="MsoNormal" style="line-height: normal; margin-bottom: 0in;"><span style="font-family: "Times New Roman",serif; font-size: 12.0pt; mso-fareast-font-family: "Times New Roman"; mso-font-kerning: 0pt; mso-ligatures: none;">The bank used
presentation materials as documentation so there were hundreds of words on
every page. It was horrible to communicate because you had to read every page.
You repeated all the words on every page. It was pretty evil. Then it was
passed around to others. In that case, it made sense because they didn’t
require explanation but they were rigid. One downside is that you can get
stuck in the words. Sometimes the abstractions are good like diagrams and
pictures because people understand that you just provide enough information.</span></p></div><h2 style="text-align: left;">Checklist review</h2><div><p class="MsoNormal">Our checklist includes<o:p></o:p></p>
<div style="text-align: left;"><ol style="text-align: left;"><li>The top three things we want to communicate</li><li>Who is the audience going to be </li><li>What outcome do I expect from this communication?</li><li>How will the materials be used in the future and what needs to be included to deal with that?</li></ol></div></div><div><br /></div></div></div></div></div>
<h3 style="text-align: left;">Revision History</h3><div>Created 2023/11</div></div><div>"How will they use.." revised 2024/01</div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-23269748718565950082023-10-15T12:01:00.004-04:002023-10-15T12:01:57.312-04:00Upgrade your laptop padding with an old mousepad.<span style="background-color: white; color: #0d0d0d; font-family: Roboto, Noto, sans-serif; font-size: 15px; white-space-collapse: preserve;">I have a backpack that I love except for the bottom-of-the-bag laptop protection. The laptop slots in some backpacks end before the bottom of the bag. My laptop sits on the bottom of the bag and I just don't trust the padding.</span><span style="background-color: white; color: #0d0d0d; font-family: Roboto, Noto, sans-serif; font-size: 15px; white-space-collapse: preserve;">
</span><span style="background-color: white; color: #0d0d0d; font-family: Roboto, Noto, sans-serif; font-size: 15px; white-space-collapse: preserve;">
</span><span style="background-color: white; color: #0d0d0d; font-family: Roboto, Noto, sans-serif; font-size: 15px; white-space-collapse: preserve;">Old </span><i style="background: rgb(255, 255, 255); border: 0px; color: #0d0d0d; font-family: Roboto, Noto, sans-serif; font-size: 15px; margin: 0px; padding: 0px; white-space-collapse: preserve;">swag</i><span style="background-color: white; color: #0d0d0d; font-family: Roboto, Noto, sans-serif; font-size: 15px; white-space-collapse: preserve;"> mouse pads make good extra padding. They are often a thick neoprene with material glued to one side. The thicker the neoprene the better the cushion. I just stuff my mouse pad down into the bottom of the backpack with the material side up. That puts the material side of the pad against my laptop when I slide it down into the fold. It doesn't have perfect corner coverage but it has saved me a few times.</span><span style="background-color: white; color: #0d0d0d; font-family: Roboto, Noto, sans-serif; font-size: 15px; white-space-collapse: preserve;">
</span><span style="background-color: white; color: #0d0d0d; font-family: Roboto, Noto, sans-serif; font-size: 15px; white-space-collapse: preserve;"> </span><p><span style="background-color: white; color: #0d0d0d; font-family: Roboto, Noto, sans-serif; font-size: 15px; white-space-collapse: preserve;">Slide the laptop <b>into the folded </b>mousepad. It only protects the laptop if the laptop is wrapped by the mouse pad.</span> </p><h2 style="text-align: left;">Use a mouse pad at the bottom of the laptop compartment.</h2><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtnX9UqxCtQe-jI2y9Wvuo86OoUHQ_naCR3Pg2wfpTLBMR6Up2yVIXGWSykDlZZG3vmLG8JtqTe0keo9WV1LHcC06rHFC4CcmhsnHpjXTo2QwS-BmBZrYclPYqPau1lB1Y0CarVmzSXqxpDg0UWH7BCeIjS0tMB3y1RgAuuXu6IhBd7O0y1qfDHN9ngx6u/s4032/IMG_0089%202.jpeg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="4032" data-original-width="3024" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtnX9UqxCtQe-jI2y9Wvuo86OoUHQ_naCR3Pg2wfpTLBMR6Up2yVIXGWSykDlZZG3vmLG8JtqTe0keo9WV1LHcC06rHFC4CcmhsnHpjXTo2QwS-BmBZrYclPYqPau1lB1Y0CarVmzSXqxpDg0UWH7BCeIjS0tMB3y1RgAuuXu6IhBd7O0y1qfDHN9ngx6u/s320/IMG_0089%202.jpeg" width="240" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><h2 style="clear: both; text-align: left;">The laptop slides into the fold</h2><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu17S81FIHekRsgAhBdbfL8WTAIqwO4REs2iHpHDahSbX9q5qTbZeKGt0vG6TzlhMAr1aQbT1BaS5eXy5x8NdZFdgYUcz7Vrj8DM_IOGwpWaljRZnUT1A_JJHp7iBM9wTvC4gBr16bPL-QaUALo-7I03bjbWr58n1hpK77NeNUgR3FHtAEhZljk6QNCMqb/s4032/IMG_0090%202.jpeg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="4032" data-original-width="3024" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu17S81FIHekRsgAhBdbfL8WTAIqwO4REs2iHpHDahSbX9q5qTbZeKGt0vG6TzlhMAr1aQbT1BaS5eXy5x8NdZFdgYUcz7Vrj8DM_IOGwpWaljRZnUT1A_JJHp7iBM9wTvC4gBr16bPL-QaUALo-7I03bjbWr58n1hpK77NeNUgR3FHtAEhZljk6QNCMqb/s320/IMG_0090%202.jpeg" width="240" /></a></div><br /><h2 style="text-align: left;">Get a better backpack?</h2><div>You can always get a backpack that holds the laptop off the bottom of the bag. It provides more protection. I tend to still stuff a mousepad into those slots also.</div><div><br /></div><h2 style="text-align: left;">Video Demo</h2>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/rezs0rsNnZs" width="640"></iframe>
<h3 style="text-align: left;">Revision History</h3><div>Created 2023 10</div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-45518210675273776442023-10-14T19:08:00.002-04:002023-10-14T19:08:26.278-04:00Remove Windows executables from your WSL Linux path<p>Windows and WSL interoperability is fantastic but sometimes you end up with unintended side effects. When coding I like to have specific tools and configurations for each environment. Otherwise, I can get pathing or other problems. Why is it complaining about a '\r' character when I'm running Linux? It is because some Linux tools and Windows files collide with each other.</p><p>With that in mind, you may wish to remove the Windows executables from your Linux $PATH. There are two ways to configure the WSL to not import the Windows $PATH.</p><p></p><ol style="text-align: left;"><li>You can disable or enable it on a per WSL instance basis. </li><li>You can globally configure WSL so that the PATHbehavior is the same for all WSL instances you have configured.</li></ol><p></p><h3 style="text-align: left;">Configuring individual WSL instances with wsl.conf</h3><div>Each individual WSL instance looks in its own configuration file <b>/etc/wsl.conf</b> that may not be created by default in your WSL instance.</div><div><br /></div><div>1. Edit the existing <b>/etc/wsl.conf </b>. Create a new <b>wsl.conf</b> file in /etc if one doesn't already exist. </div><div>1. Add the following section to <b>/etc/wsl.conf</b></div><div><br /></div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><br /><div> [<span style="color: #9cdcfe;">interop</span>]</div><div><span style="color: #9cdcfe;"> appendWindowsPath</span> <span style="color: #d4d4d4;">=</span> <span style="color: #569cd6;">false</span></div><br /></div><div><br /></div><div><i>wsl.conf'</i>s <b>interop</b> section can contain other settings other settings. They are outside of the scope of this discussion. You can read up on them at documented in https://learn.microsoft.com/en-us/windows/wsl/wsl-config#example-wslconf-file</div><div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; line-height: 19px; white-space: pre;"><div><h3><span style="font-size: 14px;"><br /></span></h3><h3><span style="font-size: 14px;"> # </span><span style="color: #4ec9b0; font-size: 14px;">Set</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">whether</span><span style="font-size: 14px;"> </span><span style="color: #d4d4d4; font-size: 14px;">WSL</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">supports</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">interop</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">processes</span><span style="font-size: 14px;"> </span><span style="color: #d4d4d4; font-size: 14px;">like</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">launching</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">Windows</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">apps</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">and</span><span style="font-size: 14px;">
adding </span><span style="color: #d4d4d4; font-size: 14px;">path</span><span style="font-size: 14px;"> </span><span style="color: #dcdcaa; font-size: 14px;">variables</span><span style="font-size: 14px;">. </span><span style="color: #d4d4d4; font-size: 14px;">Setting</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">these</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">to</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">false</span><span style="font-size: 14px;"> </span><span style="color: #d4d4d4; font-size: 14px;">will</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">block</span><span style="font-size: 14px;"> </span><span style="color: #d4d4d4; font-size: 14px;">the</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">launch
</span><span style="color: #9cdcfe; font-size: 14px;"> Windows</span><span style="font-size: 14px;"> </span><span style="color: #d4d4d4; font-size: 14px;">processes</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">and</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">block</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">adding</span><span style="font-size: 14px;"> </span><span style="color: #d4d4d4; font-size: 14px;">$PATH</span><span style="font-size: 14px;"> </span><span style="color: #9cdcfe; font-size: 14px;">environment</span><span style="font-size: 14px;"> </span><span style="color: #dcdcaa; font-size: 14px;">variables</span></h3></div><div style="font-size: 14px;"> [<span style="color: #9cdcfe;">interop</span>]</div><div style="font-size: 14px;"><span style="color: #9cdcfe;"> enabled</span> <span style="color: #d4d4d4;">=</span> <span style="color: #569cd6;">false</span></div><div style="font-size: 14px;"><span style="color: #9cdcfe;"> appendWindowsPath</span> <span style="color: #d4d4d4;">=</span> <span style="color: #569cd6;">false</span></div><span style="font-size: 14px;"><br /></span></div></div><div><br /></div><div><h3>Configuring all WSL instances identically using .wslconfig</h3><div>You can configure <i>interop </i>and other properties globally, across all WSL instances on this physical machine. </div><div><br /></div><div>Global WSL configuration can be shared across all WSL instances on a machine <i>.wslconfig </i>stored in your <b>Microsoft Windows</b> home directory. See https://learn.microsoft.com/en-us/windows/wsl/wsl-config#configuration-setting-for-wslconfig The WSL user's home directory is defined in <span face="SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace" style="background-color: #e6e6e6; color: #161616; font-size: 13.6px;">%UserProfile%</span> . It is typically something like <i>/Users/<username>.</i></div><div><br /></div><div>Ex: <i>Joe </i>is a Windows and WSL user whose Windows home directory is in c:/Users/Joe. The full path to <i>.wslconfig</i> would be <i>C:/Users/Joe/.wslconfig.</i></div><h3 style="text-align: left;">References</h3></div><p></p><ol style="text-align: left;"><li>https://github.com/microsoft/WSL/issues/4234</li><li>https://learn.microsoft.com/en-us/windows/wsl/wsl-config</li><li>https://learn.microsoft.com/en-us/windows/wsl/wsl-config#global-configuration-options-with-wslconfig</li><li>https://docs.flutter.dev/get-started/install/linux</li></ol><h3 style="text-align: left;">Revision History</h3><div>2023/10 Created</div><p></p>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-27365385995112518932023-09-17T21:24:00.008-04:002023-09-19T07:33:48.229-04:00Generate Model schemas from JSON and JSON Schemas in 20 languages using quicktype.io<p>Sometimes you just want to create Model objects around some type of JSON payload of schema definition. It seems like you either end up building your own tool, doing the conversion manually, or hunting around to find some schema generator. Sometimes there just isn't a tool that you want to use and sometimes there is.</p><p>I'm working Dart where we want to create immutable objects from deserialized JSON schema for AdaptiveCards. I ran across an open-source tool, quicktype.io, that can generate JSON serializable model classes for over 20 different languages. There are several different settings that let do some customization. You can then consume those classes/functions <i>as is</i> or tune them to your needs. It is a great learning aid even if you don't use it for your models. I <i>really</i> like how it lets me compare how different languages handle models and JSON.</p><p>Disclaimer: I have no idea who created <a href="https://quicktype.io">https://quicktype.io</a> or how it is run. Pull down <a href="https://github.com/glideapps/quicktype" target="_blank">their GitHub repo</a> and scan it for malware if you don't trust other websites with your JSON or JSON schema and need to run it locally.</p><h2 style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQN6EKVY4UbdS7u2V0beN0xcorb6JFvr8w2dYLhlaXXsKLua1ix5xnD-TnXYLh2pGWR9FafMBQW9ivQ4-InXCMtRAXaSAPYJohDNaxNHJ7htENgGW2PfiAMHsz-3tDhKw8yNxTV2GGXVLYzJ3vf6PG7wrfcLdjgQaGmGsSS9qFDNrO3rzJ6ZKgP2wXfyMA/s453/quicktype-language-selector.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="453" data-original-width="287" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQN6EKVY4UbdS7u2V0beN0xcorb6JFvr8w2dYLhlaXXsKLua1ix5xnD-TnXYLh2pGWR9FafMBQW9ivQ4-InXCMtRAXaSAPYJohDNaxNHJ7htENgGW2PfiAMHsz-3tDhKw8yNxTV2GGXVLYzJ3vf6PG7wrfcLdjgQaGmGsSS9qFDNrO3rzJ6ZKgP2wXfyMA/s16000/quicktype-language-selector.png" /></a></div>Pick a language</h2><div>The quicktype.io language selector. JSON serialization and deserialization are supported across all languages. Immutable and functional model objects are supported in some subset of the languages. My only experience is some exploration with Dart.</div><p></p><br /><h2 style="clear: both; text-align: left;">Generator Options for Various Languages</h2><div>Programming languages have their on requirements and capabilities. The language generators each have their own feature settings.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6E2Z4Orq1ru82doGa_BT0pdTBxeKUa4t5qu8z5p45kBKm-AUylC_QnzkF3MER3hnWRmCnh-EdN6xb0oK93PjMUB8S6OK0BrujkEzCa_r8AEhV8A1zswnGuil_HQi_egI__NJyLpQKlZk-7x_pzZ_NcGxPJdqSxoefOOlrYri0A2XDwPZBz0ezMdYnEx_/s836/quicktype-language-options.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="626" data-original-width="836" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6E2Z4Orq1ru82doGa_BT0pdTBxeKUa4t5qu8z5p45kBKm-AUylC_QnzkF3MER3hnWRmCnh-EdN6xb0oK93PjMUB8S6OK0BrujkEzCa_r8AEhV8A1zswnGuil_HQi_egI__NJyLpQKlZk-7x_pzZ_NcGxPJdqSxoefOOlrYri0A2XDwPZBz0ezMdYnEx_/w640-h480/quicktype-language-options.png" width="640" /></a></div><h2 style="text-align: left;">Video</h2>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/ImMYz6nPlgs" width="640"></iframe>
<h2 style="text-align: left;">Creating an Immutable Serializable Dart Class from JSON</h2><div>The Dart generator gives us an immutable, serializable Model object that looks like</div><div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div><span style="color: #569cd6;">import</span> <span style="color: #ce9178;">'package:meta/meta.dart'</span>;</div><div><span style="color: #569cd6;">import</span> <span style="color: #ce9178;">'dart:convert'</span>;</div><br /><br /><div><span style="color: #6a9955;">///A geographical coordinate</span></div><div><span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">Coordinate</span> {</div><div> <span style="color: #569cd6;">final</span> <span style="color: #4ec9b0;">double</span> <span style="color: #9cdcfe;">latitude</span>;</div><div> <span style="color: #569cd6;">final</span> <span style="color: #4ec9b0;">double</span> <span style="color: #9cdcfe;">longitude</span>;</div><br /><div> <span style="color: #d4d4d4;">Coordinate</span>({</div><div> <span style="color: #569cd6;">required</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #569cd6;">required</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">longitude</span>,</div><div> });</div><br /><div> <span style="color: #4ec9b0;">Coordinate</span> <span style="color: #dcdcaa;">copyWith</span>({</div><div> <span style="color: #4ec9b0;">double</span><span style="color: #d4d4d4;">?</span> <span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #4ec9b0;">double</span><span style="color: #d4d4d4;">?</span> <span style="color: #9cdcfe;">longitude</span>,</div><div> }) <span style="color: #d4d4d4;">=></span> </div><div> <span style="color: #4ec9b0;">Coordinate</span>(</div><div> <span style="color: #9cdcfe;">latitude</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">latitude</span> <span style="color: #d4d4d4;">??</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #9cdcfe;">longitude</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">longitude</span> <span style="color: #d4d4d4;">??</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">longitude</span>,</div><div> );</div><br /><div> <span style="color: #569cd6;">factory</span> <span style="color: #4ec9b0;">Coordinate</span>.<span style="color: #dcdcaa;">fromRawJson</span>(<span style="color: #4ec9b0;">String</span> <span style="color: #9cdcfe;">str</span>) <span style="color: #d4d4d4;">=></span> <span style="color: #4ec9b0;">Coordinate</span>.<span style="color: #d4d4d4;">fromJson</span>(<span style="color: #9cdcfe;">json</span>.<span style="color: #dcdcaa;">decode</span>(<span style="color: #9cdcfe;">str</span>));</div><br /><div> <span style="color: #4ec9b0;">String</span> <span style="color: #dcdcaa;">toRawJson</span>() <span style="color: #d4d4d4;">=></span> <span style="color: #9cdcfe;">json</span>.<span style="color: #dcdcaa;">encode</span>(<span style="color: #dcdcaa;">toJson</span>());</div><br /><div> <span style="color: #569cd6;">factory</span> <span style="color: #4ec9b0;">Coordinate</span>.<span style="color: #dcdcaa;">fromJson</span>(<span style="color: #4ec9b0;">Map</span><<span style="color: #4ec9b0;">String</span>, <span style="color: #4ec9b0;">dynamic</span>> <span style="color: #9cdcfe;">json</span>) <span style="color: #d4d4d4;">=></span> <span style="color: #4ec9b0;">Coordinate</span>(</div><div> <span style="color: #9cdcfe;">latitude</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">json</span>[<span style="color: #ce9178;">"latitude"</span>]<span style="color: #d4d4d4;">?</span>.<span style="color: #d4d4d4;">toDouble</span>(),</div><div> <span style="color: #9cdcfe;">longitude</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">json</span>[<span style="color: #ce9178;">"longitude"</span>]<span style="color: #d4d4d4;">?</span>.<span style="color: #d4d4d4;">toDouble</span>(),</div><div> );</div><br /><div> <span style="color: #4ec9b0;">Map</span><<span style="color: #4ec9b0;">String</span>, <span style="color: #4ec9b0;">dynamic</span>> <span style="color: #dcdcaa;">toJson</span>() <span style="color: #d4d4d4;">=></span> {</div><div> <span style="color: #ce9178;">"latitude"</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #ce9178;">"longitude"</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">longitude</span>,</div><div> };</div><div>}</div><br /></div></div><h2 style="text-align: left;">Python</h2><div>The Python generator created </div><div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div><span style="color: #c586c0;">from</span> <span style="color: #4ec9b0;">dataclasses</span> <span style="color: #c586c0;">import</span> <span style="color: #dcdcaa;">dataclass</span></div><div><span style="color: #c586c0;">from</span> <span style="color: #4ec9b0;">typing</span> <span style="color: #c586c0;">import</span> <span style="color: #4ec9b0;">Optional</span>, <span style="color: #9cdcfe;">Any</span>, <span style="color: #4ec9b0;">TypeVar</span>, <span style="color: #4ec9b0;">Type</span>, <span style="color: #dcdcaa;">cast</span></div><br /><br /><div><span style="color: #4fc1ff;">T</span> <span style="color: #d4d4d4;">=</span> <span style="color: #4ec9b0;">TypeVar</span>(<span style="color: #ce9178;">"T"</span>)</div><br /><br /><div><span style="color: #569cd6;">def</span> <span style="color: #dcdcaa;">from_float</span>(<span style="color: #9cdcfe;">x</span>: <span style="color: #9cdcfe;">Any</span>) -> <span style="color: #4ec9b0;">float</span>:</div><div> <span style="color: #c586c0;">assert</span> <span style="color: #dcdcaa;">isinstance</span>(<span style="color: #9cdcfe;">x</span>, (<span style="color: #4ec9b0;">float</span>, <span style="color: #4ec9b0;">int</span>)) <span style="color: #569cd6;">and</span> <span style="color: #569cd6;">not</span> <span style="color: #dcdcaa;">isinstance</span>(<span style="color: #9cdcfe;">x</span>, <span style="color: #4ec9b0;">bool</span>)</div><div> <span style="color: #c586c0;">return</span> <span style="color: #4ec9b0;">float</span>(<span style="color: #9cdcfe;">x</span>)</div><br /><br /><div><span style="color: #569cd6;">def</span> <span style="color: #dcdcaa;">from_none</span>(<span style="color: #9cdcfe;">x</span>: <span style="color: #9cdcfe;">Any</span>) -> <span style="color: #9cdcfe;">Any</span>:</div><div> <span style="color: #c586c0;">assert</span> <span style="color: #9cdcfe;">x</span> <span style="color: #569cd6;">is</span> <span style="color: #4fc1ff;">None</span></div><div> <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">x</span></div><br /><br /><div><span style="color: #569cd6;">def</span> <span style="color: #dcdcaa;">from_union</span>(<span style="color: #9cdcfe;">fs</span>, <span style="color: #9cdcfe;">x</span>):</div><div> <span style="color: #c586c0;">for</span> <span style="color: #9cdcfe;">f</span> <span style="color: #c586c0;">in</span> <span style="color: #9cdcfe;">fs</span>:</div><div> <span style="color: #c586c0;">try</span>:</div><div> <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">f</span>(<span style="color: #9cdcfe;">x</span>)</div><div> <span style="color: #c586c0;">except</span>:</div><div> <span style="color: #c586c0;">pass</span></div><div> <span style="color: #c586c0;">assert</span> <span style="color: #4fc1ff;">False</span></div><br /><br /><div><span style="color: #569cd6;">def</span> <span style="color: #dcdcaa;">to_float</span>(<span style="color: #9cdcfe;">x</span>: <span style="color: #9cdcfe;">Any</span>) -> <span style="color: #4ec9b0;">float</span>:</div><div> <span style="color: #c586c0;">assert</span> <span style="color: #dcdcaa;">isinstance</span>(<span style="color: #9cdcfe;">x</span>, <span style="color: #4ec9b0;">float</span>)</div><div> <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">x</span></div><br /><br /><div><span style="color: #569cd6;">def</span> <span style="color: #dcdcaa;">to_class</span>(<span style="color: #9cdcfe;">c</span>: <span style="color: #4ec9b0;">Type</span>[<span style="color: #4fc1ff;">T</span>], <span style="color: #9cdcfe;">x</span>: <span style="color: #9cdcfe;">Any</span>) -> <span style="color: #4ec9b0;">dict</span>:</div><div> <span style="color: #c586c0;">assert</span> <span style="color: #dcdcaa;">isinstance</span>(<span style="color: #9cdcfe;">x</span>, <span style="color: #9cdcfe;">c</span>)</div><div> <span style="color: #c586c0;">return</span> <span style="color: #dcdcaa;">cast</span>(<span style="color: #9cdcfe;">Any</span>, <span style="color: #9cdcfe;">x</span>).to_dict()</div><br /><br /><div><span style="color: #dcdcaa;">@</span><span style="color: #dcdcaa;">dataclass</span></div><div><span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">Coordinate</span>:</div><div> <span style="color: #ce9178;">"""A geographical coordinate"""</span></div><br /><div> <span style="color: #9cdcfe;">latitude</span>: <span style="color: #4ec9b0;">Optional</span>[<span style="color: #4ec9b0;">float</span>] <span style="color: #d4d4d4;">=</span> <span style="color: #4fc1ff;">None</span></div><div> <span style="color: #9cdcfe;">longitude</span>: <span style="color: #4ec9b0;">Optional</span>[<span style="color: #4ec9b0;">float</span>] <span style="color: #d4d4d4;">=</span> <span style="color: #4fc1ff;">None</span></div><br /><div> <span style="color: #dcdcaa;">@</span><span style="color: #4ec9b0;">staticmethod</span></div><div> <span style="color: #569cd6;">def</span> <span style="color: #dcdcaa;">from_dict</span>(<span style="color: #9cdcfe;">obj</span>: <span style="color: #9cdcfe;">Any</span>) -> <span style="color: #ce9178;">"</span><span style="color: #4ec9b0;">Coordinate</span><span style="color: #ce9178;">"</span>:</div><div> <span style="color: #c586c0;">assert</span> <span style="color: #dcdcaa;">isinstance</span>(<span style="color: #9cdcfe;">obj</span>, <span style="color: #4ec9b0;">dict</span>)</div><div> <span style="color: #9cdcfe;">latitude</span> <span style="color: #d4d4d4;">=</span> <span style="color: #dcdcaa;">from_union</span>([<span style="color: #dcdcaa;">from_float</span>, <span style="color: #dcdcaa;">from_none</span>], <span style="color: #9cdcfe;">obj</span>.<span style="color: #dcdcaa;">get</span>(<span style="color: #ce9178;">"latitude"</span>))</div><div> <span style="color: #9cdcfe;">longitude</span> <span style="color: #d4d4d4;">=</span> <span style="color: #dcdcaa;">from_union</span>([<span style="color: #dcdcaa;">from_float</span>, <span style="color: #dcdcaa;">from_none</span>], <span style="color: #9cdcfe;">obj</span>.<span style="color: #dcdcaa;">get</span>(<span style="color: #ce9178;">"longitude"</span>))</div><div> <span style="color: #c586c0;">return</span> <span style="color: #4ec9b0;">Coordinate</span>(<span style="color: #9cdcfe;">latitude</span>, <span style="color: #9cdcfe;">longitude</span>)</div><br /><div> <span style="color: #569cd6;">def</span> <span style="color: #dcdcaa;">to_dict</span>(<span style="color: #9cdcfe;">self</span>) -> <span style="color: #4ec9b0;">dict</span>:</div><div> <span style="color: #9cdcfe;">result</span>: <span style="color: #4ec9b0;">dict</span> <span style="color: #d4d4d4;">=</span> {}</div><div> <span style="color: #c586c0;">if</span> <span style="color: #9cdcfe;">self</span>.<span style="color: #9cdcfe;">latitude</span> <span style="color: #569cd6;">is</span> <span style="color: #569cd6;">not</span> <span style="color: #4fc1ff;">None</span>:</div><div> <span style="color: #9cdcfe;">result</span>[<span style="color: #ce9178;">"latitude"</span>] <span style="color: #d4d4d4;">=</span> <span style="color: #dcdcaa;">from_union</span>([<span style="color: #dcdcaa;">to_float</span>, <span style="color: #dcdcaa;">from_none</span>], <span style="color: #9cdcfe;">self</span>.<span style="color: #9cdcfe;">latitude</span>)</div><div> <span style="color: #c586c0;">if</span> <span style="color: #9cdcfe;">self</span>.<span style="color: #9cdcfe;">longitude</span> <span style="color: #569cd6;">is</span> <span style="color: #569cd6;">not</span> <span style="color: #4fc1ff;">None</span>:</div><div> <span style="color: #9cdcfe;">result</span>[<span style="color: #ce9178;">"longitude"</span>] <span style="color: #d4d4d4;">=</span> <span style="color: #dcdcaa;">from_union</span>([<span style="color: #dcdcaa;">to_float</span>, <span style="color: #dcdcaa;">from_none</span>], <span style="color: #9cdcfe;">self</span>.<span style="color: #9cdcfe;">longitude</span>)</div><div> <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">result</span></div><br /><br /><div><span style="color: #569cd6;">def</span> <span style="color: #dcdcaa;">coordinate_from_dict</span>(<span style="color: #9cdcfe;">s</span>: <span style="color: #9cdcfe;">Any</span>) -> <span style="color: #4ec9b0;">Coordinate</span>:</div><div> <span style="color: #c586c0;">return</span> <span style="color: #4ec9b0;">Coordinate</span>.<span style="color: #dcdcaa;">from_dict</span>(<span style="color: #9cdcfe;">s</span>)</div><br /><br /><div><span style="color: #569cd6;">def</span> <span style="color: #dcdcaa;">coordinate_to_dict</span>(<span style="color: #9cdcfe;">x</span>: <span style="color: #4ec9b0;">Coordinate</span>) -> <span style="color: #9cdcfe;">Any</span>:</div><div> <span style="color: #c586c0;">return</span> <span style="color: #dcdcaa;">to_class</span>(<span style="color: #4ec9b0;">Coordinate</span>, <span style="color: #9cdcfe;">x</span>)</div><br /></div></div><div style="text-align: left;"><br /></div><h2 style="text-align: left;">How good is it?</h2><div>The generators vary in completeness, capabilities and, how well they track language standards. IMO it is a good tool to see how pieces fit together even if you manually write your own classes or your own generator.</div><p>Some of the converters may be out of date with respect to the latest version of the language. You can always submit a pull request to bring it up to date. That is one of the beauties of OpenSource.</p><h3 style="text-align: left;">Revision History</h3><div>Created 2023/09</div><p><br /></p>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0tag:blogger.com,1999:blog-8922440028126506489.post-38660684720047009022023-09-17T21:20:00.014-04:002023-09-19T07:35:08.298-04:00Creating Immutable Functional Style Dart Model Objects based on JSON or JSON schemas<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWFYZY2UE7HT-w_F1PbaTSzwv4GQl8LT55F0SqzR-q_UM-HJVvvKElGsCquZmSvfonRNwXk95IVS-ewobG2b2Ab5xWWquGBX6kAQHi6l_y0m2tloHcnbwarhsEkLL-OwAyl1i3xFs7-bDiHAbBn_7NF2SEG08Y60mciSWPbESeb_rVaAHUmwVPd2VPqEV4/s747/quicktype-dart-fullscreen.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="692" data-original-width="747" height="370" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWFYZY2UE7HT-w_F1PbaTSzwv4GQl8LT55F0SqzR-q_UM-HJVvvKElGsCquZmSvfonRNwXk95IVS-ewobG2b2Ab5xWWquGBX6kAQHi6l_y0m2tloHcnbwarhsEkLL-OwAyl1i3xFs7-bDiHAbBn_7NF2SEG08Y60mciSWPbESeb_rVaAHUmwVPd2VPqEV4/w400-h370/quicktype-dart-fullscreen.png" width="400" /></a></div>Flutter is opinionated about using a functional programming model that includes immutable data objects. I've found that we tend to ignore that whole immutable thing when dealing with JSON data. <div><div><br /></div><div>Projects can be loose when handling incoming JSON data and how it is accessed, sprinkling string-style map keys across the codebase. We can reduce that tendency by providing models to eliminate the string keys and mutable models to stop people from directly manipulating maps of strings.<div><p></p><div>Some languages are functional only. Other languages have no way of generating immutable objects. In those cases, we just end up with objects that can be easily converted into and out of JSON structures and strings.</div><p>I'm working in Dart right now so the rest of this will be Dart samples</p><h2>Modeling Longitude and Latitude Using the JSON Schema</h2><h2><div style="font-size: medium; font-weight: 400;">Let's pick something simple as an example. We're going to use this Latitude and Longitude <i>JSON Schema</i> that follows the schema definition style from <a href="http://json-schema.org/draft-06/schema">http://json-schema.org/draft-06/schema</a>#<i>. </i>A coordinate on the earth's surface is represented by a <i>longitude </i>and a <i>latitude</i>.</div><div style="font-size: medium; font-weight: 400;"><i><br /></i></div><div style="font-size: medium; font-weight: 400;"><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div>{</div><div> <span style="color: #9cdcfe;">"id"</span>: <span style="color: #ce9178;">"http://json-schema.org/geo"</span>,</div><div> <span style="color: #9cdcfe;">"$schema"</span>: <span style="color: #ce9178;">"http://json-schema.org/draft-06/schema#"</span>,</div><div> <span style="color: #9cdcfe;">"description"</span>: <span style="color: #ce9178;">"A geographical coordinate"</span>,</div><div> <span style="color: #9cdcfe;">"type"</span>: <span style="color: #ce9178;">"object"</span>,</div><div> <span style="color: #9cdcfe;">"properties"</span>: {</div><div> <span style="color: #9cdcfe;">"latitude"</span>: {</div><div> <span style="color: #9cdcfe;">"type"</span>: <span style="color: #ce9178;">"number"</span></div><div> },</div><div> <span style="color: #9cdcfe;">"longitude"</span>: {</div><div> <span style="color: #9cdcfe;">"type"</span>: <span style="color: #ce9178;">"number"</span></div><div> }</div><div> }</div><div> }</div></div></div><div style="font-size: medium; font-weight: 400;"><br /></div></h2><h2>Watch the video</h2>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/pg43oZbpcxo" width="640"></iframe>
<h2>Incrementally creating the right kind of Dart Model Class</h2><div>I generated these files using <a href="https://app.quicktype.io/">https://app.quicktype.io/</a> sourced <a href="https://github.com/glideapps/quicktype" target="_blank">from this GitHub repository</a> I have no interest in the tool other than it saved me a few days of coding.</div><h3 style="text-align: left;">Starting with just property definitions</h3><div><br /></div><div>Enabling just <i style="font-weight: bold;">Types Only</i> results in</div><div><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div><span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">Coordinate</span> {</div><div> <span style="color: #4ec9b0;">double</span> <span style="color: #9cdcfe;">latitude</span>;</div><div> <span style="color: #4ec9b0;">double</span> <span style="color: #9cdcfe;">longitude</span>;</div><br /><div> <span style="color: #4ec9b0;">Coordinate</span>({</div><div> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">longitude</span>,</div><div> });</div><div>}</div></div></div><h3 style="text-align: left;">Making the objects immutable, unchangeable after creation.</h3><p></p><p>We want to operate in a Functional Programming model with immutable objects. Enabling <i style="font-weight: bold;">Make all properties required </i>and <b><i>Make all properties final </i></b>results in. Note the added <b>final</b> and <b>required</b>.</p><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div><span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">Coordinate</span> {</div><div> <span style="color: #569cd6;">final</span> <span style="color: #4ec9b0;">double</span> <span style="color: #9cdcfe;">latitude</span>;</div><div> <span style="color: #569cd6;">final</span> <span style="color: #4ec9b0;">double</span> <span style="color: #9cdcfe;">longitude</span>;</div><br /><div> <span style="color: #4ec9b0;">Coordinate</span>({</div><div> <span style="color: #569cd6;">required</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #569cd6;">required</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">longitude</span>,</div><div> });</div><br /><div>}</div></div><h3 style="text-align: left;">Simplify creating mutated copies of the data.</h3><p>Information changes. This means we need a way of creating new model objects that have one or more property differences from the object they were created from.</p><p>We want to make it easy on the programmer so we enable <i><b>Generate CopyWith</b> </i>that creates a new instance by making a copy with the requested changes applied. Note the added <i><b>CopyWith()</b> </i>method.</p><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div><span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">Coordinate</span> {</div><div> <span style="color: #569cd6;">final</span> <span style="color: #4ec9b0;">double</span> <span style="color: #9cdcfe;">latitude</span>;</div><div> <span style="color: #569cd6;">final</span> <span style="color: #4ec9b0;">double</span> <span style="color: #9cdcfe;">longitude</span>;</div><br /><div> <span style="color: #4ec9b0;">Coordinate</span>({</div><div> <span style="color: #569cd6;">required</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #569cd6;">required</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">longitude</span>,</div><div> });</div><br /><div> <span style="color: #4ec9b0;">Coordinate</span> <span style="color: #dcdcaa;">copyWith</span>({</div><div> <span style="color: #4ec9b0;">double</span><span style="color: #d4d4d4;">?</span> <span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #4ec9b0;">double</span><span style="color: #d4d4d4;">?</span> <span style="color: #9cdcfe;">longitude</span>,</div><div> }) <span style="color: #d4d4d4;">=></span> </div><div> <span style="color: #4ec9b0;">Coordinate</span>(</div><div> <span style="color: #9cdcfe;">latitude</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">latitude</span> <span style="color: #d4d4d4;">??</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #9cdcfe;">longitude</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">longitude</span> <span style="color: #d4d4d4;">??</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">longitude</span>,</div><div> );</div><div>}</div></div><h3 style="text-align: left;">We're all about serializing and deserializing our model with JSON</h3><p>The next step is to add JSON serialization and deserialization support. <i>quicktype</i> can generate global functions or static methods on the class. I'm a <i>classy </i>sort of person so I'll do the latter by turning off the <i><b>Types only</b> </i>selector. We get a class that can </p><p></p><ol><li>Create a model instance from a JSON string.</li><li>Create a model instance from a Map<String, dynamic></li><li>Create a JSON data structure from a model object instance.</li><li>Create a JSON string from a model object instance.</li></ol><p></p><div style="background-color: #1f1f1f; color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; line-height: 19px; white-space: pre;"><div><span style="color: #569cd6;">import</span> <span style="color: #ce9178;">'dart:convert'</span>;</div><br /><div><span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">Coordinate</span> {</div><div> <span style="color: #569cd6;">final</span> <span style="color: #4ec9b0;">double</span> <span style="color: #9cdcfe;">latitude</span>;</div><div> <span style="color: #569cd6;">final</span> <span style="color: #4ec9b0;">double</span> <span style="color: #9cdcfe;">longitude</span>;</div><br /><div> <span style="color: #d4d4d4;">Coordinate</span>({</div><div> <span style="color: #569cd6;">required</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #569cd6;">required</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">longitude</span>,</div><div> });</div><br /><div> <span style="color: #4ec9b0;">Coordinate</span> <span style="color: #dcdcaa;">copyWith</span>({</div><div> <span style="color: #4ec9b0;">double</span><span style="color: #d4d4d4;">?</span> <span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #4ec9b0;">double</span><span style="color: #d4d4d4;">?</span> <span style="color: #9cdcfe;">longitude</span>,</div><div> }) <span style="color: #d4d4d4;">=></span> </div><div> <span style="color: #4ec9b0;">Coordinate</span>(</div><div> <span style="color: #9cdcfe;">latitude</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">latitude</span> <span style="color: #d4d4d4;">??</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #9cdcfe;">longitude</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">longitude</span> <span style="color: #d4d4d4;">??</span> <span style="color: #569cd6;">this</span>.<span style="color: #9cdcfe;">longitude</span>,</div><div> );</div><br /><div> <span style="color: #569cd6;">factory</span> <span style="color: #4ec9b0;">Coordinate</span>.<span style="color: #dcdcaa;">fromRawJson</span>(<span style="color: #4ec9b0;">String</span> <span style="color: #9cdcfe;">str</span>) <span style="color: #d4d4d4;">=></span> <span style="color: #4ec9b0;">Coordinate</span>.<span style="color: #d4d4d4;">fromJson</span>(<span style="color: #9cdcfe;">json</span>.<span style="color: #dcdcaa;">decode</span>(<span style="color: #9cdcfe;">str</span>));</div><br /><div> <span style="color: #4ec9b0;">String</span> <span style="color: #dcdcaa;">toRawJson</span>() <span style="color: #d4d4d4;">=></span> <span style="color: #9cdcfe;">json</span>.<span style="color: #dcdcaa;">encode</span>(<span style="color: #dcdcaa;">toJson</span>());</div><br /><div> <span style="color: #569cd6;">factory</span> <span style="color: #4ec9b0;">Coordinate</span>.<span style="color: #dcdcaa;">fromJson</span>(<span style="color: #4ec9b0;">Map</span><<span style="color: #4ec9b0;">String</span>, <span style="color: #4ec9b0;">dynamic</span>> <span style="color: #9cdcfe;">json</span>) <span style="color: #d4d4d4;">=></span> <span style="color: #4ec9b0;">Coordinate</span>(</div><div> <span style="color: #9cdcfe;">latitude</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">json</span>[<span style="color: #ce9178;">"latitude"</span>]<span style="color: #d4d4d4;">?</span>.<span style="color: #d4d4d4;">toDouble</span>(),</div><div> <span style="color: #9cdcfe;">longitude</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">json</span>[<span style="color: #ce9178;">"longitude"</span>]<span style="color: #d4d4d4;">?</span>.<span style="color: #d4d4d4;">toDouble</span>(),</div><div> );</div><br /><div> <span style="color: #4ec9b0;">Map</span><<span style="color: #4ec9b0;">String</span>, <span style="color: #4ec9b0;">dynamic</span>> <span style="color: #dcdcaa;">toJson</span>() <span style="color: #d4d4d4;">=></span> {</div><div> <span style="color: #ce9178;">"latitude"</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">latitude</span>,</div><div> <span style="color: #ce9178;">"longitude"</span><span style="color: #d4d4d4;">:</span> <span style="color: #9cdcfe;">longitude</span>,</div><div> };</div><div>}</div></div><div><br /></div><div><h2>Using Annotations to Generate Serialization Code</h2><div>As an alternative... We can reduce the boiler-plate code by using annotations to help us generate the serialization code as part of the build process. </div><h2><div><div><div style="background-color: #1f1f1f; line-height: 19px;"><div><span style="color: #cccccc; font-family: Consolas, Courier New, monospace;"><span style="font-size: 14px; font-weight: 400; white-space: pre;">import 'package:meta/meta.dart';
import 'package:json_annotation/json_annotation.dart';
import 'dart:convert';
part 'coordinate.g.dart';
@JsonSerializable()
class Coordinate {
@JsonKey(name: "latitude")
final double latitude;
@JsonKey(name: "longitude")
final double longitude;
Coordinate({
required this.latitude,
required this.longitude,
});
Coordinate copyWith({
double? latitude,
double? longitude,
}) =>
Coordinate(
latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude,
);
factory Coordinate.fromJson(Map<String, dynamic> json) => _$CoordinateFromJson(json);
Map<String, dynamic> toJson() => _$CoordinateToJson(this);
}</span></span></div><div style="color: #cccccc; font-family: Consolas, "Courier New", monospace; font-size: 14px; font-weight: 400; white-space: pre;"><br /></div></div></div></div></h2></div><h3 style="text-align: left;">Endgame</h3><div>Real-world JSON schemas and modeling are a lot more complicated than this simple example. There are a variety of tools out there. At the time of this article, I'd recommend exploring JSON modeling including functional programming if your language supports it. I used <a href="https://app.quicktype.io">https://app.quicktype.io</a> but there are other tools out there.</div><div><br /></div><h3 style="text-align: left;">Revision History</h3><div>Created 2023/09</div></div></div></div>Joe Freemanhttp://www.blogger.com/profile/13642403834748872077noreply@blogger.com0