commit 318838659c1ae33fbeffbbdeaf78e44c076e90ad Author: Tyrel Souza Date: Sat Oct 14 23:45:03 2023 -0400 first commit diff --git a/data/blog/images/2012/02/graphite-menu.png b/data/blog/images/2012/02/graphite-menu.png new file mode 100644 index 0000000..74ac64a Binary files /dev/null and b/data/blog/images/2012/02/graphite-menu.png differ diff --git a/data/blog/images/2021/05/20210527_c27.jpg b/data/blog/images/2021/05/20210527_c27.jpg new file mode 100644 index 0000000..abcb749 Binary files /dev/null and b/data/blog/images/2021/05/20210527_c27.jpg differ diff --git a/data/blog/images/2021/05/20210527_hu16.jpg b/data/blog/images/2021/05/20210527_hu16.jpg new file mode 100644 index 0000000..ecc9690 Binary files /dev/null and b/data/blog/images/2021/05/20210527_hu16.jpg differ diff --git a/data/blog/images/2021/05/20210527_track.png b/data/blog/images/2021/05/20210527_track.png new file mode 100644 index 0000000..523a3bc Binary files /dev/null and b/data/blog/images/2021/05/20210527_track.png differ diff --git a/data/blog/images/2021/06/14-clouds.jpg b/data/blog/images/2021/06/14-clouds.jpg new file mode 100644 index 0000000..2a8a047 Binary files /dev/null and b/data/blog/images/2021/06/14-clouds.jpg differ diff --git a/data/blog/images/2021/06/14_DAN.gif b/data/blog/images/2021/06/14_DAN.gif new file mode 100644 index 0000000..09f763c Binary files /dev/null and b/data/blog/images/2021/06/14_DAN.gif differ diff --git a/data/blog/images/2021/06/14_TTA.gif b/data/blog/images/2021/06/14_TTA.gif new file mode 100644 index 0000000..3dee309 Binary files /dev/null and b/data/blog/images/2021/06/14_TTA.gif differ diff --git a/data/blog/images/2021/06/14_danville-VA.jpg b/data/blog/images/2021/06/14_danville-VA.jpg new file mode 100644 index 0000000..727366f Binary files /dev/null and b/data/blog/images/2021/06/14_danville-VA.jpg differ diff --git a/data/blog/images/2021/06/14_powerlines.jpg b/data/blog/images/2021/06/14_powerlines.jpg new file mode 100644 index 0000000..8dce12d Binary files /dev/null and b/data/blog/images/2021/06/14_powerlines.jpg differ diff --git a/data/blog/images/2021/06/14_tyrel-looking-left.jpg b/data/blog/images/2021/06/14_tyrel-looking-left.jpg new file mode 100644 index 0000000..0714e03 Binary files /dev/null and b/data/blog/images/2021/06/14_tyrel-looking-left.jpg differ diff --git a/data/blog/images/2021/06/14_tyrel-passenger-seat.jpg b/data/blog/images/2021/06/14_tyrel-passenger-seat.jpg new file mode 100644 index 0000000..beddba1 Binary files /dev/null and b/data/blog/images/2021/06/14_tyrel-passenger-seat.jpg differ diff --git a/data/blog/images/2021/06/14_tyrel-pulling.jpg b/data/blog/images/2021/06/14_tyrel-pulling.jpg new file mode 100644 index 0000000..4a36288 Binary files /dev/null and b/data/blog/images/2021/06/14_tyrel-pulling.jpg differ diff --git a/data/blog/images/2021/07/10_loop.png b/data/blog/images/2021/07/10_loop.png new file mode 100644 index 0000000..14ea05a Binary files /dev/null and b/data/blog/images/2021/07/10_loop.png differ diff --git a/data/blog/images/2021/07/10_propeller-and-dashboard.jpg b/data/blog/images/2021/07/10_propeller-and-dashboard.jpg new file mode 100644 index 0000000..6800ee1 Binary files /dev/null and b/data/blog/images/2021/07/10_propeller-and-dashboard.jpg differ diff --git a/data/blog/images/2021/07/10_right-side-haze.jpg b/data/blog/images/2021/07/10_right-side-haze.jpg new file mode 100644 index 0000000..6585a1c Binary files /dev/null and b/data/blog/images/2021/07/10_right-side-haze.jpg differ diff --git a/data/blog/images/2021/07/10_tony-and-tyrel.jpg b/data/blog/images/2021/07/10_tony-and-tyrel.jpg new file mode 100644 index 0000000..5f76d8c Binary files /dev/null and b/data/blog/images/2021/07/10_tony-and-tyrel.jpg differ diff --git a/data/blog/images/2021/07/10_tyrel-and-tony.jpg b/data/blog/images/2021/07/10_tyrel-and-tony.jpg new file mode 100644 index 0000000..4802b53 Binary files /dev/null and b/data/blog/images/2021/07/10_tyrel-and-tony.jpg differ diff --git a/data/blog/images/2021/07/10_tyrel-in-passenger-seat.jpg b/data/blog/images/2021/07/10_tyrel-in-passenger-seat.jpg new file mode 100644 index 0000000..299a286 Binary files /dev/null and b/data/blog/images/2021/07/10_tyrel-in-passenger-seat.jpg differ diff --git a/data/blog/images/2021/07/10_tyrel-pointing-out-window.jpg b/data/blog/images/2021/07/10_tyrel-pointing-out-window.jpg new file mode 100644 index 0000000..6e700e4 Binary files /dev/null and b/data/blog/images/2021/07/10_tyrel-pointing-out-window.jpg differ diff --git a/data/blog/images/2021/08/04_3d-track-1.jpg b/data/blog/images/2021/08/04_3d-track-1.jpg new file mode 100644 index 0000000..3aebacd Binary files /dev/null and b/data/blog/images/2021/08/04_3d-track-1.jpg differ diff --git a/data/blog/images/2021/08/04_3d-track-2.jpg b/data/blog/images/2021/08/04_3d-track-2.jpg new file mode 100644 index 0000000..c375592 Binary files /dev/null and b/data/blog/images/2021/08/04_3d-track-2.jpg differ diff --git a/data/blog/images/2021/08/04_cessna-152-cockpit.jpg b/data/blog/images/2021/08/04_cessna-152-cockpit.jpg new file mode 100644 index 0000000..69eae3b Binary files /dev/null and b/data/blog/images/2021/08/04_cessna-152-cockpit.jpg differ diff --git a/data/blog/images/2021/08/04_me-being-weird.jpg b/data/blog/images/2021/08/04_me-being-weird.jpg new file mode 100644 index 0000000..405dc57 Binary files /dev/null and b/data/blog/images/2021/08/04_me-being-weird.jpg differ diff --git a/data/blog/images/2021/08/08_behind_left_wing.jpg b/data/blog/images/2021/08/08_behind_left_wing.jpg new file mode 100644 index 0000000..a60b239 Binary files /dev/null and b/data/blog/images/2021/08/08_behind_left_wing.jpg differ diff --git a/data/blog/images/2021/08/08_behind_right_wing.jpg b/data/blog/images/2021/08/08_behind_right_wing.jpg new file mode 100644 index 0000000..4d1ab69 Binary files /dev/null and b/data/blog/images/2021/08/08_behind_right_wing.jpg differ diff --git a/data/blog/images/2021/08/08_cockpit_selfie.jpg b/data/blog/images/2021/08/08_cockpit_selfie.jpg new file mode 100644 index 0000000..581ca89 Binary files /dev/null and b/data/blog/images/2021/08/08_cockpit_selfie.jpg differ diff --git a/data/blog/images/2021/08/08_hazy-highway.jpg b/data/blog/images/2021/08/08_hazy-highway.jpg new file mode 100644 index 0000000..d18a909 Binary files /dev/null and b/data/blog/images/2021/08/08_hazy-highway.jpg differ diff --git a/data/blog/images/2021/08/08_hazy_runway.jpg b/data/blog/images/2021/08/08_hazy_runway.jpg new file mode 100644 index 0000000..f1c61c6 Binary files /dev/null and b/data/blog/images/2021/08/08_hazy_runway.jpg differ diff --git a/data/blog/images/2021/10/17_back-of-my-head.jpg b/data/blog/images/2021/10/17_back-of-my-head.jpg new file mode 100644 index 0000000..67f967e Binary files /dev/null and b/data/blog/images/2021/10/17_back-of-my-head.jpg differ diff --git a/data/blog/images/2021/10/17_cloudy-sun-view.jpg b/data/blog/images/2021/10/17_cloudy-sun-view.jpg new file mode 100644 index 0000000..0a4e843 Binary files /dev/null and b/data/blog/images/2021/10/17_cloudy-sun-view.jpg differ diff --git a/data/blog/images/2021/10/17_hannaford-kmart.jpg b/data/blog/images/2021/10/17_hannaford-kmart.jpg new file mode 100644 index 0000000..51e1f98 Binary files /dev/null and b/data/blog/images/2021/10/17_hannaford-kmart.jpg differ diff --git a/data/blog/images/2021/10/17_landing-32-14-on-final.jpg b/data/blog/images/2021/10/17_landing-32-14-on-final.jpg new file mode 100644 index 0000000..c499a9a Binary files /dev/null and b/data/blog/images/2021/10/17_landing-32-14-on-final.jpg differ diff --git a/data/blog/images/2021/10/17_landing-32-14-short-final.jpg b/data/blog/images/2021/10/17_landing-32-14-short-final.jpg new file mode 100644 index 0000000..27f1632 Binary files /dev/null and b/data/blog/images/2021/10/17_landing-32-14-short-final.jpg differ diff --git a/data/blog/images/2021/10/17_left-wing-looking-at-airport-and-monadnock.jpg b/data/blog/images/2021/10/17_left-wing-looking-at-airport-and-monadnock.jpg new file mode 100644 index 0000000..78089a8 Binary files /dev/null and b/data/blog/images/2021/10/17_left-wing-looking-at-airport-and-monadnock.jpg differ diff --git a/data/blog/images/2021/10/17_me_mom_n43337.jpg b/data/blog/images/2021/10/17_me_mom_n43337.jpg new file mode 100644 index 0000000..17c27f1 Binary files /dev/null and b/data/blog/images/2021/10/17_me_mom_n43337.jpg differ diff --git a/data/blog/images/2021/10/17_sun-above-right-wing.jpg b/data/blog/images/2021/10/17_sun-above-right-wing.jpg new file mode 100644 index 0000000..ecb30bd Binary files /dev/null and b/data/blog/images/2021/10/17_sun-above-right-wing.jpg differ diff --git a/data/blog/images/2021/10/17_turning-from-backseat.jpg b/data/blog/images/2021/10/17_turning-from-backseat.jpg new file mode 100644 index 0000000..4d8f8ee Binary files /dev/null and b/data/blog/images/2021/10/17_turning-from-backseat.jpg differ diff --git a/data/blog/images/2021/11/github_cli-alex_prompt.png b/data/blog/images/2021/11/github_cli-alex_prompt.png new file mode 100644 index 0000000..0411000 Binary files /dev/null and b/data/blog/images/2021/11/github_cli-alex_prompt.png differ diff --git a/data/blog/images/2021/11/github_cli-prompting_and_table.png b/data/blog/images/2021/11/github_cli-prompting_and_table.png new file mode 100644 index 0000000..5f4d251 Binary files /dev/null and b/data/blog/images/2021/11/github_cli-prompting_and_table.png differ diff --git a/data/blog/images/2021/11/github_cli-pytest_running.png b/data/blog/images/2021/11/github_cli-pytest_running.png new file mode 100644 index 0000000..046ddf3 Binary files /dev/null and b/data/blog/images/2021/11/github_cli-pytest_running.png differ diff --git a/data/blog/images/2022/01/09_relay.jpg b/data/blog/images/2022/01/09_relay.jpg new file mode 100644 index 0000000..fb8ef3c Binary files /dev/null and b/data/blog/images/2022/01/09_relay.jpg differ diff --git a/data/blog/images/2022/01/garage-Garage_door_schematic.png b/data/blog/images/2022/01/garage-Garage_door_schematic.png new file mode 100644 index 0000000..f0ebf9a Binary files /dev/null and b/data/blog/images/2022/01/garage-Garage_door_schematic.png differ diff --git a/data/blog/images/2022/01/garage-Lovelace_garage_door_closed.png b/data/blog/images/2022/01/garage-Lovelace_garage_door_closed.png new file mode 100644 index 0000000..334ec2b Binary files /dev/null and b/data/blog/images/2022/01/garage-Lovelace_garage_door_closed.png differ diff --git a/data/blog/images/2022/01/garage-magnetic_reed_switch.png b/data/blog/images/2022/01/garage-magnetic_reed_switch.png new file mode 100644 index 0000000..07e06cc Binary files /dev/null and b/data/blog/images/2022/01/garage-magnetic_reed_switch.png differ diff --git a/data/blog/images/2022/01/garage-nodemcu_esp8266_module.jpg b/data/blog/images/2022/01/garage-nodemcu_esp8266_module.jpg new file mode 100644 index 0000000..24fddeb Binary files /dev/null and b/data/blog/images/2022/01/garage-nodemcu_esp8266_module.jpg differ diff --git a/data/blog/images/2022/01/garage-pulses_180ms_140ms.png b/data/blog/images/2022/01/garage-pulses_180ms_140ms.png new file mode 100644 index 0000000..2e021e0 Binary files /dev/null and b/data/blog/images/2022/01/garage-pulses_180ms_140ms.png differ diff --git a/data/blog/images/2022/06/bl2cam-leds.jpg b/data/blog/images/2022/06/bl2cam-leds.jpg new file mode 100644 index 0000000..a163222 Binary files /dev/null and b/data/blog/images/2022/06/bl2cam-leds.jpg differ diff --git a/data/blog/images/2022/06/ebook-ebook_reader.png b/data/blog/images/2022/06/ebook-ebook_reader.png new file mode 100644 index 0000000..e0c24f3 Binary files /dev/null and b/data/blog/images/2022/06/ebook-ebook_reader.png differ diff --git a/data/blog/images/2022/10/scrollbar-chrome.png b/data/blog/images/2022/10/scrollbar-chrome.png new file mode 100644 index 0000000..bf9e2d4 Binary files /dev/null and b/data/blog/images/2022/10/scrollbar-chrome.png differ diff --git a/data/blog/images/2022/10/scrollbar-firefox.png b/data/blog/images/2022/10/scrollbar-firefox.png new file mode 100644 index 0000000..5a1364a Binary files /dev/null and b/data/blog/images/2022/10/scrollbar-firefox.png differ diff --git a/data/blog/images/2022/10/scrollbar-safari.png b/data/blog/images/2022/10/scrollbar-safari.png new file mode 100644 index 0000000..21b8be7 Binary files /dev/null and b/data/blog/images/2022/10/scrollbar-safari.png differ diff --git a/data/blog/images/2022/11/04_heater.png b/data/blog/images/2022/11/04_heater.png new file mode 100644 index 0000000..0cdef03 Binary files /dev/null and b/data/blog/images/2022/11/04_heater.png differ diff --git a/data/blog/images/2022/11/04_lights.jpg b/data/blog/images/2022/11/04_lights.jpg new file mode 100644 index 0000000..bc80906 Binary files /dev/null and b/data/blog/images/2022/11/04_lights.jpg differ diff --git a/data/blog/images/2022/11/04_nodered.png b/data/blog/images/2022/11/04_nodered.png new file mode 100644 index 0000000..254f106 Binary files /dev/null and b/data/blog/images/2022/11/04_nodered.png differ diff --git a/data/blog/images/2022/11/04_servo.png b/data/blog/images/2022/11/04_servo.png new file mode 100644 index 0000000..889be0d Binary files /dev/null and b/data/blog/images/2022/11/04_servo.png differ diff --git a/data/blog/images/2022/11/04_stepper.png b/data/blog/images/2022/11/04_stepper.png new file mode 100644 index 0000000..abfdc23 Binary files /dev/null and b/data/blog/images/2022/11/04_stepper.png differ diff --git a/data/blog/images/2022/11/04_stepper_wheel.png b/data/blog/images/2022/11/04_stepper_wheel.png new file mode 100644 index 0000000..4909567 Binary files /dev/null and b/data/blog/images/2022/11/04_stepper_wheel.png differ diff --git a/data/blog/images/2022/11/04_webpage.png b/data/blog/images/2022/11/04_webpage.png new file mode 100644 index 0000000..b4989cb Binary files /dev/null and b/data/blog/images/2022/11/04_webpage.png differ diff --git a/data/blog/images/2023/01/NES_Atlantico.png b/data/blog/images/2023/01/NES_Atlantico.png new file mode 100644 index 0000000..bd6fa0a Binary files /dev/null and b/data/blog/images/2023/01/NES_Atlantico.png differ diff --git a/data/blog/images/2023/01/NES_Console.png b/data/blog/images/2023/01/NES_Console.png new file mode 100644 index 0000000..b26e897 Binary files /dev/null and b/data/blog/images/2023/01/NES_Console.png differ diff --git a/data/blog/images/2023/01/dosbox_1_environment_menu.png b/data/blog/images/2023/01/dosbox_1_environment_menu.png new file mode 100644 index 0000000..8284997 Binary files /dev/null and b/data/blog/images/2023/01/dosbox_1_environment_menu.png differ diff --git a/data/blog/images/2023/01/dosbox_2_directories.png b/data/blog/images/2023/01/dosbox_2_directories.png new file mode 100644 index 0000000..b6d149f Binary files /dev/null and b/data/blog/images/2023/01/dosbox_2_directories.png differ diff --git a/data/blog/images/2023/01/dosbox_3_directories_edit.png b/data/blog/images/2023/01/dosbox_3_directories_edit.png new file mode 100644 index 0000000..992d77c Binary files /dev/null and b/data/blog/images/2023/01/dosbox_3_directories_edit.png differ diff --git a/data/blog/images/2023/01/dosbox_4_directories_filled.png b/data/blog/images/2023/01/dosbox_4_directories_filled.png new file mode 100644 index 0000000..1411f0b Binary files /dev/null and b/data/blog/images/2023/01/dosbox_4_directories_filled.png differ diff --git a/data/blog/images/2023/01/dosbox_5_save_config.png b/data/blog/images/2023/01/dosbox_5_save_config.png new file mode 100644 index 0000000..b9d61f9 Binary files /dev/null and b/data/blog/images/2023/01/dosbox_5_save_config.png differ diff --git a/data/blog/images/2023/09/26_ls.png b/data/blog/images/2023/09/26_ls.png new file mode 100644 index 0000000..80e3f1c Binary files /dev/null and b/data/blog/images/2023/09/26_ls.png differ diff --git a/data/blog/images/2023/09/26_which.png b/data/blog/images/2023/09/26_which.png new file mode 100644 index 0000000..c1f5efb Binary files /dev/null and b/data/blog/images/2023/09/26_which.png differ diff --git a/data/blog/pages/404.rst b/data/blog/pages/404.rst new file mode 100644 index 0000000..ee6ae2b --- /dev/null +++ b/data/blog/pages/404.rst @@ -0,0 +1,13 @@ +404 +### +:date: 2022-10-16 00:00 +:author: tyrel +:category: Website +:slug: 404 +:status: hidden + + +404 +~~~ + +Please go back and try again. Page is missing. \ No newline at end of file diff --git a/data/blog/pages/about.rst b/data/blog/pages/about.rst new file mode 100644 index 0000000..dccd0b9 --- /dev/null +++ b/data/blog/pages/about.rst @@ -0,0 +1,36 @@ +About +###### +:date: 2022-05-01 00:00 +:author: tyrel +:category: About +:slug: about +:status: published + +About Tyrel +=========== + + +Senior Software Engineer with a back end focus. Specializing in Python and Go. + + + +Licenses +~~~~~~~~ + +* I have a Ham Radio License (Amateur Radio) +* I have a Restricted Radiotelephone Operator Permit (RR - for flying in certain countries) +* I have a General Mobile Radio Service (GMRS) +* I have a North Carolina Driver's License +* I have a North Carolina "Boater Education Card" (boating license for those of us born after 1988-01-31) +* I have a North Carolina Motorcycle Endorsement/License +* I have a Pilot's license +* I am an Ordained Minister at the Universal Life Church Monastery +* I am a North Carolina Notary Public - Commission expiring May 30, 2027 + + +Site notes +~~~~~~~~~~ + +This blog is proudly powered by `Pelican `_, which takes great advantage of Python. + +The Theme is modified from `Blue Penguin Dark `_ Theme diff --git a/data/blog/pages/active_projects.rst b/data/blog/pages/active_projects.rst new file mode 100644 index 0000000..d2c49e1 --- /dev/null +++ b/data/blog/pages/active_projects.rst @@ -0,0 +1,13 @@ +Active Projects +############### +:date: 2022-10-16 00:00 +:author: tyrel +:category: Tech +:slug: active-projects +:status: draft + +I'm always tooling around on a couple things in the background to keep myself fresh with some programming languiages. + +* `itor `_ - a flashcard tool, create and practice with flash cards. 100% meant as a BubbleTea learning tool for TUIs in Go. + +* `Frank Tank `_ - Manipulating a water heater from a NodeMCU and Stepper motor. diff --git a/data/blog/pages/blogroll.rst b/data/blog/pages/blogroll.rst new file mode 100644 index 0000000..42e7c93 --- /dev/null +++ b/data/blog/pages/blogroll.rst @@ -0,0 +1,23 @@ +Blogroll +######## +:date: 2022-11-07 00:00 +:author: tyrel +:category: Blogroll +:slug: blogroll +:status: draft + + +* Blogs I Read + + * `Ned Batchelder's blog `_ + * `Andrey Petrov `_ + * `bitprophet.org on bitprophet.org `_ + * `bitquabit `_ + * `Julia Evans `_ + * `Nik Kantar `_ + * `Sam.Codes `_ + * `Stories by Jess Shapiro on Medium `_ + * `The Industrious Rabbit - Blog `_ + * `The Industrious Rabbit - Videos `_ + * `__fredeb.dev `_ + diff --git a/data/blog/pages/contact.rst b/data/blog/pages/contact.rst new file mode 100644 index 0000000..b0d9ea1 --- /dev/null +++ b/data/blog/pages/contact.rst @@ -0,0 +1,12 @@ +Contact +####### +:date: 2022-10-15 00:00 +:author: tyrel +:category: Contact +:slug: contact +:status: published + + + +To Contact Me: `Email `__ + diff --git a/data/blog/pages/notary.rst b/data/blog/pages/notary.rst new file mode 100644 index 0000000..a649dd6 --- /dev/null +++ b/data/blog/pages/notary.rst @@ -0,0 +1,20 @@ +Notary Public +============= +:date: 2022-12-06 00:00 +:author: tyrel +:category: Notary +:slug: notary-public +:status: published + + +I am a North Carolina Notary Public, located in Durham, North Carolina. + +I can perform notarial acts in all 100 counties. + +If you want to use my services, I charge $5 per stamp (The maximum that NC allows you, and $0 for Absentee Ballots). + + +To prepare, first familiarize yourself with `How to get something notarized `_. + +Then, to request my services, `Email me `_ or `Book Me `_ + diff --git a/data/blog/pages/now.rst b/data/blog/pages/now.rst new file mode 100644 index 0000000..f7d9078 --- /dev/null +++ b/data/blog/pages/now.rst @@ -0,0 +1,31 @@ +Now +### +:date: 2023-03-12 00:00 +:author: tyrel +:category: Now +:slug: now +:status: published + +`What is this? `_ + +June 2023 +--------- + +* I was laid off from EverQuote in a round of layoffs. + +April 2023 +---------- + +* Set up FreeDOS on an old Dell Vostro 1720 - so I can now use things like TurboC, WordStar, and play old DOS Games. +* Set up Amiberry with AmigaOS Workbench3.1 on a flash drive, so I can tool around with Amiga finally. +* Ordered a Godot course on Zenva from HumbleBundle. I'm glad to finally peek at it at least, always interested me as a game engine. +* Astrid is two months old now, and not sleeping the best, but growing bigger. +* Making some new parent friends. Helped one new father move. Walked with a couple in our neighborhood about to have a baby and hopefully we stay connected. +* Grandmother is out of the hospital after having two heart stints put in, and giving us a scare with potential fatal surgery. + +March 2023 +~~~~~~~~~~ + +* Wife had our first child Astrid Mina, on March 2, 2023. On Parental leave until April 26. +* Still working at EverQuote, on a different team now that does Go, React and Ruby. +* Haven't flown in a while, taking some time off because of some `mental health reasons `_, and now with a kid, no time for a bit. diff --git a/data/blog/pages/references.rst b/data/blog/pages/references.rst new file mode 100644 index 0000000..75bdd65 --- /dev/null +++ b/data/blog/pages/references.rst @@ -0,0 +1,34 @@ +References +########## +:date: 2022-11-04 00:00 +:author: tyrel +:category: References +:slug: references +:status: draft + +Blog Citations +~~~~~~~~~~~~~~ + +* 2023 + + * Another citation from my friend Nik. `Self Hosting: Resolved? `_ + +* 2020 + + * My friend Nik has a blog, and I pointed out an alternative so he `Cited Me `_. + +Work Blogs +~~~~~~~~~~ + +* 2021 + + * I wrote a blog post about our `Command Line Interface `_ that we are building at Tidelift. (Note: I am no longer employed by Tidelift nor work on this project) + + +Interviews +~~~~~~~~~~ + +* 2021 + + * `Daily Tar Heel - Covid19 Standby List Twitter Opinions `_ (Nothing special, just gave my opinion on UNC's Standby Covid Vaccinations nearby.) + diff --git a/data/blog/pages/resume.rst b/data/blog/pages/resume.rst new file mode 100644 index 0000000..bfaa0b9 --- /dev/null +++ b/data/blog/pages/resume.rst @@ -0,0 +1,129 @@ +Resume +###### +:author: tyrel +:category: Resume +:slug: resume +:status: published + + +`2023 Resume <{attach}/pdfs/Tyrel-Souza-Resume-2023.pdf>`_ + +Also `Online Resume `_ for more up to date resume. + +NOTE: I live in North Carolina and am only entertaining Remote Roles right now. + +---- + +Senior Software Engineer with focus on Python, Django, Go. Technology enthusiast and IOT tinkerer. Private pilot. Amateur radio operator. + +EXPERIENCE +---------- + +EverQuote — Boston, MA — Senior Software Engineer >>> January 2022 - June 2023 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Email Remarketing Team + + * Ported a Python2.7 monolith to Python3 microservices using FastAPI, and Kafka. + * Automated a daily task for two analysts to be able to be run in only a few moments which saves 18 hours per week of analyst time. + * Built a UI for manipulating database settings to aid in automating work for analysts. + * Migrated a handful of repositories from CircleCI testing to GitHub Actions. + * Ported multiple long running scripts and command line tools from Python 2.7 to Python 3. + * Migrated alerts system platform from PagerDuty to Opsgenie + +* Consumer Engagement Team + + * Launched a web service using Go and React (in TypeScript) to assist Sales Reps to provide alternative insurance company matches. + * Transitioned multiple projects from Atlassian Bamboo to GitHub Actions. + +Tidelift — Boston, MA — Software Engineer III >>> April 2018 - December 2021 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* `Command Line Interface `_ + + * Developed binary CI/CD tool in Go to analyze software dependencies for security/licensing problems + * Added ability to create users and manage repositories from the command line + * Expanded personal project into company product + +* Tidelift core redesign and pivot + + * Pivoted from solely a vulnerability scanner to supporting a catalog of open source dependencies + * Part of small team developing Tidelift 2.0/3.0 + * Developed front end Vue, and back end Ruby on Rails + * Worked on many Sinatra microservices. + +* Expanded open source code under https://libraries.io to support more languages. + + * Added new languages manifests support + * Programmed in multiple projects with code all available as open source on github + * `Bibliothecary `_: Added ability to detect dependencies in manifests for Poetry, pip-compile, pipfile, + * `Conda Parser `_: Developed ingestor of Conda environment files, parsing environment.yml files + * `Conda API `_: Programmed web scraping of Anaconda to detect new packages, and REST API endpoints for packages + + +Addgene — Cambridge, MA — Software Engineer >>> March 2015 - March 2018 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Wrote code to support front end ecommerce site, and back end inventory management system using Django, Django Rest Framework, jQuery, Bootstrap +* Rewrote file storage backend to keep on Amazon S3 instead of local in-house file system + + * Released my own custom Django module as a result + +* Trained non-developers in Python +* Helped start and expand a “non-developer developer” club +* Created mircoservices to support 3rd party API integration + + +Akamai — Cambridge, MA — Sr. Software Engineer >>> July 2014 - March 2015 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Wrote an API with Python/Flask that the front end could communicate with + + * Checked if email was a valid user in Salesforce, then sent the user an email from a template + +* Rewrote the backend for an internal dashboard to load data in 0.1 second, down from 10 seconds + +Propel Marketing — Quincy, MA — Software Engineer >>> September 2012 - July 2014 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Built CMS to host and build hundreds of customer websites using Django, jQuery, Bootstrap +* Created custom widgets, custom styling, custom templates, and more + + +Appropriate Solutions Inc — Peterborough, NH — Software Developer >>> July 2010 - September 2012 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Built multiple client websites in Django, jQuery, Bootstrap. + +SKILLS +------ + +* Python, Go, Django, Flask, Linux, Docker, Git, Mercurial, Postgres, MySQL, MariaDB, Amazon AWS (S3 mostly) Google Cloud Storage, REST APIs, Kafka, Redis, memcached, Sidekiq, RabbitMQ, Eclipse Mosquitto + +EDUCATION +--------- + +* Keene State College - B.S. Applied Computer Science (Honors Society), Minor in Mathematics (Honors Society), Dean's List + +PERSONAL PROJECTS +----------------- + +* `Django DBFileStorage `_ Goal: to continue running CI tests on a remote storage when working on moving file storage to S3 without incurring additional AWS charges. + + +CONTRACTING WORK +---------------- + +Benchtop Devices +~~~~~~~~~~~~~~~~ + +* Multiple C# Desktop programs, interfacing via serial to get results about pressure tests, calculate decay rates, printing results to pdfs, label machines, and local Sqlite databases. Custom per each client. + + * Clients include: Waymo, FlexFlow, Hypertherm + +* Python/Django and VueJS tool to convert pressure test results from the Cincinnati Test Blackbelt Machine, to PDFs. + +VOLUNTEERING +------------ + +* Boston Athletic Association Amateur Radio Operator - 2019 Boston Marathon diff --git a/data/blog/pdfs/Tyrel-Souza-Resume-2022.pdf b/data/blog/pdfs/Tyrel-Souza-Resume-2022.pdf new file mode 100644 index 0000000..2f93559 Binary files /dev/null and b/data/blog/pdfs/Tyrel-Souza-Resume-2022.pdf differ diff --git a/data/blog/pdfs/Tyrel-Souza-Resume-2023.pdf b/data/blog/pdfs/Tyrel-Souza-Resume-2023.pdf new file mode 100644 index 0000000..e28665a Binary files /dev/null and b/data/blog/pdfs/Tyrel-Souza-Resume-2023.pdf differ diff --git a/data/blog/posts/2011-12-21_python-progress-bar.rst b/data/blog/posts/2011-12-21_python-progress-bar.rst new file mode 100644 index 0000000..2397a1b --- /dev/null +++ b/data/blog/posts/2011-12-21_python-progress-bar.rst @@ -0,0 +1,43 @@ +Python Progress Bar +################### +:date: 2011-12-21 03:52 +:author: tyrel +:category: Tech +:tags: Python +:slug: python-progress-bar +:status: published + +I was looking for a nice progress bar today at work to show progress rather than just printing "\ **Waiting 30 seconds…**\ " and having the script do nothing, I wanted to have a progress bar show. + +I found a progress bar from `Corey Goldberg `__ + +I did make a couple changes, and have uploaded my changes to my GitHub account. + +newPythonProgressBar [deadlink] + +To use this progressbar, it is very easy. + +.. code-block:: python + + # To Setup + from progress_bar import ProgressBar + import sys + import time + def updateBar(step): + p.update_time(step) + sys.stdout.write("%s\r" % p) + sys.stdout.flush() + # + # to call + # + wait_time = 100 # seconds + p = ProgressBar(wait_time) + p.empty_char = "." + p.unit = "^" + for step in range(wait_time+1): + updateBar(step) + time.sleep(1) + +It will look like this when you use it + +``[###...............7%..................] 7^/100^`` diff --git a/data/blog/posts/2012-01-05_custom-django-urlfield.rst b/data/blog/posts/2012-01-05_custom-django-urlfield.rst new file mode 100644 index 0000000..a205995 --- /dev/null +++ b/data/blog/posts/2012-01-05_custom-django-urlfield.rst @@ -0,0 +1,32 @@ +Custom Django URLField +###################### +:date: 2012-01-05 03:55 +:author: tyrel +:category: Tech +:tags: python, django +:slug: custom-django-urlfield +:status: published + +For work I had to write a custom url model field. This model field when setting up accepts a default protocol, and a list of other protocols. + +When checking the protocol, the url is split by "://". If the split has one or two parts, then the url is validly formed. + +In the event of a single element split, there is no protocol specified. When there is no protocol, the url is prepended with the default protocol specified. If there is a protocol, it is checked to make sure it exists in a union of the default protocol and other protocols. If it is not, a ValidationError is raised letting the user know that the protocol is not accepted. + +This can all be found at On my github [deadlink]. + +I have a couple ways I could have done this better and probably will. Improvements would be just one parameter called parameters in which it is checked if there is at least one element. Passing this, when there is no protocol specified, the first element is the default one. + +This would be a little cleaner. + +this example would allow for http, https, ssh, spdy and mailto, anything else would error out. + +.. code-block:: python + + facebook_page = URLField(default_protocol="http", protocols=["https","ssh","spdy","mailto"]) + +The way I could improve this would be + +.. code-block:: python + + facebook_page = URLField(protocols=["https","https","ssh","spdy","mailto"]) diff --git a/data/blog/posts/2012-01-13_you-can-un-expire-a-gpg-key.rst b/data/blog/posts/2012-01-13_you-can-un-expire-a-gpg-key.rst new file mode 100644 index 0000000..fd85873 --- /dev/null +++ b/data/blog/posts/2012-01-13_you-can-un-expire-a-gpg-key.rst @@ -0,0 +1,53 @@ +You can un-expire a GPG key. +############################ +:date: 2012-01-13 03:54 +:author: tyrel +:category: Tech +:tags: linux, gpg +:slug: you-can-un-expire-a-gpg-key +:status: published + +Today we had a problem at work on a system. +Without getting into too much detail as to give away secrets behind the verbal NDA I am behind, I will just say that it had to do with a GPG public key of mine that was expired on a dev machine, accidentally propagating during install to a production machine. +This key had a sub key as well, so figuring out this was tricky. + +To start, you can list your gpg keys like so: + +.. code-block:: console + + $ gpg --list-keys + + +This will list keys such as + +.. code-block:: console + + pub 4096R/01A53981 2011-11-09 [expires: 2016-11-07] + uid Tyrel Anthony Souza (Five year key for email.) + sub 4096R/C482F56D 2011-11-09 [expires: 2016-11-07] + +To make this not expire, (same steps to change expiration date to another time), you must first edit the key + +.. code-block:: console + + $ gpg --edit-key 01A53981 + + +You will then see a gpg prompt ``gpg>`` + +Type "expire" in and you will be prompted for how long to change it to + + +.. code-block:: console + + Changing expiration time for the primary key. + Please specify how long the key should be valid. + 0 = key does not expire + = key expires in n days + w = key expires in n weeks + m = key expires in n months + y = key expires in n years + +You are then done setting the expiration on the primary key, if you have sub key, doing this is as easy as typing ``key 1`` and repeating the expiration step. + +To finish and wrap things up, type ``save`` and you are done. diff --git a/data/blog/posts/2012-02-08_vertical_bars_in_graphite.rst b/data/blog/posts/2012-02-08_vertical_bars_in_graphite.rst new file mode 100644 index 0000000..c1b0771 --- /dev/null +++ b/data/blog/posts/2012-02-08_vertical_bars_in_graphite.rst @@ -0,0 +1,15 @@ +Vertical Bars In Graphite +######################### +:date: 2012-02-08 15:10 +:author: tyrel +:category: Tech +:tags: graphite, statsd +:slug: vertical-bars-in-graphite +:status: published + +I am working with txStatsD and Graphite. I was having the hardest problem looking through the txStatsD code today finding how to graph something as an event, not a data point. I eventually went into every option on the graphite dashboard and found an option to make a bar. + +.. figure:: {static}/images/2012/02/graphite-menu.png + :alt: menu in graphite showing draw nonzero as infinite + +This is the option that you must use when you want to mark events. For example we want to know "Server restarted", we would use this technique, as it doesn't make sense to aggregate "server restarted". Using nonzero as infinite is a good way to show an event took place. diff --git a/data/blog/posts/2012-02-17_hubspot.rst b/data/blog/posts/2012-02-17_hubspot.rst new file mode 100644 index 0000000..3002157 --- /dev/null +++ b/data/blog/posts/2012-02-17_hubspot.rst @@ -0,0 +1,25 @@ +Hubspot +####### +:date: 2012-02-17 15:10 +:author: tyrel +:category: Personal +:tags: hackathon +:slug: hubspot +:status: published + +I was invited to a Hackathon that one of our client's client was throwing. Being that I love programming and learning, I decided I would go. + +The event was in Cambridge, MA. I arrive early, (my friend said there would be a lot more traffic than there was at that time of day) so I got a tour of office. It's situated in an old, what I believe to be, factory building. The coolest part of the office was that they had whiteboard paint on every wall surface, complete with markers of course. + +The event started and people who were attending had tossed up ideas on the white board. A couple people wanted to integrate LinkedIN with HubSpot. Another person wanted to integrate Eventbrite with HubSpot, to get information to/from event goers after the event ends. I didn't like any of those ideas and my only experience with HubSpot is their Leads API, so I stuck to what I know. + +I had an idea for an app the second I walked in the door, it was like magic. My main hassle was that HubSpot's Canvas integration REQUIRES HTTPS. Now, my web host is DreamHost and I am kind of cheap, so of course I don't have any way to host a HTTPS site immediately. A big part of me wanted to bite the bullet and order a secure server from DreamHost, or setup another linode, but I felt that I've been spending a lot of money lately and that I would figure out a way. Adrian, my contact at HubSpot, of who I am working with on the PPC project(more on that later), walked by and saved me. + +He asked if I had ever used GoogleAppEngine. Of course I hadn't because I was under the belief that it cost money to use, but then I realized I was thinking of Amazon's EC2. I sign up for GAE and within an hour I have a HelloWorld site setup. The slow part was installing Python2.5 so I could use the same version that GAE used and not have to fix a lot of backwards compatibility errors between 2.5->2.7. + +After I had a site up that could do HTTPS I dove into programming for my HubSpot app. The app I am doing for work graphs leads per day combined with Google AdWords data per day. I decided to do something different. My app is still a graph, as graphs are fun and easy to understand by everyone. + +This app graphs a set of leads and shows how many leads happened in a given hour for the previous day. Given extra time I would have added an interface to specify the day to graph leads, but last night my time was severely limited by the fact that I had to setup my environment for GoogleAppsEngine. + +Improvements I can and want to do to this app are database, faster processing, and being able to select a date. I almost wanted to break down and learn NodeJS for this, because from my understanding of the event driven nature of NodeJS would be a lot easier to load data over a longer period of time, than to just load it all at once and timeout with HubSpot's Jakarta Commons-HttpClient. + diff --git a/data/blog/posts/2012-03-08_some-bash-tips.rst b/data/blog/posts/2012-03-08_some-bash-tips.rst new file mode 100644 index 0000000..a74a27d --- /dev/null +++ b/data/blog/posts/2012-03-08_some-bash-tips.rst @@ -0,0 +1,27 @@ +Some BASH tips +############## +:date: 2012-03-08 03:56 +:author: tyrel +:category: Tech +:tags: bash, linux +:slug: some-bash-tips +:status: published + +I realize I haven't updated in a while. I haven't had much free time recently as I've been working on a project for my father in C# after work hours. This is a great change from only working in Python and JavaScript recently. I'm making a program that will analyze test results from a plasma torch for a company called HyperTherm. My father built the physical machine, but the employees want something that they can better see the results of a passed torch unit, or a failed torch unit. This program has a bar code scanner that scans the tool used in the test and matches it up to the lot of torch parts. Another added feature is the ability to print a white label that says "UNIT PASSED" or a giant red label that says the unit failed and which of the 8 tests failed were.I had to learn how to use delegates, as my serial event listener is on a separate thread and I can't update labels, or parts of the User Interface without them. Still working on it, hopefully will wrap it up by Saint Patrick's day. + + +I recently found a cool command in BASH that I hadn't previously known. ``C-o`` will execute the current line, and then bring the following line up from BASH history. If you have a set of commands you want to execute again, rather than having to press up 20 times, hit enter, press up 19 times, hit enter, and so on… You can just hit up 20 times. Press C-o as many times as you need to. + +For example: + +.. code-block:: console + + $ touch a + $ touch b + $ touch c + # [up] [up] [up] + $ touch a [C-o] + $ touch b [C-o] + $ touch c [C-o] + +As you can see there, all I had to do was go back to the ``$ touch a`` line, and hit control-o three times and it touched the files again! diff --git a/data/blog/posts/2012-05-04_ganymede_twilio.rst b/data/blog/posts/2012-05-04_ganymede_twilio.rst new file mode 100644 index 0000000..cd3cb79 --- /dev/null +++ b/data/blog/posts/2012-05-04_ganymede_twilio.rst @@ -0,0 +1,20 @@ +Ganymede, Twilio +################ +:date: 2012-05-04 23:30 +:author: tyrel +:category: Tech +:tags: nodejs, twilio +:slug: ganymede-twilio +:status: published + +Last night I wrote the beginnings of my first NodeJS application. Is application even the correct word? + +I've been meaning to try out the cool API by Twilio, which is used for SMS and VoiceCalling. I decided to design a system that will be two+ endpoints. One is the main server which will listen for UDP messages. When it receives the correct UDP message, configured in the config(`konphyg `_) files, it will fire off a message to Twilio and send me a text message. + +The next steps, which I should be getting to tonight, are to create the Arduino portion and the serial listener. The Arduino will have a button that will send a message over serial to another NodeJS listener. This will decide if the press was good enough, if it passes the debouncing filter, and then fire a message to the main Ganymede server. + +This could be used as a little text message doorbell for when you have your music on too loud. I don't believe I will ever sell this, as it's just for me to get into NodeJS, but It would be fun to share with friends. + +The source so far is located on my github at [DEADLINK]. + +I will write more as the project continues about the different technologies and comment on my choices in the source a little bit. diff --git a/data/blog/posts/2012-05-07_hypertherm.rst b/data/blog/posts/2012-05-07_hypertherm.rst new file mode 100644 index 0000000..48815f8 --- /dev/null +++ b/data/blog/posts/2012-05-07_hypertherm.rst @@ -0,0 +1,27 @@ +Hypertherm +########## +:date: 2012-05-07 23:30 +:author: tyrel +:category: Personal +:slug: hypertherm +:status: published + +For the past three months I have been upgrading and rewriting version 2 of my software for Hypertherm. I am under a contract for my father's company. His company is developing a machine to test how well air flows (laminar flow) through a plasma cutting torch head, and how much air leaks out over a certain time (delta pressure loss). + +This has been a nice adventure. I am talking to the tester over serial, reading in a hand scanner (barcodes and acts as a keyboard easy), talking to a DYMO printer and using a database. + +The serial communication was pretty straightforward. I started a new thread and listen for serial all the time. The tricky part with that was that because it was on another thread, I needed a delegate to talk to my UI when I did things like change the picture from blank to a big red X, or update a label. + +The hand scanner wasn't even a factor that took longer than 10 minutes, I just pop up a dialog box asking for input. + +The DYMO printer was the hardest part. This took me a month to figure out, I kept fighting with the printer. I could figure out how to print to the left roll, the ones we setup as as the passing labels, but I couldn't for the life of me figure out how to get it to print to the right label, using a custom label. I tried to load the labels into data and use a StreamWriter/StreamReader object to treat that as the label, but it kept printing one that had, for reasons unknown to me, been locked into the printer. I finally gave up on using the interface they provided and am writing the label to a temporary file. The file is in the user's %appdata% directory in a sub directory that it will not be mistakenly written to, so I feel safe doing it this way. Granted, the machine is a single purpose machine, once this program is installed it will only run this program day and night. + +Once I got the printer working, I checked it in to github and realized it took me way longer than anticipated. I learned a lot about .NET development (by no means everything, or even most things, just a lot compared to what I did know before [nothing].) + +Tonight while developing I decided to video some aspects of the Program. + +The following four links are videos, showing parts of the program and machine in action. + +* [Hosted on Qik - no longer available] +* [Hosted on Qik - no longer available] +* [Hosted on Qik - no longer available] \ No newline at end of file diff --git a/data/blog/posts/2012-05-25_harry-delmolino.rst b/data/blog/posts/2012-05-25_harry-delmolino.rst new file mode 100644 index 0000000..7883e3b --- /dev/null +++ b/data/blog/posts/2012-05-25_harry-delmolino.rst @@ -0,0 +1,31 @@ +Harry Delmolino +############### +:date: 2012-05-25 03:56 +:author: tyrel +:category: Personal +:tags: friends +:slug: harry-delmolino +:status: published + +I met this random kid online on IRC a year and a half ago (I believe it was December 19th, 2010). His name was HarryD. We got talking and one time he mentioned that he was going to hike Mount Monadnock. That is near me so we got talking and he said he lived near North Hampton, MA. That was cool that I met some random kid who lived near me. He was only 17 at the time. + +Eventually we met up because my new girlfriend lived near NoHo in Holyoke. One day I went down to see her and met up with him. He was wearing all brown, so I joked that he was a UPS delivery guy. We hung out for a bit, I think I met his friend Sam that day. Then I left and went home. For the next year we would hang occasionally, usually I would go down and visit NoHo because it was easy for him to Bike to and he worked there. We went bowling, once or twice, and he came up for this super bowl to watch Star Wars instead because screw sports! + +I think that was the last time I saw him. + +On this Saturday, May 19th, 2012, he was riding his bicycle in North Hampton and collided with a car, sending him flying off and smacking his head on the edge of the curb. There are some other details which I am not 100% sure about. He then was in the hospital until Tuesday May 22, 2012 at which time he passed away. + +We used to talk every day, either on IRC, AIM or gTalk. His account on IRC (no_numbers_here) is still active because his VPS for http://harry.is is still paid for and online until the next billing period. Its so sad seeing his name online and not being able to send a "hey dood" or some other random greeting, then start talking about computers, python, bikes, etc. + +Harry was an avid cyclist. I am reading stories that even if he had to go thirty feet, he would hop on his bike to get there. He got me interested in cycling as well. He was going to sell me a bike, but the I was talking to a friend and he gave me on, so I never bought it. Which as it turns out was good as he wanted to give that to his new girlfriend. + +I was planning on hanging out with him next weekend, as he was busy with something this weekend that I can't remember. I wanted to take him target shooting, and wanted to eventually be in enough shape to go hiking with him. None of this ever came to fruition. + +Harry Delmolino, we may not have been as close as we could have been, but you definitely made a difference in my life for the better and I never got to thank you for this. + + +Edit: +----- +I went to the Calling Hours today and I was only there maybe a half hour, but there were so many people there. It's amazing that a man so young has touched so many lives of so many people, and had accomplished so much. People might say "Oh we was just a kid, he was only 18″ but if they looked at the accomplishments of this young man, they would realize how grown up he was. + +I think his mother and father might eventually delete his Facebook, so I went back and took a screenshot of one of our last conversations so I can remember. Everything he said usually warranted a laugh. diff --git a/data/blog/posts/2012-11-07_cfengine3-install-on-centos-5-7.rst b/data/blog/posts/2012-11-07_cfengine3-install-on-centos-5-7.rst new file mode 100644 index 0000000..aaaeb91 --- /dev/null +++ b/data/blog/posts/2012-11-07_cfengine3-install-on-centos-5-7.rst @@ -0,0 +1,43 @@ +CFEngine3 Install on CentOS 5.7 +############################### +:date: 2012-05-25 03:57 +:author: tyrel +:category: Outdated +:tags: cfengine, centos +:slug: cfengine3-install-on-centos-5-7 +:status: published + +| Today I was tasked with installing CFEngine3 on CentOS-5.7 (A little outdated). When installing CFEngine-3.3.1 I kept getting an error that I couldn't find libtokyocabinet.so.9. I had to set my prefix to /usr/ because the location that tokyocabinet was installing my libraries to was not being read by CFEngine's make script. +| To do this (I am using tokyocabinet 1.4.47) + +.. code-block:: console + + wget http://fallabs.com/tokyocabinet/tokyocabinet-1.4.47.tar.gz + tar -xzvf tokyocabinet-1.4.47.tar.gz + cd tokyocabinet-1.4.47/ + ./configure --prefix=/usr/ + make + sudo make install + +Then I was able to ./configure && make && make install cfengine3 without any problems. + +So my overall script looked something like this: + +.. code-block:: console + + wget http://fallabs.com/tokyocabinet/tokyocabinet-1.4.47.tar.gz + tar -xzvf tokyocabinet-1.4.47.tar.gz + cd tokyocabinet-1.4.47/ + ./configure --prefix=/usr/ + make + sudo make install + +.. code-block:: console + + wget http://cfengine.com/source-code/download?file=cfengine-3.3.1.tar.gz + tar -xzvf cfengine-3.3.1.tar.gz + cd cfengine-3.3.1 + ./configure + make + sudo make install + sudo cp /var/cfengine/bin/cf-* /usr/bin/ diff --git a/data/blog/posts/2013-07-02_getting-started-in-python-part-1.rst b/data/blog/posts/2013-07-02_getting-started-in-python-part-1.rst new file mode 100644 index 0000000..e9f4601 --- /dev/null +++ b/data/blog/posts/2013-07-02_getting-started-in-python-part-1.rst @@ -0,0 +1,67 @@ +Getting started in Python Part 1 +################################ +:date: 2013-07-02 03:59 +:author: tyrel +:category: Tech +:tags: python, pip, virtualenv +:slug: getting-started-in-python-part-1 +:status: published + +I have a friend who is interested in becoming a Python developer. He has some Python experience with CodeAcademy, but he of course wants to take this a step further and develop on his own computer. I figure I'd give him a few pointers, and I know this has been rehashed a million times, but what the hell, why not blog on it again. +There are a few important things to learn besides the actual language itself. The first I am going to discuss is has to deal with installing packages, then followed up closely with Python's path trickery. Finally I'm going to wrap up by discussing some software related to development, that could be used for any language, but I use daily in my work as a Python Software Engineer. Let's get started. + +PIP +--- + +Python is a wonderful language, but how useful would it be if you had to rewrite everything by hand? Not useful at all. That's why the lovely pip developers were born. `PIP `__ (executable pip) is a package manager written for Python. It's very simple to use, and in my opinion is way better than easy_install. To use pip you need to know at a minimum three commands. + +``pip install`` + +This command does exactly what it says on the box. It queries PyPI (Python Package Index) and downloads the latest version of the package on the server. It then installs it to your site-packages. + +``pip uninstall`` + +This deletes all files associated with the package supplied. 100% simple. + +``pip freeze`` +This shows what packages are installed on your system and what versions. If you supply ‐‐local it will show what packages are installed in your current environment. +These three commands will get you started with package management, there are more commands you can find by looking through the help documents. + +Virtualenv +---------- + +If you notice I mentioned a current environment in my previous ``pip freeze`` explanation, here is why. Python has a default place that it looks when you reference a package. This is generally in something like ``/usr/lib/python2.7/site-packages/`` or ``C:\Python27\lib``. There is a set of scripts called ``virtualenv`` that creates an environment where you run it with a complete copy of your Python executable, and a blank (unless you copy them over) site-packages directory. You can then install any packages there activate the virtual environment. When activated you use those specific versions, no matter the version of what is installed on your system. + +Let's show an example of the first time use of ``virtualenv``: + + +.. code-block:: console + + $ sudo pip install virtualenv # Only time you might need sudo, try without first. + $ virtualenv myenv # Create the virtual environment + $ source myenv/bin/activate # Activate the virtual environment + (myenv)$ python -c "import MYPACKAGE; print MYPACKAGE" + +Notice how it says your package is not in ``/usr/lib/python2.7/site-packages/`` ? That's because you're using ``virtualenv`` to tell your copied python to use that library instead. There are many reasons you would want to use a virtual environment. The most frequent reason is to preserve version numbers of installed packages between a production and a development environment. Another reason virtualenv is useful if you do not have the power to install packages on your system, you can create a virtual environment and install them there. + +Virtualenvwrapper +----------------- + +After you create a virtual environment, you just run``source bin/activate`` and it will activate the virtual environment. This can get tedious knowing exactly where your virtual environments are all the time, so some developers wrote some awesome scripts to fix that problem. This is called``virtualenvwrapper`` and once you use it once, you will always want to use it more. What it does is that it has you create a hidden directory in your home directory, set that to an environment variable and references that directory as the basis for your virtual environments. The installation of this is pretty easy, you can``pip install virtualenvwrapper`` if you want, or download the package and compile by hand. + +Once installed correctly, you can run the command ``mkvirtualenv envname`` to create a virtual environment. You can then run``workon envname`` from anywhere, and it will activate that environment. For example, you could be at``/var/www/vhosts/www.mysite.com/django/`` and run``workon envname`` and it would activate the environment from there. This isn't a required package (none of them are really…) as I went a couple years without using``virtualenvwrapper``, but it is very useful and now I use it every day. Some tips I use with my setup of``virtualenvwrapper`` is that I use the postactivate scripts to automatically try to change into the proper project directory of my environment. This also means I usually name my``virtualenv`` after my project name for easy memory. It makes no sense to have a project called "cash_register" but the``virtualenv`` be called "fez". This is how I change to the right project after activating my ``virtualenv``. This goes in ``$WORKON_HOME/postactivate`` + +.. code-block:: bash + + #!/bin/bash + # This hook is run after every virtualenv is activated. + # if its a work or a personal project (Example) + proj_name=$(echo $VIRTUAL_ENV|awk -F'/' '{print $NF}') + if [[ -e "/Users/tsouza/PersonalProjects/$proj_name" ]] + then + cd ~/PersonalProjects/$proj_name + else + cd ~/WorkProjects/$proj_name + fi + +This about wraps up part one of this two part blog series. Next time I will discuss how to use Git and how to configure SublimeText2 and Aptana Studio for use with Python. Stay tuned! diff --git a/data/blog/posts/2013-08-06_help-i-have-too-many-django-manytomany-queries-fixed.rst b/data/blog/posts/2013-08-06_help-i-have-too-many-django-manytomany-queries-fixed.rst new file mode 100644 index 0000000..52b8ab0 --- /dev/null +++ b/data/blog/posts/2013-08-06_help-i-have-too-many-django-manytomany-queries-fixed.rst @@ -0,0 +1,31 @@ +Help, I have too many Django ManyToMany Queries [FIXED] +####################################################### +:date: 2013-08-06 04:00 +:author: tyrel +:category: Tech +:tags: python, django, bugs +:slug: help-i-have-too-many-django-manytomany-queries-fixed +:status: published + +My boss tasked me with getting the load time of 90 seconds(HOLY CARP!) on one page down. First thing I did was install the Django Debug Toolbar to see what was really happening. + +There are currently 2,000 users in the database, the way our model is setup is that a UserProfile can have other UserProfiles attached to it in one of three M2M relations, which in the Django Admin would cause 2,000 queries PER M2M field. This is very expensive as obviously you don't want 10,000 queries that each take 0.3ms to take place. + +The solution, after a day and a half of research is to override the **formfield_for_manytomany** method in the Admin class for our UserProfile object. + +Our solution is to prefetch for any M2M that are related to the current Model. + +.. code-block:: python + + def formfield_for_manytomany(self, db_field, request, **kwargs): + if db_field.__class__.__name__ == "ManyToManyField" and \ + db_field.rel.to.__name__ == self.model.__name__: + kwargs['queryset'] = db_field.rel.to.objects.prefetch_related("user") + return super(UserProfileInline, self).formfield_for_manytomany( + db_field, request, **kwargs) + +This goes inside our admin class **UserProfileInline(admin.StackedInline)**. Simple clean and easy to drop into another ModelAdmin with minimal changes. + +Other things I pondered was to set all our M2M's as raw_id_fields, then using Select2 or Chosen, query our UserProfiles when the related users were being selected. This would take a lot of load off the initial page load, but is more of a bandaid rather than a real fix. + +I tried to override the Admin class's **def queryset(self, request):** but this was not affecting anything. diff --git a/data/blog/posts/2013-11-13_how-to-not-trigger-a-post_save-in-django-but-still-modify-data.rst b/data/blog/posts/2013-11-13_how-to-not-trigger-a-post_save-in-django-but-still-modify-data.rst new file mode 100644 index 0000000..33518d8 --- /dev/null +++ b/data/blog/posts/2013-11-13_how-to-not-trigger-a-post_save-in-django-but-still-modify-data.rst @@ -0,0 +1,36 @@ +How to not trigger a post_save in Django, but still modify data. +################################################################ +:date: 2013-11-13 03:58 +:author: tyrel +:category: Tech +:tags: django, python +:slug: how-to-not-trigger-a-post_save-in-django-but-still-modify-data +:status: published + +Recently I have been diving into using signals with Django, which of course are pretty neat. + +I am working on a website for work which in the most basicexplanation, is a task management site. Recently I have added in the ability to subscribe to tasks and get emails, I did this by connecting to the post_save signal. I only email out when a task is changed, not created (of course, no one would be subscribed to it). This worked flawlessly and "emails" out to anyone who is subscribed. I say that in quotes, because I haven't actually hooked it up to a real SMTP server, and only use + +.. code-block:: shell + + python -m smtpd -n -c DebuggingServer localhost:1025 + +which will output any emails to stdout. But I digress… A problem arose when I was working on ordering tasks. + +I store an integer in the "ordering" column, which any authenticated user can drag the row to a new location and that will reorder the task. I did this after I setup the emailing signal, so I didn't think about an email being sent out for EVERY task being changed. + +I tried a lot of different things, and was debating some that would be a bit messy. Among those ideas were trying to store the past values in another table, but that would get expensive fast. The reason I tried this was because I wanted to see if the ordering was the only thing that changed, and if that was the case, not send an email. I eventually found a thread on StackOverflow that says to use update on the queryset to not trigger the signal. + +You can do this by doing something like this: + +.. code-block:: python + + from app.models import ModelName + + def reorder(request): + new_order = request.POST.get('new_order', None) + pk = request.POST.get('modelname_pk', None) + if new_order: + ModelName.objects.filter(pk=pk).update(ordering=new_order) + +I am not sure if this is the proper way save changes and not trigger a post_save signal, but this is the way that worked for me so I figured I would document this. diff --git a/data/blog/posts/2014-06-21_readline.rst b/data/blog/posts/2014-06-21_readline.rst new file mode 100644 index 0000000..d9277ab --- /dev/null +++ b/data/blog/posts/2014-06-21_readline.rst @@ -0,0 +1,43 @@ +Readline +######## +:date: 2014-06-21 04:01 +:author: tyrel +:category: Tech +:tags: readline, linux, cli +:slug: readline +:status: published + +A lot of times when I stop at someone's computer and help them in the terminal, I use a Readline command and people say "How the heck did you do that?" + +Let me first backup and explain what Readline is. From the GNU Readline Documentation - "The GNU Readline library provides a set of functions for use by applications that allow users to edit command lines as they are typed in." By default, Readline is set up in Emacs mode, no you don't have to have an extra four fingers to use Readline, most of the commands are simple. + +Here are a couple of the commands I use daily: + + +Movement +~~~~~~~~ + +- To move to the beginning of a line, you press ``C-a`` +- To move to the end of a line you press ``C-e`` + +Killing and Yanking +~~~~~~~~~~~~~~~~~~~ + +- To cut the rest of the line from where your cursor is, to the end, you press ``C-k`` +- To delete one word you press ``C-w`` +- To paste either of the two previous back you can press ``C-y`` + +Miscellaneous +~~~~~~~~~~~~~ + +- To clear the screen and get to a fresh start, you can press ``C-l`` +- To end your session you can send a ``C-d`` (This will send an end of file character) +- To search for a command you typed recently, press ``C-r`` and start typing, it will search backwards. ``C-r`` again will search for an earlier match. +- The inverse of ``C-r`` is ``C-s``, they function the same. +- To open your ``$EDITOR`` to edit the current shell command you wish to write, press ``C-x C-e`` + +Finally, don't forget about ``C-c``. While not specifically Readline, it's very useful because it sends the SIGINT signal to the program, which if just on the command line, will not execute the line you have type, and give you a new line with nothing on it. A nice clean start. + +To find out a lot more, read the documentation at `the Readline Commands Docs `__ I even learned some things while writing this up, apparently pressing ``C-x $`` will list off all the possible usernames. Good to know, and good to always keep learning. + + diff --git a/data/blog/posts/2014-10-01_first_day_java_college.rst b/data/blog/posts/2014-10-01_first_day_java_college.rst new file mode 100644 index 0000000..fbe8fcf --- /dev/null +++ b/data/blog/posts/2014-10-01_first_day_java_college.rst @@ -0,0 +1,20 @@ +First day back in Java since college +#################################### +:date: 2014-10-01 04:03 +:author: tyrel +:category: Tech +:tags: java +:slug: java +:status: published + +Recently I decided I wanted to learn Java again. I last programmed in Java when I was in College and that was the main language they taught in. I wouldn't say I was a great Java developer, although I completed every Java course well enough to get an A or better. + +I want to relearn Java because for the past four years I have primarily focused on Python. While it is a great language, I feel I need a change from what I'm focusing on now with primarily web based programming. + +I decided to refresh myself with Java and read a "Java for Python developers" guide, which was a great refresher. After that I sat around wondering what to program, inspiration wasn't coming quickly. I settled on a SSH Configuration Manager, which is something I've wanted for a while now. + +This Configuration Manager will read in your ~/.ssh/config files, and show you what hosts you have in a GUI interface. The great part of it will be that you can also create new ssh configurations, without having to remember every little detail. There will be a lot of help tooltips, and pre-fills as well. I have a pretty basic idea of what I want it to look like. Ideally a list on the far left with +/- buttons to add a new Host, and to the right of that will be another hierarchy list of all the key groups you can change, with the most common (that I or people I talk to) being in a "General" or "Common" list. To the right of that will be the actual keys and values you change. I think I would like to be able to "favorite" keys that you use frequently. This way when you create a new host entry, you can quickly fill out your usual configurations be it only adding an IdentityFile and User. Another feature I thought of would be copying/templating, for example being able to create a new "work based server" configuration by just copying one you already have. + +Some of the options will be a bit tricky, a couple of them are along the lines of allowing "yes", "no", "ask", or an integer, and I haven't figured out exactly how I want to manage that yet. + +Currently I have a model that only has getters/setters and toString support, there's a lot of them so it's already a 1050 line file last I checked. Next time I work on this project I want to start with data validation and learning how to write tests in Java. I think learning good BDD or TDD habits while learning a "new" language would definitely benefit me. \ No newline at end of file diff --git a/data/blog/posts/2015-01-09_ssh-agent-on-boot.rst b/data/blog/posts/2015-01-09_ssh-agent-on-boot.rst new file mode 100644 index 0000000..ca89595 --- /dev/null +++ b/data/blog/posts/2015-01-09_ssh-agent-on-boot.rst @@ -0,0 +1,25 @@ +SSH Agent on "boot" +################### +:date: 2015-01-09 04:03 +:author: tyrel +:category: Tech +:tags: linux, ssh +:slug: ssh-agent-on-boot +:status: published + +I had a friend complain that he had to keep adding his ssh key to his ssh-agent every time he rebooted. I have a really easy bit of shell code you can put into your .bashrc or your .zshrc file: + +.. code-block:: bash + + SSHKEYFILE=/path/to/your/ssh/key/file + ssh-add -l | grep -q $SSHKEYFILE + RC=$? + if [ $RC -eq 1 ] + then + ssh-add -t 86400 $SSHKEYFILE + echo "Added key internal to keychain." + fi + +This will check every time your bash or zsh rc file is sourced for your key in the currently added keys, and if it's not, it will add it. + +This has the benefit of not requiring you to put in your password every time you connect to a remote machine if you password your ssh keys (which you should). diff --git a/data/blog/posts/2015-01-13_python-debugger.rst b/data/blog/posts/2015-01-13_python-debugger.rst new file mode 100644 index 0000000..644e956 --- /dev/null +++ b/data/blog/posts/2015-01-13_python-debugger.rst @@ -0,0 +1,10 @@ +Python Debugger +############### +:date: 2015-01-13 04:02 +:author: tyrel +:category: Tech +:tags: python, pdb +:slug: python-debugger +:status: published + +When I worked at Propel Marketing, my dev team used to have presentations on things they loved. I love the Python debugger. It's very useful and I believe a proper understanding of how to use a debugger, will make you a better programmer. Here is a presentation on the debugger I made for my team. https://prezi.com/cdc4uyn4ghih/python-debugger/ diff --git a/data/blog/posts/2015-01-28_too-many-open-files.rst b/data/blog/posts/2015-01-28_too-many-open-files.rst new file mode 100644 index 0000000..3db0eb3 --- /dev/null +++ b/data/blog/posts/2015-01-28_too-many-open-files.rst @@ -0,0 +1,20 @@ +Too many open files +################### +:date: 2015-01-28 04:02 +:author: tyrel +:category: Tech +:tags: python, linux, ulimit, bugs +:slug: too-many-open-files +:status: published + +When I worked at Propel Marketing, we used to outsource static websites to a third party vendor, and then host them on our server. It was our job as developers to pull down the finished website zip file from the vendor, check it to make sure they used the proper domain name, (they didn't a lot of the time,) and make sure it actually looks nice. If these few criteria were met, we could launch the site. + +Part of this process was SCPing the directory to our sites server. The sites server was where we had Apache running with every custom static site as a vhost. We would put the website in ``/var/www/vhosts/domain.name.here/`` and then create the proper files in sites-available and sites-enabled (more on this in another entry). After that the next step was to run a checkconfig and restart Apache. + +Here's where it all went wrong one day. If I can recall correctly, my boss was on vacation so he had me doing a bit of extra work and launching a few more sites than I usually do. Not only that, but we also had a deadline of the end of the month which was either the next day, or the day after. I figure I'll just setup all mine for two days, and then have some extra time the next day for other things to work on. So I started launching my sites. After each one, I would add the domain it was supposed to be at into my ``/etc/hosts`` file and make sure it worked. + +I was probably half way done with my sites, and suddenly I ran into one that didn't work. I checked another one to see if maybe it was just my network being stupid and not liking my hosts file, but no, that wasn't the problem. Suddenly, EVERY SITE stopped working on this server. Panicking, I delete the symlink in sites-enabled and restart Apache. Everything works again. I then proceed to put that site aside, maybe something in the php files breaks the server, who knows, but I have other sites I can launch. + +I setup the next site and the same thing happens again, no sites work. Okay, now it's time to freak out and call our sysadmin. He didn't answer his phone, so I call my boss JB. I tell him the problem and he says he will reach out to the sysadmin and see what the problem is, all the while I'm telling JB "It's not broken broken, it just doesn't work, it's not my fault" etc etc. A couple hours later, our sysadmin emails us back and says he was able to fix the problem. + +It turns out, there's a hard limit to the number of files your system can have open per user, and this was set to 1000 for the www-data user. The site I launched was coincidentally the 500th site on that server, each of them having an access.log and an error.log. These two files apparently constantly open on each site for apache to log to. He was able to change www-data's ulimit to a lot higher, (I don't recall now what it was) and that gave a lot more leeway in how many sites the sites server could host. diff --git a/data/blog/posts/2021-05-26_first-flight-as-a-ppl.rst b/data/blog/posts/2021-05-26_first-flight-as-a-ppl.rst new file mode 100644 index 0000000..f73bbd8 --- /dev/null +++ b/data/blog/posts/2021-05-26_first-flight-as-a-ppl.rst @@ -0,0 +1,12 @@ +May 26, 2021 - First flight as a PPL +#################################### +:author: tyrel +:category: flying +:tags: flying +:status: published + +On April 26th, I took my first flight as a Private Pilot. I was flying with a CFI for a checkout to rent planes from the Wings of Carolina club, so the flight was nothing really to talk much about. It was really hot, and my first flight in almost 5 months, so I was definitely rusty. + +Mostly using this flight as a stepping stone to try out the GPX viewer§. I flew a PA28-161 - Warrior II - N8080A for One hour. Flew from KTTA, up north west a bit, did a few steep turns, then back to KTTA. + +§ - Not ported over, no JS on this blog. diff --git a/data/blog/posts/2021-05-27-second_flight_as_a_ppl.rst b/data/blog/posts/2021-05-27-second_flight_as_a_ppl.rst new file mode 100644 index 0000000..7ad234b --- /dev/null +++ b/data/blog/posts/2021-05-27-second_flight_as_a_ppl.rst @@ -0,0 +1,37 @@ +May 27, 2021 - Second flight as a PPL +##################################### +:author: tyrel +:category: flying +:tags: flying +:status: published + +I was able to sneak in a cooler morning flight in N8080A. I took my motorcycle to the airport, but forgot my yoke mount so I was iPad-less. We had to wait for a long while to get fuel because the services truck was filling the HU-16 that had arrived a few days beforehand. There was also a C-27 taking off, super cute, Feels like a baby C-130! + +.. figure:: {static}/images/2021/05/20210527_hu16.jpg + :alt: Grumman HU-16 Albatross + + Grumman HU-16 Albatross + +.. figure:: {static}/images/2021/05/20210527_c27.jpg + :alt: Alenia C-27J Spartan + + Alenia C-27J Spartan + +We went up, took a few minutes to do another 45° bank turn. I did that a little better, less shaky. Then he had me practice some emergency descents. I didn't do as well at those as I should. I need faster ADM skills. That will come with time, one of my big weaknesses is emergency things, so it's one thing I'm excited to start practicing. + +After the emergency procedures, he had me practice rudder control. Putting in enough rudder while turning to hold on target before the turn and rolling out while pointing at a target - but rolling out. I'm much better at rolling out than rolling in, so I'm excited to have a new procedure to practice. One of the things I like a lot about this CFI giving me the checkout to borrow planes, is that he's young. He doesn't have these really old ways of thinking about things and I appreciate that. + +After the higher altitude stuff, he wanted to see some short-field landings. The first time in the pattern we had to extend our downwind A LOT, there were three people on long finals, so our downwind was funky and long. I'm still getting used to this plane and the runway so by the time we were about to touch down, the other plane in front of us was still on the runway (albeit turning onto taxiway A3 a mile down the runway) so he said go around, as technically the other plane was still on the runway and things could go wrong even with that far separation. + +Did a go around and the next two short fields were okay. We got off at Taxiway A2 I think, and then took taxiway A back to the ramp. When we got to the intersection with A and A1 there was another plane getting ready to cross the threshold, but it was a Cessna 172… high wing! They didn't notice that there was a taildragger apparently norad coming on a 45° final, not straight in and almost rolled into the runway. Luckily the CFI I was with shouted STOP on the CTAF and they stopped, seconds before an incursion! EEEEK! + +Wrapped up, and planned our third checkout ride, it'll be down to KFAY, a controlled field. Should be on June 2nd and then I'll be cleared to rent the planes and feel like a full member of the Wings of Carolina! + +Unfortunately no GPX file, but I have a screenshot. + + +.. figure:: {static}/images/2021/05/20210527_track.png + :alt: GPX Track across the Raleigh Durham area west of Jordan Lake + + GPX Track across the Raleigh Durham area west of Jordan Lake + diff --git a/data/blog/posts/2021-06-07_woc-final-checkout-ride.rst b/data/blog/posts/2021-06-07_woc-final-checkout-ride.rst new file mode 100644 index 0000000..23adfac --- /dev/null +++ b/data/blog/posts/2021-06-07_woc-final-checkout-ride.rst @@ -0,0 +1,22 @@ +WOC Final Checkout Ride +####################### +:author: tyrel +:category: flying +:tags: flying +:status: published + +I can finally rent planes through Wings of Carolina! Flew today and the CFI passed me. + +I was a bit nervous about the North Carolina weather so I text the instructor to see if we could leave an hour early for the controlled airspace portion of the checkout. He said let's shoot for 3:30, we took off at 4 because he gave me a bit of ground instruction on the systems in N8116J. (Also we couldn't figure out the radio, so had to push a few buttons to get comms working. It seems when 16J was in the shop, they had undone all the COM1 COM2 selection buttons, which caught us off guard.) + +We took off, then flew south to Fayetteville (KFAY), Luke showed me the auto pilot, how to climb, descend, turn to a heading, so we enabled that on the flight to KFAY. 20 miles north, called approach, I fumbled my radio A LOT. I definitely need more controlled airspace practice. ATC Talks FAST. It's hard to fly the plane and copy things down, I think I need to start using a pen and paper, not Foreflight for ATC remarks, it'll be much easier. I put a FieldNotes book in my flight bag the other day, time to use it! After a few more fumbles (I copied back the altimeter, not the altitude, talk about stress!) I was cleared to land. Squawked 0210 and landed on runway 22 almost straight in, I took a right handed base, it was weird! There were no commercial flights so they were at a lull of traffic which is why I just got the RWY22 CLEAR TO LAND. Unfortunately (or Fortunately..) LIVE ATC and KFAY approach are down so I have no recording of my fumbles. Would have been nice to hear them again so I could learn. + +We landed, taxied back to runway 22, and then said we were taking off VFR. Squawked 0212 now and departed, stayed runway heading until they said turn right heading 320 staying at or below 2500. Then a bit later they said fly flight plan heading I turned 355 and then we finally got out of their airspace. There's two airports there, KFAY and Simmons Army Airfield (KFBG), so their airspace looks like a cell dividing, two round circles and of course we bisected it the long way. + +Out of their airspace Luke said "I have the controls", then banked hard 60 degrees right, and pointed down. He said "If you're ever VFR and inadvertently hit IMC weather, and get into an unusual attitude, hit this button [LVL] and presto, the wings will level." which of course they did. Auto pilot is still wild to me, I never flew with it in N43337, so it's going to take a lot to get used to, but I'm sure I'll start loving it. + +We then called 10mi on the 45 for RWY22, did a touch and go, (I came in a little higher than I wanted I still need to learn the sight pictures for this airport, but oof it's FLAT still.) took off did the pattern once more and did a full stop. + +On this flight, I learned the GPS a little bit more, the touch screen menu will make selecting comms so much easier, glad they have these consistently in all their planes. I also learned AutoPilot, I need to find a flight sim model with this auto pilot (I did get a Logitech/Saitek Multi Panel with AutoPilot on it this weekend, so good timing!) that I can learn how the IAS, ALT, VS, HDG buttons work better. HDG and ALT I get, those just hold heading and alt, but airspeed and vertical speed climbs are still magical! + +Luke then signed me off, so I'm clear to rent any of the PA-28-161 that they have at Wings of Carolina. I told him I want to learn how to fly the Mooney M20J soon, I'm excited to get my complex endorsement. diff --git a/data/blog/posts/2021-06-14_first-flight-with-lauren.rst b/data/blog/posts/2021-06-14_first-flight-with-lauren.rst new file mode 100644 index 0000000..bb9576f --- /dev/null +++ b/data/blog/posts/2021-06-14_first-flight-with-lauren.rst @@ -0,0 +1,69 @@ +First Flight With Lauren +######################## +:author: tyrel +:category: flying +:tags: flying +:status: published + +Today was my first flight with my wife, Lauren. She is my first real passenger ("technically your DPE is, blah blah"). I didn't know what to expect, I knew she had been up in planes before - she's gone skydiving, something I will probably never do. I'd rather be the person to fly the "JUMPERS AWAY OVER X" than be a jumper. I don't want to make this entire blog about Lauren, although we all know I could. So I will try to keep it 50% airplane talk! + +We scheduled a 12pm flight, with an estimate of me probably taking off at 12:30 (took off at 12:40 for pattern and 12:49 to KDAN), Cross country from KTTA to KDAN and back to KTTA. I wanted to fly at 4500'msl there, and 5500'msl back, following the East is Odd +500 and West is Even +500, because it was a 358°ish flight there and a 178°ish back (that's from airport center to airport center, but I did some cloud avoidance, a few times (like, the whole time) so I was pretty off course, but ForeFlight will get me there. I brought my Stratux because I remembered that N8116J didn't like bluetooth and ADSB so my iPad wouldn't connect to the plane. I also bought a new GPYes unit that I wanted to try out, and it worked great, no more magnetic GPS sticking to random magnetic things, everything is self contained in my Stratux! + +Wind was good for RWY21, so I had to taxi via Taxiway Alfa to get there, still not a fan of how Wings of Carolina has us do a runup off the taxiway, something I need to get used to at a busier airport than KEEN. Well, we took off and I did a loop of the pattern - I wanted to gauge Lauren's stomach, and my flying abilities to make sure she was okay to fly. The Pressure Density was 2,800'-2,900, which for a 255'msl runway, oooof. After the second takeoff we flew out to the traffic pattern then departed to the north! We climbed up to 3000, the cloud layer was about 3800 so I chose to stay under it, vs fly over the top and chance not having an opening. It was pretty bumpy - there's a convective SIGMET over like the whole east coast - so I knew it was going to be a little bumpy. Lauren was taking pictures the whole time (she took 47! I'll share some) and got some fun ones of me. In my pictures I look like I'm super concentration face, but she got a couple of me smiling. + +Nearing KDAN I realized that the CTAF was Actually a CTAF (Common Traffic Advisory Frequency) and that KDAN had a tiny little tower. I called 15 to the south (because I heard a lot of traffic) and the nice person on the frequency came in and said something like 16J there's two planes on the taxiway, one in the pattern and one just departing, which now I understand why people come on our Unicom back at KEEN and keep saying "KEEN Unicom please advise". Well we got closer I said I was flying over midfield to make a tear drop into the downwind. I did as such, flew over at 2500', did the tear drop, down to 1500' and landed. We then taxied to the FBO and shut down [my outside camera picked THREE seconds after I shut down to die, perfect timing]. The nice ramp assistant gave us a ride to the FBO in the golf cart so we could pee. + +Gatorade passed through our system completely we headed out to the plane. We got some weird looks from the people chilling in the FBO (Enterprise people, and like a grandfather? idk) for wearing masks. Yes we're both vaccinated, but I sure as hell don't know if the random people I run into are, gotta keep you safe! + +We took a few pictures, and then went and started up the plane. When I was doing my run up, I didn't have the mixture full rich, was still leaned for ground ops, so when I did the mag drop I heard a backfire, was like OH SHIT and realized I was leaned. Put in full mixture, and did the mag drops again, ~150rpm loss and we were good to go. Lesson learned here is even if there are shortcuts for when the engine is hot, and you just land for 10 minutes, probably good to go through the full checklist even just skimming it. + +We took down taxiway Alfa again and got to RWY02. Side note, KEEN has 02-20 and 14-32, this airport KDAN has 02-20 and 13-31 so when I came upon it, I was like "holycrap I feel like I'm home". Anyway - when we got to the end of the taxiway there were two entrances only like 100ft apart to the runway, it was weird. We had to wait like 10 minutes to take off, because there were 4 planes doing touch and goes and they were perfectly spaced to give me like NO time to take off. I know how long of a runway I need to be safe, but with a 2900' Density Altitude, I know I needed a longer ground roll, so finding time to slip in to where I knew I could take off was annoying. Finally one of the guys on downwind said "Plane waiting at 02, I'm extending my downwind a little bit to give you some time to take off, so I rolled out, thanked him and took off to the north a bit. I extended my upwind and gave the pattern a large buffer, then took off to the south. + +Lauren seemed to be getting tired by like 75% of the trip back so maybe next time I'll take the plane for four hours, and then we can take a full hour between legs, instead of 15 minutes. I did three hours, because I planned for a longer break between - we even bought zucchini bread I made last night! - but I threw in that extra pattern lap before we left the airport, so that added like 15 minutes, and I couldn't find the flight book so that took some extra time. + +When we were on the way back to Raleigh Exec, we saw another plane like 500' below us so I flew above them and circled past them then tried to come in behind them but I have no idea where they went when I flew over them, my ADSB decided to not pick them up, so I did a larger right turn so I knew I'd avoid them. It was weird and I'll have to check FlightRadar24 playback to see - but it's not up now for some reason. After that steep turn I entered the downwind and landed. We landed with 2 minutes to spare, but it took a few minutes to shut down, and tie down. Glad someone wasn't right after us (if someone was I would not have gone as far of course). Covered the plane, paid the $260 for 2.3 hours of flying, put the book away and headed home. + +The return trip took about exactly the same length. We left KTTA at 12:49, landed at KDAN at 1:36 (49min). Then with waiting for takeoff, we left KDAN at 2:07 and landed at 2:57(50min). On the flight back, it was a bit bumpier, the clouds were still scattered at about 3800 but some were darker than others so I flew around them. I wonder if when I finally fly IFR if I'll fly a lot straighter, instead of just "oops avoid that cloud!" that remains to be seen. + + +.. figure:: {static}/images/2021/06/14_tyrel-passenger-seat.jpg + :alt: Tyrel sitting in passenger seat, waiting for Lauren to get into plane. + + Tyrel sitting in passenger seat, waiting for Lauren to get into plane. + +.. figure:: {static}/images/2021/06/14_powerlines.jpg + :alt: Power line swath through the trees, with a nuclear power plant in the disance. + + Power line swath through the trees, with a nuclear power plant in the disance. + + +.. figure:: {static}/images/2021/06/14_tyrel-looking-left.jpg + :alt: Tyrel looking to the left, watching for traffic + + Tyrel looking to the left, watching for traffic + +.. figure:: {static}/images/2021/06/14_danville-VA.jpg + :alt: Danville, Virginia airport, from above. + + Danville, Virginia airport, from above. + +.. figure:: {static}/images/2021/06/14-clouds.jpg + :alt: Clouds and the right wing of a Piper Warrior airplane + + Clouds and the right wing of a Piper Warrior airplane + + +.. figure:: {static}/images/2021/06/14_tyrel-pulling.jpg + :alt: Tyrel pulling the plane forward, to get it lined up to push back for parking. + + Tyrel pulling the plane forward, to get it lined up to push back for parking. + +.. figure:: {static}/images/2021/06/14_TTA.gif + :alt: TTA Airpot Diagram + + Airport Diagram of TTA + +.. figure:: {static}/images/2021/06/14_DAN.gif + :alt: DAN Airport Diagram + + Airport Diagram of DAN diff --git a/data/blog/posts/2021-07-10_first-flight-with-dad.rst b/data/blog/posts/2021-07-10_first-flight-with-dad.rst new file mode 100644 index 0000000..419c3df --- /dev/null +++ b/data/blog/posts/2021-07-10_first-flight-with-dad.rst @@ -0,0 +1,52 @@ +First Flight With My Dad +######################## +:author: tyrel +:category: flying +:tags: flying +:status: published + +I actually did a flight on July 7th, but It was only one lap around the pattern. It was 95°F here, and I knew I wouldn't be in the right mind to continue my flight as I got to the traffic pattern after take off, so I just called crosswind, and headed back to land, a whopping 0.3 hour flight time! So that's what just one melting traffic pattern loop looks like! + +.. figure:: {static}/images/2021/07/10_loop.png + :alt: GPS track around the TTA Airport, just one pattern loop + + GPS track around the TTA Airport, just one pattern loop + +Anyway! My father was excited to fly with me, we took N2114F up. We intended to go from KTTA-SDZ-KRCZ, but it was still hot, so we decided to just do SDZ and back. + +We take off, and do one lap around the pattern - I want this to be my standard when I bring new people up, it gives them a way out to say "GET ME OFF PLEASE". After that one pattern lap we headed towards SDZ. It was a really smooth flight, we flew at 4500 feet to the VOR, then tried to get to 7500 feet on the way back (We had time, I wanted to climb) but it was just SO HOT that we ended up just staying at 5500feet, the climb was taking forever! + +When we were 10mi from the airport, it started getting super busy (BBQ day at the airport!), so we decided to waste some time in the practice area and I did a steep turn for him to waste more time. After that the traffic died down so we headed back to KTTA. I did a touch and go, a regular landing, and then being that there was no other traffic, practiced an emergency engine out procedure (what I failed on my checkride) and that went smoothly. + +So nothing super special about this trip, besides it was my first time flying my father. + + +.. figure:: {static}/images/2021/07/10_tyrel-in-passenger-seat.jpg + :alt: Me in the passenger seat climbing in and getting ready to go. + + Me in the passenger seat climbing in and getting ready to go. + +.. figure:: {static}/images/2021/07/10_tyrel-and-tony.jpg + :alt: Me and Dad sitting in airplane, somewhere above North Carolina + + Me and Dad sitting in airplane, somewhere above North Carolina + +.. figure:: {static}/images/2021/07/10_tyrel-pointing-out-window.jpg + :alt: Me pointing out the tiny window on the airplane. + + Me pointing out the tiny window on the airplane. + +.. figure:: {static}/images/2021/07/10_propeller-and-dashboard.jpg + :alt: Part of the dashboard, and CO detector, plus a blurry propeller in the background + + Part of the dashboard, and CO detector, plus a blurry propeller in the background + +.. figure:: {static}/images/2021/07/10_right-side-haze.jpg + :alt: Looking out the right window, the sky is hazy, part of a wing can be seen + + Looking out the right window, the sky is hazy, part of a wing can be seen + +.. figure:: {static}/images/2021/07/10_tony-and-tyrel.jpg + :alt: Inside the cockpit, profile view of me, and part of my father's head. + + Inside the cockpit, profile view of me, and part of my father's head. diff --git a/data/blog/posts/2021-07-25_mooney-m20j-checkout-sort-of.rst b/data/blog/posts/2021-07-25_mooney-m20j-checkout-sort-of.rst new file mode 100644 index 0000000..19b5815 --- /dev/null +++ b/data/blog/posts/2021-07-25_mooney-m20j-checkout-sort-of.rst @@ -0,0 +1,22 @@ +Mooney M20J checkout... Sort of +############################### +:author: tyrel +:category: flying +:tags: flying +:status: published + +Tuesday night I emailed my instructor saying "Hey the Mooney is available Sunday afternoon, could we start getting me ready for a checkout", and he said "sure, book it", so I did. Then last night I started reading through the POH. There's a lot of neat differences. Cowl flaps, retractable landing gear, constant speed prop. I definitely wanted to start getting my complex rating. I read through that POH, and look up a lot of videos on how to work the prop. I'm used to just throttle and mixture in the Warriors, but this has throttle, manifold pressure, and mixture levers. I learned the cool parts about how the oil pressure, and springs set the propeller angle. + +So I get to the airport at about 1pm, and we start talking about what I know. I explained how the undercarriage stuff works, under 132kias for lowering, and under 107kias for raising, and learned from Luke that there's two different speeds because of gravity and fighting gravity. We spend the first hour talking about why you need different manifold pressures. Then we head out to the plane and do a very comprehensive pre-check. I learned that the M20J doesn't have a trim tab, the whole tail raises and lowers on a pivot, NEAT! The flaps are suuuper wide and the ailerons are kind of thin. The landing gear was an interesting inspection because of how the poles and hinges open/close the gear. + +After the pre-flight we get flying. Things are pretty similar on the ground, as the propeller lever is pushed all the way forward, and the throttle is the main control. Luke points out that the propeller is rounded, so there's an RPM range that you shouldn't idle in, (1550-1950ish), vs the squarer prop that the other Mooney had. + +We take off, and one thing I check different is if I have available runway left, and when I don't, I can raise the landing gear at that point. It was pretty cloudy, so we had to climb up to 5500′, and eventually we hit 7000′ to jump over some clouds. First time really being above clouds this much, for the first hour we were over them. For the climb out he told me to set the plane to "twenty-five squared" - 2500rpm and 25in/hg. And every so often I would need to add more manifold pressure because of the atmosphere lapse rate while climbing. + +We got up to 6000′ ish, initially and then did some straight and level flying, super smooth above the clouds today. Then we did some 30 degree turns, did a few of those okay. When we did 45 degree turns, my first left one was kind of bad - lost a lot of altitude. One to the right was okay, and then the third one to the left I kept my altitude right. + +After turns we did slow flight, and it's pretty much the same controls. The power off stalls I was I guess putting in unconscious left aileron, because we kept starting to spin a little to the left, next time I fly I definitely need to practice that more. + +Well we ended up going pretty far east, at one point we were like 15 miles southeast of RDU, felt uncomfortable not talking to anyone there, but I guess it was out of the normal flight path for the jets so we were fine.We ended up needing to head back to the airport for 4pm, so I put the plane in a 750fpm descent while heading back, at one point I was able to punch through the clouds at around 2500′, and we joined the traffic pattern on the 45 for downwind on 21. I landed VERY well he said. He said that people transitioning from Warriors to Mooneys land hard because the M20J is a lot lower of a plane than the PA28, so they flare early and land hard. I didn't flare early both times we landed so he said we could just end for the day. I always love when instructors say I do well with landings! + +We then get back to the airport to do some payments and find out "Oh, Tyrel needs 250 hours without an instrument rating, to fly the M20J, or 150 with an instrument rating." So Oops, I guess it'll be a while before I can rent this, and get an actual check out. diff --git a/data/blog/posts/2021-08-04_cessna-152-checkout.rst b/data/blog/posts/2021-08-04_cessna-152-checkout.rst new file mode 100644 index 0000000..ad97f2d --- /dev/null +++ b/data/blog/posts/2021-08-04_cessna-152-checkout.rst @@ -0,0 +1,50 @@ +Cessna 152 Checkout +################### +:author: tyrel +:category: flying +:tags: flying +:status: published + +Today I got checked out in a Cessna 152. It was really my first time (besides spin training) flying high wing planes and I was a little nervous. We had a pretty standard pre-flight check, took a lot longer than just a "I'm out here to go on a flight", again because Luke was explaining things to me. There's a lot of differences like with the warrior, all the flaps are on hinges, but in the Cessna, there's one pulley hinge and some rollers you need to check, not just actuation hinges. Pointed out how the landing gear is different, there's plastic fairings that we need to repair quite frequently, and there are no dampeners. + +After the pre-flight we took off, Vr is pretty low, at 50 so the plane just wanted to float almost immediately in my opinion. It was also a cooler day outside than my most recent few flights, so that helped too. We took off, headed north to the practice area. I get up to 3000' msl and we level off, trim for cruise flight and lean the engine. This plane also has a vernier mixture control, so it's nice to be able to dial in the mixture, even if there's no on screen display of the gallons per hour like the M20J. There were some clouds at 4000' I think, but they were tiny, and we figured we didn't need to go above them, so we stuck at the 3000' level. + +This is where Luke says to just take the plane and do tiny things with it, like turn it uncoordinated, see how much rudder I'd need to do a turn. So I did that for a bit, did a standard rate turn to the left, and we switched to some 30° turns, leveled off then did two 45° turns, back to back. I hit my prop wash at the end of the second one, that always feels great. The first few turns were gross, this plane has old cables for the ailerons, so there's a lot of play when turning, so it took a few tries to be able to maintain altitude and find that comfortable dead spot with cable tension. The last plane (M20J) I flew, everything was much stiffer for turning. + +After the few turns, he said "oh no, there goes your engine" and throttled to idle. I looked around for a place to land, and didn't see one immediately. One thing I could have done better is look out the back window, because the 152 actually has one! Well I found a spot to "crash land" so I brought the plane to Vglide (60) and slowly descended towards the place to land. I guess in my "everything is okay" did the A B part of the emergency checklist, but I didn't do ABCDE, so we did it again. This time I: + +* A- pitched for best airspeed (60kts) +* B- found the best place to land +* C - Checklist, pretended to check fuel, master, key, primer, etc. +* D - pretended to switch to 121.5mHz and declare an emergency +* E- executed the landing, and prepared to exit (opened the door, turn off mixture, etc so the plane doesn't blow up and I can get out) + +and found a cute little field to land in. That was successful so we headed back towards the airport. + +At the airport everything was standard traffic pattern. There were three of us in the pattern, one tail dragger that was always just behind us, really friendly guy who liked chatting on frequency, and one low wing - maybe a warrior? - I can't remember. Well I did six landings. One normal landing, three short field (the last one I did great, landed on numbers and was done before A2 taxiway entrance) and then a few more normal. +Luke said that I was death gripping the yoke, so he did one lap around the airport - where he trimmed and only used his two fingers lightly to move the yoke. Watching him do that, I copied and did a lap around the pattern the same, much easier this time. + +We then landed, taxied back and went to park. Parking is WILD, you sit on the tail and then back then walk backwards to get the plane in place. I guess when your plane's max ramp weight is 1675lbs, that's easy to do! Three times as much as my motorcycle… After we chatted, he said he feels safe with me flying it, and I agreed. "I feel safe, but not super comfortable, but that only comes with time so I feel safe to take it up and get more comfortable". + +I then had to fill out the quiz, and scanned it, then emailed it to him. + + +.. figure:: {static}/images/2021/08/04_me-being-weird.jpg + :alt: Picture of me in the cockpit being a weirdo with a big smirky mouth + + Picture of me in the cockpit being a weirdo with a big smirky mouth + +.. figure:: {static}/images/2021/08/04_cessna-152-cockpit.jpg + :alt: The cockpit of a Cessna 152, yokes, gauges, etc. Outside you can see other planes on the ramp. + + The cockpit of a Cessna 152, yokes, gauges, etc. Outside you can see other planes on the ramp. + +.. figure:: {static}/images/2021/08/04_3d-track-1.jpg + :alt: Just a 3d diagram of the flight, not sure what I was focusing on here. + + Just a 3d diagram of the flight, not sure what I was focusing on here. + +.. figure:: {static}/images/2021/08/04_3d-track-2.jpg + :alt: Just a 3d diagram of the flight, not sure what I was focusing on here. + + Just a 3d diagram of the flight, not sure what I was focusing on here. diff --git a/data/blog/posts/2021-08-08_two-flights-and-some-nice-weather.rst b/data/blog/posts/2021-08-08_two-flights-and-some-nice-weather.rst new file mode 100644 index 0000000..0ccd80f --- /dev/null +++ b/data/blog/posts/2021-08-08_two-flights-and-some-nice-weather.rst @@ -0,0 +1,57 @@ +Two flights and some nice weather +################################# +:author: tyrel +:category: flying +:tags: flying +:status: published + +The other day there was a call to action on the Wings of Carolina slack. "Is anyone able to help this pilot get one of our planes back?" Seems this newly licensed pilot, (Congrats!) passed his check ride on Friday and the weather was not great so had to leave N69012 at Asheboro. I said I would be glad to help out if the IFR weather cleared by 9am, I could give him a ride over in N8080A. + +I booked a 9am-12pm block, and got to the airport at 8:55am. Grabbed the 80A book book and went to the lounge to check weather. Luckily the fog had burned off and the skies were clear! The pilot met me in the lounge, I wrapped up the weather briefing (nothing of note) and we went out to preflight. The left red nav light was out, but that's only required at night, confirmed by asking the other pilot. The fuel truck came and filled us up to the tabs (34 gallons) and we were off. + +My flight plans were from KTTA -> KHBI (drop off pilot) -> KEXX then back to KTTA. Just under a 2 hour round trip, which took a bit longer because when we got to the hold short line for RWY03, I realized my iPad wasn't connected to the GPS, so we fiddled with it for a few (no one was behind us, but oof Hobbs running) and couldn't get it working. That's fine, we had our GPS and the pilot knew what KHBI looked like form the air. We took off, departed the pattern to the north, got to a little bit higher then turned west. + +The pilot was super helpful, having a copilot be able to put in the radios for Siler City as we passed, and the AWOS/CTAFs in for KHBI was actually a really big load off my plate. It's the little things! Flew at about 3000ft over to KHBI because It was only a 20 minute flight so I stayed low. We get to the airport and he lets me know that RWY24 has a papi, so we chose that runway to land on. Land pretty okay, didn't grease it, but it is what it is. Taxi over and get him to his plane. I shut down so he can get out safely, then realize after he got out, I CAN'T OPEN THE DOOR. I had to yell him over to make sure I could open the door again, the bottom latch was stuck! I recorded a video, then instagrammed a `fellow pilot who is famous in his circles for getting stuck in his plane and having to call someone down in the area who landed and helped him out `_. Told him he's not alone, hah. + +Anyway, I was able to open the door so I felt like it was okay if I crashed and had to open the door myself. I turn the plane back on, plug in my Stratux for ADSB on iPad, and get ready to take off. I taxi over to the runway (took 24 again out) and hold short for a taildragger and another plane to land. I then take off and woahh there were like 80 birds at the end of the runway at like 300ft up. Luckily I was able to scare them and flew above them, then departed to the right (west) and headed to KEXX. Luckily thr CTAF is the same for both (122.8) so I was able to hear the traffic without fiddling with the radio. I set the AWOS beforehand, so I checked that when I was close, and still winds calm, not bad. + +Got 10 miles, out, announced position, 5 miles out, announced I was entering the downwind at a 45. I was on the down when a pilot to my right asked if I was on base, kind of weirded out because I wasn't so I was vigilant and said I was on downwind (again…). Then called base, and final and landed. Final was neat at Davidson county, had to fly over a factory, which was kind of cool. + +Landed, taxied back to the runway and lined up and waited again. The taildragger had followed me! Saw him land as I was at the hold short line. Waited for a jet to land and then off I went! I climbed out, departed to the east, and climbed up to 5500msl. Held 5500 very well this trip, the air was super calm and I'm getting better at electric trim, much easier than just wheel trim. + +At one point I turn the auto pilot on, with it set to 109° and 5500msl, but for some reason it made me descend and banked me left 45° and I wasn't liking that so I disengaged it. I climbed up back to 5500' for the rest of my trip. By the time I was back to KTTA, the winds still favored RWY03, so I joined the pattern on a 45° entry, and landed smoothly. At around 3000msl descending near Ashbury it was a bit bumpy, but that's the only turbulence I experienced. Such a nice morning. + +Well I get back to the airport and land and shut down… I'm stuck again! Door won't open. There was no one around I could yell to, so I called the front desk, no answer. I tried again, using different pressures and trying to see if there was like a little latch that didn't hook. I fiddled for a few minutes and was able to get out, I guess I got it just right. + +I then cover the plane, and squawk the door not opening, and posted the video on slack. The next pilot replied to my slack post later that evening that he had no problems. Maybe I'm just door cursed. + +Lauren and I then went to lunch before my next flight where I was taking her up in a Cessna 152 (N89333). Well we do the preflight dance, go taxi and take off. At like 300' above the runway, the damn pilot side door opens up! It's fine but I don't want to worry my wife, on her second flight with me ever, so I have her hold the door closed while I go finish the pattern loop (I was doing that anyway). Well MORE fun happened the pilots PTT(push to talk) button got stuck off, so I had to use her microphone to talk, (I could have just switched the plugs, but we were in the base turn). I kind of overshot base so had to over correct a little bit, but we landed okay, taxied back and agreed we were discontinuing our flight. We could have gone back up and carried on - we had the allotted time - but you know what? The rest of the flight wasn't in the cards. + +Video of me trapped in The Warrior - `https://youtu.be/0uAL30nCuYE `_. + + +.. figure:: {static}/images/2021/08/08_cockpit_selfie.jpg + :alt: Selfie in the cockpit, me wearing a blue shirt. + + Cockpit Selfie + +.. figure:: {static}/images/2021/08/08_behind_left_wing.jpg + :alt: A look at the ground behind the left wing + + A look at the ground behind the left wing + +.. figure:: {static}/images/2021/08/08_behind_right_wing.jpg + :alt: Looking at a runway in the distance, not sure which one now. + + Looking at a runway in the distance, not sure which one now. + +.. figure:: {static}/images/2021/08/08_hazy-highway.jpg + :alt: Looking in front of the left wing, at a highway, the sky is hazy + + Looking in front of the left wing, at a highway, the sky is hazy + +.. figure:: {static}/images/2021/08/08_hazy_runway.jpg + :alt: Hazy sky with a runway a couple miles away. + + Hazy sky with a runway a couple miles away. + diff --git a/data/blog/posts/2021-10-17_back-above-keene.rst b/data/blog/posts/2021-10-17_back-above-keene.rst new file mode 100644 index 0000000..e22f73e --- /dev/null +++ b/data/blog/posts/2021-10-17_back-above-keene.rst @@ -0,0 +1,78 @@ +Back Above Keene +################ +:author: tyrel +:category: flying +:tags: flying +:status: published + +.. figure:: {static}/images/2021/10/17_me_mom_n43337.jpg + :alt: Mom and I hugging in front of the right wing of N43337 + + Mom and I hugging in front of the right wing of N43337 + +It's my brother's wedding weekend and he asked me to take him and his new father-in-law flying. On October 16th I get to the airport, very well knowing that it's still too foggy for my 8am-10am flight that I was about to cancel in person. I still went in, because I haven't seen Beth in almost a year and wanted to say hi. We caught up, talked about how I've been going to Monadnock Aviation since I was in college - after my first flight on March 17, 2010! I called Levi and told him that we weren't flying, so he should just go finish getting ready for his wedding at 11:30. + +The next day I had booked for my mother to go up the first time with me. We get to the airport at 9:55 and the FBO is locked up, Uhhhh. I email/text some people and no one knows how to get a hold of the desk attendant. We had apparently JUST missed David, one of the CFIs, who was getting into N44836 with a student I presume. So we waited a bit and it seems he was called away to do some fueling with the fuel truck. No fault of his at all, Sundays can get a little rough with only one person being at the FBOs desk. + +We finally grab the book and keys and I go out and preflight. I'm in N43337 today, my favorite plane. I'm even wearing my "WARRIOR 337" shirt I got for soloing, for good luck! Preflight was easy as usual, once you've done it a hundred times in the same plane, you know what you're looking for from the checklists. + +I waved to mom and Lauren to come over and we got in, I showed mom and Lauren how to set up the headphones and get into the back seat. Following a safety briefing we were off! + +We taxied over from the Northwest ramp (The FBO area) to the East ramp and I did my run up there, the winds were 340° at 7kts gusting to 17, so the other planes were using runway 32. Waited around a bit for a radio check, the PTT button was sticky and no one was replying to my ask for radio checks, even though there were a few planes around, oh well. + +We took off on runway 32 after waiting in line (it was busy today, wow!) and I did a lap around the pattern. It was not clean, I haven't flown in just over two months, and we had the added weight of a back passenger, so I was a little nervous the first pattern loop. But I will always do one pattern loop with a new passenger - just to give them a chance to bail out! + +I'm not fond of runway 32, it has a high hill over Marcy Hill in Swanzey, NH and it always throws me off (probably because I have like 300 landings on runway 02 and maybe 15 on runway 32). The landing was fine, Lauren was taking pictures from the back and my mom said it was "wicked smooth, and you barely even felt the tires hit, I thought it was great, especially with the wind and everything" which as a first time passenger in a "oh my god I didn't know it was quite this small" airplane, I feel good about! + +We then taxied back to 32, and took off again, this time it was a west departure out to Spofford Lake and over my mom's house in Chesterfield. I probably should have headed to Brattleboro first after Spofford. Had I done this, we would have been in position to fly Brattleboro, VT north to Putney, VT west of the Connecticut River and my mom would have gotten a MUCH nicer view of her house. Instead we flew over the lake and through a little valley over Westmoreland and went south along the river. + +At one point I saw a dark cloud above and said "Okay we're about to go under a dark cloud, I'll try to avoid it but it might get turbulent", so mom and Lauren would know to hold on and probably 10 seconds later the plane went "woomp" down a bit because of turbulence - so they were prepared. After that small cloud we found a tiny patch of clean air and I turned to the right a little bit above Exit 3 and headed north again towards moms house, staying a little bit west of the river. I paralleled I91 again for a minute or two and then we got in line that I could fly close enough to mom's house that she could see it. + +After that fun bit we headed back, directly over Spofford Lake for some more sights, and onward to KEEN. + +We flew over Yale Forest, and I saw a cool cliff face I had never seen before - as we were entering RWY32 on a 45° entry. Entered the pattern and found myself VERY high (900msl at at 488msl airport) on final, on a 4000' runway with a displaced threshold so I executed a go around there. I probably could have made it, but with the wind and avoiding Marcy Hill, I figure it's always safe to Go Around. + +The next loop I had my sight pictures again at runway 32 and we landed, rolled out to Taxiway Sierra and parked the plane! + +Mom said it was fun! + + +.. figure:: {static}/images/2021/10/17_back-of-my-head.jpg + :alt: Back of my head while turning a bit + + Back of my head while turning a bit + +.. figure:: {static}/images/2021/10/17_cloudy-sun-view.jpg + :alt: Cloudy and Sunny View off the right wing. Monadnock in the distance. + + Cloudy and Sunny View off the right wing. Monadnock in the distance. + +.. figure:: {static}/images/2021/10/17_hannaford-kmart.jpg + :alt: Turning over route 9 near Home Depot and Hannaford + + Turning over route 9 near Home Depot and Hannaford + +.. figure:: {static}/images/2021/10/17_landing-32-14-on-final.jpg + :alt: Cockpit view, a mile out from runway 32/14 with the runway in sight. + + Cockpit view, a mile out from runway 32/14 with the runway in sight. + +.. figure:: {static}/images/2021/10/17_landing-32-14-short-final.jpg + :alt: Cockpit view, a 1/4 mile from runway 32/14 with the runway in sight. + + Cockpit view, a 1/4 mile from runway 32/14 with the runway in sight. + +.. figure:: {static}/images/2021/10/17_left-wing-looking-at-airport-and-monadnock.jpg + :alt: Looking off the left wing at mount Monadnock. + + Looking off the left wing at mount Monadnock. + +.. figure:: {static}/images/2021/10/17_sun-above-right-wing.jpg + :alt: Just pretty sky views, out the right window above the right wing at the sun and clouds. + + Just pretty sky views, out the right window above the right wing at the sun and clouds. + +.. figure:: {static}/images/2021/10/17_turning-from-backseat.jpg + :alt: Three degree turn above moms house. + + Three degree turn above moms house. diff --git a/data/blog/posts/2021-10-31_hello-world.rst b/data/blog/posts/2021-10-31_hello-world.rst new file mode 100644 index 0000000..f56a0c8 --- /dev/null +++ b/data/blog/posts/2021-10-31_hello-world.rst @@ -0,0 +1,11 @@ +Hello, world! +############# +:date: 2021-10-31 19:10 +:author: tyrel +:category: Blog +:slug: hello-world +:status: published + +This blog here I want to keep some permanent tech thoughts in more written form than my `Wiki `__ where I keep the majority of my written work. I do have a `flight blog `__, which has a lot of customization for maps and such that I don't want on here. + +I have revived some old blog posts from previous blogs, to give some life to this blog while I'm writing new articles. diff --git a/data/blog/posts/2021-11-04_python3-github-cli-tool-as-a-refresher.rst b/data/blog/posts/2021-11-04_python3-github-cli-tool-as-a-refresher.rst new file mode 100644 index 0000000..e476217 --- /dev/null +++ b/data/blog/posts/2021-11-04_python3-github-cli-tool-as-a-refresher.rst @@ -0,0 +1,37 @@ +Python3 GitHub CLI tool as a refresher +###################################### +:date: 2021-11-04 01:29 +:author: tyrel +:category: Tech +:tags: python, cli +:slug: python3-github-cli-tool-as-a-refresher +:status: published + +It's no lie that I love terminals. I wish I could live on a terminal and never really need to see a GUI application again. + +Last night I migrated a lot of my old code from one GitLab account to another (`tyrelsouza `__ to `tyrel `__) in an effort to clean up some of my usernames spread across the world. While doing that I noticed my `django-dbfilestorage `__ Python module that has been sitting and rotting for three years. I played around a little bit in order to port it to Python 3.9, but I ran into some base64 errors. I tried a little bit but it was late and I couldn't figure it out. My resolve is that I have been away from Python for too long so the little things - that I knew and love - had fallen away. I mentioned this to my friend Alex and he said *"make a barebones github cli (readonly?) with issue viewer, and stats display"*. I've embarked on a journey to refresh my Python well enough to repair DBFS. + +.. figure:: {static}/images/2021/11/github_cli-alex_prompt.png + :alt: Me: "okay python frioends, what should I make as a quick refresher into the Python world?" alex: "maybe: barebonx github cli (reasdonly?) with issue viewer and stats display" + +I knew I wanted to use ``httpx`` as my network client library, it's new, fast, and I have a couple friends who work on it. I started with a barebones ``requirements.in`` file, tossed in ``invoke``, ``pytes``\ t, and ``black``. From there I used ``pip-compile`` to generate my ``requirements.txt`` - (a tip I picked up recently while adding Pip-Compile support to the `Tidelift CLI `__) and I was good to go. + +The `docs for the GitHub API `__ are pretty easy to read, so I knew all I really needed to do was set my ``Accept`` header to be Version3 and I could view the schema. With the schema saved to a ``.json`` file I then wrote a ``GHub`` class to pull this data down using ``httpx.client.Client.get``, super simple! The only two endpoints I care about right now are the user and repos endpoints, so I made two ``get_`` functions for each. After a little bit of work - which I won't bore you with the super intricate details - I have a functional cli.py file. For now, the only interaction is a propmt from ``rich`` for a username, and then you get a fancy table (also from ``rich``) of the first page of results of repos, stargazer/watchers/forks counts, and a description. + +.. figure:: {static}/images/2021/11/github_cli-prompting_and_table.png + :alt: Prompting for the username and showing my table of repositories. + + Prompting for the username and showing my table of repositories. + +It was a fun evening of learning what's changed in Python3 since I last touched it, especially as I've spent the majority of my career in Python2.7. Type annotations are super awesome. I'll probably pick it up again once I get some more free time later in the week. It's also nice blog fodder! I already have a million things I want to do next - pagination, caching, some more interaction. + +.. figure:: {static}/images/2021/11/github_cli-pytest_running.png + :alt: Showing py.test running + + Showing py.test running + +I know the tool I'm writing is nothing special, especially with their own `cli `__ now, but I'm not looking at reinventing the wheel! + +Check out the code so far on my `GitLab `__ (*heh*, ironic it's there). + +Dependencies: `httpx `__, `pip-tools `__, `black `__, `invoke `__, `pytest `__, `pytest-httpx `__, `rich `__. diff --git a/data/blog/posts/2021-11-05_finished-my-github-cli-tool.rst b/data/blog/posts/2021-11-05_finished-my-github-cli-tool.rst new file mode 100644 index 0000000..5eefe9b --- /dev/null +++ b/data/blog/posts/2021-11-05_finished-my-github-cli-tool.rst @@ -0,0 +1,61 @@ +Finished my GitHub CLI tool +########################### +:date: 2021-11-05 00:08 +:author: tyrel +:category: Tech +:tags: python, cli +:slug: finished-my-github-cli-tool +:status: published + +I never intended this to be a full fleshed CLI tool comparable to the likes of the real GitHub CLI. This was simply a way to refresh myself and have fun. I have accomplished this, and am now calling this *"Feature Complete"*. You can play around with it yourself from the `repository on gitlab `__. + +HTTPX.LINKS +----------- + +Today I learned some fun things with ``httpx`` mainly. The main thing I focused on today was figuring out pagination. The GitHub API uses the ``link`` header which I had never seen before. + +The format of the header is a url, and then a relationship of that url. Lets take my friend Andrey's repos for example: + +.. raw:: html + +
+ +.. + + ``{'link': '; rel="next", ; rel="last"', ...}`` + + sample header value from Getting Andrey's repositories list and checking pagination + +.. raw:: html + +
+ +The link header there has two items split by a comma, each with two fields split by a semicolon, the first of which is a URL inside angle brackets... and AGHH this is going to be annoying to parse! Luckily ``httpx`` responses have this handled and ``client.get(...).links`` returns a lovely dictionary of the proper data. + +With a ``response.links.get('next')`` check, you can get the url from ``response.links['next']['url']``. So much nicer than writing some regular expressions. + +TESTING +------- + +With that accomplished, I then added ``pytest-cov`` to my ``requirements.in`` and was able to leverage some coverage checks. I was about 30% with the latest changes (much higher than anticipated!) so I knew what I wanted to focus on next. The API seemed the easiest to test first again, so I changed around how I loaded my fixtures and made it pass in a name and open that file instead. In real code I would not have the function in both my test files, I would refactor it, but again, this is just a refresher, I'm lazy. + +I decided earlier that I also wanted to catch HTTP 403 errors as I ran into a rate limit issue. Which, *I assure you dear reader*, was a thousand percent intentional so I would know what happens. Yeah, we'll go with that. + +Py.Test has a context manager called ``pytest.raises`` and I was able to just ``with pytest.raises(httpx.HttpStatusError)`` and check that raise really easily. + +The next bits of testing for the API were around the pagination, I faked two responses and needed to update my ``link`` header, checking the cases where there was NO ``link``, was multiple pages, and with my shortcut return - in case the response was an object not a list. Pretty straight forward. + +The GHub file tests were kind of annoying, I'm leveraging ``rich.table.Table`` so I haven't been able to find a nice "this will make a string for you" without just using ``rich``'s ``print`` function. I decided the easiest check was to see if the ``Table.Columns.Cells`` matched what I wanted, which felt a little off but it's fine. + +The way I generated the table is by making a generator in a pretty ugly way and having a bunch of ``repo['column'], repo['column']`` responses, rather than doing a dict comprehension and narrowing the keys down. If I ever come back to this, I MIGHT reassess that with a ``{k:v for k,v in repos if k in SELECTED_KEYS}`` and then yield a dictionary, but it's not worth the effort. + +Overall I'd say this project was fun. It gave me a glimpse back into the Python world, and an excuse to write a couple blog posts. My next project is to get a Django site up and running again, so I can figure out how to debug my ``django-dbfilestorage``. + +Closing Thoughts +---------------- + +If I had to do this again, I would probably have tried some test driven development. I've tried in the past, but I don't work on a lot of greenfield projects. I tend to be the kind of engineer who jumps HEAD FIRST into code and then tests are an after thought. + +I also kind of want to rewrite this in Go and Rust, two other languages I've been fond of lately, just to see how they'd compare in fun. I haven't done any API calls with Rust yet, only made a little Roguelike by following `Herbert Wolverson's Hands-On-Rust book `__. The `Tidelift CLI `__ is all Go and a bazillion API calls (okay like ten) so that wouldn't be too hard to use like SPF13's Cobra CLI library and make a quick tool that way. + +One fun thing I learned while moving things over to GitLab is that my user Tyrel is a super early adopter. I was in the first 36,000 people! I showed a screenshot of my user ID to my friend Sunanda at GitLab and we had fun finding that out. diff --git a/data/blog/posts/2021-11-11_postmortem-of-a-fun-couple-bugs.rst b/data/blog/posts/2021-11-11_postmortem-of-a-fun-couple-bugs.rst new file mode 100644 index 0000000..d70f0de --- /dev/null +++ b/data/blog/posts/2021-11-11_postmortem-of-a-fun-couple-bugs.rst @@ -0,0 +1,38 @@ +Postmortem of a fun couple bugs +############################### +:date: 2021-11-11 14:55 +:author: tyrel +:category: Tech +:tags: Go, dbus, bugs +:slug: postmortem-of-a-fun-couple-bugs +:status: published + +Story at my previous job: + + Tieg: Hey Tyrel, I can't run ``invoke sign 5555``, can you help with this? + +This is How my night started last night at 10pm. My coworker Tieg did some work on our `CLI `_ project and was trying to release the latest version. We use `invoke `_ to run our code signing and deployment scripts, so I thought it was just a quick "oh maybe I screwed up some python!" fix. It wasn't. + +I spent from 10:30 until 1:30am this morning going through and looking into why Tieg wasn't able to sign the code. The first thing I did was re-run the build on CircleCI, which had the same error, so hey! at least it was reproducible. The problem was that in our Makefile scripts we run ``tidelift version > tidelift-cli.version`` and then upload that to our deployment directories, but this was failing for some reason. We let clients download this file to see what the latest version is and then our CLI tool has the ability to selfupdate (except on homebrew) to pull this latest version if you're outdated. + +Once I knew what was failing, I was able to use CircleCI's ssh commands and log in, and see what happened, but I was getting some other errors. I was seeing some problems with ``dbus-launch`` so I promptly (mistakenly) yelled to the void on twitter about ``dubs-launch``. Well would you know it, I may have mentioned before, but I work with Havoc Pennington. + + Havoc Pennington: fortunately I wrote dbus-launch so may be able to tell you something, unfortunately it was like 15 years ago + +Pumped about this new revelation, I started looking at our ``keychain`` dependency, because I thought the issue was there as that's the only thing that uses ``dbus`` on Linux. Then we decided (Havoc Pointed it out) that it was a red herring, and maybe the problem was elsewhere. I at least learned a bit about dbus and what it does, but not enough to really talk about it to any detail. + +Would you know it, the problem was elsewhere. Tieg was running ``dtruss`` and saw that one time it was checking his ``/etc/hosts`` file when it was failing, and another time it was NOT, which was passing. Then pointed out a 50ms lookup to our ``download.tidelift.com`` host. + +Tieg then found `Issue 49517 `_ this issue where someone mentions that Go 1.17.3 was failing them for net/http calls, but not the right way. + +It turns out, that it wasn't the keyring stuff, it wasn't the *technically* the version calls that failed. What was happening is every command starts with a check to https://download.tidelift.com/cli/tidelift-cli.version which we then compare to the current running version, if it's different and outdated, we then say "you can run selfupdate!". What fails is that call to download.tidelift.com, because of compiling with go1.17.3 and a ``context canceled`` due to stream cleanup I guess? + +Okay so we need to downgrade to Go 1.17.2 to fix this. Last night in my trying, I noticed that our CircleCI config was using ``circle/golang:1.16`` as its docker image, which has been superseded by ``cimg/go:1.16.x`` style of images. But I ran into some problems with that while upgrading to ``cimg/go:1.17.x``. The problem was due to the image having different permissions, so I couldn't write to the same directories that when Mike wrote our ``config.yml`` file, worked properly. + +Tieg and I did a paired zoom chat and finished this up by cutting out all the testing/scanning stuff in our config files, and just getting down to the Build and Deploy steps. Found ANOTHER bug that Build seems to run as the ``circleci`` user, but Deploy was running as ``root``. So in the build ``working_directory`` setting, using a ``~/go/tidelift/cli`` path, worked. But when we restored the saved cache to Deploy, it still put it in ``/home/circle/go/tidelift/cli``, but then the ``working_directory`` of ``~/go/tidelift/cli`` was relative to ``/root/``. What a nightmare! + +All tildes expanded to ``/home/circleci/go/tidelift/cli`` set, Makefile hacks undone, (removing windows+darwin+arm64 builds from your scripts during testing makes things A LOT faster!) and PR Merged, we were ready to roll. + +I merged the PR, we cut a new version of TideliftCLI 1.2.5, updated the changelog and signed sealed delivered a new version which uses Go 1.17.2, writes the proper ``tidelift-cli.version`` file in deployment steps, and we were ready to ROCK! + +That was fun day. Now it's time to write some rspec tests. diff --git a/data/blog/posts/2022-01-09_garage-door-opener.rst b/data/blog/posts/2022-01-09_garage-door-opener.rst new file mode 100644 index 0000000..3547bc6 --- /dev/null +++ b/data/blog/posts/2022-01-09_garage-door-opener.rst @@ -0,0 +1,131 @@ +Garage Door Opener +################## +:date: 2022-01-09 22:46 +:author: tyrel +:category: Tech +:tags: home-assistant, home, esp8266, automation, esphome +:slug: garage-door-opener +:status: published + +I bought a house on October 9, 2020. This house has a garage door, and like any *normal person* of course I had to automate it. + +One of the first things I did when I moved into my house was research some automation. I initially bought a half dozen `ESP8266 devices `__ and tried to figure out what I could do with them. I found Home Assistant and set that up on my server computer, along with ZoneMinder for security cameras. + +.. figure:: {static}/images/2022/01/garage-nodemcu_esp8266_module.jpg + :alt: NodeMCU ESP8266 module + + NodeMCU ESP8266 module + +I knew I would need some sort of relay (domain purchased from is gone) and `reed switches `__ to trigger the door and sense its position, so I purchased some from the internet. But my friend Paul said all I needed was a `MOSFET `__ so I bought one of those too. I tried to figure out how to trigger the door with a mosfet, but I was never able to. I won't document those failures. + +.. figure:: {static}/images/2022/01/garage-magnetic_reed_switch.png + :alt: Magnetic Reed Switch + :figclass: wp-image-183 + + Magnetic Reed Switch + +Home Assistant has a plugin called ESPHome where you can write yaml files to configure an esp8266 module. This then builds a binary which you need to flash onto the esp8266 at least once via usb. From then on you can then on you can upload from a web form and drop the bin file in manually, or just press the UPLOAD button from ESPHome. I set my relay up on pin 19/D1 for the digital pin, and 16/GND,10/3v3 for the power. The Reed switch I tossed on 15/D7 and 11/GND but that could have been anywhere. See Schematic below. It still doesn't have an enclosure. + +.. figure:: {static}/images/2022/01/09_relay.jpg + :alt: Relay in blue, and wires going to the NodeMCU + + Relay in blue, and wires going to the NodeMCU + +.. figure:: {static}/images/2022/01/garage-Garage_door_schematic.png + :alt: Schematic + + Schematic + +With the relay triggering, I still had one problem - I'd trigger the door and it wouldn't do anything! Something else was a problem. The wiring for the garage door terminates in four screws, two of which are the door trigger. I tried poking it with a multimeter and having someone push the door button on the wall, but I was never successful that way, as any contact between the two poles would just open the door anyway. + +After some unsuccessful thinking, I figured it was time to purchase an oscilloscope. I've always wanted one, in fact I bought an old `Heathkit `__ one once, but never got it working as I would have had to replace all the capacitors, and the insides is all perfboard - A NIGHTMARE. + +I found this `USB Logic Analyzer and Oscilloscope `__ on amazon and figure I'd try it out. It came while my father was in town, so he was pretty interested in seeing it work as well. Of course I'm very fond of open source software so I downloaded `PulseView `__ and found out that the LHT00SU1 is a ``fx2lafw`` driver device. The interface to connect is really simple, you just look for your driver, and Scan for any usb devices that are using that driver and they show up to choose from. + +I plugged the 1ACH and GND cables in and hooked them on the +/- wires where they attach to the door motor. Once you have a device connected, you then click Run on the top left, and trigger what ever mechanism (my garage door button) and see what happens. + +I was very pleasantly surprised when I saw some movement on the A0 line! + +.. figure:: {static}/images/2022/01/garage-pulses_180ms_140ms.png + :alt: Pulses of about 180ms and 140ms + :figclass: wp-image-184 + + Pulses of about 180ms and 140ms + +I added some markers to figure out what I needed to imitate in ESPHome, and saw that it's about 150ms high with a 225ms low, then another 150ms high and then low again. + +This looks like this in yaml: + +.. code-block:: yaml + + switch: + - platform: gpio + pin: D1 + id: relay + - platform: template + name: "Garage Remote" + icon: "mdi:gate" + turn_on_action: + - switch.turn_on: relay + - delay: 150ms + - switch.turn_off: relay + - delay: 225ms + - switch.turn_on: relay + - delay: 150ms + - switch.turn_off: relay + +I'm pretty sure I jumped and screamed with excitement when it opened! + +Once the door was opening and closing, I was able to add more yaml to set another binary sensor to show whether it was open or closed (from the reed sensor): + +.. code-block:: yaml + + binary_sensor: + - platform: gpio + pin: + number: D7 + inverted: true + mode: INPUT_PULLUP + name: "Garage Door Closed" + +All together this is shown on my Home Assistant Lovelace dashboard using two cards, one that shows a closed door, and one with an open door (both actual pictures of the door!) with a button to open it. Once it opens or closes the other card switches into place, Home Assistant at least at the time didn't have good conditional cards like I wanted. + +.. code-block:: yaml + + type: conditional + conditions: + - entity: binary_sensor.garage_door_closed + state: 'on' + card: + type: picture-glance + title: Garage (Closed) + image: 'https://tyrel.dev/house/garage_door.jpeg' + entities: + - entity: switch.garage_remote + hold_action: + action: none + + +.. figure:: {static}/images/2022/01/garage-Lovelace_garage_door_closed.png + :alt: Closed door state and button + + Closed door state and button + +Happy with the state of my Garage Door opening button, I can now yell at my phone to open the garage door (it's a "secure" switch so it requires the phone to to be open before OK Google will trigger the door). + +There's a couple more pictures in my `Instagram post `__ about it. + +I know I could have bought a device to do this myself, but this is fully mine, my code, and my experiment with learning how to automate things at home, I gained way more out of this project than I did if I just bought a MyQ or what ever is popular these days. + +Bill of Materials +~~~~~~~~~~~~~~~~~ + +* `Magnetic Switch `_ +* `NodeMCU `_ +* `Relay Shield `_ + + +Notes +~~~~~ + +This is no longer in service, as I replaced the door and have a Chamberlain MyQ system now. Less fun, but at least it's serviceable. diff --git a/data/blog/posts/2022-05-07_an-update-on-flying.rst b/data/blog/posts/2022-05-07_an-update-on-flying.rst new file mode 100644 index 0000000..9523593 --- /dev/null +++ b/data/blog/posts/2022-05-07_an-update-on-flying.rst @@ -0,0 +1,14 @@ +An Update On Flying +################### +:date: 2022-05-07 01:15 +:author: tyrel +:category: Personal +:tags: flying +:slug: an-update-on-flying +:status: published + +I took ten years to get my pilot's license. From March 17, 2010 to December 30, 2020. It was amazing. I now find myself a year and a half later from achieving my goal and I don't find myself interested enough right now to go flying. There's a gas crisis, there's a pandemic, there's a lot of political things going on, a war in Ukraine, that it kind of feels bad wasting hundreds of dollars just going sight seeing. + +I just completed a ground school course for my Instrument Rating -- I still need to take the written test part. With that out of the way I can start actually flying with an instructor to get my instrument rating. One of the planes the club that I am a part of has is a `Mooney M20J `__. This requires 250 hours of flight time, or 100 hours and an instrument rating. I'm at that annoying 145 hour mark that dissuades me from wasting 100 hours just to fly that plane, and wanting to get my instrument rating. + +I left my previous job last December so I didn't have the excess money to fly for a month while job hunting, and well, habit becomes habit... I haven't flown since `October `__! I'm definitely in a better place now, with a much nicer job and salary though. I'm hoping to maybe pick it back up again this fall. I wasn't pleased AT ALL (those who follow me on twitter will probably know this, online class is not the environment for me) with the ground school over Zoom, so I want to redo this by watching the Sporty's ground school. I need to put aside some time over the next coming weeks to actually sit down and watch it. Hopefully I can start flying with an instructor soon. I'm not looking forward to taking the written test, as I have to go visit a testing center at RDU airport - so there is that kind of delaying me too. diff --git a/data/blog/posts/2022-06-01_writing-an-epub-parser-part-1.rst b/data/blog/posts/2022-06-01_writing-an-epub-parser-part-1.rst new file mode 100644 index 0000000..86fcb92 --- /dev/null +++ b/data/blog/posts/2022-06-01_writing-an-epub-parser-part-1.rst @@ -0,0 +1,99 @@ +Writing an EPUB parser. Part 1 +############################## +:date: 2022-06-01 01:41 +:author: tyrel +:category: Python +:tags: epub, python +:slug: writing-an-epub-parser-part-1 +:status: published + +Parsing Epubs +------------- + +Recently I've become frustrated with the experience of reading books on my Kindle Paperwhite. The swipe features, really bother me. I really like MoonReader on Android, but reading on my phone isn't always pleasing. This lead me to look into other hardware. I've been eyeing the BOOX company a while ago, but definitely considering some of their new offerings some time. Until the time I can afford the money to splurge on a new ebook reader, I've decided to start a new project, making my own ebook reader tools! + +I'm starting with EPUBs, as this is one of the easiest to work with. At its core, an EPUB is a zip file with the ``.epub`` extension instead of ``.epub`` with many individual XHTML file chapters inside it. You can read more of how they're structured yourself over at `FILEFORMAT `__. + +The tool I've chosen for reading EPUBs is the Python library `ebooklib `__. This seemed to be a nice lightweight library for reading EPUBs. I also used `DearPyGUI `__ for showing this to the screen, because I figured why not, I like GUI libraries. + +My first task was to find an EPUB file, so I downloaded one from my calibre server. I convert all my ebook files to ``.epub`` and ``.mobi`` on my calibre server so I can access them anywhere I can read my OPDS feed. I chose Throne of Glass (abbreviating to ``TOG.epub`` for rest of post). Loading I launched Python, and ran + +.. code-block:: console + + >>> from ebooklib import epub + >>> print(book := epub.read_epub("TOG.epub") + +This returned me a ```` , seeing I had an EpubBook I ran a ``dir(book)`` and found the properties available to me + +.. code-block:: python + + ['add_author', 'add_item', 'add_metadata', 'add_prefix', + 'bindings', 'direction', 'get_item_with_href', 'get_item_with_id', + 'get_items', 'get_items_of_media_type', 'get_items_of_type', + 'get_metadata', 'get_template', 'guide', + 'items', 'language', 'metadata', 'namespaces', 'pages', 'prefixes', + 'reset', 'set_cover', 'set_direction', 'set_identifier', 'set_language', + 'set_template', 'set_title', 'set_unique_metadata', 'spine', + 'templates', 'title', 'toc', 'uid', 'version'] + +Of note, the ``get_item_with_X`` entries caught my eye, as well as ``spine``. For my file, ``book.spine`` looks like it gave me a bunch of tuples of ID and a ``"yes"`` string of which I had no Idea what was. I then noticed I had a ``toc`` property, assuming that was a Table of Contents, I printed that out and saw a bunch of ``epub.Link`` objects. This looks like something I could use. + +I will note, at this time I was thinking that this wasn't the direction I wanted to take this project. I really wanted to learn how to parse these things myself, unzip, parse XML, or HTML, etc., but I realized I needed to see someone else's work to even know what is going on. With this "defeat for the evening" admitted, I figured hey, why not at least make SOMETHING, right?" I decided to carry on. + +Seeing I was on at least some track, I opened up PyCharm and made a new Project. First I setup a class called Epub, made a couple of functions for setting things up and ended up with + +.. code-block:: python + + class Epub: + def __init__(self, book_path: str) -> None: + self.contents: ebooklib.epub.EpubBook = epub.read_epub(book_path) + self.title: str = self.contents.title + self.toc: List[ebooklib.epub.Link] = self.contents.toc + +I then setup a ``parse_chapters`` file, where I loop through the TOC. Here I went to the definition of ``Link`` and saw I was able to get a ``href`` and a ``title``, I decided my object for chapters would be a dictionary (I'll move to a DataClass later) with ``title`` and ``content``. I remembered from earlier I had a ``get_item_by_href`` so I stored the itext from the TOC's href: ``self.contents.get_item_with_href(link.href).get_content()``. This would later prove to be a bad decision when I opened "The Fold.epub" and realized that a TOC could have a tuple of ``Section`` and ``Link``, not just ``Links``. I ended up storing the item itself, and doing a double loop in the ``parse_chapters`` function to loop if it's a tuple. + +.. code-block:: python + + def parse_chapters(self) -> None: + idx = 0 + for _item in self.toc: + if isinstance(_item, tuple): # In case is section tuple(section, [link, ...]) + for link in _item[1]: + self._parse_link(idx, link) + idx += 1 + else: + self._parse_link(idx, _item) + idx += 1 + +``_parse_link`` simply makes that dictionary of ``title`` and ``item`` I mentioned earlier, with a new ``index`` as I introduced buttons in the DearPyGUI at this time as well. + +.. code-block:: python + + def _parse_link(self, idx, link) -> None: + title = link.title + self.chapters.append(dict( + index=idx, + title=title, + item=self.contents.get_item_with_href(link.href) + )) + +That's really all there is to make an MVP of an EPUB parser. You can use ``BeautifulSoup`` to parse the HTML from the ``get_body_contents()`` calls on items, to make more readable text if you want, but depending on your front end, the HTML may be what you want. + +In my implementation my Epub class keeps track of the currently selected chapter, so this loads from all chapters and sets the ``current_text`` variable. + +.. code-block:: python + + def load_view(self) -> None: + item = self.chapters[self.current_index]['item'] + soup = BeautifulSoup(item.get_body_content(), "html.parser") + text = [para.get_text() for para in soup.find_all("p")] + self.current_text = "\n".join(text) + +I don't believe any of this code will be useful to anyone outside of my research for now, but it's my first step into writing an EPUB parser myself. + +The DearPyGUI steps are out of scope of this blog post, but here is my `final ebook Reader `__ which is super inefficient! + +.. figure:: {static}/images/2022/06/ebook-ebook_reader.png + :alt: final ebook reader, chapters on left, text on right + +I figure the Dedication page is not *as* copywrited as the rest of the book, so it's fair play showing that much. Sarah J Maas, if you have any issues, I can find another book for my screenshots. diff --git a/data/blog/posts/2022-06-02_2016-monitoring-a-co2-tank-in-a-lab-with-a-raspberry-pi.rst b/data/blog/posts/2022-06-02_2016-monitoring-a-co2-tank-in-a-lab-with-a-raspberry-pi.rst new file mode 100644 index 0000000..feddb2b --- /dev/null +++ b/data/blog/posts/2022-06-02_2016-monitoring-a-co2-tank-in-a-lab-with-a-raspberry-pi.rst @@ -0,0 +1,44 @@ +2016 Monitoring a CO2 tank in a Lab with a raspberry pi +####################################################### +:date: 2022-06-02 16:54 +:author: tyrel +:category: Tech +:tags: Linux, raspberrypi +:slug: monitoring-a-co2-tank-in-a-lab-with-a-raspberry-pi +:status: published + +This was written in 2017, but I found a copy again, I wanted to post it again. + +The Story +--------- + +For a few months last year, I lived around the block from work. I would sometimes stop in on the weekends and pick up stuff I forgot at my desk. One day the power went out at my apartment and I figure I would check in at work and see if there were any problems. I messaged our Lab Safety Manager on slack to say "hey the power went out, and I am at the office. Is there anything you'd like me to check?". He said he hadn't even gotten the alarm email/pages yet, so if I would check out in the lab and send him a picture of the CO2 tanks to make sure that nothing with the power outage compromised those. Once I had procured access to the BL2 lab on my building badge, I made my way out back and took a lovely picture of the tanks, everything was fine. + +The following week, in my one on one meeting with my manager, I mentioned what happened and she and I discussed the event. It clearly isn't sustainable sending someone in any time there was a power outage if we didn't need to, but the lab equipment doesn't have any monitoring ports. + +Operation Lab Cam was born. I decided to put together a prototype of a Raspberry Pi 3 with a camera module and play around with getting a way to monitor the display on the tanks. After a few months of not touching the project, I dug into it in a downtime day again. The result is now we have an automated camera box that will take a picture once a minute and display it on an auto refreshing web page. There are many professional products out there that do exactly this, but I wanted something that has the ability to be upgraded in the future. + +Summary of the Technical Details +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Currently the entire process is managed by one bash script, which is a little clunky, but it's livable. The implementation of the script goes a little like: + +#. Take a picture to a temporary location. +#. Add a graphical time stamp. +#. Copy that image to both the currently served image, and a timestamped filename backup. + +The web page that serves the image is just a simple web page that shows the image, and refreshes once every thirty seconds. + +The Gritty Technical Details +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The program I'm using to take pictures is the **raspistill** program. If I had written my script to just call **raspistill** every time I wanted a picture taken, it would have potentially taken a lot longer to save the images. This happens because it needs to meter the image every time, which adds up. The solution is Signal mode and turning raspistill into a daemon. If you enable signal mode, any time you send a SIGUSR1 to the process, the backgrounded process will then take the image. + +Instead if setting up a service with *systemd*, I have a small bash script. At the beginning, I run a **ps aux** and check if raspistill is running, if it's not, I start up a new instance of raspistill with the appropriate options and background it. The next time this script runs, it will detect that raspistill is running and be almost a no-op. + +After this, I send a SIGUSR1 (kill -10) to take the picture which is then saved, un-timestamped. Next up I call imagemagick's convert on this image, I crop out the center (so I couldn't use **raspistill's** "-a 12" option) because all I care about is a 500x700 pixel region. + +This is then copied to the image that is served by the web page, and also backed up in a directory that nginx will listen to. + +.. figure:: {static}/images/2022/06/bl2cam-leds.jpg + :alt: Leds on a CO2 tank diff --git a/data/blog/posts/2022-10-13_scrollbar-colors.rst b/data/blog/posts/2022-10-13_scrollbar-colors.rst new file mode 100644 index 0000000..2432bcb --- /dev/null +++ b/data/blog/posts/2022-10-13_scrollbar-colors.rst @@ -0,0 +1,44 @@ +Scrollbar Colors +################ +:date: 2022-10-13 12:07 +:author: tyrel +:category: Website +:tags: css +:slug: scrollbar-colors +:status: published + +Was talking to someone about CSS Nostalgia and "back in my day" when scrollbar colors came up. + +.. code-block:: css + + /* For Chromium based browsers */ + ::-webkit-scrollbar { + background: #2A365F; + } + ::-webkit-scrollbar-thumb { + background: #514763; + } + + /* For Firefox */ + html { + scrollbar-color: #514763 #2A365F; + } + +Firefox and Chrome have different selectors, so in order to support the majority of browsers, you need both. + +.. figure:: {static}/images/2022/10/scrollbar-chrome.png + :alt: Chrome with a blue/purple scrollbar + :width: 940px + :height: 688px + + Chrome with a blue/purple scrollbar + +.. figure:: {static}/images/2022/10/scrollbar-safari.png + :alt: Safari with a blue/purple scrollbar + + Safari with a blue/purple scrollbar + +.. figure:: {static}/images/2022/10/scrollbar-firefox.png + :alt: Firefox with a blue/purple scrollbar + + Firefox with a blue/purple scrollbar diff --git a/data/blog/posts/2022-10-16_pelican.rst b/data/blog/posts/2022-10-16_pelican.rst new file mode 100644 index 0000000..3dd6cb8 --- /dev/null +++ b/data/blog/posts/2022-10-16_pelican.rst @@ -0,0 +1,27 @@ +New Blog - Pelican! +################### +:date: 2022-10-16 23:30 +:author: tyrel +:category: Blog +:tags: python, pelican +:slug: pelican-new-blog +:status: published + +If you have read the previous post, and then looked at this one, there are a LOT of changes that happened. +I was recently exploited and had ``heysrv.php`` files everywhere, so I have decided to forego wordpress for now. +I am now using `Pelican `_! + +It's very sleek, and only took me a few hours to port my Wordpress export to Pelican reStructuredText format. + +All I have to do is run ``invoke publish`` and it will be on the server. +No PHP, no database. +All files properly in their right places. + +It comes with your standard blogging experience: Categories, Tags, RSS/Atom feeds, etc. +You need to set up Disqus — which I probably won't — in order to get comments though. + +I'm pleased with it. +I have posts go under YYYY/MM/slug.html files, which I like for organization. +Posting images is easy, I just toss it under ``content/images/YYYY/MM/`` with date for organization. + + diff --git a/data/blog/posts/2022-10-17_comparing-go-gorm-and-sqlx.rst b/data/blog/posts/2022-10-17_comparing-go-gorm-and-sqlx.rst new file mode 100644 index 0000000..556403e --- /dev/null +++ b/data/blog/posts/2022-10-17_comparing-go-gorm-and-sqlx.rst @@ -0,0 +1,190 @@ +Comparing Go GORM and SQLX +########################## +:author: tyrel +:category: Tech +:tags: go, sql, python, gorm, sqlx +:status: published + +Django ORM - My History +~~~~~~~~~~~~~~~~~~~~~~~ + +I'm not the best SQL developer, I know it's one of my weak points. +My history is I did php/mysql from the early 2000s until college. +In college I didn't really focus on the Database courses, the class selection didn't have many database course. +The one Data Warehousing course I had available, I missed out on because I was in England doing a study abroad program that semester. +My first job out of college was a Python/Django company - and that directed my next eight years of work. + +Django, if you are unaware, is a MVC framework that ships with a really great ORM. +You can do about 95% of your database queries automatically by using the ORM. + +.. code-block:: python + + entry, created = Entry.objects.get_or_create(headline="blah blah blah") + +.. code-block:: python + + q = Entry.objects.filter(headline__startswith="What") + q = q.filter(pub_date__lte=datetime.date.today()) + q = q.exclude(body_text__icontains="food") + +Above are some samples from the DjangoDocs. +But enough about Django. + +My Requirements +~~~~~~~~~~~~~~~ + +Recently at my job I was given a little bit of leeway on a project. +My team is sort of dissolving and merging in with another team who already does Go. +My Go history is building a CLI tool for the two last years of my `previous job. `_ +I had never directly interacted with a database from Go yet. +I wanted to spin up a REST API (I chose Go+Gin for that based on forty five seconds of Googling) and talk to a database. + +GORM +~~~~ + +Being that I come from the Django (and a few years of ActiveRecord) land, I reached immediately for an ORM, I chose GORM. +If you want to skip directly to the source, check out `https://gitea.tyrel.dev/tyrel/go-webservice-gin `_. +Full design disclosure: I followed a couple of blog posts in order to develop this, so it is in the form explictly decided upon by the `logrocket blog post `_ and may not be the most efficient way to organize the module. + +In order to instantiate a model definition, it's pretty easy. +What I did is make a new package called ``models`` and inside made a file for my Album. + +.. code-block:: go + + type Album struct { + ID string `json:"id" gorm:"primary_key"` + Title string `json:"title"` + Artist string `json:"artist"` + Price float64 `json:"price"` + } + +This tracks with how I would do the same for any other kind of struct in Go, so this wasn't too difficult to do. +What was kind of annoying was that I had to also make some structs for Creating the album and Updating the Album, this felt like duplicated effort that might have been better served with some composition. + +I would have structured the controllers differently, but that may be a Gin thing and how it takes points to functions, vs pointers to receivers on a struct. +Not specific to GORM. +Each of the controller functions were bound to a ``gin.Context`` pointer, rather than receivers on an AlbumController struct. + +The ``FindAlbum`` controller was simple: + +.. code-block:: go + + func FindAlbum(c *gin.Context) { + var album models.Album + if err := models.DB.Where("id = ?", c.Param("id")).First(&album).Error; err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"}) + } + c.JSON(http.StatusOK, gin.H{"data": album}) + } + +Which will take in a ``/:id`` path parameter, and the GORM part of this is the third line there. + +.. code-block:: go + + models.DB.Where("id = ?", c.Param("id")).First(&album).Error + +To run a select, you chain a ``Where`` on the DB (which is the connection here) and it will build up your query. +If you want to do joins, this is where you would chain ``.Joins`` etc... +You then pass in your album variable to bind the result to the struct, and if there's no errors, you continue on with the bound variable. +Error handling is standard Go logic, ``if err != nil`` etc and then pass that into your API of choice (Gin here) error handler. + +This was really easy to set up, and if you want to get a slice back you just use ``DB.Find`` instead, and bind to a slice of those structs. + +.. code-block:: go + + var albums []models.Album + models.DB.Find(&albums) + + +SQLX +~~~~ + +SQLX is a bit different, as it's not an ORM, it's extensions in Go to query with SQL, but still a good pattern for abstracting away your SQL to some dark corner of the app and not inline everywhere. +For this I didn't follow someone's blog post — I had a grasp on how to use Gin pretty okay by now and essentially copied someone elses repo with my existing model. +`gin-sqlx-crud `_. + +This one set up a bit wider of a structure, with deeper nested packages. +Inside my ``internal`` folder there's ``controllers``, ``forms``, ``models/sql``, and ``server``. +I'll only bother describing the ``models`` package here, as thats the SQLX part of it. + +In the ``models/album.go`` file, there's your standard struct here, but this time its bound to ``db`` not ``json``, I didn't look too deep yet but I presume that also forces the columns to set the json name. + +.. code-block:: go + + type Album struct { + ID int64 `db:"id"` + Title string `db:"title"` + Artist string `db:"artist"` + Price float64 `db:"price"` + } + +An interface to make a service, and a receiver are made for applying the ``CreateAlbum`` form (in another package) which sets the form name and json name in it. + +.. code-block:: go + + func (a *Album) ApplyForm(form *forms.CreateAlbum) { + a.ID = *form.ID + a.Title = *form.Title + a.Artist = *form.Artist + a.Price = *form.Price + } + +So there's the receiver action I wanted at least! + +Nested inside the ``models/sql/album.go`` file and package, is all of the Receiver code for the service. +I'll just comment the smallest one, as that gets my point across. +Here is where the main part of GORM/SQLX differ - raw SQL shows up. + +.. code-block:: go + + func (s *AlbumService) GetAll() (*[]models2.Album, error) { + q := `SELECT * FROM albums;` + + var output []models2.Album + err := s.conn.Select(&output, q) + // Replace the SQL error with our own error type. + if err == sql.ErrNoRows { + return nil, models2.ErrNotFound + } else if err != nil { + return nil, err + } else { + return &output, nil + } + } + +This will return a slice of Albums - but if you notice on the second line, you have to write your own queries. +A little bit more in control of how things happen, with a ``SELECT * ...`` vs the gorm ``DB.Find`` style. + +To me this feels more like using ``pymysql``, in fact its a very similar process. +(SEE NOTE BELOW) +You use the ``service.connection.Get`` and pass in what you want the output bound to, the string query, and any parameters. +This feels kind of backwards to me - I'd much rather have the order be: query, bound, parameters, but thats what they decided for their order. + +Conclusion +~~~~~~~~~~ + +Overall, both were pretty easy to set up for one model. +Given the choice I would look at who the source code is written for. +If you're someone who knows a lot of SQL, then SQLX is fine. +If you like abstractions, and more of a "Code as Query" style, then GORM is probably the best of these two options. + +I will point out that GORM does more than just "query and insert" there is migration, logging, locking, dry run mode, and more. +If you want to have a full fledged system, that might be a little heavy, then GORM is the right choice. + +SQLX is great if what you care about is marshalling, and a very quick integration into any existing codebase. + +Repositories +~~~~~~~~~~~~ + +* `Go, Gin, Gorm `_ +* `Go, Gin, sqlx `_ + +Notes +~~~~~ + +I sent this blog post to my friend `Andrey `_ and he mentioned that I was incorrect with my comparision of sqlx to pymysql. +To put it in a python metaphor, "sqlx is like using urllib3, gorm is like using something that generates a bunch of requests code for you. Using pymysql is like using tcp to do a REST request." +Sqlx is more akin to `SqlAlchemy core `_ vs using `SqlAlchemy orm `_. +Sqlx is just some slight extensions over ``database/sql``. +As the sort of equivalent to ``pymysql`` in Go is ``database/sql/driver`` from the stdlib. + diff --git a/data/blog/posts/2022-11-04_neighbors-water-heater-automation-part-1.rst b/data/blog/posts/2022-11-04_neighbors-water-heater-automation-part-1.rst new file mode 100644 index 0000000..4209d58 --- /dev/null +++ b/data/blog/posts/2022-11-04_neighbors-water-heater-automation-part-1.rst @@ -0,0 +1,115 @@ +Neighbor's Water Heater Automation (part 1) +########################################### +:author: tyrel +:category: Automation +:tags: automation, c++, esp8266, servo, stepper +:status: published + +The Setting +~~~~~~~~~~~ + +My neighbor has a Bosch tankless water heater he put in last year. +This water heater has one slight problem that when the power even blips a single second, it gets set back to its lowest temperature of 95°F. +My neighbor (we'll call him Frank for this post because Frank Tank is funny) Frank wants to set his heater to 120°F in his house. +The problem arises in that his water heater is under the house in his crawl space. + +Without an easy way to set his temperature, he needs to crawl under his crawl space and turn a dial *EVERY. SINGLE. TIME.* + +He asked me if I knew of anything off the shelf that would help. +I did not. +So I said the only logical thing someone `like me `_ would have done. +"I can totally automate that!" + +The Lay Of The Land +~~~~~~~~~~~~~~~~~~~ + +He has a `Bosch Tronic 6000C `_, with what appears to be a rotary encoder knob to set the temperature. +I only spent a few minutes under his house while planning this and didn't think to any measuring of how many detents to rotate, or how long the dial took to rotate to 120°F, so my first pass of this project is done with estimations. + +.. figure:: {static}/images/2022/11/04_heater.png + :alt: bosch heater with a temperature 7 segment LED set to 120F + :width: 920px + + +Project Time - Round 1! +~~~~~~~~~~~~~~~~~~~~~~~ + +I have a few random servos laying around, and an NodeMCU ESP8266 module. +I figure these would be the perfect solution! ... note: was half right... + +I found some code online by `Kumar Aditya `_ that is for the `two items in my current parts list `_ (ESP8266 and SG90) + +The Original code runs a web server on port 80, and runs a web page with some jQuery (wow it's been a while) to change the angle of the servo. +I realized this wasn't what I needed because my servos could only go 180° and I might need to go multiple rotations. +I found a youtube video on how to make a `SG90 run infinite in either direction `_, so I did those modifications. +I then modified the front end code a little bit. + +The new code on the back end was actually exactly the same, even though the effect was slightly different. +It would run on port 80, listen at ``/`` and ``/angle``, but the angle here was more of direction and speed (a vector?). +The way the servo was built, 160° was "Stop", higher than that was rotate clockwise, lower was rotate counter clockwise. + +I put three buttons on my page that would be "Lower" (150), "STOP" (160), and "Higher" (170). +I then did some standard debouncing and disabling of buttons using setTimeout and such. + +For a final touch I added in a range slider for "Time". +This held how many seconds after pressing Higher or Lower, that I would send the STOP command again. + +This seemed to work relatively well, but I figure I should just use a stepper motor if I was attempting to emulate one this way. +I dug around in my closet and was able to find some parts. + +.. figure:: {static}/images/2022/11/04_servo.png + :alt: blue case servo with a white arm, cables running off screen. sitting on a desk. + +Project Time - Round 2! +~~~~~~~~~~~~~~~~~~~~~~~ + +I was able to rummage up a `28BYJ-48 `_ stepper with control board, and a `HW-131 power module `_. + +With these I needed a new library so I stripped the c++ code down to its basics, just getting me a server with the index page for the first pass. + +On the Javascript side of things, I then decided I would add a temperature slider, from 90° to 120° (which writing this realize it should be from 95°... git commit...) with a confirmation button, and a small button to initialize down to 95°. + +The initialize button would need to trigger an initialization where I rotate counter clockwise an appropriate amount of time (Length TBD) in order to force the rotary encoder dial to always start at a known state of 95. +The green submit button sends the new desired temperature as a post. + +Server side, I was using a library called `AccelStepper `_. +This I set some made up max speeds and steps per rotation, actual values TBD. + +I added an endpoint called ``/setTemperature`` that takes in a temperature and sets a local temperature variable. +From there, I calculate the temperature less 95, to find out how many degrees I need to increase by, for now I'm considering this rotations. + +I then apply a multiplier (TBD also... there's a lot of these as you can see!) and call ``stepper.moveTo()`` and it actually feels like it's pretty accurate. + +The endpoint ``/initialize`` runs ``stepper.moveTo`` with ten rotations CCW, and then resets the "known location" back to zero (this also runs on power on for now). + + +.. figure:: {static}/images/2022/11/04_webpage.png + :alt: webpage controls, title "Water Heater Control", a blue slider with a green button saying "Set Temperature: 90", and red "Initialize to 90" button + :width: 920px + + +.. figure:: {static}/images/2022/11/04_stepper.png + :alt: blue case servo with a white arm, cables running off screen. sitting on a desk. + :width: 920px + +In Action +~~~~~~~~~ + +The result of this second round of coding is a lot more that I expect to happen once I can finally get down beneath his house. +Frank will lose power, his water heater will reset to 95°F, the NodeMCU will reboot, and reinitialize itself. +Frank will then open his browser to the NodeMCU's server, set the desired temperature, and take warm showers. + +Version 2 will come once I actually test EVERYTHING. +My first quesiton is if a rubber band on a lego tire with a servo wheel adaptor (`which I 3d modeled and printed... `_) will work sufficiently. +Programming wise, I need to figure out how many steps is one degree. Is the rotary encoder one degree per detent? Is it a constant speed? Is it like an alarm clock where you can sometimes jump by 10? + +Stay tuned to find out the exciting conclusion once I can go down below Frank's house. + +.. figure:: {static}/images/2022/11/04_stepper_wheel.png + :alt: blue case servo with a white arm, cables running off screen. sitting on a desk. + :width: 920px + +Code +~~~~ + +The code is currently at https://gitea.tyrel.dev/tyrel/frank_tank.git diff --git a/data/blog/posts/2022-11-04_office-meeting-sensor.rst b/data/blog/posts/2022-11-04_office-meeting-sensor.rst new file mode 100644 index 0000000..edd3caf --- /dev/null +++ b/data/blog/posts/2022-11-04_office-meeting-sensor.rst @@ -0,0 +1,105 @@ +Office Meeting Sensor +##################### +:author: tyrel +:category: Tech +:tags: python, nodered, home-assistant, automation +:status: published + +NOTES +===== + + +This post is ported over from my wiki, so the format isn't as storytelling as a blog post could be, but I wanted it here. + +Bill of Materials +================= + +* `Raspberry Pi Zero W H (WiFi + Headers) `_ +* `BlinkT LED Strip GPIO `_ + +Home Assistant Parts +==================== + +Third Party Plugin Requirements +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* `Node-RED `_ +* `HACS `_ +* `Mosquitto `_ + +Zoom Plugin +~~~~~~~~~~~ + +I followed the Read Me from https://github.com/raman325/ha-zoom-automation#installation-single-account-monitoring and set up a Zoom Plugin for my account, that will detect if I am in a meeting or not. + +Pi Zero +~~~~~~~ + +I have a tiny project Enclosure box that I dremeled a hole for the GPIO pins in the cover and I then sandwich the Blinkt onto the Pi Zero with another dremeled hole running to the micro usb power, and that's it for hardware. + +For software, I installed the python packages for Pimoroni and Blinkt, which came with a lovely set of sample projects. I deleted everything except the `mqtt.py `_ file, which I then put my Mosquitto server settings. + +I then added a new service in systemd to control the mqtt server + +.. code-block:: ini + + [Unit] + Description=Meeting Indicator + + [Service] + Type=simple + ExecStart=/usr/bin/python2 /home/pi/mqtt.py + WorkingDirectory=/home/pi/Pimoroni/blinkt/examples + Restart=always + RestartSec=2 + + [Install] + WantedBy=sysinit.target + + +Pleased with the results, and testing by sending some messages over mqtt that changed the color, I then dove into Node-RED + +Node-Red +~~~~~~~~ + +This is my first project using Node-RED, so I'm sure I could optimize better, but I have two entry points, one is from running HomeAssistant app on my mac, which gets me sensor data for my webcam, and the other is the aforementioned Zoom Presence plugin I created. These are ``Events:State`` nodes. + +When either of these are True, they call first my ceiling light to turn on, which next will then add a ``msg.payload`` of + +.. code:: + + rgb,0,255,0,0 + rgb,1,255,0,0 + rgb,2,255,0,0 + rgb,3,255,0,0 + rgb,4,255,0,0 + rgb,5,255,0,0 + rgb,6,255,0,0 + rgb,7,255,0,0 + +as one string. This leads to a Split, which will in turn, emit a new MQTT message for each line (I split on ``\n``) and turn on all 8 LEDs as red. This is inefficient because I am still using the sample code for the blinkt which requires you to address each LED individually, my next phase I will remove the pin requirement and just have it send a color for all of them at once, one line. + +When either of the sensors states are False, I then flow into a Time Range node, in which I check if it's between 9-5 or not. If it is, then I turn all the LEDs Green, and if it's outside 9-5 I just turn the LEDs off. I do not turn OFF the overhead light, in case it was already on. I don't care about the state enough. + +I also intentionally trigger at the Office Hours node, which will inherently turn the Green on at 9:01am, and off at 5:01pm. As well as turn on Red for any long standing meeting times I have. + +Images +~~~~~~ + +.. figure:: {static}/images/2022/11/04_nodered.png + :alt: Screenshot of Nodered, with the flow of control for turning on the lights. + +.. figure:: {static}/images/2022/11/04_lights.jpg + :alt: wall mounted enclosure with a strip of LED lights. + +Videos +~~~~~~ + +* https://i.imgur.com/kKIafiI.mp4 +* https://i.imgur.com/DLypDGD.mp4 + +Source +~~~~~~ + +Nodered configuration source json https://gist.github.com/tyrelsouza/c94329280848f0319d380cc750e995c2 + diff --git a/data/blog/posts/2022-11-11_coffee-gear.rst b/data/blog/posts/2022-11-11_coffee-gear.rst new file mode 100644 index 0000000..b9b989e --- /dev/null +++ b/data/blog/posts/2022-11-11_coffee-gear.rst @@ -0,0 +1,78 @@ +Coffee Gear +########### +:author: tyrel +:category: Coffee +:tags: coffee gear, coffee beans, coffee +:status: published + + +I put this up on my wiki a bit ago when a friend asked for coffee recommendations. Hopefully you can enjoy it and learn about some coffee machines you don't know yet. + + +************ +What I drink +************ + +Woke Living Coffee +================== + +My favorite coffee is from Pamela and Marcus at https://wokelivingcoffee.com/. They are a couple of local to me roasters in Wake Forest, NC who have connections to a farm in La Dalia, Nicaragua. Not only do they sell great coffee, they are extremely nice and we visit them any chance we get at our local `Black Farmers Market `_. + +**** +Gear +**** + +Grinders +======== + +I prefer burr grinders, there's documented evidence that they are better, I won't get into that here. + +Baratza +======= + +I use a `Baratza Virtuoso `_ that I picked up refurbished. It works great! For drip coffee I will grind at step 28, for aeropress I will grind at 20, and for french press I will set to 30. + +Hario +===== + +For travel I will bring my `Hario Skerton `_, works great, super easy to clean. I usually don't change the grind setting while traveling so I don't complain about the annoying screw post to set it. + + +**************** +Brewing Machines +**************** + +Technivorm +========== + +For my daily coffee, I have a `Moccamaster Technivorm `_. My friend Andrey recommended it. It works extremely well and very consistent pours. + +Chemex +====== + +When I'm feeling fancy - or I'm trying a new coffee - I will break out my `Chemex `_. I do a 1:16 ratio of beans to water. I use the `Brown Paper Chemex Filters `_. I appreciate the bleached papers, but prefer unbleached. + +Aeropress +========= + +For camping, I will bring my `Aeropress `_. It's plastic, lightweight, and to my experience it is indestructible for travel. + +French Press +============ + +When I roast my own coffee, I like to experience it in multiple brewing methods. I have a `Bodum Bean French Press `_ I got over a decade ago as a gift that has worked great. This one has an o-ring to seal the pouring spout, so the temperature chamber inside doesn't leak - a feature I like. + +**************************** +Roasting Machines & Software +**************************** + +FreshRoast SR700 +================ + +I have a **glorified popcorn maker** `SR700 `_ as a roaster. I'm not the biggest fan of it, the built in software is a mess, the manual buttons on it are a nightmare to use. It **works**, I can only get consistent coffee out of it if I use OpenRoast. It has a USB port so you can control it with software. + +OpenRoast +========= + +The `OpenRoast `_ software is okay, but I don't have a temperature probe on my SR700, so I can only see "what is set" for temperature, and not get an accurate reading if I were using something like Artisan. I could set up a PID server on an arduino and plug into the usb port, but I feel at that rate I'd rather just buy a new roaster that works with better software. I do like OpenRoast is in Python so I can read and write the code. + diff --git a/data/blog/posts/2022-12-06_notary-public.rst b/data/blog/posts/2022-12-06_notary-public.rst new file mode 100644 index 0000000..0c5c1d8 --- /dev/null +++ b/data/blog/posts/2022-12-06_notary-public.rst @@ -0,0 +1,14 @@ +Notary Public +############# +:author: tyrel +:category: Notary +:tags: notary +:status: published + +Short update today. + +I kept meaning to put together a way for people to utilize my notary services, so I finally made https://tyrel.bike/notary to redirect here to my Notary Public page. + +I am a North Carolina Notary Public, and love helping my neighbors use my services. + +If you're near me and need help - checkout `My Notary Page `_. diff --git a/data/blog/posts/2022-12-16_advent-of-code-2022.rst b/data/blog/posts/2022-12-16_advent-of-code-2022.rst new file mode 100644 index 0000000..dbdf61b --- /dev/null +++ b/data/blog/posts/2022-12-16_advent-of-code-2022.rst @@ -0,0 +1,16 @@ +Advent of Code 2022 + End of Year Updates +========================================= +:author: tyrel +:category: Blog +:tags: python, adventofcode, 6502, assembly, rust, go +:status: published + +Advent of Code this year is kicking my butt so I haven't been doing any tech blogging really lately. If you want to follow my progress, I think I might be done as of day 15 - This one seems to be a traveling salesman/knapsack problem related. Here's my repo: `https://gitea.tyrel.dev/tyrel/advent-of-code/src/branch/main/2022/python `_. + +I'm not on the computer that runs it, but I've been spending a lot of time playing with Apple's System7 in the BasiliskII emulator. Might have some fun projects with that coming up, but wanted to do some more learning before I start anything. So I have been going through a course on 6052 Assembly programming for the NES, and I'm about 73% done with that, it's really great! + +It's By Gustavo Pezzi at `Pikuma `_, if "oldschool" programming floats your boat then I definitely recommend it. It's all programming through making roms with CC65/CA65 assembler, and using FCEUX to see your results, super neat. + +I've been picking up some more Go work at work. My current team is sort of disbanding so I'm going to be moving away from doing just Python. It's been a year since I've done Go stuff, since I left Tidelift, so I'm really rusty. + +Speaking of Rust, I was trying to do Advent of code in Rust also, and made it TWO whole days in Rust. It's still on my bucket of stuff to learn, but my free time seems to be running out lately, and I have a lot of things on my plate to get done. diff --git a/data/blog/posts/2023-01-10_dotfiles-the-2022-way.rst b/data/blog/posts/2023-01-10_dotfiles-the-2022-way.rst new file mode 100644 index 0000000..c4364db --- /dev/null +++ b/data/blog/posts/2023-01-10_dotfiles-the-2022-way.rst @@ -0,0 +1,57 @@ +Dotfiles - My 2022 Way +###################### +:author: tyrel +:category: Tech +:tags: dotfiles, macos, linux, nix, ubuntu, +:status: published + +New Year's eve eve, my main portable computer crashed. Rebooting to Safe mode, I could mount this MacBook's hard drive long enough to SCP the files over the network to my server, but I had to start that over twice because it fell asleep. I don't have access to rsync in the "Network Recovery Mode" it seems - maybe I should look to see if next time I can install things, it's moot now. + +I spent all January 1st evening working on learning how Nix works. Of course, I started with Nix on macOS (intel at least) so I had to also learn how nix-darwin works. I have my `dotfiles `_ set up to use Nix now, rather than an `INSTALL.sh` file that just sets a bunch of symlinks. + +I played around for a litle bit with different structures, but what I ended up with by the end of the weekend was two bash scripts (still working on makefile, env vars are being funky) one for each operating system `rebuild-macos.sh` and `rebuild-ubuntu.sh`. For now I'm only Nixifying one macOS system and two Ubuntu boxes. Avoiding it on my work m1 Mac laptop, as I don't want to have to deal with managing `synthetic.conf` and mount points on a work managed computer. No idea how JAMF and Nix will fight. + +My filetree currently looks like (trimmed out a host and a bunch of files in `home/`) + +.. code-block:: none + + . + ├── home + │   ├── bin/ + │   ├── config/ + │   ├── gitconfig + │   ├── gitignore + │   ├── gpg/ + │   ├── hushlogin + │   └── ssh/ + ├── hosts/ + │   ├── _common/ + │   │   ├── fonts.nix + │   │   ├── home.nix + │   │   ├── programs.nix + │   │   └── xdg.nix + │   ├── ts-tl-mbp/ + │   │   ├── brew.nix + │   │   ├── default.nix + │   │   ├── flake.lock + │   │   ├── flake.nix + │   │   ├── home-manager.nix + │   │   └── home.nix + │   └── x1carbon-ubuntu/ + │   ├── default.nix + │   ├── flake.lock + │   ├── flake.nix + │   ├── home-manager.nix + │   └── home.nix + ├── rebuild-macos.sh + └── rebuild-ubuntu.sh + +Under `hosts/` as you can see, I have a `brew.nix `_ file in my macbook pro's folder. This is how I install anything in homebrew. In my `flake.nix` for my macos folder I am using `home-manager`, `nix-darwin`, and `nixpkgs`. I provide this `brew.nix` to my `darwinConfigurations` and it will install anything I put in my `brew` nixfile. + +I also have a `_common` directory in my `hosts`, this is things that are to be installed on EVERY machine. Things such as `bat`, `wget`, `fzf`, `fish`, etc. along with common symlinks and xdg-config links. My nvim and fish configs are installed and managed this way. Rather than need to maintain a neovim config for every different system, in the nix way, I can just manage it all in `_common/programs.nix`. + +This is not "The Standard Way" to organize things, if you want more inspiration, I took a lot from my friend `Andrey's Nixfiles `_. I was also chatting with him a bunch during this, so I was able to get three systems up and configured in a few days. After the first ubuntu box was configured, it was super easy to manage my others. + +My `home/` directory is where I store my config files. My ssh public keys, my gpg public keys, my `~/.` and my `~/.config/`. This doesn't really need any explaination, but as an added benefit is I also decided to LUA-ify my nvim configs the same weekend. But that's a story for another time. + +I am at this time choosing not to do NixOS - and relying on Ubuntu for managing my OS. I peeked into Andrey's files, and I really don't want to have to manage a full system configuration, drivers, etc. with Nix. Maybe for the future - when my Lenovo X1 Carbon dies and I need to reinstall that though. diff --git a/data/blog/posts/2023-01-17_turboc2-setting-header-and-include-locations.rst b/data/blog/posts/2023-01-17_turboc2-setting-header-and-include-locations.rst new file mode 100644 index 0000000..14e224a --- /dev/null +++ b/data/blog/posts/2023-01-17_turboc2-setting-header-and-include-locations.rst @@ -0,0 +1,58 @@ +TurboC2 Setting Header and Include Locations +############################################ +:author: tyrel +:category: DOS +:tags: DOS, DOSBox, TurboC2, C +:status: published + +This weekend I purchased a book from this seller on Craigslist - `"Advanced MS-DOS Programming: The Microsoft Guide for Assembly Language and C Programmers" `_ and before opening it, I wanted to get a C environment running. + +I found a copy of `TurboC2 on Archive.org `_ and tossed that into my DOS Box install. I wrote a "Hello world" and pressed compile and it couldn't include "stdio.h", what the heck? + +It seems that the Archive.org copy of Turbo C 2 ships with configuration that sets where the Includes and Lib directories to ``C:\TC``. I keep all my programs in ``C:\PROGS`` so of course it can't find any header files for me! + +To fix this you can either move your TurboC install to ``C:\TC``, which feels wrong to me, or you could configure it in the options properly. + + + +Steps +~~~~~ + +* Go to the Directories entry in the Options Menu. +* You can see the default provided configuration directories +* Fill out your appropriate directories for all three of the options. +* Make sure all three are configured properly. +* Then you can save your config, so you only have to do this once. + + +The Screenshot Way +~~~~~~~~~~~~~~~~~~ + +.. figure:: {static}/images/2023/01/dosbox_1_environment_menu.png + :alt: TurboC with the Options Menu selected and the Directories entry highlighted. + + Go to the Directories entry in the Options Menu. + +.. figure:: {static}/images/2023/01/dosbox_2_directories.png + :alt: TurboC with a pop up, showing entries of Include Directories, Library Directories, and Turbo C Directory configured to C:\TC\INCLUDE C:\TC\LIB and C:\TC + + You can see the default provided configuration directories + +.. figure:: {static}/images/2023/01/dosbox_3_directories_edit.png + :alt: TurboC with a pop up, showing C:\PROGS\TC\INCLUDE + + Fill out your appropriate directories for all three of the options. + +.. figure:: {static}/images/2023/01/dosbox_4_directories_filled.png + :alt: TurboC with a pop up, showing entries of Include Directories, Library Directories, and Turbo C Directory configured to C:\PROGS\TC\INCLUDE C:\PROGS\TC\LIB and C:\PROGS\TC + + Make sure all three are configured properly. + +.. figure:: {static}/images/2023/01/dosbox_5_save_config.png + :alt: TurboC with a popup showing a Save Options location of C:\PROGS\TC\TCCONFIG.TC + + Then you can save your config, so you only have to do this once. + +Unfortunately - this file is a binary file. You can't just edit it in a text editor and carry on, so this is the only way I know how to change these locations. + +Hopefully this helps anyone else who runs into any include errors with Borland Turbo C 2! diff --git a/data/blog/posts/2023-01-31_6502-nes-course-by-pikuma.rst b/data/blog/posts/2023-01-31_6502-nes-course-by-pikuma.rst new file mode 100644 index 0000000..f8613c5 --- /dev/null +++ b/data/blog/posts/2023-01-31_6502-nes-course-by-pikuma.rst @@ -0,0 +1,48 @@ +6502 NES Course by Pikuma +######################### +:author: tyrel +:category: Tech +:tags: 6502, assembly, NES +:status: published + +As I mentioned in my `December `_ post I'm doing a 6502 course on `Pikuma. `_ + +I'm about 75% of the way done, and I think I need to circle back to some earlier stuff about how the PPU works, but it's super fun. + +Over the holidays I was able to stop at my father's and pick up my old NES. +I swapped out the ZIF connector for a new one, and cleaned up some contacts on the RCA ports, and it works great! +Once I found out that it was working - I played Sesame Street ABC 123, as that's the only one I had up in my office - I ordered an EverDrive N8. +That came last week. + +The pictures are tall due to how I took them, so sorry I'll attach them at the end of the post. + +Once I got the `EverDrive N8 `_ I made sure it worked by playing a Battletoads ROM. +Battletoad tested - I then copied Atlantico.NES to my Everdrive. +Atlantico is the game that Gustavo is walking us through making in the current part of the course - not a real published game. +I loaded it up and HOLY COW - something I actually wrote in Assembly is running on real hardware. + +If you want to watch the video, it's very simplistic at the 75% mark, this was before the Collisions chapter, and no sound yet. + +The feeling of getting something running, locally, and seeing it working on screen, despite being a programmer for ~~20 years, is AMAZING. +Writing code that executes on the system you grew up playing the early 90's, wow. + +I do wish the CRT TV my wife had was square, things get cut off on it. +I even got a remote, so I could try to fix that in the menu, alas, only picture option is brightness. +(Not that I realistically thought I could scale it, CRT Pixels are only Pixels. + +---- + +Picture Gallery +=============== + +Trying out putting all the pictures at the end of my posts, if they are not directly related to paragraph content. + +.. figure:: {static}/images/2023/01/NES_Console.png + :alt: An NES Console with the flap opened up. On top is a Zapper gun with another controller, both with cables neatly wrapped up. On the floor, plugged in, in front of the NES, is another controller. The Power LED is on. + + My NES plugged in and running. + +.. figure:: {static}/images/2023/01/NES_Atlantico.png + :alt: A CRT TV screen with a game running on it. In 8bit graphics, the game is a ship sailing to the right, with planes flying to the left. Some missiles are shooting up. + + Atlantico game. There may be some interference with scanlines causing moire patterns, sorry. diff --git a/data/blog/posts/2023-03-14_i-have-been-hit-by-malware.rst b/data/blog/posts/2023-03-14_i-have-been-hit-by-malware.rst new file mode 100644 index 0000000..d90b5dd --- /dev/null +++ b/data/blog/posts/2023-03-14_i-have-been-hit-by-malware.rst @@ -0,0 +1,22 @@ +I have been hit by Malware. +########################### +:author: tyrel +:category: Website +:tags: malware +:status: published + +This morning I woke up to an email from DigitalOcean saying they have scanned my host and on port 8080 was botnet. + + "We are writing to let you know that your Droplet tyrelsouza.com at 138.197.14.67 is a Command & Control server part of a botnet." + +UGH. This is not what I wanted to have to deal with today. + +My first steps were to shut down all php things (the issue is with heysrv.php in EVERY directory). Then I ran ``find / -name heysrv.php -delete`` to delete all the files. After this, I decommissioned my pixelfed instance (rip pix.tyrel.dev) and disabled the startup scripts for that. + +I then installed Simply Static on my one `remaining wordpress `_ and turned that into a static collecton of html and related files. This elimiated two php instances. With one more remaining - my Mediawiki server. + +I found an Export Pages link and now have an XML file of all my pages (only 78 or so) and can start working on putting this back to html notes on my joplin tool, instead of my wiki. Before I shut it down for good, I need to extract all the images, that's the only thing that's left to keep this knowledge secure. + +Now the only thing left on this server is this static blog, pushed up from pelican. Everything else on this machine is just ``index.php`` files that redirect around (example `https://tyrel.bike/ `_ to my Strava) + +It's a bit sad I had to do this today, when I have other things I want to deal with - but DigitalOcean gave me a 24 hour ultimatum. I'll rebuild this server later, but for now, blog on! diff --git a/data/blog/posts/2023-03-28_brand-new-server.rst b/data/blog/posts/2023-03-28_brand-new-server.rst new file mode 100644 index 0000000..2383e70 --- /dev/null +++ b/data/blog/posts/2023-03-28_brand-new-server.rst @@ -0,0 +1,13 @@ +Brand New Server +################ +:author: tyrel +:category: Blog +:tags: blog +:status: published + +Per my last post, I did not succeed in cleaning off the malware. + +That machine is dead and I am now running on a $4/mo Digital Ocean droplet - much less power than before, but I don't really need it anymore now that I have my own server at home. + +I am sad I don't have a Pixelfed anymore, maybe I'll relaunch it some day. + diff --git a/data/blog/posts/2023-04-04_now-page.rst b/data/blog/posts/2023-04-04_now-page.rst new file mode 100644 index 0000000..d7209f1 --- /dev/null +++ b/data/blog/posts/2023-04-04_now-page.rst @@ -0,0 +1,13 @@ +Now Page +######## +:author: tyrel +:category: blog +:tags: blog +:status: published + +My friend Nik showed me his ``/now/`` page, and I find that a cool idea. +This blog is more of a technical braindump than a log of my personal life, so I don't really talk about my life much. + +I assume no one will visit `https://tyrel.dev/now `_ but if you do, you will see a handcrafted update of my life and what has happend with me lately. + +Thanks to Derek Sivers for setting up a network of ``/now/`` pages! `https://nownownow.com/ `_. diff --git a/data/blog/posts/2023-05-26_emulation.rst b/data/blog/posts/2023-05-26_emulation.rst new file mode 100644 index 0000000..2eb14b8 --- /dev/null +++ b/data/blog/posts/2023-05-26_emulation.rst @@ -0,0 +1,22 @@ +Emulation +######### +:author: tyrel +:category: Tech +:tags: emulation +:status: published + +I haven't had much time lately for blog posts, I've been dealing with bed time routines with my newborn, and once those are done, I get a few hours of alone time for computer things. + +Lately I've been toying around with Amiga OS, FreeDOS, Windows 95, and Apple IIe things. + +I got a raspberry pi and installed Pimiga, got a fun set up and that was neat. I then installed Amiberry on my macbook, with some remote hard drive images on my samba share, and I have a consistent setup for Amiga on any machine in the house or on tailscale. + +I then decided to install FreeDOS to a barely used Dell Vostro 1720 and install to that. It works great, I have WordStar, TurboC, and more installed and it's fun to get back to my roots in that way. + +After that I decided to install Windows 95 with 86Box, did the same with remote hard disk images, and got that running. Been toying around in Visual Basic 6, Oh the memories!! I installed that so I could play Lego City, but having voodoo graphics errors I need to figure out before I can play. + +I also ordered an Apple IIe emulator machine that runs on an Esp8266 from `CT6502 `_ and it works great. So cool just tossing a disk image on the MicroSD card and loading it up. The downside to this is I can't figure out how to swap disks in realtime, so I can't play Ultima, or any multi disk games. I can however load .hdv files so if something comes with a hard disk image. + +Not really much for a tech post, and nothing to share codewise, but thought I'd break some radio silence. I also imported my flying blog here, so I added the Flying category/tags. + + diff --git a/data/blog/posts/2023-05-26_neovim-nix-telescope-mason.rst b/data/blog/posts/2023-05-26_neovim-nix-telescope-mason.rst new file mode 100644 index 0000000..1039fae --- /dev/null +++ b/data/blog/posts/2023-05-26_neovim-nix-telescope-mason.rst @@ -0,0 +1,30 @@ +Neovim, Nix, Telescope, Tree-sitter, Mason +########################################## +:author: tyrel +:category: Tech +:tags: nix, nvim, rust +:status: published + +I made a mistake with not reading CHANGELOGs for all my packages in Neovim this week. This sent me down a small rabbit hole trying to fix all the things. + +What happened is I ran ``:PackerUpdate`` which, pulls the latest version of Packer packages, good, updates! But... Telescope has a new requirement on main branch that requires Neovim 0.9.0. The problem is that the latest NixPkgs for Neovim right now is 0.8.1. I ran to google, tried to set an overlay to use ``neovim-nightly``, but that didn't work. If you recall in `Dotfiles - My 2022 Way `_ I'm not actually using NixOS so (please correct me if I'm wrong) overlays don't work. I tried specifing a version in my ``programs.nix``, I tried a bunch of other things at 1AM that I don't remember anymore. + +Almost ripped it all out just to use Nvim 0.9.0 on this machine until NixPkgs has updated the repo. I decided that was the wrong idea, and went to sleep. + +Tonight, I was able to figure out that in Packer, you can pin a commit! + +It's clear in the docs, but I was trying to fix it at the Nix level, so I didn't immediately think of this, even though at my last job, Tidelift, I was doing package pinning analysis! Derp. + +So, I added ``commit="c1a2af0"`` to my ``use`` statment in ``plugins.lua`` and Telescope started working again without a warning, or issue. `Commit `_. + +That wasn't the only problem though. In my infinite wisdom, I followed some reddit posts that I won't link to, that suggested deleting ``~/.local/share/nvim`` and rerunning ``PackerInstall``, the problem there -- my tree-sitter configs are in my nix files. + +This is an issue I need to look at later, but in my `programs.nix `_ file, I some reason have two entries of ``plugins =``. I had to uncomment the first one where I inject tree-sitter, and comment out the second setting. Then rebuild my nix flakes. + +After that,I had to comment the first, uncomment the second, and rebuild with ``withAllGrammars`` config. + +This worked, I had my rust tree-sitter configs working, but was missing ``rust-analyzer``. + +That's in Mason! So I ran ``:Mason``, found ``rust-analyzer`` slapped that ``i`` button, and I finally had my system back after 2 days of issues. + +This was mostly a blogpost so I can reference back to it in the future, but hopefully at least _someone_ learns to pin your dang nvim Packages! diff --git a/data/blog/posts/2023-05-26_set-environment-variables-with-lastpass.rst b/data/blog/posts/2023-05-26_set-environment-variables-with-lastpass.rst new file mode 100644 index 0000000..9c33775 --- /dev/null +++ b/data/blog/posts/2023-05-26_set-environment-variables-with-lastpass.rst @@ -0,0 +1,35 @@ +Set Environment Variables with LastPass +####################################### +:author: tyrel +:category: Tech +:tags: bash, automation, work +:status: published + +I have to use LastPass at work, and I store some API keys in there. Rather than copy/paste and have the actual api key on my terminal, I like to use ``read -rs ENV_VAR_NAME`` to set environment variables, so they are hidden from scrollback. + +Recently my coworker set something up that we need an environment variable set up for running some Terraform commands. I don't feel like pasting it in every time from LastPass, so I figured out how to set this up and automate it. I'm sure I've already talked a lot about how I love ``direnv`` and I maintain a lot of different ``.envrc`` files for work things. For my last team I had one per repo! Well ``direnv`` comes to the rescue again. + +* The first step is installing the `lastpass-cli `_. +* Then you need to set it up so you log in, how you do that is up to you. I have lpass checking status, and if it exits nonzero, then running lpass login again in my direnv. +* After that you can use ``lpass show`` and capture that in a variable to export your API key as an environment variable. + + +.. code-block:: bash + + lpass status + if [ $? -ne 0 ]; then + lpass login email@address.com + fi + export API_KEY=$(lpass show "Secret-Name-Here" --password) + +Example ``.envrc`` file. + + +I love automating things, and when a coworker says "oh no we have to do this"... I run to automate it! + + +Resources +~~~~~~~~~ + +* LastPass CLI https://github.com/lastpass/lastpass-cli +* Direnv https://github.com/direnv/direnv diff --git a/data/blog/posts/2023-06-07_pfsense.rst b/data/blog/posts/2023-06-07_pfsense.rst new file mode 100644 index 0000000..1bf2db7 --- /dev/null +++ b/data/blog/posts/2023-06-07_pfsense.rst @@ -0,0 +1,28 @@ +pfSense +####### +:author: tyrel +:category: Tech +:tags: networking +:status: published + +This week I finally got a machine that is solely to run pfSense. +I didn't want to spend _too_ much money so I bought a $200.00 Qotom Firewall Q330G4. +This was great and easy to set up. + +First I bought a Netgear WAC104 and installed OpenWRT on it. Simple enough. +Then I put that into bridge mode, so it's just an Access Point and not a "smart" router too. + +Then I put my Linksys EA9300 into bridge mode and behind the pfSense machine (into a switch) and couldn't access any of my server's sites. + +After futzing with that for a couple days, I finally figured out the problem. +I thought I was behind a double NAT, but I wasn't. When I moved my EA9300 from my sole WiFi router, to behind the pfSense machine, I neglected to change some settings on my AT&T modem. + +You see — dear reader— when I set up this network on my AT&T Modem, I had to enable Passthrough mode. +This, was set to a MAC Address, not an IP Address. +So when I was making sure to keep my IP network on the same 192.168.1.1/24, I thought that was all I needed. + +Alas, there's a dropdown to pick the MAC address of the machine that everything passes through. +I can now access my bookmarks, notes, ebooks, and plex server! + + +Thanks to my friend Daniel (@sanitybit) - who was a great rubber duck and gave me some pointers when I was debugging, and also helped me find the hardware for the pfSense box! diff --git a/data/blog/posts/2023-06-08_netgear-wac104.rst b/data/blog/posts/2023-06-08_netgear-wac104.rst new file mode 100644 index 0000000..7019a0b --- /dev/null +++ b/data/blog/posts/2023-06-08_netgear-wac104.rst @@ -0,0 +1,18 @@ +Netgear WAC104 +############## +:author: tyrel +:category: Tech +:tags: networking +:status: published + +I recently bought four Netgear WAC104 devices, and am flashing OpenWRT onto them. +I have struggled a lot to get the firmware on, due to the not great interface they provide. + +The issue is, that it prompts you to change the password, but then when you change it on the page you land on, nothing connects anymore and you can't access the router. + +The solution is to click "Set Password" in the Administration menu on the left, and set it there. +Even though there is a prompt to set the password on every page, that will change other settings too and break things. + +The router isn't great, and the software is awful so thats why I'm installing OpenWRT anyway. + +Two down, two more to go! diff --git a/data/blog/posts/2023-06-16_laid-off.rst b/data/blog/posts/2023-06-16_laid-off.rst new file mode 100644 index 0000000..558ed5c --- /dev/null +++ b/data/blog/posts/2023-06-16_laid-off.rst @@ -0,0 +1,20 @@ +Laid Off - 2023 Edition! +######################## +:author: tyrel +:category: Personal +:tags: work +:status: published + +"Hey Tyrel, I put a meeting on your calendar, let me know if you can make it." The last words you want to hear from your manager. + +Well it happened again, and I got caught in some layoffs from work and am on the job hunt again. I did want to be able to spend more time with Astrid, but not like this! + +After the call with HR and them all explaining what was happening, and panic texting my wife and some friends. I emailed the recruiters I've been working with for a few weeks back, and all day today I've been appling to a lot of places. + +There are a LOT of jobs out there I'm not interested in, a lot of Ruby on Rails jobs, crypto companies, etc. But I am finding a LOT of Python or Go jobs I'm applying to. I'd love to get a job doing rust and firmware work, but that's unlikely as I want to stay remote, and I have very minimal Rust experience. + +What worries me is finding health insurance, because America ties it to work... my wife is unemployed and now we have to cancel a few appointments for us the next few weeks. Still going to keep Astrids 4mo vaccinations though. Those I'll be okay paying out of pocket for. + +I just ended the day applying to twelve jobs, hopefully one pans out! + +If anyone needs any Python consultation, let me know too! Thanks! diff --git a/data/blog/posts/2023-06-19_i-am-now-matrix-compatible.rst b/data/blog/posts/2023-06-19_i-am-now-matrix-compatible.rst new file mode 100644 index 0000000..a9ab5f8 --- /dev/null +++ b/data/blog/posts/2023-06-19_i-am-now-matrix-compatible.rst @@ -0,0 +1,10 @@ +I am now Matrix Compatible +########################## +:author: tyrel +:category: Tech +:tags: matrix +:status: published + +You can now message me on Matrix using `@tyrel:tyrel.dev `_. + +I'm running Synapse. diff --git a/data/blog/posts/2023-08-23_general-job-search-update.rst b/data/blog/posts/2023-08-23_general-job-search-update.rst new file mode 100644 index 0000000..25a761b --- /dev/null +++ b/data/blog/posts/2023-08-23_general-job-search-update.rst @@ -0,0 +1,23 @@ +General Job Search Update +######################### +:author: tyrel +:category: Personal +:tags: work +:status: published + +As mentioned in a previous post, I'm on the job hunt again. +I got laid off in June with a 30% Reduction In Force. + +I've been searching primarily for Python and Go roles, but I'm not having a lot of luck right now - seems everyone else also got laid off is who I am competing against. +`(That said, go hire my friend Nik! He's fantastic!) `_ +I've had a lot of job opportunity people ghost me. +Gotten through a few late rounds, only to never hear from the company again. +Even if I have emailed them a thank you letter for the interviews, to express my interest. + +I've been around professionally for thirteen years. +Over those years, I have picked up mostly back end skills. +I have eight solid years of Django experience. +Four years of Ruby on Rails experience. +A couple years of Flask, FastAPI, and other smaller Python Frameworks mixed in. + +I'm looking for an IC role, where I can move into a Tech Lead role. I want to eventually some day be a Staff/Principal role, but I don't have that on paper to show I can do it, so trying to get in somewhere new with an IC role. diff --git a/data/blog/posts/2023-08-24_my-life-story.rst b/data/blog/posts/2023-08-24_my-life-story.rst new file mode 100644 index 0000000..78842fc --- /dev/null +++ b/data/blog/posts/2023-08-24_my-life-story.rst @@ -0,0 +1,128 @@ +My Life Story +############# +:author: tyrel +:category: Personal +:tags: life +:status: published + +Trying to write a more prose version of my Resume. +Kind of a living post about my life in the Software Engineering World. + +Early Life +---------- + +I started programming with Visual Basic in the 90s on a laptop in my father's car. +Parents had just divorced and the drive to his new place every other weekend was two hours, so I had a lot of downtime going through the Visual Basic 5 and 6 books we had. +After that, I started trying to program chat bots for Starcraft Battle.net, and irc bots. +In highschool I started taking more Visual Basic courses, mostly because that's all that my teachers had for classes. +Senior year I took some Early Education courses at the local college, and then went to that college (Keene State) for my Computer Science Degree. +At Keene State, I took a lot of Java classes, that was the core curriculum. +I also took some more Visual Basic courses, some C++ and web design courses as well. + +After college, I thought I'd be working Java again, but I got a referral from one of my favorite professors to this company called Appropriate Solutions Inc, and started working in Python/Django. +I had never touched Python outside of OpenRPG - and even then it was just installing the interpretor so I could run the game. + +Appropriate Solutions Inc +------------------------- + +At Appropriate Solutions I worked on maybe ten different projects. +The first thing I worked on was an Hour Tracker to learn how Django works, it worked great, but definitley didn't look too flashy. +From there, I went on to work on a Real Estate Website, a Bus tracking/mapping system for a school district, a Pinterest Clone, and some more GIS mapping things. +One of our projects was with HubSpot, and I went to one of their hackathons, and a couple conferences in Boston. +I had a lot of friends in Boston, so I decided to move there mid 2012. + +Propel Marketing +---------------- + +I got my first Boston job in Quincy, MA - working at a startup called Propel Marketing. +This was also a Python/Django role, but had more front end work. +While there I worked on their internal CMS tooling for selling white labeld websites to clients. +I worked on a lot of internal tooling, one that would pull leads from our system, and then upload to a lot of different vendor tooling. +A couple of Python PIL tools that would generate facebook and twitter banners, but a lot of the work there was learning and writing tests in Python. + +Akamai +------ + +From there, I started a six month contract at Akamai, working for their Tech Marketing team on a couple tools. +This later got extended another three months, until the team ran out of budget for contractors. +I worked on their `"Spinning Globe" `_ which was really fun. +Some internal dashboards, and a couple email tools that worked with SalesForce. + +Addgene +------- + +In 2015 I then landed a spot at Addgene - a nonprofit biotech! +This is where my career really started taking off. +I started to lead projects, do more valuable research and go to conferences. +The company itself was - for my tenure there - two Django Projects and some jQuery/Bootstrap. +The "core" site was the ecommerce and research site. +Buying, selling, research on Plasmids and Viruses. +The back end was the inventory management system. + +While there, I also lead the charge on a couple projects. +We were migrating to AWS - from an in house rackmount server, so we needed to get a lot of data on S3. +Testing at Addgene was fickle, as everything was stored in tsv files and reloaded in memory. +I developed a python package that would save thousands of dollars of S3 costs, while still making the file upload process in testing seamless. +`Django DBFileStorage `_ was born. + +Another charge I lead was helping Celigo alpha test Integrator.IO - working on building an integration of a lot of our sales data into netsuite/salesforce (I forget which one) by working with the Celigo API. +This was a fun project - as when I was done with this, we got to archive an old Java repo that was barely hanging on, and had no bugfixes in years. + +While at Addgene, I also started the "Teaching Scientists How To Program" lunch and learn club. +We would have meetings where anyone from the Scientist team could come, ask questions about Python, and work through any of the problems they were having with our Jupyter Notebooks we set up that they could run. +This was great, I helped foster some friendships that I believe will last a lifetime, helped people transition into actual programmers, and helped the company save a lot of time by helping more people learn. + +Tidelift +-------- + +After Addgene, in 2018 I joined an early stage startup called Tidelift. +I was one of the first engineers there, so I got to help lead a lot of early shaping of the company. +I started with working on a lot of the `Lifter `_ focused side of the site. +Helping create tasks the lifters could complete so they could get paid. +From there working on the Subscriber side of things where the paying clients would get information about their dependencies. +I have an upcoming blog post about some work there, so I won't go into too much details. +I did help start the Tidelift CLI though. +A tool written in Go, it was a CI/CD tool to analyze software dependencies for security/licensing problems + + +EverQuote +--------- +After Tidelift, I started at EverQuote in 2022. +My longtime friend Sam was a Director leading a team that was working on replatforming a monolith and needed to backfill a python role. +I had been asking him for years when he would work on Python stuff, as he had only been working at Ruby companies for the past few years, so this caught my ear and I started there. + +The first project was replatforming a Python 2.7 monolith - with code from as far back as 2010 - to micro services. +These were mainly FastAPI services, that communicted amongst eachother with kafka. +Some of them would read from the database, and send events into the kafka stream. +Some would read from the stream, and write to the database. +Others would read from database or kafka, and then communicate with a third party system. + +All of these had Grafana (and later NewRelic) metrics attached. +Every server had at least one dashboard, with many graphs, event logs and charts. +These were all deployed using kubernetes, terraform, AWS. +I can't speak to the specifics about past there - as there was another dedicated ops team. + +Some other projects I worked on there were really fun. +One of the analysts used to maintain her daily workflow in a google doc, and I helped lead a project that took that apart and worked on it programatically. +This was then turned into a five part rebalancing, reweighting, and email route manipulating script - that ran daily using Cron, and saved that team over fourteen hours a week. + +The Remarketing team came to an end, and there was a small re-org and we merged with another team and became the Consumer Engagement team. +This dealt with Calls, SMS, Email, and working with the Sales Reps. + +We started a project using Go and React that would pull users from the database and show a script for the Sales rep to read to the client on the phone, with specific information about what plans were available. +Other projects on that team, which is what I spent most of my time on, was porting CI/CD processes from Atlassian Bamboo, to GitHub Actions. + +During this time, I took a couple months of paternity leave, and a few weeks after I came back there was a major reduction in force and I was laid off. + + +After EverQuote +--------------- + +Since leaving EQ, I have been on the job hunt. +If you know anything about tech in 2023, a LOT of people are job hunting right now. +I'm excited that this happened at a time in my daughter's life where I can spend SO MUCH more time with her than some fathers can. +That's the good side of things. +I hope I get a job soon though, as the sole earner in my family. + +If you've made it this far, please check out my Resume in the sidebar, and contact me if you have anything you think would be a fit. +Who knows, I might delete this last section once I get a job! diff --git a/data/blog/posts/2023-09-26_which-which-is-which.rst b/data/blog/posts/2023-09-26_which-which-is-which.rst new file mode 100644 index 0000000..aa09e6f --- /dev/null +++ b/data/blog/posts/2023-09-26_which-which-is-which.rst @@ -0,0 +1,40 @@ +Which which is which? +##################### +:author: tyrel +:category: Tech +:tags: linux, macos, zsh +:status: published + +I had a bit of a "Tyrel you know nothing" moment today with some commandline tooling. + +I have been an avid user of ZSH for a decade now, but recently I tried to swap to fish shell. +Along the years, I've maintained a lot of different iterations of `dotfiles `_, and shell aliases/functions. +I was talking to a `friend `_ [citation needed] about updating from ``exa`` to ``eza`` and then noticed I didn't have my aliases loaded, so I was still using ``ls`` directly, as I have ``alias ls="exa -lhFgxUm --git --time-style long-iso --group-directories-first"`` in my ``.shell_aliases`` file. + +I did this by showing the following output: + +.. code-block:: shell + + $ which ls + /usr/bin/ls + +Because I expected it to show me which alias was being pointed to by ``ls``. + +My friend pointed out that "Which doesn't show aliases, it only points to files" to which I replied along the lines of "What? No way, I've used ``which`` to show me aliases and functions loads of times." + +And promptly sent a screenshot of my system NOT showing that for other aliases I have set up. Things then got conversational and me being confused, to the point of me questioning if "Had I ever successfully done that? Maybe my macbook is set up differrently" and went and grabbed that. + +Friend then looked at the man page for which, and noticed that there's the ``--read-alias`` and ``--read-functions`` flags on ``which``, and I didn't have those set. +I then swapped over to bash "Maybe it's a bash thing only? I'm using Fish". + +Nope, still nothing! Then went to google, and it turns out that ZSH is what has this setup by default. +Thank you `"Althorion" `_ from Stackoverflow for settling my "Yes you've done this before" confusion. + +It turns out that ZSH's ``which`` is equivalent to the ZSH shell built-in ``whence -c`` which shows aliases and functions. + +After running ``/usr/bin/zsh`` and sourcing my aliases (I don't have a zshrc file anymore, I need to set that back up), I was able to settle my fears and prove to myself that I wasn't making things up. There is a which which shows you which aliases you have set up, which is default for ZSH. + +.. code-block:: shell + + $ which ls + ls: aliased to exa -lhFgxUm --git --time-style long-iso --group-directories-first diff --git a/data/blog/posts/2023-10-03_rotate-a-matrix-in-python.rst b/data/blog/posts/2023-10-03_rotate-a-matrix-in-python.rst new file mode 100644 index 0000000..0c5ae39 --- /dev/null +++ b/data/blog/posts/2023-10-03_rotate-a-matrix-in-python.rst @@ -0,0 +1,93 @@ +Rotate a Matrix in Python +######################### +:author: tyrel +:category: Tech +:tags: python +:status: published + +I've been doing Advent of Code for a few years now, and every year I do it in my favorite language, Python. +One thing that comes up a lot, is rotating matrices. + +One way to do this, is to use Numpy, using ``np.rot90(mat)``, but not everyone wants to install Numpy just to do one small task. +I know I don't always. + +The way I always do it, that will support non-square matrixes, is to use zip. + +.. code-block:: python + + >>> matrix = [ + [1,2,3], + [4,5,6], + [7,8,9] + ] + >>> rotated = list(zip(*matrix[::-1])) + # And result is + [[7, 4, 1], + [8, 5, 2], + [9, 6, 3]] + +We can break this down bit by bit. + +This will copy the list, with a -1 step, resulting in a reverse order + +.. code-block:: python + + >>> matrix[::-1] + [[7,8,9], + [4,5,6], + [1,2,3]] + +Next we need to call zip in order to get the x-th item from each inner list, but first, we need to unpack it. If you'll notice, the unpacked version isn't wrapped with another list, which is what zip needs from us. + +.. code-block:: python + + # Too many lists + >>> print(matrix[::-1]) + [[7, 8, 9], [4, 5, 6], [1, 2, 3]] + + # Just right + >>> print(*matrix[::-1]) + [7, 8, 9] [4, 5, 6] [1, 2, 3] + +From there, we can pass this unpacked list of - in our case - three lists, to zip (and in Python 3 this returns a generator, so we need to call list again on it, or just use it) + +.. code-block:: python + + >>> # Again, we get the rotated matrix + >>> list(zip(*matrix[::-1])) + [[7, 4, 1], + [8, 5, 2], + [9, 6, 3]] + + +Notes +----- + +Small note: If you run this, you will actually get a list of tuples, so you can map those back to a list, if you need to update them for any reason. +I just wanted square brackets in my examples. + +.. code-block:: python + + # This is just messy looking, so I didn't mention it until now + >>> list(map(list, zip(*matrix[::-1]))) + + +As I mentioned, due to using ``zip`` this will work with non-square examples as well. + +.. code-block:: python + + >>> matrix = [ + ... [1,2,3,4,5,6,7,8,9], + ... [9,8,7,6,5,4,3,2,1], + ... ] + >>> print(list(zip(*matrix[::-1]))) + [(9, 1), + (8, 2), + (7, 3), + (6, 4), + (5, 5), + (4, 6), + (3, 7), + (2, 8), + (1, 9)] + diff --git a/templates/blog/CONTRIBUTORS.md b/templates/blog/CONTRIBUTORS.md new file mode 100644 index 0000000..de4e6ac --- /dev/null +++ b/templates/blog/CONTRIBUTORS.md @@ -0,0 +1,20 @@ +# Contributors + +* [Nevan Scott](https://github.com/nevanscott/Mockingbird) (original author) +* [wrl](https://github.com/wrl/pelican-mockingbird) (port to pelican, pelican-mockingbird) +* [Jody Frankowski](http://github.com/jody-frankowski) (Blue Penguin) +* [Grimbox](https://github.com/Grimbox) +* [ix5](https://github.com/ix5) +* [dn0](https://github.com/dn0) +* [anhtuann](https://github.com/anhtuann) +* [aperep](https://github.com/aperep) +* [iranzo](https://github.com/iranzo) +* [thetlk](https://github.com/thetlk) +* [SnorlaxYum](https://github.com/SnorlaxYum) +* [guikcd](https://github.com/guikcd) +* [jorgesumle](https://github.com/jorgesumle) +* [crxxn](https://github.com/crxxn) +* [gavinzbq](https://github.com/gavinzbq) +* [sylvainmetayer](https://github.com/sylvainmetayer) +* [wbob](https://github.com/wbob) +* [Tyler Carr](https://github.com/tcarwash) diff --git a/templates/blog/LICENSE.md b/templates/blog/LICENSE.md new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/templates/blog/LICENSE.md @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/templates/blog/README.md b/templates/blog/README.md new file mode 100644 index 0000000..7c71e43 --- /dev/null +++ b/templates/blog/README.md @@ -0,0 +1,70 @@ +![screenshot](https://tyler-carr.com/images/ftp/out.png) + +# Blue Penguin Dark for pelican +A simple dark theme for pelican, with light/dark mode support using media queries available. Solarized pygments. Feeds support. + +This theme has been adapted from the original [Blue Penguin Theme](https://github.com/jody-frankowski/blue-penguin) for use on [my website](https://tyler-carr.com), all changes are generic and this theme can be used interchangeably with the original. + +## Settings +```python +# all the following settings are *optional* + +# HTML metadata +SITEDESCRIPTION = '' + +# all defaults to True. +DISPLAY_HEADER = True +DISPLAY_FOOTER = True +DISPLAY_HOME = True +DISPLAY_MENU = True +DARK_LIGHT_SWITCHING_OFF = True + +# provided as examples, they make ‘clean’ urls. used by MENU_INTERNAL_PAGES. +TAGS_URL = 'tags' +TAGS_SAVE_AS = 'tags/index.html' +AUTHORS_URL = 'authors' +AUTHORS_SAVE_AS = 'authors/index.html' +CATEGORIES_URL = 'categories' +CATEGORIES_SAVE_AS = 'categories/index.html' +ARCHIVES_URL = 'archives' +ARCHIVES_SAVE_AS = 'archives/index.html' + +# use those if you want pelican standard pages to appear in your menu +MENU_INTERNAL_PAGES = ( + ('Tags', TAGS_URL, TAGS_SAVE_AS), + ('Authors', AUTHORS_URL, AUTHORS_SAVE_AS), + ('Categories', CATEGORIES_URL, CATEGORIES_SAVE_AS), + ('Archives', ARCHIVES_URL, ARCHIVES_SAVE_AS), +) +# additional menu items +MENUITEMS = ( + ('GitHub', 'https://github.com/'), + ('Linux Kernel', 'https://www.kernel.org/'), +) + +# example pagination pattern +PAGINATION_PATTERNS = ( + (1, '{url}', '{save_as}'), + (2, '{base_name}/page/{number}/', '{base_name}/page/{number}/index.html'), +) + +``` + +Dark/Light switching is disabled by default, but can be enabled in the config file by adding: + +``` +DARK_LIGHT_SWITCHING_OFF = FALSE +``` + + +## How to contribute +Contributions are very welcome. Keep in mind that this theme goal is to be +minimalistic/simple. Contributions will be accepted through Github Pull +Requests. If you don’t have a Github account you can suggest me your +changes by email. + +## Contributors +See [CONTRIBUTORS.md](CONTRIBUTORS.md). + +## License +Public domain. diff --git a/templates/blog/sample/content/code.md b/templates/blog/sample/content/code.md new file mode 100644 index 0000000..e01dac9 --- /dev/null +++ b/templates/blog/sample/content/code.md @@ -0,0 +1,11 @@ +Title: Solarized Pygments +Date: 2022-6-28 12:00 +Category: Python +Tags: pelican, publishing +Authors: Alexis Metaireau, Conan Doyle +Summary: Short version for index and feeds + +``` +while True: + print("Pelican is neat!") +``` diff --git a/templates/blog/sample/content/post.md b/templates/blog/sample/content/post.md new file mode 100644 index 0000000..19c44c2 --- /dev/null +++ b/templates/blog/sample/content/post.md @@ -0,0 +1,8 @@ +Title: Blue Penguin Dark +Date: 2022-6-28 13:00 +Category: Python +Tags: pelican, publishing +Authors: Alexis Metaireau, Conan Doyle +Summary: Short version for index and feeds + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. diff --git a/templates/blog/sample/settings.py b/templates/blog/sample/settings.py new file mode 100644 index 0000000..0c7bf28 --- /dev/null +++ b/templates/blog/sample/settings.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- # + +AUTHOR = 'Tyler Carr' +SITENAME = 'Blue Penguin Dark' +SITEURL = 'http://localhost:8000' +SITELOGO = 'https://www.gravatar.com/avatar/de3ba847c89c8cab9dd7080abebf9df1' +DEFAULT_METADATE = { + 'status': 'draft', + } + + +PATH = 'content' + +TIMEZONE = 'America/Los_Angeles' + +DEFAULT_LANG = 'en' + + +MENUITEMS = ( + ('About', '/'), + ) diff --git a/templates/blog/screenshot.png b/templates/blog/screenshot.png new file mode 100644 index 0000000..ee4afb7 Binary files /dev/null and b/templates/blog/screenshot.png differ diff --git a/templates/blog/static/css/main.css b/templates/blog/static/css/main.css new file mode 100644 index 0000000..698d106 --- /dev/null +++ b/templates/blog/static/css/main.css @@ -0,0 +1,473 @@ +/* http://meyerweb.com/eric/tools/css/reset/ +v2.0 | 20110126 +License: none (public domain) + */ +/* Mockingbird Theme by Nevan Scott nevanscott.com */ +/* Modified by Jody Frankowski */ +/* Modified by ix5 */ +/* Modified by Tyler Carr */ + +:root { + --main-bg-color: #1c2833; + --code-bg-color: #17202a; + --accent-color: #C7254E; + --tab-bg-hover: var(--code-bg-color); + --info-bg-color: var(--code-gb-color); +} + + + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + color: #BBB; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +em { + font-style: italic; +} +strong { + font-weight: bold; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; + background-color: var(--main-bg-color); + background: url("/blog/theme/images/moroccan-flower-dark.png"); +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + +body { + font-family: Georgia, serif; + font-size: 16px; + line-height: 1.5em; +} + +header, #wrapper { + padding: 0 10px; + max-width: 910px; + margin: auto; + clear:both; +} + +a { + text-decoration: none; + color: #15A9DB; +} + +ul { + list-style: outside disc; +} + +ol { + list-style: outside decimal; +} + +h1, h2, h3, h4, h5, h6 { + font-family: sans-serif; + font-weight: bold; +} +h1, h2, h3 { + font-size: 1.5em; + line-height: 1em; + margin: 1em 0; +} + +img, p, .post > .highlight, .highlighttable, h4, h5, h6 { + margin-top: 1.2em; +} +img + em { + font-size: .8em; +} + +blockquote { + margin: 1.5em 1.5em 1.5em .75em; + padding-left: .75em; + border-left: 1px solid #EEE; +} + +table { + width: 100%; + margin: 1em 0; +} +thead { + border-bottom: 1px solid black; +} + +.avatar { + border-radius: 50%; + -mox-border-radius: 50%; + -webkit-border-radius: 50%; + position: relative; + bottom: 0; + top: 0; + margin: auto; + display: inline; + padding-right: 5px; +} + +.date { + color: #CCC; + float: left; + clear: both; + width: 130px; + font-size: 1.5em; + line-height: 1em; + margin: 0 20px 1em 0; +} + +.info { + margin-top: 1.3em; + font-family: sans-serif; + text-align: right; + color: #BBB; +} +.info a { + color: inherit; +} +.info a.tags { + background: var(--info-bg-color); + color: #FFF; + display: inline-block; + padding: 0 .3em; + border: 1px transparent solid; + border-radius: 5px; + margin: 0 0 0.3em 0; +} +.info a.tags:hover { + background: inherit; + color: inherit; +} +.info a.tags.selected { + border: 1px #999 solid; +} + +.post { + margin: 0 0 4.5em 150px; +} +.post.archives { + margin-bottom: 1.5em; + margin-left: 160px; +} +.post p { + text-align: justify; +} + +.page { + margin: 0 90px; +} + +.highlight { + border-radius: 3px; +} +.code > .highlight { + border-radius: 0px 3px 3px 0px; + background-color: var(--code-bg-color); +} +.linenos { + border-radius: 3px 0px 0px 3px; + background-color: var(--code-bg-color); + border-right: 1px solid #00232C; + color: #586E75; + text-shadow: 0px -1px #021014; + font-size: 12.6px; +} +td.code { + width: 100%; + max-width: 100px; +} +.linenos a { + color: #586E75; +} + +img { + box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.15); + border-radius: 0.3em; + max-width: 100%; + display: block; + margin-left: auto; + margin-right: auto; +} + +/*sub and sup stolen from Twitter bootstrap.*/ +sub, sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +.post pre, .page pre{ + padding: .8em; + font-size: 12.6px; + font-family: Monaco,Menlo,Consolas,"Courier New",monospace; + line-height: 18px; + overflow: auto; + background-color: var(--code-bg-color); +} + +.literal { + padding: .5em; + overflow: pre-line; + font-size: 12.6px; + font-family: Monaco,Menlo,Consolas,"Courier New",monospace; + background-color: var(--code-bg-color); +} + +form.inline_edit { + clear: both; + margin: 4.5em 0; + background-color: #DDD; + color: #000; + padding: 20px; + border-radius: 5px; +} +.inline_edit .sub { + color: #888; + white-space: nowrap; +} +.inline_edit label { + float: left; + clear: both; + width: 140px; + margin-right: 20px; +} +.inline_edit .buttons { + display: block; + text-align: right; +} + +nav ul { + float: right; + list-style: none; + margin: 0 0 0 3em; + padding: 0; +} +nav li { + float: left; +} +nav a { + display: block; + padding: 1.5em 10px 10px 10px; +} +nav a:hover { + background-color: var(--tab-bg-hover); + color: var(--accent-color); +} +nav li.selected a { + background-color: #15A9DB; + color: #FFF; +} + +nav li.ephemeral a { + background-color: #15A9DB; + color: #FFF; + filter: brightness(0.75); +} + + +header .header_box { + padding-top: 1.5em; + text-align: justify; + -moz-text-align-last: justify; + text-align-last: justify; + +} +.site_title { + color: #15A9DB; + display: inline-block; + margin:0; +} +.site_subtitle { + display: inline-block; + margin: 0; + +} +header h1 { + font-size: 1.5em; + line-height: 1em; + margin: 0; +} +header h2 { + font-size: 1em; + margin: .3em 0; + color: #DDD; +} + +#content { + margin-top: 3em; +} + +.pages { + font-family: sans-serif; + line-height: 2.5em; + margin: 4.5em 0 3em; + background-color: #2e4053; + border-radius: 5px; +} +.pages a.next_page { + float: right; + width: 140px; + text-align: center; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + background-color: #EEE; +} +.pages a.prev_page { + float: left; + width: 140px; + text-align: center; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + background-color: #EEE; +} +.pages a { + color: inherit; + border: none; +} +.pages a:hover { + background-color: #DDD; +} +.pages span { + display: block; + margin: 0 160px; + text-align: center; +} + +code { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + box-sizing: border-box; + background-color: #17202a; + color: #C7254E; + font-family: Monaco,Menlo,Consolas,"Courier New",monospace; + font-size: 12.6px; + line-height: 18px; + padding-bottom: 2px; + padding-left: 0px; + padding-right: 4px; + padding-top: 2px; +} + +footer { + font-family: sans-serif; + line-height: 2.5em; + text-align: center; + color: #BBB; + margin: 3em 0; + border: 1px solid #EEE; + border-radius: 5px; +} +footer p { margin: 0; } + +.right { float: right; } + +.clear { clear: both; } + +@media screen and (max-width: 1024px) { + #wrapper { + margin-left: 2.6em; + padding:1em; + } +} + +@media screen and (max-width: 800px), print { + #wrapper { + margin: 0; + padding: 1em; + } + .date { + width: 100%; + margin: 0 0 .5em 0; + } + .post { + margin: 0 0 4.5em 0; + } + .post pre, + .post code { + white-space: pre-wrap; + } + .archives { + margin: 0 0 1.5em 0 !important; + } + nav ul { + margin: 0 0 1em 0; + } +} +@media print { + * { + background: #fff; + } + #wrapper, #content { + margin: 0; + padding: 0; + } + header, footer, nav { + display: none; + } + img { + break-inside: avoid; + } + p, table, pre { + widows: 3; + orphans: 3; + } + h1, h2, h3, h4, h5, h6 { + break-after: avoid; + } + h1, h2 { + font-size: 14pt; + } + h3, h4, h4.date { + font-size: 12pt; + } + .post p, .post ul li, .post ol li, .post table { + font-size: 11pt; + } +} +@page { + margin: 1.5cm; +} diff --git a/templates/blog/static/css/pygments.css b/templates/blog/static/css/pygments.css new file mode 100644 index 0000000..cb38d12 --- /dev/null +++ b/templates/blog/static/css/pygments.css @@ -0,0 +1,87 @@ +/* Solarized Dark + +For use with Jekyll and Pygments + +http://ethanschoonover.com/solarized + +SOLARIZED HEX ROLE +--------- -------- ------------------------------------------ +base03 #002b36 background +base01 #586e75 comments / secondary content +base1 #93a1a1 body text / default code / primary content +orange #cb4b16 constants +red #dc322f regex, special keywords +blue #268bd2 reserved keywords +cyan #2aa198 strings, numbers +green #859900 operators, other keywords +*/ + +.highlight { color: #93a1a1; background-color: #17202a;} +.highlight .c { color: #586e75 } /* Comment */ +.highlight .err { color: #93a1a1 } /* Error */ +.highlight .g { color: #93a1a1 } /* Generic */ +.highlight .k { color: #859900 } /* Keyword */ +.highlight .l { color: #93a1a1 } /* Literal */ +.highlight .n { color: #93a1a1 } /* Name */ +.highlight .o { color: #859900 } /* Operator */ +.highlight .x { color: #cb4b16 } /* Other */ +.highlight .p { color: #93a1a1 } /* Punctuation */ +.highlight .cm { color: #586e75 } /* Comment.Multiline */ +.highlight .cp { color: #859900 } /* Comment.Preproc */ +.highlight .c1 { color: #586e75 } /* Comment.Single */ +.highlight .cs { color: #859900 } /* Comment.Special */ +.highlight .gd { color: #2aa198 } /* Generic.Deleted */ +.highlight .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #dc322f } /* Generic.Error */ +.highlight .gh { color: #cb4b16 } /* Generic.Heading */ +.highlight .gi { color: #859900 } /* Generic.Inserted */ +.highlight .go { color: #93a1a1 } /* Generic.Output */ +.highlight .gp { color: #93a1a1 } /* Generic.Prompt */ +.highlight .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #cb4b16 } /* Generic.Subheading */ +.highlight .gt { color: #93a1a1 } /* Generic.Traceback */ +.highlight .kc { color: #cb4b16 } /* Keyword.Constant */ +.highlight .kd { color: #268bd2 } /* Keyword.Declaration */ +.highlight .kn { color: #859900 } /* Keyword.Namespace */ +.highlight .kp { color: #859900 } /* Keyword.Pseudo */ +.highlight .kr { color: #268bd2 } /* Keyword.Reserved */ +.highlight .kt { color: #dc322f } /* Keyword.Type */ +.highlight .ld { color: #93a1a1 } /* Literal.Date */ +.highlight .m { color: #2aa198 } /* Literal.Number */ +.highlight .s { color: #2aa198 } /* Literal.String */ +.highlight .na { color: #93a1a1 } /* Name.Attribute */ +.highlight .nb { color: #B58900 } /* Name.Builtin */ +.highlight .nc { color: #268bd2 } /* Name.Class */ +.highlight .no { color: #cb4b16 } /* Name.Constant */ +.highlight .nd { color: #268bd2 } /* Name.Decorator */ +.highlight .ni { color: #cb4b16 } /* Name.Entity */ +.highlight .ne { color: #cb4b16 } /* Name.Exception */ +.highlight .nf { color: #268bd2 } /* Name.Function */ +.highlight .nl { color: #93a1a1 } /* Name.Label */ +.highlight .nn { color: #93a1a1 } /* Name.Namespace */ +.highlight .nx { color: #93a1a1 } /* Name.Other */ +.highlight .py { color: #93a1a1 } /* Name.Property */ +.highlight .nt { color: #268bd2 } /* Name.Tag */ +.highlight .nv { color: #268bd2 } /* Name.Variable */ +.highlight .ow { color: #859900 } /* Operator.Word */ +.highlight .w { color: #93a1a1 } /* Text.Whitespace */ +.highlight .mf { color: #2aa198 } /* Literal.Number.Float */ +.highlight .mh { color: #2aa198 } /* Literal.Number.Hex */ +.highlight .mi { color: #2aa198 } /* Literal.Number.Integer */ +.highlight .mo { color: #2aa198 } /* Literal.Number.Oct */ +.highlight .sb { color: #586e75 } /* Literal.String.Backtick */ +.highlight .sc { color: #2aa198 } /* Literal.String.Char */ +.highlight .sd { color: #93a1a1 } /* Literal.String.Doc */ +.highlight .s2 { color: #2aa198 } /* Literal.String.Double */ +.highlight .se { color: #cb4b16 } /* Literal.String.Escape */ +.highlight .sh { color: #93a1a1 } /* Literal.String.Heredoc */ +.highlight .si { color: #2aa198 } /* Literal.String.Interpol */ +.highlight .sx { color: #2aa198 } /* Literal.String.Other */ +.highlight .sr { color: #dc322f } /* Literal.String.Regex */ +.highlight .s1 { color: #2aa198 } /* Literal.String.Single */ +.highlight .ss { color: #2aa198 } /* Literal.String.Symbol */ +.highlight .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #268bd2 } /* Name.Variable.Class */ +.highlight .vg { color: #268bd2 } /* Name.Variable.Global */ +.highlight .vi { color: #268bd2 } /* Name.Variable.Instance */ +.highlight .il { color: #2aa198 } /* Literal.Number.Integer.Long */ diff --git a/templates/blog/static/css/wordcloud.css b/templates/blog/static/css/wordcloud.css new file mode 100644 index 0000000..be976da --- /dev/null +++ b/templates/blog/static/css/wordcloud.css @@ -0,0 +1,55 @@ +.wordcloud { + padding: 0.1rem; + text-align:center; + margin:auto; +} +@media screen and (min-width: 700px) { + .wordcloud { + width: 400px; + } +} + +.size-1 { font-size:1.0rem; } +.size-2 { font-size:1.1rem; } +.size-3 { font-size:1.2rem; } +.size-4 { font-size:1.3rem; } +.size-5 { font-size:1.4rem; } +.size-6 { font-size:1.5rem; } +.size-7 { font-size:1.6rem; } +.size-8 { font-size:1.7rem; } +.size-9 { font-size:1.8rem; } +.size-10 { font-size:1.9rem; } + + +.size-11 { font-size:2.0rem; } +.size-12 { font-size:2.1rem; } +.size-13 { font-size:2.2rem; } +.size-14 { font-size:2.3rem; } +.size-15 { font-size:2.4rem; } +.size-16 { font-size:2.5rem; } +.size-17 { font-size:2.6rem; } +.size-18 { font-size:2.7rem; } +.size-19 { font-size:2.8rem; } +.size-20 { font-size:2.9rem; } + +.size-21 { font-size:3.0rem; } +.size-22 { font-size:3.1rem; } +.size-23 { font-size:3.2rem; } +.size-24 { font-size:3.3rem; } +.size-25 { font-size:3.4rem; } +.size-26 { font-size:3.5rem; } +.size-27 { font-size:3.6rem; } +.size-28 { font-size:3.7rem; } +.size-29 { font-size:3.8rem; } +.size-30 { font-size:3.9rem; } + +.size-31 { font-size:4.0rem; } +.size-32 { font-size:4.1rem; } +.size-33 { font-size:4.2rem; } +.size-34 { font-size:4.3rem; } +.size-35 { font-size:4.4rem; } +.size-36 { font-size:4.5rem; } +.size-37 { font-size:4.6rem; } +.size-38 { font-size:4.7rem; } +.size-39 { font-size:4.8rem; } +.size-40 { font-size:4.9rem; } diff --git a/templates/blog/static/images/moroccan-flower-dark.png b/templates/blog/static/images/moroccan-flower-dark.png new file mode 100644 index 0000000..e9756aa Binary files /dev/null and b/templates/blog/static/images/moroccan-flower-dark.png differ diff --git a/templates/blog/templates/analytics.html b/templates/blog/templates/analytics.html new file mode 100644 index 0000000..ec79a77 --- /dev/null +++ b/templates/blog/templates/analytics.html @@ -0,0 +1 @@ + diff --git a/templates/blog/templates/archives.html b/templates/blog/templates/archives.html new file mode 100644 index 0000000..73c53ba --- /dev/null +++ b/templates/blog/templates/archives.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block title %}{{ SITENAME }} | Archives{% endblock %} +{% block content %} + +

