├── .gitignore ├── NOTES ├── display-weather-usb-power.sh ├── display-weather.sh ├── COPYING ├── sci.py ├── aqi.py ├── weather_api.py ├── README.md ├── weather_script.py ├── weather-script-preprocess-landscape.svg ├── weather-script-preprocess-landscape-right.svg └── weather-script-preprocess.svg /.gitignore: -------------------------------------------------------------------------------- 1 | weather-script-output.* 2 | *.pyc 3 | -------------------------------------------------------------------------------- /NOTES: -------------------------------------------------------------------------------- 1 | for x in *.svg; do 2 | cat $x | perl -pe 's/path/path id="'`basename $x .svg`'"/' | grep path; 3 | done > paste.svg 4 | -------------------------------------------------------------------------------- /display-weather-usb-power.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | FOLDER="/mnt/base-us/weather" 4 | FILE="weather.png" 5 | URL="http://cn-vpn.grisge.info/weather/weather.png" 6 | 7 | cd $FOLDER 8 | 9 | ifconfig wlan0 >/dev/null 2>&1 10 | if [ $? -ne 0 ];then 11 | exit 12 | fi 13 | 14 | rm $FOLDER/$FILE 15 | 16 | wget $URL -O $FOLDER/$FILE 17 | 18 | if [ -e $FOLDER/$FILE ];then 19 | eips -f -g $FOLDER/$FILE 20 | fi 21 | -------------------------------------------------------------------------------- /display-weather.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | FOLDER="/mnt/base-us/weather" 4 | FILE="weather.png" 5 | URL="http://cn-vpn.grisge.info/weather/weather.png" 6 | 7 | cd $FOLDER 8 | 9 | if [ $(gasgauge-info -s 2>/dev/null | sed -ne 's/\([0-9]\+\).*/\1/p') -le 10 ]; 10 | then 11 | eips -f "Low battery, charge please" 12 | exit; 13 | fi 14 | 15 | # Wake Up 16 | lipc-set-prop com.lab126.powerd wakeUp 1 17 | 18 | sleep 10 19 | 20 | ifconfig wlan0 >/dev/null 2>&1 21 | if [ $? -ne 0 ];then 22 | exit 23 | fi 24 | 25 | rm $FOLDER/$FILE 26 | 27 | # Lock screen 28 | /usr/bin/powerd_test -p 29 | 30 | sleep 10 31 | 32 | wget $URL -O $FOLDER/$FILE 33 | 34 | if [ -e $FOLDER/$FILE ];then 35 | eips -f -g $FOLDER/$FILE 36 | fi 37 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Gris Ge 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /sci.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | # Copyright (C) 2015 Gris Ge 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 3 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; If not, see . 15 | # 16 | # Author: Gris Ge 17 | import urllib2 18 | 19 | _SCI_URL = 'http://hq.sinajs.cn/list=sh000001' 20 | 21 | def _fetch_html(url): 22 | request = urllib2.Request(url) 23 | return urllib2.urlopen(request).read() 24 | 25 | def sci_get(): 26 | ''' 27 | Return current Shanghai Composite Index and change percentage in list. 28 | Data is from parsed from sina 29 | ''' 30 | html_content = _fetch_html(_SCI_URL) 31 | tmp_ar = html_content.split(',') 32 | cur = float(tmp_ar[3]) 33 | pre = float(tmp_ar[2]) 34 | sci = "%.2f" % cur 35 | sci_chg = "%.2f%%" % float( (cur - pre) / pre * 100) 36 | return (sci, sci_chg) 37 | -------------------------------------------------------------------------------- /aqi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | # Copyright (C) 2015 Gris Ge 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 3 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; If not, see . 15 | # 16 | # Author: Gris Ge 17 | import urllib2 18 | import re 19 | 20 | _AQI_URL = 'http://aqicn.org/city//m' 21 | _AQI_DIV_NAME = 'aqi' 22 | _AQI_REGEX = re.compile('summary.+\+([0-9]+)\+') 23 | _HTTP_HEADER = { 24 | "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:42.0) " 25 | "Gecko/20100101 Firefox/42.0", 26 | "Accept": "text/html,application/xhtml+xml,application/xml;" 27 | "q=0.9,*/*;q=0.8", 28 | } 29 | 30 | 31 | def _fetch_html(url): 32 | request = urllib2.Request(url, headers=_HTTP_HEADER) 33 | return urllib2.urlopen(request).read() 34 | 35 | 36 | def aqi_get(city_name): 37 | ''' 38 | Return integer for AQI of given city and raise error if failure. 39 | Data is from parsed from aqicn.org webpage. 40 | ''' 41 | url = _AQI_URL.replace('', city_name) 42 | html_content = _fetch_html(url) 43 | for line in html_content.split("\n"): 44 | match = _AQI_REGEX.search(line) 45 | if match: 46 | return int(match.group(1)) 47 | return -1 48 | -------------------------------------------------------------------------------- /weather_api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | # Copyright (C) 2015 Gris Ge 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 3 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; If not, see . 15 | # 16 | # Author: Gris Ge 17 | import datetime 18 | import json 19 | from urllib2 import urlopen 20 | 21 | 22 | def _fetch_json(url): 23 | json_str = urlopen(url).read() 24 | return json.loads(json_str) 25 | 26 | 27 | def _parse_forecast(data_json): 28 | """ 29 | return [WeatherData] 30 | """ 31 | tmp_list = [] 32 | for data in data_json["forecast"]["simpleforecast"]["forecastday"]: 33 | tmp_list.append(WeatherData(data["icon"], data["high"]["celsius"], 34 | data["low"]["celsius"])) 35 | return tmp_list 36 | 37 | 38 | class WeatherData(object): 39 | def __init__(self, condition, temp_max, temp_min): 40 | self.condition = condition 41 | self.temp_max = temp_max 42 | self.temp_min = temp_min 43 | 44 | 45 | class WeatherAPI(object): 46 | 47 | _BASE_API_URL = "http://api.wunderground.com/api/" 48 | 49 | def __init__(self, api_key): 50 | self._api_key = api_key 51 | self._today = datetime.date.today() 52 | 53 | def set_lat_lon(self, lat, lon): 54 | forecast_json = _fetch_json( 55 | "%s/%s/forecast/q/%s,%s.json" % 56 | (WeatherAPI._BASE_API_URL, self._api_key, lat, lon)) 57 | 58 | self._data = _parse_forecast(forecast_json) 59 | 60 | def set_airport_code(self, airport_code): 61 | forecast_json = _fetch_json( 62 | "%s/%s/forecast/q/%s.json" % 63 | (WeatherAPI._BASE_API_URL, self._api_key, airport_code)) 64 | 65 | self._data = _parse_forecast(forecast_json) 66 | 67 | 68 | def temp_max(self, day): 69 | """ 70 | Input day as integer, 0 means today, 1 means tomorrow, max is 3. 71 | """ 72 | if day > 3: 73 | raise Exception("Invalid day, should less or equal to 3") 74 | 75 | return self._data[day].temp_max 76 | 77 | def temp_min(self, day): 78 | if day > 3: 79 | raise Exception("Invalid day, should less or equal to 3") 80 | return self._data[day].temp_min 81 | 82 | def condition(self, day): 83 | if day > 3: 84 | raise Exception("Invalid day, should less or equal to 3") 85 | return self._data[day].condition 86 | 87 | @property 88 | def today(self): 89 | """ 90 | Return a object of datetime.date 91 | """ 92 | return self._today 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Kindle Weather 2 | 3 | # STOP, THIS WILL BRICK YOUR KINDLE, ONLY FOR LINUX GEEK. 4 | # ~~UNLESS YOU KNOW ALL THE FOLLOWING LINUX COMMANDS, GO AWAY, THIS IS NOT FOR YOU.~~ 5 | 6 | # Big Thanks to Matthew Petroff for his sharing. 7 | 8 | This project is fully based on the project shared by Matthew Petroff: 9 | http://mpetroff.net/2012/09/kindle-weather-display/ 10 | 11 | Without his kind share of this great work, my kindle 3 might still sleep in the 12 | dust. 13 | 14 | The changes I made: 15 | * Use `crontab` instead of `kite` or stop `powerd` by changing the 16 | `suspend_levels`. 17 | 18 | * Reorganized the `weather_script.py` by spliting weather API out. 19 | 20 | * Change `weather-script-preprocess.svg` to fit the weather API. 21 | 22 | * Warn user when low battery(less than 10%) 23 | 24 | * I tried to change `weather-script-preprocess.svg` to use `` tag 25 | for external image which allow me to easily change the icon, but I failed to 26 | at the rsvg-convert point. So still in embedded way. 27 | 28 | # License 29 | 30 | * The code is in MIT license. 31 | * The `weather_script.py` file is based on the work of Matthew Petroff 2012, MIT 32 | license. 33 | * The `weather-script-preprocess.svg` and its embedded icon are under 34 | [CC0 Public Domain Dedication][4] license by Matthew Petroff also. I(Gris Ge) 35 | did small change to fit the [weather.com][1] API. 36 | 37 | # Pre-requirements 38 | 39 | * Kindle 3 wireless (Only tested on this one). 40 | * The API key from [weather.com][1] 41 | * [Kindle Jailbreak][2] and [USB network][3]. 42 | * A kindle WIFI accessible Linux server with `python2 pngcrush librsvg2-tools`. 43 | 44 | # HowTo 45 | ## Kindle Side 46 | 47 | ### Jailbreak your Kindle and install USB network. 48 | * The above wiki pages works on me. The tips would be disable wifi at the 49 | initial login, then change root password, change ssh config to allow wifi 50 | login, set as auto start, reboot. 51 | 52 | ### Install weather display script 53 | * Edit the `URL` in `display-weather.sh` by pointing to your own http server. 54 | * Create folder `mkdir /mnt/base-us/weather`. 55 | * Make sure kindle root mount point is writeable `mntroot rw` 56 | * `scp display-weather.sh root@k3w:/usr/bin/` 57 | * Make sure `display-weather.sh` is executable via `chmod +x` on kindle. 58 | 59 | ### Setup crontab 60 | * Execute `mntroot rw`. 61 | * Add this line into `/etc/crontab/root` 62 | 63 | ``` 64 | 5 6-22 * * * /usr/bin/display-weather.sh 65 | ``` 66 | * Changed above line to suit your needs. 67 | 68 | ### Keep your kindle connected to a USB power during use. 69 | * I(Gris Ge) don't suggest your to hack kindle in order to stop the auto 70 | suspend, you might face batter drain. Please collect your kindle to a USB 71 | charger always. 72 | 73 | ### Reboot your kindle via `reboot` command. 74 | 75 | ## Server Side 76 | ### Install httpd daemon. 77 | * `sudo yum install httpd pngcrush librsvg2-tools python2 -y` 78 | * `sudo systemctl enable httpd` 79 | * `sudo systemctl start httpd` 80 | 81 | ### Create weather output folder with correct permission. 82 | * `sudo mkdir /var/www/html/weather` 83 | * `sudo chmod 777 /var/www/html/weather/` or use `chown`. 84 | 85 | ### Setup crontab 86 | * Create a script named as kindle_weather.sh like below: 87 | 88 | ``` 89 | export KW_INCLUDE_SCI=1 90 | # ^ Include China Shanghai Composite index. 91 | export KW_LATITUDE=30.6586 92 | export KW_LONGTITUDE=104.0647 93 | # ^ If you have airport in your city, use something like `KW_AIRPORT=CTU` 94 | # instead 95 | export KW_WEATHER_KEY="" 96 | # ^ Weather.com API key 97 | export KW_AQI_CITY="chengdu" 98 | # ^ aqicn.org city name. 99 | export KW_LANSCAPE_LEFT=1 100 | # ^ Lanscape left mode(usb port at your right side). 101 | # KW_LANSCAPE_RIGHT for usb port at your left side. 102 | export KW_OUTPUT="/var/www/html/weather/weather.png" 103 | 104 | /home/fge/Source/kindle-weather/weather_script.py 105 | ``` 106 | 107 | * Add this line to `crontab -e` to refresh weather every 30 minutes 108 | during 6AM to 10PM: 109 | 110 | ``` 111 | 0,30 6-22 * * * /home/fge/bin/kindle_weather.sh 1>/dev/null 2>/dev/null 112 | ``` 113 | 114 | ### Create initial weather PNG 115 | * Invoke `/weather_script.py `. 116 | * Check whether you can get the weather png via: 117 | `http:///weather/weather.png` 118 | 119 | # TODO 120 | * Include a TODO list or note. 121 | 122 | # Contact 123 | * https://github.com/cathay4t/kindle-weather/issues 124 | 125 | [1]: http://www.wunderground.com/weather/api/d/login.html 126 | [2]: http://wiki.mobileread.com/wiki/Kindle_Hacks_Information 127 | [3]: https://blitiri.com.ar/p/other/kindle/#usb-networking 128 | [4]: http://creativecommons.org/publicdomain/zero/1.0/ 129 | -------------------------------------------------------------------------------- /weather_script.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | # Copyright (c) 2015 Gris Ge 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included 12 | # in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | # 22 | # 23 | # Author: Gris Ge 24 | 25 | # Code was edit based on 26 | # https://github.com/mpetroff/kindle-weather-display.git 27 | # Which is also MIT license. 28 | # 29 | # Kindle Weather Display 30 | # Matthew Petroff (http://mpetroff.net/) 31 | # September 2012 32 | 33 | import codecs 34 | import datetime 35 | import os 36 | import sys 37 | 38 | from weather_api import WeatherAPI 39 | from argparse import ArgumentParser 40 | from aqi import aqi_get 41 | from sci import sci_get 42 | 43 | 44 | def _exec(cmd): 45 | rc = os.system(cmd) 46 | if (rc != 0): 47 | print("`%s` failed with error %d" % (cmd, rc)) 48 | exit(rc) 49 | 50 | CODE_FOLDER = os.path.dirname(os.path.realpath(__file__)) 51 | 52 | OUTPUT = os.environ.get("KW_OUTPUT", "/var/www/html/weather/weather.png") 53 | 54 | TMP_OUTPUT = "%s/weather.png" % CODE_FOLDER 55 | SVG_PORTRAIT_FILE = "%s/weather-script-preprocess.svg" % CODE_FOLDER 56 | SVG_LANSCAPE_LEFT = "%s/weather-script-preprocess-landscape.svg" % CODE_FOLDER 57 | SVG_LANSCAPE_RIGHT = "%s/weather-script-preprocess-landscape-right.svg" % \ 58 | CODE_FOLDER 59 | SVG_FILE = SVG_PORTRAIT_FILE 60 | SVG_OUTPUT = "%s/weather-script-output.svg" % CODE_FOLDER 61 | MAX_WEATHER_DAY_COUNT = 3 62 | INCLUDE_SCI = False 63 | 64 | if len(sys.argv) >= 5 and sys.argv[4] != '0': 65 | SVG_FILE = SVG_LANSCAPE_FILE 66 | 67 | if os.environ.get("KW_LANSCAPE_RIGHT") is not None: 68 | SVG_FILE = SVG_LANSCAPE_RIGHT 69 | elif os.environ.get("KW_LANSCAPE_LEFT") is not None: 70 | SVG_FILE = SVG_LANSCAPE_LEFT 71 | else: 72 | SVG_FILE = SVG_PORTRAIT_FILE 73 | 74 | 75 | AQI_CITY = os.environ.get("KW_AQI_CITY", None) 76 | 77 | if os.environ.get("KW_INCLUDE_SCI") is not None: 78 | INCLUDE_SCI = True 79 | 80 | WEATHER_KEY = os.environ.get("KW_WEATHER_KEY") 81 | LATITUDE = os.environ.get("KW_LATITUDE") 82 | LONGTITUDE = os.environ.get("KW_LONGTITUDE") 83 | WEATHER_AIRPORT = os.environ.get("KW_AIRPORT") 84 | 85 | if WEATHER_KEY is None: 86 | print("Need KW_WEATHER_KEY environment variables") 87 | exit(1) 88 | 89 | weather_obj = WeatherAPI(WEATHER_KEY) 90 | 91 | if WEATHER_AIRPORT: 92 | weather_obj.set_airport_code(WEATHER_AIRPORT) 93 | else: 94 | weather_obj.set_lat_lon(LATITUDE, LONGTITUDE) 95 | 96 | # Open SVG to process 97 | output = codecs.open(SVG_FILE, "r", encoding="utf-8").read() 98 | 99 | _MAP = { 100 | "$I": WeatherAPI.condition, 101 | "$H": WeatherAPI.temp_max, 102 | "$L": WeatherAPI.temp_min, 103 | } 104 | 105 | for x in _MAP.keys(): 106 | for i in range(MAX_WEATHER_DAY_COUNT + 1): 107 | output = output.replace("%s%d" % (x, i), 108 | "%s" % _MAP[x](weather_obj, i)) 109 | 110 | # Replace refresh time 111 | output = output.replace("$TIME", 112 | datetime.datetime.now().strftime("%b %d %a %H:%M")) 113 | 114 | # Updaet AQI. TODO(Gris Ge): still place holder yet. 115 | if AQI_CITY is not None: 116 | output = output.replace("$AQI", str(aqi_get(AQI_CITY))) 117 | 118 | if INCLUDE_SCI: 119 | (sci, sci_change) = sci_get() 120 | output = output.replace("$SCI", str(sci)) 121 | output = output.replace("$SCHG", str(sci_change)) 122 | else: 123 | output = output.replace("SCI: $SCI $SCHG", "") 124 | 125 | day_one = weather_obj.today 126 | 127 | # Insert days of week 128 | one_day = datetime.timedelta(days=1) 129 | days_of_week = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 130 | 131 | for i in range(MAX_WEATHER_DAY_COUNT + 1): 132 | output = output.replace("$D%s" % i, 133 | days_of_week[(day_one + i * one_day).weekday()]) 134 | 135 | # Write output 136 | codecs.open(SVG_OUTPUT, "w", encoding="utf-8").write(output) 137 | 138 | _exec("rsvg-convert --background-color=white -o %s %s" % 139 | (TMP_OUTPUT, SVG_OUTPUT)) 140 | _exec("pngcrush -c 0 -ow %s 1>/dev/null 2>&1" % TMP_OUTPUT) 141 | _exec("mv -f '%s' '%s'" % (TMP_OUTPUT, OUTPUT)) 142 | -------------------------------------------------------------------------------- /weather-script-preprocess-landscape.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | $TIME 34 | 35 | 36 | AQI: $AQI 37 | 38 | 39 | SCI: $SCI $SCHG 40 | 41 | 44 | 45 | $H0°C 46 | 47 | 48 | $L0°C 49 | 50 | 51 | 52 | 53 | $D1 54 | 55 | 58 | 59 | $H1°C 60 | 61 | 62 | $L1°C 63 | 64 | 65 | 66 | 67 | $D2 68 | 69 | 72 | 73 | $H2°C 74 | 75 | 76 | $L2°C 77 | 78 | 79 | 80 | 81 | $D3 82 | 83 | 86 | 87 | $H3°C 88 | 89 | 90 | $L3°C 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /weather-script-preprocess-landscape-right.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | $TIME 34 | 35 | 36 | AQI: $AQI 37 | 38 | 39 | SCI: $SCI $SCHG 40 | 41 | 44 | 45 | $H0°C 46 | 47 | 48 | $L0°C 49 | 50 | 51 | 52 | 53 | $D1 54 | 55 | 58 | 59 | $H1°C 60 | 61 | 62 | $L1°C 63 | 64 | 65 | 66 | 67 | $D2 68 | 69 | 72 | 73 | $H2°C 74 | 75 | 76 | $L2°C 77 | 78 | 79 | 80 | 81 | $D3 82 | 83 | 86 | 87 | $H3°C 88 | 89 | 90 | $L3°C 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /weather-script-preprocess.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 34 | 36 | 38 | 39 | 40 | 42 | $TIME 43 | 44 | 46 | High 47 | 48 | 50 | $H0 51 | 52 | 54 | °C 55 | 56 | 58 | Low 59 | 60 | 62 | $L0 63 | 64 | 66 | °C 67 | 68 | 69 | 71 | $D1 72 | 73 | 75 | High 76 | 77 | 79 | $H1 80 | 81 | 83 | °C 84 | 85 | 87 | Low 88 | 89 | 91 | $L1 92 | 93 | 95 | °C 96 | 97 | 98 | 100 | $D2 101 | 102 | 104 | High 105 | 106 | 108 | $H2 109 | 110 | 112 | °C 113 | 114 | 116 | Low 117 | 118 | 120 | $L2 121 | 122 | 124 | °C 125 | 126 | 127 | 129 | $D3 130 | 131 | 133 | High 134 | 135 | 137 | $H3 138 | 139 | 141 | °C 142 | 143 | 145 | Low 146 | 147 | 149 | $L3 150 | 151 | 153 | °C 154 | 155 | 156 | 157 | 158 | 159 | 160 | --------------------------------------------------------------------------------