Farm alarm #28

Merged
senaduka merged 12 commits from farm_alarm into master 2016-10-30 09:05:00 +01:00
103 changed files with 2130 additions and 82 deletions

1
.gitignore vendored
View File

@@ -17,7 +17,6 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/

8
android/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures

1
android/.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
Farm Alarm

22
android/.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

6
android/.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

24
android/.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.14.1" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="myModules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

19
android/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

9
android/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/android.iml" filepath="$PROJECT_DIR$/android.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
</modules>
</component>
</project>

12
android/.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

9
android/FarmAlarm/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild

22
android/FarmAlarm/.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

6
android/FarmAlarm/.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

19
android/FarmAlarm/.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.14.1" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

46
android/FarmAlarm/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

9
android/FarmAlarm/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/FarmAlarm.iml" filepath="$PROJECT_DIR$/FarmAlarm.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
</modules>
</component>
</project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

6
android/FarmAlarm/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

1
android/FarmAlarm/app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,33 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.1"
defaultConfig {
applicationId "com.zoblak.farmalarm"
minSdkVersion 15
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:design:24.2.1'
compile 'com.android.support:support-v4:24.2.1'
compile 'com.android.support:support-vector-drawable:24.2.1'
testCompile 'junit:junit:4.12'
}

View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/senadu/Android/Sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,26 @@
package com.zoblak.farmalarm;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.zoblak.farmalarm", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zoblak.farmalarm">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainScreen"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="agrar.zoblak.com" />
</intent-filter>
</activity>
<service
android:name=".AlarmPollingService"
android:exported="false" />
<receiver
android:name=".PeriodicalPingReceiver"
android:enabled="true"
android:exported="true" />
<activity
android:name=".SettingsActivity"
android:label="@string/title_activity_settings"
android:parentActivityName=".MainScreen">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.zoblak.farmalarm.MainScreen" />
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,136 @@
package com.zoblak.farmalarm;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
/**
* Helper class for showing and canceling alarm
* notifications.
* <p>
* This class makes heavy use of the {@link NotificationCompat.Builder} helper
* class to create notifications in a backward-compatible way.
*/
public class AlarmNotification {
/**
* The unique identifier for this type of notification.
*/
private static final String NOTIFICATION_TAG = "Alarm";
/**
* Shows the notification, or updates a previously shown notification of
* this type, with the given parameters.
* <p>
* TODO: Customize this method's arguments to present relevant content in
* the notification.
* <p>
* TODO: Customize the contents of this method to tweak the behavior and
* presentation of alarm notifications. Make
* sure to follow the
* <a href="https://developer.android.com/design/patterns/notifications.html">
* Notification design guidelines</a> when doing so.
*
* @see #cancel(Context)
*/
public static void notify(final Context context,
final String exampleString, final int number) {
final Resources res = context.getResources();
// This image is used as the notification's large icon (thumbnail).
// TODO: Remove this if your notification has no relevant thumbnail.
final Bitmap picture = BitmapFactory.decodeResource(res, R.drawable.example_picture);
final String ticker = exampleString;
final String title = res.getString(
R.string.alarm_notification_title_template, exampleString);
final String text = res.getString(
R.string.alarm_notification_placeholder_text_template, exampleString);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
// Set appropriate defaults for the notification light, sound,
// and vibration.
.setDefaults(Notification.DEFAULT_ALL)
// Set required fields, including the small icon, the
// notification title, and text.
.setSmallIcon(R.drawable.ic_stat_alarm)
.setContentTitle(title)
.setContentText(text)
// All fields below this line are optional.
// Use a default priority (recognized on devices running Android
// 4.1 or later)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
// Provide a large icon, shown with the notification in the
// notification drawer on devices running Android 3.0 or later.
.setLargeIcon(picture)
// Set ticker text (preview) information for this notification.
.setTicker(ticker)
// Show a number. This is useful when stacking notifications of
// a single type.
.setNumber(number)
// If this notification relates to a past or upcoming event, you
// should set the relevant time information using the setWhen
// method below. If this call is omitted, the notification's
// timestamp will by set to the time at which it was shown.
// TODO: Call setWhen if this notification relates to a past or
// upcoming event. The sole argument to this method should be
// the notification timestamp in milliseconds.
//.setWhen(...)
// Set the pending intent to be initiated when the user touches
// the notification.
.setContentIntent(
PendingIntent.getActivity(
context,
0,
new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com")),
PendingIntent.FLAG_UPDATE_CURRENT))
// Automatically dismiss the notification when it is touched.
.setAutoCancel(true);
notify(context, builder.build());
}
@TargetApi(Build.VERSION_CODES.ECLAIR)
private static void notify(final Context context, final Notification notification) {
final NotificationManager nm = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
nm.notify(NOTIFICATION_TAG, 0, notification);
} else {
nm.notify(NOTIFICATION_TAG.hashCode(), notification);
}
}
/**
* Cancels any notifications of this type previously shown using
* {@link #notify(Context, String, int)}.
*/
@TargetApi(Build.VERSION_CODES.ECLAIR)
public static void cancel(final Context context) {
final NotificationManager nm = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
nm.cancel(NOTIFICATION_TAG, 0);
} else {
nm.cancel(NOTIFICATION_TAG.hashCode());
}
}
}

