PageRenderTime 341ms CodeModel.GetById 121ms app.highlight 140ms RepoModel.GetById 71ms app.codeStats 0ms

/src/de/androvdr/devices/Devices.java

https://code.google.com/p/androvdr/
Java | 766 lines | 662 code | 80 blank | 24 comment | 137 complexity | b4fabd16ee18ad1c4d2afb71f52c8d0d MD5 | raw file
  1/*
  2 * Copyright (c) 2010-2011 by androvdr <androvdr@googlemail.com>
  3 *
  4 * This program is free software; you can redistribute it and/or modify
  5 * it under the terms of the GNU General Public License version 2 as
  6 * published by the Free Software Foundation.
  7 *
  8 * This program is distributed in the hope that it will be useful,
  9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 11 * GNU General Public License for more details.
 12 *
 13 * You should have received a copy of the GNU General Public License
 14 * along with this program; if not, write to the Free Software
 15 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 16 *
 17 * For more information on the GPL, please go to:
 18 * http://www.gnu.org/copyleft/gpl.html
 19 */
 20
 21package de.androvdr.devices;
 22
 23import java.io.BufferedReader;
 24import java.io.File;
 25import java.io.FilenameFilter;
 26import java.io.InputStream;
 27import java.io.InputStreamReader;
 28import java.text.SimpleDateFormat;
 29import java.util.ArrayList;
 30import java.util.Date;
 31import java.util.Enumeration;
 32import java.util.Hashtable;
 33import java.util.LinkedList;
 34import java.util.List;
 35import java.util.Timer;
 36import java.util.TimerTask;
 37import java.util.jar.JarFile;
 38import java.util.zip.ZipEntry;
 39
 40import org.slf4j.Logger;
 41import org.slf4j.LoggerFactory;
 42
 43import android.app.Activity;
 44import android.content.ContentValues;
 45import android.content.SharedPreferences;
 46import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 47import android.database.Cursor;
 48import android.database.sqlite.SQLiteDatabase;
 49import android.os.Bundle;
 50import android.os.Environment;
 51import android.os.Handler;
 52import android.os.Looper;
 53import android.os.Message;
 54import android.preference.PreferenceManager;
 55import dalvik.system.DexClassLoader;
 56import de.androvdr.AndroApplication;
 57import de.androvdr.DBHelper;
 58import de.androvdr.DevicesTable;
 59import de.androvdr.Preferences;
 60import de.androvdr.Recordings;
 61
 62public class Devices implements OnSharedPreferenceChangeListener, OnChannelChangedListener {
 63	private static transient Logger logger = LoggerFactory.getLogger(Devices.class);
 64	
 65	private static Devices sInstance;
 66	
 67	public static final String VDR_CLASSNAME = "VDR";
 68	public static final CharSequence[] volumePrefNames = new CharSequence[] { "volumeDevice", "volumeUp", "volumeDown" };
 69
 70	private ActivityDevice mActivity;
 71	private SensorJob mChannelSensorJob;
 72	private final DBHelper mDBHelper;
 73	private int mDatabaseIsExternalOpen = 0;
 74	private Hashtable<String, IActuator> mDevices = new Hashtable<String, IActuator>();
 75	private Hashtable<String, Macro> mMacros = new Hashtable<String, Macro>();
 76	private OnChangeListener mOnDeviceConfigurationChangedListener = null;
 77	private Hashtable<String, Class<?>> mPlugins = new Hashtable<String, Class<?>>();
 78	private Handler mResultHandler = null;
 79	private DeviceSendThread mSendThread = null;
 80	private ArrayList<SensorJob> mSensorJobs = new ArrayList<SensorJob>();
 81	private Hashtable<String, ISensor> mSensors = new Hashtable<String, ISensor>();
 82	private Timer mSensorUpdateTimer;
 83	private SensorReceiveThread mReceiveThread = null;
 84	private String mVolumeDownCommand = "";
 85	private String mVolumeUpCommand = "";
 86
 87	public static final String MSG_RESULT = "result";
 88
 89	public static String macroConfig = Preferences.getMacroFileName();
 90	public static String pluginDir = Preferences.getPluginDirName();
 91	
 92	private Devices() {
 93		mReceiveThread = new SensorReceiveThread();
 94		mReceiveThread.start();
 95
 96		mSendThread = new DeviceSendThread();
 97		mSendThread.start();
 98		
 99		mDBHelper = new DBHelper(AndroApplication.getAppContext());
100		
101		init();
102		SharedPreferences sp = PreferenceManager
103				.getDefaultSharedPreferences(AndroApplication.getAppContext());
104		initVolumeCommands(sp);
105	}
106
107	public void addDevice(Cursor cursor) {
108		if (cursor.getString(1).equals(VDR_CLASSNAME)) {
109			VdrDevice vdr = new VdrDevice();
110			vdr.setId(cursor.getLong(0));
111			vdr.setName(cursor.getString(2));
112			vdr.setIP(cursor.getString(3));
113			vdr.setPort(cursor.getInt(4));
114			vdr.macaddress = cursor.getString(7);
115			vdr.remote_host = cursor.getString(8);
116			vdr.remote_user = cursor.getString(9);
117			vdr.remote_port = cursor.getInt(10);
118			vdr.remote_local_port = cursor.getInt(11);
119			vdr.remote_timeout = cursor.getInt(12);
120			vdr.channellist = cursor.getString(13);
121			vdr.epgmax = cursor.getInt(14);
122			vdr.characterset = cursor.getString(15);
123			vdr.margin_start = cursor.getInt(16);
124			vdr.margin_stop = cursor.getInt(17);
125			vdr.vps = cursor.getString(18).equals("true");
126			vdr.timeout = cursor.getInt(19);
127			vdr.sshkey = cursor.getString(20);
128			vdr.streamingport = cursor.getInt(21);
129			vdr.broadcastaddress = cursor.getString(22);
130			vdr.extremux = cursor.getString(23).equals("true");
131			vdr.extremux_param = cursor.getString(24);
132			vdr.remote_streaming_port = cursor.getInt(25);
133			vdr.extremux_command = cursor.getString(26);
134			vdr.vdradmin = cursor.getString(27).equals("true");
135			vdr.vdradmin_port = cursor.getInt(28);
136			vdr.remote_vdradmin_port = cursor.getInt(29);
137			vdr.generalstreaming = cursor.getString(30).equals("true");
138			vdr.generalstreaming_url = cursor.getString(31);
139			
140			mDevices.put(vdr.getName(), vdr);
141			mSensors.put(vdr.getName(), vdr);
142		} else {
143			try {
144				if (mPlugins.containsKey(cursor.getString(1))) {
145					IDevice c = (IDevice) mPlugins.get(cursor.getString(1)).newInstance();
146					if (c != null && c instanceof IActuator) { 
147						IActuator actuator = (IActuator) c;
148						actuator.setId(cursor.getLong(0));
149						actuator.setName(cursor.getString(2));
150						actuator.setIP(cursor.getString(3));
151						actuator.setPort(cursor.getInt(4));
152						actuator.setUser(cursor.getString(5));
153						actuator.setPassword(cursor.getString(6));
154						mDevices.put(actuator.getName(), actuator);
155					}
156					if (c != null && c instanceof ISensor) {
157						ISensor sensor = (ISensor) c;
158						sensor.setId(cursor.getLong(0));
159						sensor.setName(cursor.getString(2));
160						sensor.setIP(cursor.getString(3));
161						sensor.setPort(cursor.getInt(4));
162						sensor.setUser(cursor.getString(5));
163						sensor.setPassword(cursor.getString(6));
164						mSensors.put(sensor.getName(), sensor);
165
166					}
167				}
168			} catch (Exception e) {
169				e.printStackTrace();
170			}
171		}
172	}
173	
174	public void addOnSensorChangeListener(String command, int interval, OnSensorChangeListener listener) {
175		SensorJob job = new SensorJob(command, interval, listener);
176		synchronized (mSensorJobs) {
177			mSensorJobs.add(job);
178		}
179		
180		if (VdrDevice.isChannelSensor(command)) {
181			mChannelSensorJob = job;
182			VdrDevice vdr = Preferences.getVdr();
183			if (vdr != null)
184				vdr.addChannelChangedListener(this);
185		}
186	}
187	
188	public void clearOnSensorChangeListeners() {
189		synchronized (mSensorJobs) {
190			VdrDevice vdr = Preferences.getVdr();
191			if (vdr != null)
192				for (SensorJob job : mSensorJobs) {
193					if (VdrDevice.isChannelSensor(job.command))
194						vdr.removeChannelChangedListener(this);
195				}
196			mSensorJobs.clear();
197		}
198		logger.trace("SensorJobs cleared");
199	}
200	
201	public void dbClose() {
202		mDatabaseIsExternalOpen -= 1;
203		if (mDatabaseIsExternalOpen == 0)
204			mDBHelper.close();
205	}
206	
207	public int dbDelete(long id) {
208		SQLiteDatabase db = mDBHelper.getWritableDatabase();
209		int result = db.delete(DevicesTable.TABLE_NAME, "_id = ?", new String[] { Long.toString(id) });
210		if (mDatabaseIsExternalOpen == 0)
211			db.close();
212		Recordings.clearIds(id);
213		initDevices();
214		return result;
215	}
216	
217	public long dbStore(ContentValues values) {
218		long result;
219		SQLiteDatabase db = mDBHelper.getWritableDatabase();
220		if (values.containsKey(DevicesTable.ID)) {
221			result = db.update(DevicesTable.TABLE_NAME, values, DevicesTable.ID + "=?",
222					new String[] { values.getAsString(DevicesTable.ID) });
223		} else {
224			result = db.insert(DevicesTable.TABLE_NAME, null, values);
225		}
226		if (mDatabaseIsExternalOpen == 0)
227			db.close();
228		return result;
229	}
230
231	public long dbUpdate(long id, ContentValues values) {
232		ContentValues storevalues = new ContentValues(values);
233		storevalues.put("_id", id);
234		return dbStore(storevalues);
235	}
236	
237//	public static Devices getInstance() {
238//		return sDevices;
239//	}
240	
241	public static Devices getInstance() {
242		if (sInstance == null) {
243			sInstance = new Devices();
244		}
245		return sInstance;
246	}
247
248	public IActuator get(String name) {
249		return mDevices.get(name);
250	}
251
252	public Cursor getCursorForAllDevices() {
253		final SQLiteDatabase db = mDBHelper.getReadableDatabase();
254		mDatabaseIsExternalOpen += 1;
255		return db.query(DevicesTable.TABLE_NAME, DevicesTable.ALL_COLUMNS,
256				null, null, null, null, DevicesTable.NAME);
257	}
258	
259	public Cursor getCursorForDevice(long id) {
260		final SQLiteDatabase db = mDBHelper.getReadableDatabase();
261		mDatabaseIsExternalOpen += 1;
262		return db.query(DevicesTable.TABLE_NAME, DevicesTable.ALL_COLUMNS,
263				"_id = ?", new String[] { Long.toString(id)}, null, null, null);
264	}
265
266	public IActuator getDevice(long id) {
267		IActuator device = null;
268		Enumeration<String> e = mDevices.keys();
269		while (e.hasMoreElements()) {
270			String key = e.nextElement();
271			IDevice idev = (IDevice) mDevices.get(key);
272			if ((idev instanceof IDevice) && (((IDevice) idev).getId() == id)) {
273				device = (IActuator) idev;
274				break;
275			}
276		}
277		return device;
278	}
279	
280	public ArrayList<IActuator> getDevices() {
281		ArrayList<IActuator> actuators = new ArrayList<IActuator>();
282		Enumeration<String> e = mDevices.keys();
283		while (e.hasMoreElements()) {
284			String key = e.nextElement();
285			IActuator ac = mDevices.get(key);
286			if ((ac instanceof IActuator) && !(ac instanceof VdrDevice)) {
287				actuators.add(ac);
288			}
289		}
290		return actuators;
291	}
292	
293	public String[] getPluginNames() {
294		List<CharSequence> list = new LinkedList<CharSequence>();
295		Enumeration<String> e = mPlugins.keys();
296		while (e.hasMoreElements()) {
297			String key = e.nextElement();
298			if (IActuator.class.isAssignableFrom(mPlugins.get(key)))
299				list.add(key); 
300		}
301		return list.toArray(new String[list.size()]);
302	}
303
304	public VdrDevice getFirstVdr() {
305		VdrDevice vdr = null;
306		Enumeration<String> e = mDevices.keys();
307		while (e.hasMoreElements()) {
308			String key = e.nextElement();
309			IActuator ac = (IActuator) mDevices.get(key);
310			if (ac instanceof VdrDevice) {
311				vdr = (VdrDevice) ac;
312				break;
313			}
314		}
315		return vdr;
316	}
317	
318	public VdrDevice getVdr(long id) {
319		VdrDevice vdr = null;
320		Enumeration<String> e = mDevices.keys();
321		while (e.hasMoreElements()) {
322			String key = e.nextElement();
323			IActuator ac = (IActuator) mDevices.get(key);
324			if ((ac instanceof VdrDevice) && (((VdrDevice) ac).getId() == id)) {
325				vdr = (VdrDevice) ac;
326				break;
327			}
328		}
329		return vdr;
330	}
331	
332	public ArrayList<VdrDevice> getVdrs() {
333		ArrayList<VdrDevice> list = new ArrayList<VdrDevice>();
334		Enumeration<String> e = mDevices.keys();
335		while (e.hasMoreElements()) {
336			String key = e.nextElement();
337			IActuator ac = (IActuator) mDevices.get(key);
338			if (ac instanceof VdrDevice) {
339				list.add((VdrDevice) ac);
340			}
341		}
342		return list;
343	}
344	
345	public CharSequence[] getVdrNames() {
346		List<CharSequence> list = new LinkedList<CharSequence>();
347		Enumeration<String> e = mDevices.keys();
348		while (e.hasMoreElements()) {
349			String key = e.nextElement();
350			IActuator ac = (IActuator) mDevices.get(key);
351			if (ac instanceof VdrDevice) {
352				list.add(ac.getName());
353			}
354		}
355		return list.toArray(new CharSequence[list.size()]);
356	}
357	
358	public boolean hasPlugins() {
359		return (! mPlugins.isEmpty());
360	}
361	
362	private void init() {
363		mActivity = new ActivityDevice();
364
365		if (new File(macroConfig).exists()) {
366			MacroConfigParser parser = new MacroConfigParser(macroConfig);
367			ArrayList<Macro> macros = parser.parse();
368			if (macros == null) {
369				logger.error("Couldn't parse macro configuration: {}", parser.lastError);
370			} else {
371				for (Macro macro : macros) {
372					logger.debug("Macro: {}", macro.name);
373					for (String command : macro.commands)
374						logger.debug("  -> {}", command);
375					mMacros.put(macro.name, macro);
376				}
377			}
378		}
379		
380		loadPlugins();
381		initDevices();
382	}
383
384	public void initDevices() {
385		mDevices.clear();
386		Cursor cursor = getCursorForAllDevices();
387		while (cursor.moveToNext()) {
388			addDevice(cursor);
389		}
390		cursor.close();
391		dbClose();
392		
393		if (mOnDeviceConfigurationChangedListener != null)
394			mOnDeviceConfigurationChangedListener.onChange();
395	}
396	
397	private void initVolumeCommands(SharedPreferences sp) {
398		if (sp.getBoolean("volumeVDR", false)) {
399			mVolumeUpCommand = "VDR.vol_up";
400			mVolumeDownCommand = "VDR.vol_down";
401		} else {
402			long deviceId = Long.parseLong(sp.getString("volumeDevice", "-1"));
403			if (deviceId == -1) {
404				mVolumeUpCommand = "";
405				mVolumeDownCommand = "";
406			} else {
407				IDevice device = getDevice(deviceId);
408				if (device == null) {
409					mVolumeUpCommand = "";
410					mVolumeDownCommand = "";
411				} else {
412					mVolumeUpCommand = device.getName() + "." + sp.getString("volumeUp", "");
413					mVolumeDownCommand = device.getName() + "." + sp.getString("volumeDown", "");
414				}
415			}
416		}
417	}
418	
419	private void loadPlugins() {
420		FilenameFilter filter = new FilenameFilter() {
421
422			@Override
423			public boolean accept(File dir, String filename) {
424				return filename.endsWith(".jar");
425			}
426		};
427
428		if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
429			return;
430		
431		File filepath = new File(pluginDir);
432		if (!filepath.exists())
433			return;
434
435		File dexpath = new File(pluginDir + File.separatorChar + "dex");
436		if (!dexpath.exists())
437			dexpath.mkdir();
438
439		File files[] = filepath.listFiles(filter);
440		if (files != null) {
441			for (File file : files) {
442				if (file.isFile()) {
443					try {
444						String className = null;
445						JarFile jar = new JarFile(file);
446						ZipEntry zipentry = jar
447								.getEntry("META-INF/services/de.androvdr.devices.IActuator");
448						if (zipentry != null) {
449							InputStream is = jar.getInputStream(zipentry);
450							BufferedReader br = new BufferedReader(
451									new InputStreamReader(is), 8192);
452							className = br.readLine();
453							br.close();
454						}
455						ClassLoader loader = new DexClassLoader(file
456								.getAbsolutePath(), dexpath.getAbsolutePath(),
457								null, getClass().getClassLoader());
458						Class<?> c = loader.loadClass(className);
459						if (IDevice.class.isAssignableFrom(c)) {
460							IDevice ac = (IDevice) c.newInstance();
461							mPlugins.put(ac.getDisplayClassName(), c);
462							logger.debug("Plugin: {}", 
463									(ac.getDisplayClassName() + " (" + c.getName() + ")"));
464						}
465					} catch (Exception e) {
466						e.printStackTrace();
467					}
468				}
469			}
470		}
471	}
472
473	@Override
474	public void onChannelChanged() {
475		if (mChannelSensorJob != null) {
476			mReceiveThread.updateChannel(mChannelSensorJob);
477		}
478	}
479
480	@Override
481	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
482		boolean isVolumePref = false;
483		for (CharSequence s : volumePrefNames)
484			if (s.equals(key)) {
485				isVolumePref = true;
486				break;
487			}
488		if (key.equals("volumeVDR") || isVolumePref)
489			initVolumeCommands(sharedPreferences);
490		if (key.equals("currentVdrId")) {
491			VdrDevice vdr = Preferences.getVdr();
492			if (vdr != null)
493				vdr.addChannelChangedListener(this);
494		}
495	}
496
497	public void onPause() {
498		Enumeration<String> e = mDevices.keys();
499		while (e.hasMoreElements()) {
500			String key = e.nextElement();
501			mDevices.get(key).disconnect();
502		}
503	}
504
505	public void onResume() {
506		// --- next full minute --
507		long now = new Date().getTime();
508		long delay = (now / 1000 / 60 + 1) * 60000 - now;
509
510		// --- run SensorUpdate every minute ---
511		mSensorUpdateTimer = new Timer();
512		mSensorUpdateTimer.schedule(new SensorUpdater(), delay + 2000, 60000);
513		logger.trace("Sensor update timer started");
514	}
515	
516	public void send(String command) {
517		logger.debug("send: {}", command);
518		String sa[] = command.split("\\.");
519		if (sa.length > 1 && sa[0].equals("Macro")) {
520			Macro macro = mMacros.get(sa[1]);
521			if (macro != null)
522				new MacroThread(macro);
523			else {
524				sendErrorMessage("Macro " + sa[1] + " not found");
525			}
526		} else if (sa.length > 1 && sa[0].equals("Activity")) {
527			String result = null;
528			if (mActivity != null) {
529				StringBuilder sb = new StringBuilder();
530				for (int i = 1; i < sa.length; i++) {
531					if (i > 1)
532						sb.append(".");
533					sb.append(sa[i]);
534				}
535				if (!mActivity.write(sb.toString()))
536					result = mActivity.getLastError();
537				if (result != null)
538					sendErrorMessage(result);
539			}
540		} else {
541			mSendThread.send(command);
542		}
543	}
544
545	private void sendErrorMessage(String msg) {
546		if (mResultHandler != null) {
547			Bundle resultBundle = new Bundle();
548			resultBundle.putString(MSG_RESULT, msg);
549			Message resultMessage = new Message();
550			resultMessage.setData(resultBundle);
551			mResultHandler.sendMessage(resultMessage);
552		}
553	}
554
555	public void setOnDeviceConfigurationChangedListener(OnChangeListener listener) {
556		mOnDeviceConfigurationChangedListener = listener;
557	}
558
559	public void setParentActivity(Activity activity) {
560		mActivity.setParentActivity(activity);
561	}
562
563	public void setResultHandler(Handler resultHandler) {
564		mResultHandler = resultHandler;
565	}
566
567	public void startSensorUpdater(int delaySeconds) {
568		if (mSensorJobs.size() > 0 && mSensorUpdateTimer == null) {
569			new Timer().schedule(new SensorUpdater(), delaySeconds * 1000);
570			
571			mSensorUpdateTimer = new Timer();
572			long now = new Date().getTime();
573			long delay = (now / 60000 + 1) * 60000 - now;
574			mSensorUpdateTimer.schedule(new SensorUpdater(), delay + 2000, 60000);
575			logger.trace("Sensor update timer started (delay = {} sec)", delay / 1000 + 2 );
576		}
577	}
578	
579	public void stopSensorUpdater() {
580		if (mSensorUpdateTimer != null) {
581			mSensorUpdateTimer.cancel();
582			mSensorUpdateTimer = null;
583			logger.trace("Sensor update timer stopped");
584		}
585	}
586
587	public void updateChannelSensor() {
588		if (mChannelSensorJob != null)
589			mReceiveThread.updateChannel(mChannelSensorJob);
590	}
591	
592	public boolean volumeControl() {
593		return (mVolumeDownCommand.length() > 0) || (mVolumeUpCommand.length() > 0);
594	}
595	
596	public void volumeDown() {
597		if (mVolumeDownCommand.length() > 0)
598			send(mVolumeDownCommand);
599	}
600	
601	public void volumeUp() {
602		if (mVolumeUpCommand.length() > 0)
603			send(mVolumeUpCommand);
604	}
605	
606	private class DeviceSendThread extends Thread {
607		private Handler mHandler;
608
609		public void run() {
610			Looper.prepare();
611			mHandler = new Handler() {
612				@Override
613				public void handleMessage(Message msg) {
614					Bundle bundle = msg.getData();
615					if (bundle != null) {
616						String command = bundle.getString("command");
617						String[] sa = command.split("\\.");
618						IActuator ac = null;
619						if (sa[0].equals(VDR_CLASSNAME))
620							ac = Preferences.getVdr();
621						else {
622							ac = mDevices.get(sa[0]);
623						}
624						String result = null;
625						if (ac != null && sa.length > 1) {
626							if (!ac.write(sa[1])) {
627								result = ac.getLastError();
628							}
629						} else {
630							result = "Error in command: " + command;
631						}
632						if (result != null)
633							sendErrorMessage(result);
634					}
635				}
636			};
637			logger.trace("DeviceSendThread started");
638			Looper.loop();
639		}
640
641		public void send(String command) {
642			Bundle bundle = new Bundle();
643			bundle.putString("command", command);
644			Message msg = new Message();
645			msg.setData(bundle);
646			mHandler.sendMessage(msg);
647		}
648	}
649	
650	private class MacroThread extends Thread {
651		private final Macro mMacro;
652
653		public MacroThread(Macro macro) {
654			mMacro = macro;
655			start();
656		}
657
658		public void run() {
659			for (String command : mMacro.commands) {
660				String[] sa = command.split("\\.");
661				if (sa.length > 1 && sa[0].equalsIgnoreCase("Sleep")) {
662					try {
663						int microSecond = Integer.parseInt(sa[1]);
664						Thread.sleep(microSecond);
665					} catch (NumberFormatException e) {
666						logger.error("Invalid sleep value");
667					} catch (InterruptedException e) {
668						logger.trace("MacroThread interrupted");
669					}
670				} else {
671					send(command);
672				}
673			}
674		}
675	}
676
677	private class SensorJob {
678		public String command;
679		public int interval;
680		public long lastReceiveTime = 0;
681		public OnSensorChangeListener listener;
682		
683		public SensorJob(String command, int interval, OnSensorChangeListener listener) {
684			this.command = command;
685			this.interval = interval;
686			this.listener = listener;
687		}
688	}
689	
690	private class SensorReceiveThread extends Thread {
691		private Handler mHandler;
692		private Boolean isUpdating = false;
693		
694		public void run() {
695			Looper.prepare();
696			mHandler = new Handler() {
697				@Override
698				public void handleMessage(Message msg) {
699					SensorJob job = (SensorJob) msg.obj;
700					logger.trace("SensorReceiveThread: {}", job.command);
701					ISensor sensor = null;
702					String[] sa = job.command.split("\\.");
703					if (sa[0].equals(VDR_CLASSNAME))
704						sensor = Preferences.getVdr();
705					else {
706						sensor = mSensors.get(sa[0]);
707					}
708					String result = null;
709					if (sensor != null && sa.length > 1) {
710						result = sensor.read(sa[1]);
711						if (result != null) {
712							job.listener.onChange(result);
713						} else {
714							logger.error("SensorReceiveThread: {}", sensor.getLastError());
715							job.listener.onChange("N/A");
716						}
717					} else {
718						logger.error("SensorReceiveThread: Error in command: {}", job.command);
719					}
720					job.lastReceiveTime = new Date().getTime() / 60000;
721				}
722			};
723			logger.trace("SensorReceiveThread started");
724			Looper.loop();
725		}
726		
727		public void receive(SensorJob job) {
728			Message msg = new Message();
729			msg.obj = job;
730			mHandler.sendMessage(msg);
731		}
732		
733		public void updateChannel(final SensorJob job) {
734			synchronized (isUpdating) {
735				if (! isUpdating) {
736					TimerTask tt = new TimerTask() {
737						@Override
738						public void run() {
739							synchronized (isUpdating) {
740								mReceiveThread.receive(job);
741								isUpdating = false;
742							}
743						}
744					};
745					isUpdating = true;
746					new Timer().schedule(tt, 2000);
747				}
748			}
749		}
750	}
751
752	private class SensorUpdater extends TimerTask {
753		
754		@Override
755		public void run() {
756			long timeInMinutes = new Date().getTime() / 60000;
757			logger.trace("SensorUpdater: {}", new SimpleDateFormat("HH:mm:ss").format(new Date()));
758			synchronized (mSensorJobs) {
759				for (SensorJob job : mSensorJobs) {
760					if (job.lastReceiveTime + job.interval <= timeInMinutes)
761						mReceiveThread.receive(job);
762				}
763			}
764		}
765	}
766}