Archives

+ + {# based on http://stackoverflow.com/questions/12764291/jinja2-group-by-month-year #} + + {% for year, year_group in dates|groupby('date.year')|reverse %} + {% for month, month_group in year_group|groupby('date.month')|reverse %} +

{{ (month_group|first).date|strftime('%b %Y') }}

+
+ +
+ {% endfor %} + {% endfor %} +{% endblock %} diff --git a/templates/blog/templates/article.html b/templates/blog/templates/article.html new file mode 100644 index 0000000..40c60b1 --- /dev/null +++ b/templates/blog/templates/article.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block head %} + {{ super() }} + {% if article.tags %} + + {% endif %} + {% if article.description %} + + {% endif %} +{% endblock %} + +{% block title %}{{ SITENAME }} | {{ article.title|striptags }}{% endblock %} + +{% block content %} +{% include "article_stub.html" %} +{% endblock %} diff --git a/templates/blog/templates/article_stub.html b/templates/blog/templates/article_stub.html new file mode 100644 index 0000000..893ee53 --- /dev/null +++ b/templates/blog/templates/article_stub.html @@ -0,0 +1,36 @@ + {% if not articles_page or first_article_of_day %} +

{{ article.date.strftime("%b %d, %Y") }}

+ {% endif %} + +
+ {% if article.title %} +