View File

@@ -0,0 +1,62 @@
package com.zoblak.farmalarm;
import android.app.IntentService;
import android.content.Intent;
import android.content.Context;
/**
* An {@link IntentService} subclass for handling asynchronous task requests in
* a service on a separate handler thread.
* <p>
* TODO: Customize class - update intent actions, extra parameters and static
* helper methods.
*/
public class AlarmPollingService extends IntentService {
// TODO: Rename actions, choose action names that describe tasks that this
// IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
private static final String ACTION_PING = "com.zoblak.farmalarm.action.PING";
// TODO: Rename parameters
private static final String CONTROLLERS = "com.zoblak.farmalarm.extra.CONTROLLLERS";
public AlarmPollingService() {
super("AlarmPollingService");
}
/**
* Starts this service to perform action Foo with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
// TODO: Customize helper method
public static void startActionPing(Context context, String controllers) {
Intent intent = new Intent(context, AlarmPollingService.class);
intent.setAction(ACTION_PING);
intent.putExtra(CONTROLLERS, controllers);
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_PING.equals(action)) {
final String controllers = intent.getStringExtra(CONTROLLERS);
handleActionPing(controllers);
}
}
}
/**
* Handle action Foo in the provided background thread with the provided
* parameters.
*/
private void handleActionPing(String controllers) {
// do something
}
}

View File

@@ -0,0 +1,109 @@
package com.zoblak.farmalarm;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}

View File

@@ -0,0 +1,52 @@
package com.zoblak.farmalarm;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
public class MainScreen extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_screen);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main_screen, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -0,0 +1,34 @@
package com.zoblak.farmalarm;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebSettings;
import android.webkit.WebView;
/**
* A placeholder fragment containing a simple view.
*/
public class MainScreenFragment extends Fragment {
public MainScreenFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main_screen, container, false);
WebView webView = (WebView)view.findViewById(R.id.main_web_view);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.loadUrl("http://agrar.zoblak.com/alarm");
return view;
}
}

View File

@@ -0,0 +1,24 @@
package com.zoblak.farmalarm;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
public class PeriodicalPingReceiver extends BroadcastReceiver {
public PeriodicalPingReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String controllers = prefs.getString("controllers", null);
Boolean isAlarmOn = prefs.getBoolean("alarm_set", false);
if(isAlarmOn && controllers != null) {
AlarmPollingService.startActionPing(context, controllers);
}
}
}

View File

