diff --git a/README.md b/README.md index e3666f8..da886be 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Inspired by [Slack Status Updater With ESP8266](https://www.instructables.com/Sl ![EasySlackStatus](doc/easyslackstatus.jpg) ![EasySlackStatus](doc/easyslackstatusB.jpg) -# Required Hardware +## Required Hardware - Wemos D1 Mini ESP32 [Amzon](https://www.amazon.com/MELIFE-Bluetooth-Development-Functional-Compatible/dp/B08FWXFSVN/) - 128x64 SH1106 I2C OLED Display [Amazon](https://www.amazon.com/gp/product/B08V97FYD2/) - Rotary encoder with push button [Amazon](https://www.amazon.com/gp/product/B07T3672VK/ref=ppx_yo_dt_b_search_asin_image?ie=UTF8&psc=1) @@ -14,7 +14,7 @@ Inspired by [Slack Status Updater With ESP8266](https://www.instructables.com/Sl - 3D Printed Housing [ThingiVerse](https://www.thingiverse.com/thing:4879479) - 2x M2x6mm HexHead screws -# Features +## Features - 100% OpenSource MIT License. - Set your current slack status with optional emjoi. - Set an Expiring (in x minutes) status. @@ -24,12 +24,14 @@ Inspired by [Slack Status Updater With ESP8266](https://www.instructables.com/Sl - ScreenSaver triggers after 60 minutes of no input. Simply turn the rotary dial to wake back up. - Simple json Based Configuration. -# Future Plans +## Future Plans - Command line file configuration. - Automatic Softwrae Updates - A wifi configurator. -# Obtain a **unique to you** Slack-Token +# Getting Started + +## Obtain a **unique to you** Slack-Token In order for the status to be correctly sent to your account, you must first obtain a unique `Slack-Token`. This token binds your EasySlackStatus device to your specific user and workspace. Original Instructions I used are here https://github.com/witnessmenow/arduino-slack-api @@ -47,12 +49,30 @@ Steps: - users:write - Scroll back to the top and click `Install app to Workspace` - Click `Allow` - - You will now have an `OAuth Access Token` that you must copy to the `config.json` file. The toke will be something like `xoxp-17986256612-407223851215-376290712391-48df5e2e6083745488da8823455afe67` + - You will now have an `OAuth Access Token` that you must copy to the `config.json` file. The toke will be something like `xoxp-< ... >` Ask your Slack Admin for help getting a slack token if your slack has restrictions. +## Quick-Start Setup Command + +- Run `./slacker --help` for a list of commands. +- Run `./slacker flash ` to flash the device. +- Run `./slacker bootstrap ` to install the files needed to run the application. + +### Configuring the Cli + +1. The CLI is generated used [Bashly](https://github.com/DannyBen/bashly). +> If you have docker installed, you can create an alias that will run the docker image: +``` +alias bashly='docker run --rm -it --user $(id -u):$(id -g) --volume "$PWD:/app" dannyben/bashly' +``` + +Otherwise, refer to [Installing Bashly](https://bashly.dannyb.co/installation/) +2. Update the `src/bashly.yml` and run `bashly generate` to generate the updated CLI. +> Note: updating the scripts in `src` won't commit the changes to the CLI. You will need to run `bashly generate` each time you want to update the CLI commands/params. +3. Finally, you can validate with `./slacker --help` -# Flashing the firmware and EasySlackStatus software +## Flashing the firmware and EasySlackStatus software `EasySlackStatus` runs on an ESP32 running Micropython. The following steps will get you up and running. 1. Install the tools [esptool](https://docs.espressif.com/projects/esptool/en/latest/esp32/) and [ampy](https://github.com/scientifichackers/ampy) to your computer. 1. `pip3 install esptool` @@ -81,13 +101,13 @@ Ask your Slack Admin for help getting a slack token if your slack has restrictio 9. To Disable the auto-start script if you need to do more testing. 1. `ampy -p /dev/ttyUSB0 rm main.py` -# Items on the Screen +### Items on the Screen - Bottom Left shows the wifi status and strength. An hourglass indicates the wifi is connecting. A circle with a line indicates not-connected. - Bottom Right shows the current day and time in your timezone. - Top Left shows your name from Slack. This value will be only your first name, and a max of 7 characters only when a status is set. When your status is clear, your entire first name (within the max of 16 characters) is displayed. - Top Right shows the current slack status text when there is one. -# Send a Slack Status +### Send a Slack Status 1. Rotate the dial until the status you want is highlighted. 2. Single Press the dial. * The top should show a `Sending...` @@ -95,17 +115,17 @@ Ask your Slack Admin for help getting a slack token if your slack has restrictio * ERROR simply means.. well. .something went wrong. Try again. -# Send an Expiring status +### Send an Expiring status 1. Rotate the dial until the desired status is highlighted. 2. Double-Press (quickly) the rotary dial to enter `Expire In` selection screen. 3. Rotate the dial to set the minutes to expire. The default minutes is pulled from the `Default Expire In Minutes` field on the webconfig. 4. Press the rotary dial once to send the status. 5. The expiry minutes will be now set as the new default for that status, until the next reboot. The expiry value is not set into your `config.json` file. -# View WiFi Info +### View WiFi Info 1. Long Press the rotary dial to switch back-and-forth between the main status selection screen, and the WiFi Info Screen. -# Setup your `config.json` file with new status values. +### Setup your `config.json` file with new status values. Each entry in the `status_list` within yoru config.json file is made up of: - `status`: The text to send. Can't be empty. Except if you want to use a `clear` status. - `emoji`: The slack emoji code you want. Can be empty. diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..76a63db --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,19 @@ +# Troubleshooting + +## MacOS Users + +Finding the `/dev` mount for the USB device can be a little tricky, so here are the steps to find them. + +In a new terminal, run the command `system_profiler SPUSBDataType`. +This will spit out all of the connected USB devices. +You should see something that looks like this. Pay close attention to the serial number. + +![Screen Shot 2022-09-02 at 1 25 49 PM](https://user-images.githubusercontent.com/2481437/188215356-4bd5acdb-274d-4a63-a3b5-6d3a60ab47d8.png) + +Run the command `ls /dev/ | grep tty | grep ` eg: `ls /dev/ | grep tty | grep 024A9AC9` + +You should see a list of the devices: + +![Screen Shot 2022-09-02 at 1 31 45 PM](https://user-images.githubusercontent.com/2481437/188216236-15b09861-8e32-465c-a354-d4b4c77c3ccc.png) + +When using the commands that reference a `/dev/` resource, use the value like `/dev/tty.usbserial-024A9AC9` diff --git a/config.json.example b/config.json.example new file mode 100644 index 0000000..213e7e9 --- /dev/null +++ b/config.json.example @@ -0,0 +1,75 @@ +{ + "wifi_ssid": "", + "wifi_password": "", + "slack_token": "xoxp-", + "timezone": "America/Central", + "enable_telnet": false, + "status_list": [ + { + "display": "Clear", + "status": "", + "emoji": "", + "expiry": 0 + }, + { + "status": "Available", + "emoji": ":here:", + "expiry": 0 + }, + { + "status": "Coffee", + "emoji": ":coffee:", + "expiry": 15 + }, + { + "status": "Lunch", + "emoji": ":hamburger:", + "expiry": 60 + }, + { + "status": "Meeting", + "emoji": ":calendar:", + "expiry": 30 + }, + { + "status": "OOO", + "emoji": ":car:", + "expiry": 0 + }, + { + "status": "Offline", + "emoji": ":no_entry:", + "expiry": 0 + }, + { + "status": "Walk Dog", + "emoji": ":service_dog:", + "expiry": 30 + }, + { + "status": "On a Break", + "emoji": ":hourglass:", + "expiry": 15 + }, + { + "status": "Very Busy", + "emoji": ":persevere:", + "expiry": 120 + }, + { + "status": "Doctor", + "emoji": ":stethoscope:", + "expiry": 90 + }, + { + "status": "PTO", + "emoji": ":palm_tree:", + "expiry": 0 + }, + { + "status": "Sleeping", + "emoji": ":sleeping:", + "expiry": 0 + } + ] +} diff --git a/esp32.bin b/esp32.bin new file mode 100644 index 0000000..72d5846 Binary files /dev/null and b/esp32.bin differ diff --git a/slacker b/slacker new file mode 100755 index 0000000..1ef237b --- /dev/null +++ b/slacker @@ -0,0 +1,480 @@ +#!/usr/bin/env bash +# This script was generated by bashly 0.8.3 (https://bashly.dannyb.co) +# Modifying it manually is not recommended + +# :wrapper.bash3_bouncer +if [[ "${BASH_VERSINFO:-0}" -lt 4 ]]; then + printf "bash version 4 or higher is required\n" + exit 1 +fi + +# :command.master_script + +# :command.version_command +version_command() { + echo "$version" +} + +# :command.usage +slacker_usage() { + if [[ -n $long_usage ]]; then + printf "slacker - EasySlackStatus quick installer\n" + echo + + else + printf "slacker - EasySlackStatus quick installer\n" + echo + + fi + + printf "Usage:\n" + printf " slacker [command]\n" + printf " slacker [command] --help | -h\n" + printf " slacker --version | -v\n" + echo + # :command.usage_commands + printf "Commands:\n" + echo " flash Flash the device" + echo " bootstrap Installs needed files on the device and tests the setup" + echo + + # :command.long_usage + if [[ -n $long_usage ]]; then + printf "Options:\n" + + # :command.usage_fixed_flags + echo " --help, -h" + printf " Show this help\n" + echo + echo " --version, -v" + printf " Show version number\n" + echo + + fi +} + +# :command.usage +slacker_flash_usage() { + if [[ -n $long_usage ]]; then + printf "slacker flash - Flash the device\n" + echo + + else + printf "slacker flash - Flash the device\n" + echo + + fi + + printf "Alias: f\n" + echo + + printf "Usage:\n" + printf " slacker flash DEVICE\n" + printf " slacker flash --help | -h\n" + echo + + # :command.long_usage + if [[ -n $long_usage ]]; then + printf "Options:\n" + + # :command.usage_fixed_flags + echo " --help, -h" + printf " Show this help\n" + echo + + # :command.usage_args + printf "Arguments:\n" + + # :argument.usage + echo " DEVICE" + printf " The /dev mount point where the device is mounted\n" + echo + + # :command.usage_examples + printf "Examples:\n" + printf " slacker flash /dev/ttyUSB0\n" + printf " slacker f /dev/ttyUSB0\n" + echo + + fi +} + +# :command.usage +slacker_bootstrap_usage() { + if [[ -n $long_usage ]]; then + printf "slacker bootstrap - Installs needed files on the device and tests the setup\n" + echo + + else + printf "slacker bootstrap - Installs needed files on the device and tests the setup\n" + echo + + fi + + printf "Alias: b\n" + echo + + printf "Usage:\n" + printf " slacker bootstrap DEVICE [options]\n" + printf " slacker bootstrap --help | -h\n" + echo + + # :command.long_usage + if [[ -n $long_usage ]]; then + printf "Options:\n" + + # :command.usage_fixed_flags + echo " --help, -h" + printf " Show this help\n" + echo + + # :command.usage_flags + # :flag.usage + echo " --force, -f" + printf " Force installing auto-start script\n" + echo + + # :command.usage_args + printf "Arguments:\n" + + # :argument.usage + echo " DEVICE" + printf " The /dev mount point where the device is mounted\n" + echo + + # :command.usage_examples + printf "Examples:\n" + printf " slacker bootstrap /dev/ttyUSB0\n" + printf " slacker b /dev/ttyUSB0\n" + printf " slacker bootstrap --force /dev/ttyUSB0\n" + printf " slacker b --force /dev/ttyUSB0\n" + echo + + fi +} + +# :command.normalize_input +normalize_input() { + local arg flags + + while [[ $# -gt 0 ]]; do + arg="$1" + if [[ $arg =~ ^(--[a-zA-Z0-9_\-]+)=(.+)$ ]]; then + input+=("${BASH_REMATCH[1]}") + input+=("${BASH_REMATCH[2]}") + elif [[ $arg =~ ^(-[a-zA-Z0-9])=(.+)$ ]]; then + input+=("${BASH_REMATCH[1]}") + input+=("${BASH_REMATCH[2]}") + elif [[ $arg =~ ^-([a-zA-Z0-9][a-zA-Z0-9]+)$ ]]; then + flags="${BASH_REMATCH[1]}" + for (( i=0 ; i < ${#flags} ; i++ )); do + input+=("-${flags:i:1}") + done + else + input+=("$arg") + fi + + shift + done +} +# :command.inspect_args +inspect_args() { + readarray -t sorted_keys < <(printf '%s\n' "${!args[@]}" | sort) + if (( ${#args[@]} )); then + echo args: + for k in "${sorted_keys[@]}"; do echo "- \${args[$k]} = ${args[$k]}"; done + else + echo args: none + fi + + if (( ${#other_args[@]} )); then + echo + echo other_args: + echo "- \${other_args[*]} = ${other_args[*]}" + for i in "${!other_args[@]}"; do + echo "- \${other_args[$i]} = ${other_args[$i]}" + done + fi +} + +# :command.command_functions +# :command.function +slacker_flash_command() { + # src/flash_command.sh + latest_esp32="https://micropython.org/resources/firmware/esp32-20220618-v1.19.1.bin" + device=${args[device]} + force=${args[--force]} + + if ! [ -z "$force" ]; then + echo "downloading $latest_esp32" + curl -o esp32.bin $latest_esp32 + echo "Flashing on micropython to $device" + esptool.py --chip esp32 --port "$device" --baud 460800 write_flash -z 0x1000 esp32.bin + else + echo "Flashing $device with --force" + echo "Running erase_flash on $device" + esptool.py --chip esp32 --port "$device" erase_flash + echo "downloading $latest_esp32" + curl -o esp32.bin $latest_esp32 + echo "Flashing on micropython to $device" + esptool.py --chip esp32 --port "$device" --baud 460800 write_flash -z 0x1000 esp32.bin + fi + +} + +# :command.function +slacker_bootstrap_command() { + # src/bootstrap_command.sh + device=${args[device]} + force=${args[--force]} + + if ! [ -z "$device" ]; then + echo "Pushing ess folder to device '${device}' (Might take a while)" + ampy -p $device put ess + echo "Pushing lib folder to device '${device}'" + ampy -p $device put lib + echo "Pushing easyslack.py to device '${device}'" + ampy -p $device put easyslack.py + echo "Pushing config.json to device '${device}'" + ampy -p $device put config.json + + echo "Testing configuration..." + if ampy -p $device run test.py; then + echo "Test successful. Installing auto-start script" + ampy -p $device put main.py + else + echo "Test failed." + if ! [ -z "$force" ]; then + echo "--force is used. Pushing auto-start anyway..." + ampy -p $device put main.py + echo "Done." + fi + fi + else + echo "Error: missing device" + fi + +} + +# :command.parse_requirements +parse_requirements() { + # :command.fixed_flags_filter + case "${1:-}" in + --version | -v ) + version_command + exit + ;; + + --help | -h ) + long_usage=yes + slacker_usage + exit + ;; + + esac + + # :command.command_filter + action=${1:-} + + case $action in + -* ) + ;; + + flash | f ) + action="flash" + shift + slacker_flash_parse_requirements "$@" + shift $# + ;; + + bootstrap | b ) + action="bootstrap" + shift + slacker_bootstrap_parse_requirements "$@" + shift $# + ;; + + # :command.command_fallback + "" ) + slacker_usage + exit 1 + ;; + + * ) + printf "invalid command: %s\n" "$action" + exit 1 + ;; + + esac + + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -?* ) + printf "invalid option: %s\n" "$key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + # :command.parse_requirements_case_simple + printf "invalid argument: %s\n" "$key" + exit 1 + + ;; + + esac + done + +} + +# :command.parse_requirements +slacker_flash_parse_requirements() { + # :command.fixed_flags_filter + case "${1:-}" in + --help | -h ) + long_usage=yes + slacker_flash_usage + exit + ;; + + esac + + # :command.command_filter + action="flash" + + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -?* ) + printf "invalid option: %s\n" "$key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + # :command.parse_requirements_case_simple + if [[ -z ${args[device]+x} ]]; then + + args[device]=$1 + shift + else + printf "invalid argument: %s\n" "$key" + exit 1 + fi + + ;; + + esac + done + + # :command.required_args_filter + if [[ -z ${args[device]+x} ]]; then + printf "missing required argument: DEVICE\nusage: slacker flash DEVICE\n" + exit 1 + fi + +} + +# :command.parse_requirements +slacker_bootstrap_parse_requirements() { + # :command.fixed_flags_filter + case "${1:-}" in + --help | -h ) + long_usage=yes + slacker_bootstrap_usage + exit + ;; + + esac + + # :command.command_filter + action="bootstrap" + + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + # :flag.case + --force | -f ) + + # :flag.case_no_arg + args[--force]=1 + shift + ;; + + -?* ) + printf "invalid option: %s\n" "$key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + # :command.parse_requirements_case_simple + if [[ -z ${args[device]+x} ]]; then + + args[device]=$1 + shift + else + printf "invalid argument: %s\n" "$key" + exit 1 + fi + + ;; + + esac + done + + # :command.required_args_filter + if [[ -z ${args[device]+x} ]]; then + printf "missing required argument: DEVICE\nusage: slacker bootstrap DEVICE [options]\n" + exit 1 + fi + +} + +# :command.initialize +initialize() { + version="0.1.0" + long_usage='' + set -e + + # src/initialize.sh + +} + +# :command.run +run() { + declare -A args=() + declare -a other_args=() + declare -a input=() + normalize_input "$@" + parse_requirements "${input[@]}" + + if [[ $action == "flash" ]]; then + if [[ ${args[--help]:-} ]]; then + long_usage=yes + slacker_flash_usage + else + slacker_flash_command + fi + + elif [[ $action == "bootstrap" ]]; then + if [[ ${args[--help]:-} ]]; then + long_usage=yes + slacker_bootstrap_usage + else + slacker_bootstrap_command + fi + + elif [[ $action == "root" ]]; then + root_command + fi +} + +initialize +run "$@" diff --git a/src/bashly.yml b/src/bashly.yml new file mode 100644 index 0000000..cce2f17 --- /dev/null +++ b/src/bashly.yml @@ -0,0 +1,35 @@ +name: slacker +help: EasySlackStatus quick installer +version: 0.1.0 + +commands: +- name: flash + alias: f + help: Flash the device + args: + - name: device + required: true + help: The /dev mount point where the device is mounted + + examples: + - slacker flash /dev/ttyUSB0 + - slacker f /dev/ttyUSB0 + +- name: bootstrap + alias: b + help: Installs needed files on the device and tests the setup + args: + - name: device + required: true + help: The /dev mount point where the device is mounted + + flags: + - long: --force + short: -f + help: Force installing auto-start script + + examples: + - slacker bootstrap /dev/ttyUSB0 + - slacker b /dev/ttyUSB0 + - slacker bootstrap --force /dev/ttyUSB0 + - slacker b --force /dev/ttyUSB0 diff --git a/src/bootstrap_command.sh b/src/bootstrap_command.sh new file mode 100644 index 0000000..2076da5 --- /dev/null +++ b/src/bootstrap_command.sh @@ -0,0 +1,28 @@ +device=${args[device]} +force=${args[--force]} + +if ! [ -z "$device" ]; then + echo "Pushing ess folder to device '${device}' (Might take a while)" + ampy -p $device put ess + echo "Pushing lib folder to device '${device}'" + ampy -p $device put lib + echo "Pushing easyslack.py to device '${device}'" + ampy -p $device put easyslack.py + echo "Pushing config.json to device '${device}'" + ampy -p $device put config.json + + echo "Testing configuration..." + if ampy -p $device run test.py; then + echo "Test successful. Installing auto-start script" + ampy -p $device put main.py + else + echo "Test failed." + if ! [ -z "$force" ]; then + echo "--force is used. Pushing auto-start anyway..." + ampy -p $device put main.py + echo "Done." + fi + fi +else + echo "Error: missing device" +fi diff --git a/src/config.json.example b/src/config.json.example new file mode 100644 index 0000000..213e7e9 --- /dev/null +++ b/src/config.json.example @@ -0,0 +1,75 @@ +{ + "wifi_ssid": "", + "wifi_password": "", + "slack_token": "xoxp-", + "timezone": "America/Central", + "enable_telnet": false, + "status_list": [ + { + "display": "Clear", + "status": "", + "emoji": "", + "expiry": 0 + }, + { + "status": "Available", + "emoji": ":here:", + "expiry": 0 + }, + { + "status": "Coffee", + "emoji": ":coffee:", + "expiry": 15 + }, + { + "status": "Lunch", + "emoji": ":hamburger:", + "expiry": 60 + }, + { + "status": "Meeting", + "emoji": ":calendar:", + "expiry": 30 + }, + { + "status": "OOO", + "emoji": ":car:", + "expiry": 0 + }, + { + "status": "Offline", + "emoji": ":no_entry:", + "expiry": 0 + }, + { + "status": "Walk Dog", + "emoji": ":service_dog:", + "expiry": 30 + }, + { + "status": "On a Break", + "emoji": ":hourglass:", + "expiry": 15 + }, + { + "status": "Very Busy", + "emoji": ":persevere:", + "expiry": 120 + }, + { + "status": "Doctor", + "emoji": ":stethoscope:", + "expiry": 90 + }, + { + "status": "PTO", + "emoji": ":palm_tree:", + "expiry": 0 + }, + { + "status": "Sleeping", + "emoji": ":sleeping:", + "expiry": 0 + } + ] +} diff --git a/src/flash_command.sh b/src/flash_command.sh new file mode 100644 index 0000000..c075020 --- /dev/null +++ b/src/flash_command.sh @@ -0,0 +1,18 @@ +latest_esp32="https://micropython.org/resources/firmware/esp32-20220618-v1.19.1.bin" +device=${args[device]} +force=${args[--force]} + +if ! [ -z "$force" ]; then + echo "downloading $latest_esp32" + curl -o esp32.bin $latest_esp32 + echo "Flashing on micropython to $device" + esptool.py --chip esp32 --port "$device" --baud 460800 write_flash -z 0x1000 esp32.bin +else + echo "Flashing $device with --force" + echo "Running erase_flash on $device" + esptool.py --chip esp32 --port "$device" erase_flash + echo "downloading $latest_esp32" + curl -o esp32.bin $latest_esp32 + echo "Flashing on micropython to $device" + esptool.py --chip esp32 --port "$device" --baud 460800 write_flash -z 0x1000 esp32.bin +fi diff --git a/src/initialize.sh b/src/initialize.sh new file mode 100644 index 0000000..79f6b50 --- /dev/null +++ b/src/initialize.sh @@ -0,0 +1,11 @@ +## Code here runs inside the initialize() function +## Use it for anything that you need to run before any other function, like +## setting environment vairables: +## CONFIG_FILE=settings.ini +## +## Feel free to empty (but not delete) this file. + +FILE=/etc/resolv.conf +if [ -f "$FILE" ]; then + echo "$FILE exists." +fi diff --git a/src/root_command.sh b/src/root_command.sh new file mode 100644 index 0000000..91e6430 --- /dev/null +++ b/src/root_command.sh @@ -0,0 +1,3 @@ +echo "# this file is located in 'src/root_command.sh'" +echo "# you can edit it freely and regenerate (it will not be overwritten)" +inspect_args