Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ matrix_patterns
build
dist
.env
weather.py
config-local.yaml
__pycache__/
4 changes: 2 additions & 2 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ quadrants:
scope: panel
args:
file: zigzag.json
path: snapshot_files/left
path: snapshot_files
panel: left
top-right:
- app:
Expand Down Expand Up @@ -67,7 +67,7 @@ quadrants:
scope: panel
args:
file: every-third-row.json
path: snapshot_files/right
path: snapshot_files
panel: right
bottom-right:
- app:
Expand Down
18 changes: 11 additions & 7 deletions led_system_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,16 @@ def find_keyboard_device():
log.warning(f"Warning: Could not auto-detect keyboard device: {e}")
return None

def get_config(config_file):
config_file = os.environ.get("CONFIG_FILE", None) or config_file
def get_config():
current_dir = os.path.dirname(os.path.abspath(__file__))
config_file = os.path.join(current_dir, config_file)
config_file_name = 'config-local.yaml'
config_file = os.path.join(current_dir, config_file_name)
if os.path.exists(config_file):
log.debug(f"using local config file {config_file}")
else:
config_file_name = 'config.yaml'
config_file = os.path.join(current_dir, config_file_name)
log.debug(f"Using default config file {config_file}")
with open(config_file, 'r') as f:
return safe_load(f)

Expand Down Expand Up @@ -134,7 +140,7 @@ def app(args, base_apps, plugin_apps):
################################################################################
### Parse config file to enable control of apps by quadrant and by time slice ##
################################################################################
config = get_config(args.config_file)
config = get_config()
duration = config['duration'] #Default config to be applied if not set in an app
quads = config['quadrants']
top_left, bottom_left, top_right, bottom_right, = \
Expand Down Expand Up @@ -469,14 +475,12 @@ def main(args):
mode_group.add_argument("--help", "-h", action="help",
help="Show this help message and exit")

mode_group.add_argument("-config-file", "-cf", type=str, default="config.yaml", help="File that specifies which apps to run in each panel quadrant")
mode_group.add_argument("--no-key-listener", "-nkl", action="store_true", help="Do not listen for key presses")
mode_group.add_argument("--disable-plugins", "-dp", action="store_true", help="Do not load any plugin code")
mode_group.add_argument("--list-apps", "-la", action="store_true", help="List the installed apps, and exit")

args = parser.parse_args()
if args.no_key_listener: print("Key listener disabled")
log.info(f"Using config file {args.config_file}")
app(args, base_apps, plugin_apps)

if __name__ == "__main__":
Expand All @@ -491,6 +495,6 @@ def main(args):
logging.basicConfig(
level=level,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
)
main(sys.argv)

6 changes: 5 additions & 1 deletion plugins/time_weather_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

@cache
# Cache results so we avoid exceeding the API rate limit
# No need to invalidate cache since location per given zip is fixed
def get_location_by_zip(zip_info, weather_api_key):
zip_code, country = zip_info
result = requests.get(f"{OPENWEATHER_HOST}/geo/1.0/zip?zip={zip_code},{country}&appid={weather_api_key}").json()
Expand All @@ -38,6 +39,8 @@ def get_location_by_zip(zip_info, weather_api_key):
return loc

@cache
# Cache results so we avoid exceeding the API rate limit
# No need to invalidate cache since location per given IP address is generally fixed
def get_location_by_ip(ip_api_key, ip):
client = IPLocateClient(api_key=ip_api_key)
result = client.lookup(ip)
Expand Down Expand Up @@ -173,7 +176,8 @@ def wrapper():
Timer(interval, wrapper).start()


# Get fresh weather data every 30 secs
# Get fresh weather data every 30 secs. Two calls per minute will be well within the openweather API
# free tierlimit of 60 calls/minute and 1,000,000 calls/month
repeat_function(30, weather_monitor.get.cache_clear)

draw_chars = getattr(drawing, 'draw_chars')
Expand Down
14 changes: 7 additions & 7 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
pyserial
numpy
psutil
evdev
pynput
pyyaml
pyserial >= 3.5
numpy >= 2.4.1
psutil >= 7.2.1
evdev >= 1.9.2
pynput >= 1.8.1
pyyaml >= 6.0.3
pyinstaller
requests >= 2.32.5
python-iplocate >= 1.0.0
dotenv >= 0.9.9
python-dotenv >= 1.2.1
115 changes: 115 additions & 0 deletions weather.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import os, sys
import requests
from zoneinfo import ZoneInfo
from iplocate import IPLocateClient
from datetime import datetime, timedelta

OPENWEATHER_HOST = 'https://api.openweathermap.org'
IPIFY_HOST = 'https://api.ipify.org'

