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
1 change: 1 addition & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
temperature_unit: 0,
refresh_rate: 20,
weather_enabled: true,
weather_variable: 0,
vibrate_on_the_hour: false,
military_time: false,
selected_theme: 0,
Expand Down
20 changes: 19 additions & 1 deletion docs/template/ConfigView.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
{{/if}}
<g>
{{#if weather_enabled}}
<text x="177" y="123" text-anchor="middle" fill="{{ infoColor }}" style="font-family:'Nupe-CondensedRegular'; font-size:42;">b18°</text>
<text x="177" y="123" text-anchor="middle" fill="{{ infoColor }}" style="font-family:'Nupe-CondensedRegular'; font-size:42;">{{ weatherPreviewText }}</text>
{{/if}}
{{#if date_displayed}}
<text x="255" y="195.5" text-anchor="middle" fill="{{ infoColor }}" style="font-family:'Nupe-CondensedRegular'; font-size:42;">31</text>
Expand Down Expand Up @@ -107,6 +107,7 @@
<Toggle value="{{ weather_enabled }}" label="Weather"/>
{{#if weather_enabled}}
<div intro-outro='slide'>
<Tabs source="{{ weatherVariableSource }}" value="{{ weather_variable }}" header="Weather variable"/>
<Tabs source="{{ temparetureUnitSource }}" value="{{ temperature_unit }}" header="Temperature unit"/>
<Tabs source="{{ refreshRateSource }}" value="{{ refresh_rate }}" header="Refresh rate"/>
</div>
Expand Down Expand Up @@ -421,6 +422,22 @@
{ label: "40 min", value: 40 }
];
},
weatherVariableSource: function(){
return [
{ label: "Current Temp", value: 0 },
{ label: "Apparent Temp", value: 1 },
{ label: "Sunset", value: 2 }
];
},
weatherPreviewText: function(){
switch(this.get('weather_variable')){
case 2:
return this.get('military_time') ? "18:34" : "6:34";
case 1:
default:
return "18°";
}
},
infoColor: function(){
return this.color(this.get('info_color'))
},
Expand Down Expand Up @@ -565,6 +582,7 @@
time_color: this.get('time_color'),
info_color: this.get('info_color'),
weather_enabled: this.get('weather_enabled'),
weather_variable: this.get('weather_variable'),
refresh_rate: this.get('refresh_rate'),
temperature_unit: this.get('temperature_unit'),
vibrate_on_the_hour: this.get('vibrate_on_the_hour'),
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"AppKeyWeatherFailed": 14,
"AppKeyWeatherIcon": 13,
"AppKeyWeatherRequest": 15,
"AppKeyWeatherTemperature": 12
"AppKeyWeatherTemperature": 12,
"AppKeyWeatherVariable": 21
},
"projectType": "native",
"resources": {
Expand Down Expand Up @@ -87,5 +88,5 @@
}
},
"private": true,
"version": "2.7.0"
"version": "2.8.0"
}
3 changes: 2 additions & 1 deletion package.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"AppKeyWeatherFailed": 14,
"AppKeyWeatherIcon": 13,
"AppKeyWeatherRequest": 15,
"AppKeyWeatherTemperature": 12
"AppKeyWeatherTemperature": 12,
"AppKeyWeatherVariable": 21
},
"projectType": "native",
"resources": {
Expand Down
59 changes: 47 additions & 12 deletions src/c/minimalin.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ typedef enum {
AppKeyVibrateOnTheHour,
AppKeyMilitaryTime,
AppKeyHealthEnabled,
AppKeyBatteryDisplayedAt
AppKeyBatteryDisplayedAt,
AppKeyWeatherVariable
} AppKey;

typedef enum {
Expand All @@ -43,7 +44,7 @@ typedef enum {
typedef struct {
int32_t timestamp;
int8_t icon;
int8_t temperature;
int16_t value_primary;
} __attribute__((__packed__)) Weather;

typedef struct {
Expand Down Expand Up @@ -146,6 +147,12 @@ static void config_temperature_unit_updated(DictionaryIterator * iter, Tuple * t
text_block_mark_dirty(s_weather_info);
}

static void config_weather_variable_updated(DictionaryIterator * iter, Tuple * tuple){
config_set_int(s_config, ConfigKeyWeatherVariable, tuple->value->int32);
s_context.reset_weather = true;
text_block_mark_dirty(s_weather_info);
}

static void config_bluetooth_icon_updated(DictionaryIterator * iter, Tuple * tuple){
config_set_int(s_config, ConfigKeyBluetoothIcon, tuple->value->int32);
text_block_mark_dirty(s_watch_info);
Expand Down Expand Up @@ -199,11 +206,13 @@ static void js_ready_callback(DictionaryIterator * iter, Tuple * tuple){
static void weather_requested_callback(DictionaryIterator * iter, Tuple * tuple){
s_context.reset_weather = false;
const Tuple * const icon_tuple = dict_find(iter, AppKeyWeatherIcon);
const Tuple * const temp_tuple = dict_find(iter, AppKeyWeatherTemperature);
if(icon_tuple && temp_tuple){
const Tuple * const primary_tuple = dict_find(iter, AppKeyWeatherTemperature);
if(primary_tuple){
s_context.weather.timestamp = time(NULL);
s_context.weather.icon = icon_tuple->value->int8;
s_context.weather.temperature = temp_tuple->value->int8;
if(icon_tuple){
s_context.weather.icon = icon_tuple->value->int8;
}
s_context.weather.value_primary = primary_tuple->value->int16;
}
persist_write_data(PersistKeyWeather, &s_context.weather, sizeof(Weather));
text_block_mark_dirty(s_weather_info);
Expand Down Expand Up @@ -360,15 +369,40 @@ static void weather_info_update_proc(TextBlock * block){
const Context * const context = (Context *) text_block_get_context(block);
const Config * const config = context->config;
const Weather weather = context->weather;
char info_buffer[10] = {0};
char info_buffer[20] = {0};
const int timeout = (config_get_int(config, ConfigKeyRefreshRate) + 5) * 60;
const int expiration = weather.timestamp + timeout;
const bool weather_valid = time(NULL) < expiration;
if(weather_valid){
const int temp = weather.temperature;
const bool is_farhrenheit = config_get_int(config, ConfigKeyTemperatureUnit) == Fahrenheit;
const int converted_temp = is_farhrenheit ? temp * 9 / 5 + 32 : temp;
snprintf(info_buffer, sizeof(info_buffer), "%c%d°", weather.icon, converted_temp);
const char icon = weather.icon ? weather.icon : 'b';
const WeatherVariable weather_variable = config_get_int(config, ConfigKeyWeatherVariable);
switch(weather_variable){
case WeatherVariableCurrentTemperature:
case WeatherVariableApparentTemperature: {
const int temp = weather.value_primary;
const bool is_farhrenheit = config_get_int(config, ConfigKeyTemperatureUnit) == Fahrenheit;
const int converted_temp = is_farhrenheit ? temp * 9 / 5 + 32 : temp;
snprintf(info_buffer, sizeof(info_buffer), "%c%d°", icon, converted_temp);
break;
}
case WeatherVariableSunset: {
const int total_minutes = weather.value_primary;
if(total_minutes >= 0){
const int hour_24 = total_minutes / 60;
const int minute = total_minutes % 60;
if(config_get_bool(config, ConfigKeyMilitaryTime)){
snprintf(info_buffer, sizeof(info_buffer), "%c%d:%02d", icon, hour_24, minute);
}else{
const int hour_12 = hour_24 % 12 == 0 ? 12 : hour_24 % 12;
snprintf(info_buffer, sizeof(info_buffer), "%c%d:%02d", icon, hour_12, minute);
}
}
break;
}
default:
snprintf(info_buffer, sizeof(info_buffer), "%c%d°", icon, weather.value_primary);
break;
}
}
const GColor info_color = config_get_color(s_config, ConfigKeyInfoColor);
text_block_set_text(block, info_buffer, info_color);
Expand Down Expand Up @@ -649,14 +683,15 @@ static void init() {
{ AppKeyBluetoothIcon, config_bluetooth_icon_updated },
{ AppKeyRefreshRate, config_refresh_rate_updated },
{ AppKeyTemperatureUnit, config_temperature_unit_updated },
{ AppKeyWeatherVariable, config_weather_variable_updated },
{ AppKeyWeatherEnabled, config_weather_enabled_updated },
{ AppKeyWeatherTemperature, weather_requested_callback },
{ AppKeyVibrateOnTheHour, config_hourly_vibrate_updated },
{ AppKeyMilitaryTime, config_military_time_updated },
{ AppKeyHealthEnabled, config_health_enabled_updated },
{ AppKeyBatteryDisplayedAt, config_battery_displayed_at_updated }
};
s_messenger = messenger_create(17, messenger_callback, messages);
s_messenger = messenger_create(18, messenger_callback, messages);
s_weather_request_timeout = 0;
s_js_ready = false;
#if defined(PBL_PLATFORM_GABBRO)
Expand Down
13 changes: 10 additions & 3 deletions src/c/minimalin.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

typedef enum { NoIcon = 0, Bluetooth , Heart } BluetoothIcon;
typedef enum { Celsius = 0, Fahrenheit } TemperatureUnit;
typedef enum {
WeatherVariableCurrentTemperature = 0,
WeatherVariableApparentTemperature,
WeatherVariableSunset
} WeatherVariable;

typedef enum {
ConfigKeyMinuteHandColor = 0,
Expand All @@ -19,10 +24,11 @@ typedef enum {
ConfigKeyVibrateOnTheHour,
ConfigKeyMilitaryTime,
ConfigKeyHealthEnabled,
ConfigKeyBatteryDisplayedAt
ConfigKeyBatteryDisplayedAt,
ConfigKeyWeatherVariable
} ConfigKey;

#define CONF_SIZE 16
#define CONF_SIZE 17


#ifndef CONFIG_BLUETOOTH_ICON
Expand Down Expand Up @@ -60,5 +66,6 @@ ConfValue CONF_DEFAULTS[CONF_SIZE] = {
{ .key = ConfigKeyVibrateOnTheHour, .type = BoolConf, .value = { .boolean = false } },
{ .key = ConfigKeyMilitaryTime, .type = BoolConf, .value = { .boolean = CONFIG_MILITARY_TIME } },
{ .key = ConfigKeyHealthEnabled, .type = BoolConf, .value = { .boolean = false } },
{ .key = ConfigKeyBatteryDisplayedAt, .type = IntConf, .value = { .integer = -1 } }
{ .key = ConfigKeyBatteryDisplayedAt, .type = IntConf, .value = { .integer = -1 } },
{ .key = ConfigKeyWeatherVariable, .type = IntConf, .value = { .integer = WeatherVariableCurrentTemperature } }
};
56 changes: 51 additions & 5 deletions src/pkjs/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,30 @@ var Config = function(name){
}
};

var Weather = function(pebble){
var Weather = function (pebble) {
var BASE_URL = 'https://api.open-meteo.com/v1/forecast';
var LOCATION_OPTS = {
'timeout': 5000,
'maximumAge': 30 * 60 * 1000
};

var WeatherVariable = {
CURRENT_TEMPERATURE: 0,
APPARENT_TEMPERATURE: 1,
SUNSET: 2
};

var getWeatherVariable = function () {
var config = Config('config').load();
if (typeof config.weather_variable !== 'number') {
return WeatherVariable.CURRENT_TEMPERATURE;
}
if (config.weather_variable < WeatherVariable.CURRENT_TEMPERATURE || config.weather_variable > WeatherVariable.SUNSET) {
return WeatherVariable.CURRENT_TEMPERATURE;
}
return config.weather_variable;
};

// Map WMO weather code + is_day to icon charCode for custom font
var wmoToIcon = function(code, isDay) {
var char;
Expand All @@ -54,21 +71,49 @@ var Weather = function(pebble){
return char.charCodeAt(0);
};

var fetchWeather = function(latitude, longitude) {
var buildUrl = function (latitude, longitude, weatherVariable) {
var url = BASE_URL + '?latitude=' + latitude + '&longitude=' + longitude + '&forecast_days=1&timezone=auto';
var currentVars = ['weather_code', 'is_day'];
if (weatherVariable === WeatherVariable.SUNSET) {
url += '&daily=sunset';
} else if (weatherVariable === WeatherVariable.APPARENT_TEMPERATURE) {
currentVars.unshift('apparent_temperature');
} else {
currentVars.unshift('temperature_2m');
}
url += '&current=' + currentVars.join(',');
return url;
};

var parseSunsetMinutes = function (sunsetIso) {
var match = /T(\d{2}):(\d{2})/.exec(sunsetIso || '');
if (!match) return -1;
return parseInt(match[1], 10) * 60 + parseInt(match[2], 10);
};

var fetchWeather = function (latitude, longitude) {
var req = new XMLHttpRequest();
var url = BASE_URL + '?latitude=' + latitude + '&longitude=' + longitude + '&current=temperature_2m,weather_code,is_day';
var weatherVariable = getWeatherVariable();
var url = buildUrl(latitude, longitude, weatherVariable);
debug('fetchWeather requesting:', url);
req.open('GET', url, true);
req.onload = function () {
debug('fetchWeather onload, readyState:', req.readyState, 'status:', req.status);
if (req.readyState === 4) {
if (req.status === 200) {
var response = JSON.parse(req.responseText);
var temperature = Math.round(response.current.temperature_2m);
var icon = wmoToIcon(response.current.weather_code, response.current.is_day);
var valuePrimary = 0;
if (weatherVariable === WeatherVariable.APPARENT_TEMPERATURE) {
valuePrimary = Math.round(response.current.apparent_temperature);
} else if (weatherVariable === WeatherVariable.SUNSET) {
valuePrimary = parseSunsetMinutes(response.daily.sunset[0]);
} else {
valuePrimary = Math.round(response.current.temperature_2m);
}
var data = {
'AppKeyWeatherIcon': icon,
'AppKeyWeatherTemperature': temperature
'AppKeyWeatherTemperature': valuePrimary
};
debug('fetchWeather success, sending:', data);
Pebble.sendAppMessage(data);
Expand Down Expand Up @@ -141,6 +186,7 @@ Pebble.addEventListener('webviewclosed', function(e) {
bluetooth_icon: 'AppKeyBluetoothIcon',
battery_displayed_at: 'AppKeyBatteryDisplayedAt',
temperature_unit: 'AppKeyTemperatureUnit',
weather_variable: 'AppKeyWeatherVariable',
refresh_rate: 'AppKeyRefreshRate',
rainbow_mode: 'AppKeyRainbowMode',
vibrate_on_the_hour: 'AppKeyVibrateOnTheHour',
Expand Down