[{"element_type":"image_url","content":"https://github.com/cadet702/cadet702.github.io/assets/32227645/fde648ca-0abd-4202-9ff1-8e8e0be272ba","metadata":{"alt":"The","caption":""}},{"element_type":"text","content":"\n  \n        \u003ch1\u003eInspiration\u003c/h1\u003e\n\u003cp\u003eThis project is an extension of the \u003ca href=\"https://adafruit-playground.com/u/cadet702/pages/doggy-buttons\"\u003eoriginal doggy buttons project\u003c/a\u003e\u0026nbsp;where I'll be showing how to make a \u003ca href=\"https://io.adafruit.com/cadet702/dashboards/delayed-puppy-buttons\"\u003epublic dashboard that publishes everything my dog says\u003c/a\u003e.\u0026nbsp; This project is just one example of how to extend the features of my doggy buttons and perhaps one of the silliest IOT devices ever made.\u0026nbsp; If you want to know about the buttons themselves, see the \u003ca href=\"https://adafruit-playground.com/u/cadet702/pages/doggy-buttons\"\u003eoriginal project writeup\u003c/a\u003e.\u003cbr\u003e\u003cbr\u003eWith a project like this, it's also important to think about privacy.\u0026nbsp; I don't want my dog making it possible to tell when we're away from home, so I'll also show how to add a delay so that activity is only published once it's sufficiently stale.\u003c/p\u003e\n      \n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch1\u003e\n\u003ca href=\"https://io.adafruit.com/\"\u003eAdafruitIO\u003c/a\u003e Is Awesome\u003c/h1\u003e\n\u003ch2\u003eFree (as in beer)\u003c/h2\u003e\n\u003cp\u003eThis is a silly project and I can't reasonably justify spending money to publish my dog's requests online.\u0026nbsp; Fortunately \u003ca href=\"https://io.adafruit.com/\"\u003eAdafruitIO\u003c/a\u003e has a very generous free tier.\u0026nbsp; If at some point I want her to be able to text me, the Adafruit IO+ account is reasonably priced.\u003c/p\u003e\n\u003ch2\u003eEasy\u003c/h2\u003e\n\u003cp\u003eThis is my first Internet Of Things (IOT) project and I was a little worried I wouldn't be able to make it work without investing\u0026nbsp;\u003cem\u003ewaaaaay\u003c/em\u003e to much effort.\u0026nbsp; But nope, there are excellent guides and code examples you can pretty much copy-paste onto your device to get started!\u0026nbsp;\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"guide","content":"https://learn.adafruit.com/adafruit-io/overview","metadata":{"guide_id":"570","page_id":"3583"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003ePowerful\u003c/h2\u003e\n\u003cp\u003eAs we'll see, \u003ca href=\"https://io.adafruit.com/\"\u003eAdafruitIO\u003c/a\u003e has all kinds of useful features.\u0026nbsp; It can collect data from devices, send data to devices, and with a little cleverness, perform translation and delayed publication to a public dashboard while keeping the live feed private.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"guide","content":"https://learn.adafruit.com/geofencing-with-the-fona-808-and-adafruit-io/introduction","metadata":{"guide_id":"1145","page_id":"6419"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n        \u003ch1\u003eConfiguring AdafruitIO\u003c/h1\u003e\n\u003ch2\u003e\u003cspan\u003eRaw Feed\u003c/span\u003e\u003c/h2\u003e\n\u003cp\u003e\u003cspan\u003eThe first thing we'll need is the raw feed of data from the Pi.\u0026nbsp; This feed should be set to private since it sends data in real time.\u0026nbsp; You'll need to at least setup a feed before you jump to the coding part because you'll need the secret key in order to publish your data.\u0026nbsp; You can do the remaining setup steps before you have any actually data, or after.\u003c/span\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n","metadata":{}},{"element_type":"image_url","content":"https://github.com/cadet702/cadet702.github.io/assets/32227645/e3e35ed9-04bf-4834-a67c-23cb00814dc2","metadata":{"alt":"The","caption":""}},{"element_type":"text","content":"\n  \n  \n        \u003cp\u003eBut wait, you say.\u0026nbsp; This is just a list of numbers!\u0026nbsp; How do I know what my dog is saying?\u003c/p\u003e\n\u003cp\u003eWell, don't worry, we can perform the translation at the same time we do the delay.\u003c/p\u003e\n      \n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003ePublic Feed\u003c/h2\u003e\n\u003cp\u003eThe next thing is to set up a public feed that we can publish the translated words to and use to support the public dashboard.\u0026nbsp; Every public dashboard is built on top of at least one public feed.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"image_url","content":"https://github.com/cadet702/cadet702.github.io/assets/32227645/107a80e1-4424-4bc7-900a-c5138c366afd","metadata":{"alt":"The","caption":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eActions\u003c/h2\u003e\n\u003cp\u003eNow we just need to set up some actions to translate the data from the private feed and republish it to the public feed with a delay.\u0026nbsp; I couldn't find a way to put complex conditional logic in an action, so I just created an action for each raw value with simple logic to translate it to text.\u003c/p\u003e\n\u003cp\u003eI have occasionally had the code that publishes the button presses mysteriously stop working, so I also created an action that functions as a dead man's switch.\u0026nbsp; This will display an error message if no button presses have been detected in 24 hours.\u003c/p\u003e\n\u003cp\u003eThe setup for a single translate-and-delay action looks like this:\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"image_url","content":"https://github.com/cadet702/cadet702.github.io/assets/32227645/066767ae-3358-4f36-8ffd-bb63a206bd0a","metadata":{"alt":"Example","caption":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eAll together, the set of actions looks like this:\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"image_url","content":"https://github.com/cadet702/cadet702.github.io/assets/32227645/1f47886b-8313-4d0c-9950-f5c105f3a0a2","metadata":{"alt":"The","caption":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eDashboard\u003c/h2\u003e\n\u003cp\u003eThe last thing is to setup a public dashboard where we can configure how the messages are displayed.\u0026nbsp; Create a tile (I used a \"stream\"), connect the delayed feed, and configure the settings.\u0026nbsp; Most of this is straight forward, but I've included a picture of the settings I used below in case you want to reproduce the look and feel of my dashboard.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"image_url","content":"https://github.com/cadet702/cadet702.github.io/assets/32227645/7abbddbd-7e3a-445e-9408-29dd1e540e10","metadata":{"alt":"Dashboard","caption":""}},{"element_type":"text","content":"\n        \u003ch1\u003eSetup Challenges\u003c/h1\u003e\n\u003ch2\u003eUser Space vs Admin Space\u003c/h2\u003e\n\u003cp\u003eThe first version of my program was kicked off by\u0026nbsp;\u003ccode\u003e/etc/rc.local\u003c/code\u003e on boot, but this had the side effect of running the program as \u003ccode\u003eroot\u003c/code\u003e.\u0026nbsp; That was fine for my original project, but I had followed best practice and\u0026nbsp;\u003cem\u003enot\u003c/em\u003e used\u0026nbsp;\u003ccode\u003esudo\u003c/code\u003e when\u0026nbsp;\u003ccode\u003epip3 install\u003c/code\u003eing the Python libraries needed for loading data to AdafruitIO.\u0026nbsp; If I logged in to run the program as my user, it would end when I logged off but \u003ccode\u003eroot\u003c/code\u003e no longer had all the Python libraries needed to run.\u003c/p\u003e\n\u003cp\u003e\u003cspan\u003eLennart (\u003ca href=\"https://binarylabs.dev/\"\u003eBinary Labs\u003c/a\u003e) suggested the simple and elegant solution of adding\u003c/span\u003e \u003ccode\u003esudo -u MY_PI_USERNAME\u003c/code\u003e before the command that would launch my code in\u0026nbsp;\u003ccode\u003e/etc/rc.local\u003c/code\u003e.\u0026nbsp; This allows the program to be run as my user without having to login and was much simpler and cleaner than the other solutions I was considering.\u003c/p\u003e\n\u003ch2\u003eConcurrency\u003c/h2\u003e\n\u003cp\u003eThe first version of my code simply added a call to \u003ccode\u003eaio.send_data(...)\u003c/code\u003e in the same loop that processed button presses, but I quickly discovered that the upload process could take 3 - 4 seconds leading to very delayed sounds when more then one button was pressed.\u0026nbsp; Things like \"water outside walk later\" became \"water ... outside ... walk ... later\". \u0026nbsp;This was starting to confuse my dog because the sounds weren't immediately connected with the button presses and would sometimes sound when she had gone to push something else entirely because the button \"wasn't working\".\u003c/p\u003e\n\u003cp\u003eTo fix this it was going to be necessary to introduce separate queues, one for playing sounds that could respond quickly, and one for uploading button presses that could slowly upload data to AdafruitIO in the background.\u003c/p\u003e\n      ","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n        \u003ch1\u003eCode\u003c/h1\u003e\n\u003cp\u003eThe code below is what I currently use to have responsive buttons and upload data to AdafruitIO in the background.\u0026nbsp; It's a little more advanced than the original doggy button code but it's an excellent foundation for adding additional features without disrupting the original purpose.\u0026nbsp; A big thank you to Thomas over at the Embedded podcast slack for helping architect this threading system and adding a nice refactor to simplify the main loop!\u003c/p\u003e\n      \n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"#!/usr/bin/env python3\n\nimport sys\nimport time\nimport os\nimport board\nimport keypad\nimport queue\nimport threading\nfrom pygame import mixer\n\n## Use the first argument as the delay\nwait_seconds_raw = sys.argv[1]\nwait_seconds = int(wait_seconds_raw)\n\n# Import Adafruit IO REST client.\nfrom Adafruit_IO import Client,  RequestError\n\n## Set to your Adafruit IO key.\n## Remember, your key is a secret,\n## so make sure not to publish it when you publish this code!\nADAFRUIT_IO_KEY = 'YOUR_KEY_HERE'\n\n## Set to your Adafruit IO username.\n## (go to https://accounts.adafruit.com to find your username)\nADAFRUIT_IO_USERNAME = 'YOUR_USERNAME_HERE'\n\n## Create an instance of the REST client.\naio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)\n\n## Sleep for a long time on bootup to give the wi-fi time to connect\ntime.sleep(wait_seconds) ## argument via command line\n\ntry:\n    puppy_buttons_feed = aio.feeds('puppy-buttons')\nexcept RequestError:\n    print('could not find specified feed')\n\n## Load the sounds:\nmixer.init()\nsound_applause = mixer.Sound('/home/pi/pianobar/wav_files/applause-1.wav')\nsound_play       = mixer.Sound('/home/pi/pianobar/wav_files/play.wav')\nsound_potty      = mixer.Sound('/home/pi/pianobar/wav_files/potty.wav')\nsound_water      = mixer.Sound('/home/pi/pianobar/wav_files/water.wav')\nsound_walk       = mixer.Sound('/home/pi/pianobar/wav_files/walk.wav')\nsound_later      = mixer.Sound('/home/pi/pianobar/wav_files/later.wav')\nsound_anabelle   = mixer.Sound('/home/pi/pianobar/wav_files/anabelle.wav')\nsound_outside    = mixer.Sound('/home/pi/pianobar/wav_files/outside.wav')\nsound_hmmm       = mixer.Sound('/home/pi/pianobar/wav_files/hmmm.wav')\nsound_daddy      = mixer.Sound('/home/pi/pianobar/wav_files/daddy.wav')\nsound_mommy      = mixer.Sound('/home/pi/pianobar/wav_files/mommy.wav')\n\nsound_all_done   = mixer.Sound('/home/pi/pianobar/wav_files/all-done-happy-v2.wav')\nsound_ball       = mixer.Sound('/home/pi/pianobar/wav_files/ball.wav')\nsound_bed_time   = mixer.Sound('/home/pi/pianobar/wav_files/bed-time.wav')\nsound_cuddles    = mixer.Sound('/home/pi/pianobar/wav_files/cuddles.wav')\nsound_downstairs = mixer.Sound('/home/pi/pianobar/wav_files/downstairs.wav')\nsound_hard       = mixer.Sound('/home/pi/pianobar/wav_files/hard-best-v4.wav')\nsound_lazer      = mixer.Sound('/home/pi/pianobar/wav_files/lazer.wav')\nsound_lila       = mixer.Sound('/home/pi/pianobar/wav_files/lila.wav')\nsound_love_you   = mixer.Sound('/home/pi/pianobar/wav_files/love-you.wav')\nsound_nap_time   = mixer.Sound('/home/pi/pianobar/wav_files/nap-time.wav')\nsound_noise      = mixer.Sound('/home/pi/pianobar/wav_files/noise.wav')\nsound_pets       = mixer.Sound('/home/pi/pianobar/wav_files/pets.wav')\nsound_soft       = mixer.Sound('/home/pi/pianobar/wav_files/soft.wav')\nsound_thea       = mixer.Sound('/home/pi/pianobar/wav_files/thea.wav')\nsound_tug        = mixer.Sound('/home/pi/pianobar/wav_files/tug.wav')\n\nsound_upstairs   = mixer.Sound('/home/pi/pianobar/wav_files/upstairs.wav')\nsound_want       = mixer.Sound('/home/pi/pianobar/wav_files/want.wav')\nsound_yard       = mixer.Sound('/home/pi/pianobar/wav_files/yard.wav')\n\nkm = keypad.KeyMatrix(\n    row_pins=(board.D4, board.D7, board.D8, board.D11, board.D9, board.D10),\n    column_pins=(board.D17, board.D18, board.D27, board.D22, board.D23, board.D24, board.D25),\n    columns_to_anodes=True,\n)\nevent_map = {\n    1: sound_mommy,\n    5: sound_daddy,\n    21: sound_hmmm,\n    22: sound_play,\n    23: sound_outside,\n    24: sound_potty,\n    25: sound_water,\n    27: sound_walk,\n    26: sound_later,\n}\n\ndef sound_player(play_queue, quit):\n    while True:\n        sound = play_queue.get()\n        if quit.is_set():\n            return\n        sound.play()\n\ndef event_logger(log_queue, quit):\n    while True:\n        event_number = log_queue.get()\n        if quit.is_set():\n            return\n        aio.send_data(puppy_buttons_feed.key, event_number)\n\nlog_queue = queue.Queue()\nplay_queue = queue.Queue()\nquit_flag = threading.Event()\n\nthreads = [threading.Thread(target=sound_player, args=(play_queue, quit_flag)),\n           threading.Thread(target=event_logger, args=(log_queue, quit_flag))]\n\n## Code to actually \".Start()\" the threads\nfor t in threads:\n    t.start()\n\ntry:\n    while True:\n        event = km.events.get()\n        # Maybe print the event here?...\n        if not (event and event.pressed):\n            continue\n        if event.key_number == 0:\n            break\n        if event.key_number in event_map:\n            play_queue.put(event_map[event.key_number])\n            log_queue.put(event.key_number)\n\nfinally:\n    quit_flag.set()\n    # Put \"None\" into the queues to wake up the threads blocking in queue.get()\n    log_queue.put(None)\n    play_queue.put(None)\n    for t in threads:\n        t.join()\n\nos.system('sudo shutdown now -h')\nexit(0)","metadata":{"language":"python","linenums":false}}]