TEST_CONFIG = {
'zip_info': ('10001', 'US'), # New York, NY
'lat_lon': (40.7128, -74.0060), # New York, NY
'units': 'metric',
'forecast_day': 1, # 1=tomorrow, 2=day after tomorrow, etc.
'forecast_hour': 12, # Hour of the day for forecast (0-23)
}

def get_weather(forecast):
zip_info = TEST_CONFIG['zip_info']
lat_lon =TEST_CONFIG['lat_lon']
units =TEST_CONFIG['units']
forecast_day = TEST_CONFIG['forecast_day']
forecast_hour =TEST_CONFIG['forecast_hour']
mist_like = ['Mist', 'Fog', 'Dust', 'Haze', 'Smoke', 'Squall', 'Ash', 'Sand', 'Tornado']

ip = requests.get(IPIFY_HOST).text
ip_api_key = os.environ.get("IP_LOCATE_API_KEY", None)
weather_api_key = os.environ.get("OPENWEATHER_API_KEY", None)

try:
if lat_lon:
loc = lat_lon
elif zip_info:
loc = get_location_by_zip(zip_info, weather_api_key)
elif ip_api_key:
loc = get_location_by_ip(ip_api_key, ip)
else:
raise Exception("No location method configured")

temp_symbol = 'degC'if units == 'metric' else 'degF' if units == 'imperial' else 'degK'

if forecast:
forecast = requests.get(f"{OPENWEATHER_HOST}/data/2.5/forecast?lat={loc[0]}&lon={loc[1]}&appid={weather_api_key}&units={units}").json()
fc = forecast['list'][0]
temp = fc['main']['temp']
cond = fc['weather'][0]['main']
target_date = (datetime.now(ZoneInfo('GMT')).date() + timedelta(days=forecast_day))
for fc in forecast['list']:
dt = datetime.strptime(fc['dt_txt'], '%Y-%m-%d %H:%M:%S')
if dt.date() == target_date and dt.hour >= forecast_hour:
temp = fc['main']['temp']
cond = fc['weather'][0]['main']
if cond in mist_like: cond = 'mist-like'
_forecast = [temp, temp_symbol, cond]
print(f"Forecast weather for time {fc['dt_txt']}")
return _forecast
temp = forecast['list'][-1]['main']['temp']
cond = forecast['list'][-1]['weather'][0]['main']
if cond in mist_like: cond = 'mist-like'
_forecast = [temp, temp_symbol, cond]
print(f"Forecast weather for time {fc['dt_txt']}")
return _forecast
else:
current = requests.get(f"{OPENWEATHER_HOST}/data/2.5/weather?lat={loc[0]}&lon={loc[1]}&appid={weather_api_key}&units={units}").json()

_current = [current['main']['temp'], temp_symbol, current['weather'][0]['main']]
if _current[2] in mist_like: _current[2] = 'mist-like'
return _current
except Exception as e:
print(f"Error getting weather: {e}")
return None



def get_time():
"""
Return the current time as a tuple (HHMM, is_pm). is_pm is False if 24-hour format is used.
Represent in local time or GMT, and in 24-hour or 12-hour format, based on configuration.
"""
from datetime import datetime
# TODOD get from config file
format_24_hour = False
use_gmt = False
now = datetime.now(ZoneInfo("GMT")) if use_gmt else datetime.now().astimezone()
if format_24_hour :
return (now.strftime("%H%M"), False)
else:
return (now.strftime("%I%M"),now.strftime("%p") == 'PM' )

def get_location_by_zip(zip_info, weather_api_key):
zip_code, country = zip_info
result = requests.get(f"http://api.openweathermap.org/geo/1.0/zip?zip={zip_code},{country}&appid={weather_api_key}").json()
lat = result['lat']
lon = result['lon']
loc = lat, lon
return loc

def get_location_by_ip(ip_api_key, ip):
client = IPLocateClient(api_key=ip_api_key)
result = client.lookup(ip)
if result.country: print(f"Country: {result.country}")
if result.city: print(f"City: {result.city}")
if result.privacy.is_vpn: print(f"VPN: {result.privacy.is_vpn}")
if result.privacy.is_proxy: print(f"Proxy: {result.privacy.is_proxy}")
loc = result.latitude, result.longitude
return loc

if __name__ == "__main__":
from dotenv import load_dotenv
load_dotenv()
current_time = get_time()
print(f"Time: {current_time[0]} {'PM' if current_time[1] else 'AM/24-hour'}")
fc = get_weather(forecast=True)
current = get_weather(forecast=False)
print(f"Weather: Current: {current}, Forecast: {fc}")