@@ -0,0 +1,208 @@
package com.zoblak.farmalarm;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.support.v7.app.ActionBar;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.RingtonePreference;
import android.text.TextUtils;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;
import java.util.List;
/**
* A {@link PreferenceActivity} that presents a set of application settings. On
* handset devices, settings are presented as a single list. On tablets,
* settings are split by category, with category headers shown to the left of
* the list of settings.
* <p>
* See <a href="http://developer.android.com/design/patterns/settings.html">
* Android Design: Settings</a> for design guidelines and the <a
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
* API Guide</a> for more information on developing a Settings UI.
*/
public class SettingsActivity extends AppCompatPreferenceActivity {
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();
if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);
} else if (preference instanceof RingtonePreference) {
// For ringtone preferences, look up the correct display value
// using RingtoneManager.
if (TextUtils.isEmpty(stringValue)) {
// Empty values correspond to 'silent' (no ringtone).
//preference.setSummary(R.string.pref_ringtone_silent);
} else {
Ringtone ringtone = RingtoneManager.getRingtone(
preference.getContext(), Uri.parse(stringValue));
if (ringtone == null) {
// Clear the summary if there was a lookup error.
preference.setSummary(null);
} else {
// Set the summary to reflect the new ringtone display
// name.
String name = ringtone.getTitle(preference.getContext());
preference.setSummary(name);
}
}
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
};
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/**
* Binds a preference's summary to its value. More specifically, when the
* preference's value is changed, its summary (line of text below the
* preference title) is updated to reflect the value. The summary is also
* immediately updated upon calling this method. The exact display format is
* dependent on the type of preference.
*
* @see #sBindPreferenceSummaryToValueListener
*/
private static void bindPreferenceSummaryToValue(Preference preference) {
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
// Trigger the listener immediately with the preference's
// current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupActionBar();
}
/**
* Set up the {@link android.app.ActionBar}, if the API is available.
*/
private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
// Show the Up button in the action bar.
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
if (!super.onMenuItemSelected(featureId, item)) {
NavUtils.navigateUpFromSameTask(this);
}
return true;
}
return super.onMenuItemSelected(featureId, item);
}
/**
* {@inheritDoc}
*/
@Override
public boolean onIsMultiPane() {
return isXLargeTablet(this);
}
/**
* {@inheritDoc}
*/
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
/**
* This method stops fragment injection in malicious applications.
* Make sure to deny any unknown fragments here.
*/
protected boolean isValidFragment(String fragmentName) {
return PreferenceFragment.class.getName().equals(fragmentName)
|| GeneralPreferenceFragment.class.getName().equals(fragmentName);
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GeneralPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
setHasOptionsMenu(true);
// Bind the summaries of EditText/List/Dialog/Ringtone preferences
// to their values. When their values change, their summaries are
// updated to reflect the new value, per the Android Design
// guidelines.
bindPreferenceSummaryToValue(findPreference("alarm_set"));
bindPreferenceSummaryToValue(findPreference("controllers"));
bindPreferenceSummaryToValue(findPreference("period_minutes"));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
startActivity(new Intent(getActivity(), SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-.25 1.97,-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01.25,-1.97.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z" />
</vector>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.zoblak.farmalarm.MainScreen">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main_screen" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>

View File

@@ -0,0 +1,9 @@
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragment"
android:name="com.zoblak.farmalarm.MainScreenFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:layout="@layout/fragment_main_screen" />

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content_main_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.zoblak.farmalarm.MainScreenFragment"
tools:showIn="@layout/activity_main_screen">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main_web_view"
android:partition="persist:browser"
></WebView>
</RelativeLayout>

View File

@@ -0,0 +1,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.zoblak.farmalarm.MainScreen">
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"
app:showAsAction="never" />
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,9 @@
<resources>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>

View File

@@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>

View File

@@ -0,0 +1,6 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,39 @@
<resources>
<string name="app_name">Farm Alarm</string>
<string name="action_settings">Settings</string>
<string name="title_activity_settings">Podešavanje</string>
<!-- Strings related to Settings -->
<!-- Example General settings -->
<string name="pref_header_general">Generalno</string>
<string name="pref_title_add_friends_to_messages">Add friends to messages</string>
<string-array name="pref_example_list_titles">
<item>Always</item>
<item>When possible</item>
<item>Never</item>
</string-array>
<string-array name="pref_example_list_values">
<item>1</item>
<item>0</item>
<item>-1</item>
</string-array>
<string name="alarm_notification_title_template">Alarm: %1$s</string>
<!-- TODO: remove this placeholder text -->
<string name="alarm_notification_placeholder_text_template">You said %1$s and lorem ipsum dolor
sit amet, consectetur adipiscing elit. Etiam non enim magna. Morbi dictum, velit vel semper
venenatis, magna odio volutpat velit, at ullamcorper nulla lacus sed turpis. Pellentesque
vitae metus elit, nec tincidunt tellus. Integer sed nisl sem, ullamcorper ornare lacus. Duis
ac mauris sed massa congue volutpat. Donec sed erat sit amet turpis viverra rhoncus sit amet
nec magna. Donec lacinia ligula at libero volutpat volutpat nec nec tortor.
</string>
<string name="action_share">Share</string>
<string name="action_reply">Reply</string>
</resources>

View File

@@ -0,0 +1,20 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

View File

@@ -0,0 +1,27 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<!-- NOTE: EditTextPreference accepts EditText attributes. -->
<!-- NOTE: EditTextPreference's summary should be set to its value by the activity code. -->
<SwitchPreference
android:defaultValue="false"
android:title="Uključiti alram"
android:key="alarm_set" />
<EditTextPreference
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="Kontroler"
android:key="controllers"
android:summary="Broj koji jedinstveno određuje vašu Zoblak kutiju" />
<EditTextPreference
android:defaultValue="7"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="Period provjere"
android:key="period_minutes"
android:summary="Koliko često telefon provjerava da li je alarm uključen (u minutama)" />
<!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
dismiss it. -->
<!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
</PreferenceScreen>

View File

@@ -0,0 +1,10 @@
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<!-- These settings headers are only used on tablets. -->
<header
android:fragment="com.zoblak.farmalarm.SettingsActivity$GeneralPreferenceFragment"
android:icon="@drawable/ic_info_black_24dp"
android:title="@string/pref_header_general" />
</preference-headers>

View File

@@ -0,0 +1,17 @@
package com.zoblak.farmalarm;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

View File

@@ -0,0 +1,23 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,17 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Sat Oct 15 06:26:29 CEST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

160
android/FarmAlarm/gradlew vendored Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
android/FarmAlarm/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1 @@
include ':app'

View File

@@ -28,3 +28,5 @@ fortawesome:fontawesome
mfpierre:chartist-js
standard-minifier-css
standard-minifier-js
iron:router
accolver:twilio-meteor

View File

@@ -1,3 +1,4 @@
accolver:twilio-meteor@1.10.1
accounts-base@1.2.5
accounts-password@1.1.7
allow-deny@1.0.3
@@ -39,6 +40,14 @@ http@1.1.4
huttonr:bootstrap3@3.3.6_10
huttonr:bootstrap3-assets@3.3.6_3
id-map@1.0.6
iron:controller@1.0.12
iron:core@1.0.11
iron:dynamic-template@1.0.12
iron:layout@1.0.12
iron:location@1.0.11
iron:middleware-stack@1.1.0
iron:router@1.0.13
iron:url@1.0.11
jquery@1.11.7
launch-screen@1.0.10
less@2.5.7

20
app/client/alarm.html Normal file
View File

@@ -0,0 +1,20 @@
<template name="alarm">
<div class="hello">
<h1> Temperatura </h1>
<div class="jumbotron text-center center-block" >
{{#with state}}
{{#if alarmTriggered}}
<img src="/images/alarm.gif" class="img-responsive center-block" id="alarm_image" />
<button id="stop_alarm" class="btn btn-danger"> <i class="fa fa-ban"></i> Prekini </button>
{{/if}}
{{/with}}
{{#with last_reading}}
<div class="huge_text"> {{ all_temperatures }}</div>
<div>{{pretty_time created_at}}</div>
{{/with}}
<button id="run_alarm_settings" class="btn btn-default"> <i class="fa fa-wrench"></i> Podešavanje </button>
</div>
</div>
</template>

57
app/client/alarm.js Normal file
View File

@@ -0,0 +1,57 @@
function sensor_data_collection() {
var controllerId = Session.get('controller_id');
return SensorData.find({
controllerId: controllerId
}, {
sort: {
created_at: -1
},
limit: 3
});
}
function last_sensor_reading() {
var controller = Session.get('controller_id');
var result = null;
if (controller) {
result = sensor_data_collection();
}
if (result && result.count() > 0) {
return result.fetch()[0];
} else {
return {}
}
}
Template.alarm.helpers({
last_reading: last_sensor_reading,
state: function() {
return Meteor.zoblak.client.controller_state().state;
},
pretty_time: function(time) {
return moment(time).format("DD.MM.YYYY, HH:mm")
},
all_temperatures: function() {
var result = "";
var temperatures = last_sensor_reading().temperatures;
for (var i in temperatures) {
result += '' + parseFloat(temperatures[i]).toFixed(1) + ' °C ';
}
return result;
}
});
Template.alarm.events({
'click #run_alarm_settings': function() {
Modal.show('alarm_settings');
},
'click #stop_alarm': function() {
let controller_id = Meteor.zoblak.client.controller_state().controller_id;
Meteor.call('stopTheAlarm', controller_id);
}
});
Template.alarm.helpers({
});

View File

@@ -0,0 +1,84 @@
<template name="alarm_settings">
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Podešavanje Alarma</h4>
</div>
<div class="modal-body">
<div class="form-group">
<h3>Alarmiraj:</h3>
<table class="table">
<tr>
<td>ako je temperatura niža od
</td>
<td>
<input required name="min_temperature" id="min_temperature" type="number" min="-20" max="50" step="0.1" value={{ config 'minTemperature'}} />°C</td>
</tr>
<tr>
<td>ako je temperatura viša od
</td>
<td>
<input required name="max_temperature" id="max_temperature" type="number" min="-20" max="50" step="0.1" value={{ config 'maxTemperature'}} />°C</td>
</tr>
<tr>
<td>ako se Zoblak Alarm Kutija ne javi
</td>
<td>
<input required name="timeout_box" id="timeout_box" type="number" min="1" max="90" value={{ config 'timeoutBox'}} /> minuta </td>
</tr>
<tr>
<td>ako se bar jedan mobitel ne javi
</td>
<td>
<input required name="timeout_phone" id="timeout_phone" type="number" min="1" max="90" value={{ config 'timeoutPhone'}} /> minuta </td>
</tr>
</table>
</div>
<div class="form-group">
<h3>U slučaju kritične situacije - javi SMS-om na telefone: </h3>
<table class="table">
<td>1.
</td>
<td>
<input required name="sms1" type="tel" id="sms1" placeholder="+3876xxxxxxxx" value={{ config 'sms1'}} />
</td>
<tr>
<td>2.
</td>
<td>
<input required name="sms2" type="tel" id="sms2" placeholder="+3876xxxxxxxx" value={{ config 'sms2'}}/>
</td>
</tr>
<tr>
<td>3.
</td>
<td>
<input required name="sms3" type="tel" id="sms3" placeholder="+3876xxxxxxxx" value={{ config 'sms3'}}/>
</td>
</tr>
<tr>
<td>4.
</td>
<td>
<input required name="sms4" type="tel" id="sms4" placeholder="+3876xxxxxxxx" value={{ config 'sms4'}}/>
</td>
</tr>
</table>
</div>
</div>
<div class="modal-footer">
<button id="save_settings" class="btn btn-default" name="save_settings" data-dismiss="modal">Zapamti</button>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,53 @@
controller_state = function() {
var controller = Session.get('controller_id');
var result = {}
if (controller) {
result = ControllerState.findOne({
controller_id: controller
});
}
if (!result) {
result = {}
};
return result;
};
config = function() {
return Meteor.zoblak.client.controller_state().config;
}
Template.alarm_settings.helpers({
config: function(property) {
console.log('asking for property', property);
console.log('config is', config());
var result = config()[property];
console.log('returning', result);
return result;
}
});
Template.alarm_settings.events({
'click #save_settings': function() {
var controller_id = Meteor.zoblak.client.controller_state().controller_id;
var instance = Template.instance();
var minTemperature = instance.$('#min_temperature').val();
var maxTemperature = instance.$('#max_temperature').val();
var timeoutBox = instance.$('#timeout_box').val();
var timeoutPhone = instance.$('#timeout_phone').val();
var sms1 = instance.$('#sms1').val();
var sms2 = instance.$('#sms2').val();
var sms3 = instance.$('#sms3').val();
var sms4 = instance.$('#sms4').val();
Meteor.call('saveAlarmSettings', controller_id, minTemperature, maxTemperature, timeoutBox, timeoutPhone, [sms1, sms2, sms3, sms4]);
}
});
Template.sensorData.helpers({
created_at_formatted: function() {
return moment(this.created_at).format("DD.MM.YYYY, HH:mm")
}
});

View File

@@ -12,7 +12,7 @@
{{> tabs}}
<div class="container-fluid">
{{> Template.dynamic template=template_name }}
{{> Template.dynamic template=template_name }}
</div>

View File

@@ -6,6 +6,10 @@
padding-top: 10px;
}
.huge_text {
font-size: 3em;
}
@media all and (orientation: portrait) {
#bucket_image {
width: 90%;
@@ -19,3 +23,23 @@
cursor: pointer;
}
}
@media all and (orientation: portrait) {
#alarm_image {
width: 90%;
cursor: pointer;
}
}
@media all and (orientation: landscape) {
#alarm_image {
width: 40%;
cursor: pointer;
}
}
.clickable {
cursor:pointer;
}

View File

@@ -23,5 +23,14 @@ Template.log.events({
Template.sensorData.helpers({
created_at_formatted: function() {
return moment(this.created_at).format("DD.MM.YYYY, HH:mm")
},
all_temperatures: function(temperatures) {
var result = '';
if (temperatures.length > 0) {
for (var i in temperatures) {
result += '' + parseFloat(temperatures[i]).toFixed(1) + ' °C ';
}
}
return result;
}
});

View File

@@ -0,0 +1,3 @@
<template name="no_access">
No access!
</template>

View File

@@ -1,3 +1,3 @@
<template name="sensorData">
<li>{{owner}} / <strong>{{temperatureValue}}°C</strong> / <strong>{{humidityValue}}%</strong> / Bačva puna: <strong>{{tankFull}}</strong> (L0:{{tankLevel0}}-L1:{{tankLevel1}}-L2:{{tankLevel2}}-L3:{{tankLevel3}}-L4:{{tankLevel4}}-full:{{tankFull}}) / {{created_at_formatted}}</li>
<li>{{owner}} / <strong>{{temperatureValue}}°C {{all_temperatures temperatures}}</strong> / <strong>{{humidityValue}}%</strong> / Bačva puna: <strong>{{tankFull}}</strong> (L0:{{tankLevel0}}-L1:{{tankLevel1}}-L2:{{tankLevel2}}-L3:{{tankLevel3}}-L4:{{tankLevel4}}-full:{{tankFull}}) / {{created_at_formatted}}</li>
</template>

View File

@@ -1,32 +1,18 @@
function controller_state() {
var controller = Session.get('controller_id');
var result = {}
if (controller) {
result = ControllerState.findOne({
controller_id: controller
});
}
if (!result) {
result = {}
};
return result;
};
Template.settings.helpers({
timeSelected: function(time) {
var config = controller_state().config;
var config = Meteor.zoblak.client.controller_state().config;
return config.automaticTimeOfDay == time;
},
dayChecked: function(day) {
var config = controller_state().config;
var config = Meteor.zoblak.client.controller_state().config;
var days = config.automaticDaysOfWeek || [];
return days.includes(day)
},
manualInflowChecked: function(day) {
var config = controller_state().config;
var config = Meteor.zoblak.client.controller_state().config;
return config.manualInflow;
}

View File

@@ -1,4 +1,4 @@
Tracker.autorun(function () {
Tracker.autorun(function() {
var id = Session.get('controller_id');
if (id) {
Meteor.subscribe("sensor_data", id);
@@ -6,3 +6,21 @@ Tracker.autorun(function () {
Meteor.subscribe('pictures', id);
}
});
function safeRoute(route) {
return function () {
console.log('go ', route);
if (Meteor.zoblak.client.accessible(route)) {
Session.set('templateName', route);
} else {
Session.set('templateName', 'no_access')
}
}
}
Router.route('/', safeRoute('start'));
Router.route('/alarm', safeRoute('alarm'));
Router.route('/log', safeRoute('log'));
Router.route('/surveillance', safeRoute('surveillance'));
Router.route('/weather', safeRoute('weather'));

View File

@@ -1,11 +1,3 @@
function controller_state() {
var controllerId = Session.get('controller_id');
result = ControllerState.findOne({});
if (!result) {
result = {}
};
return result;
};
function sensor_data_collection() {
var controllerId = Session.get('controller_id');
@@ -44,7 +36,7 @@ Template.state.helpers({
bucket_image: function() {
var sensor = last_sensor_reading();
var stateObject = controller_state();
var stateObject = Meteor.zoblak.client.controller_state();
if (sensor) {
if (parseInt(sensor.tankFull) === 0 && stateObject.state.in_valve === 'open' && stateObject.state.out_valve === 'closed') return "/images/barrellFillingUp.png";
else if (parseInt(sensor.tankFull) === 1 && (stateObject.state.out_valve === 'closed')) return "/images/barrellFull.png";
@@ -74,7 +66,7 @@ Template.state.helpers({
},
water_now_button_class: function() {
var stateObject = controller_state();
var stateObject = Meteor.zoblak.client.controller_state();
if (stateObject.state && (stateObject.state.out_valve === 'open' || stateObject.state.out_valve === 'opening')) {
return 'hidden btn btn-success';
} else {
@@ -82,7 +74,7 @@ Template.state.helpers({
}
},
stop_button_class: function() {
var stateObject = controller_state();
var stateObject = Meteor.zoblak.client.controller_state();
if (stateObject.state && (stateObject.state.out_valve === 'closed' || stateObject.state.out_valve === 'closing')) {
return 'hidden btn btn-success';
} else {
@@ -91,7 +83,7 @@ Template.state.helpers({
},
start_inflow_button_class: function() {
var stateObject = controller_state();
var stateObject = Meteor.zoblak.client.controller_state();
if(stateObject.config && stateObject.config.manualInflow && stateObject.state.out_valve === 'closed' && ( stateObject.state.in_valve === 'closed' || stateObject.state.in_valve === 'closing')) {
return 'btn btn-danger'
} else {
@@ -100,7 +92,7 @@ Template.state.helpers({
},
stop_inflow_button_class: function() {
var stateObject = controller_state();
var stateObject = Meteor.zoblak.client.controller_state();
if(stateObject.config && stateObject.config.manualInflow && stateObject.state.out_valve === 'closed' && ( stateObject.state.in_valve === 'open' || stateObject.state.in_valve === 'opening')) {
return 'btn btn-danger'
} else {
@@ -159,7 +151,7 @@ Template.state.events({
},
'click #bucket_image': function() {
Modal.show('state_details', controller_state());
Modal.show('state_details', Meteor.zoblak.client.controller_state());
}
});

View File

@@ -1,11 +1,3 @@
function controller_state() {
var controllerId = Session.get('controller_id');
result = ControllerState.findOne({});
if (!result) {
result = {}
};
return result;
};
function picture() {
var controllerId = Session.get('controller_id');

View File

@@ -1,10 +1,26 @@
<template name="tabs">
<div></div>
<ul class="nav nav-tabs">
<li role="presentation" class="{{ class_for 'start' }}"><a href="#">Stanje</a></li>
<li role="presentation" class="{{ class_for 'weather' }}"><a href="#">Vrijeme</a></li>
<li role="presentation" class="{{ class_for 'log' }}"><a href="#">Novosti</a></li>
<li role="presentation" class="{{ class_for 'surveillance' }}"><a href="#">Videonadzor</a></li>
{{#if accessible 'start'}}
<li role="presentation" class="{{ class_for 'start' }}"><a class="clickable">Stanje</a></li>
{{/if}}
{{#if accessible 'weather'}}
<li role="presentation" class="{{ class_for 'weather' }}"><a class="clickable">Vrijeme</a></li>
{{/if}}
{{#if accessible 'log'}}
<li role="presentation" class="{{ class_for 'log' }}"><a class="clickable">Novosti</a></li>
{{/if}}
{{#if accessible 'surveillance'}}
<li role="presentation" class="{{ class_for 'surveillance' }}"><a class="clickable">Videonadzor</a></li>
{{/if}}
{{#if accessible 'alarm'}}
<li role="presentation" class="{{ class_for 'alarm' }}"><a class="clickable">Alarm</a></li>
{{/if}}
<li role="presentation" class="controller_selection"> <input type="number" id="controller" name="controller" value="{{ selected_controller }}" min="1" max="99999"> <button id="switch" name="switch">Prebaci</button>
</li>
</ul>

View File

@@ -14,24 +14,33 @@ Template.tabs.helpers({
selected_controller: function() {
return Session.get('controller_id');
},
accessible: Meteor.zoblak.client.accessible
});
Template.tabs.events({
'click .start': function() {
Session.set('templateName', 'start');
Router.go('/');
},
'click .weather': function() {
Session.set('templateName', 'weather');
Router.go('/weather');
},
'click .log': function() {
Session.set('templateName', 'log');
Router.go('/log');
},
'click .surveillance': function() {
Session.set('templateName', 'surveillance');
Router.go('/surveillance');
},
'click .alarm': function() {
Router.go('/alarm');
},
'click .settings': function() {
Session.set('templateName', 'settings');
},
'click #switch': function() {
var instance = Template.instance();
controller_id = instance.$('#controller').val();

View File

@@ -1,4 +0,0 @@
SensorData = new Mongo.Collection("sensorData");
ControllerState = new Mongo.Collection("controller_states");
Picture = new Mongo.Collection("pictures");

44
app/lib/zoblak.js Normal file
View File

@@ -0,0 +1,44 @@
SensorData = new Mongo.Collection("sensorData");
ControllerState = new Mongo.Collection("controller_states");
Picture = new Mongo.Collection("pictures");
Meteor.zoblak = {}
Meteor.zoblak.client = {
controller_state: function() {
result = ControllerState.findOne({});
if (!result) {
result = {}
};
return result;
},
config: function() {
Meteor.zoblak.client.controller_state().config
},
accessible: function(feature) {
var controller = Meteor.zoblak.client.controller_state();
console.log('cotnroller ', controller);
if (!controller.features) return false;
return controller.features[feature] === true;
}
}
Meteor.zoblak.server = {
controller_state: function(controller_id) {
var result = {}
if (controller_id) {
result = ControllerState.findOne({
controller_id: controller_id
});
}
if (!result) {
result = {}
};
return result;
}
}

BIN
app/public/images/alarm.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

View File

@@ -13,6 +13,7 @@ Api.addRoute('sensorData', {
var sensorObject = {
temperatureValue: parseFloat(this.bodyParams.temperatureValue),
humidityValue: parseFloat(this.bodyParams.humidityValue),
temperatures: this.bodyParams.temperatures,
tankLevel0: this.bodyParams.tankLevel0,
tankLevel1: this.bodyParams.tankLevel1,
tankLevel2: this.bodyParams.tankLevel2,
@@ -23,6 +24,7 @@ Api.addRoute('sensorData', {
stopPumpingAt: this.bodyParams.stopPumpingAt,
owner: this.bodyParams.owner,
controllerId: this.bodyParams.controllerId,
lastBoxContact: new Date(),
created_at: new Date()
};
SensorData.insert(sensorObject);
@@ -30,6 +32,20 @@ Api.addRoute('sensorData', {
}
});
Api.addRoute('alarm/:id/phonePing', {
authRequired: false
}, {
post: function() {
return ControllerState.update({
controller_id: this.urlParams.id
}, {
'$set': {
'lastPhoneContact': new Date(),
}
});
}
});
reactToSensorData = function(nextSensorReading) {
console.log("reacting to sensor");
@@ -162,6 +178,13 @@ function stateOrDefault(id) {
draining_period_unit: 'minutes',
manualInflow: true
},
features: {
start: true,
weather: true,
surveillance: true,
log: true,
alarm: true
},
set_by: 'server'
});
};

View File

@@ -1,19 +1,5 @@
function controller_state(controller_id) {
var result = {}
if (controller_id) {
result = ControllerState.findOne({
controller_id: controller_id
});
}
if (!result) {
result = {}
};
return result;
};
function setOutValveTo(controller_id, nextState) {
var state = controller_state(controller_id);
var state = Meteor.zoblak.server.controller_state(controller_id);
ControllerState.update(state._id, {
'$set': {
'state.out_valve': nextState,
@@ -33,7 +19,7 @@ function setOutValveTo(controller_id, nextState) {
}
function setInValveTo(controller_id, nextState) {
var state = controller_state(controller_id);
var state = Meteor.zoblak.server.controller_state(controller_id);
ControllerState.update(state._id, {
'$set': {
'state.in_valve': nextState,
@@ -53,7 +39,7 @@ function setInValveTo(controller_id, nextState) {
}
function requestNewPicture(controller_id) {
var state = controller_state(controller_id);
var state = Meteor.zoblak.server.controller_state(controller_id);
ControllerState.update(state._id, {
'$set': {
'state.picture_requested': 'true',
@@ -64,7 +50,7 @@ function requestNewPicture(controller_id) {
};
function openInValve(controller_id) {
var state = controller_state(controller_id);
var state = Meteor.zoblak.server.controller_state(controller_id);
var config = state.config;
if (config.manualInflow) {
setInValveTo(controller_id, 'opening');
@@ -73,7 +59,7 @@ function openInValve(controller_id) {
}
function closeInValve(controller_id) {
var state = controller_state(controller_id);
var state = Meteor.zoblak.server.controller_state(controller_id);
var config = state.config;
if (config.manualInflow) {
setInValveTo(controller_id, 'closing');
@@ -87,7 +73,7 @@ function closeInValve(controller_id) {
function openOutValve(controller_id) {
setOutValveTo(controller_id, 'opening');
setInValveTo(controller_id, 'closing');
var state = controller_state(controller_id);
var state = Meteor.zoblak.server.controller_state(controller_id);
var config = state.config;
var jobName = "Close out valve " + state.controller_id + " after draining";
console.log("Opening valve ", controller_id, jobName);
@@ -108,7 +94,7 @@ function openOutValve(controller_id) {
}
function closeOutValve(controller_id) {
var state = controller_state(controller_id);
var state = Meteor.zoblak.server.controller_state(controller_id);
var jobName = "Close out valve " + state.controller_id + " after draining";
console.log("Closing valve ", controller_id, jobName);
@@ -127,7 +113,7 @@ function clearLog() {
}
function saveControllerConfig(controller_id, time, days, manualInflow) {
var state = controller_state(controller_id);
var state = Meteor.zoblak.server.controller_state(controller_id);
ControllerState.update(state._id, {
'$set': {
'config.automaticTimeOfDay': time,
@@ -157,6 +143,162 @@ function saveControllerConfig(controller_id, time, days, manualInflow) {
});
}
function saveAlarmSettings(controller_id, minTemperature, maxTemperature, timeoutBox, timeoutPhone, smsNumbers) {
var state = Meteor.zoblak.server.controller_state(controller_id);
ControllerState.update(state._id, {
'$set': {
'config.minTemperature': parseFloat(minTemperature),
'config.maxTemperature': parseFloat(maxTemperature),
'config.timeoutBox': parseInt(timeoutBox),
'config.timeoutPhone': parseInt(timeoutPhone),
'config.smsNumbers': smsNumbers,
'config.sms1': smsNumbers[0],
'config.sms2': smsNumbers[1],
'config.sms3': smsNumbers[2],
'config.sms4': smsNumbers[3]
}
});
var jobName = "automatic_alarm_" + controller_id;
SyncedCron.remove(jobName);
SyncedCron.add({
name: jobName,
schedule: function(parser) {
return parser.text('every 30 seconds');
},
job: function() {
reactToAlarmData(controller_id);
}
});
}
// there are three states of alarm:
// 1. normal ( state.alarmTriggered: false, state.alarmStopped: null )
// 2. triggered ( state.alarmTriggered: true, state.alarmStopped: null )
// 3. silenced ( state.alarmTriggered: false, state.alarmStopped: (sometime) )
function reactToAlarmData(controller_id) {
var reading = last_sensor_reading(controller_id);
var state = Meteor.zoblak.server.controller_state(controller_id);
var config = state.config;
var minTemperature = function(temperatures) {
// if it gets a lot colder than absolute zero we will
// we will have more problems than the bug in this code
if (temperatures.length <= 0) return -1000;
var minimal = parseFloat(temperatures[0]);
for (var i in temperatures) {
if (parseFloat(temperatures[i]) < minimal) {
minimal = parseFloat(temperatures[i]);
}
}
return minimal;
}
var maxTemperature = function(temperatures) {
// obviously - hell is not supported in this version
if (temperatures.length <= 0) return 1000;
var maximal = parseFloat(temperatures[0]);
for (var i in temperatures) {
if (parseFloat(temperatures[i]) > maximal) {
maximal = parseFloat(temperatures[i]);
}
}
return maximal;
}
var tooCold = config.minTemperature && (minTemperature(reading.temperatures) < config.minTemperature);
var tooHot = config.maxTemperature && (maxTemperature(reading.temperatures) > config.maxTemperature);
var minutesSinceLastBoxContact = reading.lastBoxContact ? moment(new Date()).diff(moment(reading.lastBoxContact), 'minutes') : -1;
var boxSilent = config.timeoutBox && minutesSinceLastBoxContact > config.timeoutBox;
var minutesSinceLastPhoneContact = state.lastPhoneContact ? moment(new Date()).diff(moment(state.lastPhoneContact), 'minutes') : -1;
var phoneSilent = config.timeoutPhone && minutesSinceLastPhoneContact > config.timeoutPhone;
console.log("lpc", state.lastPhoneContact);
console.log("mslpc", minutesSinceLastPhoneContact);
console.log("phoneSilent", phoneSilent);
if (tooCold || tooHot || boxSilent || phoneSilent) {
var alarmSilenced = !!state.state.alarmStopped;
if (!alarmSilenced) soundTheAlarm(controller_id, tooCold, tooHot, boxSilent, phoneSilent);
} else {
stopTheAlarm(controller_id, true);
}
}
function soundTheAlarm(controller_id, tooCold, tooHot, boxSilent, phoneSilent) {
var state = Meteor.zoblak.server.controller_state(controller_id);
var reason = {
tooHot: tooHot,
tooCold: tooCold,
boxSilent: boxSilent,
phoneSilent: phoneSilent
};
console.log("Alarmiram", reason);
var firstTime = {};
if (!state.state.alarmTriggered) {
firstTime = {
'state.alarmStarted': new Date()
}
};
var smsSent = !!state.state.alarmSmsSent;
var needsToSendSms = !smsSent; // && (boxSilent || phoneSilent);
var sendSmsPart = needsToSendSms ? { 'state.alarmSmsSent': true } : {};
ControllerState.update(state._id, {
'$set': Object.assign({
'state.alarmTriggered': true,
'state.alarmStopped': null,
'state.alarmReasons': reason
}, firstTime, sendSmsPart)
});
if (needsToSendSms) {
sendAlarmingSms(reason, state.config.smsNumbers)
}
}
function sendAlarmingSms(reason, numbers) {
for (var i in numbers) {
var number = numbers[i];
twilio = Twilio('AC10d7ed0bf54c1be4b1cd7133130e63f4', 'e133d3f02a69b79e93ad9ca1d73517d1');
twilio.sendSms({
to: number, // Any number Twilio can deliver to
from: '+447481345235', // A number you bought from Twilio and can use for outbound communication
body: 'Zoblak alarm! Pokrenite aplikaciju! HITNO! http://agrar.zoblak.com' // body of the SMS message
}, function(err, responseData) { //this function is executed when a response is received from Twilio
if (!err) { // "err" is an error received during the request, if any
// "responseData" is a JavaScript object containing data received from Twilio.
// A sample response from sending an SMS message is here (click "JSON" to see how the data appears in JavaScript):
// http://www.twilio.com/docs/api/rest/sending-sms#example-1
console.log(responseData.from); // outputs "+14506667788"
console.log(responseData.body); // outputs "word to your mother."
}
});
}
}
function stopTheAlarm(controller_id, everythingIsBackToNormal = false) {
// time of alarm stopped is reset so that scheduled job can raise the alarm
// again
var timeOfStopping = (everythingIsBackToNormal) ? null : new Date();
var state = Meteor.zoblak.server.controller_state(controller_id);
ControllerState.update(state._id, {
'$set': {
'state.alarmTriggered': false,
'state.alarmStopped': timeOfStopping,
'state.alarmSmsSent': false
}
});
}
function last_sensor_reading(controller_id) {
var result = null;
@@ -186,5 +328,7 @@ Meteor.methods({
closeInValve: closeInValve,
clearLog: clearLog,
saveControllerConfig: saveControllerConfig,
requestNewPicture: requestNewPicture
requestNewPicture: requestNewPicture,
saveAlarmSettings: saveAlarmSettings,
stopTheAlarm: stopTheAlarm
});

Some files were not shown because too many files have changed in this diff Show More