+ {{ article.title }} +

+ {% endif %} + + {% if not articles_page %} + {% include "translations.html" %} + {% endif %} + + + {{ article.content }} +
+ +
+ {% if article.category.name != "misc" %} + + {% endif %} + {% if article.tags %} +  · · · + {% for t in article.tags %} +  {{ t }} + {% endfor %} + {% endif %} +
+ {% if articles_page and DISQUS_SITENAME %} + Click to read and post comments + {% else %} + {% include "disqus.html" %} + {% endif %} +
diff --git a/templates/blog/templates/author.html b/templates/blog/templates/author.html new file mode 100644 index 0000000..b9ff61e --- /dev/null +++ b/templates/blog/templates/author.html @@ -0,0 +1,7 @@ +{% extends "index.html" %} + +{% block title %}{{ SITENAME }} | Articles by {{ author }}{% endblock %} +{% block ephemeral_nav %} + + {{ ephemeral_nav_link(author, output_file, True) }} +{% endblock %} diff --git a/templates/blog/templates/base.html b/templates/blog/templates/base.html new file mode 100644 index 0000000..5a80a3b --- /dev/null +++ b/templates/blog/templates/base.html @@ -0,0 +1,129 @@ +{% macro ephemeral_nav_link(what, where, selected=False) -%} +
  • {{what}}
  • +{%- endmacro -%} + + + + + {% block head %} + + + {% block title %}{{ SITENAME }}{% endblock title %} + {# favicon #} + + + {% if FEED_ALL_ATOM %} + + {% endif %} + {% if FEED_ALL_RSS %} + + {% endif %} + {% if FEED_ATOM %} + + {% endif %} + {% if FEED_RSS %} + + {% endif %} + {% if CATEGORY_FEED_ATOM and category %} + + {% endif %} + {% if CATEGORY_FEED_RSS and category %} + + {% endif %} + {% if TAG_FEED_ATOM and tag %} + + {% endif %} + {% if TAG_FEED_RSS and tag %} + + {% endif %} + {% assets filters="cssmin", output="css/main.min.css", "css/main.css" %} + + {% endassets%} + {% assets filters="cssmin", output="css/pygments.min.css", "css/pygments.css" %} + + {% endassets%} + {% if DARK_LIGHT_SWITCHING_OFF == False %} + + {% endif %} + + + + {% include 'analytics.html' %} + {% endblock head %} + {% block extrahead %} + {% endblock extrahead %} + + + {% if DISPLAY_HEADER or DISPLAY_HEADER is not defined %} +
    +
    +

    + + {{ SITENAME }} + +

    + {% if SITESUBTITLE %} +

    {{ SITESUBTITLE }}

    + {% endif %} +
    + + {% if DISPLAY_MENU or DISPLAY_MENU is not defined %} + + {% endif %} +
    + {% endif %} +
    +
    + {%- block content -%}{%- endblock %} + + {% if DISPLAY_FOOTER or DISPLAY_FOOTER is not defined %} +
    +
    +

    + {% if FEED_ALL_ATOM %} + Atom Feed + {% endif %} + {% if FEED_ALL_RSS %} + · + Rss Feed + {% endif %} +

    + {% endif %} +
    +
    +
    + + diff --git a/templates/blog/templates/category.html b/templates/blog/templates/category.html new file mode 100644 index 0000000..6587d98 --- /dev/null +++ b/templates/blog/templates/category.html @@ -0,0 +1,6 @@ +{% extends "index.html" %} +{% block title %}{{ SITENAME }} | articles in the "{{ category }}" category{% if articles_page.number != 1 %} | Page {{ articles_page.number }}{% endif %}{% endblock %} +{% block ephemeral_nav %} + + {{ ephemeral_nav_link(category, output_file, True) }} +{% endblock %} diff --git a/templates/blog/templates/disqus.html b/templates/blog/templates/disqus.html new file mode 100644 index 0000000..b4093e5 --- /dev/null +++ b/templates/blog/templates/disqus.html @@ -0,0 +1,12 @@ +{% if DISQUS_SITENAME %} +
    + + +{% endif %} diff --git a/templates/blog/templates/index.html b/templates/blog/templates/index.html new file mode 100644 index 0000000..b8b40f4 --- /dev/null +++ b/templates/blog/templates/index.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block title %}{{ SITENAME }}{% if articles_page.number != 1 %} | Page {{ articles_page.number }}{% endif %}{% endblock %} +{% block content %} +{% set date = None %} +{% for article in articles_page.object_list %} +{% if date != article.date.date() %} +{% set first_article_of_day = True %} +{% else %} +{% set first_article_of_day = False %} +{% endif %} +{% set date = article.date.date() %} +{% include "article_stub.html" %} +{% endfor %} + +{% include "pagination.html" %} +{% endblock %} diff --git a/templates/blog/templates/page.html b/templates/blog/templates/page.html new file mode 100644 index 0000000..c285678 --- /dev/null +++ b/templates/blog/templates/page.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block title %}{{ SITENAME }} | {{ page.title }}{% endblock %} + +{% block content %} + +
    +

    {{ page.title }}

    + {{ page.content }} +
    +{% endblock %} diff --git a/templates/blog/templates/pagination.html b/templates/blog/templates/pagination.html new file mode 100644 index 0000000..6562444 --- /dev/null +++ b/templates/blog/templates/pagination.html @@ -0,0 +1,17 @@ +{# Use PAGINATION_PATTERNS or pagination may break #} +{% if DEFAULT_PAGINATION and (articles_page.has_previous() or articles_page.has_next()) %} + +
    +
    + + {% if articles_page.has_previous() %} + ← Previous + {% endif %} + + {% if articles_page.has_next() %} + Next → + {% endif %} + + Page {{ articles_page.number }} of {{ articles_paginator.num_pages }} +
    +{% endif %} diff --git a/templates/blog/templates/tag.html b/templates/blog/templates/tag.html new file mode 100644 index 0000000..92c3439 --- /dev/null +++ b/templates/blog/templates/tag.html @@ -0,0 +1,5 @@ +{% extends "index.html" %} +{% block title %}{{ SITENAME }} | articles tagged "{{ tag }}"{% if articles_page.number != 1 %} | Page {{ articles_page.number }}{% endif %}{% endblock %} +{% block ephemeral_nav %} + {{ ephemeral_nav_link(tag, output_file, True) }} +{% endblock %} diff --git a/templates/blog/templates/tags.html b/templates/blog/templates/tags.html new file mode 100644 index 0000000..e71176b --- /dev/null +++ b/templates/blog/templates/tags.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% block extrahead %} + {% assets filters="cssmin", output="css/wordcloud.min.css", "css/wordcloud.css" %} + + {% endassets %} +{% endblock %} +{% block content %} +

    Word Cloud of Tags

    +
    + {% for tag, articles in tags|sort %} + {{ tag }} + {% endfor %} +
    +{% endblock %} diff --git a/templates/blog/templates/translations.html b/templates/blog/templates/translations.html new file mode 100644 index 0000000..f0a0fa2 --- /dev/null +++ b/templates/blog/templates/translations.html @@ -0,0 +1,6 @@ +{% if article.translations %} +Translations: + {% for translation in article.translations %} + {{ translation.lang }} + {% endfor %} +{% endif %} \ No newline at end of file